From 1745e5cff9f97c3b7fc35a5bf443bb09466738b2 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 18 Jul 2018 16:37:44 +0200 Subject: [PATCH 001/119] Small objects can now fit inside free space surrounded by objects. --- xs/src/libnest2d/examples/main.cpp | 22 +- .../libnest2d/geometry_traits_nfp.hpp | 56 ++++- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 227 ++++++++++++------ .../libnest2d/selections/firstfit.hpp | 4 +- xs/src/libslic3r/Model.cpp | 12 +- 5 files changed, 238 insertions(+), 83 deletions(-) diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index 4623a6add7..e5a47161eb 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -519,20 +519,28 @@ void arrangeRectangles() { std::vector proba = { { - { {0, 0}, {20, 20}, {40, 0}, {0, 0} } + Rectangle(100, 2) }, { - { {0, 100}, {50, 60}, {100, 100}, {50, 0}, {0, 100} } - + Rectangle(100, 2) + }, + { + Rectangle(100, 2) + }, + { + Rectangle(10, 10) }, }; + proba[0].rotate(Pi/3); + proba[1].rotate(Pi-Pi/3); + std::vector input; input.insert(input.end(), prusaParts().begin(), prusaParts().end()); // input.insert(input.end(), prusaExParts().begin(), prusaExParts().end()); -// input.insert(input.end(), stegoParts().begin(), stegoParts().end()); + input.insert(input.end(), stegoParts().begin(), stegoParts().end()); // input.insert(input.end(), rects.begin(), rects.end()); -// input.insert(input.end(), proba.begin(), proba.end()); + input.insert(input.end(), proba.begin(), proba.end()); // input.insert(input.end(), crasher.begin(), crasher.end()); Box bin(250*SCALE, 210*SCALE); @@ -569,9 +577,9 @@ void arrangeRectangles() { Packer::SelectionConfig sconf; // sconf.allow_parallel = false; // sconf.force_parallel = false; -// sconf.try_triplets = true; +// sconf.try_triplets = false; // sconf.try_reverse_order = true; -// sconf.waste_increment = 0.1; +// sconf.waste_increment = 0.005; arrange.configure(pconf, sconf); diff --git a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp index 56e8527b4c..581b6bed08 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp @@ -25,9 +25,60 @@ using Shapes = typename ShapeLike::Shapes; /// Minkowski addition (not used yet) template -static RawShape minkowskiDiff(const RawShape& sh, const RawShape& /*other*/) +static RawShape minkowskiDiff(const RawShape& sh, const RawShape& cother) { + using Vertex = TPoint; + //using Coord = TCoord; + using Edge = _Segment; + using sl = ShapeLike; + using std::signbit; + // Copy the orbiter (controur only), we will have to work on it + RawShape orbiter = sl::create(sl::getContour(cother)); + + // Make the orbiter reverse oriented + for(auto &v : sl::getContour(orbiter)) v = -v; + + // An egde with additional data for marking it + struct MarkedEdge { Edge e; Radians turn_angle; bool is_turning_point; }; + + // Container for marked edges + using EdgeList = std::vector; + + EdgeList A, B; + + auto fillEdgeList = [](EdgeList& L, const RawShape& poly) { + L.reserve(sl::contourVertexCount(poly)); + + auto it = sl::cbegin(poly); + auto nextit = std::next(it); + + L.emplace_back({Edge(*it, *nextit), 0, false}); + it++; nextit++; + + while(nextit != sl::cend(poly)) { + Edge e(*it, *nextit); + auto& L_prev = L.back(); + auto phi = L_prev.e.angleToXaxis(); + auto phi_prev = e.angleToXaxis(); + auto turn_angle = phi-phi_prev; + if(turn_angle > Pi) turn_angle -= 2*Pi; + L.emplace_back({ + e, + turn_angle, + signbit(turn_angle) != signbit(L_prev.turn_angle) + }); + it++; nextit++; + } + + L.front().turn_angle = L.front().e.angleToXaxis() - + L.back().e.angleToXaxis(); + + if(L.front().turn_angle > Pi) L.front().turn_angle -= 2*Pi; + }; + + fillEdgeList(A, sh); + fillEdgeList(B, orbiter); return sh; } @@ -193,6 +244,9 @@ static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother) // Lindmark's reasoning about the reference vertex of nfp in his thesis // ("No fit polygon problem" - section 2.1.9) + // TODO: dont do this here. Cache the rmu and lmd in Item and get translate + // the nfp after this call + auto csh = sh; // Copy sh, we will sort the verices in the copy auto& cmp = _vsort; std::sort(ShapeLike::begin(csh), ShapeLike::end(csh), cmp); diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index d6bd154dba..89848eb538 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -48,32 +48,89 @@ template class EdgeCache { using Coord = TCoord; using Edge = _Segment; - mutable std::vector corners_; + struct ContourCache { + mutable std::vector corners; + std::vector emap; + std::vector distances; + double full_distance = 0; + } contour_; - std::vector emap_; - std::vector distances_; - double full_distance_ = 0; + std::vector holes_; void createCache(const RawShape& sh) { - auto first = ShapeLike::cbegin(sh); - auto next = first + 1; - auto endit = ShapeLike::cend(sh); + { // For the contour + auto first = ShapeLike::cbegin(sh); + auto next = std::next(first); + auto endit = ShapeLike::cend(sh); - distances_.reserve(ShapeLike::contourVertexCount(sh)); + contour_.distances.reserve(ShapeLike::contourVertexCount(sh)); - while(next != endit) { - emap_.emplace_back(*(first++), *(next++)); - full_distance_ += emap_.back().length(); - distances_.push_back(full_distance_); + while(next != endit) { + contour_.emap.emplace_back(*(first++), *(next++)); + contour_.full_distance += contour_.emap.back().length(); + contour_.distances.push_back(contour_.full_distance); + } + } + + for(auto& h : ShapeLike::holes(sh)) { // For the holes + auto first = h.begin(); + auto next = std::next(first); + auto endit = h.end(); + + ContourCache hc; + hc.distances.reserve(endit - first); + + while(next != endit) { + hc.emap.emplace_back(*(first++), *(next++)); + hc.full_distance += hc.emap.back().length(); + hc.distances.push_back(hc.full_distance); + } + + holes_.push_back(hc); } } void fetchCorners() const { - if(!corners_.empty()) return; + if(!contour_.corners.empty()) return; // TODO Accuracy - corners_ = distances_; - for(auto& d : corners_) d /= full_distance_; + contour_.corners = contour_.distances; + for(auto& d : contour_.corners) d /= contour_.full_distance; + } + + void fetchHoleCorners(unsigned hidx) const { + auto& hc = holes_[hidx]; + if(!hc.corners.empty()) return; + + // TODO Accuracy + hc.corners = hc.distances; + for(auto& d : hc.corners) d /= hc.full_distance; + } + + inline Vertex coords(const ContourCache& cache, double distance) const { + assert(distance >= .0 && distance <= 1.0); + + // distance is from 0.0 to 1.0, we scale it up to the full length of + // the circumference + double d = distance*cache.full_distance; + + auto& distances = cache.distances; + + // Magic: we find the right edge in log time + auto it = std::lower_bound(distances.begin(), distances.end(), d); + auto idx = it - distances.begin(); // get the index of the edge + auto edge = cache.emap[idx]; // extrac the edge + + // Get the remaining distance on the target edge + auto ed = d - (idx > 0 ? *std::prev(it) : 0 ); + auto angle = edge.angleToXaxis(); + Vertex ret = edge.first(); + + // Get the point on the edge which lies in ed distance from the start + ret += { static_cast(std::round(ed*std::cos(angle))), + static_cast(std::round(ed*std::sin(angle))) }; + + return ret; } public: @@ -102,37 +159,36 @@ public: * @return Returns the coordinates of the point lying on the polygon * circumference. */ - inline Vertex coords(double distance) { - assert(distance >= .0 && distance <= 1.0); - - // distance is from 0.0 to 1.0, we scale it up to the full length of - // the circumference - double d = distance*full_distance_; - - // Magic: we find the right edge in log time - auto it = std::lower_bound(distances_.begin(), distances_.end(), d); - auto idx = it - distances_.begin(); // get the index of the edge - auto edge = emap_[idx]; // extrac the edge - - // Get the remaining distance on the target edge - auto ed = d - (idx > 0 ? *std::prev(it) : 0 ); - auto angle = edge.angleToXaxis(); - Vertex ret = edge.first(); - - // Get the point on the edge which lies in ed distance from the start - ret += { static_cast(std::round(ed*std::cos(angle))), - static_cast(std::round(ed*std::sin(angle))) }; - - return ret; + inline Vertex coords(double distance) const { + return coords(contour_, distance); } - inline double circumference() const BP2D_NOEXCEPT { return full_distance_; } + inline Vertex coords(unsigned hidx, double distance) const { + assert(hidx < holes_.size()); + return coords(holes_[hidx], distance); + } + + inline double circumference() const BP2D_NOEXCEPT { + return contour_.full_distance; + } + + inline double circumference(unsigned hidx) const BP2D_NOEXCEPT { + return holes_[hidx].full_distance; + } inline const std::vector& corners() const BP2D_NOEXCEPT { fetchCorners(); - return corners_; + return contour_.corners; } + inline const std::vector& + corners(unsigned holeidx) const BP2D_NOEXCEPT { + fetchHoleCorners(holeidx); + return holes_[holeidx].corners; + } + + inline unsigned holeCount() const BP2D_NOEXCEPT { return holes_.size(); } + }; template @@ -294,12 +350,20 @@ public: for(auto& nfp : nfps ) ecache.emplace_back(nfp); - auto getNfpPoint = [&ecache](double relpos) { - auto relpfloor = std::floor(relpos); - auto nfp_idx = static_cast(relpfloor); - if(nfp_idx >= ecache.size()) nfp_idx--; - auto p = relpos - relpfloor; - return ecache[nfp_idx].coords(p); + struct Optimum { + double relpos; + unsigned nfpidx; + int hidx; + Optimum(double pos, unsigned nidx): + relpos(pos), nfpidx(nidx), hidx(-1) {} + Optimum(double pos, unsigned nidx, int holeidx): + relpos(pos), nfpidx(nidx), hidx(holeidx) {} + }; + + auto getNfpPoint = [&ecache](const Optimum& opt) + { + return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) : + ecache[opt.nfpidx].coords(opt.nfpidx, opt.relpos); }; Nfp::Shapes pile; @@ -310,6 +374,8 @@ public: pile_area += mitem.area(); } + // This is the kernel part of the object function that is + // customizable by the library client auto _objfunc = config_.object_function? config_.object_function : [this](const Nfp::Shapes& pile, double occupied_area, @@ -334,9 +400,8 @@ public: }; // Our object function for placement - auto objfunc = [&] (double relpos) + auto rawobjfunc = [&] (Vertex v) { - Vertex v = getNfpPoint(relpos); auto d = v - iv; d += startpos; item.translation(d); @@ -359,46 +424,74 @@ public: stopcr.type = opt::StopLimitType::RELATIVE; opt::TOptimizer solver(stopcr); - double optimum = 0; + Optimum optimum(0, 0); double best_score = penality_; - // double max_bound = 1.0*nfps.size(); - // Genetic should look like this: - /*auto result = solver.optimize_min(objfunc, - opt::initvals(0.0), - opt::bound(0.0, max_bound) - ); - - if(result.score < penality_) { - best_score = result.score; - optimum = std::get<0>(result.optimum); - }*/ - // Local optimization with the four polygon corners as // starting points for(unsigned ch = 0; ch < ecache.size(); ch++) { auto& cache = ecache[ch]; + auto contour_ofn = [&rawobjfunc, &getNfpPoint, ch] + (double relpos) + { + return rawobjfunc(getNfpPoint(Optimum(relpos, ch))); + }; + std::for_each(cache.corners().begin(), cache.corners().end(), - [ch, &solver, &objfunc, - &best_score, &optimum] - (double pos) + [ch, &contour_ofn, &solver, &best_score, + &optimum] (double pos) { try { - auto result = solver.optimize_min(objfunc, - opt::initvals(ch+pos), - opt::bound(ch, 1.0 + ch) + auto result = solver.optimize_min(contour_ofn, + opt::initvals(pos), + opt::bound(0, 1.0) ); if(result.score < best_score) { best_score = result.score; - optimum = std::get<0>(result.optimum); + optimum.relpos = std::get<0>(result.optimum); + optimum.nfpidx = ch; + optimum.hidx = -1; } } catch(std::exception& e) { derr() << "ERROR: " << e.what() << "\n"; } }); + + for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) { + auto hole_ofn = + [&rawobjfunc, &getNfpPoint, ch, hidx] + (double pos) + { + Optimum opt(pos, ch, hidx); + return rawobjfunc(getNfpPoint(opt)); + }; + + std::for_each(cache.corners(hidx).begin(), + cache.corners(hidx).end(), + [&hole_ofn, &solver, &best_score, + &optimum, ch, hidx] + (double pos) + { + try { + auto result = solver.optimize_min(hole_ofn, + opt::initvals(pos), + opt::bound(0, 1.0) + ); + + if(result.score < best_score) { + best_score = result.score; + Optimum o(std::get<0>(result.optimum), + ch, hidx); + optimum = o; + } + } catch(std::exception& e) { + derr() << "ERROR: " << e.what() << "\n"; + } + }); + } } if( best_score < global_score ) { diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index 2253a0dfee..b6e80520c9 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -56,7 +56,7 @@ public: }; // Safety test: try to pack each item into an empty bin. If it fails - // then it should be removed from the not_packed list + // then it should be removed from the list { auto it = store_.begin(); while (it != store_.end()) { Placer p(bin); @@ -72,7 +72,7 @@ public: while(!was_packed) { for(size_t j = 0; j < placers.size() && !was_packed; j++) { - if(was_packed = placers[j].pack(item)) + if((was_packed = placers[j].pack(item))) makeProgress(placers[j], j); } diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 2925251eb4..b2e439e5d4 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -530,12 +530,12 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // arranger.useMinimumBoundigBoxRotation(); pcfg.rotations = { 0.0 }; - // Magic: we will specify what is the goal of arrangement... - // In this case we override the default object to make the larger items go - // into the center of the pile and smaller items orbit it so the resulting - // pile has a circle-like shape. This is good for the print bed's heat - // profile. We alse sacrafice a bit of pack efficiency for this to work. As - // a side effect, the arrange procedure is a lot faster (we do not need to + // Magic: we will specify what is the goal of arrangement... In this case + // we override the default object function to make the larger items go into + // the center of the pile and smaller items orbit it so the resulting pile + // has a circle-like shape. This is good for the print bed's heat profile. + // We alse sacrafice a bit of pack efficiency for this to work. As a side + // effect, the arrange procedure is a lot faster (we do not need to // calculate the convex hulls) pcfg.object_function = [bin, hasbin]( NfpPlacer::Pile pile, // The currently arranged pile From 86caf83721870c41425e27e9165d38ba05381627 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 23 Jul 2018 08:57:00 +0200 Subject: [PATCH 002/119] Update objects' list after scaling using gizmo --- lib/Slic3r/GUI/Plater.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 4a56ce632b..22b887bafb 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -127,6 +127,8 @@ sub new { $range->[1] *= $variation; } $_->set_scaling_factor($scale) for @{ $model_object->instances }; + + $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); $object->transform_thumbnail($self->{model}, $obj_idx); #update print and start background processing From ee4f2cf549cc6e32b3b4d9ef7bf7e9f9d055f910 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 23 Jul 2018 09:59:04 +0200 Subject: [PATCH 003/119] Try to fix #977 --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 064a9adce9..94f6f0441e 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -3379,7 +3379,13 @@ void GLCanvas3D::on_key_down(wxKeyEvent& evt) if (key == WXK_DELETE) m_on_remove_object_callback.call(); else - evt.Skip(); + { +#ifdef __WXOSX__ + if (key == WXK_BACK) + m_on_remove_object_callback.call(); +#endif + evt.Skip(); + } } } From df36de0d355672b83c4bad3f0997f5defeb2ec9c Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 23 Jul 2018 10:16:56 +0200 Subject: [PATCH 004/119] Fixed status of Slice now and Export G-Code buttons after object import --- lib/Slic3r/GUI/Plater.pm | 3 ++- xs/src/slic3r/GUI/3DScene.cpp | 2 +- xs/src/slic3r/GUI/3DScene.hpp | 2 +- xs/src/slic3r/GUI/GLCanvas3D.cpp | 6 ++++-- xs/src/slic3r/GUI/GLCanvas3D.hpp | 2 +- xs/src/slic3r/GUI/GLCanvas3DManager.cpp | 2 +- xs/src/slic3r/GUI/GLCanvas3DManager.hpp | 2 +- xs/xsp/GUI_3DScene.xsp | 2 +- 8 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 22b887bafb..1c590a7f72 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -2102,7 +2102,8 @@ sub object_list_changed { my $export_in_progress = $self->{export_gcode_output_file} || $self->{send_gcode_file}; my $model_fits = $self->{canvas3D} ? Slic3r::GUI::_3DScene::check_volumes_outside_state($self->{canvas3D}, $self->{config}) : 1; - my $method = ($have_objects && ! $export_in_progress && $model_fits) ? 'Enable' : 'Disable'; + # $model_fits == 1 -> ModelInstance::PVS_Partly_Outside + my $method = ($have_objects && ! $export_in_progress && ($model_fits != 1)) ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method for grep $self->{"btn_$_"}, qw(reslice export_gcode print send_gcode); } diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 2ffd788eb2..62659033ad 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -1651,7 +1651,7 @@ void _3DScene::update_volumes_selection(wxGLCanvas* canvas, const std::vector& selections); - static bool check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config); + static int check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config); static bool move_volume_up(wxGLCanvas* canvas, unsigned int id); static bool move_volume_down(wxGLCanvas* canvas, unsigned int id); diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 064a9adce9..722f1c1124 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1878,9 +1878,11 @@ void GLCanvas3D::update_volumes_selection(const std::vector& selections) } } -bool GLCanvas3D::check_volumes_outside_state(const DynamicPrintConfig* config) const +int GLCanvas3D::check_volumes_outside_state(const DynamicPrintConfig* config) const { - return m_volumes.check_outside_state(config, nullptr); + ModelInstance::EPrintVolumeState state; + m_volumes.check_outside_state(config, &state); + return (int)state; } bool GLCanvas3D::move_volume_up(unsigned int id) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index d18ca0cabe..a9a6117ecc 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -495,7 +495,7 @@ public: void deselect_volumes(); void select_volume(unsigned int id); void update_volumes_selection(const std::vector& selections); - bool check_volumes_outside_state(const DynamicPrintConfig* config) const; + int check_volumes_outside_state(const DynamicPrintConfig* config) const; bool move_volume_up(unsigned int id); bool move_volume_down(unsigned int id); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp index 23a8f4c15c..5e9048d541 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -237,7 +237,7 @@ void GLCanvas3DManager::update_volumes_selection(wxGLCanvas* canvas, const std:: it->second->update_volumes_selection(selections); } -bool GLCanvas3DManager::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const +int GLCanvas3DManager::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const { CanvasesMap::const_iterator it = _get_canvas(canvas); return (it != m_canvases.end()) ? it->second->check_volumes_outside_state(config) : false; diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp index 98205982fb..32aa712d35 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -75,7 +75,7 @@ public: void deselect_volumes(wxGLCanvas* canvas); void select_volume(wxGLCanvas* canvas, unsigned int id); void update_volumes_selection(wxGLCanvas* canvas, const std::vector& selections); - bool check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const; + int check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const; bool move_volume_up(wxGLCanvas* canvas, unsigned int id); bool move_volume_down(wxGLCanvas* canvas, unsigned int id); diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 38c85c3283..5c2f7df854 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -230,7 +230,7 @@ update_volumes_selection(canvas, selections) CODE: _3DScene::update_volumes_selection((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), selections); -bool +int check_volumes_outside_state(canvas, config) SV *canvas; DynamicPrintConfig *config; From 33175a02f37b588654ce7fe294b38863072e9aa5 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 23 Jul 2018 10:58:39 +0200 Subject: [PATCH 005/119] Added xml escape characters detection when exporting object and columes names to amf files --- xs/src/libslic3r/Format/AMF.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp index 21d4b4d3b7..be513166e5 100644 --- a/xs/src/libslic3r/Format/AMF.cpp +++ b/xs/src/libslic3r/Format/AMF.cpp @@ -761,7 +761,7 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c for (const std::string &key : object->config.keys()) stream << " " << object->config.serialize(key) << "\n"; if (!object->name.empty()) - stream << " " << object->name << "\n"; + stream << " " << xml_escape(object->name) << "\n"; std::vector layer_height_profile = object->layer_height_profile_valid ? object->layer_height_profile : std::vector(); if (layer_height_profile.size() >= 4 && (layer_height_profile.size() % 2) == 0) { // Store the layer height profile as a single semicolon separated list. @@ -805,7 +805,7 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c for (const std::string &key : volume->config.keys()) stream << " " << volume->config.serialize(key) << "\n"; if (!volume->name.empty()) - stream << " " << volume->name << "\n"; + stream << " " << xml_escape(volume->name) << "\n"; if (volume->modifier) stream << " 1\n"; for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) { From 40b55a3818fa52158616944daaa1d0e04c786f40 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 23 Jul 2018 11:33:13 +0200 Subject: [PATCH 006/119] Allow delete button in object list --- lib/Slic3r/GUI/Plater.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 4a56ce632b..44b5d94a98 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -310,6 +310,9 @@ sub new { my ($list, $event) = @_; if ($event->GetKeyCode == WXK_TAB) { $list->Navigate($event->ShiftDown ? &Wx::wxNavigateBackward : &Wx::wxNavigateForward); + } elsif ($event->GetKeyCode == WXK_DELETE || + ($event->GetKeyCode == WXK_BACK && &Wx::wxMAC) ) { + $self->remove; } else { $event->Skip; } From 0bd8affab9f7dce5dd1a73bc4390b88d8507a9c9 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 23 Jul 2018 13:54:43 +0200 Subject: [PATCH 007/119] Attempt to fix #1067 --- xs/src/slic3r/GUI/GLTexture.cpp | 9 ++++++--- xs/src/slic3r/GUI/GLTexture.hpp | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/xs/src/slic3r/GUI/GLTexture.cpp b/xs/src/slic3r/GUI/GLTexture.cpp index 2af555707f..18c9f5dea0 100644 --- a/xs/src/slic3r/GUI/GLTexture.cpp +++ b/xs/src/slic3r/GUI/GLTexture.cpp @@ -79,7 +79,8 @@ bool GLTexture::load_from_file(const std::string& filename, bool generate_mipmap if (generate_mipmaps) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards - _generate_mipmaps(image); + unsigned int levels_count = _generate_mipmaps(image); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1 + levels_count); ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); } else @@ -149,14 +150,14 @@ void GLTexture::render_texture(unsigned int tex_id, float left, float right, flo ::glDisable(GL_BLEND); } -void GLTexture::_generate_mipmaps(wxImage& image) +unsigned int GLTexture::_generate_mipmaps(wxImage& image) { int w = image.GetWidth(); int h = image.GetHeight(); GLint level = 0; std::vector data(w * h * 4, 0); - while ((w > 1) && (h > 1)) + while ((w > 1) || (h > 1)) { ++level; @@ -183,6 +184,8 @@ void GLTexture::_generate_mipmaps(wxImage& image) ::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); } + + return (unsigned int)level; } } // namespace GUI diff --git a/xs/src/slic3r/GUI/GLTexture.hpp b/xs/src/slic3r/GUI/GLTexture.hpp index 2e936161e3..3113fcab20 100644 --- a/xs/src/slic3r/GUI/GLTexture.hpp +++ b/xs/src/slic3r/GUI/GLTexture.hpp @@ -32,7 +32,7 @@ namespace GUI { static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); protected: - void _generate_mipmaps(wxImage& image); + unsigned int _generate_mipmaps(wxImage& image); }; } // namespace GUI From de085fdb6c2b575d2fa4f2ea84df7e3fa7cee5e5 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 23 Jul 2018 14:21:14 +0200 Subject: [PATCH 008/119] Power of two squared bed textures to try to fix #1067 --- resources/icons/bed/mk2_top.png | Bin 102340 -> 33621 bytes resources/icons/bed/mk3_top.png | Bin 85263 -> 190302 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/icons/bed/mk2_top.png b/resources/icons/bed/mk2_top.png index 142050c3a26328237307af611062de60bf1fe3ab..cab4b966f86544dd5fa1f4736ee0052056810873 100644 GIT binary patch literal 33621 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 literal 102340 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 Zf>v$#So~4p4D%+_LC1hof!Z~DRXUj1% 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

yUt7LI_ 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�{gNZn0IrO5y8xTtTq~u}RzgN8 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�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≧eD;nEh|q= zXp>3e0<$mMkG;vkfVp>UEM|_2PvzLN16bTXobnO*p*@^j`#{Z$pb+-kU|ah}uM3KK zc0jMV_AwMv0(h%|fdLqp+@E%~)3TxF;jf%Q`48-gT(@d_q+|IJz(@v|Q8%YJ)5mH% z4*=l+>}0{9^UAYV-CF_J6=+1Swl<+nC4+Opu4&zHd;K2;!YoO&cpt=N$}}HA$PmJP zm|Ous5qedOORC;|A@q79l~1{RNLkiMNx~6-k%$ zs$qI31E1aQwy*+`tBmI2JM z=ipTUvF$(k$B?hZ47=(b=u@C-8M5nk+XB)0F|21iU`57)Nck+WoT}3>vzXlIB*2MT=%H z^(J2pcsK>+8+9?hp_ctlv~=jc2~`Ro zfd-n`?Bsf*m@IlLj0F`gfhiU$0U7ua8?Wl6J>#Ro)X!_-k$0GdJ3~2 ziRZcHVW5KpIyn$)KL+ncvvni?JIDHmUDm4=k?$!;c6V+3&*Sd*#~%H^oA}ZX;F($d zxZ!{P_TS~(k-<3o2rP>5G~)mwU|MaAq41zOuju((aehz`FVUp;licUXoBRY^JSe>8 z+rRs7)8Bt?xJ`<0dB?0DJeq-62Iap9|9ia5V0F>@b5PZ?9ujz;7C1acm(d92-8}CH zV*dME^UsG&Osy}zkmElQHAzKO*QRGU*B=B*r-0M>RnJ|RrwPir)4N^cr=P}sX+4jY zRC)Wir; z#zJm|70}`J+(q{U)dXBuNWiopFTdB;2Hy;QWf~=o!xJVeEzTRhy*Qg|EiMpNYqB#E zNx}SsX;? zR{4(49=KI>+gHu3%ZS%+ikI!{y^}xom==_ku4``%)A!R6xzjAs2=-YnJ<(>~oU7|5 zJERqkF{1;Jh2>^u*Ogy)1u{mnoOUWSE6Yp5#4Zy8$EyEd{9E9$GT2vOd8)? zj%@DHf~f!cUij;~=DfU>m5GL(N%>a|walog0cZEaW@;J4`Rn<*c#oLiYd=WlA5wi3 z9HL3n(o-JvCLW^g&n0WHPUvrAVDS-!Og^?sq-rx1QrFcXHS-aWa z1QmEGo7xgm6Or3ObZMkM-#~Z( znT!7%4U2whI+>mAr}B55!?Df*`=XFp869;=jPu85RntOK7IdYTik$pKD^h76Yu7+g zIP<7#`|^f>APY^uQI6b)9iDmRWUjWYIL~Dy_F}bewcFI^qnQtVRi&tw*N)?h1FCNG(3L?~CcG9A$P?))^9gT1Gw5 z95)Q#ax8Dr*bmuUUTj1!1#EK-LF5+*U08=HEOzJ6Wu)OU{<{aFUTy?}QM)9KG9Ez~X z-Y|uce}z$9GTk{$&@ec!X?ofUZTU=ou{D26kha-Euk!-Yz$A60We6OG@V^+h;Ul8j zH4O$qyismRo-#3x>n!65%`D6}im+KaFtk5heY%W{u@;-(D=Jo7`xweMhq+Dwx4*!c z&iW8__X3fVmEH`+*h7@wU4@*{Na3GSI1a=_vpy>|-Ns?xQa3-l_Bcti(>f#$5h8l| zXqL&XT6mv`J~Cf)j|7+nHE!gu9JnRsl(ip>ZXM;}*u zX=pPQQ8gBxx>$|s2ermJ$<~o?;v5ymRkHd(bHEjA67wO^JrW|OKDn$G_^ligEn)a^ z@dYb`F)E#i^;93OVkdLfR!Y|T-eTNbTPuU%x(0eM&r&pPlqhQZJG$ILIP66(Icvk( z;$;fv$^1~+^y_!({rJE<=^<#&pK2vZq0sYZNyt z@;B)`50^{>&q z5aK$8&%Dw;Rn==vRQvpBdhCARk*{5G!1dZXM&2fMulwR>Kq(D8qrFCy)8dic@{4k5 zZdpEf0rck0egW+bl=EPE@3*Qx+a`>q-%;tM4G~=+volBeW{QbszwV7+7#vN}J+3Fp zP*_4fFbGlI)9xsCQbOVBoG&W| zeTV?=ZAs-m9B(qlmeoWXgnO!JC9m@(eH;s_PF>w#Y*e4v;ZuTBL5fG^eQdk+s};-8 z$d?G3?z98+M2toXTI*I(Mha?ziG6uFbpknwrKaj{XmhxRGY_jktY<~_hfN_@=}xhR zXY^NBsCP^q4zJRjh%}QCn9lf_;Ah48WoWD+9rES5GXs(uFT_mNY-)1wFxVwkb>FON zTo)x&Ki?~H#aIgA?<#Z+ERe(gUVq^{ydDqAkRduX3dFdN&H zAB6V$-Lyz*OgFGtQ^S_qu(dn3^J8Ic*WbS~gh%{g=e>78oxa?Xj3JoSg_{r4S|%50 z(HV<_;R(KA^{pL{OlBw?5P!IY6qbIHqYXb@`GBZC`@VPu$&VxZ*?;~Ws#a%mUPD>; z$3lKy>oA&1MNN9Ri8rHEwO~R=!CXwF4GAo5YGhyK@3rk0v@+z<4KG6RB^PSt=22kY zM^J0QH1LV`bff23nqN*)i(o}-&!)sYZrA9Ld*3IU-8=X~%ogr59!pIz%sBV*y#zy- zGq9!Fw6l+^eV67N81;1ctEH`^G=OC{;92UZ=+u}@@1e_as9G?U7A2{SUuq$yi}~&Y z7m3YLw-*P3f!X{v?GhJli?G}e%|#F1xjWtrCwmD~C7h_rT^XG(^(SiZDYTVgu`kKI z2UUEGhyIG8Pgw*-nj2*3)^mi2&u#=~x@?i9yes|uoQmRZ88hCdoB*eQ8T#W7OY^cW zWQ*;wIWYyN**g=N5#lc^E~@CqMZav5_``nXwmZ^C-0DVG&E2%)`_8DRzhI;zgYS7f z1Z^bha|*80{TF}!G`X$u+zTH`Bac!2N$uiI!s~e2KNgO!K3p z%wn{DLO@J7yjmPc;q9}4=Z~9BvroCmCZKv#PM)u57$;tBSxr>zqJ-NUWphtZ^{uvw zYW|b%qRj_bm=MaLE3fxq=V9(U-ipjwdzFuBOMV-&(zCf#3A;}vs5H8B{Eu(U$tTN_w1+8=!UgRw)HjsLt0>rqWy_uwNXUQmwc zDCu{GB4PD|4zh9ORLcA#KZ`fcxc9tQD;BW+0rS@8gDB`6`(*;=iLGsWUcG0=4b}@GKgSsI>Gzaun zzx)r>hTsAkhAwIc-NBE7${Hw4N8#V3hnr6HzXKf!8p{HCH3RDx--qSq0&v%yy6*vh zWw9qPa7R4Nf>gRnKXpS+A1c-X!yo9pal1BM(?~h*8td!fWaCKN;&NKM@_)_qA;Yz9 z#|c5BXrPgef*bmI>;d&i0zcTkMzTTS>1Ah{tLtB`xma)*m)o_ElHmKPaP*?uGI1iN zUID0ro6xQbNM#H#48JQQ1v?}~yZ$|$Mf}&4%7mmg`jc9}LO*V(yv+EYrU%k$!|vUd zb+L&S?8>*{|JFcZ4EnnN;%(zrm*E>q`k{?(Ji~F(`7L0CaB;y21LJofNpZB|?Cq{H z_jUBB5OjR4KG%!@hiJ9_p!SL+|EeF@?4Vu4-k#NoZle1$U3TBr#x{)EsxTDuBJ}b1 z$#ziwe%R3HHxMtg0Y=pNdYq1ydZWahJRxPe@v*T^B<2mDV-~NIxI|FG$_0^@DBW78YhRk}d~k!K0~b z1+52kbphLa0spm*;ID^Be=|IAn)~{afPfteNbw1h+4_eK$YH-Y2P%*pMopZ3sHt=4 z$Oq6DDQ627Nr&fSra*gVpg;9pc0LycGpfkXc|ntO8;|1~pI#Yx!RGki=rTlTYiIWV z7OwCYad)S;rpGW&332!FI9FTpi2h&EVOf9>=~LqWgrD&AyCD< z4vW&LF+TnTVY$2Go@e{JrOAN*L^S8kB>Bw#I$%A7L|{Q~d;UEg2UXj+WPHQ|5F9A* zw2-()_W?ll0BJ}Wnp*4xSdtlxW9GCu0JQ$yb!+6cmtk#UVIhUe;_d!*-y5oi@q>@I z@ZW=ie(K4QfWTQLzP=Qu_nV-vD!QmE=)U!*0KzEr@&e(~Y*6*EX%(u8Idxz_X8^nA zgZ>@ngP71e^4{2=XO@UIYY*t!*CHl|{Fne-gx6cIS0z!U9ctIK%;z2*#}Z`)42i=1 zaLo2i#J~wDm&8gb+UX<+)3>Pze2X$gP9{LE?)5MPHe;Sg>3RLHrBA6^-C8uq zD7GUF8=5ei=lxQ!mGrilp)$j>1 zXQY7^b6{LngsHT8ZZ}!uuUodvSEL%H2quC6vr!Yht2G`zK^FVcf~qF}x+4PJu${86 zbUM97r#JC7zPh`*qcT>8eX5LL#fNrHFTdU3pB%7b!YCArBl#HNa^f1ERU2&3ppm2Bl8uc-V*-Z z{CMPZm5P~@)j6*&l1fU>VBKx%`d+QGUf@%|r*jcwMsD&ufT!RWJ8Qq7{XZ{2k-0J? zP&4gqq=F2GBHDmM1@(g1Tv$-tABKb6&hWC4XnfoW<`oC(mk%<@6)cGDX< zyj*Qzxs0qy?HL~CV`cGfl)&=V$u>R%ITSKlcNfZSyN^uR2x`*)4rN*fF!A0Pe>Bs2*e zozQpH7$?N^wO(AL^b?`d<=u>v)f;eyj;tXhS=j3v4SmCZR3Mk^w^eQ z-b4o}jikW|?)^zmm!zg{qw64Dd+Y*_#mk%3N=G!^uE2ztTP&{0W{)77P=a+*q3*SWx0fpH~Oc}}2 zgFG`0LS`A0?>T#Z!hB2matylhWr!tlQ&-fHLZIVv>nfWrH#0~{GY0>Sj%NrxGKaV4 zw47EI9h4~IUAH<;9$3tru)oV)8exPRk@Kau4*B7Q>wK9f@pJ0?$Uos-EJCRfcon?6 z3S5&+I9A8pSpt)SEF_0ZH{73vUd&4=jyHa~wpl+U1Ktnks%t86DHEz5!DlAl@LhM6 zNm!dk>*1t$g>|L+bPsBhtnp&y+WN`d4kRvDp}@Q7xo6@1lAwYxhsrm>z;V@@ibZ~O zHk$=a8qAGBrlUzicb>iP3L>V5DXgyjZ}v@7_)y#Zf!svi((h*JLtc_&N}^MPCYnd| zHD+dWA+=g?5Fdos(mQ9xcWc8%}|M`f(r#qv=j3VBCNtNsLB@z4svrw)mj<2uJHZ znf$ujhkL6L%le5A0?lC&>Z=-yUzIRrsLbHQuuyRiUs)oj|29qFc#D{Ffv!KlaHVgP zZlbTEag)NF9X@Su+kkFbMCIr=UH?X&?$440?k6F_FV0`|Yn6k^f|;YQQ2 z8f^y5YB|!mH`wd6=0SRnD4egEo`89`QE}lTr#NW@WO?C}53{3LsZ-Fv8FO1?;#*u) zy(}|kvwbd#%st}Nbhej-2IWXE97->R7iC_RC@2lSiR?`bi{K-^MBUFb`x{H$gsqb$ zM&rvc?i9D3YaE!utvXG@S$~jvYCl!WD`&0BmXdnok3(5sN86y}=}E*+dR-NY|HrI{ zY||=Eq6tOQTxk)Ki&Mh^37UB%cmYe~UCSaL6l>lw60nGzX}NUMx#x4!@fx?O{(?_` zDNC7;A_|?+k;j2Z^#i0bzVu8y3T0$bhKujLRBD3VN`2$9j<>j`TPd3;laLcL9rGpa zA633ma{<$Uy05>G((=)k&C~2}^y-Z)mtALsAFJ$-;gY?ts!4(>0%|9nx$fBuEjm>L zqDO+%bE+wr%krW=YUbD_|NJBPI+cE*omn!lW3fH2d)=K0;&lClx(1tKsg%q?JMvCL*8Ll^35ZYRA2=rip1UeZ4;)@^ zxiW|3(p#lwMrZj583H|y$p|);u@nVX-~4bk&c4LR4{_qO4Yc}wGOv7DTysQdk9AjG zlWNWC6YGq?MCc%p;-*6mlwRH|%g)_O+a!8ymUdtNxNF9_tP)0mG>&~_>};pL!3>qV zeoXXl#?Px^R>JGEoEP<=$^S*0^G&cESM>`*mSxE-qD zaGK6FSA^}=ay~Mz=heC+rcN~$&dBPDY&D0-F{d$uWac-4R}eqx-vSyNp{C=g-#pkV z4D=nWNs8T+BgVC*unwA8I4)6e=`ngdU}1=35i6^z#KH7^qS2`Z4Y=0;EOGwriPV7J zt9#E6A8WV{ zPVg{dz-R{k0uUo3_qFB%$mT+~a>nah^8cbQR2kJkptQ;3Ym%#~I1iA}v9h){wX~Gx zCuCWk{QmvL*w~mwGflyjWzOdU{w_WnmHA_j#X55&Xoh6Ip+3e7crDG$@@pWY^VBRt z5$6x!Z3BpVPn@i!&AVk1-J8OFZ=gQ9L7v$Xj!t8qxgMx~X zfKxwneh^kY|3ZOlLYD0;4d$OP^L8c&U&>)PJO2!VR}>U@epbGvu{cPF)n~lMoajF-|8<* z*E6&80Dmd8?E&xHQj9Nh0kB{EiBL~l=!x_1L_3kTuM~5<-37A)D1ZhFHpcuaGFcVG zd}bcantvlyxp3SEq^!q_w4P5^4H@>La#u7@M#O7Cm<5s^z}5(BH(yS8U{4J!<;d-u z|L5_1@ZofvP~%JudoNed^Qctu^T<4jABcH5KcLLR zq~;C4i^TwY3w#(v7;w-P6&1nb+ICf_mg7o)EGNifLfbUxCW)P} zAm-fU`ThQWiE59e)1qT{L0~-ITy1k3IgmRl04~Vg(X_3TrG~m=+K{-)4Ul65Z89uO z5a`&NoRJQ0^bo~ip!X6QZ6gsn)CW%*a5aK;4u|j7SRnA}^WbUO%Ko?>{NBhLA#4x_ z24H32!5$0hx=7YZ;7H#QY3}Umzfp#t)t5o_>z8_4+(MVWD{%K{8?&uKyF&X-33pWv4X3MRVJ8M_S8pj2v+CPTcddG%U14se+t2<_L=pLdo`mA0pBbFBJR9E!l`j z4J90+*)L#ik?5OvLaD9=IKZqCz!j@AA#*pKHZGBl)#&K1F*SOr?DJ;UW3R0g2UC@7 zW*kGK;J!D*N+P;?J#6s|OujNANqvo&cC;TQaFDimFiUcf@83mbh4F z{OD?o%czuedB}*B>adZ#Q~iW?7AqS4`0NhPR>>PrIDNY>-VPYIp+S;DE(lkx1CWA~1E9*rE;GNW~Cze_Q`D9*Xe@Hs7Sp^sl~`YFri z+u0Q?M#~9&?M{=H@l5vU(@)_~VS%+x^;CiF`xfTZBrgRxqb|_1 zlD=~<&D2ql{ZwT6>pQppei=_fK?{Yew9(#4Q_{pkhAN%bIMfaGU*Ne)hG^1rdgG)b ztF^S8?B_(5>9i;Vjl`~S>VXNf_a8XP+Af*xR+tb{btAvSY2FpDuR80B?hp|GCO<8E z@DP1aNr4rKb`F7nv&VP@Wbgbkuky*tcfh{f{BqqwqyRzZLi|m*k#a)c04$%-e7uL1 zD|@7_RjD_RW1IX<5k!my6guG*Fjwd~PUn*aA`(HkRMwH{N2%1lFnqxh& z>+-V}$;7=J2w&B9dz2}yF0!l;UHrN$$n`&wQTj}zlhONaF%jwG0sO!dz z==%V%yHZC;nd>ba=B)6EBZR!-Ab7{;{VhXPaMa3$-cHeThrP@4(1RT(qR)KzjLDU6 zg^fgU{KaSv954`u$l>l)hbqhS&`aOi>&fWopRzgF%T7;=MH<;XN@wHqV!1Xm(iOiS zUu?IR{*yQS5GhV`mcmJi>(lbSXAz~L5W9|5lS@5LDE!$lESzt7s~?;Xa@Gf8?$5R?9TLnBbx-o-%*H+nOL!+S1^oyR5> z?W{G5o$v5YzB58I9KiEG`J6|hC zV$Zk|q}FwCEQXh#z}OAD)4w@)t!4Ylf;J(Odf$saN^E|mXSyzn&UniEOk{GAxzgNi zne);Vt*nswf$7%+ePf zbA2?vEJ=<;MD^c&K8cg_UF%2+)@m}m+1hc0RkHN@KETTNvvY7B3&C9Fn!GmwsNKMmBjb?ZI-8WC zmMHy_GH&v-S8e_!dWp$flQuj;Wtbm9U&-%yadehzr_kNUmOpnItrC{HG$^%{vRjq) zB(Z*|&S=GQtaetedK0d!I?|l+>|H;Yc#uwrE0&I0xTG_{BJ1#Z*;dL0cY$+XQ1c}REM zmI6JoyOqG8((IDuAw61-Vn9;Ny+qKd7kqFLCc7BQyG8XkDP|h@tQHM@1L4%lp<*}b z?W5W*xD|Ac!mnNdWzJ;Bx`^;*7{efuG z4@uPZd1merVTIaCB7EPMv%0w}-Z*H?b+Cn;M&ucMNIDV-?5K%jL>ZT2hS%mDM(B^N!004M>&7UmSRGL(#5car zGN8*=;X)KOSHIbbd0P(R`I1gQE^5sAj&$0?HdE#sE3rq;Yu!PhDtVqRBLv<>1b(LO z0AxUyQI?ioP+FMg;jMx8=481E4Ky*t+itF|F9Bu!1sIy61kQA$g}}_u&ja>>v{!gv z{s(SQ3jv3g=xy61ezDxuG8C!K(y|H%^#GP4EntlPkSmxD0`(_|8A-t3L32q!0J;@t z4xIoA6@U%|<(-c18m4 z{rmTz4WUWPV@+}fwJIpJfZ-3%WC$4Ox_tQ_!RD)Ob^%7nsLMZi7{5KPZ-*K}leYcvg2T*B+)d>iPX=L@^CP}^6S5e7a&=pHH2fA_#Ec?WW1&<+mx zw?6(B7)>b1$jG=2+J!FtsxW@CI~mXb?p3hkt3>pg;XlXMyIhM;RZxt)|LqzQFns+3 z0Cdxi%L$>a-8AFy*jNmj?Hix2@7L$TSin98>$USmw%23LU}GeA;ZoS^-@^tLFjp@0 zJbO6ye&9FX6S-Z52SmFd08;mRYz@7AemIK&cyfcgXSU@@prA3cvi1Z2S?P;f%fsWA zfa8|Utc^#j>S$8kJca*>V_UB&?vaHbP8y)vh%G|$)@0T=?dpe77T_q8gTf&AFReu{ ziSBCt{bl2(oB-m*;-DxZ*4sxHW0j*DQLr?$7kKox!d@A@L!e@`x%Q`k-Uleq@o^uR zzcBVAG0i8yZoBO4u$sW9V-W00`9oLL1H?KkkiSWlTlk#IQWgLI!5f&!LT`3J2FdT# z-nheQZDdCE$`{nG;Dh30dLHX%M!)d;y8$<2Ke#<*AA>CcMntbF z98L%H5H5zVpr}HVj4Q*TERdLcF=S;+6`yaTj?oFbUKP6c9|mESQdZE5n=p=-lQSgW zXe!sCV&ougK4P2Se`x@!h3I`#VT937eg1XRBd;p3_|e8;zer)Py{&r1n2(t%nJTar zW)w&$B@9P!%Ccy;-N)@CF!GtosQ*ZQ(2;G{tCw9J$}KyW2V2rB=mc46cSzCHqMfdo zX1ZIikWF>if8uSVOFF^%DU9m4QutKtBhpZrHPVEB=u+<>{}Ztd zGgqdnETG*|5XJ6{~2s?GEX`hkC%16%YW)>n1 zhH;JNN5S6MLiRG=P5SxnLI<$C(deBaBL8;wVx1W8FvPRS_km* z--V44>%0!}@jWBT(BZEyF%%pfW>1ynAAg`;xN{E{Vkng5GV;0M*CNwE$W{CiNzSrR zas@GwRnP^X>lcFl9CPalJFM3{%wz#coR@a~m zu<@FY^J3BP1e`Ck8#^HE7Izi#VHngG$bEa{tbOCDpDT%SkV&UyGQ?Nr0P+qTxyiPz zCKu~j=j(}PpZ&jx^#9c%WmEL6Hp_HS^&z&_b5mSBr{%L7iTOpl3mnOvPdfnV7 z$x!cyRqSEjRhX)sy|1YL&N2swwKLvzQA-^AoXRP~pEXHa77+XKeNN6qMp$F2+v6M# zDwbT(M_ed=Ieh&kTP@T34waK9izdo{uz!&KK3CK=DM^$ z*ztoGTq{ghwjHuC3|2Y`R}X4%M^S5DkD{`K3b6{O&FCFMlJXT52y+ zlT;tj?mFl=yfMwsilt4}8jN#AouBXVya>KA(9tA0n+Z+5gXDm6_{g-WoS9k|Y12m6m z^$R{J1_rX31)Q8FL$>>!nLSvM5lp8rI<@|M{xwi|_q(qVGJqI1K~;OdkbSJa?B*IJ zK!waF@Q`hWf)}&oaX+>v)8bc>MyromRH^EFe zm2Vr05luGvB&SSKuweI@HkS{79=CmtLspHXsho>CJV0~0=act}}Gs-{KoUvWaZ zw|>1V4DUOCU)%+yv1^ITl1->uJK1}lYk$DAXi4IxRrp6!iVtC zDx*IyO%WlD*|^mm^_b9yd*9orNFvY_raif{aM8gKGmOSAy>zJ4N?%6jNDfA^!g$zp z5uMRH+gqe{%SaaeRVHD+e`zSJVyAkB>?aa<1$(SdTk5YD`N+bt<2gB=c!^PqiL(u| ze*dexE#mk5zkp3&sMFTX11An8WV>C<%F{Ks+PsQup)6Pp^wT}$)ndc4X&O-~bFbagRq`T4F#QJ0z(PE1PhQCB{;9BK6_x$+;m=?LIU!DsuMi%_R z`6H|HyYIB^<&N#=ym?rK*+d+TkVsog>2^;HCC_Ao`8ax_{7*~59qdKuYDkMDkF2t~ zu6~qc_UkO!ZB~J+zlugs*%seW7+LOglP8-L*SR4|RHuXA!5xiEOgpqjr*d1465edbd7cI>9f=jle(nkaC2L;c43p;9iZ5s)xdni7j=h5#veuUHc`JEYQA~iIi}ms79lIj~d`sVChV?a?J?^ZI4TWn7^teC^x;eYi#c{ zk%DPpUcMWkR_>_v_+9&<)?+1y3APQE(NURRYmzOF?yFw=?%8IbX*3A+FfRWVDcuk@ zKfLx~{+Q0sQpijrz?fP3se~zCWj}fyrLOLE<@nl8>t#WHN9o~OQ$updA>zly!!+xK zRlk=$@1`rZ|&|LhyRee&R(p~<2T<3tS!La0R# z$*mD0L-ROq=|0l{OGz(Nt?1McCPRH@*D5L7*WHB{*%=t@6!JfYNcQMxDpPw$U%Y&7 z*!Nb4|3kkndH?BH*}@F_h>BE5VN(fLK~v3U!TUsip0xjN4-ktn$`Xw4VpRw8V)zaC zkR9pg$RbLxC5(y2*WBR`luI=Dv9SAO?;cd0b-i$|PfbNR8=W_9!~BKLoPk@;scZNb zA99F?z}Hf5f-R3Jq@j7aR6owf*M{{(W73RLQUGvpHLMbl2nw#+9qXxSniN=>V27H8 z0?!U5P+0S6RcMv%=Ew=1xeCFKTO|RGocOoDb0uaSHa||E{g@B`&kG<%6U!8}06^Wd zpt1zB^Xpy07GI7beVn(UK0_l7XAkZm&Z1E;{jREopJe#5qjdvOTnxOpCS%IMLg3Rv znE@-B$_*rOhdKHK$fq<~j;4CR(kQb*PSkb|C0bUBUdzT!EiMvz=kU$(%KS;+JBz8W zQ7@AMdJ4CIfE+lI#G{J(4E`>ka`*IXE1*S8fhvDNg>m-T)5Q?~x@OC(luU_y08eoT zbKkwOpr6xAVI8RJXE{f}=ddUS+7EYN?vYavO`Mk@RiTI;wJ{>uvpM);~ds;pSN5edWTFNL@EFYDyOyp#B&qMg7V5vi75FInu1r(?p`-DcX}B3oR88Ld%9jY1YM4bBJ-^J zI@#sH8K87Gf#U+Z75|4$>S2l04X`e2R`%^*wZ9V|yOYpP2)b~I($XnJe3HSGUlJt! z`(ZV5ptl}O$BSgiy+1LgL?t?7J~)=l=J}p=r2&A(Az1aC0LR?$+`Mo+4G;o{puIVO zSHPr8{qJ`u{M}1qEH2pjC%^_T$EEr(aNvTJZwETy)7gF7b!I8bk@RB)IHsh5hmRs# z*gG)*WN7LR3JoiM+Dzt%xP#V;_v183NgaYs;DvTg@}#BU;RdfCK@q^;@(s`5XVHe+ z#?|!;u&428&eyB36bSS}_e9MC@`4Q16ZT3+zAFgcWR5~H@hE9QGwJ>gu3(2kZ7GSfc~E@)`dcQHD0<@0OSW& zMF|ki(!3d0ySKo8aWy50wN?x0>rU0_s>usL|62wki~nuMhSJkhXJ_#^SF_WaJUCw? z@7A6K!_I_}1Q1r4Co0e)Zs;SZc z*B)$HrCk8}G0RYDThhr(nQFe2&M;n0*0cwqc0qBPm)DN^Z{A7|qFfIYE$M67#9hpmT$w!Luc*jy&VE~Ur9DF90JqVQQCA*4t0#~zX{hm9aWxhwx66yawa*o0?$!~Tpg+63vk25V^dW! zg;XdzaE;e28>dhniN$Dk;6qsRu-vXEnqIT~Dw6KSiJM0sf?X?M9#|jhT`JZ^aFHBI1g2i-@WX6$1;doI(3|(({Ncof@Z6;({ zvWuUQbm5t|NwIN^uy^8u!h1hoqyD1fJ3x7}g48i3^?60G-rsP$aF)u1XdlKw0b40E zUMX6oc~z?$7uEW+K!_OY;;VW20D+8k)i^W?#=YQJ)r6`Y(=S0{PDw2GuTaX`E zAz>XA+YLRNsxnxjeuW6D(@#WoATE;tkNl!vQ0Bq`Jt26b*_rIf;jzF+2@-G`OX218 z;zbB9Y;fZelEcdzQ|ig~z9zJV&tgXoa@U#RRm%$C2hd>UP?6gsL_%tF#1MyV9r#gh`A_-cv_ixrCuy^!{7BM7 zA42|xo*wUE@FIVsOQOrps;OS`EYs}wK}0D)C8Z4+*=%)TPr#jji=4}`wh`JD;;=3T zL)l=g4aO4z=x<`CI*~444lsqFA|F#+@OiI$@5QYJRN@<&z*w-l&Keu(d(!kE5MeS~ zvhouWAX^xRgm_?Nmmwy9g|*TatqrIR*i2e$rg_E7cwQLwx)(r%>uki|!BFZau>9pv z+poTJ%9=M%oN$Rk6@u@(`HGmD>o-9XXMaT<5+n9*(HoHlQ-x<}r;mGMTY`ub=N!2ZpAhz1pSOC&YY|$WxaqniodC+~g4C%QQl*IJUEQ{0+Sf;AD(WCJP-Mz()>8EPOlMTE=?3s;M$ zym(E_Lcx#CABc^76Mw%<;I>6EKAw~-w212hgFNn=P#TmApCIlDw-UOVaL|t_b}pNM z1@6xaxL`3D^mmD3nJ1wYPkl0CwQsS5Xpmo6qre5%(o+3DnyxyosXttgkd#oAkj~Kx z(v6gKcO#%6-5@1MiF7y8Al=>FCEeX1-Q4ef_ul{fd>m(I+d13&yia`%KdTS%af)Wm ziF=J=9L0|Y7mK22^D#W84mF6Gpv~G_r<{HGGPEjihGs6*K)Dnep2YGJ8teZCk_eNHT40(pL+Rx+(g z6%8~BmfB(zaa)Aon>PPE2TjADg{jhVY<-(xC+U% zLC$neZtgA!7`AY7Bn&~aoUaZ6Lzl#KiT>v{zJYXqE(z#{Vt6gm|?2ss}^~$GfuAwK|3``+w9Voo1_-W<-4V;JTP4gmJOaY3?*?+fFaPZ{^{}VJ+EKs zhElY9JBuJF%^8(`+Z64>ldcs8t}U?I7J)gx!N4Fr-Iup~+bpmDu%~WjYUMrzb-uV038`T-TPC>ADgn|G-+Gb+!_%&Y`TZJi1z zGhSVeJRXef96voC=d1mpt2(S@m0!Nv9eKJR+3gI%Ilme45-2NF63hR5-T)9q&OqGT ziLPM0n7hubi%Y_me}v~SFL{|KYl-eFx(gjWy&S=Z3Bhcy+gvXxT{La_7(4xMQtuzD zE$6?uyC=iU?2avp9(wqm*034w0_^EM3fOUH&ix`%om(9 zHsK{d{Xtx^MH>2zXd3@Kc!wHsT+Fy@HPl2Sl^^M~hOHURJKD5DzP=MfU(b)m znvO3#MM%V-dFUp^@f^|(3)dfun*sce(QB1^W}9kd;PgdSqJG zwSIJ1#y-`gDo7r)nzl7yv630PjNUxbpY;6Mt94F1u#*4hJ1 zHSVWKoSOCi!{_}UH<)dv1ZzNKz7e!Q9%gw&LA z&gyjzdfk|u_azvRs1CYprfQ#S>wx5Ri3*GS9l|ZLUVrw|AGQgfOI_qA7}svq7?D$T zY6w-!IUhpGDMl_@Jr7GQwK+FSSCQerpDH~YMx>%|ZTC#Kr?S{8!I++A^-j>^uG2yb zX7}cvd3gp#zp`VEcC!9D(dT(gHNQl%eViWj|Pa z4G7}{L{nuJF#L~gzw&S15L+Pb400RUf7=xP6 zwiR>Oeuxeu8=%?+Ixt>mq!7LMF*weNf*2X4(e6yK1mD?E;cvK1ruh}O^`*Fv0lZPR z^~?(kRKy!fMfMTg_T>*#8c$@-LDZ!`l;PVV8XCm88gr?6Mk#E3!}0f0f-S%D9NpEs zemF8MF2v3hQbUl&zy5NQPMPM}Q>3h>L1{XR`R2Mc2ku@7Vq3}*YCo8y+>TeRSq$6D_ihTZ^YgNyQG6pC_-Z2R$x>JHM?`2wPN^$g_n#l{13 zS8Hw6-Yf?>mw7ndDO3FvSD`Aw>AKNbPISjy{4cULB?e07({9Q`GHoMD6z+^aUtf=e zw9W{J?u~Y{szuxzgpf=6%1wIVTq1d4dgVGb7u-3u4j<59`NMA`>$b;2rphpNp0SQT zLqW7#%uQ!tG(zJw*fl>ji^N%BB-O?2H}3he?}3|ioR?07d*+kYIam6(xlAtt$2Z*z z`aOHM;n)VgRAk=A#etb)zUGfCoxyfE!a3xdkj};c=RNv{+3V6D!k^<;&3b-R_W^Wy?dpx)}~BFIf_cu*!O**+bZFt?r5*w&-GJ^ za2&+{2y)uUvF2krvx2TEzzN@zp)QVcdc;L82aPS|?ZvX+VAqd2R;%x1z!PN7c=GMb zF4b)2r-e?O>UDlyNuwe>F(@^CgT7Sk8`bfKWrFm#d;RqlT*jg9w+xrErii#9vgv1U zqN9%Pq5Jq;5P0ecsQqtmGD0r;-obNrpMALaKZo|gcO;4IZC4GrQ96r*W&BS*@`?w>eTU$}^H~|qTS`5!jZFp082vV%D>&kF$epef*5X)9 z@+Yk$GLy^CK;3!PAhb_Bef6fN#Vka7GjG*n!FhMv{wv1l@kd)-cDAi~DH%_Q;Vv^) zF|epCTdU3)KWMwI{rat0^QW)Wh+8J|#Z>L7j$5)s9yA>OgTvGeqN1>o0$_LP4c9JP z(MkApDC`B)6#N*TMz6k6mJ))_M93j zfjMoi+2b2IFFnQl4ru;k9vU82s+i4s>-jHO>5vxPaG`B=K^XdSQR^%4l1}Mcfs?2oo1eEbbU{l3I-KfIk|CA_^rXmjs187M5!LD==$k)b;)(} zIXj)eRXPqheHs1xH9WtWDB4G0`22Erd1w*YpPx^+=fG3h*f-g*kE*3YX-JS-R68PKrL9*Y;PCSYv4 z8)*4Zg4UP2rX(c%4)Z+EJlB7^(ErdTAF;e>+e7RCn)T{EAL`X+e}FSt1WT)P&>0a3 z_E=5^p#EeLu=i}7oD$3TTbvSeN@JqrhsUs;(Rjs(fb^l0_DxOHxfAfSj)G#qsP@)! z6?#-jna`%CU!0s`J#Pl|&0}PvJ-3}b10L^Mp1v*md+!Y;(Js2}7de1#!rSGi2Yojn zOUN4LKjZ^i?8DiLfYfhC+#Kn<&R$o}yWszq^R@Fwn&1tQGwq0JqHSWVq#)O z3a|6H;C+W6h}9gmH79tq0;&=R&}?6Cv+DDbkmO<>pd011+gGr)mTt|XYFv=GS50H1 zjMqOrFX83uZce2x&ex4rU_1xs{@?s4^`&9)h)45axg`yjIn#^<>wW-BQQTI**28wg z>`6l^!%EM3zj7o2_aPXsbhIhBuNxG^&4|j-bdiKdwrUN5z^aR+z($a=M(I~1DDiZZc&Wcj+de1KSTj4t(CFFonyAWJER{{JlF2%G6QvOedj}MX4I&)RBo4j5 zN3!UMt!s^~=nI;6qz@;WH9Tugb?qO`?y2<1yctwk-PhOcV0hOsgXFucFY)$WH^juYm?{2ACZ3}X?~HebX;3q>{~nB%iPu6 zxGa7{XRWy^MDxVQABw)P^$com#Sw@8)8gq^s{Kpwk#1V%E|j-Ejlb@#}Tfw>M$e+i!=j4;Wx9r+l!zgxl$*6)=VI~HzUcc<-1kuTPl zwUyYm-0igQl3($bbi+%}Mu|m2eDrQcx;Bl+9C-1&c-`@Ys6UbhRwy2qzJG6Lp?+?x zG=isTET*nOygXj_QO4E%H7drI-elWaYPP0RBn{UGtUU*dibCB+?LOHw+xtAZa9#dD z>?T+A7DoB%;)otuI%Zz9P^7PG6m&*{^#evL(WNx1v)_l2Hx0L)?ksokSe=A1t9KyI zj2EA8OvSC6Zj*-6i$weY8b8rR-v35)2jUo$~kdOwlIy{_hu=?-+dvgY{ zrNp9u*gn@QPQ51|^OsQ>PV%)dJiCNAdSf`2|HVLxY_@J!rI|Q;e*R8K#tu{C1Kmd( z;n6w<`zShwxK`@>=Nu>&XR^gNihTdFyEz=xeDkN6rW#CIUt0PJ=kQt+SO55ys1XOD zDaCvJILR(#HBN&$ovUUn$QeD=Y+H|^Eyh?j5vquW{;5<=y!JCHZ#nt92B()lta7%x zNFy3spXv;kN8gtui%j2jlih8sx8V;i%=Tsy67weWNOR~ke>PjUdgtsh2l21{{@P;c zUtDm3>>EN3>y_ZHknjCuWw%5ze^ya3!?6ueMb`G?b!)vb2<;}Oemgg@uEuw^)424v z`A;~cj2ylC&6g`va1EajqntfPaBi+X7~?uhRrHFZ!RE@wkh0{IRlRN3ngduOEyTu!i6*>|Hty3LMu4)2Ll66kowiP>pYe+(-vwHnZ z!N)Z&1T$^9QCa??!878dWvIV3bE>_)J0UKH3xu(XFiQF-JF0ti|bf*q!IcDVdOv;>L(T27!~`@t@%gg2Y;t1qxdB`}tnnr%PNDUA+|ZHomeL z`5{Ynb7*) zetY?QIli1RLL=n2Eq^*Z$KA11#kXcBXj6o9$$q!KMCy9aJ@_Y%;#Sn?x0+sY>~-7r z$z)0p21$nd{zeGzifU8Br+VuxvP`A?&SCXLg$!#qM_ zFnSPk{aj}gzSxEHPRo9$t&Mgq=SDHMtVKXmKz&KAk6He9FH@!nf1=FQcuq@XGb4Uk zmFukm9#N^}NcpSP8d_TRx+0#b?+vG=w#^+|#^&P!1u}zwYcyot^oMA;gO&~y*h5ht zF$vN4P92E@$WAnG`|5dPxehzqac(RZFMoWN7?j!4k$tpWV6!35>D(Ra=33Q)&#S)) z8~n_G9X7O1=sh62u-eBK+Sn3xm~0_;AY?_=Q2M$zZN1UptJBVF7Fp8eoD%Zkzt6>7 zM;DW2ke7?E;rSFPj%rdZah$v2Wf7%w_FnguYk3$))X+($6hT>hm&ImlX1|Va%>*}O zTv|!ZL`QdAUW7B0Nq6z_qv#y-$<}|$S8is8vmvPG*gbr=$kWk>8YvmqMlF=xY%8|3 z`S-VcLNlR2cj1NNs~>-c5p1Li@HOgBttjLe4=bKA%VW&IU#l+63PZKuTVgi#@g^ql)Ieu)JAO+8}l9ONNrg%G`qS%pERqQhG9+lQLoX!j=}kWfh;Bqv z6iTo=K7eYj8;1$M3(BpH07H=&+_N5@!?H|dJh-;Y7K{n+)jOF@>M0<-NhestBg zYnfdLA&6ldTU(<5d+XaysDSXMohB{Wn`ld-7TYWYwz>b;0(e;iC;^7xX=Eg^W?pmt zu7}08e?DaCAmbt=amdvC9hqAZ#;G9K*#XHf;K#$SoyBh=Dwk!>21UUf1e+W|p8c(4 z$wxw|@@=YwD>r@^8hG)7n9n5~5SK=WlnW{^BI0{)eziTpw!T0i%m$2#zJiltz|NU2 z@4kj(;cbPD=m7SU;4=n}!a`D&XFKiIRtvQe-90^Ko}K~~vd=5Z6z!#c^8U770Q4$s zl2<92R=zXToV2PjGc^1T;$8%R|4>Q#tHkE>)t0+vd5JKRYyg-x?YLQD3n0qmX$?+? z8O}?t0YIpYwi#r0Z~q2N$dw*Tw}+Kw`QUw(=d zp3fMUUDh#RoV-Ykl$aFZ4P(op1JA~tfCo@DzpWf#JUzFW$dNsd2y_3@e3o)dd$&c) z<91G)BH)n-E)c5@+&*jYc7iH^+g*7t9u?~hxkvE`AnJf28NhYJID2e(MzQ-xP|1GY zj{bH7DK>*s7JTNgUH=GR;3xNGjp2zF8BxsK+!BFNbr+Zb+c$CWDx)}1Gge@$P*|?z zqt)iSQ4&r)t5C=sF-FR-3)N7=?DV7q>$6n6C^b}3h(HMp%K*6WIPOKWej~swabM0AyonP0J+1GR z;tBALQkB+_YAAQ%SKCBH?%SvTWA(v`ocymjyW#zFnrqEX7x22>?*H}JBYaS7Wz7PV z0qmte-n*{eZxCco?2ZB*{s&luC-{@iWzxzL0BuHpi@;F%?b20q91m8wEj?~k9}M*V zNA)x7gg-8_wV=x*Rjff0?u!cxN5NgS==Jo#Xe=tqCMYNmWG&~X18+aip+t1;gH^yT z7DzE&MvmRIOQ4VG|M;u`A_ph}a4#tR%ltVFn3cGQ!XLc*{59ac0Thz)Za%$dM<>8V z&ll4{<0%k2Pp1Xm(NaVf17?CjG4dHB|H%G6(|}u)COL?qxfKu!_=VKN5F@qWO7hpd zN{0}h!WARx`#Z_V!?<#Vyc)Z@1wnE~v@9_Toga@$9mIb|<^A21`;^CPz|EyEZcE5t zaE)I%{VsN62thQB0_4<~xeX9vwO`N_u$9p215gE+u=-)>t_9Kb{>3d1LwN*PwX;tu z?lg2@{A5E$C?8M+Yzz>S{B~nF{kD7slrgZ6M#YjsSq^-#1%LN2oU$@ixb7{B)b{VF zF(w`>SC z*0ZOKp@qzGtOpv#VEIYEZpXo##I19x`S&uS=2d0hg6NFHR8>w#jm?*Z%*>lLS|e0O zSp>J_UotTrOyhqi{%#YAT14Bx>r{uC_%z{45?LVZku9Ba5UAMwW$M8!Tv)K|YEGOJ8<|c^1Z18w zb=Bq2{qrJ`r%XN4R#QI$&zbf9`26ndf>6)W2SFc!WVCZk1IvsfxOZK>SXUp1=I+Z5 zE0wyra;(98*YcC`&y=AcSL2#VFv*pekyg85;xCzn*ZpVVY|y0g-N{$ZOc6gCAmJ9v z;o-J#G1JEn9S^jQKC`Z@o(x+@~@8P+ZV%B({w7JJ-zrWiF|$F){JNyAs~)4Kz6;QI~!ed@xFe z`o4$5Vt#1Tb39OVO2<_~IC@_|F6TK{Oq;Dm8dhnfo^$o!$cF2TLQswoX013)0AFZu zbcgmgp6A7ZzhrdCa`b*m*nQttqZ@b1u+ zVq%%2W(=2q{FM0Vjg^w3qFPWwXkcyc%Q;uHmdKVgR6!8X7i^)-$)DMXuz=vO(`zki z_0Dc4OGqqA(>v&!A6aXmvB9nq0;#%`4V;Yd!Un2MKAukELM6qKSF(`t!c$VMQ(2iw zuDpnjnBF@42&Cfl`a*_-*le!k!9(Eb-{k#dKFSD|jk64-zbh-^Z|MCYk!4}rr{#y~I2&Yn$=z>V zml&P%w`>T?0-^et$~_Z-S!!&)TWY2osQZtFL}Jy(eKIx5UI?;LLH>PeFFu$4W|xoJ zX9VtdV;J(Wt+T!387^^;gZ^qk$@|05EIBfaq-Y4E33_i#1Ij{cWJq>Dv$um)woGw# zE8u17lWpJ>E(@0@Tbrc3DdR3aX{monIeSKHg2E`v@MjIB+COA}t;-Ton+jd$pzE1U z&;^nZ>XQ#5w;2(!x;aE4k6Wywod zgLM6&AwB=J0b^Noy7y)ClQXwttciALQ}h zQDRVzq>QQlBz^ka+l(XLp)RA7Lm@#mjaf}T{++(EFkLEInYth#$xwcz8w=!f6+vnp z2zB5MK5X&al#d-rWk<$Fna;~GtVWR`hf}wXXA`53-PcdHNmmtG?qV{SLkSy3AaUhF z&)o4zS4=B(6Kiv1_q;K@tdVUS^tysbS6+*FXG<{Z*+^ZIGQc2&# zAzi=MpI*4YP%taj?^u+Ad{SM0eIZW&u}Rm32;B)TXgTX5W)kbhYqwh`9M$)}t<^&0jAFxd z9kb$>^>$1Y6VsrWBev6Or>=xu0p%)(q^XvkyJX_8j@XSk#=mvUKF%ae@-*jI(?hyMJUIX zY0u&zP|W^njJ>$PYROej$+AXq-i{upU`59Zqn(P|mFDfMIOcV`xU%OxS=xH#K4E>1 zSIn`UiK&$Z_r*scHMKaha!$BCxw1(HdDx0Q4Q95Y(`M9q1lUl@7?aYBa~jsq61%QE z1=ugZoRbKa##*bKv1JR`%-!An z1;_w4Y66&k?WFlVkzO@r2?jr!cKBn!)VpBm8Sfcs9On-PMlfF|aa!j{WtUrFW)SN| z%4B#gUwP2S4S>l6@DD(u5xPmYI|4Gl+ijg2bl;GY<^ov=5NDU2eJoGOaDN_3@Mw>X z2zInz&bEd&T=fbrNPDE`mc^dr#)&vB=U|HAO4qY39co_IPOAq zyjhEJfR?P9{JfPahdq|{x8wYaGTQ`6tC7xTT!FL{s7zrPg@Yr9KL{+4AFivxyLHM} zBDf72$9bl5|K(?zJD(QQ}1qWZi;TlLcO=1#Q}~Jh1Zc*= zdnrLuq)|@pIv7ohDSD@=#g0)KuXl&A@$-9h__w}GlCg`` zY*ksXmd;ip-2sl+h;7$)OPUe4yy<=Lm=rp*D8n<~zV-Wiz9x_wS+^P8gGY6ftyLrr zf_z{H@eQHs`mXrsFsUVfyM1!(SH^@U7YXl%+Z-7=WJoC1q!$LRNPyZV`l6tK0Qi+G zF*P(%NN8txkLA&7AQGR&a@ILt3GgBe-~rzx&;S|IhAXQ|0wXJ!V8?+(8Aeb*abR4P z^Rb@Wc8c@uzF;3|?xKq98j~d>DD?oUuC5$o7fVaalqJ#YO-0npci}sC0!(k;;=CBU zJe;sPKVK>Xs}5p4ScyDd<-zFJ$4Whg~Pk#C&iL1Xr0Tly)o5RDcg+oOl^NI*@LC9Ci2HeYK?*>pZG4c?;|-|X5tL}4NAYKz~XV}bh& z^=B{6&%6oPR|ycYl{B9IG63}8f*fl&Zf4L? z*StYJ>x1ch06dohyN_vnibG(=)za1mUldVL`K8h}#=AR7Z?GjA1Hmx6YhA%g%_$dB zG2rd_JVL_bdTLC*%oMo|gxp$MT6^o>Y{(?#CRM-~fXA$p{{Hjuh8`t_3uX(UmCHBQ zsIm;ybQ!(!gU75{47HSlf=T7r(~in)526EbS7#AO=Q2iaQLI-oy!C4g43GJhV^nY+ zHwEpE;S5H)b=~a9CW@UPW(d7%Dxa&OSBUG3E8fNwE%8!};bXO+$J&Y6B|Y=88B>=s zzV>NTvetF)9j-Y$p{STE3Ouwa&1@UePPy4!apu>R;~nDU)-{mjx&2!fY^3$|i-SRh zGA8SroAj8ckY&mFKylNXg#54AA-tWl?~!>&Z~RFDOerM!Y=>fMPpAuiO&TXn^#ugU zHpv9Kj#b5OC59H%xh?oTdkLkSMHf>ugCOUc;?NRpbZxboN|$b&(l9Qa+u=CWpVO2L zhFIaa6V+%0)Vy6pbZ&(+?PaJ&8%D|um{|EuN$A{&O*AM?0byP&7Y={$R^rSPb!SnJ z)uC^!wU)Ez#kcuKtgmeo-eYT&!`F+Bu=X2S*|_^=a{Lk);LV^rkiM}$!Abb#Ehck0 z1Oa@IBr|iN-FGLJ=2Zlkf6j;g{#4Sy5C4YBA+c>Sj?X+t+nTLI2A^)moZw_lnjUFg zg~F-tExYM(-)9crW$4@PL{|9HdJ`$O-p?1YOYSB;2ut20(1wvFx^u3QxeQk*gA=(l zcJPfp75>VOd}#!F8sZwpmnhGD+XcFJX1N~%J_H{z4_sT$k$tYOC@o?Ou$L?h%KpT4 zA*^JewATGX0bV5sIi?!^1Jwhcr}ZA)n*#ic$R5d3v1ID!k|mjJ+0jy*VHBI4FLaFT zg_`*4AU|rM2@qCSc&TgC3SnyXr?-2rUOO`^rUH?PboIlOuWiyUq?VuLIMTxaM`ZC{ z#)J2{?)e}b8#6@m$opkF>s{>vgos0?>n#2BDudyftb0Q_%Ikp9QOE(G0_i&zFLm)C`w;azZ!`8+oFN*DC@rrcX&8V$OElzdUGp>buR$rC|z#H_j zuXwxk_FX!s$VC2TaAuN~Dtsu&A|5`QI3t(_@3P%=8z*5lpi-Wv9kbi(gT?UEc0nO;cPLfdPY8By`$(6ib z?V)5#U2wIYVnlAX7*bkow6M19+oo`N^4=f2rS=ThT7FGw8Q8JALV4)R%8rcUZsPD?Psanjh*domx$awvu?e zfqjpMI*Y%aP8cU-GDQzfnkLEpi5TZqGC;c88G$!4#c^E5tr^H$=1>ayik}^%F)YuKHGWW zKLq`DMyj~1sw)@54l&rhFNVk=4eAya;J^_(+gLn{D=_I|PavJreeK!Tob>^@rQ<$J zmXJ@Zo3wh#!3r@E@!D2Pa#*e9ccRi#GCzKkt39SO{W@-1pW`iavXXN_w%VQ;Zvr)~}01;9A16LJc3T zW&pIQohs`w7uun5mzjLOEvq%*Mz?g;2{(J1p2tusz$L`R@2-rfDD#?dNvA{kw>9#8*+ito_p01Anu+c{zNfnUlC630u@1G!`;DU+jQ2 z*gg8QoGKOTK)huk&04rR)y2UmwM6l2*DrIFa2kn;I{R~r?`Gj}n@X{^hmJSaXwtR}g1RlQc_SFI5s92blYX zDuwlvA@oj>L#wY|RUv#ugR(KiHBecd5zSh)N;1s(DDArI_8(twen^CdhhTWu zE>-WpE4zxT<597OiTqCM737) zJe#X76sIziD^Ot*CYVh4b)mnJB9o$vb+*G4y`8=stEq=y%sy*}wjG`QTrP`0`@wVU zT5W?@bDQjxH$(23*of}A_%AtvpwW~b>v)<=qq7y1WBW z7h^!{MJ(u<+`nl@gADbZJLDi1aE}L#U~}6N-3SX~t^6*io+m9KVPO+s*$p0f?{91w zUem^s@uVNUOnV4qJpi730eLsQ@wR<3xG>s>$8G}!7yt^?{(NwZ-M$jWRsiq_jCZ#n z2SpsDb?$MZx=UDo$SNY`aR>w;6OePXagyU(ovR%IW+o5gi9RuAN;Zhs8`f zB7XJ_uXc@9Ht$ibuMZh^^HQWs;GOkq8py!$4h)3(TT|q={kJ{WnyHmNS9LH+1!Tbf z8N8?h*&;(P=zfmI0?{^bDT3NHs*y7^%krSPbkLONaIzu{8}3Rtb<;VMqqbdPo=06b0y_<#2=W-cPP(j>a7D*+1wvoRhup2-2>6+`r|pz>_s zF#)PP*i@Vv*K#eB^Cq=(lpJx(+FZW(6500jV4 z6d8cMqeG^Z*pMM%fQZI z2K4g|z;1B0E?G;ChxaY=R5s$P0YDrueHQ@sCS!wUR#rzqHwUA5KqFc)0-|K=7md?+ zlke0_Fc%8IROe@DPkdvV{%vo+DzVNexvC_XT`$8x_k~-E=i^x#9@@*XbpGdqG!NgR zkHL!sozEG6|Na#f%6d5iv{&F!scw7ugy%U38o+YUa`mm{KT{>?({;~&auoAByuA&O z{6CGvh&-*6SF@e^joJA$#T18H8hZ)g3{dkv@z(2Rz_C?K^aF&KAT8bK2J90)q=+HU%_(qlG`D4sjRL|R8M**`})~wzx zT&CEF0AM4zfqDXT%Z2+eCvw8YTJZ3U6zQ48TQ2!72*Kn?dV#-F|Vy4(LIE zDYRr(KH<|!?yG0o1%(oJF8~dsd8p*Q@#fpUv}O0RA%}l!V&GUm!-o?2?1CSwf&NfJ z;PeHIX#!aDOV#gpOH07R41*AEk2l--f6Yyn!e8(Q0H-O)c>3>Yi}hQWAMR{h?G;)8 zb9H`xj!~U*d1cFOA0gWX8qPs?28;rQ9Wmgxk86+$UZZ=q7vNN2h;ajEUS)(G#(kT~ z?-S_<&WCds3wRjt88+bS$NhY_L%WCh`w`MIW0xSlcS0wB>EO zT%3TUQOZXFI>i5OOrXt6bGvIUdA8Es2hi|qo|~6_@_EqZtM{smy&Fs*d0hu26HC3s zCwb0Hajxd@i7fiX*j+BJ>;2E?n*Lt}&!Y3K`&1zjGsW zGRmh%#kx=h-q!g;+{mb2oX~fDQfm?yO(y`}rEZsn%YOtA8+b$gt|b*;!~Z_|c|v6_ z3uL6g9^&DZP=%@16uOy8_O@!hDSvrpKV#3 zEi4#ulXhqckG7$%$mPH5#Ftjuzn9Yp(kH#n!>liODLU5RNHi7n^P6qPRKm~CZz7E; z*Af4G@zF*k>#s+D{j4teh}|^dk41Cc;@ztu96bGu(^(3fGxFhMvbm|!+?^)z*;^%B ztwhf5gavCc=cS^7E&kA8cG>*MAUPyrEmt1rzblb{m~7_Q#!}83sI12@l!UDM1Et~f z60vmCBbAxkU#QH@sOn7}bF0M7kbE}zH^h?a!u-Z(x0US}_2-eUYGd$j>_oKVxB+2p z*s~*rKEOesZi*Kv(g#O&fmgT@>o~rjDCxDUK*!%+9bjpNpKF#lM&N z$O8tKh5LkJj%%{B)-1-9?Ok7k3IFiIhSr>G=Wzb?VdF4I*6~fM9^;OVeClIc2=fiq)JX%G1| z(6Os*V9Ool8dCX#j?-|ncjRXEb6EkE-nK-E&-SphpB(D+uen(kA8jNA9|bcLedIz} zT6cIpn(XswUp5`8Z!uKT!Bana5W=x}NDjc)x-k@9*~;HE1v&~$_9KQ<-k^mCi*X>Po7 zW@Qqg;gK5Q+ELBc4@Vu>L5Sh#7S~?7np%bG_D9K(g+?)SYfvV^1%XAZ+{_2s9rMP*)&q+`onR4C7SoimaDavIG zm1=xO?G3?ZnPS{h!6w(|dASofm5WoVHMvZT*7nVP73P+MaFE{?6XfIEfERcu`AZra zR{&ouD@g-gvBJS0vU`K}GcGNd152S#&}ugFIB3OJ`v~>P+^P6*wsEEO4q`KOC=1n= z4>qR8uGQusaz`-mshz=5Pbxor%%h7zF19Gd)wEA`S&cs1w6xiJv$XQ2g})fyp1sYf zNrW+%+h)FPlSD_h(LTRptB!h^%MW4JX76_rRMmPb5gW?_IY#G}yLO6uiKE5rYw@uy zJKKCc)%SDk+y+aIFlxI&u9W;eu01J*NOI(b0q3{*@adBX^kT)FcuNHvDD4(5dWh34 zki7SB(kP3IU1$M4&O~^vCQ2e?#C?R*RPmIf;*F5_%4d8w@MWyLME3d>Cv;@Rm*1h{ zcJR{pM7{o0fl*Ah3np7g%mIu1WiNELZgL7HgxLz=60ntZxwBvGBIuY;b=dl9Y&VHx zu5@lrD9wM$@6mK&o_>pU=aD#Y$5pkVp4h|Ay?xnxnYWxTwZoO>xl^8LNCpF1K9Wb! zXc`N|zEfYGgqTIO5 z=79@I)VU9GV{>MOe51uBzh~`SX{Bf55{F;`Cz1{4#Gwd%%(64QM&cvN z*M*K=R%z4PJ?|*{E)VINw(MQupOiB{WV+xk3jckdKotfug8FV1E0sjDH};Jy+aqC*Kseh${SWkFEwi280Vs_VT9FZFV6+7vH zPakh}=V}$$JdI{HJa^;^k( zF&e++I)IP@1HZsDfS@yO@D^_f>xE*KRyHtU75p{lytayYm2xG^xhP6{zk)nHs}cE@ z1Xp4G-ZYt*YE~<{+Im?IhytN72LOctF1eY$gDc)q~j4=^N@< zcd8rY|jsEtxS29 z@Z$4<#WiD+heeoZZ?5Vqkn~k`5?RdLe#EhkI<2pD95=7}&-MjM1cBw9(|fFT86&Fe zooCe4@XE422rB^ZK|KgL!dY*7r$BH!)po%~PlAEa33FeAV}u@STtX4Bj9LKvc9Rj5Igxegg{V>)?hT0HpzG z>kce=2m}q_vYNBKfw{#>nl9%spl}&NM!3LHbq))}s;sOO1(q%^sH~i3;u-+QX%7dV zcLg8U1*cNnvoG#gUG%lJGsC>@*U#5UpW>gP9ACJ>4t401#H`=UI7sqyW*SINJpk&? zwf4H3&%dDgnO3_1Y7lQXpn{UH(`tE`Z)qa;!f`AooLeeY?$}udpQ83tC+jY!X2H_W zIZtp$o$n~Z9RAa)&e{3i!GDNwU=a)Fbt4|3u!i-Bec2z+$7k9=y9#WFA(tk=9~qa$ z5@48C2s;;8894|mXtY`VAjqu<7wR7}_P?8pRQJ=q&JEF5T+-4ovLD=v=ijsUj-hZq zfDIc35yfiWgcyx55hMu4&1Tk(G?Pe?3jm)u(A4_E@cHkc;@+X3ikKNX%oArLfmX1di;BVm5EOGKBD&>c@4R4%Qn2R)0RvSh*wEa% zI&L>m+=yoB5^Ql@0o-!1h&I@rk^hi&;4cE(YXK6}7Z!=NdFKm)-~=f8Zx#XvtxU~u z9AY}lsGtkqw?BX{?bq^Syx$5A=!Uq=OtR=v0^F1j5gdx^8XN5oRJ5snN4H@~==Y7u z+eL+a6r!US#HNEPy61a-Q&1;S=#;S2wqCd<*lcjzs7c;L)MgFNU1(K`n^-&b;A=8k zZiud)sg_EWwN5MA58(^7YgPT`EGcS7G2mZ&+GUuMbUyrU`?Hh-zoOB=w_%R$I_B-j z|Doxt!>V4JXg8^}lz^m2gGhI(h;*lPcOxMnVbKlJ-H3EZ2r5V;4bmkbT?gqq`@8r4 zbDqZ|xcC0;_nnzFvt})sw!J1HTS_97K#^vY(q0pTV+Q=56p9|FyD?bVo7$)v7Ve=n z4`=?(HJ(bKunnLl%DIwJ{`Vi^MKX8X`f|Rh$A7`h8X*yb+5OW=T0shk{v|15i$|6i zENnZu8yCa%ojkF7)M%QyzeCx%JX_goH@PQy^}H*X#B#qFPS=Wd;gO`Gb2g<1Zef~; z5UcOD;nHc)#%Scs@SXlrn!a6i(p>1c*o4aF*lqmuoPo1_%+et@?e&nwQj=ZFY&wGM zMWxTJkhtjwu{6|K1kcW)hTOrMkjzm5k(#pvqU@2ux_;Ke@PO1OKg0|qg?LiL zP$?Y!45)%WFySEHybM&JOeNZ=m5rC8L=^fOv%abcj8}etu=I^7CiFVt;-i7UXm|@5 zOR8o|(?=bNQlEBPOV{f~)-#ViO}TNe{S$J!j=rstjK7J0HtO)L z5PgU#5G%Xq)x&2egeUO^l$N*xWv&IIW~qBFTYq+lihnRQUW!c>6wNrGP0+lt%|z+5 z(Qq_RvwzWNuY_7AFx!mYo`A-OA~gOxVzd5x$Lli0qV0O`=9MYMVbzs9_Lp|6A@?A( ze!AHzK98c{tzEhX?nMU#rz2|eEQ-}{3%Xr&ybePS?_@kx16}7*_6Mrhy)#S)PqF@V zkWRVvdWuIq9;n%RtD(SPDMMsPTEFCGt)mtFt{HRNLCynHWkU6_s6o9!+K!21(;BU7 z(TKyvs;_?N<$}YZgCQ4}lMs890INtln!)chT&p@($`Es^KsQD`MK;>I;;)_X-Ws4a zR8#&-SsikGeKGRipJ4iRvk@v+{icA3Fr|0;wTYsW+WUHBc^?Y->mxc`shnPZcB^2s zC{|Zt+cQe(j;@f()Ev%v!D)-g{i|Evd{p(Y=l)NNJK1rQx-#rXjXkn|Qq}5QgGc2L zG?y^95l*r<0b>i?Sai+uxtwa_>aif!$@&S+_T0YU`Ev&-p!D=Mj0T0%{X z-ua_y_F^QeWo$aMz3JtXsJF_)6=O-siW}YTN^|L1=o%p$v@G|Ij+6b?y+iVhQBnY9 zI-kd2rq@;L(~~H-QMLa{>F=Uw9)kCC7jH&@Y-gw#S9JQqt9qr8w^AAZX^!62n_8W{ zb%N<)OWU}*@l!L1K7lmlR-0Brq5B+PC29NCvay%ftN6=kPDztqxjZak&V6MuAH>T) z6y>E>IP`s82+sJ|*4T}FttHW0MahNxR-)6_7AWiShK6D(Z9z#dw>f+eUN;X!b$+3I zQ$xim&wGqXLt<4Jud>pvC4!)~6lVG>B+ZB9q=Rz3`qyK4kW z-)CcESx=Q5#S8jJW3aR1R5EkUo5<6(QH#ssf^K!D2)@6g`P;A89J_H9A5%%pw05pS zNocyK?*VFk6JnT&tAtYZ_u_Yp^4B_21(6Ht;_u4WKMK*_n+}c^iz03)@soOoVubFY;rmL@)d}DA`anXBs}O$<-qi5lXKRg`vJ4wi+KU zNYP0RhlJ74)ed8}Y;kfl+X_KPh zg-d9?ph}I7@xapj-5WpHqP2<=|FBBT z$vrerLVWPuTL$h7v~@FGk%;uN07cZ~TrXuyNxhtvd<0|l@@3qXiN+r}nOWP_V*c-E zZ2{hFKRcDb%vEUlgmYy7xmQf0CH;s`;KABVxDC&rD8>kd^p&JKce5wV5|q16 zE+5MrE@}@9MTn&;I4C+%?*F@VSQK-1SmdxCmx>CHu$cK;eqgzVQXkaYC`OiHv4b&P z?U8$qt^b=_OoQZmNBe&JwoB&Ww%^RQq7ust_nEjn^4X%pE3#66+-+ z6#3x0k;PP2|0*k$&dal`zx+KN1jPy5KlAyO#?jZY(HWHd;u&7B(<9jCns&{2ife6g zU+j5-^jdDv?RM5^2Gs;*M};^8wB+g?_47(TK%VzC`hl0X6 z6Ft#j_rW{0+?BQ=`ev%QdQ>u{Scf1?z?tj~Wz34^{8n#NzTD1IO_U)z`KE~NVm;09 zThdUe3H0Qd)M2%^CV6|RuEz0@j6V0eup8EG0+lMK?Q%9;#D#Q@T#1yg;m7cinD#kK zqWoiJNtqtmF{kW-MIPI^E&0c}BJ9|lGQatY7}VCq|L|RUXS%*h{2iFCz}%-NC~Dx{ zp#AdHfz*wvJZ>@J)|(cW&}LWQHYE?HE4@>aRr&garb(hKC+${6RX!09at5GYlCRyy z@tzzT7F<-D43s@^>CsbOMz}R8K79%*1RK0~GMTun|668(Nk>8T^IW>yVE3m6@?Lgz zrHN$pxiEWzf)ch$L|WHeRb9gcom!-&_-xo=sYYxl54K$Qwd^O@<;}p^Q(F)N`hgi1J=jbuL)JC4|neUf69{6p0FmAk} z@|tpvGxLevl5jo54Te!BklkV`e$pdi_AY?liT#;B_Da&X)rj`i#?(i0D&}JaK%wIu zQP?K>U=b}LCDia9 zMB=bm^#%N}3%bq5qO{Y^X8@)l$Gg3~Vd{De!Uh267CS;f(ZfJUx9jTF1WzyBZDeBr zNO|D4O{ToOtb8((zE2Qb;eYWTP&Z)`8t$bOLYLk8>U5;ee$p(Sm{im>WZ@%_Bmo5i z^scH07a&S~%a{81j9PN-;^#v!7Ne)nMb21#k8PkHp1pUko={!$ zHLMx(@$-+XBnc&Kzdyu^SD3p59wHJ*;*fE|NZA~PLlCH@u%+}Z=*5UHwdG#h9*{2q z!XX)k{*qiA0)=|AG7ijfj#xqyqEZ0dfITAo2<`YM3j{EjD{z-G88 z@oKzEb&MKNtC8O|XruHM@>xS)35oZrScma~1CiWc70IjGg!o1=5~uVwKv_){x*ssn zmkMW^+}Gdql(0m@6-Fw~0ra-8GGQfNMIJt46|vz4=Q)+c7U)tS&+X;ek-jBK6i}l! z&1o;l#jpRZJP9gbyM|4`SG!H`k?ke`jJU9Rwj%}|02t!uA|cmX9J1O2p2jBd-4W$^ z6gwcB0M3M)A+(ot;{Q#Vk9@`}yt;xWiY-vsJ30l{# z{OeTruw82zoi3je8yL7nuS@@FMVX`KQt}R|eEwb*WXH`{@Rhka_nf|#H;3l#1%&=x z>^V^`FP*YC`z|!RcAe{vm=lxv{G(kqY)RI#WocrRE+KX}4GL-(^^jCO_^z zz!NaR`MV-Wp0F)$hM1AATa&z!?_sP52E*39?{D4^Esmt~Z(+Cd9kli|jw;6DaZ1_Q z?yrW2E4i`st)&mS_}%dAEO=pQhFxO*jC{E|cP-@4c+uK}fh+V>>h9ekU!?#l2(arrxGm@c8nVf>O%75{fs zXxclbAA(V5D5>=Hr-HVenGd5-!^S1Mif@HGdRlRYC} z{A#q!t#KeEM1^SQmPI03uLGO4XmKqmVySk-XwH|4*}@I&wvK2($rs~J(tIdko9ZlR z>!p|I+iOpbAL0K)xZFjH@!3u)>(_cIzP3%KDS#Tc)T|_|SJ%pheag0zhBF!Oke5YS z<#Wqoj9gFuhkH!xr)DpKK!s7%LG99);Qn(&t5ZBF~&Qv!kV ziKl9gpYOSQO3*J3Z1zCb&`T>&-v8`P>3}!iRh1WRMbR(PL6+I$MHY7&kvKaw#`yHE*c|rV zm6FOVdNFH%9HoMp9}31*+V=RTe3#YSc77Out+=2dQ@s@hd>-nj8gTVF|E1U zy>2m6uY&0#-Z0wig6Y73%UqYX{w9lxs zLg|$@*7+iK&#h7a{woIxU!W82qCSqeZ~T=;w(~uKz!O_DV&asep5FMfJ@qi54gtc1 z>Fc`%SXjJLOtJFve%j7d+TS{4>eK*V#*`=>AZ$u$2*5ZoIDTfzg9Ur*&}bnZU@HP zkbn8C#W2vhVxu}cZ`ILhSrgbWFla>jx<11Q)#P{2=RPU?G$O0%dilzHvkFAwe^GCV zbdL_Dm5)lgE$#vLFUdZEEK1CEEaSt)ch^B_?Vq}&Dt~YTo>+a$?Gq64idCZYSRC3> zR2@muaQl*L7FTY0NOzQ(8FlutQW#g&HZ$w{{Y3o~U)480m>scX$^`uGkJOd~{!}iM zx|rosQbnF8?P}m?s9>lG*fZmyF;gwf@iC%|Rw*Oc2GB|oiB_w-0*>Xqwt?!s|DOv` zZ$3nNA7RkD_V)Wl0aFBHb^i;JEZg19Db0I*C?;-8C{ppaVph!FGokfJr+HwFrS9gT zu_@+?Vm6dwjzYm1cj^6Qfuuu4QxZ(GXD$u*58Ut!+RA&5Kyi4R2XF zJYzYz0%ez;9X$29HJ|i2H{vXhJN!E7)r@`2SW>@ne>HVFrTG&}6{H_LZKDY1$LIQ~ z#YCDIFW+4J%(H~!i%?TU$ABp*HS_O}@4_7{4idb?rt33~#r4i(I^0K&9+WzUtq|X@ zPyF*Cy#dScV#J9f@;9Hy=9|e0ujKH68cjC2a56-q_o;e=5zqn^yu`Hr``OL&jIl3U zAc&h&&U2m{lbFD%dJXlZWiQK+x2;HJ>VHfHY=}Z)6Z)pLAszJ;pSxE*idOOk>NnIt z&B0mxX!gmqoNBSUuyKA;L%1#6i_bEGajU7Cer0OJTIvtE!GY%62O4Z~&Xg)tjw>?g zDy~JwRLY!=Xvu7jvdNL}LaBb9bTD>*Y;!FxMgLWZr=609I>o|$BU+fQ{so&Y{NDq< zE0p%g2Sb(xDPmcT=sKs%2~8^uhcww(doNN*A}k|@mK>F$7L>n}%_pL#BpfqLmn!dn zdWDCgi3&Ga;Xfe*(M;4uY|Nzk92ZKI(gEr=q2#NNn4FfGnIE3_Hnx1SrElkZD;e|R zVC+qyA{ByB3Uo>AGF-~DyXl{JxUaue64M~}G}WO~;Mwt+|J6(;vRanAQWCy6ax$u* z5hp#^^9FB@LHh9dpR28wdh3@bX2!Alxgp`X62 zKG4iiN7YQd#l{`gbe7W3)*Fvo@mjHT(rw6eetHc_zWpO*8h`MooLbIR`V z`G!R@Z~m+=et%VuSxuNs&MjAjxXLKc%PiNtPyTtX+wxy*Xmr5EN9)bmwfM_=9@(L*?omO1MqBGRSClpEE&B0máOFscC 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 v2;WP1`X2QOI;AyF=)_j9Kg From 1c6d3c9c695849fd879527a2891eb35e4cdfaaec Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 23 Jul 2018 14:39:50 +0200 Subject: [PATCH 009/119] Added xml escape characters detection when exporting object and volumes names to 3mf files --- xs/src/libslic3r/Format/3mf.cpp | 4 ++-- xs/src/libslic3r/Format/AMF.cpp | 28 +--------------------------- xs/src/libslic3r/Utils.hpp | 2 ++ xs/src/libslic3r/utils.cpp | 27 +++++++++++++++++++++++++++ 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp index 2c32db1a67..dd3500eba0 100644 --- a/xs/src/libslic3r/Format/3mf.cpp +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -1989,7 +1989,7 @@ namespace Slic3r { // stores object's name if (!obj->name.empty()) - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"name\" " << VALUE_ATTR << "=\"" << obj->name << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"name\" " << VALUE_ATTR << "=\"" << xml_escape(obj->name) << "\"/>\n"; // stores object's config data for (const std::string& key : obj->config.keys()) @@ -2012,7 +2012,7 @@ namespace Slic3r { // stores volume's name if (!volume->name.empty()) - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << volume->name << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n"; // stores volume's modifier field if (volume->modifier) diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp index be513166e5..600aa6cd97 100644 --- a/xs/src/libslic3r/Format/AMF.cpp +++ b/xs/src/libslic3r/Format/AMF.cpp @@ -8,6 +8,7 @@ #include "../libslic3r.h" #include "../Model.hpp" #include "../GCode.hpp" +#include "../Utils.hpp" #include "../slic3r/GUI/PresetBundle.hpp" #include "AMF.hpp" @@ -686,33 +687,6 @@ bool load_amf(const char *path, PresetBundle* bundle, Model *model) return false; } -std::string xml_escape(std::string text) -{ - std::string::size_type pos = 0; - for (;;) - { - pos = text.find_first_of("\"\'&<>", pos); - if (pos == std::string::npos) - break; - - std::string replacement; - switch (text[pos]) - { - case '\"': replacement = """; break; - case '\'': replacement = "'"; break; - case '&': replacement = "&"; break; - case '<': replacement = "<"; break; - case '>': replacement = ">"; break; - default: break; - } - - text.replace(pos, 1, replacement); - pos += replacement.size(); - } - - return text; -} - bool store_amf(const char *path, Model *model, Print* print, bool export_print_config) { if ((path == nullptr) || (model == nullptr) || (print == nullptr)) diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index a501fa4d3a..3492228543 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -84,6 +84,8 @@ inline T next_highest_power_of_2(T v) return ++ v; } +extern std::string xml_escape(std::string text); + class PerlCallback { public: PerlCallback(void *sv) : m_callback(nullptr) { this->register_callback(sv); } diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 13ec1d0661..55164bbdd6 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -387,4 +387,31 @@ unsigned get_current_pid() #endif } +std::string xml_escape(std::string text) +{ + std::string::size_type pos = 0; + for (;;) + { + pos = text.find_first_of("\"\'&<>", pos); + if (pos == std::string::npos) + break; + + std::string replacement; + switch (text[pos]) + { + case '\"': replacement = """; break; + case '\'': replacement = "'"; break; + case '&': replacement = "&"; break; + case '<': replacement = "<"; break; + case '>': replacement = ">"; break; + default: break; + } + + text.replace(pos, 1, replacement); + pos += replacement.size(); + } + + return text; +} + }; // namespace Slic3r From dd088ba0cc84cb3cb80c4152e388ec16feb85c67 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 23 Jul 2018 15:44:01 +0200 Subject: [PATCH 010/119] Localized the (modified) profile indicator --- resources/localization/Slic3rPE.pot | 2298 +++++++------ resources/localization/uk/Slic3rPE.mo | Bin 140068 -> 137296 bytes resources/localization/uk/Slic3rPE_uk.po | 3737 +++++++++++++++------- xs/src/slic3r/GUI/GUI.cpp | 2 + xs/src/slic3r/GUI/Preset.cpp | 5 + xs/src/slic3r/GUI/Preset.hpp | 3 +- xs/src/slic3r/GUI/Tab.cpp | 7 +- 7 files changed, 3867 insertions(+), 2185 deletions(-) diff --git a/resources/localization/Slic3rPE.pot b/resources/localization/Slic3rPE.pot index 029e7c4b2f..7fa65fc5ed 100644 --- a/resources/localization/Slic3rPE.pot +++ b/resources/localization/Slic3rPE.pot @@ -3,12 +3,13 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # +#: xs/src/slic3r/GUI/Tab.cpp:1721 #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-06-26 11:11+0200\n" +"POT-Creation-Date: 2018-07-23 12:06+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -32,8 +33,8 @@ msgstr "" msgid "Rectangular" msgstr "" -#: xs/src/slic3r/GUI/BedShapeDialog.cpp:50 xs/src/slic3r/GUI/Tab.cpp:1745 -#: lib/Slic3r/GUI/Plater.pm:477 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:50 xs/src/slic3r/GUI/Tab.cpp:1826 +#: lib/Slic3r/GUI/Plater.pm:498 msgid "Size" msgstr "" @@ -56,33 +57,33 @@ msgid "Circular" msgstr "" #: xs/src/slic3r/GUI/BedShapeDialog.cpp:65 -#: xs/src/slic3r/GUI/ConfigWizard.cpp:87 -#: xs/src/slic3r/GUI/ConfigWizard.cpp:439 -#: xs/src/slic3r/GUI/ConfigWizard.cpp:453 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:88 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:446 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:460 #: xs/src/slic3r/GUI/RammingChart.cpp:81 #: xs/src/slic3r/GUI/WipeTowerDialog.cpp:79 -#: xs/src/libslic3r/PrintConfig.cpp:130 xs/src/libslic3r/PrintConfig.cpp:173 -#: xs/src/libslic3r/PrintConfig.cpp:181 xs/src/libslic3r/PrintConfig.cpp:229 -#: xs/src/libslic3r/PrintConfig.cpp:240 xs/src/libslic3r/PrintConfig.cpp:354 -#: xs/src/libslic3r/PrintConfig.cpp:365 xs/src/libslic3r/PrintConfig.cpp:384 -#: xs/src/libslic3r/PrintConfig.cpp:497 xs/src/libslic3r/PrintConfig.cpp:851 -#: xs/src/libslic3r/PrintConfig.cpp:871 xs/src/libslic3r/PrintConfig.cpp:879 -#: xs/src/libslic3r/PrintConfig.cpp:937 xs/src/libslic3r/PrintConfig.cpp:955 -#: xs/src/libslic3r/PrintConfig.cpp:973 xs/src/libslic3r/PrintConfig.cpp:1035 -#: xs/src/libslic3r/PrintConfig.cpp:1152 xs/src/libslic3r/PrintConfig.cpp:1160 -#: xs/src/libslic3r/PrintConfig.cpp:1202 xs/src/libslic3r/PrintConfig.cpp:1211 -#: xs/src/libslic3r/PrintConfig.cpp:1221 xs/src/libslic3r/PrintConfig.cpp:1229 -#: xs/src/libslic3r/PrintConfig.cpp:1237 xs/src/libslic3r/PrintConfig.cpp:1323 -#: xs/src/libslic3r/PrintConfig.cpp:1529 xs/src/libslic3r/PrintConfig.cpp:1599 -#: xs/src/libslic3r/PrintConfig.cpp:1633 xs/src/libslic3r/PrintConfig.cpp:1829 -#: xs/src/libslic3r/PrintConfig.cpp:1836 xs/src/libslic3r/PrintConfig.cpp:1843 -#: xs/src/libslic3r/PrintConfig.cpp:1857 xs/src/libslic3r/PrintConfig.cpp:1867 -#: xs/src/libslic3r/PrintConfig.cpp:1877 +#: xs/src/libslic3r/PrintConfig.cpp:133 xs/src/libslic3r/PrintConfig.cpp:181 +#: xs/src/libslic3r/PrintConfig.cpp:189 xs/src/libslic3r/PrintConfig.cpp:237 +#: xs/src/libslic3r/PrintConfig.cpp:248 xs/src/libslic3r/PrintConfig.cpp:363 +#: xs/src/libslic3r/PrintConfig.cpp:374 xs/src/libslic3r/PrintConfig.cpp:393 +#: xs/src/libslic3r/PrintConfig.cpp:531 xs/src/libslic3r/PrintConfig.cpp:890 +#: xs/src/libslic3r/PrintConfig.cpp:1002 xs/src/libslic3r/PrintConfig.cpp:1010 +#: xs/src/libslic3r/PrintConfig.cpp:1068 xs/src/libslic3r/PrintConfig.cpp:1086 +#: xs/src/libslic3r/PrintConfig.cpp:1104 xs/src/libslic3r/PrintConfig.cpp:1166 +#: xs/src/libslic3r/PrintConfig.cpp:1176 xs/src/libslic3r/PrintConfig.cpp:1292 +#: xs/src/libslic3r/PrintConfig.cpp:1300 xs/src/libslic3r/PrintConfig.cpp:1342 +#: xs/src/libslic3r/PrintConfig.cpp:1351 xs/src/libslic3r/PrintConfig.cpp:1361 +#: xs/src/libslic3r/PrintConfig.cpp:1369 xs/src/libslic3r/PrintConfig.cpp:1377 +#: xs/src/libslic3r/PrintConfig.cpp:1463 xs/src/libslic3r/PrintConfig.cpp:1669 +#: xs/src/libslic3r/PrintConfig.cpp:1739 xs/src/libslic3r/PrintConfig.cpp:1773 +#: xs/src/libslic3r/PrintConfig.cpp:1969 xs/src/libslic3r/PrintConfig.cpp:1976 +#: xs/src/libslic3r/PrintConfig.cpp:1983 xs/src/libslic3r/PrintConfig.cpp:2015 +#: xs/src/libslic3r/PrintConfig.cpp:2025 xs/src/libslic3r/PrintConfig.cpp:2035 msgid "mm" msgstr "" #: xs/src/slic3r/GUI/BedShapeDialog.cpp:66 -#: xs/src/libslic3r/PrintConfig.cpp:494 +#: xs/src/libslic3r/PrintConfig.cpp:528 msgid "Diameter" msgstr "" @@ -93,7 +94,7 @@ msgid "" msgstr "" #: xs/src/slic3r/GUI/BedShapeDialog.cpp:71 -#: xs/src/libslic3r/GCode/PreviewData.cpp:170 +#: xs/src/libslic3r/GCode/PreviewData.cpp:175 #: lib/Slic3r/GUI/Plater/3DPreview.pm:102 msgid "Custom" msgstr "" @@ -124,7 +125,7 @@ msgid "" msgstr "" #: xs/src/slic3r/GUI/BedShapeDialog.hpp:44 -#: xs/src/slic3r/GUI/ConfigWizard.cpp:402 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:409 msgid "Bed Shape" msgstr "" @@ -210,7 +211,7 @@ msgstr "" msgid "printer" msgstr "" -#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:49 xs/src/slic3r/GUI/Tab.cpp:758 +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:49 xs/src/slic3r/GUI/Tab.cpp:730 msgid "vendor" msgstr "" @@ -242,70 +243,74 @@ msgstr "" msgid "Activate" msgstr "" -#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:96 xs/src/slic3r/GUI/GUI.cpp:323 +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:96 xs/src/slic3r/GUI/GUI.cpp:349 msgid "Configuration Snapshots" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:87 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:88 msgid "nozzle" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:105 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:89 +msgid "(default)" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:108 msgid "Select all" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:106 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:109 msgid "Select none" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:212 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:218 #, possible-c-format msgid "Welcome to the Slic3r %s" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:212 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:218 msgid "Welcome" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:218 xs/src/slic3r/GUI/GUI.cpp:320 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:224 xs/src/slic3r/GUI/GUI.cpp:346 #, possible-c-format msgid "Run %s" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:220 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:226 #, possible-c-format msgid "" "Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial " "configuration; just a few settings and you will be ready to print." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:224 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:230 msgid "" "Remove user profiles - install from scratch (a snapshot will be taken " "beforehand)" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:245 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:252 msgid "Other vendors" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:247 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:254 msgid "Custom setup" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:271 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:278 msgid "Automatic updates" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:271 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:278 msgid "Updates" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:279 xs/src/slic3r/GUI/Preferences.cpp:59 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:286 xs/src/slic3r/GUI/Preferences.cpp:59 msgid "Check for application updates" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:282 xs/src/slic3r/GUI/Preferences.cpp:61 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:289 xs/src/slic3r/GUI/Preferences.cpp:61 msgid "" "If enabled, Slic3r checks for new versions of Slic3r PE online. When a new " "version becomes available a notification is displayed at the next " @@ -313,11 +318,11 @@ msgid "" "notification mechanisms, no automatic installation is done." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:286 xs/src/slic3r/GUI/Preferences.cpp:67 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:293 xs/src/slic3r/GUI/Preferences.cpp:67 msgid "Update built-in Presets automatically" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:289 xs/src/slic3r/GUI/Preferences.cpp:69 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:296 xs/src/slic3r/GUI/Preferences.cpp:69 msgid "" "If enabled, Slic3r downloads updates of built-in system presets in the " "background. These updates are downloaded into a separate temporary location. " @@ -325,134 +330,134 @@ msgid "" "startup." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:290 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:297 msgid "" "Updates are never applied without user's consent and never overwrite user's " "customized settings." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:295 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:302 msgid "" "Additionally a backup snapshot of the whole configuration is created before " "an update is applied." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:302 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:309 msgid "Other Vendors" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:304 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:311 msgid "Pick another vendor supported by Slic3r PE:" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:363 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:370 msgid "Firmware Type" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:363 xs/src/slic3r/GUI/Tab.cpp:1628 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:370 xs/src/slic3r/GUI/Tab.cpp:1606 msgid "Firmware" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:367 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:374 msgid "Choose the type of firmware used by your printer." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:402 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:409 msgid "Bed Shape and Size" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:405 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:412 msgid "Set the shape of your printer's bed." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:419 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:426 msgid "Filament and Nozzle Diameters" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:419 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:426 msgid "Print Diameters" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:435 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:442 msgid "Enter the diameter of your printer's hot end nozzle." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:438 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:445 msgid "Nozzle Diameter:" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:448 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:455 msgid "Enter the diameter of your filament." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:449 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:456 msgid "" "Good precision is required, so use a caliper and do multiple measurements " "along the filament, then compute the average." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:452 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:459 msgid "Filament Diameter:" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:470 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:477 msgid "Extruder and Bed Temperatures" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:470 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:477 msgid "Temperatures" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:486 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:493 msgid "Enter the temperature needed for extruding your filament." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:487 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:494 msgid "A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:490 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:497 msgid "Extrusion Temperature:" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:491 -#: xs/src/slic3r/GUI/ConfigWizard.cpp:505 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:498 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:512 msgid "°C" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:500 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:507 msgid "" "Enter the bed temperature needed for getting your filament to stick to your " "heated bed." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:501 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:508 msgid "" "A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have " "no heated bed." msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:504 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:511 msgid "Bed Temperature:" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:817 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:824 msgid "< &Back" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:818 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:825 msgid "&Next >" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:819 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:826 msgid "&Finish" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:889 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:896 msgid "Configuration Wizard" msgstr "" -#: xs/src/slic3r/GUI/ConfigWizard.cpp:891 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:898 msgid "Configuration Assistant" msgstr "" @@ -526,138 +531,138 @@ msgstr "" msgid "Confirmation" msgstr "" -#: xs/src/slic3r/GUI/GLCanvas3D.cpp:1990 +#: xs/src/slic3r/GUI/GLCanvas3D.cpp:2308 msgid "Detected object outside print volume" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:208 +#: xs/src/slic3r/GUI/GUI.cpp:233 msgid "Array of language names and identifiers should have the same size." msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:219 +#: xs/src/slic3r/GUI/GUI.cpp:244 msgid "Select the language" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:219 +#: xs/src/slic3r/GUI/GUI.cpp:244 msgid "Language" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:281 xs/src/libslic3r/PrintConfig.cpp:187 +#: xs/src/slic3r/GUI/GUI.cpp:306 xs/src/libslic3r/PrintConfig.cpp:195 msgid "Default" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:323 +#: xs/src/slic3r/GUI/GUI.cpp:349 msgid "Inspect / activate configuration snapshots" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:324 +#: xs/src/slic3r/GUI/GUI.cpp:350 msgid "Take Configuration Snapshot" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:324 +#: xs/src/slic3r/GUI/GUI.cpp:350 msgid "Capture a configuration snapshot" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:327 xs/src/slic3r/GUI/Preferences.cpp:9 +#: xs/src/slic3r/GUI/GUI.cpp:353 xs/src/slic3r/GUI/Preferences.cpp:9 msgid "Preferences" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:327 +#: xs/src/slic3r/GUI/GUI.cpp:353 msgid "Application preferences" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:328 +#: xs/src/slic3r/GUI/GUI.cpp:354 msgid "Change Application Language" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:330 +#: xs/src/slic3r/GUI/GUI.cpp:356 msgid "Flash printer firmware" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:330 +#: xs/src/slic3r/GUI/GUI.cpp:356 msgid "Upload a firmware image into an Arduino based printer" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:342 +#: xs/src/slic3r/GUI/GUI.cpp:368 msgid "Taking configuration snapshot" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:342 +#: xs/src/slic3r/GUI/GUI.cpp:368 msgid "Snapshot name" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:380 +#: xs/src/slic3r/GUI/GUI.cpp:406 msgid "Application will be restarted" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:380 +#: xs/src/slic3r/GUI/GUI.cpp:406 msgid "Attention!" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:396 +#: xs/src/slic3r/GUI/GUI.cpp:422 msgid "&Configuration" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:420 +#: xs/src/slic3r/GUI/GUI.cpp:446 msgid "You have unsaved changes " msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:420 +#: xs/src/slic3r/GUI/GUI.cpp:446 msgid ". Discard changes and continue anyway?" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:421 +#: xs/src/slic3r/GUI/GUI.cpp:447 msgid "Unsaved Presets" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:629 +#: xs/src/slic3r/GUI/GUI.cpp:655 msgid "Notice" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:634 +#: xs/src/slic3r/GUI/GUI.cpp:660 msgid "Attempt to free unreferenced scalar" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:636 xs/src/slic3r/GUI/WipeTowerDialog.cpp:39 +#: xs/src/slic3r/GUI/GUI.cpp:662 xs/src/slic3r/GUI/WipeTowerDialog.cpp:39 #: xs/src/slic3r/GUI/WipeTowerDialog.cpp:321 msgid "Warning" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:825 +#: xs/src/slic3r/GUI/GUI.cpp:859 msgid "Support" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:828 +#: xs/src/slic3r/GUI/GUI.cpp:862 msgid "Select what kind of support do you need" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:829 xs/src/libslic3r/GCode/PreviewData.cpp:157 +#: xs/src/slic3r/GUI/GUI.cpp:863 xs/src/libslic3r/GCode/PreviewData.cpp:162 msgid "None" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:830 xs/src/libslic3r/PrintConfig.cpp:1516 +#: xs/src/slic3r/GUI/GUI.cpp:864 xs/src/libslic3r/PrintConfig.cpp:1656 msgid "Support on build plate only" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:831 +#: xs/src/slic3r/GUI/GUI.cpp:865 msgid "Everywhere" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:843 xs/src/slic3r/GUI/Tab.cpp:872 +#: xs/src/slic3r/GUI/GUI.cpp:877 xs/src/slic3r/GUI/Tab.cpp:844 msgid "Brim" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:845 +#: xs/src/slic3r/GUI/GUI.cpp:879 msgid "" "This flag enables the brim that will be printed around each object on the " "first layer." msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:854 +#: xs/src/slic3r/GUI/GUI.cpp:888 msgid "Purging volumes" msgstr "" -#: xs/src/slic3r/GUI/GUI.cpp:896 +#: xs/src/slic3r/GUI/GUI.cpp:930 msgid "Export print config" msgstr "" @@ -669,248 +674,249 @@ msgstr "" msgid "Slic3r has encountered an error" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:103 +#: xs/src/slic3r/GUI/Tab.cpp:84 msgid "Save current " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:104 +#: xs/src/slic3r/GUI/Tab.cpp:85 msgid "Delete this preset" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:116 +#: xs/src/slic3r/GUI/Tab.cpp:97 msgid "" "Hover the cursor over buttons to find more information \n" "or click this button." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:744 +#: xs/src/slic3r/GUI/Tab.cpp:716 msgid "It's a default preset." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:745 +#: xs/src/slic3r/GUI/Tab.cpp:717 msgid "It's a system preset." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:746 +#: xs/src/slic3r/GUI/Tab.cpp:718 msgid "Current preset is inherited from " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:751 +#: xs/src/slic3r/GUI/Tab.cpp:723 msgid "It can't be deleted or modified. " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:752 +#: xs/src/slic3r/GUI/Tab.cpp:724 msgid "" "Any modifications should be saved as a new preset inherited from this one. " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:753 +#: xs/src/slic3r/GUI/Tab.cpp:725 msgid "To do that please specify a new name for the preset." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:757 +#: xs/src/slic3r/GUI/Tab.cpp:729 msgid "Additional information:" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:765 +#: xs/src/slic3r/GUI/Tab.cpp:737 msgid "printer model" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:767 +#: xs/src/slic3r/GUI/Tab.cpp:739 msgid "default print profile" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:770 +#: xs/src/slic3r/GUI/Tab.cpp:742 msgid "default filament profile" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:814 +#: xs/src/slic3r/GUI/Tab.cpp:786 msgid "Layers and perimeters" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:815 xs/src/libslic3r/PrintConfig.cpp:847 +#: xs/src/slic3r/GUI/Tab.cpp:787 xs/src/libslic3r/PrintConfig.cpp:886 msgid "Layer height" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:819 +#: xs/src/slic3r/GUI/Tab.cpp:791 msgid "Vertical shells" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:830 +#: xs/src/slic3r/GUI/Tab.cpp:802 msgid "Horizontal shells" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:831 xs/src/libslic3r/PrintConfig.cpp:1422 +#: xs/src/slic3r/GUI/Tab.cpp:803 xs/src/libslic3r/PrintConfig.cpp:1562 msgid "Solid layers" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:836 +#: xs/src/slic3r/GUI/Tab.cpp:808 msgid "Quality (slower slicing)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:843 xs/src/slic3r/GUI/Tab.cpp:857 -#: xs/src/slic3r/GUI/Tab.cpp:951 xs/src/slic3r/GUI/Tab.cpp:954 -#: xs/src/slic3r/GUI/Tab.cpp:1303 xs/src/slic3r/GUI/Tab.cpp:1631 -#: xs/src/libslic3r/PrintConfig.cpp:107 xs/src/libslic3r/PrintConfig.cpp:237 -#: xs/src/libslic3r/PrintConfig.cpp:799 xs/src/libslic3r/PrintConfig.cpp:1863 +#: xs/src/slic3r/GUI/Tab.cpp:815 xs/src/slic3r/GUI/Tab.cpp:829 +#: xs/src/slic3r/GUI/Tab.cpp:923 xs/src/slic3r/GUI/Tab.cpp:926 +#: xs/src/slic3r/GUI/Tab.cpp:1276 xs/src/slic3r/GUI/Tab.cpp:1625 +#: xs/src/libslic3r/PrintConfig.cpp:110 xs/src/libslic3r/PrintConfig.cpp:245 +#: xs/src/libslic3r/PrintConfig.cpp:833 xs/src/libslic3r/PrintConfig.cpp:2021 msgid "Advanced" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:847 xs/src/slic3r/GUI/Tab.cpp:848 -#: xs/src/slic3r/GUI/Tab.cpp:1155 xs/src/libslic3r/PrintConfig.cpp:87 -#: xs/src/libslic3r/PrintConfig.cpp:276 xs/src/libslic3r/PrintConfig.cpp:551 -#: xs/src/libslic3r/PrintConfig.cpp:565 xs/src/libslic3r/PrintConfig.cpp:603 -#: xs/src/libslic3r/PrintConfig.cpp:744 xs/src/libslic3r/PrintConfig.cpp:754 -#: xs/src/libslic3r/PrintConfig.cpp:772 xs/src/libslic3r/PrintConfig.cpp:790 -#: xs/src/libslic3r/PrintConfig.cpp:809 xs/src/libslic3r/PrintConfig.cpp:1371 -#: xs/src/libslic3r/PrintConfig.cpp:1388 +#: xs/src/slic3r/GUI/Tab.cpp:819 xs/src/slic3r/GUI/Tab.cpp:820 +#: xs/src/slic3r/GUI/Tab.cpp:1127 xs/src/libslic3r/PrintConfig.cpp:90 +#: xs/src/libslic3r/PrintConfig.cpp:284 xs/src/libslic3r/PrintConfig.cpp:585 +#: xs/src/libslic3r/PrintConfig.cpp:599 xs/src/libslic3r/PrintConfig.cpp:637 +#: xs/src/libslic3r/PrintConfig.cpp:778 xs/src/libslic3r/PrintConfig.cpp:788 +#: xs/src/libslic3r/PrintConfig.cpp:806 xs/src/libslic3r/PrintConfig.cpp:824 +#: xs/src/libslic3r/PrintConfig.cpp:843 xs/src/libslic3r/PrintConfig.cpp:1511 +#: xs/src/libslic3r/PrintConfig.cpp:1528 msgid "Infill" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:853 +#: xs/src/slic3r/GUI/Tab.cpp:825 msgid "Reducing printing time" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:865 +#: xs/src/slic3r/GUI/Tab.cpp:837 msgid "Skirt and brim" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:866 xs/src/libslic3r/GCode/PreviewData.cpp:166 +#: xs/src/slic3r/GUI/Tab.cpp:838 xs/src/libslic3r/GCode/PreviewData.cpp:171 #: lib/Slic3r/GUI/Plater/3DPreview.pm:98 msgid "Skirt" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:875 xs/src/slic3r/GUI/Tab.cpp:876 -#: xs/src/libslic3r/PrintConfig.cpp:220 xs/src/libslic3r/PrintConfig.cpp:1138 -#: xs/src/libslic3r/PrintConfig.cpp:1488 xs/src/libslic3r/PrintConfig.cpp:1495 -#: xs/src/libslic3r/PrintConfig.cpp:1507 xs/src/libslic3r/PrintConfig.cpp:1517 -#: xs/src/libslic3r/PrintConfig.cpp:1525 xs/src/libslic3r/PrintConfig.cpp:1540 -#: xs/src/libslic3r/PrintConfig.cpp:1561 xs/src/libslic3r/PrintConfig.cpp:1572 -#: xs/src/libslic3r/PrintConfig.cpp:1588 xs/src/libslic3r/PrintConfig.cpp:1597 -#: xs/src/libslic3r/PrintConfig.cpp:1606 xs/src/libslic3r/PrintConfig.cpp:1617 -#: xs/src/libslic3r/PrintConfig.cpp:1631 xs/src/libslic3r/PrintConfig.cpp:1639 -#: xs/src/libslic3r/PrintConfig.cpp:1640 xs/src/libslic3r/PrintConfig.cpp:1649 -#: xs/src/libslic3r/PrintConfig.cpp:1657 xs/src/libslic3r/PrintConfig.cpp:1671 -#: xs/src/libslic3r/GCode/PreviewData.cpp:167 +#: xs/src/slic3r/GUI/Tab.cpp:847 xs/src/slic3r/GUI/Tab.cpp:848 +#: xs/src/libslic3r/PrintConfig.cpp:228 xs/src/libslic3r/PrintConfig.cpp:1278 +#: xs/src/libslic3r/PrintConfig.cpp:1628 xs/src/libslic3r/PrintConfig.cpp:1635 +#: xs/src/libslic3r/PrintConfig.cpp:1647 xs/src/libslic3r/PrintConfig.cpp:1657 +#: xs/src/libslic3r/PrintConfig.cpp:1665 xs/src/libslic3r/PrintConfig.cpp:1680 +#: xs/src/libslic3r/PrintConfig.cpp:1701 xs/src/libslic3r/PrintConfig.cpp:1712 +#: xs/src/libslic3r/PrintConfig.cpp:1728 xs/src/libslic3r/PrintConfig.cpp:1737 +#: xs/src/libslic3r/PrintConfig.cpp:1746 xs/src/libslic3r/PrintConfig.cpp:1757 +#: xs/src/libslic3r/PrintConfig.cpp:1771 xs/src/libslic3r/PrintConfig.cpp:1779 +#: xs/src/libslic3r/PrintConfig.cpp:1780 xs/src/libslic3r/PrintConfig.cpp:1789 +#: xs/src/libslic3r/PrintConfig.cpp:1797 xs/src/libslic3r/PrintConfig.cpp:1811 +#: xs/src/libslic3r/GCode/PreviewData.cpp:172 #: lib/Slic3r/GUI/Plater/3DPreview.pm:99 msgid "Support material" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:881 +#: xs/src/slic3r/GUI/Tab.cpp:853 msgid "Raft" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:885 +#: xs/src/slic3r/GUI/Tab.cpp:857 msgid "Options for support material and raft" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:899 xs/src/libslic3r/PrintConfig.cpp:119 -#: xs/src/libslic3r/PrintConfig.cpp:307 xs/src/libslic3r/PrintConfig.cpp:698 -#: xs/src/libslic3r/PrintConfig.cpp:810 xs/src/libslic3r/PrintConfig.cpp:1072 -#: xs/src/libslic3r/PrintConfig.cpp:1309 xs/src/libslic3r/PrintConfig.cpp:1359 -#: xs/src/libslic3r/PrintConfig.cpp:1410 xs/src/libslic3r/PrintConfig.cpp:1731 +#: xs/src/slic3r/GUI/Tab.cpp:871 xs/src/libslic3r/PrintConfig.cpp:122 +#: xs/src/libslic3r/PrintConfig.cpp:315 xs/src/libslic3r/PrintConfig.cpp:732 +#: xs/src/libslic3r/PrintConfig.cpp:844 xs/src/libslic3r/PrintConfig.cpp:1212 +#: xs/src/libslic3r/PrintConfig.cpp:1449 xs/src/libslic3r/PrintConfig.cpp:1499 +#: xs/src/libslic3r/PrintConfig.cpp:1550 xs/src/libslic3r/PrintConfig.cpp:1871 #: lib/Slic3r/GUI/Plater/3DPreview.pm:77 msgid "Speed" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:900 +#: xs/src/slic3r/GUI/Tab.cpp:872 msgid "Speed for print moves" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:912 +#: xs/src/slic3r/GUI/Tab.cpp:884 msgid "Speed for non-print moves" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:915 +#: xs/src/slic3r/GUI/Tab.cpp:887 msgid "Modifiers" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:918 +#: xs/src/slic3r/GUI/Tab.cpp:890 msgid "Acceleration control (advanced)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:925 +#: xs/src/slic3r/GUI/Tab.cpp:897 msgid "Autospeed (advanced)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:931 +#: xs/src/slic3r/GUI/Tab.cpp:903 msgid "Multiple Extruders" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:932 xs/src/slic3r/GUI/Tab.cpp:1473 -#: xs/src/libslic3r/PrintConfig.cpp:337 xs/src/libslic3r/PrintConfig.cpp:765 -#: xs/src/libslic3r/PrintConfig.cpp:1051 xs/src/libslic3r/PrintConfig.cpp:1380 -#: xs/src/libslic3r/PrintConfig.cpp:1553 xs/src/libslic3r/PrintConfig.cpp:1579 +#: xs/src/slic3r/GUI/Tab.cpp:904 xs/src/slic3r/GUI/Tab.cpp:1451 +#: xs/src/libslic3r/PrintConfig.cpp:345 xs/src/libslic3r/PrintConfig.cpp:799 +#: xs/src/libslic3r/PrintConfig.cpp:1191 xs/src/libslic3r/PrintConfig.cpp:1520 +#: xs/src/libslic3r/PrintConfig.cpp:1693 xs/src/libslic3r/PrintConfig.cpp:1719 +#: xs/src/libslic3r/PrintConfig.cpp:1995 xs/src/libslic3r/PrintConfig.cpp:2004 msgid "Extruders" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:939 +#: xs/src/slic3r/GUI/Tab.cpp:911 msgid "Ooze prevention" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:943 xs/src/libslic3r/GCode/PreviewData.cpp:169 +#: xs/src/slic3r/GUI/Tab.cpp:915 xs/src/libslic3r/GCode/PreviewData.cpp:174 #: lib/Slic3r/GUI/Plater/3DPreview.pm:101 msgid "Wipe tower" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:955 +#: xs/src/slic3r/GUI/Tab.cpp:927 msgid "Extrusion width" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:965 +#: xs/src/slic3r/GUI/Tab.cpp:937 msgid "Overlap" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:968 +#: xs/src/slic3r/GUI/Tab.cpp:940 msgid "Flow" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:971 +#: xs/src/slic3r/GUI/Tab.cpp:943 msgid "Other" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:978 +#: xs/src/slic3r/GUI/Tab.cpp:950 msgid "Output options" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:979 +#: xs/src/slic3r/GUI/Tab.cpp:951 msgid "Sequential printing" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:981 +#: xs/src/slic3r/GUI/Tab.cpp:953 msgid "Extruder clearance (mm)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:990 +#: xs/src/slic3r/GUI/Tab.cpp:962 msgid "Output file" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:996 xs/src/libslic3r/PrintConfig.cpp:1094 +#: xs/src/slic3r/GUI/Tab.cpp:968 xs/src/libslic3r/PrintConfig.cpp:1234 msgid "Post-processing scripts" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1002 xs/src/slic3r/GUI/Tab.cpp:1003 -#: xs/src/slic3r/GUI/Tab.cpp:1352 xs/src/slic3r/GUI/Tab.cpp:1353 -#: xs/src/slic3r/GUI/Tab.cpp:1674 xs/src/slic3r/GUI/Tab.cpp:1675 +#: xs/src/slic3r/GUI/Tab.cpp:974 xs/src/slic3r/GUI/Tab.cpp:975 +#: xs/src/slic3r/GUI/Tab.cpp:1329 xs/src/slic3r/GUI/Tab.cpp:1330 +#: xs/src/slic3r/GUI/Tab.cpp:1668 xs/src/slic3r/GUI/Tab.cpp:1669 msgid "Notes" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1009 xs/src/slic3r/GUI/Tab.cpp:1360 -#: xs/src/slic3r/GUI/Tab.cpp:1681 +#: xs/src/slic3r/GUI/Tab.cpp:981 xs/src/slic3r/GUI/Tab.cpp:1337 +#: xs/src/slic3r/GUI/Tab.cpp:1675 msgid "Dependencies" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1010 xs/src/slic3r/GUI/Tab.cpp:1361 -#: xs/src/slic3r/GUI/Tab.cpp:1682 +#: xs/src/slic3r/GUI/Tab.cpp:982 xs/src/slic3r/GUI/Tab.cpp:1338 +#: xs/src/slic3r/GUI/Tab.cpp:1676 msgid "Profile dependencies" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1011 xs/src/slic3r/GUI/Tab.cpp:1362 -#: xs/src/slic3r/GUI/Tab.cpp:2261 xs/src/libslic3r/PrintConfig.cpp:144 +#: xs/src/slic3r/GUI/Tab.cpp:983 xs/src/slic3r/GUI/Tab.cpp:1339 +#: xs/src/slic3r/GUI/Tab.cpp:2364 xs/src/libslic3r/PrintConfig.cpp:147 msgid "Compatible printers" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1044 +#: xs/src/slic3r/GUI/Tab.cpp:1016 #, no-c-format msgid "" "The Spiral Vase mode requires:\n" @@ -923,11 +929,11 @@ msgid "" "Shall I adjust those settings in order to enable Spiral Vase?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1051 +#: xs/src/slic3r/GUI/Tab.cpp:1023 msgid "Spiral Vase" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1072 +#: xs/src/slic3r/GUI/Tab.cpp:1044 msgid "" "The Wipe Tower currently supports the non-soluble supports only\n" "if they are printed with the current extruder without triggering a tool " @@ -938,11 +944,11 @@ msgid "" "Shall I adjust those settings in order to enable the Wipe Tower?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1076 xs/src/slic3r/GUI/Tab.cpp:1093 +#: xs/src/slic3r/GUI/Tab.cpp:1048 xs/src/slic3r/GUI/Tab.cpp:1065 msgid "Wipe Tower" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1090 +#: xs/src/slic3r/GUI/Tab.cpp:1062 msgid "" "For the Wipe Tower to work with the soluble supports, the support layers\n" "need to be synchronized with the object layers.\n" @@ -950,7 +956,7 @@ msgid "" "Shall I synchronize support layers in order to enable the Wipe Tower?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1108 +#: xs/src/slic3r/GUI/Tab.cpp:1080 msgid "" "Supports work better, if the following feature is enabled:\n" "- Detect bridging perimeters\n" @@ -958,15 +964,15 @@ msgid "" "Shall I adjust those settings for supports?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1111 +#: xs/src/slic3r/GUI/Tab.cpp:1083 msgid "Support Generator" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1153 +#: xs/src/slic3r/GUI/Tab.cpp:1125 msgid "The " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1153 +#: xs/src/slic3r/GUI/Tab.cpp:1125 #, no-c-format msgid "" " infill pattern is not supposed to work at 100% density.\n" @@ -974,343 +980,384 @@ msgid "" "Shall I switch to rectilinear fill pattern?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1258 xs/src/slic3r/GUI/Tab.cpp:1259 -#: lib/Slic3r/GUI/Plater.pm:433 +#: xs/src/slic3r/GUI/Tab.cpp:1231 xs/src/slic3r/GUI/Tab.cpp:1232 +#: lib/Slic3r/GUI/Plater.pm:454 msgid "Filament" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1266 +#: xs/src/slic3r/GUI/Tab.cpp:1239 msgid "Temperature " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1267 xs/src/libslic3r/PrintConfig.cpp:336 +#: xs/src/slic3r/GUI/Tab.cpp:1240 xs/src/libslic3r/PrintConfig.cpp:344 msgid "Extruder" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1272 +#: xs/src/slic3r/GUI/Tab.cpp:1245 msgid "Bed" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1277 +#: xs/src/slic3r/GUI/Tab.cpp:1250 msgid "Cooling" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1278 xs/src/libslic3r/PrintConfig.cpp:1006 -#: xs/src/libslic3r/PrintConfig.cpp:1801 +#: xs/src/slic3r/GUI/Tab.cpp:1251 xs/src/libslic3r/PrintConfig.cpp:1137 +#: xs/src/libslic3r/PrintConfig.cpp:1941 msgid "Enable" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1289 +#: xs/src/slic3r/GUI/Tab.cpp:1262 msgid "Fan settings" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1290 +#: xs/src/slic3r/GUI/Tab.cpp:1263 msgid "Fan speed" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1298 +#: xs/src/slic3r/GUI/Tab.cpp:1271 msgid "Cooling thresholds" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1304 +#: xs/src/slic3r/GUI/Tab.cpp:1277 msgid "Filament properties" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1308 +#: xs/src/slic3r/GUI/Tab.cpp:1281 msgid "Print speed override" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1318 +#: xs/src/slic3r/GUI/Tab.cpp:1291 msgid "Toolchange parameters with single extruder MM printers" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1322 +#: xs/src/slic3r/GUI/Tab.cpp:1299 msgid "Ramming" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1324 +#: xs/src/slic3r/GUI/Tab.cpp:1301 msgid "Ramming settings" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1339 xs/src/slic3r/GUI/Tab.cpp:1637 +#: xs/src/slic3r/GUI/Tab.cpp:1316 xs/src/slic3r/GUI/Tab.cpp:1631 msgid "Custom G-code" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1340 xs/src/slic3r/GUI/Tab.cpp:1638 -#: xs/src/libslic3r/PrintConfig.cpp:1450 xs/src/libslic3r/PrintConfig.cpp:1465 +#: xs/src/slic3r/GUI/Tab.cpp:1317 xs/src/slic3r/GUI/Tab.cpp:1632 +#: xs/src/libslic3r/PrintConfig.cpp:1590 xs/src/libslic3r/PrintConfig.cpp:1605 msgid "Start G-code" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1346 xs/src/slic3r/GUI/Tab.cpp:1644 -#: xs/src/libslic3r/PrintConfig.cpp:246 xs/src/libslic3r/PrintConfig.cpp:256 +#: xs/src/slic3r/GUI/Tab.cpp:1323 xs/src/slic3r/GUI/Tab.cpp:1638 +#: xs/src/libslic3r/PrintConfig.cpp:254 xs/src/libslic3r/PrintConfig.cpp:264 msgid "End G-code" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1441 xs/src/slic3r/GUI/Preferences.cpp:17 +#: xs/src/slic3r/GUI/Tab.cpp:1419 xs/src/slic3r/GUI/Preferences.cpp:17 msgid "General" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1442 +#: xs/src/slic3r/GUI/Tab.cpp:1420 msgid "Size and coordinates" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1444 xs/src/libslic3r/PrintConfig.cpp:34 +#: xs/src/slic3r/GUI/Tab.cpp:1422 xs/src/libslic3r/PrintConfig.cpp:37 msgid "Bed shape" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1446 xs/src/slic3r/GUI/Tab.cpp:2229 +#: xs/src/slic3r/GUI/Tab.cpp:1424 xs/src/slic3r/GUI/Tab.cpp:2332 msgid " Set " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1469 +#: xs/src/slic3r/GUI/Tab.cpp:1447 msgid "Capabilities" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1474 +#: xs/src/slic3r/GUI/Tab.cpp:1452 msgid "Number of extruders of the printer." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1499 +#: xs/src/slic3r/GUI/Tab.cpp:1477 msgid "USB/Serial connection" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1500 xs/src/libslic3r/PrintConfig.cpp:1301 +#: xs/src/slic3r/GUI/Tab.cpp:1478 xs/src/libslic3r/PrintConfig.cpp:1441 msgid "Serial port" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1505 +#: xs/src/slic3r/GUI/Tab.cpp:1483 msgid "Rescan serial ports" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1514 xs/src/slic3r/GUI/Tab.cpp:1561 +#: xs/src/slic3r/GUI/Tab.cpp:1492 xs/src/slic3r/GUI/Tab.cpp:1539 msgid "Test" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1527 +#: xs/src/slic3r/GUI/Tab.cpp:1505 msgid "Connection to printer works correctly." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1527 xs/src/slic3r/GUI/Tab.cpp:1571 +#: xs/src/slic3r/GUI/Tab.cpp:1505 xs/src/slic3r/GUI/Tab.cpp:1549 msgid "Success!" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1530 +#: xs/src/slic3r/GUI/Tab.cpp:1508 msgid "Connection failed." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1542 xs/src/slic3r/Utils/OctoPrint.cpp:110 +#: xs/src/slic3r/GUI/Tab.cpp:1520 xs/src/slic3r/Utils/OctoPrint.cpp:110 msgid "OctoPrint upload" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1545 xs/src/slic3r/GUI/Tab.cpp:1594 +#: xs/src/slic3r/GUI/Tab.cpp:1523 xs/src/slic3r/GUI/Tab.cpp:1572 msgid " Browse " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1571 +#: xs/src/slic3r/GUI/Tab.cpp:1549 msgid "Connection to OctoPrint works correctly." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1574 +#: xs/src/slic3r/GUI/Tab.cpp:1552 msgid "Could not connect to OctoPrint" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1574 +#: xs/src/slic3r/GUI/Tab.cpp:1552 msgid "Note: OctoPrint version at least 1.1.0 is required." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1600 +#: xs/src/slic3r/GUI/Tab.cpp:1578 msgid "Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1601 +#: xs/src/slic3r/GUI/Tab.cpp:1579 msgid "Open CA certificate file" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1615 +#: xs/src/slic3r/GUI/Tab.cpp:1593 msgid "" "HTTPS CA file is optional. It is only needed if you use HTTPS with a self-" "signed certificate." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1650 xs/src/libslic3r/PrintConfig.cpp:48 +#: xs/src/slic3r/GUI/Tab.cpp:1644 xs/src/libslic3r/PrintConfig.cpp:51 msgid "Before layer change G-code" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1656 xs/src/libslic3r/PrintConfig.cpp:836 +#: xs/src/slic3r/GUI/Tab.cpp:1650 xs/src/libslic3r/PrintConfig.cpp:875 msgid "After layer change G-code" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1662 xs/src/libslic3r/PrintConfig.cpp:1708 +#: xs/src/slic3r/GUI/Tab.cpp:1656 xs/src/libslic3r/PrintConfig.cpp:1848 msgid "Tool change G-code" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1668 +#: xs/src/slic3r/GUI/Tab.cpp:1662 msgid "Between objects G-code (for sequential printing)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1720 xs/src/slic3r/GUI/Tab.cpp:1728 +#: xs/src/slic3r/GUI/Tab.cpp:1717 xs/src/slic3r/GUI/Tab.cpp:1778 +#: xs/src/slic3r/GUI/Tab.cpp:2037 xs/src/libslic3r/PrintConfig.cpp:920 +#: xs/src/libslic3r/PrintConfig.cpp:929 xs/src/libslic3r/PrintConfig.cpp:938 +#: xs/src/libslic3r/PrintConfig.cpp:950 xs/src/libslic3r/PrintConfig.cpp:960 +#: xs/src/libslic3r/PrintConfig.cpp:970 xs/src/libslic3r/PrintConfig.cpp:980 +msgid "Machine limits" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1730 +msgid "Values in this column are for Full Power mode" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1731 +msgid "Full Power" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1736 +msgid "Values in this column are for Silent mode" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1737 +msgid "Silent" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1745 +msgid "Maximum feedrates" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1750 +msgid "Maximum accelerations" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1757 +msgid "Jerk limits" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1762 +msgid "Minimum feedrates" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1800 xs/src/slic3r/GUI/Tab.cpp:1808 +#: xs/src/slic3r/GUI/Tab.cpp:2037 msgid "Single extruder MM setup" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1729 +#: xs/src/slic3r/GUI/Tab.cpp:1809 msgid "Single extruder multimaterial parameters" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1741 xs/src/libslic3r/GCode/PreviewData.cpp:440 +#: xs/src/slic3r/GUI/Tab.cpp:1822 xs/src/libslic3r/GCode/PreviewData.cpp:446 #, possible-c-format msgid "Extruder %d" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1748 +#: xs/src/slic3r/GUI/Tab.cpp:1829 msgid "Layer height limits" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1753 +#: xs/src/slic3r/GUI/Tab.cpp:1834 msgid "Position (for multi-extruder printers)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1756 +#: xs/src/slic3r/GUI/Tab.cpp:1837 msgid "Retraction" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1759 +#: xs/src/slic3r/GUI/Tab.cpp:1840 msgid "Only lift Z" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1772 +#: xs/src/slic3r/GUI/Tab.cpp:1853 msgid "" "Retraction when tool is disabled (advanced settings for multi-extruder " "setups)" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1776 lib/Slic3r/GUI/Plater.pm:192 -#: lib/Slic3r/GUI/Plater.pm:2283 +#: xs/src/slic3r/GUI/Tab.cpp:1857 lib/Slic3r/GUI/Plater.pm:217 +#: lib/Slic3r/GUI/Plater.pm:2324 msgid "Preview" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1855 +#: xs/src/slic3r/GUI/Tab.cpp:1953 msgid "" "The Wipe option is not available when using the Firmware Retraction mode.\n" "\n" "Shall I disable it in order to enable Firmware Retraction?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:1857 +#: xs/src/slic3r/GUI/Tab.cpp:1955 msgid "Firmware Retraction" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2029 +#: xs/src/slic3r/GUI/Tab.cpp:2130 msgid "Default " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2029 +#: xs/src/slic3r/GUI/Tab.cpp:2130 msgid " preset" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2030 +#: xs/src/slic3r/GUI/Tab.cpp:2131 msgid " preset\n" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2048 +#: xs/src/slic3r/GUI/Tab.cpp:2149 msgid "" "\n" "\n" "is not compatible with printer\n" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2048 +#: xs/src/slic3r/GUI/Tab.cpp:2149 msgid "" "\n" "\n" "and it has the following unsaved changes:" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2049 +#: xs/src/slic3r/GUI/Tab.cpp:2150 msgid "" "\n" "\n" "has the following unsaved changes:" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2051 +#: xs/src/slic3r/GUI/Tab.cpp:2152 msgid "" "\n" "\n" "Discard changes and continue anyway?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2052 +#: xs/src/slic3r/GUI/Tab.cpp:2153 msgid "Unsaved Changes" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2139 +#: xs/src/slic3r/GUI/Tab.cpp:2240 msgid "The supplied name is empty. It can't be saved." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2144 +#: xs/src/slic3r/GUI/Tab.cpp:2245 msgid "Cannot overwrite a system profile." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2148 +#: xs/src/slic3r/GUI/Tab.cpp:2249 msgid "Cannot overwrite an external profile." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2172 +#: xs/src/slic3r/GUI/Tab.cpp:2275 msgid "remove" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2172 +#: xs/src/slic3r/GUI/Tab.cpp:2275 msgid "delete" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2173 +#: xs/src/slic3r/GUI/Tab.cpp:2276 msgid "Are you sure you want to " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2173 +#: xs/src/slic3r/GUI/Tab.cpp:2276 msgid " the selected preset?" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2174 +#: xs/src/slic3r/GUI/Tab.cpp:2277 msgid "Remove" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2174 lib/Slic3r/GUI/Plater.pm:233 -#: lib/Slic3r/GUI/Plater.pm:251 lib/Slic3r/GUI/Plater.pm:2174 +#: xs/src/slic3r/GUI/Tab.cpp:2277 lib/Slic3r/GUI/Plater.pm:251 +#: lib/Slic3r/GUI/Plater.pm:269 lib/Slic3r/GUI/Plater.pm:2215 msgid "Delete" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2175 +#: xs/src/slic3r/GUI/Tab.cpp:2278 msgid " Preset" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2228 +#: xs/src/slic3r/GUI/Tab.cpp:2331 msgid "All" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2260 +#: xs/src/slic3r/GUI/Tab.cpp:2363 msgid "Select the printers this profile is compatible with." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2306 xs/src/slic3r/GUI/Tab.cpp:2392 -#: xs/src/slic3r/GUI/Preset.cpp:613 xs/src/slic3r/GUI/Preset.cpp:653 -#: xs/src/slic3r/GUI/Preset.cpp:678 xs/src/slic3r/GUI/Preset.cpp:710 -#: xs/src/slic3r/GUI/PresetBundle.cpp:1119 -#: xs/src/slic3r/GUI/PresetBundle.cpp:1172 lib/Slic3r/GUI/Plater.pm:618 +#: xs/src/slic3r/GUI/Tab.cpp:2409 xs/src/slic3r/GUI/Tab.cpp:2495 +#: xs/src/slic3r/GUI/Preset.cpp:702 xs/src/slic3r/GUI/Preset.cpp:742 +#: xs/src/slic3r/GUI/Preset.cpp:770 xs/src/slic3r/GUI/Preset.cpp:802 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1193 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1246 lib/Slic3r/GUI/Plater.pm:603 msgid "System presets" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2307 xs/src/slic3r/GUI/Tab.cpp:2393 +#: xs/src/slic3r/GUI/Tab.cpp:2410 xs/src/slic3r/GUI/Tab.cpp:2496 msgid "Default presets" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2462 +#: xs/src/slic3r/GUI/Tab.cpp:2565 msgid "" "LOCKED LOCK;indicates that the settings are the same as the system values " "for the current option group" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2465 +#: xs/src/slic3r/GUI/Tab.cpp:2568 msgid "" "UNLOCKED LOCK;indicates that some settings were changed and are not equal to " "the system values for the current option group.\n" @@ -1318,13 +1365,13 @@ msgid "" "to the system values." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2471 +#: xs/src/slic3r/GUI/Tab.cpp:2574 msgid "" "WHITE BULLET;for the left button: \tindicates a non-system preset,\n" "for the right button: \tindicates that the settings hasn't been modified." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2475 +#: xs/src/slic3r/GUI/Tab.cpp:2578 msgid "" "BACK ARROW;indicates that the settings were changed and are not equal to the " "last saved preset for the current option group.\n" @@ -1332,30 +1379,30 @@ msgid "" "to the last saved preset." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2501 +#: xs/src/slic3r/GUI/Tab.cpp:2604 msgid "" "LOCKED LOCK icon indicates that the settings are the same as the system " "values for the current option group" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2503 +#: xs/src/slic3r/GUI/Tab.cpp:2606 msgid "" "UNLOCKED LOCK icon indicates that some settings were changed and are not " "equal to the system values for the current option group.\n" "Click to reset all settings for current option group to the system values." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2506 +#: xs/src/slic3r/GUI/Tab.cpp:2609 msgid "WHITE BULLET icon indicates a non system preset." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2509 +#: xs/src/slic3r/GUI/Tab.cpp:2612 msgid "" "WHITE BULLET icon indicates that the settings are the same as in the last " "saved preset for the current option group." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2511 +#: xs/src/slic3r/GUI/Tab.cpp:2614 msgid "" "BACK ARROW icon indicates that the settings were changed and are not equal " "to the last saved preset for the current option group.\n" @@ -1363,87 +1410,91 @@ msgid "" "preset." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2517 +#: xs/src/slic3r/GUI/Tab.cpp:2620 msgid "" "LOCKED LOCK icon indicates that the value is the same as the system value." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2518 +#: xs/src/slic3r/GUI/Tab.cpp:2621 msgid "" "UNLOCKED LOCK icon indicates that the value was changed and is not equal to " "the system value.\n" "Click to reset current value to the system value." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2524 +#: xs/src/slic3r/GUI/Tab.cpp:2627 msgid "" "WHITE BULLET icon indicates that the value is the same as in the last saved " "preset." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2525 +#: xs/src/slic3r/GUI/Tab.cpp:2628 msgid "" "BACK ARROW icon indicates that the value was changed and is not equal to the " "last saved preset.\n" "Click to reset current value to the last saved preset." msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2595 lib/Slic3r/GUI/MainFrame.pm:450 -#: lib/Slic3r/GUI/Plater.pm:1756 +#: xs/src/slic3r/GUI/Tab.cpp:2703 lib/Slic3r/GUI/MainFrame.pm:469 +#: lib/Slic3r/GUI/Plater.pm:1795 msgid "Save " msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2595 +#: xs/src/slic3r/GUI/Tab.cpp:2703 msgid " as:" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2634 xs/src/slic3r/GUI/Tab.cpp:2638 +#: xs/src/slic3r/GUI/Tab.cpp:2742 xs/src/slic3r/GUI/Tab.cpp:2746 msgid "The supplied name is not valid;" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2635 +#: xs/src/slic3r/GUI/Tab.cpp:2743 msgid "the following characters are not allowed:" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2639 +#: xs/src/slic3r/GUI/Tab.cpp:2747 msgid "the following postfix are not allowed:" msgstr "" -#: xs/src/slic3r/GUI/Tab.cpp:2642 +#: xs/src/slic3r/GUI/Tab.cpp:2750 msgid "The supplied name is not available." msgstr "" -#: xs/src/slic3r/GUI/Tab.hpp:283 +#: xs/src/slic3r/GUI/Tab.hpp:286 msgid "Print Settings" msgstr "" -#: xs/src/slic3r/GUI/Tab.hpp:303 +#: xs/src/slic3r/GUI/Tab.hpp:306 msgid "Filament Settings" msgstr "" -#: xs/src/slic3r/GUI/Tab.hpp:326 +#: xs/src/slic3r/GUI/Tab.hpp:332 msgid "Printer Settings" msgstr "" -#: xs/src/slic3r/GUI/Tab.hpp:341 +#: xs/src/slic3r/GUI/Tab.hpp:348 msgid "Save preset" msgstr "" -#: xs/src/slic3r/GUI/Field.cpp:82 +#: xs/src/slic3r/GUI/Field.cpp:98 msgid "default" msgstr "" -#: xs/src/slic3r/GUI/Field.cpp:112 +#: xs/src/slic3r/GUI/Field.cpp:128 #, possible-c-format msgid "%s doesn't support percentage" msgstr "" -#: xs/src/slic3r/GUI/Field.cpp:121 +#: xs/src/slic3r/GUI/Field.cpp:137 msgid "Input value is out of range" msgstr "" -#: xs/src/slic3r/GUI/Preset.cpp:657 xs/src/slic3r/GUI/Preset.cpp:714 -#: xs/src/slic3r/GUI/PresetBundle.cpp:1177 lib/Slic3r/GUI/Plater.pm:619 +#: xs/src/slic3r/GUI/Preset.cpp:144 +msgid "modified" +msgstr "" + +#: xs/src/slic3r/GUI/Preset.cpp:746 xs/src/slic3r/GUI/Preset.cpp:806 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1251 lib/Slic3r/GUI/Plater.pm:604 msgid "User presets" msgstr "" @@ -1659,7 +1710,7 @@ msgstr "" #: xs/src/slic3r/GUI/RammingChart.cpp:76 xs/src/slic3r/GUI/RammingChart.cpp:81 #: xs/src/slic3r/GUI/WipeTowerDialog.cpp:77 -#: xs/src/libslic3r/PrintConfig.cpp:481 +#: xs/src/libslic3r/PrintConfig.cpp:490 msgid "s" msgstr "" @@ -1876,56 +1927,156 @@ msgstr "" msgid "Error while uploading to the OctoPrint server" msgstr "" -#: xs/src/slic3r/Utils/OctoPrint.cpp:111 lib/Slic3r/GUI/Plater.pm:1559 +#: xs/src/slic3r/Utils/OctoPrint.cpp:111 lib/Slic3r/GUI/Plater.pm:1558 msgid "Sending G-code file to the OctoPrint server..." msgstr "" -#: xs/src/slic3r/Utils/OctoPrint.cpp:192 -msgid "Invalid API key" -msgstr "" - -#: xs/src/slic3r/Utils/PresetUpdater.cpp:533 +#: xs/src/slic3r/Utils/PresetUpdater.cpp:544 #, possible-c-format msgid "requires min. %s and max. %s" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:26 +#: xs/src/libslic3r/Print.cpp:553 +msgid "All objects are outside of the print volume." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:579 +msgid "Some objects are too close; your extruder will collide with them." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:594 +msgid "" +"Some objects are too tall and cannot be printed without extruder collisions." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:604 +msgid "The Spiral Vase option can only be used when printing a single object." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:606 +msgid "" +"The Spiral Vase option can only be used when printing single material " +"objects." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:612 +msgid "" +"All extruders must have the same diameter for single extruder multimaterial " +"printer." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:617 +msgid "" +"The Wipe Tower is currently only supported for the Marlin and RepRap/" +"Sprinter G-code flavors." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:619 +msgid "" +"The Wipe Tower is currently only supported with the relative extruder " +"addressing (use_relative_e_distances=1)." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:631 +msgid "" +"The Wipe Tower is only supported for multiple objects if they have equal " +"layer heigths" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:633 +msgid "" +"The Wipe Tower is only supported for multiple objects if they are printed " +"over an equal number of raft layers" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:635 +msgid "" +"The Wipe Tower is only supported for multiple objects if they are printed " +"with the same support_material_contact_distance" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:637 +msgid "" +"The Wipe Tower is only supported for multiple objects if they are sliced " +"equally." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:661 +msgid "" +"The Wipe tower is only supported if all objects have the same layer height " +"profile" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:670 +msgid "The supplied settings will cause an empty print." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:680 +msgid "" +"One or more object were assigned an extruder that the printer does not have." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:689 +msgid "" +"Printing with multiple extruders of differing nozzle diameters. If support " +"is to be printed with the current extruder (support_material_extruder == 0 " +"or support_material_interface_extruder == 0), all nozzles have to be of the " +"same diameter." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:695 +msgid "first_layer_height" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:710 +msgid "First layer height can't be greater than nozzle diameter" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:714 +msgid "Layer height can't be greater than nozzle diameter" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:1196 +msgid "Failed processing of the output_filename_format template." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:29 msgid "Avoid crossing perimeters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:27 +#: xs/src/libslic3r/PrintConfig.cpp:30 msgid "" "Optimize travel moves in order to minimize the crossing of perimeters. This " "is mostly useful with Bowden extruders which suffer from oozing. This " "feature slows down both the print and the G-code generation." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:38 xs/src/libslic3r/PrintConfig.cpp:1678 +#: xs/src/libslic3r/PrintConfig.cpp:41 xs/src/libslic3r/PrintConfig.cpp:1818 msgid "Other layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:39 +#: xs/src/libslic3r/PrintConfig.cpp:42 msgid "" "Bed temperature for layers after the first one. Set this to zero to disable " "bed temperature control commands in the output." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:42 +#: xs/src/libslic3r/PrintConfig.cpp:45 msgid "Bed temperature" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:49 +#: xs/src/libslic3r/PrintConfig.cpp:52 msgid "" "This custom code is inserted at every layer change, right before the Z move. " "Note that you can use placeholder variables for all Slic3r settings as well " "as [layer_num] and [layer_z]." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:59 +#: xs/src/libslic3r/PrintConfig.cpp:62 msgid "Between objects G-code" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:60 +#: xs/src/libslic3r/PrintConfig.cpp:63 msgid "" "This code is inserted between objects when using sequential printing. By " "default extruder and bed temperature are reset using non-wait command; " @@ -1935,79 +2086,80 @@ msgid "" "[first_layer_temperature]\" command wherever you want." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:68 lib/Slic3r/GUI/MainFrame.pm:309 +#: xs/src/libslic3r/PrintConfig.cpp:71 lib/Slic3r/GUI/MainFrame.pm:328 msgid "Bottom" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:69 xs/src/libslic3r/PrintConfig.cpp:268 -#: xs/src/libslic3r/PrintConfig.cpp:319 xs/src/libslic3r/PrintConfig.cpp:327 -#: xs/src/libslic3r/PrintConfig.cpp:667 xs/src/libslic3r/PrintConfig.cpp:832 -#: xs/src/libslic3r/PrintConfig.cpp:848 xs/src/libslic3r/PrintConfig.cpp:1025 -#: xs/src/libslic3r/PrintConfig.cpp:1082 xs/src/libslic3r/PrintConfig.cpp:1260 -#: xs/src/libslic3r/PrintConfig.cpp:1689 xs/src/libslic3r/PrintConfig.cpp:1745 +#: xs/src/libslic3r/PrintConfig.cpp:72 xs/src/libslic3r/PrintConfig.cpp:276 +#: xs/src/libslic3r/PrintConfig.cpp:327 xs/src/libslic3r/PrintConfig.cpp:335 +#: xs/src/libslic3r/PrintConfig.cpp:701 xs/src/libslic3r/PrintConfig.cpp:871 +#: xs/src/libslic3r/PrintConfig.cpp:887 xs/src/libslic3r/PrintConfig.cpp:1156 +#: xs/src/libslic3r/PrintConfig.cpp:1222 xs/src/libslic3r/PrintConfig.cpp:1400 +#: xs/src/libslic3r/PrintConfig.cpp:1829 xs/src/libslic3r/PrintConfig.cpp:1885 msgid "Layers and Perimeters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:70 +#: xs/src/libslic3r/PrintConfig.cpp:73 msgid "Number of solid layers to generate on bottom surfaces." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:72 +#: xs/src/libslic3r/PrintConfig.cpp:75 msgid "Bottom solid layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:77 +#: xs/src/libslic3r/PrintConfig.cpp:80 msgid "Bridge" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:78 +#: xs/src/libslic3r/PrintConfig.cpp:81 msgid "" "This is the acceleration your printer will use for bridges. Set zero to " "disable acceleration control for bridges." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:80 xs/src/libslic3r/PrintConfig.cpp:191 -#: xs/src/libslic3r/PrintConfig.cpp:639 xs/src/libslic3r/PrintConfig.cpp:747 -#: xs/src/libslic3r/PrintConfig.cpp:1045 +#: xs/src/libslic3r/PrintConfig.cpp:83 xs/src/libslic3r/PrintConfig.cpp:199 +#: xs/src/libslic3r/PrintConfig.cpp:673 xs/src/libslic3r/PrintConfig.cpp:781 +#: xs/src/libslic3r/PrintConfig.cpp:931 xs/src/libslic3r/PrintConfig.cpp:972 +#: xs/src/libslic3r/PrintConfig.cpp:982 xs/src/libslic3r/PrintConfig.cpp:1185 msgid "mm/s²" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:86 +#: xs/src/libslic3r/PrintConfig.cpp:89 msgid "Bridging angle" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:88 +#: xs/src/libslic3r/PrintConfig.cpp:91 msgid "" "Bridging angle override. If left to zero, the bridging angle will be " "calculated automatically. Otherwise the provided angle will be used for all " "bridges. Use 180° for zero angle." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:91 xs/src/libslic3r/PrintConfig.cpp:555 -#: xs/src/libslic3r/PrintConfig.cpp:1278 xs/src/libslic3r/PrintConfig.cpp:1289 -#: xs/src/libslic3r/PrintConfig.cpp:1509 xs/src/libslic3r/PrintConfig.cpp:1663 +#: xs/src/libslic3r/PrintConfig.cpp:94 xs/src/libslic3r/PrintConfig.cpp:589 +#: xs/src/libslic3r/PrintConfig.cpp:1418 xs/src/libslic3r/PrintConfig.cpp:1429 +#: xs/src/libslic3r/PrintConfig.cpp:1649 xs/src/libslic3r/PrintConfig.cpp:1803 msgid "°" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:97 +#: xs/src/libslic3r/PrintConfig.cpp:100 msgid "Bridges fan speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:98 +#: xs/src/libslic3r/PrintConfig.cpp:101 msgid "This fan speed is enforced during all bridges and overhangs." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:99 xs/src/libslic3r/PrintConfig.cpp:567 -#: xs/src/libslic3r/PrintConfig.cpp:859 xs/src/libslic3r/PrintConfig.cpp:927 -#: xs/src/libslic3r/PrintConfig.cpp:1168 +#: xs/src/libslic3r/PrintConfig.cpp:102 xs/src/libslic3r/PrintConfig.cpp:601 +#: xs/src/libslic3r/PrintConfig.cpp:990 xs/src/libslic3r/PrintConfig.cpp:1058 +#: xs/src/libslic3r/PrintConfig.cpp:1308 msgid "%" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:106 +#: xs/src/libslic3r/PrintConfig.cpp:109 msgid "Bridge flow ratio" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:108 +#: xs/src/libslic3r/PrintConfig.cpp:111 msgid "" "This factor affects the amount of plastic for bridging. You can decrease it " "slightly to pull the extrudates and prevent sagging, although default " @@ -2015,60 +2167,63 @@ msgid "" "before tweaking this." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:118 +#: xs/src/libslic3r/PrintConfig.cpp:121 msgid "Bridges" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:120 +#: xs/src/libslic3r/PrintConfig.cpp:123 msgid "Speed for printing bridges." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:121 xs/src/libslic3r/PrintConfig.cpp:462 -#: xs/src/libslic3r/PrintConfig.cpp:471 xs/src/libslic3r/PrintConfig.cpp:701 -#: xs/src/libslic3r/PrintConfig.cpp:812 xs/src/libslic3r/PrintConfig.cpp:888 -#: xs/src/libslic3r/PrintConfig.cpp:945 xs/src/libslic3r/PrintConfig.cpp:1074 -#: xs/src/libslic3r/PrintConfig.cpp:1245 xs/src/libslic3r/PrintConfig.cpp:1254 -#: xs/src/libslic3r/PrintConfig.cpp:1642 xs/src/libslic3r/PrintConfig.cpp:1755 +#: xs/src/libslic3r/PrintConfig.cpp:124 xs/src/libslic3r/PrintConfig.cpp:471 +#: xs/src/libslic3r/PrintConfig.cpp:480 xs/src/libslic3r/PrintConfig.cpp:508 +#: xs/src/libslic3r/PrintConfig.cpp:516 xs/src/libslic3r/PrintConfig.cpp:735 +#: xs/src/libslic3r/PrintConfig.cpp:846 xs/src/libslic3r/PrintConfig.cpp:922 +#: xs/src/libslic3r/PrintConfig.cpp:940 xs/src/libslic3r/PrintConfig.cpp:952 +#: xs/src/libslic3r/PrintConfig.cpp:962 xs/src/libslic3r/PrintConfig.cpp:1019 +#: xs/src/libslic3r/PrintConfig.cpp:1076 xs/src/libslic3r/PrintConfig.cpp:1214 +#: xs/src/libslic3r/PrintConfig.cpp:1385 xs/src/libslic3r/PrintConfig.cpp:1394 +#: xs/src/libslic3r/PrintConfig.cpp:1782 xs/src/libslic3r/PrintConfig.cpp:1895 msgid "mm/s" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:128 +#: xs/src/libslic3r/PrintConfig.cpp:131 msgid "Brim width" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:129 +#: xs/src/libslic3r/PrintConfig.cpp:132 msgid "" "Horizontal width of the brim that will be printed around each object on the " "first layer." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:136 +#: xs/src/libslic3r/PrintConfig.cpp:139 msgid "Clip multi-part objects" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:137 +#: xs/src/libslic3r/PrintConfig.cpp:140 msgid "" "When printing multi-material objects, this settings will make slic3r to clip " "the overlapping object parts one by the other (2nd part will be clipped by " "the 1st, 3rd part will be clipped by the 1st and 2nd etc)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:148 +#: xs/src/libslic3r/PrintConfig.cpp:151 msgid "Compatible printers condition" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:149 +#: xs/src/libslic3r/PrintConfig.cpp:152 msgid "" "A boolean expression using the configuration values of an active printer " "profile. If this expression evaluates to true, this profile is considered " "compatible with the active printer profile." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:155 +#: xs/src/libslic3r/PrintConfig.cpp:163 msgid "Complete individual objects" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:156 +#: xs/src/libslic3r/PrintConfig.cpp:164 msgid "" "When printing multiple objects or copies, this feature will complete each " "object before moving onto next one (and starting it from its bottom layer). " @@ -2076,113 +2231,113 @@ msgid "" "warn and prevent you from extruder collisions, but beware." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:164 +#: xs/src/libslic3r/PrintConfig.cpp:172 msgid "Enable auto cooling" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:165 +#: xs/src/libslic3r/PrintConfig.cpp:173 msgid "" "This flag enables the automatic cooling logic that adjusts print speed and " "fan speed according to layer printing time." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:171 +#: xs/src/libslic3r/PrintConfig.cpp:179 msgid "Cooling tube position" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:172 +#: xs/src/libslic3r/PrintConfig.cpp:180 msgid "Distance of the center-point of the cooling tube from the extruder tip " msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:179 +#: xs/src/libslic3r/PrintConfig.cpp:187 msgid "Cooling tube length" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:180 +#: xs/src/libslic3r/PrintConfig.cpp:188 msgid "Length of the cooling tube to limit space for cooling moves inside it " msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:188 +#: xs/src/libslic3r/PrintConfig.cpp:196 msgid "" "This is the acceleration your printer will be reset to after the role-" "specific acceleration values are used (perimeter/infill). Set zero to " "prevent resetting acceleration at all." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:197 +#: xs/src/libslic3r/PrintConfig.cpp:205 msgid "Default filament profile" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:198 +#: xs/src/libslic3r/PrintConfig.cpp:206 msgid "" "Default filament profile associated with the current printer profile. On " "selection of the current printer profile, this filament profile will be " "activated." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:203 +#: xs/src/libslic3r/PrintConfig.cpp:211 msgid "Default print profile" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:204 +#: xs/src/libslic3r/PrintConfig.cpp:212 msgid "" "Default print profile associated with the current printer profile. On " "selection of the current printer profile, this print profile will be " "activated." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:209 +#: xs/src/libslic3r/PrintConfig.cpp:217 msgid "Disable fan for the first" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:210 +#: xs/src/libslic3r/PrintConfig.cpp:218 msgid "" "You can set this to a positive value to disable fan at all during the first " "layers, so that it does not make adhesion worse." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:212 xs/src/libslic3r/PrintConfig.cpp:757 -#: xs/src/libslic3r/PrintConfig.cpp:1141 xs/src/libslic3r/PrintConfig.cpp:1332 -#: xs/src/libslic3r/PrintConfig.cpp:1393 xs/src/libslic3r/PrintConfig.cpp:1545 -#: xs/src/libslic3r/PrintConfig.cpp:1590 +#: xs/src/libslic3r/PrintConfig.cpp:220 xs/src/libslic3r/PrintConfig.cpp:791 +#: xs/src/libslic3r/PrintConfig.cpp:1281 xs/src/libslic3r/PrintConfig.cpp:1472 +#: xs/src/libslic3r/PrintConfig.cpp:1533 xs/src/libslic3r/PrintConfig.cpp:1685 +#: xs/src/libslic3r/PrintConfig.cpp:1730 msgid "layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:219 +#: xs/src/libslic3r/PrintConfig.cpp:227 msgid "Don't support bridges" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:221 +#: xs/src/libslic3r/PrintConfig.cpp:229 msgid "" "Experimental option for preventing support material from being generated " "under bridged areas." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:227 +#: xs/src/libslic3r/PrintConfig.cpp:235 msgid "Distance between copies" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:228 +#: xs/src/libslic3r/PrintConfig.cpp:236 msgid "Distance used for the auto-arrange feature of the plater." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:236 +#: xs/src/libslic3r/PrintConfig.cpp:244 msgid "Elephant foot compensation" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:238 +#: xs/src/libslic3r/PrintConfig.cpp:246 msgid "" "The first layer will be shrunk in the XY plane by the configured value to " "compensate for the 1st layer squish aka an Elephant Foot effect." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:247 +#: xs/src/libslic3r/PrintConfig.cpp:255 msgid "" "This end procedure is inserted at the end of the output file. Note that you " "can use placeholder variables for all Slic3r settings." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:257 +#: xs/src/libslic3r/PrintConfig.cpp:265 msgid "" "This end procedure is inserted at the end of the output file, before the " "printer end gcode. Note that you can use placeholder variables for all " @@ -2190,38 +2345,59 @@ msgid "" "extruder order." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:267 +#: xs/src/libslic3r/PrintConfig.cpp:275 msgid "Ensure vertical shell thickness" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:269 +#: xs/src/libslic3r/PrintConfig.cpp:277 msgid "" "Add solid infill near sloping surfaces to guarantee the vertical shell " "thickness (top+bottom solid layers)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:275 +#: xs/src/libslic3r/PrintConfig.cpp:283 msgid "Top/bottom fill pattern" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:277 +#: xs/src/libslic3r/PrintConfig.cpp:285 msgid "" "Fill pattern for top/bottom infill. This only affects the external visible " "layer, and not its adjacent solid shells." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:296 xs/src/libslic3r/PrintConfig.cpp:306 +#: xs/src/libslic3r/PrintConfig.cpp:294 xs/src/libslic3r/PrintConfig.cpp:654 +#: xs/src/libslic3r/PrintConfig.cpp:1764 +msgid "Rectilinear" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:295 xs/src/libslic3r/PrintConfig.cpp:660 +msgid "Concentric" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:296 xs/src/libslic3r/PrintConfig.cpp:664 +msgid "Hilbert Curve" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:297 xs/src/libslic3r/PrintConfig.cpp:665 +msgid "Archimedean Chords" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:298 xs/src/libslic3r/PrintConfig.cpp:666 +msgid "Octagram Spiral" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:304 xs/src/libslic3r/PrintConfig.cpp:314 msgid "External perimeters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:297 xs/src/libslic3r/PrintConfig.cpp:406 -#: xs/src/libslic3r/PrintConfig.cpp:655 xs/src/libslic3r/PrintConfig.cpp:773 -#: xs/src/libslic3r/PrintConfig.cpp:1060 xs/src/libslic3r/PrintConfig.cpp:1400 -#: xs/src/libslic3r/PrintConfig.cpp:1562 xs/src/libslic3r/PrintConfig.cpp:1720 +#: xs/src/libslic3r/PrintConfig.cpp:305 xs/src/libslic3r/PrintConfig.cpp:415 +#: xs/src/libslic3r/PrintConfig.cpp:689 xs/src/libslic3r/PrintConfig.cpp:807 +#: xs/src/libslic3r/PrintConfig.cpp:1200 xs/src/libslic3r/PrintConfig.cpp:1540 +#: xs/src/libslic3r/PrintConfig.cpp:1702 xs/src/libslic3r/PrintConfig.cpp:1860 msgid "Extrusion Width" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:298 +#: xs/src/libslic3r/PrintConfig.cpp:306 msgid "" "Set this to a non-zero value to set a manual extrusion width for external " "perimeters. If left zero, default extrusion width will be used if set, " @@ -2229,41 +2405,41 @@ msgid "" "(for example 200%), it will be computed over layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:301 xs/src/libslic3r/PrintConfig.cpp:660 -#: xs/src/libslic3r/PrintConfig.cpp:778 xs/src/libslic3r/PrintConfig.cpp:1065 -#: xs/src/libslic3r/PrintConfig.cpp:1404 xs/src/libslic3r/PrintConfig.cpp:1566 -#: xs/src/libslic3r/PrintConfig.cpp:1725 +#: xs/src/libslic3r/PrintConfig.cpp:309 xs/src/libslic3r/PrintConfig.cpp:694 +#: xs/src/libslic3r/PrintConfig.cpp:812 xs/src/libslic3r/PrintConfig.cpp:1205 +#: xs/src/libslic3r/PrintConfig.cpp:1544 xs/src/libslic3r/PrintConfig.cpp:1706 +#: xs/src/libslic3r/PrintConfig.cpp:1865 msgid "mm or % (leave 0 for default)" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:308 +#: xs/src/libslic3r/PrintConfig.cpp:316 msgid "" "This separate setting will affect the speed of external perimeters (the " "visible ones). If expressed as percentage (for example: 80%) it will be " "calculated on the perimeters speed setting above. Set to zero for auto." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:311 xs/src/libslic3r/PrintConfig.cpp:682 -#: xs/src/libslic3r/PrintConfig.cpp:1363 xs/src/libslic3r/PrintConfig.cpp:1414 -#: xs/src/libslic3r/PrintConfig.cpp:1609 xs/src/libslic3r/PrintConfig.cpp:1737 +#: xs/src/libslic3r/PrintConfig.cpp:319 xs/src/libslic3r/PrintConfig.cpp:716 +#: xs/src/libslic3r/PrintConfig.cpp:1503 xs/src/libslic3r/PrintConfig.cpp:1554 +#: xs/src/libslic3r/PrintConfig.cpp:1749 xs/src/libslic3r/PrintConfig.cpp:1877 msgid "mm/s or %" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:318 +#: xs/src/libslic3r/PrintConfig.cpp:326 msgid "External perimeters first" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:320 +#: xs/src/libslic3r/PrintConfig.cpp:328 msgid "" "Print contour perimeters from the outermost one to the innermost one instead " "of the default inverse order." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:326 +#: xs/src/libslic3r/PrintConfig.cpp:334 msgid "Extra perimeters if needed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:328 +#: xs/src/libslic3r/PrintConfig.cpp:336 #, no-c-format msgid "" "Add more perimeters when needed for avoiding gaps in sloping walls. Slic3r " @@ -2271,18 +2447,18 @@ msgid "" "is supported." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:338 +#: xs/src/libslic3r/PrintConfig.cpp:346 msgid "" "The extruder to use (unless more specific extruder settings are specified). " "This value overrides perimeter and infill extruders, but not the support " "extruders." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:349 lib/Slic3r/GUI/Plater/3DPreview.pm:75 +#: xs/src/libslic3r/PrintConfig.cpp:358 lib/Slic3r/GUI/Plater/3DPreview.pm:75 msgid "Height" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:350 +#: xs/src/libslic3r/PrintConfig.cpp:359 msgid "" "Set this to the vertical distance between your nozzle tip and (usually) the " "X carriage rods. In other words, this is the height of the clearance " @@ -2290,30 +2466,30 @@ msgid "" "extruder can peek before colliding with other printed objects." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:360 +#: xs/src/libslic3r/PrintConfig.cpp:369 msgid "Radius" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:361 +#: xs/src/libslic3r/PrintConfig.cpp:370 msgid "" "Set this to the clearance radius around your extruder. If the extruder is " "not centered, choose the largest value for safety. This setting is used to " "check for collisions and to display the graphical preview in the plater." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:371 +#: xs/src/libslic3r/PrintConfig.cpp:380 msgid "Extruder Color" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:372 xs/src/libslic3r/PrintConfig.cpp:435 +#: xs/src/libslic3r/PrintConfig.cpp:381 xs/src/libslic3r/PrintConfig.cpp:444 msgid "This is only used in the Slic3r interface as a visual help." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:379 +#: xs/src/libslic3r/PrintConfig.cpp:388 msgid "Extruder offset" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:380 +#: xs/src/libslic3r/PrintConfig.cpp:389 msgid "" "If your firmware doesn't handle the extruder displacement you need the G-" "code to take it into account. This option lets you specify the displacement " @@ -2321,21 +2497,21 @@ msgid "" "coordinates (they will be subtracted from the XY coordinate)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:389 +#: xs/src/libslic3r/PrintConfig.cpp:398 msgid "Extrusion axis" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:390 +#: xs/src/libslic3r/PrintConfig.cpp:399 msgid "" "Use this option to set the axis letter associated to your printer's extruder " "(usually E but some printers use A)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:396 +#: xs/src/libslic3r/PrintConfig.cpp:405 msgid "Extrusion multiplier" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:397 +#: xs/src/libslic3r/PrintConfig.cpp:406 msgid "" "This factor changes the amount of flow proportionally. You may need to tweak " "this setting to get nice surface finish and correct single wall widths. " @@ -2343,11 +2519,11 @@ msgid "" "more, check filament diameter and your firmware E steps." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:405 +#: xs/src/libslic3r/PrintConfig.cpp:414 msgid "Default extrusion width" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:407 +#: xs/src/libslic3r/PrintConfig.cpp:416 msgid "" "Set this to a non-zero value to allow a manual extrusion width. If left to " "zero, Slic3r derives extrusion widths from the nozzle diameter (see the " @@ -2356,113 +2532,139 @@ msgid "" "height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:411 +#: xs/src/libslic3r/PrintConfig.cpp:420 msgid "mm or % (leave 0 for auto)" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:416 +#: xs/src/libslic3r/PrintConfig.cpp:425 msgid "Keep fan always on" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:417 +#: xs/src/libslic3r/PrintConfig.cpp:426 msgid "" "If this is enabled, fan will never be disabled and will be kept running at " "least at its minimum speed. Useful for PLA, harmful for ABS." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:423 +#: xs/src/libslic3r/PrintConfig.cpp:432 msgid "Enable fan if layer print time is below" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:424 +#: xs/src/libslic3r/PrintConfig.cpp:433 msgid "" "If layer print time is estimated below this number of seconds, fan will be " "enabled and its speed will be calculated by interpolating the minimum and " "maximum speeds." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:426 xs/src/libslic3r/PrintConfig.cpp:1350 +#: xs/src/libslic3r/PrintConfig.cpp:435 xs/src/libslic3r/PrintConfig.cpp:1490 msgid "approximate seconds" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:434 +#: xs/src/libslic3r/PrintConfig.cpp:443 msgid "Color" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:441 +#: xs/src/libslic3r/PrintConfig.cpp:450 msgid "Filament notes" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:442 +#: xs/src/libslic3r/PrintConfig.cpp:451 msgid "You can put your notes regarding the filament here." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:450 xs/src/libslic3r/PrintConfig.cpp:894 +#: xs/src/libslic3r/PrintConfig.cpp:459 xs/src/libslic3r/PrintConfig.cpp:1025 msgid "Max volumetric speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:451 +#: xs/src/libslic3r/PrintConfig.cpp:460 msgid "" "Maximum volumetric speed allowed for this filament. Limits the maximum " "volumetric speed of a print to the minimum of print and filament volumetric " "speed. Set to zero for no limit." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:454 xs/src/libslic3r/PrintConfig.cpp:897 +#: xs/src/libslic3r/PrintConfig.cpp:463 xs/src/libslic3r/PrintConfig.cpp:1028 msgid "mm³/s" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:460 +#: xs/src/libslic3r/PrintConfig.cpp:469 msgid "Loading speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:461 +#: xs/src/libslic3r/PrintConfig.cpp:470 msgid "Speed used for loading the filament on the wipe tower. " msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:468 +#: xs/src/libslic3r/PrintConfig.cpp:477 msgid "Unloading speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:469 +#: xs/src/libslic3r/PrintConfig.cpp:478 msgid "" "Speed used for unloading the filament on the wipe tower (does not affect " "initial part of unloading just after ramming). " msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:477 +#: xs/src/libslic3r/PrintConfig.cpp:486 msgid "Delay after unloading" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:478 +#: xs/src/libslic3r/PrintConfig.cpp:487 msgid "" "Time to wait after the filament is unloaded. May help to get reliable " "toolchanges with flexible materials that may need more time to shrink to " "original dimensions. " msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:487 +#: xs/src/libslic3r/PrintConfig.cpp:496 +msgid "Number of cooling moves" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:497 +msgid "" +"Filament is cooled by being moved back and forth in the cooling tubes. " +"Specify desired number of these moves " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:505 +msgid "Speed of the first cooling move" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:506 +msgid "Cooling moves are gradually accelerating beginning at this speed. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:513 +msgid "Speed of the last cooling move" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:514 +msgid "Cooling moves are gradually accelerating towards this speed. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:521 msgid "Ramming parameters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:488 +#: xs/src/libslic3r/PrintConfig.cpp:522 msgid "" "This string is edited by RammingDialog and contains ramming specific " "parameters " msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:495 +#: xs/src/libslic3r/PrintConfig.cpp:529 msgid "" "Enter your filament diameter here. Good precision is required, so use a " "caliper and do multiple measurements along the filament, then compute the " "average." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:503 +#: xs/src/libslic3r/PrintConfig.cpp:537 msgid "Density" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:504 +#: xs/src/libslic3r/PrintConfig.cpp:538 msgid "" "Enter your filament density here. This is only for statistical information. " "A decent way is to weigh a known length of filament and compute the ratio of " @@ -2470,15 +2672,15 @@ msgid "" "displacement." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:507 +#: xs/src/libslic3r/PrintConfig.cpp:541 msgid "g/cm³" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:513 +#: xs/src/libslic3r/PrintConfig.cpp:547 msgid "Filament type" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:514 xs/src/libslic3r/PrintConfig.cpp:1095 +#: xs/src/libslic3r/PrintConfig.cpp:548 xs/src/libslic3r/PrintConfig.cpp:1235 msgid "" "If you want to process the output G-code through custom scripts, just list " "their absolute paths here. Separate multiple scripts with a semicolon. " @@ -2487,74 +2689,106 @@ msgid "" "environment variables." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:533 +#: xs/src/libslic3r/PrintConfig.cpp:567 msgid "Soluble material" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:534 +#: xs/src/libslic3r/PrintConfig.cpp:568 msgid "Soluble material is most likely used for a soluble support." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:539 lib/Slic3r/GUI/Plater.pm:519 +#: xs/src/libslic3r/PrintConfig.cpp:573 lib/Slic3r/GUI/Plater.pm:1616 msgid "Cost" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:540 +#: xs/src/libslic3r/PrintConfig.cpp:574 msgid "" "Enter your filament cost per kg here. This is only for statistical " "information." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:541 +#: xs/src/libslic3r/PrintConfig.cpp:575 msgid "money/kg" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:550 +#: xs/src/libslic3r/PrintConfig.cpp:584 msgid "Fill angle" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:552 +#: xs/src/libslic3r/PrintConfig.cpp:586 msgid "" "Default base angle for infill orientation. Cross-hatching will be applied to " "this. Bridges will be infilled using the best direction Slic3r can detect, " "so this setting does not affect them." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:564 +#: xs/src/libslic3r/PrintConfig.cpp:598 msgid "Fill density" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:566 +#: xs/src/libslic3r/PrintConfig.cpp:600 #, no-c-format msgid "Density of internal infill, expressed in the range 0% - 100%." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:602 +#: xs/src/libslic3r/PrintConfig.cpp:636 msgid "Fill pattern" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:604 +#: xs/src/libslic3r/PrintConfig.cpp:638 msgid "Fill pattern for general low-density infill." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:636 xs/src/libslic3r/PrintConfig.cpp:645 -#: xs/src/libslic3r/PrintConfig.cpp:654 xs/src/libslic3r/PrintConfig.cpp:688 +#: xs/src/libslic3r/PrintConfig.cpp:655 +msgid "Grid" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:656 +msgid "Triangles" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:657 +msgid "Stars" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:658 +msgid "Cubic" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:659 +msgid "Line" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:661 xs/src/libslic3r/PrintConfig.cpp:1766 +msgid "Honeycomb" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:662 +msgid "3D Honeycomb" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:663 +msgid "Gyroid" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:670 xs/src/libslic3r/PrintConfig.cpp:679 +#: xs/src/libslic3r/PrintConfig.cpp:688 xs/src/libslic3r/PrintConfig.cpp:722 msgid "First layer" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:637 +#: xs/src/libslic3r/PrintConfig.cpp:671 msgid "" "This is the acceleration your printer will use for first layer. Set zero to " "disable acceleration control for first layer." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:646 +#: xs/src/libslic3r/PrintConfig.cpp:680 msgid "" "Heated build plate temperature for the first layer. Set this to zero to " "disable bed temperature control commands in the output." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:656 +#: xs/src/libslic3r/PrintConfig.cpp:690 msgid "" "Set this to a non-zero value to set a manual extrusion width for first " "layer. You can use this to force fatter extrudates for better adhesion. If " @@ -2562,11 +2796,11 @@ msgid "" "layer height. If set to zero, it will use the default extrusion width." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:666 +#: xs/src/libslic3r/PrintConfig.cpp:700 msgid "First layer height" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:668 +#: xs/src/libslic3r/PrintConfig.cpp:702 msgid "" "When printing with very low layer heights, you might still want to print a " "thicker bottom layer to improve adhesion and tolerance for non perfect build " @@ -2574,58 +2808,58 @@ msgid "" "example: 150%) over the default layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:672 xs/src/libslic3r/PrintConfig.cpp:803 -#: xs/src/libslic3r/PrintConfig.cpp:1498 +#: xs/src/libslic3r/PrintConfig.cpp:706 xs/src/libslic3r/PrintConfig.cpp:837 +#: xs/src/libslic3r/PrintConfig.cpp:1638 msgid "mm or %" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:678 +#: xs/src/libslic3r/PrintConfig.cpp:712 msgid "First layer speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:679 +#: xs/src/libslic3r/PrintConfig.cpp:713 msgid "" "If expressed as absolute value in mm/s, this speed will be applied to all " "the print moves of the first layer, regardless of their type. If expressed " "as a percentage (for example: 40%) it will scale the default speeds." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:689 +#: xs/src/libslic3r/PrintConfig.cpp:723 msgid "" "Extruder temperature for first layer. If you want to control temperature " "manually during print, set this to zero to disable temperature control " "commands in the output file." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:697 -#: xs/src/libslic3r/GCode/PreviewData.cpp:165 +#: xs/src/libslic3r/PrintConfig.cpp:731 +#: xs/src/libslic3r/GCode/PreviewData.cpp:170 #: lib/Slic3r/GUI/Plater/3DPreview.pm:97 msgid "Gap fill" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:699 +#: xs/src/libslic3r/PrintConfig.cpp:733 msgid "" "Speed for filling small gaps using short zigzag moves. Keep this reasonably " "low to avoid too much shaking and resonance issues. Set zero to disable gaps " "filling." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:707 +#: xs/src/libslic3r/PrintConfig.cpp:741 msgid "Verbose G-code" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:708 +#: xs/src/libslic3r/PrintConfig.cpp:742 msgid "" "Enable this to get a commented G-code file, with each line explained by a " "descriptive text. If you print from SD card, the additional weight of the " "file could make your firmware slow down." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:715 +#: xs/src/libslic3r/PrintConfig.cpp:749 msgid "G-code flavor" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:716 +#: xs/src/libslic3r/PrintConfig.cpp:750 msgid "" "Some G/M-code commands, including temperature control and others, are not " "universal. Set this option to your printer's firmware to get a compatible " @@ -2633,35 +2867,39 @@ msgid "" "extrusion value at all." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:745 +#: xs/src/libslic3r/PrintConfig.cpp:774 +msgid "No extrusion" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:779 msgid "" "This is the acceleration your printer will use for infill. Set zero to " "disable acceleration control for infill." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:753 +#: xs/src/libslic3r/PrintConfig.cpp:787 msgid "Combine infill every" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:755 +#: xs/src/libslic3r/PrintConfig.cpp:789 msgid "" "This feature allows to combine infill and speed up your print by extruding " "thicker infill layers while preserving thin perimeters, thus accuracy." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:759 +#: xs/src/libslic3r/PrintConfig.cpp:793 msgid "Combine infill every n layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:764 +#: xs/src/libslic3r/PrintConfig.cpp:798 msgid "Infill extruder" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:766 +#: xs/src/libslic3r/PrintConfig.cpp:800 msgid "The extruder to use when printing infill." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:774 +#: xs/src/libslic3r/PrintConfig.cpp:808 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill. If " "left zero, default extrusion width will be used if set, otherwise 1.125 x " @@ -2670,32 +2908,32 @@ msgid "" "example 90%) it will be computed over layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:783 +#: xs/src/libslic3r/PrintConfig.cpp:817 msgid "Infill before perimeters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:784 +#: xs/src/libslic3r/PrintConfig.cpp:818 msgid "" "This option will switch the print order of perimeters and infill, making the " "latter first." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:789 +#: xs/src/libslic3r/PrintConfig.cpp:823 msgid "Only infill where needed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:791 +#: xs/src/libslic3r/PrintConfig.cpp:825 msgid "" "This option will limit infill to the areas actually needed for supporting " "ceilings (it will act as internal support material). If enabled, slows down " "the G-code generation due to the multiple checks involved." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:798 +#: xs/src/libslic3r/PrintConfig.cpp:832 msgid "Infill/perimeters overlap" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:800 +#: xs/src/libslic3r/PrintConfig.cpp:834 msgid "" "This setting applies an additional overlap between infill and perimeters for " "better bonding. Theoretically this shouldn't be needed, but backlash might " @@ -2703,30 +2941,30 @@ msgid "" "perimeter extrusion width." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:811 +#: xs/src/libslic3r/PrintConfig.cpp:845 msgid "Speed for printing the internal fill. Set to zero for auto." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:820 +#: xs/src/libslic3r/PrintConfig.cpp:854 msgid "Inherits profile" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:821 +#: xs/src/libslic3r/PrintConfig.cpp:855 msgid "Name of the profile, from which this profile inherits." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:827 +#: xs/src/libslic3r/PrintConfig.cpp:866 msgid "Interface shells" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:828 +#: xs/src/libslic3r/PrintConfig.cpp:867 msgid "" "Force the generation of solid shells between adjacent materials/volumes. " "Useful for multi-extruder prints with translucent materials or manual " "soluble support material." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:837 +#: xs/src/libslic3r/PrintConfig.cpp:876 msgid "" "This custom code is inserted at every layer change, right after the Z move " "and before the extruder moves to the first layer point. Note that you can " @@ -2734,21 +2972,75 @@ msgid "" "[layer_z]." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:849 +#: xs/src/libslic3r/PrintConfig.cpp:888 msgid "" "This setting controls the height (and thus the total number) of the slices/" "layers. Thinner layers give better accuracy but take more time to print." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:857 xs/src/libslic3r/PrintConfig.cpp:866 +#: xs/src/libslic3r/PrintConfig.cpp:896 +msgid "Support silent mode" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:897 +msgid "Set silent mode for the G-code flavor" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:919 +#, possible-c-format +msgid "Maximum feedrate %1%" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:921 +#, possible-c-format +msgid "Maximum feedrate of the %1% axis" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:928 +#, possible-c-format +msgid "Maximum acceleration %1%" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:930 +#, possible-c-format +msgid "Maximum acceleration of the %1% axis" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:937 +#, possible-c-format +msgid "Maximum jerk %1%" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:939 +#, possible-c-format +msgid "Maximum jerk of the %1% axis" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:949 xs/src/libslic3r/PrintConfig.cpp:951 +msgid "Minimum feedrate when extruding" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:959 xs/src/libslic3r/PrintConfig.cpp:961 +msgid "Minimum travel feedrate" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:969 xs/src/libslic3r/PrintConfig.cpp:971 +msgid "Maximum acceleration when extruding" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:979 xs/src/libslic3r/PrintConfig.cpp:981 +msgid "Maximum acceleration when retracting" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:988 xs/src/libslic3r/PrintConfig.cpp:997 msgid "Max" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:858 +#: xs/src/libslic3r/PrintConfig.cpp:989 msgid "This setting represents the maximum speed of your fan." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:867 +#: xs/src/libslic3r/PrintConfig.cpp:998 #, no-c-format msgid "" "This is the highest printable layer height for this extruder, used to cap " @@ -2757,38 +3049,38 @@ msgid "" "adhesion. If set to 0, layer height is limited to 75% of the nozzle diameter." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:877 +#: xs/src/libslic3r/PrintConfig.cpp:1008 msgid "Max print height" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:878 +#: xs/src/libslic3r/PrintConfig.cpp:1009 msgid "" "Set this to the maximum height that can be reached by your extruder while " "printing." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:884 +#: xs/src/libslic3r/PrintConfig.cpp:1015 msgid "Max print speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:885 +#: xs/src/libslic3r/PrintConfig.cpp:1016 msgid "" "When setting other speed settings to 0 Slic3r will autocalculate the optimal " "speed in order to keep constant extruder pressure. This experimental setting " "is used to set the highest print speed you want to allow." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:895 +#: xs/src/libslic3r/PrintConfig.cpp:1026 msgid "" "This experimental setting is used to set the maximum volumetric speed your " "extruder supports." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:903 +#: xs/src/libslic3r/PrintConfig.cpp:1034 msgid "Max volumetric slope positive" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:904 xs/src/libslic3r/PrintConfig.cpp:915 +#: xs/src/libslic3r/PrintConfig.cpp:1035 xs/src/libslic3r/PrintConfig.cpp:1046 msgid "" "This experimental setting is used to limit the speed of change in extrusion " "rate. A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " @@ -2796,109 +3088,109 @@ msgid "" "s) to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:908 xs/src/libslic3r/PrintConfig.cpp:919 +#: xs/src/libslic3r/PrintConfig.cpp:1039 xs/src/libslic3r/PrintConfig.cpp:1050 msgid "mm³/s²" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:914 +#: xs/src/libslic3r/PrintConfig.cpp:1045 msgid "Max volumetric slope negative" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:925 xs/src/libslic3r/PrintConfig.cpp:934 +#: xs/src/libslic3r/PrintConfig.cpp:1056 xs/src/libslic3r/PrintConfig.cpp:1065 msgid "Min" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:926 +#: xs/src/libslic3r/PrintConfig.cpp:1057 msgid "This setting represents the minimum PWM your fan needs to work." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:935 +#: xs/src/libslic3r/PrintConfig.cpp:1066 msgid "" "This is the lowest printable layer height for this extruder and limits the " "resolution for variable layer height. Typical values are between 0.05 mm and " "0.1 mm." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:943 +#: xs/src/libslic3r/PrintConfig.cpp:1074 msgid "Min print speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:944 +#: xs/src/libslic3r/PrintConfig.cpp:1075 msgid "Slic3r will not scale speed down below this speed." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:951 +#: xs/src/libslic3r/PrintConfig.cpp:1082 msgid "Minimal filament extrusion length" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:952 +#: xs/src/libslic3r/PrintConfig.cpp:1083 msgid "" "Generate no less than the number of skirt loops required to consume the " "specified amount of filament on the bottom layer. For multi-extruder " "machines, this minimum applies to each extruder." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:961 +#: xs/src/libslic3r/PrintConfig.cpp:1092 msgid "Configuration notes" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:962 +#: xs/src/libslic3r/PrintConfig.cpp:1093 msgid "" "You can put here your personal notes. This text will be added to the G-code " "header comments." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:971 +#: xs/src/libslic3r/PrintConfig.cpp:1102 msgid "Nozzle diameter" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:972 +#: xs/src/libslic3r/PrintConfig.cpp:1103 msgid "" "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:978 +#: xs/src/libslic3r/PrintConfig.cpp:1109 msgid "API Key" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:979 +#: xs/src/libslic3r/PrintConfig.cpp:1110 msgid "" "Slic3r can upload G-code files to OctoPrint. This field should contain the " "API Key required for authentication." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:992 +#: xs/src/libslic3r/PrintConfig.cpp:1123 msgid "Hostname, IP or URL" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:993 +#: xs/src/libslic3r/PrintConfig.cpp:1124 msgid "" "Slic3r can upload G-code files to OctoPrint. This field should contain the " "hostname, IP address or URL of the OctoPrint instance." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:999 +#: xs/src/libslic3r/PrintConfig.cpp:1130 msgid "Only retract when crossing perimeters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1000 +#: xs/src/libslic3r/PrintConfig.cpp:1131 msgid "" "Disables retraction when the travel path does not exceed the upper layer's " "perimeters (and thus any ooze will be probably invisible)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1007 +#: xs/src/libslic3r/PrintConfig.cpp:1138 msgid "" "This option will drop the temperature of the inactive extruders to prevent " "oozing. It will enable a tall skirt automatically and move extruders outside " "such skirt when changing temperatures." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1014 +#: xs/src/libslic3r/PrintConfig.cpp:1145 msgid "Output filename format" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1015 +#: xs/src/libslic3r/PrintConfig.cpp:1146 msgid "" "You can use all configuration options as variables inside this template. For " "example: [layer_height], [fill_density] etc. You can also use [timestamp], " @@ -2906,48 +3198,60 @@ msgid "" "[input_filename], [input_filename_base]." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1024 +#: xs/src/libslic3r/PrintConfig.cpp:1155 msgid "Detect bridging perimeters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1026 +#: xs/src/libslic3r/PrintConfig.cpp:1157 msgid "" "Experimental option to adjust flow for overhangs (bridge flow will be used), " "to apply bridge speed to them and enable fan." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1032 +#: xs/src/libslic3r/PrintConfig.cpp:1163 msgid "Filament parking position" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1033 +#: xs/src/libslic3r/PrintConfig.cpp:1164 msgid "" "Distance of the extruder tip from the position where the filament is parked " "when unloaded. This should match the value in printer firmware. " msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1041 xs/src/libslic3r/PrintConfig.cpp:1059 -#: xs/src/libslic3r/PrintConfig.cpp:1071 xs/src/libslic3r/PrintConfig.cpp:1081 +#: xs/src/libslic3r/PrintConfig.cpp:1172 +msgid "Extra loading distance" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1173 +msgid "" +"When set to zero, the distance the filament is moved from parking position " +"during load is exactly the same as it was moved back during unload. When " +"positive, it is loaded further, if negative, the loading move is shorter " +"than unloading. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1181 xs/src/libslic3r/PrintConfig.cpp:1199 +#: xs/src/libslic3r/PrintConfig.cpp:1211 xs/src/libslic3r/PrintConfig.cpp:1221 msgid "Perimeters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1042 +#: xs/src/libslic3r/PrintConfig.cpp:1182 msgid "" "This is the acceleration your printer will use for perimeters. A high value " "like 9000 usually gives good results if your hardware is up to the job. Set " "zero to disable acceleration control for perimeters." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1050 +#: xs/src/libslic3r/PrintConfig.cpp:1190 msgid "Perimeter extruder" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1052 +#: xs/src/libslic3r/PrintConfig.cpp:1192 msgid "" "The extruder to use when printing perimeters and brim. First extruder is 1." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1061 +#: xs/src/libslic3r/PrintConfig.cpp:1201 msgid "" "Set this to a non-zero value to set a manual extrusion width for perimeters. " "You may want to use thinner extrudates to get more accurate surfaces. If " @@ -2956,12 +3260,12 @@ msgid "" "it will be computed over layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1073 +#: xs/src/libslic3r/PrintConfig.cpp:1213 msgid "" "Speed for perimeters (contours, aka vertical shells). Set to zero for auto." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1083 +#: xs/src/libslic3r/PrintConfig.cpp:1223 msgid "" "This option sets the number of perimeters to generate for each layer. Note " "that Slic3r may increase this number automatically when it detects sloping " @@ -2969,59 +3273,59 @@ msgid "" "Perimeters option is enabled." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1087 +#: xs/src/libslic3r/PrintConfig.cpp:1227 msgid "(minimum)" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1107 +#: xs/src/libslic3r/PrintConfig.cpp:1247 msgid "Printer type" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1108 +#: xs/src/libslic3r/PrintConfig.cpp:1248 msgid "Type of the printer." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1112 +#: xs/src/libslic3r/PrintConfig.cpp:1252 msgid "Printer notes" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1113 +#: xs/src/libslic3r/PrintConfig.cpp:1253 msgid "You can put your notes regarding the printer here." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1121 +#: xs/src/libslic3r/PrintConfig.cpp:1261 msgid "Printer vendor" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1122 +#: xs/src/libslic3r/PrintConfig.cpp:1262 msgid "Name of the printer vendor." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1126 +#: xs/src/libslic3r/PrintConfig.cpp:1266 msgid "Printer variant" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1127 +#: xs/src/libslic3r/PrintConfig.cpp:1267 msgid "" "Name of the printer variant. For example, the printer variants may be " "differentiated by a nozzle diameter." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1137 +#: xs/src/libslic3r/PrintConfig.cpp:1277 msgid "Raft layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1139 +#: xs/src/libslic3r/PrintConfig.cpp:1279 msgid "" "The object will be raised by this number of layers, and support material " "will be generated under it." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1147 +#: xs/src/libslic3r/PrintConfig.cpp:1287 msgid "Resolution" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1148 +#: xs/src/libslic3r/PrintConfig.cpp:1288 msgid "" "Minimum detail resolution, used to simplify the input file for speeding up " "the slicing job and reducing memory usage. High-resolution models often " @@ -3029,266 +3333,282 @@ msgid "" "simplification and use full resolution from input." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1158 +#: xs/src/libslic3r/PrintConfig.cpp:1298 msgid "Minimum travel after retraction" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1159 +#: xs/src/libslic3r/PrintConfig.cpp:1299 msgid "" "Retraction is not triggered when travel moves are shorter than this length." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1165 +#: xs/src/libslic3r/PrintConfig.cpp:1305 msgid "Retract amount before wipe" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1166 +#: xs/src/libslic3r/PrintConfig.cpp:1306 msgid "" "With bowden extruders, it may be wise to do some amount of quick retract " "before doing the wipe movement." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1173 +#: xs/src/libslic3r/PrintConfig.cpp:1313 msgid "Retract on layer change" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1174 +#: xs/src/libslic3r/PrintConfig.cpp:1314 msgid "This flag enforces a retraction whenever a Z move is done." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1179 xs/src/libslic3r/PrintConfig.cpp:1188 +#: xs/src/libslic3r/PrintConfig.cpp:1319 xs/src/libslic3r/PrintConfig.cpp:1328 msgid "Length" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1180 +#: xs/src/libslic3r/PrintConfig.cpp:1320 msgid "Retraction Length" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1181 +#: xs/src/libslic3r/PrintConfig.cpp:1321 msgid "" "When retraction is triggered, filament is pulled back by the specified " "amount (the length is measured on raw filament, before it enters the " "extruder)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1183 xs/src/libslic3r/PrintConfig.cpp:1193 +#: xs/src/libslic3r/PrintConfig.cpp:1323 xs/src/libslic3r/PrintConfig.cpp:1333 msgid "mm (zero to disable)" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1189 +#: xs/src/libslic3r/PrintConfig.cpp:1329 msgid "Retraction Length (Toolchange)" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1190 +#: xs/src/libslic3r/PrintConfig.cpp:1330 msgid "" "When retraction is triggered before changing tool, filament is pulled back " "by the specified amount (the length is measured on raw filament, before it " "enters the extruder)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1198 +#: xs/src/libslic3r/PrintConfig.cpp:1338 msgid "Lift Z" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1199 +#: xs/src/libslic3r/PrintConfig.cpp:1339 msgid "" "If you set this to a positive value, Z is quickly raised every time a " "retraction is triggered. When using multiple extruders, only the setting for " "the first extruder will be considered." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1207 +#: xs/src/libslic3r/PrintConfig.cpp:1347 msgid "Above Z" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1208 +#: xs/src/libslic3r/PrintConfig.cpp:1348 msgid "Only lift Z above" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1209 +#: xs/src/libslic3r/PrintConfig.cpp:1349 msgid "" "If you set this to a positive value, Z lift will only take place above the " "specified absolute Z. You can tune this setting for skipping lift on the " "first layers." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1216 +#: xs/src/libslic3r/PrintConfig.cpp:1356 msgid "Below Z" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1217 +#: xs/src/libslic3r/PrintConfig.cpp:1357 msgid "Only lift Z below" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1218 +#: xs/src/libslic3r/PrintConfig.cpp:1358 msgid "" "If you set this to a positive value, Z lift will only take place below the " "specified absolute Z. You can tune this setting for limiting lift to the " "first layers." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1226 xs/src/libslic3r/PrintConfig.cpp:1234 +#: xs/src/libslic3r/PrintConfig.cpp:1366 xs/src/libslic3r/PrintConfig.cpp:1374 msgid "Extra length on restart" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1227 +#: xs/src/libslic3r/PrintConfig.cpp:1367 msgid "" "When the retraction is compensated after the travel move, the extruder will " "push this additional amount of filament. This setting is rarely needed." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1235 +#: xs/src/libslic3r/PrintConfig.cpp:1375 msgid "" "When the retraction is compensated after changing tool, the extruder will " "push this additional amount of filament." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1242 xs/src/libslic3r/PrintConfig.cpp:1243 +#: xs/src/libslic3r/PrintConfig.cpp:1382 xs/src/libslic3r/PrintConfig.cpp:1383 msgid "Retraction Speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1244 +#: xs/src/libslic3r/PrintConfig.cpp:1384 msgid "The speed for retractions (it only applies to the extruder motor)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1250 xs/src/libslic3r/PrintConfig.cpp:1251 +#: xs/src/libslic3r/PrintConfig.cpp:1390 xs/src/libslic3r/PrintConfig.cpp:1391 msgid "Deretraction Speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1252 +#: xs/src/libslic3r/PrintConfig.cpp:1392 msgid "" "The speed for loading of a filament into extruder after retraction (it only " "applies to the extruder motor). If left to zero, the retraction speed is " "used." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1259 +#: xs/src/libslic3r/PrintConfig.cpp:1399 msgid "Seam position" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1261 +#: xs/src/libslic3r/PrintConfig.cpp:1401 msgid "Position of perimeters starting points." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1277 +#: xs/src/libslic3r/PrintConfig.cpp:1408 +msgid "Random" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1409 +msgid "Nearest" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1410 +msgid "Aligned" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1411 lib/Slic3r/GUI/MainFrame.pm:330 +msgid "Rear" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1417 msgid "Direction" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1279 +#: xs/src/libslic3r/PrintConfig.cpp:1419 msgid "Preferred direction of the seam" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1280 +#: xs/src/libslic3r/PrintConfig.cpp:1420 msgid "Seam preferred direction" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1288 +#: xs/src/libslic3r/PrintConfig.cpp:1428 msgid "Jitter" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1290 +#: xs/src/libslic3r/PrintConfig.cpp:1430 msgid "Seam preferred direction jitter" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1291 +#: xs/src/libslic3r/PrintConfig.cpp:1431 msgid "Preferred direction of the seam - jitter" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1302 +#: xs/src/libslic3r/PrintConfig.cpp:1442 msgid "USB/serial port for printer connection." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1310 +#: xs/src/libslic3r/PrintConfig.cpp:1450 msgid "Serial port speed" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1311 +#: xs/src/libslic3r/PrintConfig.cpp:1451 msgid "Speed (baud) of USB/serial port for printer connection." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1320 +#: xs/src/libslic3r/PrintConfig.cpp:1460 msgid "Distance from object" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1321 +#: xs/src/libslic3r/PrintConfig.cpp:1461 msgid "" "Distance between skirt and object(s). Set this to zero to attach the skirt " "to the object(s) and get a brim for better adhesion." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1329 +#: xs/src/libslic3r/PrintConfig.cpp:1469 msgid "Skirt height" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1330 +#: xs/src/libslic3r/PrintConfig.cpp:1470 msgid "" "Height of skirt expressed in layers. Set this to a tall value to use skirt " "as a shield against drafts." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1337 +#: xs/src/libslic3r/PrintConfig.cpp:1477 msgid "Loops (minimum)" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1338 +#: xs/src/libslic3r/PrintConfig.cpp:1478 msgid "Skirt Loops" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1339 +#: xs/src/libslic3r/PrintConfig.cpp:1479 msgid "" "Number of loops for the skirt. If the Minimum Extrusion Length option is " "set, the number of loops might be greater than the one configured here. Set " "this to zero to disable skirt completely." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1347 +#: xs/src/libslic3r/PrintConfig.cpp:1487 msgid "Slow down if layer print time is below" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1348 +#: xs/src/libslic3r/PrintConfig.cpp:1488 msgid "" "If layer print time is estimated below this number of seconds, print moves " "speed will be scaled down to extend duration to this value." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1358 +#: xs/src/libslic3r/PrintConfig.cpp:1498 msgid "Small perimeters" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1360 +#: xs/src/libslic3r/PrintConfig.cpp:1500 msgid "" "This separate setting will affect the speed of perimeters having radius <= " "6.5mm (usually holes). If expressed as percentage (for example: 80%) it will " "be calculated on the perimeters speed setting above. Set to zero for auto." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1370 +#: xs/src/libslic3r/PrintConfig.cpp:1510 msgid "Solid infill threshold area" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1372 +#: xs/src/libslic3r/PrintConfig.cpp:1512 msgid "" "Force solid infill for regions having a smaller area than the specified " "threshold." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1373 +#: xs/src/libslic3r/PrintConfig.cpp:1513 msgid "mm²" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1379 +#: xs/src/libslic3r/PrintConfig.cpp:1519 msgid "Solid infill extruder" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1381 +#: xs/src/libslic3r/PrintConfig.cpp:1521 msgid "The extruder to use when printing solid infill." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1387 +#: xs/src/libslic3r/PrintConfig.cpp:1527 msgid "Solid infill every" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1389 +#: xs/src/libslic3r/PrintConfig.cpp:1529 msgid "" "This feature allows to force a solid layer every given number of layers. " "Zero to disable. You can set this to any value (for example 9999); Slic3r " @@ -3296,13 +3616,13 @@ msgid "" "according to nozzle diameter and layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1399 xs/src/libslic3r/PrintConfig.cpp:1409 -#: xs/src/libslic3r/GCode/PreviewData.cpp:162 +#: xs/src/libslic3r/PrintConfig.cpp:1539 xs/src/libslic3r/PrintConfig.cpp:1549 +#: xs/src/libslic3r/GCode/PreviewData.cpp:167 #: lib/Slic3r/GUI/Plater/3DPreview.pm:94 msgid "Solid infill" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1401 +#: xs/src/libslic3r/PrintConfig.cpp:1541 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill for " "solid surfaces. If left zero, default extrusion width will be used if set, " @@ -3310,22 +3630,22 @@ msgid "" "(for example 90%) it will be computed over layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1411 +#: xs/src/libslic3r/PrintConfig.cpp:1551 msgid "" "Speed for printing solid regions (top/bottom/internal horizontal shells). " "This can be expressed as a percentage (for example: 80%) over the default " "infill speed above. Set to zero for auto." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1423 +#: xs/src/libslic3r/PrintConfig.cpp:1563 msgid "Number of solid layers to generate on top and bottom surfaces." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1430 +#: xs/src/libslic3r/PrintConfig.cpp:1570 msgid "Spiral vase" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1431 +#: xs/src/libslic3r/PrintConfig.cpp:1571 msgid "" "This feature will raise Z gradually while printing a single-walled object in " "order to remove any visible seam. This option requires a single perimeter, " @@ -3334,18 +3654,18 @@ msgid "" "when printing more than an object." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1440 +#: xs/src/libslic3r/PrintConfig.cpp:1580 msgid "Temperature variation" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1441 +#: xs/src/libslic3r/PrintConfig.cpp:1581 msgid "" "Temperature difference to be applied when an extruder is not active. Enables " "a full-height \"sacrificial\" skirt on which the nozzles are periodically " "wiped." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1451 +#: xs/src/libslic3r/PrintConfig.cpp:1591 msgid "" "This start procedure is inserted at the beginning, after bed has reached the " "target temperature and extruder just started heating, and before extruder " @@ -3356,7 +3676,7 @@ msgid "" "\"M109 S[first_layer_temperature]\" command wherever you want." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1466 +#: xs/src/libslic3r/PrintConfig.cpp:1606 msgid "" "This start procedure is inserted at the beginning, after any printer start " "gcode. This is used to override settings for a specific filament. If Slic3r " @@ -3368,64 +3688,72 @@ msgid "" "extruders, the gcode is processed in extruder order." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1481 +#: xs/src/libslic3r/PrintConfig.cpp:1621 msgid "Single Extruder Multi Material" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1482 +#: xs/src/libslic3r/PrintConfig.cpp:1622 msgid "The printer multiplexes filaments into a single hot end." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1487 +#: xs/src/libslic3r/PrintConfig.cpp:1627 msgid "Generate support material" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1489 +#: xs/src/libslic3r/PrintConfig.cpp:1629 msgid "Enable support material generation." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1494 +#: xs/src/libslic3r/PrintConfig.cpp:1634 msgid "XY separation between an object and its support" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1496 +#: xs/src/libslic3r/PrintConfig.cpp:1636 msgid "" "XY separation between an object and its support. If expressed as percentage " "(for example 50%), it will be calculated over external perimeter width." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1506 +#: xs/src/libslic3r/PrintConfig.cpp:1646 msgid "Pattern angle" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1508 +#: xs/src/libslic3r/PrintConfig.cpp:1648 msgid "" "Use this setting to rotate the support material pattern on the horizontal " "plane." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1518 +#: xs/src/libslic3r/PrintConfig.cpp:1658 msgid "" "Only create support if it lies on a build plate. Don't create support on a " "print." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1524 +#: xs/src/libslic3r/PrintConfig.cpp:1664 msgid "Contact Z distance" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1526 +#: xs/src/libslic3r/PrintConfig.cpp:1666 msgid "" "The vertical distance between object and support material interface. Setting " "this to 0 will also prevent Slic3r from using bridge flow and speed for the " "first object layer." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1539 +#: xs/src/libslic3r/PrintConfig.cpp:1674 +msgid "soluble" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1675 +msgid "detachable" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1679 msgid "Enforce support for the first" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1541 +#: xs/src/libslic3r/PrintConfig.cpp:1681 msgid "" "Generate support material for the specified number of layers counting from " "bottom, regardless of whether normal support material is enabled or not and " @@ -3433,21 +3761,21 @@ msgid "" "of objects having a very thin or poor footprint on the build plate." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1547 +#: xs/src/libslic3r/PrintConfig.cpp:1687 msgid "Enforce support for the first n layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1552 +#: xs/src/libslic3r/PrintConfig.cpp:1692 msgid "Support material/raft/skirt extruder" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1554 +#: xs/src/libslic3r/PrintConfig.cpp:1694 msgid "" "The extruder to use when printing support material, raft and skirt (1+, 0 to " "use the current extruder to minimize tool changes)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1563 +#: xs/src/libslic3r/PrintConfig.cpp:1703 msgid "" "Set this to a non-zero value to set a manual extrusion width for support " "material. If left zero, default extrusion width will be used if set, " @@ -3455,91 +3783,95 @@ msgid "" "example 90%) it will be computed over layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1571 +#: xs/src/libslic3r/PrintConfig.cpp:1711 msgid "Interface loops" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1573 +#: xs/src/libslic3r/PrintConfig.cpp:1713 msgid "" "Cover the top contact layer of the supports with loops. Disabled by default." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1578 +#: xs/src/libslic3r/PrintConfig.cpp:1718 msgid "Support material/raft interface extruder" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1580 +#: xs/src/libslic3r/PrintConfig.cpp:1720 msgid "" "The extruder to use when printing support material interface (1+, 0 to use " "the current extruder to minimize tool changes). This affects raft too." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1587 +#: xs/src/libslic3r/PrintConfig.cpp:1727 msgid "Interface layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1589 +#: xs/src/libslic3r/PrintConfig.cpp:1729 msgid "" "Number of interface layers to insert between the object(s) and support " "material." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1596 +#: xs/src/libslic3r/PrintConfig.cpp:1736 msgid "Interface pattern spacing" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1598 +#: xs/src/libslic3r/PrintConfig.cpp:1738 msgid "Spacing between interface lines. Set zero to get a solid interface." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1605 -#: xs/src/libslic3r/GCode/PreviewData.cpp:168 +#: xs/src/libslic3r/PrintConfig.cpp:1745 +#: xs/src/libslic3r/GCode/PreviewData.cpp:173 #: lib/Slic3r/GUI/Plater/3DPreview.pm:100 msgid "Support material interface" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1607 +#: xs/src/libslic3r/PrintConfig.cpp:1747 msgid "" "Speed for printing support material interface layers. If expressed as " "percentage (for example 50%) it will be calculated over support material " "speed." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1616 +#: xs/src/libslic3r/PrintConfig.cpp:1756 msgid "Pattern" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1618 +#: xs/src/libslic3r/PrintConfig.cpp:1758 msgid "Pattern used to generate support material." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1630 +#: xs/src/libslic3r/PrintConfig.cpp:1765 +msgid "Rectilinear grid" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1770 msgid "Pattern spacing" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1632 +#: xs/src/libslic3r/PrintConfig.cpp:1772 msgid "Spacing between support material lines." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1641 +#: xs/src/libslic3r/PrintConfig.cpp:1781 msgid "Speed for printing support material." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1648 +#: xs/src/libslic3r/PrintConfig.cpp:1788 msgid "Synchronize with object layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1650 +#: xs/src/libslic3r/PrintConfig.cpp:1790 msgid "" "Synchronize support layers with the object print layers. This is useful with " "multi-material printers, where the extruder switch is expensive." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1656 +#: xs/src/libslic3r/PrintConfig.cpp:1796 msgid "Overhang threshold" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1658 +#: xs/src/libslic3r/PrintConfig.cpp:1798 msgid "" "Support material will not be generated for overhangs whose slope angle (90° " "= vertical) is above the given threshold. In other words, this value " @@ -3548,60 +3880,60 @@ msgid "" "detection (recommended)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1670 +#: xs/src/libslic3r/PrintConfig.cpp:1810 msgid "With sheath around the support" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1672 +#: xs/src/libslic3r/PrintConfig.cpp:1812 msgid "" "Add a sheath (a single perimeter line) around the base support. This makes " "the support more reliable, but also more difficult to remove." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1679 +#: xs/src/libslic3r/PrintConfig.cpp:1819 msgid "" "Extruder temperature for layers after the first one. Set this to zero to " "disable temperature control commands in the output." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1682 +#: xs/src/libslic3r/PrintConfig.cpp:1822 msgid "Temperature" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1688 +#: xs/src/libslic3r/PrintConfig.cpp:1828 msgid "Detect thin walls" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1690 +#: xs/src/libslic3r/PrintConfig.cpp:1830 msgid "" "Detect single-width walls (parts where two extrusions don't fit and we need " "to collapse them into a single trace)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1696 +#: xs/src/libslic3r/PrintConfig.cpp:1836 msgid "Threads" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1697 +#: xs/src/libslic3r/PrintConfig.cpp:1837 msgid "" "Threads are used to parallelize long-running tasks. Optimal threads number " "is slightly above the number of available cores/processors." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1709 +#: xs/src/libslic3r/PrintConfig.cpp:1849 msgid "" "This custom code is inserted right before every extruder change. Note that " "you can use placeholder variables for all Slic3r settings as well as " "[previous_extruder] and [next_extruder]." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1719 xs/src/libslic3r/PrintConfig.cpp:1730 -#: xs/src/libslic3r/GCode/PreviewData.cpp:163 +#: xs/src/libslic3r/PrintConfig.cpp:1859 xs/src/libslic3r/PrintConfig.cpp:1870 +#: xs/src/libslic3r/GCode/PreviewData.cpp:168 #: lib/Slic3r/GUI/Plater/3DPreview.pm:95 msgid "Top solid infill" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1721 +#: xs/src/libslic3r/PrintConfig.cpp:1861 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill for " "top surfaces. You may want to use thinner extrudates to fill all narrow " @@ -3610,7 +3942,7 @@ msgid "" "percentage (for example 90%) it will be computed over layer height." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1732 +#: xs/src/libslic3r/PrintConfig.cpp:1872 msgid "" "Speed for printing top solid layers (it only applies to the uppermost " "external layers and not to their internal solid layers). You may want to " @@ -3619,52 +3951,52 @@ msgid "" "for auto." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1744 lib/Slic3r/GUI/MainFrame.pm:308 +#: xs/src/libslic3r/PrintConfig.cpp:1884 lib/Slic3r/GUI/MainFrame.pm:327 msgid "Top" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1746 +#: xs/src/libslic3r/PrintConfig.cpp:1886 msgid "Number of solid layers to generate on top surfaces." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1748 +#: xs/src/libslic3r/PrintConfig.cpp:1888 msgid "Top solid layers" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1753 +#: xs/src/libslic3r/PrintConfig.cpp:1893 #: lib/Slic3r/GUI/Plater/3DPreview.pm:105 msgid "Travel" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1754 +#: xs/src/libslic3r/PrintConfig.cpp:1894 msgid "Speed for travel moves (jumps between distant extrusion points)." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1762 +#: xs/src/libslic3r/PrintConfig.cpp:1902 msgid "Use firmware retraction" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1763 +#: xs/src/libslic3r/PrintConfig.cpp:1903 msgid "" "This experimental setting uses G10 and G11 commands to have the firmware " "handle the retraction. This is only supported in recent Marlin." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1769 +#: xs/src/libslic3r/PrintConfig.cpp:1909 msgid "Use relative E distances" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1770 +#: xs/src/libslic3r/PrintConfig.cpp:1910 msgid "" "If your firmware requires relative E values, check this, otherwise leave it " "unchecked. Most firmwares use absolute values." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1776 +#: xs/src/libslic3r/PrintConfig.cpp:1916 msgid "Use volumetric E" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1777 +#: xs/src/libslic3r/PrintConfig.cpp:1917 msgid "" "This experimental setting uses outputs the E values in cubic millimeters " "instead of linear millimeters. If your firmware doesn't already know " @@ -3674,113 +4006,135 @@ msgid "" "only supported in recent Marlin." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1787 +#: xs/src/libslic3r/PrintConfig.cpp:1927 msgid "Enable variable layer height feature" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1788 +#: xs/src/libslic3r/PrintConfig.cpp:1928 msgid "" "Some printers or printer setups may have difficulties printing with a " "variable layer height. Enabled by default." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1794 +#: xs/src/libslic3r/PrintConfig.cpp:1934 msgid "Wipe while retracting" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1795 +#: xs/src/libslic3r/PrintConfig.cpp:1935 msgid "" "This flag will move the nozzle while retracting to minimize the possible " "blob on leaky extruders." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1802 +#: xs/src/libslic3r/PrintConfig.cpp:1942 msgid "" "Multi material printers may need to prime or purge extruders on tool " "changes. Extrude the excess material into the wipe tower." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1808 +#: xs/src/libslic3r/PrintConfig.cpp:1948 msgid "Purging volumes - load/unload volumes" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1809 +#: xs/src/libslic3r/PrintConfig.cpp:1949 msgid "" "This vector saves required volumes to change from/to each tool used on the " "wipe tower. These values are used to simplify creation of the full purging " "volumes below. " msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1816 +#: xs/src/libslic3r/PrintConfig.cpp:1956 msgid "Purging volumes - matrix" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1817 +#: xs/src/libslic3r/PrintConfig.cpp:1957 msgid "" "This matrix describes volumes (in cubic milimetres) required to purge the " "new filament on the wipe tower for any given pair of tools. " msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1827 +#: xs/src/libslic3r/PrintConfig.cpp:1967 msgid "Position X" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1828 +#: xs/src/libslic3r/PrintConfig.cpp:1968 msgid "X coordinate of the left front corner of a wipe tower" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1834 +#: xs/src/libslic3r/PrintConfig.cpp:1974 msgid "Position Y" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1835 +#: xs/src/libslic3r/PrintConfig.cpp:1975 msgid "Y coordinate of the left front corner of a wipe tower" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1841 lib/Slic3r/GUI/Plater/3DPreview.pm:76 +#: xs/src/libslic3r/PrintConfig.cpp:1981 lib/Slic3r/GUI/Plater/3DPreview.pm:76 msgid "Width" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1842 +#: xs/src/libslic3r/PrintConfig.cpp:1982 msgid "Width of a wipe tower" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1848 +#: xs/src/libslic3r/PrintConfig.cpp:1988 msgid "Wipe tower rotation angle" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1849 +#: xs/src/libslic3r/PrintConfig.cpp:1989 msgid "Wipe tower rotation angle with respect to x-axis " msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1850 +#: xs/src/libslic3r/PrintConfig.cpp:1990 msgid "degrees" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1855 +#: xs/src/libslic3r/PrintConfig.cpp:1996 +msgid "Purging into infill" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1997 +msgid "" +"Wiping after toolchange will be preferentially done inside infills. This " +"lowers the amount of waste but may result in longer print time due to " +"additional travel moves." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:2005 +msgid "Purging into objects" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:2006 +msgid "" +"Objects will be used to wipe the nozzle after a toolchange to save material " +"that would otherwise end up in the wipe tower and decrease print time. " +"Colours of the objects will be mixed as a result." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:2013 msgid "Maximal bridging distance" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1856 +#: xs/src/libslic3r/PrintConfig.cpp:2014 msgid "Maximal distance between supports on sparse infill sections. " msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1862 +#: xs/src/libslic3r/PrintConfig.cpp:2020 msgid "XY Size Compensation" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1864 +#: xs/src/libslic3r/PrintConfig.cpp:2022 msgid "" "The object will be grown/shrunk in the XY plane by the configured value " "(negative = inwards, positive = outwards). This might be useful for fine-" "tuning hole sizes." msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1872 +#: xs/src/libslic3r/PrintConfig.cpp:2030 msgid "Z offset" msgstr "" -#: xs/src/libslic3r/PrintConfig.cpp:1873 +#: xs/src/libslic3r/PrintConfig.cpp:2031 msgid "" "This value will be added (or subtracted) from all the Z coordinates in the " "output G-code. It is used to compensate for bad Z endstop position: for " @@ -3788,962 +4142,962 @@ msgid "" "print bed, set this to -0.3 (or fix your endstop)." msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:158 +#: xs/src/libslic3r/GCode/PreviewData.cpp:163 #: lib/Slic3r/GUI/Plater/3DPreview.pm:90 msgid "Perimeter" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:159 +#: xs/src/libslic3r/GCode/PreviewData.cpp:164 #: lib/Slic3r/GUI/Plater/3DPreview.pm:91 msgid "External perimeter" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:160 +#: xs/src/libslic3r/GCode/PreviewData.cpp:165 #: lib/Slic3r/GUI/Plater/3DPreview.pm:92 msgid "Overhang perimeter" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:161 +#: xs/src/libslic3r/GCode/PreviewData.cpp:166 #: lib/Slic3r/GUI/Plater/3DPreview.pm:93 msgid "Internal infill" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:164 +#: xs/src/libslic3r/GCode/PreviewData.cpp:169 #: lib/Slic3r/GUI/Plater/3DPreview.pm:96 msgid "Bridge infill" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:171 +#: xs/src/libslic3r/GCode/PreviewData.cpp:176 msgid "Mixed" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:362 +#: xs/src/libslic3r/GCode/PreviewData.cpp:367 #: lib/Slic3r/GUI/Plater/3DPreview.pm:74 msgid "Feature type" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:364 +#: xs/src/libslic3r/GCode/PreviewData.cpp:369 msgid "Height (mm)" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:366 +#: xs/src/libslic3r/GCode/PreviewData.cpp:371 msgid "Width (mm)" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:368 +#: xs/src/libslic3r/GCode/PreviewData.cpp:373 msgid "Speed (mm/s)" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:370 +#: xs/src/libslic3r/GCode/PreviewData.cpp:375 msgid "Volumetric flow rate (mm3/s)" msgstr "" -#: xs/src/libslic3r/GCode/PreviewData.cpp:372 +#: xs/src/libslic3r/GCode/PreviewData.cpp:377 #: lib/Slic3r/GUI/Plater/3DPreview.pm:79 msgid "Tool" msgstr "" -#: lib/Slic3r/GUI.pm:307 +#: lib/Slic3r/GUI.pm:308 msgid "Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:62 +#: lib/Slic3r/GUI/MainFrame.pm:66 msgid "Version " msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:62 +#: lib/Slic3r/GUI/MainFrame.pm:66 msgid "" " - Remember to check for updates at http://github.com/prusa3d/slic3r/releases" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:116 +#: lib/Slic3r/GUI/MainFrame.pm:135 msgid "Plater" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:118 +#: lib/Slic3r/GUI/MainFrame.pm:137 msgid "Controller" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:196 +#: lib/Slic3r/GUI/MainFrame.pm:215 msgid "Open STL/OBJ/AMF/3MF…\tCtrl+O" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:196 +#: lib/Slic3r/GUI/MainFrame.pm:215 msgid "Open a model" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:199 +#: lib/Slic3r/GUI/MainFrame.pm:218 msgid "&Load Config…\tCtrl+L" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:199 +#: lib/Slic3r/GUI/MainFrame.pm:218 msgid "Load exported configuration file" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:202 +#: lib/Slic3r/GUI/MainFrame.pm:221 msgid "&Export Config…\tCtrl+E" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:202 +#: lib/Slic3r/GUI/MainFrame.pm:221 msgid "Export current configuration to file" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:205 +#: lib/Slic3r/GUI/MainFrame.pm:224 msgid "&Load Config Bundle…" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:205 +#: lib/Slic3r/GUI/MainFrame.pm:224 msgid "Load presets from a bundle" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:208 +#: lib/Slic3r/GUI/MainFrame.pm:227 msgid "&Export Config Bundle…" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:208 +#: lib/Slic3r/GUI/MainFrame.pm:227 msgid "Export all presets to file" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:213 +#: lib/Slic3r/GUI/MainFrame.pm:232 msgid "Q&uick Slice…\tCtrl+U" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:213 +#: lib/Slic3r/GUI/MainFrame.pm:232 msgid "Slice a file into a G-code" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:219 +#: lib/Slic3r/GUI/MainFrame.pm:238 msgid "Quick Slice and Save &As…\tCtrl+Alt+U" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:219 +#: lib/Slic3r/GUI/MainFrame.pm:238 msgid "Slice a file into a G-code, save as" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:225 +#: lib/Slic3r/GUI/MainFrame.pm:244 msgid "&Repeat Last Quick Slice\tCtrl+Shift+U" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:225 +#: lib/Slic3r/GUI/MainFrame.pm:244 msgid "Repeat last quick slice" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:232 +#: lib/Slic3r/GUI/MainFrame.pm:251 msgid "Slice to SV&G…\tCtrl+G" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:232 +#: lib/Slic3r/GUI/MainFrame.pm:251 msgid "Slice file to a multi-layer SVG" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:236 +#: lib/Slic3r/GUI/MainFrame.pm:255 msgid "(&Re)Slice Now\tCtrl+S" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:236 +#: lib/Slic3r/GUI/MainFrame.pm:255 msgid "Start new slicing process" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:239 +#: lib/Slic3r/GUI/MainFrame.pm:258 msgid "Repair STL file…" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:239 +#: lib/Slic3r/GUI/MainFrame.pm:258 msgid "Automatically repair an STL file" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:243 +#: lib/Slic3r/GUI/MainFrame.pm:262 msgid "&Quit" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:243 +#: lib/Slic3r/GUI/MainFrame.pm:262 msgid "Quit Slic3r" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:253 +#: lib/Slic3r/GUI/MainFrame.pm:272 msgid "Export G-code..." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:253 +#: lib/Slic3r/GUI/MainFrame.pm:272 msgid "Export current plate as G-code" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:256 +#: lib/Slic3r/GUI/MainFrame.pm:275 msgid "Export plate as STL..." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:256 +#: lib/Slic3r/GUI/MainFrame.pm:275 msgid "Export current plate as STL" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:259 +#: lib/Slic3r/GUI/MainFrame.pm:278 msgid "Export plate as AMF..." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:259 +#: lib/Slic3r/GUI/MainFrame.pm:278 msgid "Export current plate as AMF" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:262 +#: lib/Slic3r/GUI/MainFrame.pm:281 msgid "Export plate as 3MF..." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:262 +#: lib/Slic3r/GUI/MainFrame.pm:281 msgid "Export current plate as 3MF" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:275 +#: lib/Slic3r/GUI/MainFrame.pm:294 msgid "Select &Plater Tab\tCtrl+1" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:275 +#: lib/Slic3r/GUI/MainFrame.pm:294 msgid "Show the plater" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:281 +#: lib/Slic3r/GUI/MainFrame.pm:300 msgid "Select &Controller Tab\tCtrl+T" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:281 +#: lib/Slic3r/GUI/MainFrame.pm:300 msgid "Show the printer controller" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:289 +#: lib/Slic3r/GUI/MainFrame.pm:308 msgid "Select P&rint Settings Tab\tCtrl+2" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:289 +#: lib/Slic3r/GUI/MainFrame.pm:308 msgid "Show the print settings" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:292 +#: lib/Slic3r/GUI/MainFrame.pm:311 msgid "Select &Filament Settings Tab\tCtrl+3" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:292 +#: lib/Slic3r/GUI/MainFrame.pm:311 msgid "Show the filament settings" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:295 +#: lib/Slic3r/GUI/MainFrame.pm:314 msgid "Select Print&er Settings Tab\tCtrl+4" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:295 +#: lib/Slic3r/GUI/MainFrame.pm:314 msgid "Show the printer settings" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:307 +#: lib/Slic3r/GUI/MainFrame.pm:326 msgid "Iso" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:307 +#: lib/Slic3r/GUI/MainFrame.pm:326 msgid "Iso View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:308 +#: lib/Slic3r/GUI/MainFrame.pm:327 msgid "Top View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:309 +#: lib/Slic3r/GUI/MainFrame.pm:328 msgid "Bottom View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:310 +#: lib/Slic3r/GUI/MainFrame.pm:329 msgid "Front" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:310 +#: lib/Slic3r/GUI/MainFrame.pm:329 msgid "Front View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:311 -msgid "Rear" -msgstr "" - -#: lib/Slic3r/GUI/MainFrame.pm:311 +#: lib/Slic3r/GUI/MainFrame.pm:330 msgid "Rear View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:312 +#: lib/Slic3r/GUI/MainFrame.pm:331 msgid "Left" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:312 +#: lib/Slic3r/GUI/MainFrame.pm:331 msgid "Left View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:313 +#: lib/Slic3r/GUI/MainFrame.pm:332 msgid "Right" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:313 +#: lib/Slic3r/GUI/MainFrame.pm:332 msgid "Right View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:319 +#: lib/Slic3r/GUI/MainFrame.pm:338 msgid "Prusa 3D Drivers" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:319 +#: lib/Slic3r/GUI/MainFrame.pm:338 msgid "Open the Prusa3D drivers download page in your browser" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:322 +#: lib/Slic3r/GUI/MainFrame.pm:341 msgid "Prusa Edition Releases" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:322 +#: lib/Slic3r/GUI/MainFrame.pm:341 msgid "Open the Prusa Edition releases page in your browser" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:329 +#: lib/Slic3r/GUI/MainFrame.pm:348 msgid "Slic3r &Website" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:329 +#: lib/Slic3r/GUI/MainFrame.pm:348 msgid "Open the Slic3r website in your browser" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:332 +#: lib/Slic3r/GUI/MainFrame.pm:351 msgid "Slic3r &Manual" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:332 +#: lib/Slic3r/GUI/MainFrame.pm:351 msgid "Open the Slic3r manual in your browser" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:336 +#: lib/Slic3r/GUI/MainFrame.pm:355 msgid "System Info" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:336 +#: lib/Slic3r/GUI/MainFrame.pm:355 msgid "Show system information" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:339 +#: lib/Slic3r/GUI/MainFrame.pm:358 msgid "Show &Configuration Folder" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:339 +#: lib/Slic3r/GUI/MainFrame.pm:358 msgid "Show user configuration folder (datadir)" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:342 +#: lib/Slic3r/GUI/MainFrame.pm:361 msgid "Report an Issue" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:342 +#: lib/Slic3r/GUI/MainFrame.pm:361 msgid "Report an issue on the Slic3r Prusa Edition" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:345 +#: lib/Slic3r/GUI/MainFrame.pm:364 msgid "&About Slic3r" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:345 +#: lib/Slic3r/GUI/MainFrame.pm:364 msgid "Show about dialog" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:355 +#: lib/Slic3r/GUI/MainFrame.pm:374 msgid "&File" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:356 +#: lib/Slic3r/GUI/MainFrame.pm:375 msgid "&Plater" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:357 +#: lib/Slic3r/GUI/MainFrame.pm:376 msgid "&Object" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:358 +#: lib/Slic3r/GUI/MainFrame.pm:377 msgid "&Window" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:359 +#: lib/Slic3r/GUI/MainFrame.pm:378 msgid "&View" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:362 +#: lib/Slic3r/GUI/MainFrame.pm:381 msgid "&Help" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:393 +#: lib/Slic3r/GUI/MainFrame.pm:412 msgid "Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:405 +#: lib/Slic3r/GUI/MainFrame.pm:424 msgid "No previously sliced file." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:406 lib/Slic3r/GUI/Plater.pm:1406 +#: lib/Slic3r/GUI/MainFrame.pm:425 lib/Slic3r/GUI/Plater.pm:1405 msgid "Error" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:410 +#: lib/Slic3r/GUI/MainFrame.pm:429 msgid "Previously sliced file (" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:410 +#: lib/Slic3r/GUI/MainFrame.pm:429 msgid ") not found." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:411 +#: lib/Slic3r/GUI/MainFrame.pm:430 msgid "File Not Found" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:450 +#: lib/Slic3r/GUI/MainFrame.pm:469 msgid "SVG" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:450 +#: lib/Slic3r/GUI/MainFrame.pm:469 msgid "G-code" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:450 lib/Slic3r/GUI/Plater.pm:1756 +#: lib/Slic3r/GUI/MainFrame.pm:469 lib/Slic3r/GUI/Plater.pm:1795 msgid " file as:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:464 +#: lib/Slic3r/GUI/MainFrame.pm:483 msgid "Slicing…" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:464 +#: lib/Slic3r/GUI/MainFrame.pm:483 msgid "Processing " msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:484 +#: lib/Slic3r/GUI/MainFrame.pm:503 msgid " was successfully sliced." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:486 +#: lib/Slic3r/GUI/MainFrame.pm:505 msgid "Slicing Done!" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:502 +#: lib/Slic3r/GUI/MainFrame.pm:521 msgid "Select the STL file to repair:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:516 +#: lib/Slic3r/GUI/MainFrame.pm:535 msgid "Save OBJ file (less prone to coordinate errors than STL) as:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:530 +#: lib/Slic3r/GUI/MainFrame.pm:549 msgid "Your file was repaired." msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:530 +#: lib/Slic3r/GUI/MainFrame.pm:549 msgid "Repair" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:541 +#: lib/Slic3r/GUI/MainFrame.pm:560 msgid "Save configuration as:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:559 lib/Slic3r/GUI/MainFrame.pm:603 +#: lib/Slic3r/GUI/MainFrame.pm:578 lib/Slic3r/GUI/MainFrame.pm:622 msgid "Select configuration to load:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:582 +#: lib/Slic3r/GUI/MainFrame.pm:601 msgid "Save presets bundle as:" msgstr "" -#: lib/Slic3r/GUI/MainFrame.pm:623 +#: lib/Slic3r/GUI/MainFrame.pm:642 #, possible-perl-format msgid "%d presets successfully imported." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:140 lib/Slic3r/GUI/Plater.pm:2282 +#: lib/Slic3r/GUI/Plater.pm:164 lib/Slic3r/GUI/Plater.pm:2323 msgid "3D" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:180 +#: lib/Slic3r/GUI/Plater.pm:206 msgid "2D" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:199 +#: lib/Slic3r/GUI/Plater.pm:224 msgid "Layers" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:232 lib/Slic3r/GUI/Plater.pm:250 +#: lib/Slic3r/GUI/Plater.pm:250 lib/Slic3r/GUI/Plater.pm:268 msgid "Add…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:234 lib/Slic3r/GUI/Plater.pm:252 +#: lib/Slic3r/GUI/Plater.pm:252 lib/Slic3r/GUI/Plater.pm:270 msgid "Delete All" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:235 lib/Slic3r/GUI/Plater.pm:253 +#: lib/Slic3r/GUI/Plater.pm:253 lib/Slic3r/GUI/Plater.pm:271 msgid "Arrange" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:237 +#: lib/Slic3r/GUI/Plater.pm:255 msgid "More" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:238 +#: lib/Slic3r/GUI/Plater.pm:256 msgid "Fewer" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:240 +#: lib/Slic3r/GUI/Plater.pm:258 msgid "45° ccw" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:241 +#: lib/Slic3r/GUI/Plater.pm:259 msgid "45° cw" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:242 lib/Slic3r/GUI/Plater.pm:258 +#: lib/Slic3r/GUI/Plater.pm:260 lib/Slic3r/GUI/Plater.pm:276 msgid "Scale…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:243 lib/Slic3r/GUI/Plater.pm:259 -#: lib/Slic3r/GUI/Plater.pm:2252 +#: lib/Slic3r/GUI/Plater.pm:261 lib/Slic3r/GUI/Plater.pm:277 +#: lib/Slic3r/GUI/Plater.pm:2293 msgid "Split" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:244 lib/Slic3r/GUI/Plater.pm:260 -#: lib/Slic3r/GUI/Plater.pm:2255 +#: lib/Slic3r/GUI/Plater.pm:262 lib/Slic3r/GUI/Plater.pm:278 +#: lib/Slic3r/GUI/Plater.pm:2296 msgid "Cut…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:246 lib/Slic3r/GUI/Plater.pm:261 -#: lib/Slic3r/GUI/Plater.pm:2259 +#: lib/Slic3r/GUI/Plater.pm:264 lib/Slic3r/GUI/Plater.pm:279 +#: lib/Slic3r/GUI/Plater.pm:2300 msgid "Settings…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:247 +#: lib/Slic3r/GUI/Plater.pm:265 msgid "Layer Editing" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:262 +#: lib/Slic3r/GUI/Plater.pm:280 msgid "Layer editing" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:285 +#: lib/Slic3r/GUI/Plater.pm:303 msgid "Name" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:286 lib/Slic3r/GUI/Plater.pm:1007 +#: lib/Slic3r/GUI/Plater.pm:304 lib/Slic3r/GUI/Plater.pm:992 msgid "Copies" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:287 lib/Slic3r/GUI/Plater.pm:1163 -#: lib/Slic3r/GUI/Plater.pm:1168 lib/Slic3r/GUI/Plater.pm:2221 +#: lib/Slic3r/GUI/Plater.pm:305 lib/Slic3r/GUI/Plater.pm:1158 +#: lib/Slic3r/GUI/Plater.pm:1163 lib/Slic3r/GUI/Plater.pm:2262 msgid "Scale" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:301 +#: lib/Slic3r/GUI/Plater.pm:322 msgid "Export G-code…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:302 +#: lib/Slic3r/GUI/Plater.pm:323 msgid "Slice now" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:303 +#: lib/Slic3r/GUI/Plater.pm:324 msgid "Print…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:304 +#: lib/Slic3r/GUI/Plater.pm:325 msgid "Send to printer" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:305 +#: lib/Slic3r/GUI/Plater.pm:326 msgid "Export STL…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:432 +#: lib/Slic3r/GUI/Plater.pm:453 msgid "Print settings" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:434 +#: lib/Slic3r/GUI/Plater.pm:455 msgid "Printer" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:467 +#: lib/Slic3r/GUI/Plater.pm:488 msgid "Info" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:478 +#: lib/Slic3r/GUI/Plater.pm:499 msgid "Volume" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:479 +#: lib/Slic3r/GUI/Plater.pm:500 msgid "Facets" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:480 +#: lib/Slic3r/GUI/Plater.pm:501 msgid "Materials" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:481 +#: lib/Slic3r/GUI/Plater.pm:502 msgid "Manifold" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:507 +#: lib/Slic3r/GUI/Plater.pm:527 msgid "Sliced Info" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:516 -msgid "Used Filament (m)" -msgstr "" - -#: lib/Slic3r/GUI/Plater.pm:517 -msgid "Used Filament (mm³)" -msgstr "" - -#: lib/Slic3r/GUI/Plater.pm:518 -msgid "Used Filament (g)" -msgstr "" - -#: lib/Slic3r/GUI/Plater.pm:520 -msgid "Estimated printing time" -msgstr "" - -#: lib/Slic3r/GUI/Plater.pm:728 +#: lib/Slic3r/GUI/Plater.pm:713 msgid "Loading…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:728 lib/Slic3r/GUI/Plater.pm:742 +#: lib/Slic3r/GUI/Plater.pm:713 lib/Slic3r/GUI/Plater.pm:727 msgid "Processing input file\n" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:765 +#: lib/Slic3r/GUI/Plater.pm:750 msgid "" "This file contains several objects positioned at multiple heights. Instead " "of considering them as multiple objects, should I consider\n" "this file as a single object having multiple parts?\n" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:768 lib/Slic3r/GUI/Plater.pm:785 +#: lib/Slic3r/GUI/Plater.pm:753 lib/Slic3r/GUI/Plater.pm:770 msgid "Multi-part object detected" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:782 +#: lib/Slic3r/GUI/Plater.pm:767 msgid "" "Multiple objects were loaded for a multi-material printer.\n" "Instead of considering them as multiple objects, should I consider\n" "these files to represent a single object having multiple parts?\n" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:794 +#: lib/Slic3r/GUI/Plater.pm:779 msgid "Loaded " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:852 +#: lib/Slic3r/GUI/Plater.pm:837 msgid "" "Your object appears to be too large, so it was automatically scaled down to " "fit your print bed." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:853 +#: lib/Slic3r/GUI/Plater.pm:838 msgid "Object too large?" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1007 +#: lib/Slic3r/GUI/Plater.pm:992 msgid "Enter the number of copies of the selected object:" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1034 +#: lib/Slic3r/GUI/Plater.pm:1019 msgid "" "\n" "Non-positive value." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1035 +#: lib/Slic3r/GUI/Plater.pm:1020 msgid "" "\n" "Not a numeric value." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1036 +#: lib/Slic3r/GUI/Plater.pm:1021 msgid "Slic3r Error" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1057 +#: lib/Slic3r/GUI/Plater.pm:1042 msgid "Enter the rotation angle:" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1057 +#: lib/Slic3r/GUI/Plater.pm:1042 msgid "Rotate around " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1057 +#: lib/Slic3r/GUI/Plater.pm:1042 msgid "Invalid rotation angle entered" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1137 +#: lib/Slic3r/GUI/Plater.pm:1132 #, possible-perl-format msgid "Enter the new size for the selected object (print bed: %smm):" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1138 lib/Slic3r/GUI/Plater.pm:1142 +#: lib/Slic3r/GUI/Plater.pm:1133 lib/Slic3r/GUI/Plater.pm:1137 msgid "Scale along " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1138 lib/Slic3r/GUI/Plater.pm:1142 -#: lib/Slic3r/GUI/Plater.pm:1163 lib/Slic3r/GUI/Plater.pm:1168 +#: lib/Slic3r/GUI/Plater.pm:1133 lib/Slic3r/GUI/Plater.pm:1137 +#: lib/Slic3r/GUI/Plater.pm:1158 lib/Slic3r/GUI/Plater.pm:1163 msgid "Invalid scaling value entered" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1142 lib/Slic3r/GUI/Plater.pm:1168 +#: lib/Slic3r/GUI/Plater.pm:1137 lib/Slic3r/GUI/Plater.pm:1163 #, no-perl-format msgid "Enter the scale % for the selected object:" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1163 +#: lib/Slic3r/GUI/Plater.pm:1158 msgid "Enter the new max size for the selected object:" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1219 +#: lib/Slic3r/GUI/Plater.pm:1218 msgid "" "The selected object can't be split because it contains more than one volume/" "material." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1228 +#: lib/Slic3r/GUI/Plater.pm:1227 msgid "" "The selected object couldn't be split because it contains only one part." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1392 +#: lib/Slic3r/GUI/Plater.pm:1391 msgid "Slicing cancelled" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1406 +#: lib/Slic3r/GUI/Plater.pm:1405 msgid "Another export job is currently running." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1556 +#: lib/Slic3r/GUI/Plater.pm:1555 msgid "File added to print queue" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1562 +#: lib/Slic3r/GUI/Plater.pm:1561 msgid "G-code file exported to " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1565 +#: lib/Slic3r/GUI/Plater.pm:1564 msgid "Export failed" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1577 +#: lib/Slic3r/GUI/Plater.pm:1576 msgid "OctoPrint upload finished." msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1620 lib/Slic3r/GUI/Plater.pm:1662 +#: lib/Slic3r/GUI/Plater.pm:1610 +msgid "Used Filament (m)" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:1612 +msgid "Used Filament (mm³)" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:1614 +msgid "Used Filament (g)" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:1618 +msgid "Estimated printing time (normal mode)" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:1620 +msgid "Estimated printing time (silent mode)" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:1659 lib/Slic3r/GUI/Plater.pm:1701 msgid "STL file exported to " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1701 +#: lib/Slic3r/GUI/Plater.pm:1740 msgid "AMF file exported to " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1705 +#: lib/Slic3r/GUI/Plater.pm:1744 msgid "Error exporting AMF file " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1717 +#: lib/Slic3r/GUI/Plater.pm:1756 msgid "3MF file exported to " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:1721 +#: lib/Slic3r/GUI/Plater.pm:1760 msgid "Error exporting 3MF file " msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2099 +#: lib/Slic3r/GUI/Plater.pm:2140 #, possible-perl-format msgid "%d (%d shells)" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2101 +#: lib/Slic3r/GUI/Plater.pm:2142 #, possible-perl-format msgid "Auto-repaired (%d errors)" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2106 +#: lib/Slic3r/GUI/Plater.pm:2147 #, possible-perl-format msgid "" "%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d " "facets reversed, %d backwards edges" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2111 +#: lib/Slic3r/GUI/Plater.pm:2152 msgid "Yes" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2174 +#: lib/Slic3r/GUI/Plater.pm:2215 msgid "Remove the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2177 +#: lib/Slic3r/GUI/Plater.pm:2218 msgid "Increase copies" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2177 +#: lib/Slic3r/GUI/Plater.pm:2218 msgid "Place one more copy of the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2180 +#: lib/Slic3r/GUI/Plater.pm:2221 msgid "Decrease copies" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2180 +#: lib/Slic3r/GUI/Plater.pm:2221 msgid "Remove one copy of the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2183 +#: lib/Slic3r/GUI/Plater.pm:2224 msgid "Set number of copies…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2183 +#: lib/Slic3r/GUI/Plater.pm:2224 msgid "Change the number of copies of the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2187 +#: lib/Slic3r/GUI/Plater.pm:2228 msgid "Rotate 45° clockwise" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2187 +#: lib/Slic3r/GUI/Plater.pm:2228 msgid "Rotate the selected object by 45° clockwise" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2190 +#: lib/Slic3r/GUI/Plater.pm:2231 msgid "Rotate 45° counter-clockwise" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2190 +#: lib/Slic3r/GUI/Plater.pm:2231 msgid "Rotate the selected object by 45° counter-clockwise" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2195 +#: lib/Slic3r/GUI/Plater.pm:2236 msgid "Rotate" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2195 +#: lib/Slic3r/GUI/Plater.pm:2236 msgid "Rotate the selected object by an arbitrary angle" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2197 +#: lib/Slic3r/GUI/Plater.pm:2238 msgid "Around X axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2197 +#: lib/Slic3r/GUI/Plater.pm:2238 msgid "Rotate the selected object by an arbitrary angle around X axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2200 +#: lib/Slic3r/GUI/Plater.pm:2241 msgid "Around Y axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2200 +#: lib/Slic3r/GUI/Plater.pm:2241 msgid "Rotate the selected object by an arbitrary angle around Y axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2203 +#: lib/Slic3r/GUI/Plater.pm:2244 msgid "Around Z axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2203 +#: lib/Slic3r/GUI/Plater.pm:2244 msgid "Rotate the selected object by an arbitrary angle around Z axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2208 +#: lib/Slic3r/GUI/Plater.pm:2249 msgid "Mirror" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2208 +#: lib/Slic3r/GUI/Plater.pm:2249 msgid "Mirror the selected object" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2210 lib/Slic3r/GUI/Plater.pm:2226 -#: lib/Slic3r/GUI/Plater.pm:2242 +#: lib/Slic3r/GUI/Plater.pm:2251 lib/Slic3r/GUI/Plater.pm:2267 +#: lib/Slic3r/GUI/Plater.pm:2283 msgid "Along X axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2210 +#: lib/Slic3r/GUI/Plater.pm:2251 msgid "Mirror the selected object along the X axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2213 lib/Slic3r/GUI/Plater.pm:2229 -#: lib/Slic3r/GUI/Plater.pm:2245 +#: lib/Slic3r/GUI/Plater.pm:2254 lib/Slic3r/GUI/Plater.pm:2270 +#: lib/Slic3r/GUI/Plater.pm:2286 msgid "Along Y axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2213 +#: lib/Slic3r/GUI/Plater.pm:2254 msgid "Mirror the selected object along the Y axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2216 lib/Slic3r/GUI/Plater.pm:2232 -#: lib/Slic3r/GUI/Plater.pm:2248 +#: lib/Slic3r/GUI/Plater.pm:2257 lib/Slic3r/GUI/Plater.pm:2273 +#: lib/Slic3r/GUI/Plater.pm:2289 msgid "Along Z axis…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2216 +#: lib/Slic3r/GUI/Plater.pm:2257 msgid "Mirror the selected object along the Z axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2221 lib/Slic3r/GUI/Plater.pm:2237 +#: lib/Slic3r/GUI/Plater.pm:2262 lib/Slic3r/GUI/Plater.pm:2278 msgid "Scale the selected object along a single axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2223 lib/Slic3r/GUI/Plater.pm:2239 +#: lib/Slic3r/GUI/Plater.pm:2264 lib/Slic3r/GUI/Plater.pm:2280 msgid "Uniformly…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2223 lib/Slic3r/GUI/Plater.pm:2239 +#: lib/Slic3r/GUI/Plater.pm:2264 lib/Slic3r/GUI/Plater.pm:2280 msgid "Scale the selected object along the XYZ axes" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2226 lib/Slic3r/GUI/Plater.pm:2242 +#: lib/Slic3r/GUI/Plater.pm:2267 lib/Slic3r/GUI/Plater.pm:2283 msgid "Scale the selected object along the X axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2229 lib/Slic3r/GUI/Plater.pm:2245 +#: lib/Slic3r/GUI/Plater.pm:2270 lib/Slic3r/GUI/Plater.pm:2286 msgid "Scale the selected object along the Y axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2232 lib/Slic3r/GUI/Plater.pm:2248 +#: lib/Slic3r/GUI/Plater.pm:2273 lib/Slic3r/GUI/Plater.pm:2289 msgid "Scale the selected object along the Z axis" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2237 +#: lib/Slic3r/GUI/Plater.pm:2278 msgid "Scale to size" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2252 +#: lib/Slic3r/GUI/Plater.pm:2293 msgid "Split the selected object into individual parts" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2255 +#: lib/Slic3r/GUI/Plater.pm:2296 msgid "Open the 3D cutting tool" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2259 +#: lib/Slic3r/GUI/Plater.pm:2300 msgid "Open the object editor dialog" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2263 +#: lib/Slic3r/GUI/Plater.pm:2304 msgid "Reload from Disk" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2263 +#: lib/Slic3r/GUI/Plater.pm:2304 msgid "Reload the selected file from Disk" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2266 +#: lib/Slic3r/GUI/Plater.pm:2307 msgid "Export object as STL…" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2266 +#: lib/Slic3r/GUI/Plater.pm:2307 msgid "Export this single object as STL file" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2270 +#: lib/Slic3r/GUI/Plater.pm:2311 msgid "Fix STL through Netfabb" msgstr "" -#: lib/Slic3r/GUI/Plater.pm:2270 +#: lib/Slic3r/GUI/Plater.pm:2311 msgid "" "Fix the model by sending it to a Netfabb cloud service through Windows 10 API" msgstr "" diff --git a/resources/localization/uk/Slic3rPE.mo b/resources/localization/uk/Slic3rPE.mo index 7ced15dc8c91acee358022d27d2d1abdf88c2313..db63e8c8f02d6385fad4ea6b9e32f275ed0facd5 100644 GIT binary patch delta 16643 zcmajlcYGGbzQ^&|5JH5|LoWg8Jp`mf=-tqzNSA~n9U`5F7J8F_fG8aV0RssVM4Al& zQ9@CX4vJC~R1^gi?&rHRp6k8$kGrok=RLof+1=Th+44j-9tlm`85+2pBY2r%yBlOo zL7bn*m|xNv^H)Wc8gsawF(vT>EQdF-IEF_XQxt1rVSEv%<9H0mCzuO!);H#PEQ6sq z1~cFkOm9rU%q9~|!9vu567d;K#=N*6WARJWjhzNq#S(ZgjJhRZPv zZp7i3f;q5wLtQ|?nCfIQQ_vN4fd0-YNDs|Q=NimSyveyA)uA)a3#j(rVhOzE;;fBq zyIiOa7eZZN6%6D2rY;!`aZA(zN1z&xLk-y?SH1!d60ddjy&Bv0@ygCaWju``Sg)x)ZW9b>=wisE!vUBT2RkP^=VNKg-$0g!`2-8$CDi_pF&pM;W-aNg zj=G@c$Y7YZm=O~(JFafV`0D^WDJX)+QH$pes@ycUBat7~!AR85Rdv=wb+|cBz%G~{ z&te9=hnliKFcP!0up*47HecphhGGHRMN8C;Z;U zk5DJb+uD{_MXm1USQ=l%k~qVaZ^Mkl2T>OmIOZx&VF(4^VKCl7{%3x6<#oAZDvF+NT_Un!6K!2p80W*?}4v>I#aRG+mXSRVkhnmwn zI1)qJ*eM!^I^jI*h)eMqyoDO6`?w0Tx3z0&2PP1Iit5<&?R0kLKbDM!_y)GeM_5x0 z2y`3HLM@^ndFpHiR$AEN3{p!U0n;dsZD zo9=cBvvp_uwc7JgphZ^@wTQ~NxC-hD>!WVR7*xkPVs`9-I>9j16-`F9U*gKwVm{&| z)EYR3I?q|>wE&s&6x_wpSb&Mr2^OOcxDqu2J5WRS9_qv=P(ytgbpgNQA$)|aL$j}^ zFO?D1Q?=HWr(kB{{m$d4HE<4dYAA1z(T)!> z7wYn5ZdAw0qZValRJ+>FhFFBS1?I;=s5Oy*8So9%oNvUen1t%+A=L4XVnDCe(`2&Z zSEwuc-WfdH-Zsx-3gy*N?eC!4-@`D5{3%8dhmEuyERX6?6)cA_&hgI8s1BSS$@uFE zFH$fWuc3DAJ<1N@1UyK*5Y_H^ZV}bDN5w->x8+FG$c#mGEP%yu5w^t?)Nz92c`V~c zSPvh?GyYY`)O^LR+98;YIDotc%wp7{xsF=pk5Q{T!x(EmEJ0ijb>eoYx$T46Zzxv9 zSy&DCV@bS^r7(YBtR3=(_yq;MQ3tFv&R$U`)D?C|9k?&5Bf~H^PQWa<1asgz=Wf&* z_|$n4wFtjPb@T?7#lY`mG#5q2+XIwEEtfF-a!_D6MK zAF2aKP$#~G&*ELohuK-#I(|9SiK8$-c2zm&H>1gD?k1pya3N}rHe)&5gE{d!YHA*! z4&bq~+80EXS4Az(Xe^BFP#qeMt#B&F;OD507o5WQYjKq(qXSe#^|U7H0aPCg;R~om zHX6(0bkwTeiII2%>tXJx)=ti)sNxWt+BV9ZJHTQEc0@tH1?8E5{MR77$DbQS{pJ9L1 zMqn}G2B?Oys3Dz>nu_(P5!i#8sykQ_AG`Xp3HE~Op{ArI>cYBXMURn0y{t9_X0lkw z?3=}$;-%R<)$q?b-0K)OkHv<^=CcT~;6mDPppuL10fHCXD=qM$baI$lSeDF1T%fl>-Jg2%8Gev7&*Dy`tWoZmDda{&7w$2Jiw?V@}a)q(S< zMU(q=Zb{-MtL&A}bS^=4U^Q04ji?TsMV>TY<98lm7f>;>k=fabmwnOdBnA(ked z^rr2=W-LJb8M3g=4_E{TuhxT`slf%9d5v8gt58$24>k8kT>K@fL)VZ#n7gPEn7fw4 zXmze#YZuifOe209E773|>+El~ZR?F;A5(dQU2LaNL-`nW#mzPv!}>I1a0))NiG$-@ zq>0J3nP&pNhc&S9TXyO?zZKx~iGlzHo$weo$A~TVr_}&#NStn~y}z4cG2(dKjw^8q z9c}uyF+UQ2vyEp09-(qCPTIlmLag=<>kNw~F~YRph&_lu5A3vmt0}jOp`>Cr{zgT^ z6rNbb)py%RX4`k|6)r%1az${Kc33`@;imm$&h`q{e2=Nbo!AHOU~PP!M#(q_M_`@z z`TG(E-XSxLOzHjnsK6E25@Qc=a89rjHT0Jb^0|bQ4%ttsv4`zcRsVoLUQizTAs-j` z5?;sJAMr-T!XNYZ9xU{U{Q}yB(Zn|~GKl#veS`yZqOM0-o!IfXeecJ8=Knx2Lot{g zen;K6k1!o(rnjM(9d*0rcb3CE#C0$PJE9ibi&zRrU>L5%%v#2q$z-KqxASAv0nTCs z{)l-o>~lMGC9o~=a@2`$Vs?D!%=CqQG8V!x%A+s~Mx#0$gF0Vl4Cef%KNL52gjO+QyK-kE?ZUyK8AGZx0IUvUzwh~d}|E8#fQ9B*~?by*0SqEXl#$6ysai%}SQ z&i-7leU9;eO2J+V^2u-PpI%O59^$a`#`MDCsG*yIIdK6N#C52VI*1y(@qfMu}5Rr_`fOd`{Tf{mzE{x@nWa$h51B&x^FunxwcR_{El zhpTWMo=5FBk(*y5_ZsR%yRa-?z)~3Ut^MC7^ds~qW%TrMRH3iL4 z9qWocaU|-5r%+dR9y4L^57ulL}K@7((0()u9+?f9DizPWejI?RXtE0y%!N9eEx#C1adRoI5a}10E$a z7O$bMsM}rp!y^tG5HCRu`B~HzWV>fi&;~VCb1@5UMy-K&ohPvb@h$ufv;J&56#R=F znTo$K{za*%O+gIyKn>M)%#Nv89FJir-bF3KKd=~9{nfVbf|~o6FgvcpW{hkKY7sxa zZ}-nZFZ5t5fEwY-zcK%jWSUaY3di6e{0LiO#ou{#;yBcr_!PV0IaEihJg~P}J=Dky z#|TWq@^}&};9pn~%l=_UwktLzP6&|EiBDi<%=@Q(%eBPZ#63_~IvRDt6{wDEz`3{^ z3t}`2NJHHn^_-Z7nwr;89ZJR`cp7IiGWSta8W{iB{gOc)_zvd5zfe66=c$nc8{sH? z0X1~Tu_|7{Y8d`Ek6mnqr7_H+5RnE$Fz?ILSwJlz0hK9 zo@cl*%o@~{%?a}SzYiwiMB=jPJpV**p*oU=k(fT%^E+G(>l3%Y&Nvsf#;#*8Mxb(f zkGqEXe>KGOhv+=kqroF=gf%mG{?tsuIO1bC3?oB5|G$i^MLp4e!iJcO-)GCP3np=b zyI2obXY%~J;}YL(HL3s6Su(R{vT=UXn@lQ>z;f(RCX46a=bf^8{^}o!n!`kFj49X} z|3n?I2_FG6fVzP9T>K|$5k~P@rIG1{<#4fcKL%8AlZ*}+p2PDOTOVfvYG}7&G+sts zY2lo9i2GnJ;?bxM&PC1r+o&l!gDttBhxiq7VOs{F%VGev|J$gJoxlN%#Miin zxPCz!Uk#ASLP3kd_DZ{;o^(S|Lq7&}pzWyVz!#_v{EAaJV7ds;f7Z_^!j%T`w_W6p zG&i61{I}nYVzz_7;y09s6t`>ODy}CEgq85jP!5!YTd{B{&;Ma@w6y2{!6AQH&rG6W zaaJa8T(cgNaTY%gSYM`A1<$;St@wpH5HD5Yw!&tWJ<}8yM|q|R9s9b9XATh;s>T{n zyXv04tODlO8g@}+tLgaGcFNj1$D+#DVmrKw znu4g>cBJcK6me?|)%_nJqXW)%zKvRRCs2#zI;sO+9nTcOjLveHm$)^mW3i~IOh8T1 zQtW`6P#t=RS{pU%+C|t8{ontSm7(Hw)B*RP9=Rt`?||#5weS%2(x_NZPcD9hVs$)- z8u|yQ+pBkddxi11o1x!=dWrRD(HqsV5vbd5CTeQeHs}6dMdk_x-8s;N z7A!{Mb38R>V6#@9|8F)HyRtoQHau)O+5J;6yA+yaF{P`%r7(JnHRQ zp6@djo#}}hxrs5{|GmiUrr;F}Z{wLhI1gLkPgozTxAjavIye^fy1vxTo~TTFyEa;3 zcFH@Vt}qVO;VGz&Y(qVeK0|fj7u1Mk4KRKh;^L@9R~fZqGaQX0QA79x>HvSD)<)ru z_H9-fwf_{-Hw<%JZ`H)vh+yz*yAWuSUJ~cA)Nx)2Ihc;aGdy z4MxQWondiyFpTnXsZc1B&u8q{rhY#{f4H8Qs-(20u-@=W_6zH%`dw+`lG6K@UW ziZE^jt2iAU80DGA#1~)j%o#j4j^p6=iJnRG_H-GR0kiTt}Jqf=YN(r$Ew6*oZGPl@io-*pj?8Tni{A@-2*jc z>rn5Somdb9C&=iDbjuZFm}wVZan#h*#(mfhwRpm2*;O2mb%h1~C{(JQ}swHenOIj2gP)b8Y*2m_XbYQyB7_ z_$~1p^F6a1r!4UNKk4ROXz!Y#7*F|B)Rq5%$*eyUzQ~^F)5V_u?{@S#qoF;3nv!2} z8z(5dgw>136YZk>W2xuAyc#d#9l~5+#VNF}zrv2h2hK~V+x8*q5goSDUPuqDuKRxs znM|038lv}6J6yy1_!|!91kb(h`G4zK|Ay!P2u=T{y&HOC7#-V$`rt@j?U`#BvWDB3 z@|#$TIPW?)C8)b#G6wXZ*+NE(>k#H-hp$mz!FSf%w^Yszc1}y9R(C7RPlpO_@=OqM z^|$z4fVH;R#XD)MXO^)4HPmf5`fbm2rT#F6V*c%(`I7QtI~e~M3Lft8%q9w&C)wBU zU5qAfyVLXkQR!lQoj81#XXaoMmSDe{$@YQOiv`e{@-wK#R%Ewlc=4HL7?O@P@vdi1 z5pUSb2x9y`&y2ux?{oiuOs3@lzF;uxA#O*mEYD%je9wVOf9RP9nDryiyi0iqjWgl? zBMc=TJ!*d++&|7;!~QRS=9!x~?F2t&sekzk&-5nl%-NrEoV};*$m~7inXAOD0%tw{ zKMa)r()0hjUlQJ=qR`i#`4dCVdH#QVdWs)#pr607&+_C8ynHx8!HcXF%3r$d8Tw*g zx#Ic1@1w7IW*qU6>z+Bwaq54^2ovYJ;hAIj{`a2w9Rmq;H!GP5w>|%#P|myKnHB7i z;YXee!F;3PQHJo~&z^ZgyIH^4*Y3;*j1=*!7*6~3f7r#9ih2NDNBuIohkBH!`_oQM zHLOeA16g}~|B=yyA{jg3CDdZ9^3cBhT47P*IMnNOHtKb|58L5GY>2HMdHx@n7UF*5 z>5pyu`hVFc=1Au{REN^AlHUL6{SWU)JQ!+y?!Hk6NPfZ$=Hi{R+b?D z!hXO&G6i?Z^u~2rgZxjZAZ~%e?9c}jaZ2_e|9c=jXORDu+!!m;{(aQD;Wp;LP2oZQ zRP9A|;5@3sVY!0*w_SPEqrEOxz@fQ<0{*lA4GNy2;4o@29Y?*bt{`jB+{L1JA2mg} zatHa}0mV=+p#hiyH=$muJ5a|vh?>HqEokCOTkEu*3( zdG0vVhor3&ae0y!)@57dKVr2o8&bB?m1zNfO6o^hGvxckB)Pgqlxg8=D@3`T1A%ua z)W(C#oFIRl{8Y?CT#uxUTikz0*@$5y4t4wKvCTv8|J`1v{gMCMO6&i>H$5t~u-g9X zfSS*vq~q>{JmPiN8ty9pfz*}!`{a4I_}iQO|BuA^NS~4Z**+&Tl{N?QSL88cQdocf z<8l!JQ)r6g5E}5%GU-X$hB%e~nl_&kzfXFPyuN%gkzYr8UJcj|k@NxbD`_z)Gj&sF z+l{n}d<7CWjNSg{_1k~*(ZN;fp~BPH45sNpe4SLAx+cVL;}YTmxR7`?d2MrvzaeQ$ zCGDfU6zMVfwUiGd{}ZV%@yn#ZA=kJ*?xtX@i^-bTXc$F$mHb@%6pK^81z#d*n?v~` z$`Wko|H)Qao;{`y?j%L4fK9LNF|^mF=SHk%GSQvz6$<97hHVgyc2Qmps}b|IG#?O; zC*>wzgcRcT(<511G-bMVf1vyg^7lv$$m_FRTRic*Se3X1`D1>$8~>pc{Cm@5Slc=p zU2z9cywtU;M7}5G+i@;Q-w>;bKOnF7|6TI>LYhq)O<4tOgKbe;OWNqQ?XTob6!Ix- zx}%UnBp4s~e0FE`MF${}l;N(vbiCXS+}CeG;F4{&vCQ|5Ghxd3F1nUendd z@21@ktlP;m#z)jfO(E! zBe~~DzmWdfGLWglLB1l@A)R#}buCh~0SNARcK&%gnMx?h%b7)tYbb$OV>Y9-+ zjQL3kqyXukO&=)F=>30hvfprB~OByGj8G$~y7|KAjhBE6s*3TL@S-zj!m zC*0%8^ON5|J`OLqvcJd|CT-+|)k$T@A0{oNehRU+DWtz#eg?io-FD6Y7J_cBVi%33 zxqN#ZN_`WI##ZLH@Qo;7Sg5oA@JFFKSZuBK7=M zF`p1$A=c&*KO(;di})qHF*X0k2~u67DB{iJi@F90#KA6KnEW`>hoq;J<)mFO`89YG zUsXBz{a0i%{DR@?A*tNnAfyN#+teYHMLJ8!6K^it^80UhxE%Po_Mt z%jdvi#O2&ErqeFQ)vYG~6M240`u{#Km^P)zr>DHLJ3m>wor1=0$4lhjAm5K2Mv$_* zgAb=XiSmM21N)N3Q8$}Zg0lXkk6hay$xk4Srp-!{MqAr8+(VnQn*ZBm9+C!9Sc!Co z23hb6Qg_Pt68FGC#M-_f{Y|`sr0qL=h*wCNNxu<)j5TTBll*_F1C5MQQjl?}~dSH9K7H>fkNOhwO=A46F|(qc(m zobvLxQ*GHMy7pfM)!}Cm1=&@~_M)qJhy3@XkuKile9M{3H|28l+vyTlG#Z?kqwy;# zUo;NQlsT?X{NT|=hL4OL6+3!V;+ZxR64SR!PC3%2giw#ZL)^|d>#8veR zq>PSRlqIELe1o8r_%YejrK}r&ASf|@()TGNCLa$;iAflhJ*D3ADPdVUj2ksNcE~@+ zOgXUOn71Kvb72FCVFoPPD(bJZ(ERVh+A zs#avRa*5j Md#PuN2DS743t%*CegFUf delta 18663 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

K0hhuweag+xKH<*o@g1zrM&VB54%zS!vJ#MD1 z&}DTbXk z-vcdK7*n~h17^lEsQr5ZGvN;yj6Y#Myk`y0Fwco%n3eo6)Z%*@HTU6|i~Bnxh~&me zm>1_;S78y-+psjA#G?2;4#S``<|8y2b>rihAJ1EFqMnrZQBT0UpP7c1Lfxk#`qiTb zMAX7(F$cE9Y#5H^u^Z0Bu}ED`wzKSOM(7ge!{5*G+$TQ^L)|b2 zm7ieK$*6{owN6G&>14%8z00JT^ypcdN=R1f|^HMHQ@Y&+(> zFb*Jn?i=&cdHS09Fd2$rlGn|XGxS^YR&0emwEtfrq8lB9R+5kE!R?c7Dxa}SGZ|7W}DIETsb zVR8H#o8jN6ZQ0~|bAyi9l=MJsgzGUFzd?=MkJuO=Vnb~BgXzd{)R50Y)%Ov?Seq#JJlp#czCZ`MPt@rdD{`^Wh=4Ufh1AZ}!GZ8hE(@+iGf@;_<>uKvvY)iiT ztJ$9IQ6n-2E8|Yo;=cMTZDk6zT@) zs44svwTScFHB(p8+5n#-{{^IoGx9FuuNKAqVTNWAmLa_c!|^z3s0!UPyQDOhCmn_w z`fm6v_Q!I#6jlB(YAt+>`7!&SJcJn8;#in;B*Uxg$M}ha5SfI9@GY!`o3R64#lcwh zzIhui!skhUjanNuADFjZGt`ZjU>L4PP3`Ac8jG+8t6>AIj!{?x{R@a_XpdlPyp6hX z-M>vmhF>V|r^sv$XX2A04QxB)fP$FLy&fW`3w1siny8kxK{aFqCgF6{i2aQNxxPS<>-|O?oyqn760#XT<@~o; z3DYvW-ly7S)Y{0E#pRvhr&UCB!!6ht4`T=wKEmdt3uSYiAF&%&z!t%-!xnd9Q9XQ# zpGRYGG`@tA#t*P9>E5|rZ>ZK{ z6Vk`9C1%RwdQ;Q}2a--fJs0j{M;x5j^|om`wjg~K-@vlGtG7`9Zft@v1zg9kq1aH+ z_5P;2%lb3EM1GY*W@N^pdOjD^aTivlf_R2W`+qUND77onurz*-t?*A&gBlkx_0O;# zLv`p;5!dhCxG`UtTAd?NL$wyQD9>44zBZJuj;gRHY7wrteuQ;N|AbAk9KUGQ^GMW? zuSe~&J*WnrMorP3P`{bOqQzaOJw0lI=Q%OGgjrlKmoyg)$71BafqDXNvH2HJyWk$` z20^9FA}fk|uCzt1{zU6kRQbiIHMh=BM2lz_szsMk7iKAKZdembk#2_?nHbbIdJEN% zomd91;7I%jKgW?ibA!%hOnnikhRj4w)e5YF{$oT`!5vh~@|Sg;7)GK5t{^?7oJl`b z-fWvWPnn)B!(h&*p@x1xcE?{(^|h;Drsx$^Pv_!z>RpKqNtfZ*N2MV4UmqeobDSwv zT<e4XQpHrSc`p@UWdi7l=7Ed%_`(fBKuG1RFG<2Qkf_OqUa-Bn@ zvo&FDs9aOm`!u`U%&e9BSf7gXHaA1w8nw!YqDE>9YVqyHGWa<*<^IlnBAUyFEsO)O zDd`#b0)B*=ni4Hd&nseW()Cdl$D%4sv@S#~#$Bkj^9iclkLbhSts$-0|JuLxiKu0r zP(z!5nzM1J=fGT4gRY?#O}XdH;tWSE#u)2lRDCN^+inl)o$(23ja);$V~R4YZLxoA z#=ilPx5?1Ze}#Icw`cs-!~VE~pxX;trgZw_>ezQt44_^@le(IrJ%VLOUqMaL zKd3cPx|{30=ZB#l&1g

XRH>bf*c#xJpszW-lh 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\n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.0.6\n" +"X-Generator: Poedit 2.0.8\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "Language: uk\n" -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:39 +#: xs/src/slic3r/GUI/AboutDialog.cpp:32 +msgid "About Slic3r" +msgstr "" + +#: xs/src/slic3r/GUI/AboutDialog.cpp:67 +msgid "Version" +msgstr "" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:39 msgid "Shape" msgstr "Вигляд (Форма)" -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:46 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:46 msgid "Rectangular" msgstr "Прямокутний" -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:50 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1191 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:408 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:50 xs/src/slic3r/GUI/Tab.cpp:1826 +#: lib/Slic3r/GUI/Plater.pm:498 msgid "Size" msgstr "Розмір" -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:51 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:51 msgid "Size in X and Y of the rectangular plate." msgstr "Розмір прямокутної подложки за X та Y." -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:57 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:57 msgid "Origin" msgstr "Початок координат" -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:58 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:58 msgid "" "Distance of the 0,0 G-code coordinate from the front left corner of the " "rectangle." msgstr "Відстань координат 0,0 G-коду від нижнього лівого кута прямокутника." -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:62 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:62 msgid "Circular" msgstr "Круговий" -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:65 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:129 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:200 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:211 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:325 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:336 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:355 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:434 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:781 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:801 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:860 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:878 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:896 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1044 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1052 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1094 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1103 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1113 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1121 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1129 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1215 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1421 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1491 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1527 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1704 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1711 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1718 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1727 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1737 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1747 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:65 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:88 xs/src/slic3r/GUI/ConfigWizard.cpp:446 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:460 xs/src/slic3r/GUI/RammingChart.cpp:81 +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:79 +#: xs/src/libslic3r/PrintConfig.cpp:133 xs/src/libslic3r/PrintConfig.cpp:181 +#: xs/src/libslic3r/PrintConfig.cpp:189 xs/src/libslic3r/PrintConfig.cpp:237 +#: xs/src/libslic3r/PrintConfig.cpp:248 xs/src/libslic3r/PrintConfig.cpp:363 +#: xs/src/libslic3r/PrintConfig.cpp:374 xs/src/libslic3r/PrintConfig.cpp:393 +#: xs/src/libslic3r/PrintConfig.cpp:531 xs/src/libslic3r/PrintConfig.cpp:890 +#: xs/src/libslic3r/PrintConfig.cpp:1002 xs/src/libslic3r/PrintConfig.cpp:1010 +#: xs/src/libslic3r/PrintConfig.cpp:1068 xs/src/libslic3r/PrintConfig.cpp:1086 +#: xs/src/libslic3r/PrintConfig.cpp:1104 xs/src/libslic3r/PrintConfig.cpp:1166 +#: xs/src/libslic3r/PrintConfig.cpp:1176 xs/src/libslic3r/PrintConfig.cpp:1292 +#: xs/src/libslic3r/PrintConfig.cpp:1300 xs/src/libslic3r/PrintConfig.cpp:1342 +#: xs/src/libslic3r/PrintConfig.cpp:1351 xs/src/libslic3r/PrintConfig.cpp:1361 +#: xs/src/libslic3r/PrintConfig.cpp:1369 xs/src/libslic3r/PrintConfig.cpp:1377 +#: xs/src/libslic3r/PrintConfig.cpp:1463 xs/src/libslic3r/PrintConfig.cpp:1669 +#: xs/src/libslic3r/PrintConfig.cpp:1739 xs/src/libslic3r/PrintConfig.cpp:1773 +#: xs/src/libslic3r/PrintConfig.cpp:1969 xs/src/libslic3r/PrintConfig.cpp:1976 +#: xs/src/libslic3r/PrintConfig.cpp:1983 xs/src/libslic3r/PrintConfig.cpp:2015 +#: xs/src/libslic3r/PrintConfig.cpp:2025 xs/src/libslic3r/PrintConfig.cpp:2035 msgid "mm" msgstr "мм" -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:66 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:431 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:66 xs/src/libslic3r/PrintConfig.cpp:528 msgid "Diameter" msgstr "Діаметр" -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:67 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:67 msgid "" "Diameter of the print bed. It is assumed that origin (0,0) is located in the " "center." @@ -92,333 +89,830 @@ msgstr "" "Діаметр подложки. Передбачається, що початок координат (0,0) знаходиться в " "центрі." -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:71 -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:150 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:92 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:71 +#: xs/src/libslic3r/GCode/PreviewData.cpp:175 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:102 msgid "Custom" msgstr "Користувацький" -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:75 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:75 msgid "Load shape from STL..." msgstr "Завантажте форму з STL ..." -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:120 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:120 msgid "Settings" msgstr "Налаштування" -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:298 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:299 msgid "Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):" msgstr "Виберіть файл, щоб імпортувати форму полотна з (STL/OBJ/AMF/PRUSA):" -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:315 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:316 msgid "Error! " msgstr "Помилка! " -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:324 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:325 msgid "The selected file contains no geometry." msgstr "Обраний файл не містить геометрії." -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.cpp:328 +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:329 msgid "" "The selected file contains several disjoint areas. This is not supported." msgstr "Обраний файл містить декілька непересічних областей. Не підтримується." -#: c:\src\Slic3r\xs\src\slic3r\GUI\BedShapeDialog.hpp:42 +#: xs/src/slic3r/GUI/BedShapeDialog.hpp:44 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:409 msgid "Bed Shape" msgstr "Форма полотна" -#: c:\src\Slic3r\xs\src\slic3r\GUI\GUI.cpp:224 +#: xs/src/slic3r/GUI/BonjourDialog.cpp:53 +msgid "Network lookup" +msgstr "" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:66 +msgid "Address" +msgstr "" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:67 +msgid "Hostname" +msgstr "" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:68 +msgid "Service name" +msgstr "" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:69 +msgid "OctoPrint version" +msgstr "" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:187 +msgid "Searching for devices" +msgstr "" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:194 +msgid "Finished" +msgstr "" + +#: xs/src/slic3r/GUI/ButtonsDescription.cpp:13 +msgid "Buttons And Text Colors Description" +msgstr "" + +#: xs/src/slic3r/GUI/ButtonsDescription.cpp:38 +msgid "Value is the same as the system value" +msgstr "" + +#: xs/src/slic3r/GUI/ButtonsDescription.cpp:55 +msgid "" +"Value was changed and is not equal to the system value or the last saved " +"preset" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:15 +msgid "Upgrade" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:17 +msgid "Downgrade" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:19 +msgid "Before roll back" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:21 +msgid "User" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:24 +msgid "Unknown" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:36 +msgid "Active: " +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:42 +msgid "slic3r version" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:43 +msgid "print" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:44 +msgid "filaments" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:45 +msgid "printer" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:49 xs/src/slic3r/GUI/Tab.cpp:730 +msgid "vendor" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:49 +msgid "version" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:50 +msgid "min slic3r version" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:52 +msgid "max slic3r version" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:55 +msgid "model" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:55 +msgid "variants" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:67 +msgid "Incompatible with this Slic3r" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:70 +msgid "Activate" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:96 xs/src/slic3r/GUI/GUI.cpp:349 +msgid "Configuration Snapshots" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:88 +msgid "nozzle" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:89 +msgid "(default)" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:108 +msgid "Select all" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:109 +msgid "Select none" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:218 +#, c-format +msgid "Welcome to the Slic3r %s" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:218 +msgid "Welcome" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:224 xs/src/slic3r/GUI/GUI.cpp:346 +#, c-format +msgid "Run %s" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:226 +#, c-format +msgid "" +"Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial " +"configuration; just a few settings and you will be ready to print." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:230 +msgid "" +"Remove user profiles - install from scratch (a snapshot will be taken " +"beforehand)" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:252 +msgid "Other vendors" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:254 +msgid "Custom setup" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:278 +msgid "Automatic updates" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:278 +msgid "Updates" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:286 xs/src/slic3r/GUI/Preferences.cpp:59 +msgid "Check for application updates" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:289 xs/src/slic3r/GUI/Preferences.cpp:61 +msgid "" +"If enabled, Slic3r checks for new versions of Slic3r PE online. When a new " +"version becomes available a notification is displayed at the next " +"application startup (never during program usage). This is only a " +"notification mechanisms, no automatic installation is done." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:293 xs/src/slic3r/GUI/Preferences.cpp:67 +msgid "Update built-in Presets automatically" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:296 xs/src/slic3r/GUI/Preferences.cpp:69 +msgid "" +"If enabled, Slic3r downloads updates of built-in system presets in the " +"background. These updates are downloaded into a separate temporary location. " +"When a new preset version becomes available it is offered at application " +"startup." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:297 +msgid "" +"Updates are never applied without user's consent and never overwrite user's " +"customized settings." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:302 +msgid "" +"Additionally a backup snapshot of the whole configuration is created before " +"an update is applied." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:309 +msgid "Other Vendors" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:311 +msgid "Pick another vendor supported by Slic3r PE:" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:370 +msgid "Firmware Type" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:370 xs/src/slic3r/GUI/Tab.cpp:1606 +msgid "Firmware" +msgstr "Прошивка" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:374 +msgid "Choose the type of firmware used by your printer." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:409 +msgid "Bed Shape and Size" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:412 +msgid "Set the shape of your printer's bed." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:426 +msgid "Filament and Nozzle Diameters" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:426 +msgid "Print Diameters" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:442 +msgid "Enter the diameter of your printer's hot end nozzle." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:445 +msgid "Nozzle Diameter:" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:455 +msgid "Enter the diameter of your filament." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:456 +msgid "" +"Good precision is required, so use a caliper and do multiple measurements " +"along the filament, then compute the average." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:459 +msgid "Filament Diameter:" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:477 +msgid "Extruder and Bed Temperatures" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:477 +msgid "Temperatures" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:493 +msgid "Enter the temperature needed for extruding your filament." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:494 +msgid "A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:497 +msgid "Extrusion Temperature:" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:498 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:512 +msgid "°C" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:507 +msgid "" +"Enter the bed temperature needed for getting your filament to stick to your " +"heated bed." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:508 +msgid "" +"A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have " +"no heated bed." +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:511 +msgid "Bed Temperature:" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:824 +msgid "< &Back" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:825 +msgid "&Next >" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:826 +msgid "&Finish" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:896 +msgid "Configuration Wizard" +msgstr "" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:898 +msgid "Configuration Assistant" +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:87 +msgid "Flash!" +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:88 +msgid "Cancel" +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:128 +msgid "Flashing in progress. Please do not disconnect the printer!" +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:155 +msgid "Flashing succeeded!" +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:156 +msgid "Flashing failed. Please see the avrdude log below." +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:157 +msgid "Flashing cancelled." +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:294 +msgid "Cancelling..." +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:347 +msgid "Firmware flasher" +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:367 +msgid "Serial port:" +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:369 +msgid "Rescan" +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:374 +msgid "Firmware image:" +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:377 +msgid "Status:" +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:378 +msgid "Ready" +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:381 +msgid "Progress:" +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:400 +msgid "Advanced: avrdude output log" +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:446 +msgid "" +"Are you sure you want to cancel firmware flashing?\n" +"This could leave your printer in an unusable state!" +msgstr "" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:447 +msgid "Confirmation" +msgstr "" + +#: xs/src/slic3r/GUI/GLCanvas3D.cpp:2308 +msgid "Detected object outside print volume" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:233 msgid "Array of language names and identifiers should have the same size." msgstr "Масив імен мов та їх ідентифікаторів має бути однакового розміру." -#: c:\src\Slic3r\xs\src\slic3r\GUI\GUI.cpp:235 +#: xs/src/slic3r/GUI/GUI.cpp:244 msgid "Select the language" msgstr "Оберіть мову" -#: c:\src\Slic3r\xs\src\slic3r\GUI\GUI.cpp:235 +#: xs/src/slic3r/GUI/GUI.cpp:244 msgid "Language" msgstr "Мова" -#: c:\src\Slic3r\xs\src\slic3r\GUI\GUI.cpp:300 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:170 +#: xs/src/slic3r/GUI/GUI.cpp:306 xs/src/libslic3r/PrintConfig.cpp:195 msgid "Default" msgstr "За замовчуванням" -#: c:\src\Slic3r\xs\src\slic3r\GUI\GUI.cpp:325 +#: xs/src/slic3r/GUI/GUI.cpp:349 +msgid "Inspect / activate configuration snapshots" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:350 +msgid "Take Configuration Snapshot" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:350 +msgid "Capture a configuration snapshot" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:353 xs/src/slic3r/GUI/Preferences.cpp:9 +msgid "Preferences" +msgstr "Налаштування" + +#: xs/src/slic3r/GUI/GUI.cpp:353 +msgid "Application preferences" +msgstr "Преференції застосування" + +#: xs/src/slic3r/GUI/GUI.cpp:354 msgid "Change Application Language" msgstr "Змінити мову застосування" -#: c:\src\Slic3r\xs\src\slic3r\GUI\GUI.cpp:332 +#: xs/src/slic3r/GUI/GUI.cpp:356 +msgid "Flash printer firmware" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:356 +msgid "Upload a firmware image into an Arduino based printer" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:368 +msgid "Taking configuration snapshot" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:368 +msgid "Snapshot name" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:406 msgid "Application will be restarted" msgstr "Застосування буде перезапущене" -#: c:\src\Slic3r\xs\src\slic3r\GUI\GUI.cpp:332 +#: xs/src/slic3r/GUI/GUI.cpp:406 msgid "Attention!" msgstr "Увага!" -#: c:\src\Slic3r\xs\src\slic3r\GUI\GUI.cpp:339 -msgid "&Localization" -msgstr "Локалізація" +#: xs/src/slic3r/GUI/GUI.cpp:422 +msgid "&Configuration" +msgstr "" -#: c:\src\Slic3r\xs\src\slic3r\GUI\GUI.cpp:488 -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:470 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1300 -msgid "Error" -msgstr "Помилка" +#: xs/src/slic3r/GUI/GUI.cpp:446 +msgid "You have unsaved changes " +msgstr "У вас є незбережені зміни " -#: c:\src\Slic3r\xs\src\slic3r\GUI\GUI.cpp:493 +#: xs/src/slic3r/GUI/GUI.cpp:446 +msgid ". Discard changes and continue anyway?" +msgstr ". Відхилити зміни і продовжити в будь-якому випадку?" + +#: xs/src/slic3r/GUI/GUI.cpp:447 +msgid "Unsaved Presets" +msgstr "Незбереженні налаштування" + +#: xs/src/slic3r/GUI/GUI.cpp:655 msgid "Notice" msgstr "Зауваження" -#: c:\src\Slic3r\xs\src\slic3r\GUI\GUI.cpp:498 -msgid "GLUquadricObjPtr | Attempt to free unreferenced scalar" +#: xs/src/slic3r/GUI/GUI.cpp:660 +msgid "Attempt to free unreferenced scalar" msgstr "" -#: c:\src\Slic3r\xs\src\slic3r\GUI\GUI.cpp:500 +#: xs/src/slic3r/GUI/GUI.cpp:662 xs/src/slic3r/GUI/WipeTowerDialog.cpp:39 +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:321 msgid "Warning" msgstr "Застереження" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:50 -msgid "Save current " -msgstr "Зберегти поточний " +#: xs/src/slic3r/GUI/GUI.cpp:859 +msgid "Support" +msgstr "" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:51 -msgid "Delete this preset" -msgstr "Видалити це налаштування" +#: xs/src/slic3r/GUI/GUI.cpp:862 +msgid "Select what kind of support do you need" +msgstr "" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:313 -msgid "Layers and perimeters" -msgstr "Шари та периметри" +#: xs/src/slic3r/GUI/GUI.cpp:863 xs/src/libslic3r/GCode/PreviewData.cpp:162 +msgid "None" +msgstr "Жодне" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:314 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:777 -msgid "Layer height" -msgstr "Висота шару" +#: xs/src/slic3r/GUI/GUI.cpp:864 xs/src/libslic3r/PrintConfig.cpp:1656 +msgid "Support on build plate only" +msgstr "Підтримка тільки на збірній пластині" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:318 -msgid "Vertical shells" -msgstr "Вертикальні оболонки" +#: xs/src/slic3r/GUI/GUI.cpp:865 +msgid "Everywhere" +msgstr "" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:329 -msgid "Horizontal shells" -msgstr "Горизонтальні оболонки" - -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:330 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1314 -msgid "Solid layers" -msgstr "Суцільні шари" - -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:335 -msgid "Quality (slower slicing)" -msgstr "Якість (повільне нарізання)" - -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:342 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:356 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:449 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:452 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:831 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1113 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:107 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:208 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:736 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1733 -msgid "Advanced" -msgstr "Розширений" - -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:346 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:347 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:664 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:87 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:247 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:488 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:502 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:540 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:681 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:691 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:709 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:727 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:746 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1263 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1280 -msgid "Infill" -msgstr "Заповнення" - -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:352 -msgid "Reducing printing time" -msgstr "Зниження часу друку" - -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:364 -msgid "Skirt and brim" -msgstr "Плінтус та край" - -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:365 -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:146 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:88 -msgid "Skirt" -msgstr "Плінтус" - -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:371 +#: xs/src/slic3r/GUI/GUI.cpp:877 xs/src/slic3r/GUI/Tab.cpp:844 msgid "Brim" msgstr "Край" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:374 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:375 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:191 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1030 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1380 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1387 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1399 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1409 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1417 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1432 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1453 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1464 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1480 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1489 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1498 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1509 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1525 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1533 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1534 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1543 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1551 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1565 -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:147 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:89 +#: xs/src/slic3r/GUI/GUI.cpp:879 +msgid "" +"This flag enables the brim that will be printed around each object on the " +"first layer." +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:888 +msgid "Purging volumes" +msgstr "" + +#: xs/src/slic3r/GUI/GUI.cpp:930 +msgid "Export print config" +msgstr "" + +#: xs/src/slic3r/GUI/MsgDialog.cpp:64 +msgid "Slic3r error" +msgstr "" + +#: xs/src/slic3r/GUI/MsgDialog.cpp:64 +msgid "Slic3r has encountered an error" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:84 +msgid "Save current " +msgstr "Зберегти поточний " + +#: xs/src/slic3r/GUI/Tab.cpp:85 +msgid "Delete this preset" +msgstr "Видалити це налаштування" + +#: xs/src/slic3r/GUI/Tab.cpp:97 +msgid "" +"Hover the cursor over buttons to find more information \n" +"or click this button." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:716 +msgid "It's a default preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:717 +msgid "It's a system preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:718 +msgid "Current preset is inherited from " +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:723 +msgid "It can't be deleted or modified. " +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:724 +msgid "" +"Any modifications should be saved as a new preset inherited from this one. " +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:725 +msgid "To do that please specify a new name for the preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:729 +msgid "Additional information:" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:737 +msgid "printer model" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:739 +msgid "default print profile" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:742 +msgid "default filament profile" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:786 +msgid "Layers and perimeters" +msgstr "Шари та периметри" + +#: xs/src/slic3r/GUI/Tab.cpp:787 xs/src/libslic3r/PrintConfig.cpp:886 +msgid "Layer height" +msgstr "Висота шару" + +#: xs/src/slic3r/GUI/Tab.cpp:791 +msgid "Vertical shells" +msgstr "Вертикальні оболонки" + +#: xs/src/slic3r/GUI/Tab.cpp:802 +msgid "Horizontal shells" +msgstr "Горизонтальні оболонки" + +#: xs/src/slic3r/GUI/Tab.cpp:803 xs/src/libslic3r/PrintConfig.cpp:1562 +msgid "Solid layers" +msgstr "Суцільні шари" + +#: xs/src/slic3r/GUI/Tab.cpp:808 +msgid "Quality (slower slicing)" +msgstr "Якість (повільне нарізання)" + +#: xs/src/slic3r/GUI/Tab.cpp:815 xs/src/slic3r/GUI/Tab.cpp:829 +#: xs/src/slic3r/GUI/Tab.cpp:923 xs/src/slic3r/GUI/Tab.cpp:926 +#: xs/src/slic3r/GUI/Tab.cpp:1276 xs/src/slic3r/GUI/Tab.cpp:1625 +#: xs/src/libslic3r/PrintConfig.cpp:110 xs/src/libslic3r/PrintConfig.cpp:245 +#: xs/src/libslic3r/PrintConfig.cpp:833 xs/src/libslic3r/PrintConfig.cpp:2021 +msgid "Advanced" +msgstr "Розширений" + +#: xs/src/slic3r/GUI/Tab.cpp:819 xs/src/slic3r/GUI/Tab.cpp:820 +#: xs/src/slic3r/GUI/Tab.cpp:1127 xs/src/libslic3r/PrintConfig.cpp:90 +#: xs/src/libslic3r/PrintConfig.cpp:284 xs/src/libslic3r/PrintConfig.cpp:585 +#: xs/src/libslic3r/PrintConfig.cpp:599 xs/src/libslic3r/PrintConfig.cpp:637 +#: xs/src/libslic3r/PrintConfig.cpp:778 xs/src/libslic3r/PrintConfig.cpp:788 +#: xs/src/libslic3r/PrintConfig.cpp:806 xs/src/libslic3r/PrintConfig.cpp:824 +#: xs/src/libslic3r/PrintConfig.cpp:843 xs/src/libslic3r/PrintConfig.cpp:1511 +#: xs/src/libslic3r/PrintConfig.cpp:1528 +msgid "Infill" +msgstr "Заповнення" + +#: xs/src/slic3r/GUI/Tab.cpp:825 +msgid "Reducing printing time" +msgstr "Зниження часу друку" + +#: xs/src/slic3r/GUI/Tab.cpp:837 +msgid "Skirt and brim" +msgstr "Плінтус та край" + +#: xs/src/slic3r/GUI/Tab.cpp:838 xs/src/libslic3r/GCode/PreviewData.cpp:171 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:98 +msgid "Skirt" +msgstr "Плінтус" + +#: xs/src/slic3r/GUI/Tab.cpp:847 xs/src/slic3r/GUI/Tab.cpp:848 +#: xs/src/libslic3r/PrintConfig.cpp:228 xs/src/libslic3r/PrintConfig.cpp:1278 +#: xs/src/libslic3r/PrintConfig.cpp:1628 xs/src/libslic3r/PrintConfig.cpp:1635 +#: xs/src/libslic3r/PrintConfig.cpp:1647 xs/src/libslic3r/PrintConfig.cpp:1657 +#: xs/src/libslic3r/PrintConfig.cpp:1665 xs/src/libslic3r/PrintConfig.cpp:1680 +#: xs/src/libslic3r/PrintConfig.cpp:1701 xs/src/libslic3r/PrintConfig.cpp:1712 +#: xs/src/libslic3r/PrintConfig.cpp:1728 xs/src/libslic3r/PrintConfig.cpp:1737 +#: xs/src/libslic3r/PrintConfig.cpp:1746 xs/src/libslic3r/PrintConfig.cpp:1757 +#: xs/src/libslic3r/PrintConfig.cpp:1771 xs/src/libslic3r/PrintConfig.cpp:1779 +#: xs/src/libslic3r/PrintConfig.cpp:1780 xs/src/libslic3r/PrintConfig.cpp:1789 +#: xs/src/libslic3r/PrintConfig.cpp:1797 xs/src/libslic3r/PrintConfig.cpp:1811 +#: xs/src/libslic3r/GCode/PreviewData.cpp:172 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:99 msgid "Support material" msgstr "Підтримка" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:380 +#: xs/src/slic3r/GUI/Tab.cpp:853 msgid "Raft" msgstr "Пліт" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:384 +#: xs/src/slic3r/GUI/Tab.cpp:857 msgid "Options for support material and raft" msgstr "Варіанти для опорного матеріалу та плоту" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:398 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:118 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:278 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:635 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:747 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:979 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1201 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1251 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1302 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1625 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:71 +#: xs/src/slic3r/GUI/Tab.cpp:871 xs/src/libslic3r/PrintConfig.cpp:122 +#: xs/src/libslic3r/PrintConfig.cpp:315 xs/src/libslic3r/PrintConfig.cpp:732 +#: xs/src/libslic3r/PrintConfig.cpp:844 xs/src/libslic3r/PrintConfig.cpp:1212 +#: xs/src/libslic3r/PrintConfig.cpp:1449 xs/src/libslic3r/PrintConfig.cpp:1499 +#: xs/src/libslic3r/PrintConfig.cpp:1550 xs/src/libslic3r/PrintConfig.cpp:1871 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:77 msgid "Speed" msgstr "Швидкість" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:399 +#: xs/src/slic3r/GUI/Tab.cpp:872 msgid "Speed for print moves" msgstr "Швидкість друкарських рухів" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:411 +#: xs/src/slic3r/GUI/Tab.cpp:884 msgid "Speed for non-print moves" msgstr "Швидкість недрукарських рухів" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:414 +#: xs/src/slic3r/GUI/Tab.cpp:887 msgid "Modifiers" msgstr "Модифікатори" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:417 +#: xs/src/slic3r/GUI/Tab.cpp:890 msgid "Acceleration control (advanced)" msgstr "Контроль прискорення (розширений)" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:424 +#: xs/src/slic3r/GUI/Tab.cpp:897 msgid "Autospeed (advanced)" msgstr "Автоматична швидкість (розширена)" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:430 +#: xs/src/slic3r/GUI/Tab.cpp:903 msgid "Multiple Extruders" msgstr "Кілька екструдерів" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:431 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:966 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:308 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:702 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:958 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1272 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1445 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1471 +#: xs/src/slic3r/GUI/Tab.cpp:904 xs/src/slic3r/GUI/Tab.cpp:1451 +#: xs/src/libslic3r/PrintConfig.cpp:345 xs/src/libslic3r/PrintConfig.cpp:799 +#: xs/src/libslic3r/PrintConfig.cpp:1191 xs/src/libslic3r/PrintConfig.cpp:1520 +#: xs/src/libslic3r/PrintConfig.cpp:1693 xs/src/libslic3r/PrintConfig.cpp:1719 +#: xs/src/libslic3r/PrintConfig.cpp:1995 xs/src/libslic3r/PrintConfig.cpp:2004 msgid "Extruders" msgstr "Екструдери" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:438 +#: xs/src/slic3r/GUI/Tab.cpp:911 msgid "Ooze prevention" msgstr "Профілактика просочування" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:442 -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:149 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:91 +#: xs/src/slic3r/GUI/Tab.cpp:915 xs/src/libslic3r/GCode/PreviewData.cpp:174 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:101 msgid "Wipe tower" msgstr "Вежа вичищування" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:453 +#: xs/src/slic3r/GUI/Tab.cpp:927 msgid "Extrusion width" msgstr "Ширина екструзії" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:463 +#: xs/src/slic3r/GUI/Tab.cpp:937 msgid "Overlap" msgstr "Перекриття" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:466 +#: xs/src/slic3r/GUI/Tab.cpp:940 msgid "Flow" msgstr "Потік" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:469 +#: xs/src/slic3r/GUI/Tab.cpp:943 msgid "Other" msgstr "Інше" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:476 +#: xs/src/slic3r/GUI/Tab.cpp:950 msgid "Output options" msgstr "Параметри виводу" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:477 +#: xs/src/slic3r/GUI/Tab.cpp:951 msgid "Sequential printing" msgstr "Послідовне друкування" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:479 +#: xs/src/slic3r/GUI/Tab.cpp:953 msgid "Extruder clearance (mm)" msgstr "Розмір екструдера (мм)" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:488 +#: xs/src/slic3r/GUI/Tab.cpp:962 msgid "Output file" msgstr "Вихідний файл" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:494 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1001 +#: xs/src/slic3r/GUI/Tab.cpp:968 xs/src/libslic3r/PrintConfig.cpp:1234 msgid "Post-processing scripts" msgstr "Скрипти пост-обробки" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:500 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:501 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:859 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:860 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1156 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1157 +#: xs/src/slic3r/GUI/Tab.cpp:974 xs/src/slic3r/GUI/Tab.cpp:975 +#: xs/src/slic3r/GUI/Tab.cpp:1329 xs/src/slic3r/GUI/Tab.cpp:1330 +#: xs/src/slic3r/GUI/Tab.cpp:1668 xs/src/slic3r/GUI/Tab.cpp:1669 msgid "Notes" msgstr "Примітки" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:507 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:867 +#: xs/src/slic3r/GUI/Tab.cpp:981 xs/src/slic3r/GUI/Tab.cpp:1337 +#: xs/src/slic3r/GUI/Tab.cpp:1675 msgid "Dependencies" msgstr "Залежності" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:508 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:868 +#: xs/src/slic3r/GUI/Tab.cpp:982 xs/src/slic3r/GUI/Tab.cpp:1338 +#: xs/src/slic3r/GUI/Tab.cpp:1676 msgid "Profile dependencies" msgstr "Залежності профілю" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:509 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:869 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1668 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:143 +#: xs/src/slic3r/GUI/Tab.cpp:983 xs/src/slic3r/GUI/Tab.cpp:1339 +#: xs/src/slic3r/GUI/Tab.cpp:2364 xs/src/libslic3r/PrintConfig.cpp:147 msgid "Compatible printers" msgstr "Сумісні принтери" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:533 +#: xs/src/slic3r/GUI/Tab.cpp:1016 #, no-c-format msgid "" "The Spiral Vase mode requires:\n" @@ -439,31 +933,11 @@ msgstr "" "\n" "Чи потрібно змінити ці налаштування, щоб увімкнути режим Спіральної вази?" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:540 +#: xs/src/slic3r/GUI/Tab.cpp:1023 msgid "Spiral Vase" msgstr "Спіральна ваза" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:560 -msgid "" -"The Wipe Tower currently supports only:\n" -"- first layer height 0.2mm\n" -"- layer height from 0.15mm to 0.35mm\n" -"\n" -"Shall I adjust those settings in order to enable the Wipe Tower?" -msgstr "" -"Вичіщуюча веж в даний час підтримує тільки:\n" -"- висота першого шару 0,2 мм\n" -"- висота шару від 0,15 мм до 0,35 мм\n" -"\n" -"Чи потрібно коригувати ці налаштування, щоб увімкнути вичіщуючу веж?" - -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:564 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:585 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:602 -msgid "Wipe Tower" -msgstr "Вичіщуюча веж" - -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:581 +#: xs/src/slic3r/GUI/Tab.cpp:1044 msgid "" "The Wipe Tower currently supports the non-soluble supports only\n" "if they are printed with the current extruder without triggering a tool " @@ -481,7 +955,11 @@ msgstr "" "\n" "Чи потрібно коригувати ці налаштування, щоб увімкнути вичіщуючу веж?" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:599 +#: xs/src/slic3r/GUI/Tab.cpp:1048 xs/src/slic3r/GUI/Tab.cpp:1065 +msgid "Wipe Tower" +msgstr "Вичіщуюча веж" + +#: xs/src/slic3r/GUI/Tab.cpp:1062 msgid "" "For the Wipe Tower to work with the soluble supports, the support layers\n" "need to be synchronized with the object layers.\n" @@ -494,7 +972,7 @@ msgstr "" "\n" "Чи потрібно синхронізувати шари підтримки, щоб увімкнути вичіщуючу веж?" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:617 +#: xs/src/slic3r/GUI/Tab.cpp:1080 msgid "" "Supports work better, if the following feature is enabled:\n" "- Detect bridging perimeters\n" @@ -506,15 +984,15 @@ msgstr "" "\n" "Чи потрібно змінити ці налаштування для підтримки?" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:620 +#: xs/src/slic3r/GUI/Tab.cpp:1083 msgid "Support Generator" msgstr "Створення підтримки" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:662 +#: xs/src/slic3r/GUI/Tab.cpp:1125 msgid "The " msgstr "Шаблон наповнення " -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:662 +#: xs/src/slic3r/GUI/Tab.cpp:1125 #, no-c-format msgid "" " infill pattern is not supposed to work at 100% density.\n" @@ -525,186 +1003,251 @@ msgstr "" "\n" "Чи потрібно змінити його на Rectilinear шаблон заповнення?" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:786 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:787 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:368 +#: xs/src/slic3r/GUI/Tab.cpp:1231 xs/src/slic3r/GUI/Tab.cpp:1232 +#: lib/Slic3r/GUI/Plater.pm:454 msgid "Filament" msgstr "Філамент" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:794 +#: xs/src/slic3r/GUI/Tab.cpp:1239 msgid "Temperature " msgstr "Температура " -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:795 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1234 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:307 +#: xs/src/slic3r/GUI/Tab.cpp:1240 xs/src/libslic3r/PrintConfig.cpp:344 msgid "Extruder" msgstr "Екструдер" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:800 +#: xs/src/slic3r/GUI/Tab.cpp:1245 msgid "Bed" msgstr "Полотно" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:805 +#: xs/src/slic3r/GUI/Tab.cpp:1250 msgid "Cooling" msgstr "Охолодження" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:806 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:922 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1695 +#: xs/src/slic3r/GUI/Tab.cpp:1251 xs/src/libslic3r/PrintConfig.cpp:1137 +#: xs/src/libslic3r/PrintConfig.cpp:1941 msgid "Enable" msgstr "Увімкнути" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:817 +#: xs/src/slic3r/GUI/Tab.cpp:1262 msgid "Fan settings" msgstr "Налаштування вентилятора" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:818 +#: xs/src/slic3r/GUI/Tab.cpp:1263 msgid "Fan speed" msgstr "Швидкість вентилятора" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:826 +#: xs/src/slic3r/GUI/Tab.cpp:1271 msgid "Cooling thresholds" msgstr "Пороги охолодження" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:832 +#: xs/src/slic3r/GUI/Tab.cpp:1277 msgid "Filament properties" msgstr "Властивості філаменту" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:836 +#: xs/src/slic3r/GUI/Tab.cpp:1281 msgid "Print speed override" msgstr "Перевизначення швидкості друку" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:846 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1119 +#: xs/src/slic3r/GUI/Tab.cpp:1291 +msgid "Toolchange parameters with single extruder MM printers" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1299 +msgid "Ramming" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1301 +msgid "Ramming settings" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1316 xs/src/slic3r/GUI/Tab.cpp:1631 msgid "Custom G-code" msgstr "Користувацький G-код" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:847 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1120 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1342 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1357 +#: xs/src/slic3r/GUI/Tab.cpp:1317 xs/src/slic3r/GUI/Tab.cpp:1632 +#: xs/src/libslic3r/PrintConfig.cpp:1590 xs/src/libslic3r/PrintConfig.cpp:1605 msgid "Start G-code" msgstr "Початок G-коду" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:853 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1126 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:217 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:227 +#: xs/src/slic3r/GUI/Tab.cpp:1323 xs/src/slic3r/GUI/Tab.cpp:1638 +#: xs/src/libslic3r/PrintConfig.cpp:254 xs/src/libslic3r/PrintConfig.cpp:264 msgid "End G-code" msgstr "Закінчення G-коду" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:937 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:11 +#: xs/src/slic3r/GUI/Tab.cpp:1419 xs/src/slic3r/GUI/Preferences.cpp:17 msgid "General" msgstr "Загальне" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:938 +#: xs/src/slic3r/GUI/Tab.cpp:1420 msgid "Size and coordinates" msgstr "Розмір і координати" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:940 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:34 +#: xs/src/slic3r/GUI/Tab.cpp:1422 xs/src/libslic3r/PrintConfig.cpp:37 msgid "Bed shape" msgstr "Форма полотна" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:942 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1637 +#: xs/src/slic3r/GUI/Tab.cpp:1424 xs/src/slic3r/GUI/Tab.cpp:2332 msgid " Set " msgstr " Встановити " -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:962 +#: xs/src/slic3r/GUI/Tab.cpp:1447 msgid "Capabilities" msgstr "Можливості" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:967 +#: xs/src/slic3r/GUI/Tab.cpp:1452 msgid "Number of extruders of the printer." msgstr "Кількість екструдерів у принтері." -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:989 +#: xs/src/slic3r/GUI/Tab.cpp:1477 msgid "USB/Serial connection" msgstr "USB/послідовне з'єднання" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:990 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1193 +#: xs/src/slic3r/GUI/Tab.cpp:1478 xs/src/libslic3r/PrintConfig.cpp:1441 msgid "Serial port" msgstr "Послідовний порт" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:995 +#: xs/src/slic3r/GUI/Tab.cpp:1483 msgid "Rescan serial ports" msgstr "Сканувати ще раз послідовні порти" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1004 -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1072 +#: xs/src/slic3r/GUI/Tab.cpp:1492 xs/src/slic3r/GUI/Tab.cpp:1539 msgid "Test" msgstr "Перевірити" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1017 +#: xs/src/slic3r/GUI/Tab.cpp:1505 msgid "Connection to printer works correctly." msgstr "Підключення до принтера працює коректно." -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1017 +#: xs/src/slic3r/GUI/Tab.cpp:1505 xs/src/slic3r/GUI/Tab.cpp:1549 msgid "Success!" msgstr "Успіх!" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1020 +#: xs/src/slic3r/GUI/Tab.cpp:1508 msgid "Connection failed." msgstr "Підключення не вдалося." -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1032 +#: xs/src/slic3r/GUI/Tab.cpp:1520 xs/src/slic3r/Utils/OctoPrint.cpp:110 msgid "OctoPrint upload" msgstr "Завантаження OctoPrint" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1035 +#: xs/src/slic3r/GUI/Tab.cpp:1523 xs/src/slic3r/GUI/Tab.cpp:1572 msgid " Browse " msgstr " Переглянути " -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1110 -msgid "Firmware" -msgstr "Прошивка" +#: xs/src/slic3r/GUI/Tab.cpp:1549 +msgid "Connection to OctoPrint works correctly." +msgstr "Підключення до OctoPrint працює правильно." -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1132 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:48 +#: xs/src/slic3r/GUI/Tab.cpp:1552 +msgid "Could not connect to OctoPrint" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1552 +msgid "Note: OctoPrint version at least 1.1.0 is required." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1578 +msgid "Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1579 +msgid "Open CA certificate file" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1593 +msgid "" +"HTTPS CA file is optional. It is only needed if you use HTTPS with a self-" +"signed certificate." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1644 xs/src/libslic3r/PrintConfig.cpp:51 msgid "Before layer change G-code" msgstr "G-код перед зміною шару" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1138 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:766 +#: xs/src/slic3r/GUI/Tab.cpp:1650 xs/src/libslic3r/PrintConfig.cpp:875 msgid "After layer change G-code" msgstr "G-код після зміни шару" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1144 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1602 +#: xs/src/slic3r/GUI/Tab.cpp:1656 xs/src/libslic3r/PrintConfig.cpp:1848 msgid "Tool change G-code" msgstr "G-код зміни інструменту" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1150 +#: xs/src/slic3r/GUI/Tab.cpp:1662 msgid "Between objects G-code (for sequential printing)" msgstr "G-код між об'єктами (для послідовного друку)" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1187 -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:400 +#: xs/src/slic3r/GUI/Tab.cpp:1717 xs/src/slic3r/GUI/Tab.cpp:1778 +#: xs/src/slic3r/GUI/Tab.cpp:2037 xs/src/libslic3r/PrintConfig.cpp:920 +#: xs/src/libslic3r/PrintConfig.cpp:929 xs/src/libslic3r/PrintConfig.cpp:938 +#: xs/src/libslic3r/PrintConfig.cpp:950 xs/src/libslic3r/PrintConfig.cpp:960 +#: xs/src/libslic3r/PrintConfig.cpp:970 xs/src/libslic3r/PrintConfig.cpp:980 +msgid "Machine limits" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1730 +msgid "Values in this column are for Full Power mode" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1731 +msgid "Full Power" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1736 +msgid "Values in this column are for Silent mode" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1737 +msgid "Silent" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1745 +msgid "Maximum feedrates" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1750 +msgid "Maximum accelerations" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1757 +msgid "Jerk limits" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1762 +msgid "Minimum feedrates" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1800 xs/src/slic3r/GUI/Tab.cpp:1808 +#: xs/src/slic3r/GUI/Tab.cpp:2037 +msgid "Single extruder MM setup" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1809 +msgid "Single extruder multimaterial parameters" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:1822 xs/src/libslic3r/GCode/PreviewData.cpp:446 #, c-format msgid "Extruder %d" msgstr "Екструдер %d" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1194 +#: xs/src/slic3r/GUI/Tab.cpp:1829 msgid "Layer height limits" msgstr "Межі висоти шару" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1199 +#: xs/src/slic3r/GUI/Tab.cpp:1834 msgid "Position (for multi-extruder printers)" msgstr "Позиція (для мульти-екструдерних принтерів)" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1202 +#: xs/src/slic3r/GUI/Tab.cpp:1837 msgid "Retraction" msgstr "Переривання" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1205 +#: xs/src/slic3r/GUI/Tab.cpp:1840 msgid "Only lift Z" msgstr "Межі підняття Z" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1218 +#: xs/src/slic3r/GUI/Tab.cpp:1853 msgid "" "Retraction when tool is disabled (advanced settings for multi-extruder " "setups)" @@ -712,13 +1255,12 @@ msgstr "" "Переривання при відключенні інструмента (додаткові налаштування для " "налагодження мульти-екструдерів)" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1222 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:150 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2125 +#: xs/src/slic3r/GUI/Tab.cpp:1857 lib/Slic3r/GUI/Plater.pm:217 +#: lib/Slic3r/GUI/Plater.pm:2324 msgid "Preview" msgstr "Попередній перегляд" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1312 +#: xs/src/slic3r/GUI/Tab.cpp:1953 msgid "" "The Wipe option is not available when using the Firmware Retraction mode.\n" "\n" @@ -729,23 +1271,23 @@ msgstr "" "\n" "Відключити його для увімкнення програмного переривання?" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1314 +#: xs/src/slic3r/GUI/Tab.cpp:1955 msgid "Firmware Retraction" msgstr "Програмне переривання" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1469 +#: xs/src/slic3r/GUI/Tab.cpp:2130 msgid "Default " msgstr "За замовчуванням " -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1469 +#: xs/src/slic3r/GUI/Tab.cpp:2130 msgid " preset" msgstr " налаштування" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1470 +#: xs/src/slic3r/GUI/Tab.cpp:2131 msgid " preset\n" msgstr " налаштування\n" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1488 +#: xs/src/slic3r/GUI/Tab.cpp:2149 msgid "" "\n" "\n" @@ -755,7 +1297,7 @@ msgstr "" "\n" "є не сумісним з принтером\n" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1488 +#: xs/src/slic3r/GUI/Tab.cpp:2149 msgid "" "\n" "\n" @@ -765,7 +1307,7 @@ msgstr "" "\n" "і має такі незбережені зміни:" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1489 +#: xs/src/slic3r/GUI/Tab.cpp:2150 msgid "" "\n" "\n" @@ -775,7 +1317,7 @@ msgstr "" "\n" "має такі незбережені зміни:" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1491 +#: xs/src/slic3r/GUI/Tab.cpp:2152 msgid "" "\n" "\n" @@ -785,93 +1327,219 @@ msgstr "" "\n" "Відхилити зміни і продовжувати в будь-якому випадку?" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1492 +#: xs/src/slic3r/GUI/Tab.cpp:2153 msgid "Unsaved Changes" msgstr "Незбережені зміни" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1560 +#: xs/src/slic3r/GUI/Tab.cpp:2240 msgid "The supplied name is empty. It can't be saved." msgstr "Надане ім'я порожнє. Не вдається зберегти." -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1580 +#: xs/src/slic3r/GUI/Tab.cpp:2245 +msgid "Cannot overwrite a system profile." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2249 +msgid "Cannot overwrite an external profile." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2275 msgid "remove" msgstr "перемістити" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1580 +#: xs/src/slic3r/GUI/Tab.cpp:2275 msgid "delete" msgstr "видалити" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1581 +#: xs/src/slic3r/GUI/Tab.cpp:2276 msgid "Are you sure you want to " msgstr "Ви впевнені, що хочете " -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1581 +#: xs/src/slic3r/GUI/Tab.cpp:2276 msgid " the selected preset?" msgstr " вибране налаштування?" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1582 +#: xs/src/slic3r/GUI/Tab.cpp:2277 msgid "Remove" msgstr "Перемістити" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1582 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:178 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:196 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2021 +#: xs/src/slic3r/GUI/Tab.cpp:2277 lib/Slic3r/GUI/Plater.pm:251 +#: lib/Slic3r/GUI/Plater.pm:269 lib/Slic3r/GUI/Plater.pm:2215 msgid "Delete" msgstr "Видалити" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1583 +#: xs/src/slic3r/GUI/Tab.cpp:2278 msgid " Preset" msgstr " Налаштування" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1636 +#: xs/src/slic3r/GUI/Tab.cpp:2331 msgid "All" msgstr "Всі" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1667 +#: xs/src/slic3r/GUI/Tab.cpp:2363 msgid "Select the printers this profile is compatible with." msgstr "Оберіть принтери, сумісні з цим профілем." -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1751 -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:514 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1645 +#: xs/src/slic3r/GUI/Tab.cpp:2409 xs/src/slic3r/GUI/Tab.cpp:2495 +#: xs/src/slic3r/GUI/Preset.cpp:702 xs/src/slic3r/GUI/Preset.cpp:742 +#: xs/src/slic3r/GUI/Preset.cpp:770 xs/src/slic3r/GUI/Preset.cpp:802 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1193 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1246 lib/Slic3r/GUI/Plater.pm:603 +msgid "System presets" +msgstr "Системні налаштування" + +#: xs/src/slic3r/GUI/Tab.cpp:2410 xs/src/slic3r/GUI/Tab.cpp:2496 +msgid "Default presets" +msgstr "Налаштування за замовчанням" + +#: xs/src/slic3r/GUI/Tab.cpp:2565 +msgid "" +"LOCKED LOCK;indicates that the settings are the same as the system values " +"for the current option group" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2568 +msgid "" +"UNLOCKED LOCK;indicates that some settings were changed and are not equal to " +"the system values for the current option group.\n" +"Click the UNLOCKED LOCK icon to reset all settings for current option group " +"to the system values." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2574 +msgid "" +"WHITE BULLET;for the left button: \tindicates a non-system preset,\n" +"for the right button: \tindicates that the settings hasn't been modified." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2578 +msgid "" +"BACK ARROW;indicates that the settings were changed and are not equal to the " +"last saved preset for the current option group.\n" +"Click the BACK ARROW icon to reset all settings for the current option group " +"to the last saved preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2604 +msgid "" +"LOCKED LOCK icon indicates that the settings are the same as the system " +"values for the current option group" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2606 +msgid "" +"UNLOCKED LOCK icon indicates that some settings were changed and are not " +"equal to the system values for the current option group.\n" +"Click to reset all settings for current option group to the system values." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2609 +msgid "WHITE BULLET icon indicates a non system preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2612 +msgid "" +"WHITE BULLET icon indicates that the settings are the same as in the last " +"saved preset for the current option group." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2614 +msgid "" +"BACK ARROW icon indicates that the settings were changed and are not equal " +"to the last saved preset for the current option group.\n" +"Click to reset all settings for the current option group to the last saved " +"preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2620 +msgid "" +"LOCKED LOCK icon indicates that the value is the same as the system value." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2621 +msgid "" +"UNLOCKED LOCK icon indicates that the value was changed and is not equal to " +"the system value.\n" +"Click to reset current value to the system value." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2627 +msgid "" +"WHITE BULLET icon indicates that the value is the same as in the last saved " +"preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2628 +msgid "" +"BACK ARROW icon indicates that the value was changed and is not equal to the " +"last saved preset.\n" +"Click to reset current value to the last saved preset." +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2703 lib/Slic3r/GUI/MainFrame.pm:469 +#: lib/Slic3r/GUI/Plater.pm:1795 msgid "Save " msgstr "Зберегти " -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1751 +#: xs/src/slic3r/GUI/Tab.cpp:2703 msgid " as:" msgstr " як:" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1785 -msgid "" -"The supplied name is not valid; the following characters are not allowed:" -msgstr "Надане ім'я недійсне; такі символи не допускаються:" +#: xs/src/slic3r/GUI/Tab.cpp:2742 xs/src/slic3r/GUI/Tab.cpp:2746 +msgid "The supplied name is not valid;" +msgstr "" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.cpp:1788 +#: xs/src/slic3r/GUI/Tab.cpp:2743 +msgid "the following characters are not allowed:" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2747 +msgid "the following postfix are not allowed:" +msgstr "" + +#: xs/src/slic3r/GUI/Tab.cpp:2750 msgid "The supplied name is not available." msgstr "Надане ім'я недійсне." -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.hpp:182 +#: xs/src/slic3r/GUI/Tab.hpp:286 msgid "Print Settings" msgstr "Параметри друку" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.hpp:202 +#: xs/src/slic3r/GUI/Tab.hpp:306 msgid "Filament Settings" msgstr "Параметри філаменту" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.hpp:228 +#: xs/src/slic3r/GUI/Tab.hpp:332 msgid "Printer Settings" msgstr "Параметри принтеру" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Tab.hpp:248 +#: xs/src/slic3r/GUI/Tab.hpp:348 msgid "Save preset" msgstr "Зберегти налаштування" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Field.cpp:42 +#: xs/src/slic3r/GUI/Field.cpp:98 msgid "default" msgstr "за замовчуванням" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:26 +#: xs/src/slic3r/GUI/Field.cpp:128 +#, c-format +msgid "%s doesn't support percentage" +msgstr "" + +#: xs/src/slic3r/GUI/Field.cpp:137 +msgid "Input value is out of range" +msgstr "" + +#: xs/src/slic3r/GUI/Preset.cpp:144 +msgid "modified" +msgstr "модифікований" + +#: xs/src/slic3r/GUI/Preset.cpp:746 xs/src/slic3r/GUI/Preset.cpp:806 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1251 lib/Slic3r/GUI/Plater.pm:604 +msgid "User presets" +msgstr "Налаштування користувача" + +#: xs/src/slic3r/GUI/PresetHints.cpp:27 #, c-format msgid "" "If estimated layer time is below ~%ds, fan will run at %d%% and print speed " @@ -882,7 +1550,7 @@ msgstr "" "%%, і швидкість друку буде зменшена, так що на цей шар витрачається не менше " "%dс (однак швидкість ніколи не зменшиться нижче %d mm/s) ." -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:30 +#: xs/src/slic3r/GUI/PresetHints.cpp:31 #, c-format msgid "" "\n" @@ -893,7 +1561,7 @@ msgstr "" "Якщо запланований час друку шару більше, але все ще нижче ~%dс, вентилятор " "буде працювати з пропорційно зменшуваною швидкістю між %d%% та %d%%." -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:34 +#: xs/src/slic3r/GUI/PresetHints.cpp:35 msgid "" "\n" "During the other layers, fan " @@ -901,95 +1569,95 @@ msgstr "" "\n" "Під час друку інших шарів вентилятор " -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:36 +#: xs/src/slic3r/GUI/PresetHints.cpp:37 msgid "Fan " msgstr "Вентилятор " -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:41 +#: xs/src/slic3r/GUI/PresetHints.cpp:42 #, c-format msgid "will always run at %d%% " msgstr "буде завжди працювати на %d%% " -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:44 +#: xs/src/slic3r/GUI/PresetHints.cpp:45 #, c-format msgid "except for the first %d layers" msgstr "за винятком перших %d шарів" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:48 +#: xs/src/slic3r/GUI/PresetHints.cpp:49 msgid "except for the first layer" msgstr "за винятком першого шару" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:50 +#: xs/src/slic3r/GUI/PresetHints.cpp:51 msgid "will be turned off." msgstr "буде вимкнено." -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:151 +#: xs/src/slic3r/GUI/PresetHints.cpp:152 msgid "external perimeters" msgstr "зовнішні периметри" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:160 +#: xs/src/slic3r/GUI/PresetHints.cpp:161 msgid "perimeters" msgstr "периметри" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:169 +#: xs/src/slic3r/GUI/PresetHints.cpp:170 msgid "infill" msgstr "наповнення" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:179 +#: xs/src/slic3r/GUI/PresetHints.cpp:180 msgid "solid infill" msgstr "суцільне наповнення" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:187 +#: xs/src/slic3r/GUI/PresetHints.cpp:188 msgid "top solid infill" msgstr "верхній суцільне наповнення" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:198 +#: xs/src/slic3r/GUI/PresetHints.cpp:199 msgid "support" msgstr "підтримка" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:208 +#: xs/src/slic3r/GUI/PresetHints.cpp:209 msgid "support interface" msgstr "інтерфейс підтримки" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:214 +#: xs/src/slic3r/GUI/PresetHints.cpp:215 msgid "First layer volumetric" msgstr "Об'єм першого шару" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:214 +#: xs/src/slic3r/GUI/PresetHints.cpp:215 msgid "Bridging volumetric" msgstr "Об'єм мостів" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:214 +#: xs/src/slic3r/GUI/PresetHints.cpp:215 msgid "Volumetric" msgstr "Об'ємний" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:215 +#: xs/src/slic3r/GUI/PresetHints.cpp:216 msgid " flow rate is maximized " msgstr " швидкість потоку максимізується " -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:218 +#: xs/src/slic3r/GUI/PresetHints.cpp:219 msgid "by the print profile maximum" msgstr "за профілем друку максимум" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:219 +#: xs/src/slic3r/GUI/PresetHints.cpp:220 msgid "when printing " msgstr "коли друкуємо " -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:220 +#: xs/src/slic3r/GUI/PresetHints.cpp:221 msgid " with a volumetric rate " msgstr " з об'ємною швидкістю " -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:224 +#: xs/src/slic3r/GUI/PresetHints.cpp:225 #, c-format msgid "%3.2f mm³/s" msgstr "%3.2f мм³/с" -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:226 +#: xs/src/slic3r/GUI/PresetHints.cpp:227 #, c-format msgid " at filament speed %3.2f mm/s." msgstr " при швидкості філаменту %3.2f мм/с." -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:245 +#: xs/src/slic3r/GUI/PresetHints.cpp:246 msgid "" "Recommended object thin wall thickness: Not available due to invalid layer " "height." @@ -997,25 +1665,21 @@ msgstr "" "Рекомендований об'єкт товщиною тонкої стінки: Недоступний через невірне " "значення висоти шару." -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:262 +#: xs/src/slic3r/GUI/PresetHints.cpp:263 #, c-format msgid "Recommended object thin wall thickness for layer height %.2f and " msgstr "Рекомендована товщина стінки для висоти шару %.2f та " -#: c:\src\Slic3r\xs\src\slic3r\GUI\PresetHints.cpp:269 +#: xs/src/slic3r/GUI/PresetHints.cpp:270 #, c-format msgid "%d lines: %.2lf mm" msgstr "%d рядків: %.2lf мм" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.hpp:17 -msgid "Preferences" -msgstr "Налаштування" - -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:27 +#: xs/src/slic3r/GUI/Preferences.cpp:34 msgid "Remember output directory" msgstr "Пам'ятати вихідний каталог" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:29 +#: xs/src/slic3r/GUI/Preferences.cpp:36 msgid "" "If this is enabled, Slic3r will prompt the last output directory instead of " "the one containing the input files." @@ -1023,22 +1687,22 @@ msgstr "" "Якщо вибрано, Slic3r запропонує останню вихідну директорію замість тої, що " "вказана у вхідному файлі." -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:35 +#: xs/src/slic3r/GUI/Preferences.cpp:42 msgid "Auto-center parts" msgstr "Автоцентрувати частини" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:37 +#: xs/src/slic3r/GUI/Preferences.cpp:44 msgid "" "If this is enabled, Slic3r will auto-center objects around the print bed " "center." msgstr "" "Якщо вибрано, Slic3r автоматично орієнтує об'єкти навколо центру друку." -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:43 +#: xs/src/slic3r/GUI/Preferences.cpp:50 msgid "Background processing" msgstr "Фонова обробка" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:45 +#: xs/src/slic3r/GUI/Preferences.cpp:52 msgid "" "If this is enabled, Slic3r will pre-process objects as soon as they're " "loaded in order to save time when exporting G-code." @@ -1046,11 +1710,11 @@ msgstr "" "Якщо вибрано, Slic3r буде попередньо обробляти об'єкти, як тільки вони " "будуть завантажені, щоб заощадити час при експорті G-коду." -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:51 +#: xs/src/slic3r/GUI/Preferences.cpp:74 msgid "Disable USB/serial connection" msgstr "Вимкнути USB / послідовне з'єднання" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:53 +#: xs/src/slic3r/GUI/Preferences.cpp:76 msgid "" "Disable communication with the printer over a serial / USB cable. This " "simplifies the user interface in case the printer is never attached to the " @@ -1060,11 +1724,11 @@ msgstr "" "Користувальницький інтерфейс спрощує, якщо принтер ніколи не приєднується до " "комп'ютера." -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:59 +#: xs/src/slic3r/GUI/Preferences.cpp:82 msgid "Suppress \" - default - \" presets" msgstr "Заборонити налаштування \"- за замовчуванням -\"" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:61 +#: xs/src/slic3r/GUI/Preferences.cpp:84 msgid "" "Suppress \" - default - \" presets in the Print / Filament / Printer " "selections once there are any other valid presets available." @@ -1072,11 +1736,11 @@ msgstr "" "Заборонити налаштування \"- за замовчуванням -\" у параметрах Друк / " "Філамент / Принтер, якщо доступні інші діючі налаштування." -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:67 +#: xs/src/slic3r/GUI/Preferences.cpp:90 msgid "Show incompatible print and filament presets" msgstr "Показувати несумісні налаштування друку та філаменту" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:69 +#: xs/src/slic3r/GUI/Preferences.cpp:92 msgid "" "When checked, the print and filament presets are shown in the preset editor " "even if they are marked as incompatible with the active printer" @@ -1084,11 +1748,11 @@ msgstr "" "Якщо вибрано, налаштування друку та філаменту відображаються у списку " "налаштувань, навіть якщо вони позначені як несумісні з активним принтером" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:75 +#: xs/src/slic3r/GUI/Preferences.cpp:98 msgid "Use legacy OpenGL 1.1 rendering" msgstr "Використовувати застарілий OpenGL 1.1 рендеринг" -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:77 +#: xs/src/slic3r/GUI/Preferences.cpp:100 msgid "" "If you have rendering issues caused by a buggy OpenGL 2.0 driver, you may " "try to check this checkbox. This will disable the layer height editing and " @@ -1099,15 +1763,355 @@ msgstr "" "редагування висоти шару та згладжування, тому краще оновити графічний " "драйвер." -#: c:\src\Slic3r\xs\src\slic3r\GUI\Preferences.cpp:101 +#: xs/src/slic3r/GUI/Preferences.cpp:124 msgid "You need to restart Slic3r to make the changes effective." msgstr "З метою ефективності зміни, Вам потрібно буде перезапустити Slic3r." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:26 +#: xs/src/slic3r/GUI/RammingChart.cpp:23 +msgid "NO RAMMING AT ALL" +msgstr "" + +#: xs/src/slic3r/GUI/RammingChart.cpp:76 +msgid "Time" +msgstr "" + +#: xs/src/slic3r/GUI/RammingChart.cpp:76 xs/src/slic3r/GUI/RammingChart.cpp:81 +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:77 +#: xs/src/libslic3r/PrintConfig.cpp:490 +msgid "s" +msgstr "" + +#: xs/src/slic3r/GUI/RammingChart.cpp:81 +msgid "Volumetric speed" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:27 +msgid "Update available" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:27 +msgid "New version of Slic3r PE is available" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:34 +msgid "To download, follow the link below." +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:41 +msgid "Current version:" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:43 +msgid "New version:" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:51 +msgid "Don't notify about new releases any more" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:69 +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:161 +msgid "Configuration update" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:69 +msgid "Configuration update is available" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:72 +msgid "" +"Would you like to install it?\n" +"\n" +"Note that a full configuration snapshot will be created first. It can then " +"be restored at any time should there be a problem with the new version.\n" +"\n" +"Updated configuration bundles:" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:108 +msgid "Slic3r incompatibility" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:108 +msgid "Slic3r configuration is incompatible" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:111 +msgid "" +"This version of Slic3r PE is not compatible with currently installed " +"configuration bundles.\n" +"This probably happened as a result of running an older Slic3r PE after using " +"a newer one.\n" +"\n" +"You may either exit Slic3r and try again with a newer version, or you may re-" +"run the initial configuration. Doing so will create a backup snapshot of the " +"existing configuration before installing files compatible with this Slic3r.\n" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:120 +#, c-format +msgid "This Slic3r PE version: %s" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:125 +msgid "Incompatible bundles:" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:141 +msgid "Exit Slic3r" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:144 +msgid "Re-configure" +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:165 +#, c-format +msgid "" +"Slic3r PE now uses an updated configuration structure.\n" +"\n" +"So called 'System presets' have been introduced, which hold the built-in " +"default settings for various printers. These System presets cannot be " +"modified, instead, users now may create their own presets inheriting " +"settings from one of the System presets.\n" +"An inheriting preset may either inherit a particular value from its parent " +"or override it with a customized value.\n" +"\n" +"Please proceed with the %s that follows to set up the new presets and to " +"choose whether to enable automatic preset updates." +msgstr "" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:181 +msgid "For more information please visit our wiki page:" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:9 +msgid "Ramming customization" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:35 +msgid "" +"Ramming denotes the rapid extrusion just before a tool change in a single-" +"extruder MM printer. Its purpose is to properly shape the end of the " +"unloaded filament so it does not prevent insertion of the new filament and " +"can itself be reinserted later. This phase is important and different " +"materials can require different extrusion speeds to get the good shape. For " +"this reason, the extrusion rates during ramming are adjustable.\n" +"\n" +"This is an expert-level setting, incorrect adjustment will likely lead to " +"jams, extruder wheel grinding into filament etc." +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:77 +msgid "Total ramming time" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:79 +msgid "Total rammed volume" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:83 +msgid "Ramming line width" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:85 +msgid "Ramming line spacing" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:137 +msgid "Wipe tower - Purging volume adjustment" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:218 +msgid "" +"Here you can adjust required purging volume (mm³) for any given pair of " +"tools." +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:219 +msgid "Extruder changed to" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:227 +msgid "unloaded" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:228 +msgid "loaded" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:233 +msgid "Tool #" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:240 +msgid "" +"Total purging volume is calculated by summing two values below, depending on " +"which tools are loaded/unloaded." +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:241 +msgid "Volume to purge (mm³) when the filament is being" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:255 +msgid "From" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:320 +msgid "" +"Switching to simple settings will discard changes done in the advanced " +"mode!\n" +"\n" +"Do you want to proceed?" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:332 +msgid "Show simplified settings" +msgstr "" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:332 +msgid "Show advanced settings" +msgstr "" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:33 +msgid "Send G-Code to printer" +msgstr "" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:33 +msgid "Upload to OctoPrint with the following filename:" +msgstr "" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:35 +msgid "Start printing after upload" +msgstr "" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:37 +msgid "Use forward slashes ( / ) as a directory separator if needed." +msgstr "" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:98 +msgid "Error while uploading to the OctoPrint server" +msgstr "" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:111 lib/Slic3r/GUI/Plater.pm:1558 +msgid "Sending G-code file to the OctoPrint server..." +msgstr "Відправка файлу G-коду на сервер OctoPrint..." + +#: xs/src/slic3r/Utils/PresetUpdater.cpp:544 +#, c-format +msgid "requires min. %s and max. %s" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:553 +msgid "All objects are outside of the print volume." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:579 +msgid "Some objects are too close; your extruder will collide with them." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:594 +msgid "" +"Some objects are too tall and cannot be printed without extruder collisions." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:604 +msgid "The Spiral Vase option can only be used when printing a single object." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:606 +msgid "" +"The Spiral Vase option can only be used when printing single material " +"objects." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:612 +msgid "" +"All extruders must have the same diameter for single extruder multimaterial " +"printer." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:617 +msgid "" +"The Wipe Tower is currently only supported for the Marlin and RepRap/" +"Sprinter G-code flavors." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:619 +msgid "" +"The Wipe Tower is currently only supported with the relative extruder " +"addressing (use_relative_e_distances=1)." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:631 +msgid "" +"The Wipe Tower is only supported for multiple objects if they have equal " +"layer heigths" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:633 +msgid "" +"The Wipe Tower is only supported for multiple objects if they are printed " +"over an equal number of raft layers" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:635 +msgid "" +"The Wipe Tower is only supported for multiple objects if they are printed " +"with the same support_material_contact_distance" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:637 +msgid "" +"The Wipe Tower is only supported for multiple objects if they are sliced " +"equally." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:661 +msgid "" +"The Wipe tower is only supported if all objects have the same layer height " +"profile" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:670 +msgid "The supplied settings will cause an empty print." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:680 +msgid "" +"One or more object were assigned an extruder that the printer does not have." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:689 +msgid "" +"Printing with multiple extruders of differing nozzle diameters. If support " +"is to be printed with the current extruder (support_material_extruder == 0 " +"or support_material_interface_extruder == 0), all nozzles have to be of the " +"same diameter." +msgstr "" + +#: xs/src/libslic3r/Print.cpp:695 +msgid "first_layer_height" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:710 +msgid "First layer height can't be greater than nozzle diameter" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:714 +msgid "Layer height can't be greater than nozzle diameter" +msgstr "" + +#: xs/src/libslic3r/Print.cpp:1196 +msgid "Failed processing of the output_filename_format template." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:29 msgid "Avoid crossing perimeters" msgstr "Уникати перетинання периметрів" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:27 +#: xs/src/libslic3r/PrintConfig.cpp:30 msgid "" "Optimize travel moves in order to minimize the crossing of perimeters. This " "is mostly useful with Bowden extruders which suffer from oozing. This " @@ -1117,12 +2121,11 @@ msgstr "" "основному це корисно для екструдерів Bowden, які страждають від протікання. " "Ця функція уповільнює як друк, так і генерацію G-коду." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:38 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1572 +#: xs/src/libslic3r/PrintConfig.cpp:41 xs/src/libslic3r/PrintConfig.cpp:1818 msgid "Other layers" msgstr "Інші шари" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:39 +#: xs/src/libslic3r/PrintConfig.cpp:42 msgid "" "Bed temperature for layers after the first one. Set this to zero to disable " "bed temperature control commands in the output." @@ -1130,11 +2133,11 @@ msgstr "" "Температура полотна для останніх шарів після першого. Установіть 0, щоб " "відключити команди керування температурою полотна на виході." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:42 +#: xs/src/libslic3r/PrintConfig.cpp:45 msgid "Bed temperature" msgstr "Температура полотна" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:49 +#: xs/src/libslic3r/PrintConfig.cpp:52 msgid "" "This custom code is inserted at every layer change, right before the Z move. " "Note that you can use placeholder variables for all Slic3r settings as well " @@ -1144,11 +2147,11 @@ msgstr "" "переміщення Z. Зауважте, що ви можете використовувати змінні-заповнювачі для " "всіх параметрів Slic3r, а також [layer_num] і [layer_z]." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:59 +#: xs/src/libslic3r/PrintConfig.cpp:62 msgid "Between objects G-code" msgstr "G-код між об'єктами" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:60 +#: xs/src/libslic3r/PrintConfig.cpp:63 msgid "" "This code is inserted between objects when using sequential printing. By " "default extruder and bed temperature are reset using non-wait command; " @@ -1165,39 +2168,32 @@ msgstr "" "Slic3r, то ж ви можете вставити команду \"M109 S [first_layer_temperature]\" " "де завгодно." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:68 -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:370 +#: xs/src/libslic3r/PrintConfig.cpp:71 lib/Slic3r/GUI/MainFrame.pm:328 msgid "Bottom" msgstr "Знизу" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:69 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:239 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:290 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:298 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:604 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:762 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:778 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:941 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:989 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1152 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1583 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1639 +#: xs/src/libslic3r/PrintConfig.cpp:72 xs/src/libslic3r/PrintConfig.cpp:276 +#: xs/src/libslic3r/PrintConfig.cpp:327 xs/src/libslic3r/PrintConfig.cpp:335 +#: xs/src/libslic3r/PrintConfig.cpp:701 xs/src/libslic3r/PrintConfig.cpp:871 +#: xs/src/libslic3r/PrintConfig.cpp:887 xs/src/libslic3r/PrintConfig.cpp:1156 +#: xs/src/libslic3r/PrintConfig.cpp:1222 xs/src/libslic3r/PrintConfig.cpp:1400 +#: xs/src/libslic3r/PrintConfig.cpp:1829 xs/src/libslic3r/PrintConfig.cpp:1885 msgid "Layers and Perimeters" msgstr "Шари та периметри" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:70 +#: xs/src/libslic3r/PrintConfig.cpp:73 msgid "Number of solid layers to generate on bottom surfaces." msgstr "Кількість суцільних шарів, генерованих на нижніх поверхнях." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:72 +#: xs/src/libslic3r/PrintConfig.cpp:75 msgid "Bottom solid layers" msgstr "Нижні суцільні шари" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:77 +#: xs/src/libslic3r/PrintConfig.cpp:80 msgid "Bridge" msgstr "Міст" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:78 +#: xs/src/libslic3r/PrintConfig.cpp:81 msgid "" "This is the acceleration your printer will use for bridges. Set zero to " "disable acceleration control for bridges." @@ -1205,19 +2201,18 @@ msgstr "" "Це прискорення, яке ваш принтер використовуватиме для мостів. Встановити 0, " "щоб відключити управління прискоренням для мостів." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:80 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:174 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:576 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:684 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:952 +#: xs/src/libslic3r/PrintConfig.cpp:83 xs/src/libslic3r/PrintConfig.cpp:199 +#: xs/src/libslic3r/PrintConfig.cpp:673 xs/src/libslic3r/PrintConfig.cpp:781 +#: xs/src/libslic3r/PrintConfig.cpp:931 xs/src/libslic3r/PrintConfig.cpp:972 +#: xs/src/libslic3r/PrintConfig.cpp:982 xs/src/libslic3r/PrintConfig.cpp:1185 msgid "mm/s²" msgstr "мм/с²" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:86 +#: xs/src/libslic3r/PrintConfig.cpp:89 msgid "Bridging angle" msgstr "Кут моста" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:88 +#: xs/src/libslic3r/PrintConfig.cpp:91 msgid "" "Bridging angle override. If left to zero, the bridging angle will be " "calculated automatically. Otherwise the provided angle will be used for all " @@ -1227,36 +2222,31 @@ msgstr "" "автоматично. Інакше передбачений кут буде використаний для всіх мостів. " "Використовуйте 180° для нульового кута." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:91 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:492 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1170 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1181 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1401 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1557 +#: xs/src/libslic3r/PrintConfig.cpp:94 xs/src/libslic3r/PrintConfig.cpp:589 +#: xs/src/libslic3r/PrintConfig.cpp:1418 xs/src/libslic3r/PrintConfig.cpp:1429 +#: xs/src/libslic3r/PrintConfig.cpp:1649 xs/src/libslic3r/PrintConfig.cpp:1803 msgid "°" msgstr "°" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:97 +#: xs/src/libslic3r/PrintConfig.cpp:100 msgid "Bridges fan speed" msgstr "Швидкість вентилятора для мостів" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:98 +#: xs/src/libslic3r/PrintConfig.cpp:101 msgid "This fan speed is enforced during all bridges and overhangs." msgstr "Ця швидкість вентилятора виконується для всіх мостів і виступів." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:99 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:504 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:789 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:850 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1060 +#: xs/src/libslic3r/PrintConfig.cpp:102 xs/src/libslic3r/PrintConfig.cpp:601 +#: xs/src/libslic3r/PrintConfig.cpp:990 xs/src/libslic3r/PrintConfig.cpp:1058 +#: xs/src/libslic3r/PrintConfig.cpp:1308 msgid "%" msgstr "%" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:106 +#: xs/src/libslic3r/PrintConfig.cpp:109 msgid "Bridge flow ratio" msgstr "Співвідношення мостового потоку" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:108 +#: xs/src/libslic3r/PrintConfig.cpp:111 msgid "" "This factor affects the amount of plastic for bridging. You can decrease it " "slightly to pull the extrudates and prevent sagging, although default " @@ -1268,32 +2258,31 @@ msgstr "" "стандартні налаштування зазвичай добрі, тому ви маете по-експериментувати з " "охолодженням (використовуйте вентилятор), перш ніж їх налаштувати." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:117 +#: xs/src/libslic3r/PrintConfig.cpp:121 msgid "Bridges" msgstr "Мости" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:119 +#: xs/src/libslic3r/PrintConfig.cpp:123 msgid "Speed for printing bridges." msgstr "Швидкість друку мостів." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:120 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:638 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:749 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:811 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:868 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:981 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1137 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1146 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1536 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1649 +#: xs/src/libslic3r/PrintConfig.cpp:124 xs/src/libslic3r/PrintConfig.cpp:471 +#: xs/src/libslic3r/PrintConfig.cpp:480 xs/src/libslic3r/PrintConfig.cpp:508 +#: xs/src/libslic3r/PrintConfig.cpp:516 xs/src/libslic3r/PrintConfig.cpp:735 +#: xs/src/libslic3r/PrintConfig.cpp:846 xs/src/libslic3r/PrintConfig.cpp:922 +#: xs/src/libslic3r/PrintConfig.cpp:940 xs/src/libslic3r/PrintConfig.cpp:952 +#: xs/src/libslic3r/PrintConfig.cpp:962 xs/src/libslic3r/PrintConfig.cpp:1019 +#: xs/src/libslic3r/PrintConfig.cpp:1076 xs/src/libslic3r/PrintConfig.cpp:1214 +#: xs/src/libslic3r/PrintConfig.cpp:1385 xs/src/libslic3r/PrintConfig.cpp:1394 +#: xs/src/libslic3r/PrintConfig.cpp:1782 xs/src/libslic3r/PrintConfig.cpp:1895 msgid "mm/s" msgstr "мм/с" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:127 +#: xs/src/libslic3r/PrintConfig.cpp:131 msgid "Brim width" msgstr "Ширина краю" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:128 +#: xs/src/libslic3r/PrintConfig.cpp:132 msgid "" "Horizontal width of the brim that will be printed around each object on the " "first layer." @@ -1301,11 +2290,11 @@ msgstr "" "Горизонтальна ширина краю, яка буде надрукована навколо кожного об'єкта на " "першому шарі." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:135 +#: xs/src/libslic3r/PrintConfig.cpp:139 msgid "Clip multi-part objects" msgstr "Обрізати об'єкти, що складаються з кількох частин" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:136 +#: xs/src/libslic3r/PrintConfig.cpp:140 msgid "" "When printing multi-material objects, this settings will make slic3r to clip " "the overlapping object parts one by the other (2nd part will be clipped by " @@ -1315,11 +2304,11 @@ msgstr "" "обрізати частини, що перекриваються один одною (друга частина буде обрізана " "першою, третя - першою та другою, тощо)." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:147 +#: xs/src/libslic3r/PrintConfig.cpp:151 msgid "Compatible printers condition" msgstr "Стан сумісних принтерів" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:148 +#: xs/src/libslic3r/PrintConfig.cpp:152 msgid "" "A boolean expression using the configuration values of an active printer " "profile. If this expression evaluates to true, this profile is considered " @@ -1329,11 +2318,11 @@ msgstr "" "принтера. Якщо цей вираз оцінюється як Правда, цей профіль вважається " "сумісним з активним профілем принтера." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:154 +#: xs/src/libslic3r/PrintConfig.cpp:163 msgid "Complete individual objects" msgstr "Закінчити окремі об'єкти" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:155 +#: xs/src/libslic3r/PrintConfig.cpp:164 msgid "" "When printing multiple objects or copies, this feature will complete each " "object before moving onto next one (and starting it from its bottom layer). " @@ -1345,11 +2334,11 @@ msgstr "" "шару). Ця функція корисна для уникнення ризику зіпсованих відбитків. Slic3r " "має попередити та запобігти зіткненню екструдера, але будьте обережні." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:163 +#: xs/src/libslic3r/PrintConfig.cpp:172 msgid "Enable auto cooling" msgstr "Увімкнути автоматичне охолодження" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:164 +#: xs/src/libslic3r/PrintConfig.cpp:173 msgid "" "This flag enables the automatic cooling logic that adjusts print speed and " "fan speed according to layer printing time." @@ -1357,7 +2346,23 @@ msgstr "" "Цей прапорець дозволяє автоматичну логіку охолодження, яка регулює швидкість " "друку та швидкість вентиляції відповідно до часу друку шару." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:171 +#: xs/src/libslic3r/PrintConfig.cpp:179 +msgid "Cooling tube position" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:180 +msgid "Distance of the center-point of the cooling tube from the extruder tip " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:187 +msgid "Cooling tube length" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:188 +msgid "Length of the cooling tube to limit space for cooling moves inside it " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:196 msgid "" "This is the acceleration your printer will be reset to after the role-" "specific acceleration values are used (perimeter/infill). Set zero to " @@ -1367,11 +2372,33 @@ msgstr "" "використані конкретні визначені прискорення (периметру / заповнення). " "Встановити 0, щоб запобігти скиданням прискорення взагалі." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:180 +#: xs/src/libslic3r/PrintConfig.cpp:205 +msgid "Default filament profile" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:206 +msgid "" +"Default filament profile associated with the current printer profile. On " +"selection of the current printer profile, this filament profile will be " +"activated." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:211 +msgid "Default print profile" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:212 +msgid "" +"Default print profile associated with the current printer profile. On " +"selection of the current printer profile, this print profile will be " +"activated." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:217 msgid "Disable fan for the first" msgstr "Вимкнути вентилятор для першого(их)" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:181 +#: xs/src/libslic3r/PrintConfig.cpp:218 msgid "" "You can set this to a positive value to disable fan at all during the first " "layers, so that it does not make adhesion worse." @@ -1380,21 +2407,18 @@ msgstr "" "протягом друку декількох перших шарів, щоб це не призвело до гіршого " "зчеплення." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:183 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:694 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1033 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1224 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1285 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1437 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1482 +#: xs/src/libslic3r/PrintConfig.cpp:220 xs/src/libslic3r/PrintConfig.cpp:791 +#: xs/src/libslic3r/PrintConfig.cpp:1281 xs/src/libslic3r/PrintConfig.cpp:1472 +#: xs/src/libslic3r/PrintConfig.cpp:1533 xs/src/libslic3r/PrintConfig.cpp:1685 +#: xs/src/libslic3r/PrintConfig.cpp:1730 msgid "layers" msgstr "шару(ів)" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:190 +#: xs/src/libslic3r/PrintConfig.cpp:227 msgid "Don't support bridges" msgstr "Не підтримувати мости" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:192 +#: xs/src/libslic3r/PrintConfig.cpp:229 msgid "" "Experimental option for preventing support material from being generated " "under bridged areas." @@ -1402,19 +2426,19 @@ msgstr "" "Експериментальний варіант для запобігання утворенню допоміжного матеріалу в " "областях під мостами." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:198 +#: xs/src/libslic3r/PrintConfig.cpp:235 msgid "Distance between copies" msgstr "Відстань між копіями" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:199 +#: xs/src/libslic3r/PrintConfig.cpp:236 msgid "Distance used for the auto-arrange feature of the plater." msgstr "Відстань використовується для автоматичного розташування платеру." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:207 +#: xs/src/libslic3r/PrintConfig.cpp:244 msgid "Elephant foot compensation" msgstr "Зрівноваження Стопи слона" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:209 +#: xs/src/libslic3r/PrintConfig.cpp:246 msgid "" "The first layer will be shrunk in the XY plane by the configured value to " "compensate for the 1st layer squish aka an Elephant Foot effect." @@ -1422,7 +2446,7 @@ msgstr "" "Перший шар буде зменшено в площині XY завдяки налаштованому значенню, щоб " "компенсувати ефект Ноги Слона для 1-го шару." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:218 +#: xs/src/libslic3r/PrintConfig.cpp:255 msgid "" "This end procedure is inserted at the end of the output file. Note that you " "can use placeholder variables for all Slic3r settings." @@ -1430,7 +2454,7 @@ msgstr "" "Ця кінцева процедура вставляється в кінці вихідного файлу. Зауважте, що ви " "можете використовувати заповнювачі змінних для всіх параметрів Slic3r." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:228 +#: xs/src/libslic3r/PrintConfig.cpp:265 msgid "" "This end procedure is inserted at the end of the output file, before the " "printer end gcode. Note that you can use placeholder variables for all " @@ -1442,11 +2466,11 @@ msgstr "" "для всіх параметрів Slic3r. Якщо у вас є кілька екструдерів, G-code " "обробляється в порядку екструдерів." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:238 +#: xs/src/libslic3r/PrintConfig.cpp:275 msgid "Ensure vertical shell thickness" msgstr "Перевірте товщину вертикальної оболонки" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:240 +#: xs/src/libslic3r/PrintConfig.cpp:277 msgid "" "Add solid infill near sloping surfaces to guarantee the vertical shell " "thickness (top+bottom solid layers)." @@ -1454,11 +2478,11 @@ msgstr "" "Додайте суцільні наповнювачі біля нахилених поверхонь, щоб гарантувати " "товщину вертикальної оболонки (верхній і нижній суцільні шари)." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:246 +#: xs/src/libslic3r/PrintConfig.cpp:283 msgid "Top/bottom fill pattern" msgstr "Верхній/нижній шаблон наповнення" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:248 +#: xs/src/libslic3r/PrintConfig.cpp:285 msgid "" "Fill pattern for top/bottom infill. This only affects the external visible " "layer, and not its adjacent solid shells." @@ -1466,23 +2490,39 @@ msgstr "" "Шаблон для верхнього/нижнього наповнення. Це впливає лише на зовнішній " "видимий шар, а не на сусідні суцільні оболонки." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:267 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:277 +#: xs/src/libslic3r/PrintConfig.cpp:294 xs/src/libslic3r/PrintConfig.cpp:654 +#: xs/src/libslic3r/PrintConfig.cpp:1764 +msgid "Rectilinear" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:295 xs/src/libslic3r/PrintConfig.cpp:660 +msgid "Concentric" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:296 xs/src/libslic3r/PrintConfig.cpp:664 +msgid "Hilbert Curve" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:297 xs/src/libslic3r/PrintConfig.cpp:665 +msgid "Archimedean Chords" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:298 xs/src/libslic3r/PrintConfig.cpp:666 +msgid "Octagram Spiral" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:304 xs/src/libslic3r/PrintConfig.cpp:314 msgid "External perimeters" msgstr "Зовнішні периметри" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:268 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:377 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:592 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:710 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:967 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1292 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1454 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1614 +#: xs/src/libslic3r/PrintConfig.cpp:305 xs/src/libslic3r/PrintConfig.cpp:415 +#: xs/src/libslic3r/PrintConfig.cpp:689 xs/src/libslic3r/PrintConfig.cpp:807 +#: xs/src/libslic3r/PrintConfig.cpp:1200 xs/src/libslic3r/PrintConfig.cpp:1540 +#: xs/src/libslic3r/PrintConfig.cpp:1702 xs/src/libslic3r/PrintConfig.cpp:1860 msgid "Extrusion Width" msgstr "Ширина екструзії" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:269 +#: xs/src/libslic3r/PrintConfig.cpp:306 msgid "" "Set this to a non-zero value to set a manual extrusion width for external " "perimeters. If left zero, default extrusion width will be used if set, " @@ -1495,17 +2535,14 @@ msgstr "" "сопла. Якщо він виражений у відсотках (наприклад, 200%), він буде " "обчислюватися за висотою шару." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:272 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:597 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:715 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:972 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1296 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1458 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1619 +#: xs/src/libslic3r/PrintConfig.cpp:309 xs/src/libslic3r/PrintConfig.cpp:694 +#: xs/src/libslic3r/PrintConfig.cpp:812 xs/src/libslic3r/PrintConfig.cpp:1205 +#: xs/src/libslic3r/PrintConfig.cpp:1544 xs/src/libslic3r/PrintConfig.cpp:1706 +#: xs/src/libslic3r/PrintConfig.cpp:1865 msgid "mm or % (leave 0 for default)" msgstr "мм або % (залиште 0 за замовчанням)" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:279 +#: xs/src/libslic3r/PrintConfig.cpp:316 msgid "" "This separate setting will affect the speed of external perimeters (the " "visible ones). If expressed as percentage (for example: 80%) it will be " @@ -1516,20 +2553,17 @@ msgstr "" "налаштування швидкості периметра вище. Встановити 0 для автоматичного " "використання." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:282 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:619 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1255 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1306 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1501 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1631 +#: xs/src/libslic3r/PrintConfig.cpp:319 xs/src/libslic3r/PrintConfig.cpp:716 +#: xs/src/libslic3r/PrintConfig.cpp:1503 xs/src/libslic3r/PrintConfig.cpp:1554 +#: xs/src/libslic3r/PrintConfig.cpp:1749 xs/src/libslic3r/PrintConfig.cpp:1877 msgid "mm/s or %" msgstr "мм/с або %" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:289 +#: xs/src/libslic3r/PrintConfig.cpp:326 msgid "External perimeters first" msgstr "Спочатку зовнішні периметри" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:291 +#: xs/src/libslic3r/PrintConfig.cpp:328 msgid "" "Print contour perimeters from the outermost one to the innermost one instead " "of the default inverse order." @@ -1537,11 +2571,11 @@ msgstr "" "Друкувати контури периметра від найзовнішнього до найвнутрішнього, замість " "інверсного порядку за замовчанням." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:297 +#: xs/src/libslic3r/PrintConfig.cpp:334 msgid "Extra perimeters if needed" msgstr "Додаткові периметри, якщо необхідно" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:299 +#: xs/src/libslic3r/PrintConfig.cpp:336 #, no-c-format msgid "" "Add more perimeters when needed for avoiding gaps in sloping walls. Slic3r " @@ -1552,7 +2586,7 @@ msgstr "" "Slic3r продовжує додавати периметри, поки підтримується більше 70% петель " "безпосередньо вище." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:309 +#: xs/src/libslic3r/PrintConfig.cpp:346 msgid "" "The extruder to use (unless more specific extruder settings are specified). " "This value overrides perimeter and infill extruders, but not the support " @@ -1562,12 +2596,11 @@ msgstr "" "екструдера). Це значення перевизначає екструдери периметра та наповнювача, " "але не екструдери підтримки." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:320 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:69 +#: xs/src/libslic3r/PrintConfig.cpp:358 lib/Slic3r/GUI/Plater/3DPreview.pm:75 msgid "Height" msgstr "Висота" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:321 +#: xs/src/libslic3r/PrintConfig.cpp:359 msgid "" "Set this to the vertical distance between your nozzle tip and (usually) the " "X carriage rods. In other words, this is the height of the clearance " @@ -1579,11 +2612,11 @@ msgstr "" "навколо вашого екструдера, і це являє собою максимальну глибину, яку " "екструдер може розглядати до зіткнення з іншими друкованими предметами." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:331 +#: xs/src/libslic3r/PrintConfig.cpp:369 msgid "Radius" msgstr "Радіус" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:332 +#: xs/src/libslic3r/PrintConfig.cpp:370 msgid "" "Set this to the clearance radius around your extruder. If the extruder is " "not centered, choose the largest value for safety. This setting is used to " @@ -1594,21 +2627,20 @@ msgstr "" "параметр використовується для перевірки зіткнень та відображення графічного " "попереднього перегляду в панелі." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:342 +#: xs/src/libslic3r/PrintConfig.cpp:380 msgid "Extruder Color" msgstr "Колір екструдера" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:343 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:406 +#: xs/src/libslic3r/PrintConfig.cpp:381 xs/src/libslic3r/PrintConfig.cpp:444 msgid "This is only used in the Slic3r interface as a visual help." msgstr "" "Ця опція використовується лише у інтерфейсі Slic3r як візуальна допомога." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:350 +#: xs/src/libslic3r/PrintConfig.cpp:388 msgid "Extruder offset" msgstr "Зміщення екструдеру" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:351 +#: xs/src/libslic3r/PrintConfig.cpp:389 msgid "" "If your firmware doesn't handle the extruder displacement you need the G-" "code to take it into account. This option lets you specify the displacement " @@ -1620,11 +2652,11 @@ msgstr "" "відносно першого. Він очікує позитивних координат (вони будуть віднімані від " "координати XY)." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:360 +#: xs/src/libslic3r/PrintConfig.cpp:398 msgid "Extrusion axis" msgstr "Ось екструзії" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:361 +#: xs/src/libslic3r/PrintConfig.cpp:399 msgid "" "Use this option to set the axis letter associated to your printer's extruder " "(usually E but some printers use A)." @@ -1632,11 +2664,11 @@ msgstr "" "Використовуйте цю опцію, щоб встановити букву осей, пов'язану з екструдером " "принтера (зазвичай E, але деякі принтери використовують A)." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:367 +#: xs/src/libslic3r/PrintConfig.cpp:405 msgid "Extrusion multiplier" msgstr "Коефіцієнт екструзії" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:368 +#: xs/src/libslic3r/PrintConfig.cpp:406 msgid "" "This factor changes the amount of flow proportionally. You may need to tweak " "this setting to get nice surface finish and correct single wall widths. " @@ -1649,11 +2681,11 @@ msgstr "" "вважаєте, що його потрібно більше змінити, перевірте діаметр нитки та E " "кроки прошивки ." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:376 +#: xs/src/libslic3r/PrintConfig.cpp:414 msgid "Default extrusion width" msgstr "Ширина екструзії за замовчанням" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:378 +#: xs/src/libslic3r/PrintConfig.cpp:416 msgid "" "Set this to a non-zero value to allow a manual extrusion width. If left to " "zero, Slic3r derives extrusion widths from the nozzle diameter (see the " @@ -1667,15 +2699,15 @@ msgstr "" "наповнювача тощо). Якщо значення виражене у відсотках (наприклад: 230%), " "воно буде обчислюватися за висотою шару." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:382 +#: xs/src/libslic3r/PrintConfig.cpp:420 msgid "mm or % (leave 0 for auto)" msgstr "мм або % (залиште 0 для автообчислення)" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:387 +#: xs/src/libslic3r/PrintConfig.cpp:425 msgid "Keep fan always on" msgstr "Тримайте вентилятор завжди" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:388 +#: xs/src/libslic3r/PrintConfig.cpp:426 msgid "" "If this is enabled, fan will never be disabled and will be kept running at " "least at its minimum speed. Useful for PLA, harmful for ABS." @@ -1684,11 +2716,11 @@ msgstr "" "триматися, як мінімум, на мінімальній швидкості. Корисно для PLA, шкідливо " "для ABS." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:394 +#: xs/src/libslic3r/PrintConfig.cpp:432 msgid "Enable fan if layer print time is below" msgstr "Увімкнути вентилятор, якщо час друку шару нижче" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:395 +#: xs/src/libslic3r/PrintConfig.cpp:433 msgid "" "If layer print time is estimated below this number of seconds, fan will be " "enabled and its speed will be calculated by interpolating the minimum and " @@ -1698,29 +2730,27 @@ msgstr "" "активований, а його швидкість буде розрахована шляхом інтерполяції " "мінімальної та максимальної швидкості." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:397 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1242 +#: xs/src/libslic3r/PrintConfig.cpp:435 xs/src/libslic3r/PrintConfig.cpp:1490 msgid "approximate seconds" msgstr "приблизні секунди" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:405 +#: xs/src/libslic3r/PrintConfig.cpp:443 msgid "Color" msgstr "Колір" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:412 +#: xs/src/libslic3r/PrintConfig.cpp:450 msgid "Filament notes" msgstr "Примітки до філаменту" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:413 +#: xs/src/libslic3r/PrintConfig.cpp:451 msgid "You can put your notes regarding the filament here." msgstr "Тут ви можете помістити свої нотатки щодо філаменту." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:421 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:817 +#: xs/src/libslic3r/PrintConfig.cpp:459 xs/src/libslic3r/PrintConfig.cpp:1025 msgid "Max volumetric speed" msgstr "Максимальна об'ємна швидкість" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:422 +#: xs/src/libslic3r/PrintConfig.cpp:460 msgid "" "Maximum volumetric speed allowed for this filament. Limits the maximum " "volumetric speed of a print to the minimum of print and filament volumetric " @@ -1730,12 +2760,76 @@ msgstr "" "максимальну об'ємну швидкість друку до мінімуму об'ємної швидкості друку та " "філаметну. Встановити 0 для відсутності обмежень." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:425 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:820 +#: xs/src/libslic3r/PrintConfig.cpp:463 xs/src/libslic3r/PrintConfig.cpp:1028 msgid "mm³/s" msgstr "мм³/с" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:432 +#: xs/src/libslic3r/PrintConfig.cpp:469 +msgid "Loading speed" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:470 +msgid "Speed used for loading the filament on the wipe tower. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:477 +msgid "Unloading speed" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:478 +msgid "" +"Speed used for unloading the filament on the wipe tower (does not affect " +"initial part of unloading just after ramming). " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:486 +msgid "Delay after unloading" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:487 +msgid "" +"Time to wait after the filament is unloaded. May help to get reliable " +"toolchanges with flexible materials that may need more time to shrink to " +"original dimensions. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:496 +msgid "Number of cooling moves" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:497 +msgid "" +"Filament is cooled by being moved back and forth in the cooling tubes. " +"Specify desired number of these moves " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:505 +msgid "Speed of the first cooling move" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:506 +msgid "Cooling moves are gradually accelerating beginning at this speed. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:513 +msgid "Speed of the last cooling move" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:514 +msgid "Cooling moves are gradually accelerating towards this speed. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:521 +msgid "Ramming parameters" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:522 +msgid "" +"This string is edited by RammingDialog and contains ramming specific " +"parameters " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:529 msgid "" "Enter your filament diameter here. Good precision is required, so use a " "caliper and do multiple measurements along the filament, then compute the " @@ -1745,11 +2839,11 @@ msgstr "" "використовуйте суматор і виконайте декілька вимірювань вздовж нитки, потім " "обчисліть середнє значення." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:440 +#: xs/src/libslic3r/PrintConfig.cpp:537 msgid "Density" msgstr "Щільність" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:441 +#: xs/src/libslic3r/PrintConfig.cpp:538 msgid "" "Enter your filament density here. This is only for statistical information. " "A decent way is to weigh a known length of filament and compute the ratio of " @@ -1761,16 +2855,15 @@ msgstr "" "обчислення співвідношення довжини до обсягу. Краще обчислити об'єм " "безпосередньо через зміщення." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:444 +#: xs/src/libslic3r/PrintConfig.cpp:541 msgid "g/cm³" msgstr "г/см³" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:450 +#: xs/src/libslic3r/PrintConfig.cpp:547 msgid "Filament type" msgstr "Тип філаменту" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:451 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1002 +#: xs/src/libslic3r/PrintConfig.cpp:548 xs/src/libslic3r/PrintConfig.cpp:1235 msgid "" "If you want to process the output G-code through custom scripts, just list " "their absolute paths here. Separate multiple scripts with a semicolon. " @@ -1784,20 +2877,19 @@ msgstr "" "аргумент, і вони можуть отримати доступ до параметрів конфігурації Slic3r, " "прочитавши змінні середовища." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:470 +#: xs/src/libslic3r/PrintConfig.cpp:567 msgid "Soluble material" msgstr "Розчинний матеріал" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:471 +#: xs/src/libslic3r/PrintConfig.cpp:568 msgid "Soluble material is most likely used for a soluble support." msgstr "Розчинний матеріал переважно використовується для розчинної підтримки." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:476 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:450 +#: xs/src/libslic3r/PrintConfig.cpp:573 lib/Slic3r/GUI/Plater.pm:1616 msgid "Cost" msgstr "Вартість" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:477 +#: xs/src/libslic3r/PrintConfig.cpp:574 msgid "" "Enter your filament cost per kg here. This is only for statistical " "information." @@ -1805,15 +2897,15 @@ msgstr "" "Введіть тут свою вартість філаменту на кг. Це тільки для статистичної " "інформації." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:478 +#: xs/src/libslic3r/PrintConfig.cpp:575 msgid "money/kg" msgstr "грошових одиниць/кг" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:487 +#: xs/src/libslic3r/PrintConfig.cpp:584 msgid "Fill angle" msgstr "Кут наповнення" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:489 +#: xs/src/libslic3r/PrintConfig.cpp:586 msgid "" "Default base angle for infill orientation. Cross-hatching will be applied to " "this. Bridges will be infilled using the best direction Slic3r can detect, " @@ -1823,31 +2915,61 @@ msgstr "" "застосовуватися крос-штрих. Мости будуть заповнені, використовуючи найкращий " "напрям, який може виявити Slic3r, тому цей параметр на них не впливає." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:501 +#: xs/src/libslic3r/PrintConfig.cpp:598 msgid "Fill density" msgstr "Щільність заповнення" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:503 +#: xs/src/libslic3r/PrintConfig.cpp:600 #, no-c-format msgid "Density of internal infill, expressed in the range 0% - 100%." msgstr "Щільність внутрішнього заповнення, виражена в діапазоні 0% - 100%." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:539 +#: xs/src/libslic3r/PrintConfig.cpp:636 msgid "Fill pattern" msgstr "Шаблон заповнення" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:541 +#: xs/src/libslic3r/PrintConfig.cpp:638 msgid "Fill pattern for general low-density infill." msgstr "Шаблон заповнення для загального низько-швидкісного наповнення." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:573 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:582 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:591 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:625 +#: xs/src/libslic3r/PrintConfig.cpp:655 +msgid "Grid" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:656 +msgid "Triangles" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:657 +msgid "Stars" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:658 +msgid "Cubic" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:659 +msgid "Line" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:661 xs/src/libslic3r/PrintConfig.cpp:1766 +msgid "Honeycomb" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:662 +msgid "3D Honeycomb" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:663 +msgid "Gyroid" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:670 xs/src/libslic3r/PrintConfig.cpp:679 +#: xs/src/libslic3r/PrintConfig.cpp:688 xs/src/libslic3r/PrintConfig.cpp:722 msgid "First layer" msgstr "Перший шар" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:574 +#: xs/src/libslic3r/PrintConfig.cpp:671 msgid "" "This is the acceleration your printer will use for first layer. Set zero to " "disable acceleration control for first layer." @@ -1855,7 +2977,7 @@ msgstr "" "Це прискорення, яке ваш принтер використовуватиме для першого шару. " "Встановити 0, щоб вимкнути керування прискоренням для першого шару." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:583 +#: xs/src/libslic3r/PrintConfig.cpp:680 msgid "" "Heated build plate temperature for the first layer. Set this to zero to " "disable bed temperature control commands in the output." @@ -1863,7 +2985,7 @@ msgstr "" "Температура підігрітої збірної пластини для першого шару. Установіть 0, щоб " "відключити команди керування температурою полотна на виході." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:593 +#: xs/src/libslic3r/PrintConfig.cpp:690 msgid "" "Set this to a non-zero value to set a manual extrusion width for first " "layer. You can use this to force fatter extrudates for better adhesion. If " @@ -1876,11 +2998,11 @@ msgstr "" "(наприклад, 120%), вона буде обчислена за висотою першого шару. Якщо " "встановлено на 0 - використовуватиме стандартну ширину екструзії." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:603 +#: xs/src/libslic3r/PrintConfig.cpp:700 msgid "First layer height" msgstr "Висота першого шару" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:605 +#: xs/src/libslic3r/PrintConfig.cpp:702 msgid "" "When printing with very low layer heights, you might still want to print a " "thicker bottom layer to improve adhesion and tolerance for non perfect build " @@ -1892,17 +3014,16 @@ msgstr "" "до невідповідних збірних пластин. Можна виразити як абсолютне значення або " "як відсоток (наприклад: 150%) по висоті шару за замовчуванням." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:609 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:740 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1390 +#: xs/src/libslic3r/PrintConfig.cpp:706 xs/src/libslic3r/PrintConfig.cpp:837 +#: xs/src/libslic3r/PrintConfig.cpp:1638 msgid "mm or %" msgstr "мм або %" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:615 +#: xs/src/libslic3r/PrintConfig.cpp:712 msgid "First layer speed" msgstr "Швидкість першого шару" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:616 +#: xs/src/libslic3r/PrintConfig.cpp:713 msgid "" "If expressed as absolute value in mm/s, this speed will be applied to all " "the print moves of the first layer, regardless of their type. If expressed " @@ -1913,7 +3034,7 @@ msgstr "" "вона виражена у відсотках (наприклад: 40%), вона буде масштабувати швидкість " "за замовчуванням." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:626 +#: xs/src/libslic3r/PrintConfig.cpp:723 msgid "" "Extruder temperature for first layer. If you want to control temperature " "manually during print, set this to zero to disable temperature control " @@ -1923,13 +3044,13 @@ msgstr "" "температуру вручну під час друку, встановіть 0, щоб вимкнути команди " "керування температурою у вихідному файлі." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:634 -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:145 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:87 +#: xs/src/libslic3r/PrintConfig.cpp:731 +#: xs/src/libslic3r/GCode/PreviewData.cpp:170 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:97 msgid "Gap fill" msgstr "Заповнення розриву" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:636 +#: xs/src/libslic3r/PrintConfig.cpp:733 msgid "" "Speed for filling small gaps using short zigzag moves. Keep this reasonably " "low to avoid too much shaking and resonance issues. Set zero to disable gaps " @@ -1940,11 +3061,11 @@ msgstr "" "надмірних потрясінь та резонансних проблем. Встановити 0, щоб вимкнути " "заповнення розривів." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:644 +#: xs/src/libslic3r/PrintConfig.cpp:741 msgid "Verbose G-code" msgstr "Докладний G-код" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:645 +#: xs/src/libslic3r/PrintConfig.cpp:742 msgid "" "Enable this to get a commented G-code file, with each line explained by a " "descriptive text. If you print from SD card, the additional weight of the " @@ -1954,11 +3075,11 @@ msgstr "" "пояснюється описовим текстом. Якщо ви друкуєте з SD-карти, додаткова вага " "файлу може призвести до уповільнення прошивки." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:652 +#: xs/src/libslic3r/PrintConfig.cpp:749 msgid "G-code flavor" msgstr "Особливість G-коду" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:653 +#: xs/src/libslic3r/PrintConfig.cpp:750 msgid "" "Some G/M-code commands, including temperature control and others, are not " "universal. Set this option to your printer's firmware to get a compatible " @@ -1970,7 +3091,11 @@ msgstr "" "сумісний вихід. \"Відсутність екструзії\" не дозволяє Slic3r експортувати " "будь-яке значення екструзії." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:682 +#: xs/src/libslic3r/PrintConfig.cpp:774 +msgid "No extrusion" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:779 msgid "" "This is the acceleration your printer will use for infill. Set zero to " "disable acceleration control for infill." @@ -1978,11 +3103,11 @@ msgstr "" "Це прискорення, яке ваш принтер використовуватиме для наповнення. Встановити " "0, щоб вимкнути регулятор прискорення для заповнення." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:690 +#: xs/src/libslic3r/PrintConfig.cpp:787 msgid "Combine infill every" msgstr "Об'єднати наповнення кожні" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:692 +#: xs/src/libslic3r/PrintConfig.cpp:789 msgid "" "This feature allows to combine infill and speed up your print by extruding " "thicker infill layers while preserving thin perimeters, thus accuracy." @@ -1990,19 +3115,19 @@ msgstr "" "Ця функція дозволяє поєднувати наповнення та прискорити друк, екструдуючи " "більш товсті шари наповнення, зберігаючи тонкі периметри, а отже, і точністю." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:696 +#: xs/src/libslic3r/PrintConfig.cpp:793 msgid "Combine infill every n layers" msgstr "Об'єднати наповнення кожні n шарів" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:701 +#: xs/src/libslic3r/PrintConfig.cpp:798 msgid "Infill extruder" msgstr "Наповнювач екструдера" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:703 +#: xs/src/libslic3r/PrintConfig.cpp:800 msgid "The extruder to use when printing infill." msgstr "Екструдер, використовуваний під час друку наповнення." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:711 +#: xs/src/libslic3r/PrintConfig.cpp:808 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill. If " "left zero, default extrusion width will be used if set, otherwise 1.125 x " @@ -2017,11 +3142,11 @@ msgstr "" "прискорити наповнення та зміцнити свої деталі. Якщо він виражений у " "відсотках (наприклад, 90%), він буде обчислюватися за висотою шару." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:720 +#: xs/src/libslic3r/PrintConfig.cpp:817 msgid "Infill before perimeters" msgstr "Заповнення перед периметрами" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:721 +#: xs/src/libslic3r/PrintConfig.cpp:818 msgid "" "This option will switch the print order of perimeters and infill, making the " "latter first." @@ -2029,11 +3154,11 @@ msgstr "" "За допомогою цього параметра можна буде змінити порядок друку периметрів та " "наповнювачів, зробивши останнє першим." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:726 +#: xs/src/libslic3r/PrintConfig.cpp:823 msgid "Only infill where needed" msgstr "Заповнити тільки там, де потрібно" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:728 +#: xs/src/libslic3r/PrintConfig.cpp:825 msgid "" "This option will limit infill to the areas actually needed for supporting " "ceilings (it will act as internal support material). If enabled, slows down " @@ -2043,11 +3168,11 @@ msgstr "" "стель (це буде діяти як внутрішній матеріал підтримки). Якщо це ввімкнено, " "сповільнюється генерація G-коду через декілька перевірок." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:735 +#: xs/src/libslic3r/PrintConfig.cpp:832 msgid "Infill/perimeters overlap" msgstr "Перекриття наповнення/периметрів" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:737 +#: xs/src/libslic3r/PrintConfig.cpp:834 msgid "" "This setting applies an additional overlap between infill and perimeters for " "better bonding. Theoretically this shouldn't be needed, but backlash might " @@ -2059,17 +3184,25 @@ msgstr "" "може спричинити розриви. Якщо він виражений у відсотках (приклад: 15%), його " "розраховують за шириною екструзії по периметру." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:748 +#: xs/src/libslic3r/PrintConfig.cpp:845 msgid "Speed for printing the internal fill. Set to zero for auto." msgstr "" "Швидкість друку внутрішнього заповнення. Встановити 0 для автоматичного " "обчислення." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:757 +#: xs/src/libslic3r/PrintConfig.cpp:854 +msgid "Inherits profile" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:855 +msgid "Name of the profile, from which this profile inherits." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:866 msgid "Interface shells" msgstr "Інтерфейсні оболонки" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:758 +#: xs/src/libslic3r/PrintConfig.cpp:867 msgid "" "Force the generation of solid shells between adjacent materials/volumes. " "Useful for multi-extruder prints with translucent materials or manual " @@ -2079,7 +3212,7 @@ msgstr "" "Корисно для друку з багатьма екструдерами з напівпрозорими матеріалами або " "ручним розчинним матеріалом для підтримки." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:767 +#: xs/src/libslic3r/PrintConfig.cpp:876 msgid "" "This custom code is inserted at every layer change, right after the Z move " "and before the extruder moves to the first layer point. Note that you can " @@ -2091,7 +3224,7 @@ msgstr "" "Зауважте, що ви можете використовувати змінні-заповнювачі для всіх " "параметрів Slic3r, а також [layer_num] і [layer_z]." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:779 +#: xs/src/libslic3r/PrintConfig.cpp:888 msgid "" "This setting controls the height (and thus the total number) of the slices/" "layers. Thinner layers give better accuracy but take more time to print." @@ -2099,16 +3232,69 @@ msgstr "" "Цей параметр визначає висоту (і, таким чином, загальну кількість) шарів. " "Тонкі шари забезпечують більшу точність, але для друку потрібно більше часу." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:787 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:796 +#: xs/src/libslic3r/PrintConfig.cpp:896 +msgid "Support silent mode" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:897 +msgid "Set silent mode for the G-code flavor" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:919 +#, c-format +msgid "Maximum feedrate %1%" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:921 +#, c-format +msgid "Maximum feedrate of the %1% axis" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:928 +#, c-format +msgid "Maximum acceleration %1%" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:930 +#, c-format +msgid "Maximum acceleration of the %1% axis" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:937 +#, c-format +msgid "Maximum jerk %1%" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:939 +#, c-format +msgid "Maximum jerk of the %1% axis" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:949 xs/src/libslic3r/PrintConfig.cpp:951 +msgid "Minimum feedrate when extruding" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:959 xs/src/libslic3r/PrintConfig.cpp:961 +msgid "Minimum travel feedrate" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:969 xs/src/libslic3r/PrintConfig.cpp:971 +msgid "Maximum acceleration when extruding" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:979 xs/src/libslic3r/PrintConfig.cpp:981 +msgid "Maximum acceleration when retracting" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:988 xs/src/libslic3r/PrintConfig.cpp:997 msgid "Max" msgstr "Максимально" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:788 +#: xs/src/libslic3r/PrintConfig.cpp:989 msgid "This setting represents the maximum speed of your fan." msgstr "Цей параметр відображає максимальну швидкість вашого вентилятора." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:797 +#: xs/src/libslic3r/PrintConfig.cpp:998 #, no-c-format msgid "" "This is the highest printable layer height for this extruder, used to cap " @@ -2122,11 +3308,21 @@ msgstr "" "для досягнення розумної міжшарової адгезії. Якщо встановлено 0, висота шару " "обмежена 75% діаметра сопла." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:807 +#: xs/src/libslic3r/PrintConfig.cpp:1008 +msgid "Max print height" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1009 +msgid "" +"Set this to the maximum height that can be reached by your extruder while " +"printing." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1015 msgid "Max print speed" msgstr "Максимальна швидкість друку" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:808 +#: xs/src/libslic3r/PrintConfig.cpp:1016 msgid "" "When setting other speed settings to 0 Slic3r will autocalculate the optimal " "speed in order to keep constant extruder pressure. This experimental setting " @@ -2137,7 +3333,7 @@ msgstr "" "екструдера. Цей експериментальний параметр використовується для встановлення " "максимальної швидкості друку, яку ви хочете дозволити." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:818 +#: xs/src/libslic3r/PrintConfig.cpp:1026 msgid "" "This experimental setting is used to set the maximum volumetric speed your " "extruder supports." @@ -2145,12 +3341,11 @@ msgstr "" "Цей експериментальний параметр використовується для встановлення " "максимальної об'ємної швидкості, яку підтримує екструдер." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:826 +#: xs/src/libslic3r/PrintConfig.cpp:1034 msgid "Max volumetric slope positive" msgstr "Максимальний об'ємний нахил позитивний" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:827 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:838 +#: xs/src/libslic3r/PrintConfig.cpp:1035 xs/src/libslic3r/PrintConfig.cpp:1046 msgid "" "This experimental setting is used to limit the speed of change in extrusion " "rate. A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " @@ -2163,27 +3358,25 @@ msgstr "" "швидкість подачі 20 мм/с) до 5,4 мм³/с (подача 60 мм/с) займе принаймні 2 " "секунди." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:831 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:842 +#: xs/src/libslic3r/PrintConfig.cpp:1039 xs/src/libslic3r/PrintConfig.cpp:1050 msgid "mm³/s²" msgstr "мм³/с²" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:837 +#: xs/src/libslic3r/PrintConfig.cpp:1045 msgid "Max volumetric slope negative" msgstr "Максимальний об'ємний схил негативний" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:848 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:857 +#: xs/src/libslic3r/PrintConfig.cpp:1056 xs/src/libslic3r/PrintConfig.cpp:1065 msgid "Min" msgstr "Мінімально" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:849 +#: xs/src/libslic3r/PrintConfig.cpp:1057 msgid "This setting represents the minimum PWM your fan needs to work." msgstr "" "Цей параметр відповідає мінімальній ШІМ, на якій повинен працювати ваш " "вентилятор." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:858 +#: xs/src/libslic3r/PrintConfig.cpp:1066 msgid "" "This is the lowest printable layer height for this extruder and limits the " "resolution for variable layer height. Typical values are between 0.05 mm and " @@ -2193,19 +3386,19 @@ msgstr "" "роздільну здатність для висоти змінного шару. Типові значення - від 0,05 мм " "до 0,1 мм." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:866 +#: xs/src/libslic3r/PrintConfig.cpp:1074 msgid "Min print speed" msgstr "Мінімальна швидкість друку" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:867 +#: xs/src/libslic3r/PrintConfig.cpp:1075 msgid "Slic3r will not scale speed down below this speed." msgstr "Slic3r не буде масштабувати швидкість нижче цієї швидкості." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:874 -msgid "Minimum extrusion length" -msgstr "Мінімальна довжина екструзії" +#: xs/src/libslic3r/PrintConfig.cpp:1082 +msgid "Minimal filament extrusion length" +msgstr "" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:875 +#: xs/src/libslic3r/PrintConfig.cpp:1083 msgid "" "Generate no less than the number of skirt loops required to consume the " "specified amount of filament on the bottom layer. For multi-extruder " @@ -2215,11 +3408,11 @@ msgstr "" "зазначеної кількості філаменту на нижньому шарі. Для машин із декількома " "екструдерами цей мінімум застосовується до кожного екструдера." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:884 +#: xs/src/libslic3r/PrintConfig.cpp:1092 msgid "Configuration notes" msgstr "Примітки до конфігурації" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:885 +#: xs/src/libslic3r/PrintConfig.cpp:1093 msgid "" "You can put here your personal notes. This text will be added to the G-code " "header comments." @@ -2227,20 +3420,20 @@ msgstr "" "Ви можете додати тут свої особисті примітки. Цей текст буде додано до " "коментарів заголовка G-коду." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:894 +#: xs/src/libslic3r/PrintConfig.cpp:1102 msgid "Nozzle diameter" msgstr "Діаметр сопла" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:895 +#: xs/src/libslic3r/PrintConfig.cpp:1103 msgid "" "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)" msgstr "Це діаметр сопла вашого екструдера (наприклад: 0.5, 0.35 тощо)" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:901 +#: xs/src/libslic3r/PrintConfig.cpp:1109 msgid "API Key" msgstr "Ключ API" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:902 +#: xs/src/libslic3r/PrintConfig.cpp:1110 msgid "" "Slic3r can upload G-code files to OctoPrint. This field should contain the " "API Key required for authentication." @@ -2248,23 +3441,21 @@ msgstr "" "Slic3r може завантажувати файли G-коду в OctoPrint. Це поле має містити ключ " "API, необхідний для аутентифікації." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:908 -msgid "Host or IP" -msgstr "Host або IP" +#: xs/src/libslic3r/PrintConfig.cpp:1123 +msgid "Hostname, IP or URL" +msgstr "" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:909 +#: xs/src/libslic3r/PrintConfig.cpp:1124 msgid "" "Slic3r can upload G-code files to OctoPrint. This field should contain the " -"hostname or IP address of the OctoPrint instance." +"hostname, IP address or URL of the OctoPrint instance." msgstr "" -"Slic3r може завантажувати файли G-коду в OctoPrint. Це поле повинно містити " -"ім'я хоста або IP-адресу екземпляру OctoPrint." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:915 +#: xs/src/libslic3r/PrintConfig.cpp:1130 msgid "Only retract when crossing perimeters" msgstr "Перервати тільки у разі перетину периметрів" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:916 +#: xs/src/libslic3r/PrintConfig.cpp:1131 msgid "" "Disables retraction when the travel path does not exceed the upper layer's " "perimeters (and thus any ooze will be probably invisible)." @@ -2272,7 +3463,7 @@ msgstr "" "Вимикає переривання, коли шлях не перевищує периметри верхніх шарів (і, " "таким чином, будь-який розрядник буде, мабуть, невидимим)." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:923 +#: xs/src/libslic3r/PrintConfig.cpp:1138 msgid "" "This option will drop the temperature of the inactive extruders to prevent " "oozing. It will enable a tall skirt automatically and move extruders outside " @@ -2282,11 +3473,11 @@ msgstr "" "протіканню. Це дозволить автоматично ввімкнути високий плінтус та " "перемістить екструдери за межі такого плінтуса у разі зміни температури." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:930 +#: xs/src/libslic3r/PrintConfig.cpp:1145 msgid "Output filename format" msgstr "Формат вихідного файлу" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:931 +#: xs/src/libslic3r/PrintConfig.cpp:1146 msgid "" "You can use all configuration options as variables inside this template. For " "example: [layer_height], [fill_density] etc. You can also use [timestamp], " @@ -2298,11 +3489,11 @@ msgstr "" "можете використовувати [timestamp], [year], [month], [day], [hour], " "[minute], [second], [version], [input_filename] ], [input_filename_base]." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:940 +#: xs/src/libslic3r/PrintConfig.cpp:1155 msgid "Detect bridging perimeters" msgstr "Виявлення висячих периметрів" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:942 +#: xs/src/libslic3r/PrintConfig.cpp:1157 msgid "" "Experimental option to adjust flow for overhangs (bridge flow will be used), " "to apply bridge speed to them and enable fan." @@ -2311,14 +3502,34 @@ msgstr "" "використано мостовий потік), щоб застосувати до них швидкість мосту та " "увімкнути вентилятор." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:948 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:966 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:978 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:988 +#: xs/src/libslic3r/PrintConfig.cpp:1163 +msgid "Filament parking position" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1164 +msgid "" +"Distance of the extruder tip from the position where the filament is parked " +"when unloaded. This should match the value in printer firmware. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1172 +msgid "Extra loading distance" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1173 +msgid "" +"When set to zero, the distance the filament is moved from parking position " +"during load is exactly the same as it was moved back during unload. When " +"positive, it is loaded further, if negative, the loading move is shorter " +"than unloading. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1181 xs/src/libslic3r/PrintConfig.cpp:1199 +#: xs/src/libslic3r/PrintConfig.cpp:1211 xs/src/libslic3r/PrintConfig.cpp:1221 msgid "Perimeters" msgstr "Периметри" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:949 +#: xs/src/libslic3r/PrintConfig.cpp:1182 msgid "" "This is the acceleration your printer will use for perimeters. A high value " "like 9000 usually gives good results if your hardware is up to the job. Set " @@ -2329,18 +3540,18 @@ msgstr "" "забезпечення відповідає завданню. Встановити 0, щоб вимкнути регулятор " "прискорення для периметрів." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:957 +#: xs/src/libslic3r/PrintConfig.cpp:1190 msgid "Perimeter extruder" msgstr "Екструдер периметру" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:959 +#: xs/src/libslic3r/PrintConfig.cpp:1192 msgid "" "The extruder to use when printing perimeters and brim. First extruder is 1." msgstr "" "Екструдер, що використовується при друці периметрів і краю. Перший екструдер " "- 1." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:968 +#: xs/src/libslic3r/PrintConfig.cpp:1201 msgid "" "Set this to a non-zero value to set a manual extrusion width for perimeters. " "You may want to use thinner extrudates to get more accurate surfaces. If " @@ -2355,14 +3566,14 @@ msgstr "" "діаметр сопла. Якщо він виражений у відсотках (наприклад, 200%), він буде " "обчислюватися за висотою шару." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:980 +#: xs/src/libslic3r/PrintConfig.cpp:1213 msgid "" "Speed for perimeters (contours, aka vertical shells). Set to zero for auto." msgstr "" "Швидкість для периметрів (контури, вертикальні оболонки). Встановити 0 для " "автоматичного використання." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:990 +#: xs/src/libslic3r/PrintConfig.cpp:1223 msgid "" "This option sets the number of perimeters to generate for each layer. Note " "that Slic3r may increase this number automatically when it detects sloping " @@ -2374,23 +3585,49 @@ msgstr "" "які отримують вигоду від більшої кількості периметрів, якщо опція «Додаткові " "периметри» увімкнена." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:994 +#: xs/src/libslic3r/PrintConfig.cpp:1227 msgid "(minimum)" msgstr "(мінімум)" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1014 +#: xs/src/libslic3r/PrintConfig.cpp:1247 +msgid "Printer type" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1248 +msgid "Type of the printer." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1252 msgid "Printer notes" msgstr "Примітки принтера" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1015 +#: xs/src/libslic3r/PrintConfig.cpp:1253 msgid "You can put your notes regarding the printer here." msgstr "Тут ви можете помістити свої нотатки щодо принтера." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1029 +#: xs/src/libslic3r/PrintConfig.cpp:1261 +msgid "Printer vendor" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1262 +msgid "Name of the printer vendor." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1266 +msgid "Printer variant" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1267 +msgid "" +"Name of the printer variant. For example, the printer variants may be " +"differentiated by a nozzle diameter." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1277 msgid "Raft layers" msgstr "Плоскі шари" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1031 +#: xs/src/libslic3r/PrintConfig.cpp:1279 msgid "" "The object will be raised by this number of layers, and support material " "will be generated under it." @@ -2398,11 +3635,11 @@ msgstr "" "Об'єкт буде піднятий цією кількістю шарів, і під ним буде згенерований " "матеріал підтримки." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1039 +#: xs/src/libslic3r/PrintConfig.cpp:1287 msgid "Resolution" msgstr "Роздільна здатність" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1040 +#: xs/src/libslic3r/PrintConfig.cpp:1288 msgid "" "Minimum detail resolution, used to simplify the input file for speeding up " "the slicing job and reducing memory usage. High-resolution models often " @@ -2416,20 +3653,20 @@ msgstr "" "вимкнути будь-яке спрощення та використовувати повну роздільну здатність від " "введення." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1050 +#: xs/src/libslic3r/PrintConfig.cpp:1298 msgid "Minimum travel after retraction" msgstr "Мінімальне переміщення після переривання" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1051 +#: xs/src/libslic3r/PrintConfig.cpp:1299 msgid "" "Retraction is not triggered when travel moves are shorter than this length." msgstr "Переривання не спрацьовує, коли переміщення коротше за цю довжину." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1057 +#: xs/src/libslic3r/PrintConfig.cpp:1305 msgid "Retract amount before wipe" msgstr "Кількість переривань перед чищенням" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1058 +#: xs/src/libslic3r/PrintConfig.cpp:1306 msgid "" "With bowden extruders, it may be wise to do some amount of quick retract " "before doing the wipe movement." @@ -2437,26 +3674,25 @@ msgstr "" "Завдяки екструдерам з бандами, має зміст зробити певну кількість переривань " "перед рухами очищення." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1065 +#: xs/src/libslic3r/PrintConfig.cpp:1313 msgid "Retract on layer change" msgstr "Переривання на зміну шарів" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1066 +#: xs/src/libslic3r/PrintConfig.cpp:1314 msgid "This flag enforces a retraction whenever a Z move is done." msgstr "" "Цей прапор забезпечує переривання кожного разу, коли виконується переміщення " "Z." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1071 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1080 +#: xs/src/libslic3r/PrintConfig.cpp:1319 xs/src/libslic3r/PrintConfig.cpp:1328 msgid "Length" msgstr "Довжина" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1072 +#: xs/src/libslic3r/PrintConfig.cpp:1320 msgid "Retraction Length" msgstr "Довжина переривання" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1073 +#: xs/src/libslic3r/PrintConfig.cpp:1321 msgid "" "When retraction is triggered, filament is pulled back by the specified " "amount (the length is measured on raw filament, before it enters the " @@ -2466,16 +3702,15 @@ msgstr "" "кількості (довжина вимірюється на сирого філаменту перед тим, як вона " "надходить у екструдер)." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1075 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1085 +#: xs/src/libslic3r/PrintConfig.cpp:1323 xs/src/libslic3r/PrintConfig.cpp:1333 msgid "mm (zero to disable)" msgstr "мм (0, щоб вимкнути)" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1081 +#: xs/src/libslic3r/PrintConfig.cpp:1329 msgid "Retraction Length (Toolchange)" msgstr "Довжина переривання (зміна інструмента)" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1082 +#: xs/src/libslic3r/PrintConfig.cpp:1330 msgid "" "When retraction is triggered before changing tool, filament is pulled back " "by the specified amount (the length is measured on raw filament, before it " @@ -2485,11 +3720,11 @@ msgstr "" "назад до вказаної кількості (довжина вимірюється на сирого філаменту перед " "тим, як вона надходить у екструдер)." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1090 +#: xs/src/libslic3r/PrintConfig.cpp:1338 msgid "Lift Z" msgstr "Підняти Z" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1091 +#: xs/src/libslic3r/PrintConfig.cpp:1339 msgid "" "If you set this to a positive value, Z is quickly raised every time a " "retraction is triggered. When using multiple extruders, only the setting for " @@ -2499,15 +3734,15 @@ msgstr "" "коли спрацьовує переривання. При використанні декількох екструдерів буде " "розглянуто налаштування лише першого екструдера." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1099 +#: xs/src/libslic3r/PrintConfig.cpp:1347 msgid "Above Z" msgstr "Вище Z" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1100 +#: xs/src/libslic3r/PrintConfig.cpp:1348 msgid "Only lift Z above" msgstr "Тільки піднімати Z" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1101 +#: xs/src/libslic3r/PrintConfig.cpp:1349 msgid "" "If you set this to a positive value, Z lift will only take place above the " "specified absolute Z. You can tune this setting for skipping lift on the " @@ -2517,15 +3752,15 @@ msgstr "" "вказаним абсолютним Z. Ви можете налаштувати цей параметр так, що підняття " "буде пропускатися на перших шарах." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1108 +#: xs/src/libslic3r/PrintConfig.cpp:1356 msgid "Below Z" msgstr "Нижче Z" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1109 +#: xs/src/libslic3r/PrintConfig.cpp:1357 msgid "Only lift Z below" msgstr "Тільки опускати Z" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1110 +#: xs/src/libslic3r/PrintConfig.cpp:1358 msgid "" "If you set this to a positive value, Z lift will only take place below the " "specified absolute Z. You can tune this setting for limiting lift to the " @@ -2535,12 +3770,11 @@ msgstr "" "вказаного абсолютного Z. Ви можете налаштувати цей параметр так, що підняття " "буде обмежене на перших шарах." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1118 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1126 +#: xs/src/libslic3r/PrintConfig.cpp:1366 xs/src/libslic3r/PrintConfig.cpp:1374 msgid "Extra length on restart" msgstr "Додаткова довжина при перезапуску" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1119 +#: xs/src/libslic3r/PrintConfig.cpp:1367 msgid "" "When the retraction is compensated after the travel move, the extruder will " "push this additional amount of filament. This setting is rarely needed." @@ -2548,7 +3782,7 @@ msgstr "" "Коли переривання компенсується після руху переміщення, екструдер буде " "проштовхувати цю додаткову кількість філамента. Цей параметр рідко потрібний." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1127 +#: xs/src/libslic3r/PrintConfig.cpp:1375 msgid "" "When the retraction is compensated after changing tool, the extruder will " "push this additional amount of filament." @@ -2556,21 +3790,19 @@ msgstr "" "Коли переривання компенсується після зміни інструмента, екструдер буде " "проштовхувати цю додаткову кількість філамента." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1134 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1135 +#: xs/src/libslic3r/PrintConfig.cpp:1382 xs/src/libslic3r/PrintConfig.cpp:1383 msgid "Retraction Speed" msgstr "Швидкість переривання" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1136 +#: xs/src/libslic3r/PrintConfig.cpp:1384 msgid "The speed for retractions (it only applies to the extruder motor)." msgstr "Швидкість переривання (це стосується лише двигуна екструдера)." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1142 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1143 +#: xs/src/libslic3r/PrintConfig.cpp:1390 xs/src/libslic3r/PrintConfig.cpp:1391 msgid "Deretraction Speed" msgstr "Швидкість після-переривання" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1144 +#: xs/src/libslic3r/PrintConfig.cpp:1392 msgid "" "The speed for loading of a filament into extruder after retraction (it only " "applies to the extruder motor). If left to zero, the retraction speed is " @@ -2580,55 +3812,71 @@ msgstr "" "лише двигуна екструдера ). Якщо залишити 0, використовується швидкість " "переривання ." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1151 +#: xs/src/libslic3r/PrintConfig.cpp:1399 msgid "Seam position" msgstr "Позиція шва" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1153 +#: xs/src/libslic3r/PrintConfig.cpp:1401 msgid "Position of perimeters starting points." msgstr "Позиція стартових точок периметра." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1169 +#: xs/src/libslic3r/PrintConfig.cpp:1408 +msgid "Random" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1409 +msgid "Nearest" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1410 +msgid "Aligned" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1411 lib/Slic3r/GUI/MainFrame.pm:330 +msgid "Rear" +msgstr "Ззаду" + +#: xs/src/libslic3r/PrintConfig.cpp:1417 msgid "Direction" msgstr "Напрямок" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1171 +#: xs/src/libslic3r/PrintConfig.cpp:1419 msgid "Preferred direction of the seam" msgstr "Бажаний напрямок шва" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1172 +#: xs/src/libslic3r/PrintConfig.cpp:1420 msgid "Seam preferred direction" msgstr "Бажаний напрямок шва" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1180 +#: xs/src/libslic3r/PrintConfig.cpp:1428 msgid "Jitter" msgstr "Джиттер" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1182 +#: xs/src/libslic3r/PrintConfig.cpp:1430 msgid "Seam preferred direction jitter" msgstr "Бажаний напрямок шва джитера" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1183 +#: xs/src/libslic3r/PrintConfig.cpp:1431 msgid "Preferred direction of the seam - jitter" msgstr "Бажаний напрямок шва - джитера" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1194 +#: xs/src/libslic3r/PrintConfig.cpp:1442 msgid "USB/serial port for printer connection." msgstr "USB / послідовний порт для підключення принтера." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1202 +#: xs/src/libslic3r/PrintConfig.cpp:1450 msgid "Serial port speed" msgstr "Швидкість послідовного порту" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1203 +#: xs/src/libslic3r/PrintConfig.cpp:1451 msgid "Speed (baud) of USB/serial port for printer connection." msgstr "Швидкість (бод) USB / послідовного порту для підключення принтера." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1212 +#: xs/src/libslic3r/PrintConfig.cpp:1460 msgid "Distance from object" msgstr "Відстань від об'єкту" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1213 +#: xs/src/libslic3r/PrintConfig.cpp:1461 msgid "" "Distance between skirt and object(s). Set this to zero to attach the skirt " "to the object(s) and get a brim for better adhesion." @@ -2636,11 +3884,11 @@ msgstr "" "Відстань між плінтусом та об'єктом (-ами). Установіть 0, щоб прикріпити " "плінтус до об'єкта (-ів) і отримати край для кращої адгезії." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1221 +#: xs/src/libslic3r/PrintConfig.cpp:1469 msgid "Skirt height" msgstr "Висота плінтусу" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1222 +#: xs/src/libslic3r/PrintConfig.cpp:1470 msgid "" "Height of skirt expressed in layers. Set this to a tall value to use skirt " "as a shield against drafts." @@ -2648,15 +3896,15 @@ msgstr "" "Висота плінтусу виражена в шарах. Встановіть це значення на високе, щоб " "використовувати плінтус як щит проти протягів." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1229 +#: xs/src/libslic3r/PrintConfig.cpp:1477 msgid "Loops (minimum)" msgstr "Петлі (мінімум)" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1230 +#: xs/src/libslic3r/PrintConfig.cpp:1478 msgid "Skirt Loops" msgstr "Петлі плінтусу" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1231 +#: xs/src/libslic3r/PrintConfig.cpp:1479 msgid "" "Number of loops for the skirt. If the Minimum Extrusion Length option is " "set, the number of loops might be greater than the one configured here. Set " @@ -2666,11 +3914,11 @@ msgstr "" "довжина екструзії\", кількість петель може бути більшою, ніж налаштована " "тут. Установіть 0, щоб повністю вимкнути плінтус." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1239 +#: xs/src/libslic3r/PrintConfig.cpp:1487 msgid "Slow down if layer print time is below" msgstr "Уповільнення, якщо час друку шару нижче" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1240 +#: xs/src/libslic3r/PrintConfig.cpp:1488 msgid "" "If layer print time is estimated below this number of seconds, print moves " "speed will be scaled down to extend duration to this value." @@ -2678,11 +3926,11 @@ msgstr "" "Якщо час друку шару оцінюється нижче цієї кількості секунд, швидкість друку " "рухів зменшуватиметься, щоб збільшити тривалість до цього значення." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1250 +#: xs/src/libslic3r/PrintConfig.cpp:1498 msgid "Small perimeters" msgstr "Маленькі периметри" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1252 +#: xs/src/libslic3r/PrintConfig.cpp:1500 msgid "" "This separate setting will affect the speed of perimeters having radius <= " "6.5mm (usually holes). If expressed as percentage (for example: 80%) it will " @@ -2693,11 +3941,11 @@ msgstr "" "вона буде розрахована за наведеним вище параметром швидкості. Встановити 0 " "для автоматичного використання." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1262 +#: xs/src/libslic3r/PrintConfig.cpp:1510 msgid "Solid infill threshold area" msgstr "Порогова площа суцільного наповнення" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1264 +#: xs/src/libslic3r/PrintConfig.cpp:1512 msgid "" "Force solid infill for regions having a smaller area than the specified " "threshold." @@ -2705,23 +3953,23 @@ msgstr "" "Встановити суцільне заповнення для регіонів, що мають площу, меншу " "зазначеного порогу." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1265 +#: xs/src/libslic3r/PrintConfig.cpp:1513 msgid "mm²" msgstr "мм²" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1271 +#: xs/src/libslic3r/PrintConfig.cpp:1519 msgid "Solid infill extruder" msgstr "Екструдер суцільних наповнень" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1273 +#: xs/src/libslic3r/PrintConfig.cpp:1521 msgid "The extruder to use when printing solid infill." msgstr "Екструдер для друку суцільних наповнень." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1279 +#: xs/src/libslic3r/PrintConfig.cpp:1527 msgid "Solid infill every" msgstr "Суцільне наповнення кожні" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1281 +#: xs/src/libslic3r/PrintConfig.cpp:1529 msgid "" "This feature allows to force a solid layer every given number of layers. " "Zero to disable. You can set this to any value (for example 9999); Slic3r " @@ -2733,14 +3981,13 @@ msgstr "" "Slic3r автоматично вибере максимально можливу кількість шарів для " "комбінування відповідно до діаметра сопла та висоти шару." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1291 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1301 -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:142 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:84 +#: xs/src/libslic3r/PrintConfig.cpp:1539 xs/src/libslic3r/PrintConfig.cpp:1549 +#: xs/src/libslic3r/GCode/PreviewData.cpp:167 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:94 msgid "Solid infill" msgstr "Суцільне наповнення" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1293 +#: xs/src/libslic3r/PrintConfig.cpp:1541 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill for " "solid surfaces. If left zero, default extrusion width will be used if set, " @@ -2753,7 +4000,7 @@ msgstr "" "діаметр сопла. Якщо він виражений у відсотках (наприклад, 90%), він буде " "обчислюватися за висотою шару." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1303 +#: xs/src/libslic3r/PrintConfig.cpp:1551 msgid "" "Speed for printing solid regions (top/bottom/internal horizontal shells). " "This can be expressed as a percentage (for example: 80%) over the default " @@ -2764,16 +4011,16 @@ msgstr "" "швидкості заповнення за замовчуванням. Встановити 0 для автоматичного " "використання." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1315 +#: xs/src/libslic3r/PrintConfig.cpp:1563 msgid "Number of solid layers to generate on top and bottom surfaces." msgstr "" "Кількість суцільних шарів для генерування на верхній і нижній поверхні." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1322 +#: xs/src/libslic3r/PrintConfig.cpp:1570 msgid "Spiral vase" msgstr "Спіральна ваза" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1323 +#: xs/src/libslic3r/PrintConfig.cpp:1571 msgid "" "This feature will raise Z gradually while printing a single-walled object in " "order to remove any visible seam. This option requires a single perimeter, " @@ -2788,11 +4035,11 @@ msgstr "" "яку кількість нижніх суцільних шарів, а також петель плінтусу/краю. Це не " "спрацює при друку більше, ніж одного об'єкта." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1332 +#: xs/src/libslic3r/PrintConfig.cpp:1580 msgid "Temperature variation" msgstr "Варіація температури" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1333 +#: xs/src/libslic3r/PrintConfig.cpp:1581 msgid "" "Temperature difference to be applied when an extruder is not active. Enables " "a full-height \"sacrificial\" skirt on which the nozzles are periodically " @@ -2801,7 +4048,7 @@ msgstr "" "Відмітка температури, яка застосовується, коли екструдер не активний. Вмикає " "\"жертовний\" плінтус на повній висоті, на які періодично очищуються сопла." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1343 +#: xs/src/libslic3r/PrintConfig.cpp:1591 msgid "" "This start procedure is inserted at the beginning, after bed has reached the " "target temperature and extruder just started heating, and before extruder " @@ -2820,7 +4067,7 @@ msgstr "" "заповнювачі для всіх параметрів Slic3r, щоб ви могли поставити команду " "\"M109 S [first_layer_temperature]\" де завгодно." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1358 +#: xs/src/libslic3r/PrintConfig.cpp:1606 msgid "" "This start procedure is inserted at the beginning, after any printer start " "gcode. This is used to override settings for a specific filament. If Slic3r " @@ -2841,27 +4088,27 @@ msgstr "" "де завгодно. Якщо у вас є кілька екструдерів, G-code обробляється в порядку " "екструдерів." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1373 +#: xs/src/libslic3r/PrintConfig.cpp:1621 msgid "Single Extruder Multi Material" msgstr "Одиночний екструдер кількох матеріалів" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1374 +#: xs/src/libslic3r/PrintConfig.cpp:1622 msgid "The printer multiplexes filaments into a single hot end." msgstr "Принтер мультиплексує нитки в єдиний гарячий кінець." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1379 +#: xs/src/libslic3r/PrintConfig.cpp:1627 msgid "Generate support material" msgstr "Створити підтримуючий матеріал" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1381 +#: xs/src/libslic3r/PrintConfig.cpp:1629 msgid "Enable support material generation." msgstr "Увімкнути генерацію матеріалів підтримки." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1386 +#: xs/src/libslic3r/PrintConfig.cpp:1634 msgid "XY separation between an object and its support" msgstr "Розподіл XY між об'єктом та його підтримкою" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1388 +#: xs/src/libslic3r/PrintConfig.cpp:1636 msgid "" "XY separation between an object and its support. If expressed as percentage " "(for example 50%), it will be calculated over external perimeter width." @@ -2869,11 +4116,11 @@ msgstr "" "Розподіл XY між об'єктом та його підтримкою. Якщо вона виражена у відсотках " "(наприклад, 50%), вона буде розрахована за зовнішньою шириною периметру." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1398 +#: xs/src/libslic3r/PrintConfig.cpp:1646 msgid "Pattern angle" msgstr "Кут шаблону" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1400 +#: xs/src/libslic3r/PrintConfig.cpp:1648 msgid "" "Use this setting to rotate the support material pattern on the horizontal " "plane." @@ -2881,11 +4128,7 @@ msgstr "" "Використовуйте цей параметр, щоб повернути шаблон підтримуючого матеріалу на " "горизонтальній площині." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1408 -msgid "Support on build plate only" -msgstr "Підтримка тільки на збірній пластині" - -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1410 +#: xs/src/libslic3r/PrintConfig.cpp:1658 msgid "" "Only create support if it lies on a build plate. Don't create support on a " "print." @@ -2893,11 +4136,11 @@ msgstr "" "Створити підтримку лише, для того, що лежить на збірній пластині. Не " "створювати підтримку на друк." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1416 +#: xs/src/libslic3r/PrintConfig.cpp:1664 msgid "Contact Z distance" msgstr "Контактна відстань по осі Z" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1418 +#: xs/src/libslic3r/PrintConfig.cpp:1666 msgid "" "The vertical distance between object and support material interface. Setting " "this to 0 will also prevent Slic3r from using bridge flow and speed for the " @@ -2907,11 +4150,19 @@ msgstr "" "Встановлення значення 0 також захистить Slic3r від використання потоку " "мостів та швидкості для першого шару об'єктну." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1431 +#: xs/src/libslic3r/PrintConfig.cpp:1674 +msgid "soluble" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1675 +msgid "detachable" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1679 msgid "Enforce support for the first" msgstr "Забезпечити підтримку першого(их)" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1433 +#: xs/src/libslic3r/PrintConfig.cpp:1681 msgid "" "Generate support material for the specified number of layers counting from " "bottom, regardless of whether normal support material is enabled or not and " @@ -2924,15 +4175,15 @@ msgstr "" "більшої адгезії об'єктів, що мають дуже тонкий або поганий слід на збірній " "пластині." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1439 +#: xs/src/libslic3r/PrintConfig.cpp:1687 msgid "Enforce support for the first n layers" msgstr "Забезпечити підтримку перших n шарів" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1444 +#: xs/src/libslic3r/PrintConfig.cpp:1692 msgid "Support material/raft/skirt extruder" msgstr "Підтримуючий матеріал / пліт / плінтус екструдеру" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1446 +#: xs/src/libslic3r/PrintConfig.cpp:1694 msgid "" "The extruder to use when printing support material, raft and skirt (1+, 0 to " "use the current extruder to minimize tool changes)." @@ -2940,7 +4191,7 @@ msgstr "" "Екструдер для друку підтримуючого матеріалу, плоту та плінтусу (1+, 0 для " "використання поточного екструдера, щоб мінімізувати зміни інструменту)." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1455 +#: xs/src/libslic3r/PrintConfig.cpp:1703 msgid "" "Set this to a non-zero value to set a manual extrusion width for support " "material. If left zero, default extrusion width will be used if set, " @@ -2953,21 +4204,21 @@ msgstr "" "Якщо він виражений у відсотках (наприклад, 90%), він буде обчислюватися за " "висотою шару." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1463 +#: xs/src/libslic3r/PrintConfig.cpp:1711 msgid "Interface loops" msgstr "Інтерфейсні петлі" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1465 +#: xs/src/libslic3r/PrintConfig.cpp:1713 msgid "" "Cover the top contact layer of the supports with loops. Disabled by default." msgstr "" "Закрити петлями верхній контактний шар підтримки. За замовчанням вимкнено." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1470 +#: xs/src/libslic3r/PrintConfig.cpp:1718 msgid "Support material/raft interface extruder" msgstr "Екструдер інтерфейсу підтримуючого матеріалу / плоту" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1472 +#: xs/src/libslic3r/PrintConfig.cpp:1720 msgid "" "The extruder to use when printing support material interface (1+, 0 to use " "the current extruder to minimize tool changes). This affects raft too." @@ -2976,11 +4227,11 @@ msgstr "" "(1+, 0 для використання поточного екструдера, щоб звести до мінімуму зміни " "інструменту). Це також впливає на плот." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1479 +#: xs/src/libslic3r/PrintConfig.cpp:1727 msgid "Interface layers" msgstr "Інтерфейсні шари" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1481 +#: xs/src/libslic3r/PrintConfig.cpp:1729 msgid "" "Number of interface layers to insert between the object(s) and support " "material." @@ -2988,23 +4239,23 @@ msgstr "" "Кількість шарів інтерфейсу для вставки між об'єктом(ами) та підтримуючим " "матеріалом." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1488 +#: xs/src/libslic3r/PrintConfig.cpp:1736 msgid "Interface pattern spacing" msgstr "Відстань між шаблонами інтерфейсу" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1490 +#: xs/src/libslic3r/PrintConfig.cpp:1738 msgid "Spacing between interface lines. Set zero to get a solid interface." msgstr "" "Відстань між інтерфейсними лініями. Встановити 0, щоб отримати надійний " "інтерфейс." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1497 -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:148 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:90 +#: xs/src/libslic3r/PrintConfig.cpp:1745 +#: xs/src/libslic3r/GCode/PreviewData.cpp:173 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:100 msgid "Support material interface" msgstr "Інтерфейс підтримуючого матеріалу" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1499 +#: xs/src/libslic3r/PrintConfig.cpp:1747 msgid "" "Speed for printing support material interface layers. If expressed as " "percentage (for example 50%) it will be calculated over support material " @@ -3014,31 +4265,35 @@ msgstr "" "виражена у відсотках (наприклад, 50%), вона буде розрахована за швидкістю " "матеріалу підтримки." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1508 +#: xs/src/libslic3r/PrintConfig.cpp:1756 msgid "Pattern" msgstr "Шаблон" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1510 +#: xs/src/libslic3r/PrintConfig.cpp:1758 msgid "Pattern used to generate support material." msgstr "Шаблон, що використовується для створення матеріалу підтримки." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1524 +#: xs/src/libslic3r/PrintConfig.cpp:1765 +msgid "Rectilinear grid" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1770 msgid "Pattern spacing" msgstr "Відстань між шаблонами" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1526 +#: xs/src/libslic3r/PrintConfig.cpp:1772 msgid "Spacing between support material lines." msgstr "Відстань між лініями підтримуючого матеріалу." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1535 +#: xs/src/libslic3r/PrintConfig.cpp:1781 msgid "Speed for printing support material." msgstr "Швидкість друку підтримуючого матеріалу." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1542 +#: xs/src/libslic3r/PrintConfig.cpp:1788 msgid "Synchronize with object layers" msgstr "Синхронізувати з шарами об'єкту" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1544 +#: xs/src/libslic3r/PrintConfig.cpp:1790 msgid "" "Synchronize support layers with the object print layers. This is useful with " "multi-material printers, where the extruder switch is expensive." @@ -3047,11 +4302,11 @@ msgstr "" "використовувати з багато-матеріальними принтерами, де перемикання " "екструдерів -затратна процедура." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1550 +#: xs/src/libslic3r/PrintConfig.cpp:1796 msgid "Overhang threshold" msgstr "Порог нависання" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1552 +#: xs/src/libslic3r/PrintConfig.cpp:1798 msgid "" "Support material will not be generated for overhangs whose slope angle (90° " "= vertical) is above the given threshold. In other words, this value " @@ -3065,11 +4320,11 @@ msgstr "" "площини), який ви можете надрукувати без підтримуючого матеріалу. Встановити " "0 для автоматичного визначення (рекомендовано)." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1564 +#: xs/src/libslic3r/PrintConfig.cpp:1810 msgid "With sheath around the support" msgstr "З оболонкою навколо підтримки" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1566 +#: xs/src/libslic3r/PrintConfig.cpp:1812 msgid "" "Add a sheath (a single perimeter line) around the base support. This makes " "the support more reliable, but also more difficult to remove." @@ -3077,7 +4332,7 @@ msgstr "" "Додати оболонку (одну лінію периметра) навколо базової підтримки. Це робить " "підтримку більш надійною, але її важче видалити." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1573 +#: xs/src/libslic3r/PrintConfig.cpp:1819 msgid "" "Extruder temperature for layers after the first one. Set this to zero to " "disable temperature control commands in the output." @@ -3085,15 +4340,15 @@ msgstr "" "Температура екструдеру для шарів після першого. Установіть 0, щоб вимкнути " "команди керування температурою на виході." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1576 +#: xs/src/libslic3r/PrintConfig.cpp:1822 msgid "Temperature" msgstr "Температура" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1582 +#: xs/src/libslic3r/PrintConfig.cpp:1828 msgid "Detect thin walls" msgstr "Виявлення тонких стін" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1584 +#: xs/src/libslic3r/PrintConfig.cpp:1830 msgid "" "Detect single-width walls (parts where two extrusions don't fit and we need " "to collapse them into a single trace)." @@ -3101,11 +4356,11 @@ msgstr "" "Визначення одношарової стінки (частини, де два екструзії не підходять, і нам " "потрібно згорнути їх у єдиний слід)." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1590 +#: xs/src/libslic3r/PrintConfig.cpp:1836 msgid "Threads" msgstr "Нитки" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1591 +#: xs/src/libslic3r/PrintConfig.cpp:1837 msgid "" "Threads are used to parallelize long-running tasks. Optimal threads number " "is slightly above the number of available cores/processors." @@ -3113,7 +4368,7 @@ msgstr "" "Нитки використовуються для паралелізації довготривалих завдань. Оптимальна " "кількість ниток трохи перевищує кількість доступних ядер / процесорів." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1603 +#: xs/src/libslic3r/PrintConfig.cpp:1849 msgid "" "This custom code is inserted right before every extruder change. Note that " "you can use placeholder variables for all Slic3r settings as well as " @@ -3123,14 +4378,13 @@ msgstr "" "екструдера. Зверніть увагу, що ви можете використовувати змінні-заповнювачі " "для всіх параметрів Slic3r, а також [previous_extruder] і [next_extruder]." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1613 -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1624 -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:143 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:85 +#: xs/src/libslic3r/PrintConfig.cpp:1859 xs/src/libslic3r/PrintConfig.cpp:1870 +#: xs/src/libslic3r/GCode/PreviewData.cpp:168 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:95 msgid "Top solid infill" msgstr "Верхнє суцільне наповнення" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1615 +#: xs/src/libslic3r/PrintConfig.cpp:1861 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill for " "top surfaces. You may want to use thinner extrudates to fill all narrow " @@ -3146,7 +4400,7 @@ msgstr "" "виражена у відсотках (наприклад, 90%), вона буде обчислюватися за висотою " "шару." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1626 +#: xs/src/libslic3r/PrintConfig.cpp:1872 msgid "" "Speed for printing top solid layers (it only applies to the uppermost " "external layers and not to their internal solid layers). You may want to " @@ -3160,33 +4414,31 @@ msgstr "" "відсотком (наприклад, 80%) звищення швидкості щільного наповнення . " "Встановити 0 для автоматичного обчислення." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1638 -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:369 +#: xs/src/libslic3r/PrintConfig.cpp:1884 lib/Slic3r/GUI/MainFrame.pm:327 msgid "Top" msgstr "Зверху" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1640 +#: xs/src/libslic3r/PrintConfig.cpp:1886 msgid "Number of solid layers to generate on top surfaces." msgstr "Кількість суцільних шарів, генерованих на верхніх поверхнях." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1642 +#: xs/src/libslic3r/PrintConfig.cpp:1888 msgid "Top solid layers" msgstr "Верхні суцільні шари" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1647 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:95 +#: xs/src/libslic3r/PrintConfig.cpp:1893 lib/Slic3r/GUI/Plater/3DPreview.pm:105 msgid "Travel" msgstr "Пересування" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1648 +#: xs/src/libslic3r/PrintConfig.cpp:1894 msgid "Speed for travel moves (jumps between distant extrusion points)." msgstr "Швидкість рухів пересування (стрибки між далекими точками екструзії)." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1656 +#: xs/src/libslic3r/PrintConfig.cpp:1902 msgid "Use firmware retraction" msgstr "Використовувати відмову прошивки" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1657 +#: xs/src/libslic3r/PrintConfig.cpp:1903 msgid "" "This experimental setting uses G10 and G11 commands to have the firmware " "handle the retraction. This is only supported in recent Marlin." @@ -3194,11 +4446,11 @@ msgstr "" "Цей експериментальний параметр використовує команди G10 і G11 для обробки " "відмови прошивки. Останнім часом це підтримується лише Marlin-ом." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1663 +#: xs/src/libslic3r/PrintConfig.cpp:1909 msgid "Use relative E distances" msgstr "Використовувати відносні E відстані" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1664 +#: xs/src/libslic3r/PrintConfig.cpp:1910 msgid "" "If your firmware requires relative E values, check this, otherwise leave it " "unchecked. Most firmwares use absolute values." @@ -3206,11 +4458,11 @@ msgstr "" "Якщо ваша прошивка потребує відносне значення E, зазначте це, інакше залиште " "його незазначеним. Більшість прошивок використовують абсолютні значення." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1670 +#: xs/src/libslic3r/PrintConfig.cpp:1916 msgid "Use volumetric E" msgstr "Використовувати об'ємний Е" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1671 +#: xs/src/libslic3r/PrintConfig.cpp:1917 msgid "" "This experimental setting uses outputs the E values in cubic millimeters " "instead of linear millimeters. If your firmware doesn't already know " @@ -3226,11 +4478,11 @@ msgstr "" "режим і використовувати діаметр нитки, пов'язаний з вибраною ниткою в " "Slic3r. Останнім часом це підтримується лише Marlin-ом." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1681 +#: xs/src/libslic3r/PrintConfig.cpp:1927 msgid "Enable variable layer height feature" msgstr "Увімкнути функцію шарів змінної висоти" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1682 +#: xs/src/libslic3r/PrintConfig.cpp:1928 msgid "" "Some printers or printer setups may have difficulties printing with a " "variable layer height. Enabled by default." @@ -3238,11 +4490,11 @@ msgstr "" "Деякі принтери або налаштування принтера можуть мати труднощі з друкуванням " "шарів змінної висоти. Увімкнено за умовчанням." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1688 +#: xs/src/libslic3r/PrintConfig.cpp:1934 msgid "Wipe while retracting" msgstr "Вичіщувати при відмові" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1689 +#: xs/src/libslic3r/PrintConfig.cpp:1935 msgid "" "This flag will move the nozzle while retracting to minimize the possible " "blob on leaky extruders." @@ -3250,7 +4502,7 @@ msgstr "" "Цей прапорець перемістить сопло під час відмови, щоб мінімізувати можливість " "утворення краплі на витікаючих екструдерах." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1696 +#: xs/src/libslic3r/PrintConfig.cpp:1942 msgid "" "Multi material printers may need to prime or purge extruders on tool " "changes. Extrude the excess material into the wipe tower." @@ -3259,50 +4511,98 @@ msgstr "" "екструдерів при зміні інструмента. Екструдуйте надлишок матеріалу до " "вичищуючої вежі." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1702 +#: xs/src/libslic3r/PrintConfig.cpp:1948 +msgid "Purging volumes - load/unload volumes" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1949 +msgid "" +"This vector saves required volumes to change from/to each tool used on the " +"wipe tower. These values are used to simplify creation of the full purging " +"volumes below. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1956 +msgid "Purging volumes - matrix" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1957 +msgid "" +"This matrix describes volumes (in cubic milimetres) required to purge the " +"new filament on the wipe tower for any given pair of tools. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1967 msgid "Position X" msgstr "Позиція X" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1703 +#: xs/src/libslic3r/PrintConfig.cpp:1968 msgid "X coordinate of the left front corner of a wipe tower" msgstr "X координата лівого переднього кута вичищуючої вежі" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1709 +#: xs/src/libslic3r/PrintConfig.cpp:1974 msgid "Position Y" msgstr "Позиція Y" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1710 +#: xs/src/libslic3r/PrintConfig.cpp:1975 msgid "Y coordinate of the left front corner of a wipe tower" msgstr "Y координата лівого переднього кута вичищуючої вежі" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1716 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:70 +#: xs/src/libslic3r/PrintConfig.cpp:1981 lib/Slic3r/GUI/Plater/3DPreview.pm:76 msgid "Width" msgstr "Ширина" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1717 +#: xs/src/libslic3r/PrintConfig.cpp:1982 msgid "Width of a wipe tower" msgstr "Ширина вичищуючої вежі" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1723 -msgid "Per color change depth" -msgstr "Змінити глибину за кольором" - -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1724 -msgid "" -"Depth of a wipe color per color change. For N colors, there will be maximum " -"(N-1) tool switches performed, therefore the total depth of the wipe tower " -"will be (N-1) times this value." +#: xs/src/libslic3r/PrintConfig.cpp:1988 +msgid "Wipe tower rotation angle" msgstr "" -"Глибина вичищення кольору для кожної зміни кольору. Для N кольорів буде " -"виконано максимум (N-1) інструментальних перемикачів, тому загальна глибина " -"вичищуючої вежі буде (N-1) разів до цього значення." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1732 +#: xs/src/libslic3r/PrintConfig.cpp:1989 +msgid "Wipe tower rotation angle with respect to x-axis " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1990 +msgid "degrees" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1996 +msgid "Purging into infill" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1997 +msgid "" +"Wiping after toolchange will be preferentially done inside infills. This " +"lowers the amount of waste but may result in longer print time due to " +"additional travel moves." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:2005 +msgid "Purging into objects" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:2006 +msgid "" +"Objects will be used to wipe the nozzle after a toolchange to save material " +"that would otherwise end up in the wipe tower and decrease print time. " +"Colours of the objects will be mixed as a result." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:2013 +msgid "Maximal bridging distance" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:2014 +msgid "Maximal distance between supports on sparse infill sections. " +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:2020 msgid "XY Size Compensation" msgstr "Зрівноваження розміру за XY" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1734 +#: xs/src/libslic3r/PrintConfig.cpp:2022 msgid "" "The object will be grown/shrunk in the XY plane by the configured value " "(negative = inwards, positive = outwards). This might be useful for fine-" @@ -3312,11 +4612,11 @@ msgstr "" "(негативний - внутрішній, позитивний - ззовнішній). Це може бути корисним " "для точного налаштування розмірів отворів." -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1742 +#: xs/src/libslic3r/PrintConfig.cpp:2030 msgid "Z offset" msgstr "Зміщення Z" -#: C:\src\Slic3r\xs\src\libslic3r\PrintConfig.cpp:1743 +#: xs/src/libslic3r/PrintConfig.cpp:2031 msgid "" "This value will be added (or subtracted) from all the Z coordinates in the " "output G-code. It is used to compensate for bad Z endstop position: for " @@ -3328,678 +4628,608 @@ msgstr "" "наприклад, якщо ваш кінцевий нуль фактично залишає сопло на 0,3 мм від " "полотна друку, встановіть його на значення -0,3 (або виправте ваш endstop)." -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:137 -msgid "None" -msgstr "Жодне" - -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:138 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:80 +#: xs/src/libslic3r/GCode/PreviewData.cpp:163 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:90 msgid "Perimeter" msgstr "Периметр" -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:139 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:81 +#: xs/src/libslic3r/GCode/PreviewData.cpp:164 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:91 msgid "External perimeter" msgstr "Зовнішній периметр" -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:140 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:82 +#: xs/src/libslic3r/GCode/PreviewData.cpp:165 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:92 msgid "Overhang perimeter" msgstr "Нависаючий периметр" -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:141 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:83 +#: xs/src/libslic3r/GCode/PreviewData.cpp:166 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:93 msgid "Internal infill" msgstr "Внутрішнє наповнення" -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:144 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:86 +#: xs/src/libslic3r/GCode/PreviewData.cpp:169 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:96 msgid "Bridge infill" msgstr "Мостове наповнення" -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:151 +#: xs/src/libslic3r/GCode/PreviewData.cpp:176 msgid "Mixed" msgstr "" -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:330 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:68 +#: xs/src/libslic3r/GCode/PreviewData.cpp:367 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:74 msgid "Feature type" msgstr "Тип ознаки" -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:332 +#: xs/src/libslic3r/GCode/PreviewData.cpp:369 msgid "Height (mm)" msgstr "Висота (мм)" -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:334 +#: xs/src/libslic3r/GCode/PreviewData.cpp:371 msgid "Width (mm)" msgstr "Ширина (мм)" -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:336 +#: xs/src/libslic3r/GCode/PreviewData.cpp:373 msgid "Speed (mm/s)" msgstr "Швидкість (мм/с)" -#: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:338 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:72 +#: xs/src/libslic3r/GCode/PreviewData.cpp:375 +msgid "Volumetric flow rate (mm3/s)" +msgstr "" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:377 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:79 msgid "Tool" msgstr "Інструмент" -#: c:\src\Slic3r\lib\Slic3r\GUI.pm:286 +#: lib/Slic3r/GUI.pm:308 msgid "Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):" msgstr "Виберіть один чи кілька файлів (STL/OBJ/AMF/PRUSA):" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:66 +#: lib/Slic3r/GUI/MainFrame.pm:66 msgid "Version " msgstr "Версія " -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:66 +#: lib/Slic3r/GUI/MainFrame.pm:66 msgid "" " - Remember to check for updates at http://github.com/prusa3d/slic3r/releases" msgstr " - Пам'ятайте оновлювати з http://github.com/prusa3d/slic3r/releases" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:118 +#: lib/Slic3r/GUI/MainFrame.pm:135 msgid "Plater" msgstr "Платер" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:120 +#: lib/Slic3r/GUI/MainFrame.pm:137 msgid "Controller" msgstr "Контролер" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:192 -msgid "No Bonjour device found" -msgstr "Немає пристрою Bonjour" +#: lib/Slic3r/GUI/MainFrame.pm:215 +msgid "Open STL/OBJ/AMF/3MF…\tCtrl+O" +msgstr "" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:192 -msgid "Device Browser" -msgstr "Браузер(список) пристроїв" - -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:211 -msgid "Connection to OctoPrint works correctly." -msgstr "Підключення до OctoPrint працює правильно." - -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:214 -msgid "I wasn't able to connect to OctoPrint (" -msgstr "Не можливо підключитися до OctoPrint (" - -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:215 -msgid "). Check hostname and OctoPrint version (at least 1.1.0 is required)." -msgstr ") Перевірте версію хоста та OctoPrint (принаймні 1.1.0 - обов'язкова)." - -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:251 -msgid "Open STL/OBJ/AMF…\tCtrl+O" -msgstr "Відкрити STL/OBJ/AMF…\tCtrl+O" - -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:251 +#: lib/Slic3r/GUI/MainFrame.pm:215 msgid "Open a model" msgstr "Відкрити модель" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:254 +#: lib/Slic3r/GUI/MainFrame.pm:218 msgid "&Load Config…\tCtrl+L" msgstr "Завантажити конфігурацію... \tCtrl+L" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:254 +#: lib/Slic3r/GUI/MainFrame.pm:218 msgid "Load exported configuration file" msgstr "Завантажити експортований файл конфігурації" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:257 +#: lib/Slic3r/GUI/MainFrame.pm:221 msgid "&Export Config…\tCtrl+E" msgstr "Експортувати конфігурацію...\tCtrl+E" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:257 +#: lib/Slic3r/GUI/MainFrame.pm:221 msgid "Export current configuration to file" msgstr "Експортувати поточну конфігурацію в файл" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:260 +#: lib/Slic3r/GUI/MainFrame.pm:224 msgid "&Load Config Bundle…" msgstr "Завантажити пакет конфігурації…" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:260 +#: lib/Slic3r/GUI/MainFrame.pm:224 msgid "Load presets from a bundle" msgstr "Завантажити налаштування з пакету" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:263 +#: lib/Slic3r/GUI/MainFrame.pm:227 msgid "&Export Config Bundle…" msgstr "Експортувати пакет налаштування…" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:263 +#: lib/Slic3r/GUI/MainFrame.pm:227 msgid "Export all presets to file" msgstr "Експортувати всі налаштування у файл" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:268 +#: lib/Slic3r/GUI/MainFrame.pm:232 msgid "Q&uick Slice…\tCtrl+U" msgstr "Швидке нарізання…\tCtrl+U" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:268 +#: lib/Slic3r/GUI/MainFrame.pm:232 msgid "Slice a file into a G-code" msgstr "Нарізати файл у G-код" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:274 +#: lib/Slic3r/GUI/MainFrame.pm:238 msgid "Quick Slice and Save &As…\tCtrl+Alt+U" msgstr "Швидко нарізати та зберегти як…\tCtrl+Alt+U" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:274 +#: lib/Slic3r/GUI/MainFrame.pm:238 msgid "Slice a file into a G-code, save as" msgstr "Нарізати файл у G-код, зберегти як" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:280 +#: lib/Slic3r/GUI/MainFrame.pm:244 msgid "&Repeat Last Quick Slice\tCtrl+Shift+U" msgstr "Повторити останнє швидке нарізання\tCtrl+Shift+U" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:280 +#: lib/Slic3r/GUI/MainFrame.pm:244 msgid "Repeat last quick slice" msgstr "Повторити останнє швидке нарізання" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:287 +#: lib/Slic3r/GUI/MainFrame.pm:251 msgid "Slice to SV&G…\tCtrl+G" msgstr "Нарізати в SV&G…\tCtrl+G" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:287 +#: lib/Slic3r/GUI/MainFrame.pm:251 msgid "Slice file to a multi-layer SVG" msgstr "Нарізати файл в багатошаровий SVG" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:291 +#: lib/Slic3r/GUI/MainFrame.pm:255 msgid "(&Re)Slice Now\tCtrl+S" msgstr "(Пере)Нарізати зараз\tCtrl+S" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:291 +#: lib/Slic3r/GUI/MainFrame.pm:255 msgid "Start new slicing process" msgstr "Почати новий процес нарізання" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:294 +#: lib/Slic3r/GUI/MainFrame.pm:258 msgid "Repair STL file…" msgstr "Відновити STL-файл…" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:294 +#: lib/Slic3r/GUI/MainFrame.pm:258 msgid "Automatically repair an STL file" msgstr "Автоматично відновити як STL-файл" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:299 -msgid "Preferences…\tCtrl+," -msgstr "Преференції…\tCtrl+," - -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:299 -msgid "Application preferences" -msgstr "Преференції застосування" - -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:304 +#: lib/Slic3r/GUI/MainFrame.pm:262 msgid "&Quit" msgstr "Вихід" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:304 +#: lib/Slic3r/GUI/MainFrame.pm:262 msgid "Quit Slic3r" msgstr "Вийти зі Slic3r" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:314 +#: lib/Slic3r/GUI/MainFrame.pm:272 msgid "Export G-code..." msgstr "Експорт G-code..." -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:314 +#: lib/Slic3r/GUI/MainFrame.pm:272 msgid "Export current plate as G-code" msgstr "Експорт поточної пластини як G-код" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:317 +#: lib/Slic3r/GUI/MainFrame.pm:275 msgid "Export plate as STL..." msgstr "Експорт пластини як STL..." -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:317 +#: lib/Slic3r/GUI/MainFrame.pm:275 msgid "Export current plate as STL" msgstr "Експорт поточної пластини як STL" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:320 +#: lib/Slic3r/GUI/MainFrame.pm:278 msgid "Export plate as AMF..." msgstr "Експорт пластини як AMF..." -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:320 +#: lib/Slic3r/GUI/MainFrame.pm:278 msgid "Export current plate as AMF" msgstr "Експорт поточної пластини як AMF" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:323 +#: lib/Slic3r/GUI/MainFrame.pm:281 msgid "Export plate as 3MF..." msgstr "Експорт пластини як 3MF..." -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:323 +#: lib/Slic3r/GUI/MainFrame.pm:281 msgid "Export current plate as 3MF" msgstr "Експорт поточної пластини як 3MF" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:336 +#: lib/Slic3r/GUI/MainFrame.pm:294 msgid "Select &Plater Tab\tCtrl+1" msgstr "Вибрати вкладку Plater\tCtrl+1" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:336 +#: lib/Slic3r/GUI/MainFrame.pm:294 msgid "Show the plater" msgstr "Показати plater" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:342 +#: lib/Slic3r/GUI/MainFrame.pm:300 msgid "Select &Controller Tab\tCtrl+T" msgstr "Вибрати вкладку Контроллер" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:342 +#: lib/Slic3r/GUI/MainFrame.pm:300 msgid "Show the printer controller" msgstr "Показати контролер принтера" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:350 +#: lib/Slic3r/GUI/MainFrame.pm:308 msgid "Select P&rint Settings Tab\tCtrl+2" msgstr "Вибрати вкладку параметрів друку\tCtrl+2" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:350 +#: lib/Slic3r/GUI/MainFrame.pm:308 msgid "Show the print settings" msgstr "Показати параметри друку" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:353 +#: lib/Slic3r/GUI/MainFrame.pm:311 msgid "Select &Filament Settings Tab\tCtrl+3" msgstr "Вибрати вкладку параметрів філаменту\tCtrl+3" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:353 +#: lib/Slic3r/GUI/MainFrame.pm:311 msgid "Show the filament settings" msgstr "Показати параметри філаменту" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:356 +#: lib/Slic3r/GUI/MainFrame.pm:314 msgid "Select Print&er Settings Tab\tCtrl+4" msgstr "Вибрати вкладку параметрів принтеру\tCtrl+4" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:356 +#: lib/Slic3r/GUI/MainFrame.pm:314 msgid "Show the printer settings" msgstr "Показати параметри принтеру" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:368 +#: lib/Slic3r/GUI/MainFrame.pm:326 msgid "Iso" msgstr "" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:368 +#: lib/Slic3r/GUI/MainFrame.pm:326 msgid "Iso View" msgstr "Вид Iso" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:369 +#: lib/Slic3r/GUI/MainFrame.pm:327 msgid "Top View" msgstr "Вид зверху" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:370 +#: lib/Slic3r/GUI/MainFrame.pm:328 msgid "Bottom View" msgstr "Вид знизу" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:371 +#: lib/Slic3r/GUI/MainFrame.pm:329 msgid "Front" msgstr "Спереду" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:371 +#: lib/Slic3r/GUI/MainFrame.pm:329 msgid "Front View" msgstr "Вид спереду" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:372 -msgid "Rear" -msgstr "Ззаду" - -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:372 +#: lib/Slic3r/GUI/MainFrame.pm:330 msgid "Rear View" msgstr "Вид ззаду" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:373 +#: lib/Slic3r/GUI/MainFrame.pm:331 msgid "Left" msgstr "З лівого боку" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:373 +#: lib/Slic3r/GUI/MainFrame.pm:331 msgid "Left View" msgstr "Вид з лівого боку" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:374 +#: lib/Slic3r/GUI/MainFrame.pm:332 msgid "Right" msgstr "З правого боку" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:374 +#: lib/Slic3r/GUI/MainFrame.pm:332 msgid "Right View" msgstr "Вид з правого боку" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:380 -msgid "&Configuration " -msgstr "Конфігурація " - -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:380 -msgid "Run Configuration " -msgstr "Запустити конфігурацію " - -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:385 +#: lib/Slic3r/GUI/MainFrame.pm:338 msgid "Prusa 3D Drivers" msgstr "Драйвери Prusa3D" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:385 +#: lib/Slic3r/GUI/MainFrame.pm:338 msgid "Open the Prusa3D drivers download page in your browser" msgstr "Відкрити сторінку завантаження драйверів Prusa3D у своєму браузері" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:388 +#: lib/Slic3r/GUI/MainFrame.pm:341 msgid "Prusa Edition Releases" msgstr "Випуски(релізи) Prusa Edition" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:388 +#: lib/Slic3r/GUI/MainFrame.pm:341 msgid "Open the Prusa Edition releases page in your browser" msgstr "Відкрити сторінку релізів Prusa Edition у своєму браузері" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:395 +#: lib/Slic3r/GUI/MainFrame.pm:348 msgid "Slic3r &Website" msgstr "Веб-сайт Slic3r" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:395 +#: lib/Slic3r/GUI/MainFrame.pm:348 msgid "Open the Slic3r website in your browser" msgstr "Відкрити сторінку Slic3r у своєму браузері" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:398 +#: lib/Slic3r/GUI/MainFrame.pm:351 msgid "Slic3r &Manual" msgstr "Посібник до Slic3r" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:398 +#: lib/Slic3r/GUI/MainFrame.pm:351 msgid "Open the Slic3r manual in your browser" msgstr "Відкрити сторінку посібнику до Slic3r у своєму браузері" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:402 +#: lib/Slic3r/GUI/MainFrame.pm:355 msgid "System Info" msgstr "Інформація про систему" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:402 +#: lib/Slic3r/GUI/MainFrame.pm:355 msgid "Show system information" msgstr "Показати інформацію про систему" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:405 +#: lib/Slic3r/GUI/MainFrame.pm:358 +msgid "Show &Configuration Folder" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:358 +msgid "Show user configuration folder (datadir)" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:361 msgid "Report an Issue" msgstr "Повідомити про проблему" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:405 +#: lib/Slic3r/GUI/MainFrame.pm:361 msgid "Report an issue on the Slic3r Prusa Edition" msgstr "Повідомити про проблему на Slic3r Prusa Edition" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:408 +#: lib/Slic3r/GUI/MainFrame.pm:364 msgid "&About Slic3r" msgstr "Про Slic3r" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:408 +#: lib/Slic3r/GUI/MainFrame.pm:364 msgid "Show about dialog" msgstr "Показати діалог Про Slic3r" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:418 +#: lib/Slic3r/GUI/MainFrame.pm:374 msgid "&File" msgstr "Файл" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:419 +#: lib/Slic3r/GUI/MainFrame.pm:375 msgid "&Plater" msgstr "&Платер" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:420 +#: lib/Slic3r/GUI/MainFrame.pm:376 msgid "&Object" msgstr "&Об'єкт" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:421 +#: lib/Slic3r/GUI/MainFrame.pm:377 msgid "&Window" msgstr "Вікно" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:422 +#: lib/Slic3r/GUI/MainFrame.pm:378 msgid "&View" msgstr "Вид" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:426 +#: lib/Slic3r/GUI/MainFrame.pm:381 msgid "&Help" msgstr "Доромога" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:457 +#: lib/Slic3r/GUI/MainFrame.pm:412 msgid "Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):" msgstr "Вибрати файл для нарізання (STL/OBJ/AMF/3MF/PRUSA):" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:469 +#: lib/Slic3r/GUI/MainFrame.pm:424 msgid "No previously sliced file." msgstr "Немає попередньо нарізаного файлу." -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:474 +#: lib/Slic3r/GUI/MainFrame.pm:425 lib/Slic3r/GUI/Plater.pm:1405 +msgid "Error" +msgstr "Помилка" + +#: lib/Slic3r/GUI/MainFrame.pm:429 msgid "Previously sliced file (" msgstr "Попередньо нарізаний файл (" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:474 +#: lib/Slic3r/GUI/MainFrame.pm:429 msgid ") not found." msgstr ") не знайдено." -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:475 +#: lib/Slic3r/GUI/MainFrame.pm:430 msgid "File Not Found" msgstr "Файл не знайдено" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:514 +#: lib/Slic3r/GUI/MainFrame.pm:469 msgid "SVG" msgstr "" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:514 +#: lib/Slic3r/GUI/MainFrame.pm:469 msgid "G-code" msgstr "G-код" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:514 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1645 +#: lib/Slic3r/GUI/MainFrame.pm:469 lib/Slic3r/GUI/Plater.pm:1795 msgid " file as:" msgstr " файл як:" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:528 +#: lib/Slic3r/GUI/MainFrame.pm:483 msgid "Slicing…" msgstr "Нарізання…" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:528 +#: lib/Slic3r/GUI/MainFrame.pm:483 msgid "Processing " msgstr "Обробка " -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:548 +#: lib/Slic3r/GUI/MainFrame.pm:503 msgid " was successfully sliced." msgstr " був успішно нарізаний." -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:550 +#: lib/Slic3r/GUI/MainFrame.pm:505 msgid "Slicing Done!" msgstr "Нарізання завершено!" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:566 +#: lib/Slic3r/GUI/MainFrame.pm:521 msgid "Select the STL file to repair:" msgstr "Вибрати STL-файл для відновлення:" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:580 +#: lib/Slic3r/GUI/MainFrame.pm:535 msgid "Save OBJ file (less prone to coordinate errors than STL) as:" msgstr "Зберегти OBJ-файл (менш схильний координувати помилки, ніж STL) як:" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:594 +#: lib/Slic3r/GUI/MainFrame.pm:549 msgid "Your file was repaired." msgstr "Ваш файл було відновлено." -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:594 +#: lib/Slic3r/GUI/MainFrame.pm:549 msgid "Repair" msgstr "Відновити" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:605 +#: lib/Slic3r/GUI/MainFrame.pm:560 msgid "Save configuration as:" msgstr "Зберегти конфігурацію як:" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:623 -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:667 +#: lib/Slic3r/GUI/MainFrame.pm:578 lib/Slic3r/GUI/MainFrame.pm:622 msgid "Select configuration to load:" msgstr "Вибрати конфігурацію для завантаження:" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:646 +#: lib/Slic3r/GUI/MainFrame.pm:601 msgid "Save presets bundle as:" msgstr "Зберегти набір налаштувань як:" -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:687 +#: lib/Slic3r/GUI/MainFrame.pm:642 #, perl-format msgid "%d presets successfully imported." msgstr "%d налаштувань успішно імпортовано." -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:749 -msgid "You have unsaved changes " -msgstr "У вас є незбережені зміни " - -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:749 -msgid ". Discard changes and continue anyway?" -msgstr ". Відхилити зміни і продовжити в будь-якому випадку?" - -#: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:750 -msgid "Unsaved Presets" -msgstr "Незбереженні налаштування" - -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:104 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2124 +#: lib/Slic3r/GUI/Plater.pm:164 lib/Slic3r/GUI/Plater.pm:2323 msgid "3D" msgstr "" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:138 +#: lib/Slic3r/GUI/Plater.pm:206 msgid "2D" msgstr "" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:157 +#: lib/Slic3r/GUI/Plater.pm:224 msgid "Layers" msgstr "Шари" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:177 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:195 +#: lib/Slic3r/GUI/Plater.pm:250 lib/Slic3r/GUI/Plater.pm:268 msgid "Add…" msgstr "Додати…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:179 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:197 +#: lib/Slic3r/GUI/Plater.pm:252 lib/Slic3r/GUI/Plater.pm:270 msgid "Delete All" msgstr "Видалити все" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:180 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:198 +#: lib/Slic3r/GUI/Plater.pm:253 lib/Slic3r/GUI/Plater.pm:271 msgid "Arrange" msgstr "Організувати" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:182 +#: lib/Slic3r/GUI/Plater.pm:255 msgid "More" msgstr "Більше" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:183 +#: lib/Slic3r/GUI/Plater.pm:256 msgid "Fewer" msgstr "Менше" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:185 +#: lib/Slic3r/GUI/Plater.pm:258 msgid "45° ccw" msgstr "45° пгс" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:186 +#: lib/Slic3r/GUI/Plater.pm:259 msgid "45° cw" msgstr "45° згс" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:187 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:203 +#: lib/Slic3r/GUI/Plater.pm:260 lib/Slic3r/GUI/Plater.pm:276 msgid "Scale…" msgstr "Масштаб…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:188 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:204 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2099 +#: lib/Slic3r/GUI/Plater.pm:261 lib/Slic3r/GUI/Plater.pm:277 +#: lib/Slic3r/GUI/Plater.pm:2293 msgid "Split" msgstr "Розділити" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:189 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:205 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2102 +#: lib/Slic3r/GUI/Plater.pm:262 lib/Slic3r/GUI/Plater.pm:278 +#: lib/Slic3r/GUI/Plater.pm:2296 msgid "Cut…" msgstr "Вирізати…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:191 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:206 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2106 +#: lib/Slic3r/GUI/Plater.pm:264 lib/Slic3r/GUI/Plater.pm:279 +#: lib/Slic3r/GUI/Plater.pm:2300 msgid "Settings…" msgstr "Параметри…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:192 +#: lib/Slic3r/GUI/Plater.pm:265 msgid "Layer Editing" msgstr "Редагування шарів" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:207 +#: lib/Slic3r/GUI/Plater.pm:280 msgid "Layer editing" msgstr "Редагування шарів" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:220 +#: lib/Slic3r/GUI/Plater.pm:303 msgid "Name" msgstr "Ім'я" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:221 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:900 +#: lib/Slic3r/GUI/Plater.pm:304 lib/Slic3r/GUI/Plater.pm:992 msgid "Copies" msgstr "Копії" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:222 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1056 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1061 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2068 +#: lib/Slic3r/GUI/Plater.pm:305 lib/Slic3r/GUI/Plater.pm:1158 +#: lib/Slic3r/GUI/Plater.pm:1163 lib/Slic3r/GUI/Plater.pm:2262 msgid "Scale" msgstr "Масштаб" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:236 +#: lib/Slic3r/GUI/Plater.pm:322 msgid "Export G-code…" msgstr "Експортувати G-код…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:237 +#: lib/Slic3r/GUI/Plater.pm:323 msgid "Slice now" msgstr "Нарізати зараз" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:238 +#: lib/Slic3r/GUI/Plater.pm:324 msgid "Print…" msgstr "Друк…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:239 +#: lib/Slic3r/GUI/Plater.pm:325 msgid "Send to printer" msgstr "Надіслати на принтер" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:240 +#: lib/Slic3r/GUI/Plater.pm:326 msgid "Export STL…" msgstr "Експортувати STL…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:367 +#: lib/Slic3r/GUI/Plater.pm:453 msgid "Print settings" msgstr "Параметри друку" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:369 +#: lib/Slic3r/GUI/Plater.pm:455 msgid "Printer" msgstr "Принтер" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:398 +#: lib/Slic3r/GUI/Plater.pm:488 msgid "Info" msgstr "Інфо" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:409 +#: lib/Slic3r/GUI/Plater.pm:499 msgid "Volume" msgstr "Обсяг" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:410 +#: lib/Slic3r/GUI/Plater.pm:500 msgid "Facets" msgstr "Грані" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:411 +#: lib/Slic3r/GUI/Plater.pm:501 msgid "Materials" msgstr "Матеріали" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:412 +#: lib/Slic3r/GUI/Plater.pm:502 msgid "Manifold" msgstr "Різноманіття" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:438 +#: lib/Slic3r/GUI/Plater.pm:527 msgid "Sliced Info" msgstr "Інформація з нарізання" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:447 -msgid "Used Filament (m)" -msgstr "Використано філаметну (м)" - -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:448 -msgid "Used Filament (mm³)" -msgstr "Використано філаметну (мм³)" - -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:449 -msgid "Used Filament (g)" -msgstr "Використано філаметну (г)" - -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:451 -msgid "Estimated printing time" -msgstr "Приблизний час друку" - -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:629 +#: lib/Slic3r/GUI/Plater.pm:713 msgid "Loading…" msgstr "Завантаження…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:629 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:643 +#: lib/Slic3r/GUI/Plater.pm:713 lib/Slic3r/GUI/Plater.pm:727 msgid "Processing input file\n" msgstr "Обробка вхідного файлу\n" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:663 +#: lib/Slic3r/GUI/Plater.pm:750 msgid "" "This file contains several objects positioned at multiple heights. Instead " "of considering them as multiple objects, should I consider\n" @@ -4009,12 +5239,11 @@ msgstr "" "того, щоб розглядати їх як кілька об'єктів, чи потрібно розглянути\n" "цей файл як єдиний об'єкт, що має декілька частин?\n" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:666 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:683 +#: lib/Slic3r/GUI/Plater.pm:753 lib/Slic3r/GUI/Plater.pm:770 msgid "Multi-part object detected" msgstr "Виявлено об'єкт, що складається з кількох частин" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:680 +#: lib/Slic3r/GUI/Plater.pm:767 msgid "" "Multiple objects were loaded for a multi-material printer.\n" "Instead of considering them as multiple objects, should I consider\n" @@ -4024,11 +5253,11 @@ msgstr "" "Замість того, щоб розглядати їх як кілька об'єктів, чи потрібно розглянути\n" "ці файл як єдиний об'єкт, що має декілька частин?\n" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:692 +#: lib/Slic3r/GUI/Plater.pm:779 msgid "Loaded " msgstr "Завантажений " -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:744 +#: lib/Slic3r/GUI/Plater.pm:837 msgid "" "Your object appears to be too large, so it was automatically scaled down to " "fit your print bed." @@ -4036,15 +5265,15 @@ msgstr "" "Ваш об'єкт видався занадто великим, тому він автоматично зменшився " "відповідно до вашої полотна друку." -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:745 +#: lib/Slic3r/GUI/Plater.pm:838 msgid "Object too large?" msgstr "Об'єкт занадто великий?" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:900 +#: lib/Slic3r/GUI/Plater.pm:992 msgid "Enter the number of copies of the selected object:" msgstr "Введіть кількість копій обраного об'єкта:" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:927 +#: lib/Slic3r/GUI/Plater.pm:1019 msgid "" "\n" "Non-positive value." @@ -4052,7 +5281,7 @@ msgstr "" "\n" "Непозитивне значення." -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:928 +#: lib/Slic3r/GUI/Plater.pm:1020 msgid "" "\n" "Not a numeric value." @@ -4060,50 +5289,46 @@ msgstr "" "\n" "Не числове значення." -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:929 +#: lib/Slic3r/GUI/Plater.pm:1021 msgid "Slic3r Error" msgstr "Помилка Slic3r" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:950 +#: lib/Slic3r/GUI/Plater.pm:1042 msgid "Enter the rotation angle:" msgstr "Введіть кут повороту:" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:950 +#: lib/Slic3r/GUI/Plater.pm:1042 msgid "Rotate around " msgstr "Обертати навколо " -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:950 +#: lib/Slic3r/GUI/Plater.pm:1042 msgid "Invalid rotation angle entered" msgstr "Введено неправильний кут повороту" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1030 +#: lib/Slic3r/GUI/Plater.pm:1132 #, perl-format msgid "Enter the new size for the selected object (print bed: %smm):" msgstr "Введіть новий розмір для обраного об'єкта (полотна друку: %smm):" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1031 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1035 +#: lib/Slic3r/GUI/Plater.pm:1133 lib/Slic3r/GUI/Plater.pm:1137 msgid "Scale along " msgstr "Масштабувати разом " -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1031 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1035 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1056 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1061 +#: lib/Slic3r/GUI/Plater.pm:1133 lib/Slic3r/GUI/Plater.pm:1137 +#: lib/Slic3r/GUI/Plater.pm:1158 lib/Slic3r/GUI/Plater.pm:1163 msgid "Invalid scaling value entered" msgstr "Введено неправильне значення масштабування" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1035 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1061 +#: lib/Slic3r/GUI/Plater.pm:1137 lib/Slic3r/GUI/Plater.pm:1163 #, no-perl-format msgid "Enter the scale % for the selected object:" msgstr "Введіть шкалу % для обраного об'єкта:" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1056 +#: lib/Slic3r/GUI/Plater.pm:1158 msgid "Enter the new max size for the selected object:" msgstr "Введіть новий максимальний розмір для обраного об'єкта:" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1112 +#: lib/Slic3r/GUI/Plater.pm:1218 msgid "" "The selected object can't be split because it contains more than one volume/" "material." @@ -4111,76 +5336,87 @@ msgstr "" "Вибраний об'єкт не можна розділити, оскільки містить більше одного об'єму/" "матеріалу." -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1121 +#: lib/Slic3r/GUI/Plater.pm:1227 msgid "" "The selected object couldn't be split because it contains only one part." msgstr "" "Вибраний об'єкт не можна розділити, оскільки він містить лише одну частину." -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1286 +#: lib/Slic3r/GUI/Plater.pm:1391 msgid "Slicing cancelled" msgstr "Нарізання скасовано" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1300 +#: lib/Slic3r/GUI/Plater.pm:1405 msgid "Another export job is currently running." msgstr "На даний час виконується інший експорт." -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1445 +#: lib/Slic3r/GUI/Plater.pm:1555 msgid "File added to print queue" msgstr "Файл додано до черги друку" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1448 -msgid "Sending G-code file to the OctoPrint server..." -msgstr "Відправка файлу G-коду на сервер OctoPrint..." - -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1451 +#: lib/Slic3r/GUI/Plater.pm:1561 msgid "G-code file exported to " msgstr "Файл G-коду експортується до " -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1454 +#: lib/Slic3r/GUI/Plater.pm:1564 msgid "Export failed" msgstr "Експортувати не вдалося" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1524 -msgid "G-code file successfully uploaded to the OctoPrint server" -msgstr "Файл G-коду успішно завантажений на сервер OctoPrint" +#: lib/Slic3r/GUI/Plater.pm:1576 +msgid "OctoPrint upload finished." +msgstr "" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1526 -msgid "Error while uploading to the OctoPrint server: " -msgstr "Помилка під час завантаження на сервер OctoPrint: " +#: lib/Slic3r/GUI/Plater.pm:1610 +msgid "Used Filament (m)" +msgstr "Використано філаметну (м)" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1539 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1581 +#: lib/Slic3r/GUI/Plater.pm:1612 +msgid "Used Filament (mm³)" +msgstr "Використано філаметну (мм³)" + +#: lib/Slic3r/GUI/Plater.pm:1614 +msgid "Used Filament (g)" +msgstr "Використано філаметну (г)" + +#: lib/Slic3r/GUI/Plater.pm:1618 +msgid "Estimated printing time (normal mode)" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:1620 +msgid "Estimated printing time (silent mode)" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:1659 lib/Slic3r/GUI/Plater.pm:1701 msgid "STL file exported to " msgstr "STL-файл експортовано в " -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1592 +#: lib/Slic3r/GUI/Plater.pm:1740 msgid "AMF file exported to " msgstr "AMF-файл експортовано в " -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1596 +#: lib/Slic3r/GUI/Plater.pm:1744 msgid "Error exporting AMF file " msgstr "Помилка експортування AMF-файлу " -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1608 +#: lib/Slic3r/GUI/Plater.pm:1756 msgid "3MF file exported to " msgstr "3MF-файл експортовано в " -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1612 +#: lib/Slic3r/GUI/Plater.pm:1760 msgid "Error exporting 3MF file " msgstr "Помилка експортування 3MF-файлу " -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1958 +#: lib/Slic3r/GUI/Plater.pm:2140 #, perl-format msgid "%d (%d shells)" msgstr "%d (%d оболонок)" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1960 +#: lib/Slic3r/GUI/Plater.pm:2142 #, perl-format msgid "Auto-repaired (%d errors)" msgstr "Автоматичне відновлення (%d помилок)" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1965 +#: lib/Slic3r/GUI/Plater.pm:2147 #, perl-format msgid "" "%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d " @@ -4189,219 +5425,302 @@ msgstr "" "вироджено %d грані, виправлено %d країв, вилучено %d грані, додано %d грані, " "змінено %d грані, повернуто %d країв" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:1970 +#: lib/Slic3r/GUI/Plater.pm:2152 msgid "Yes" msgstr "Так" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2021 +#: lib/Slic3r/GUI/Plater.pm:2215 msgid "Remove the selected object" msgstr "Видалити вибраний об'єкт" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2024 +#: lib/Slic3r/GUI/Plater.pm:2218 msgid "Increase copies" msgstr "Збільшити копії" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2024 +#: lib/Slic3r/GUI/Plater.pm:2218 msgid "Place one more copy of the selected object" msgstr "Розташувати ще одну копію обраного об'єкта" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2027 +#: lib/Slic3r/GUI/Plater.pm:2221 msgid "Decrease copies" msgstr "Зменшити копії" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2027 +#: lib/Slic3r/GUI/Plater.pm:2221 msgid "Remove one copy of the selected object" msgstr "Вилучіть одну копію обраного об'єкта" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2030 +#: lib/Slic3r/GUI/Plater.pm:2224 msgid "Set number of copies…" msgstr "Встановити кількість копій…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2030 +#: lib/Slic3r/GUI/Plater.pm:2224 msgid "Change the number of copies of the selected object" msgstr "Змінити кількість копій обраного об'єкта" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2034 +#: lib/Slic3r/GUI/Plater.pm:2228 msgid "Rotate 45° clockwise" msgstr "Повернути на 45° за годинниковою стрілкою" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2034 +#: lib/Slic3r/GUI/Plater.pm:2228 msgid "Rotate the selected object by 45° clockwise" msgstr "Повернути виділений об'єкт на 45° за годинниковою стрілкою" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2037 +#: lib/Slic3r/GUI/Plater.pm:2231 msgid "Rotate 45° counter-clockwise" msgstr "Повернути 45° проти годинникової стрілки" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2037 +#: lib/Slic3r/GUI/Plater.pm:2231 msgid "Rotate the selected object by 45° counter-clockwise" msgstr "Повернути виділений об'єкт на 45° проти годинникової стрілки" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2042 +#: lib/Slic3r/GUI/Plater.pm:2236 msgid "Rotate" msgstr "Повернути" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2042 +#: lib/Slic3r/GUI/Plater.pm:2236 msgid "Rotate the selected object by an arbitrary angle" msgstr "Повернути виділений об'єкт на довільний кут" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2044 +#: lib/Slic3r/GUI/Plater.pm:2238 msgid "Around X axis…" msgstr "Навколо осі X…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2044 +#: lib/Slic3r/GUI/Plater.pm:2238 msgid "Rotate the selected object by an arbitrary angle around X axis" msgstr "Повернути виділений об'єкт на довільний кут навколо осі Х" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2047 +#: lib/Slic3r/GUI/Plater.pm:2241 msgid "Around Y axis…" msgstr "Навколо осі Y…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2047 +#: lib/Slic3r/GUI/Plater.pm:2241 msgid "Rotate the selected object by an arbitrary angle around Y axis" msgstr "Повернути виділений об'єкт на довільний кут навколо осі Y" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2050 +#: lib/Slic3r/GUI/Plater.pm:2244 msgid "Around Z axis…" msgstr "Навколо осі Z…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2050 +#: lib/Slic3r/GUI/Plater.pm:2244 msgid "Rotate the selected object by an arbitrary angle around Z axis" msgstr "Повернути виділений об'єкт на довільний кут навколо осі Z" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2055 +#: lib/Slic3r/GUI/Plater.pm:2249 msgid "Mirror" msgstr "Віддзеркалити" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2055 +#: lib/Slic3r/GUI/Plater.pm:2249 msgid "Mirror the selected object" msgstr "Віддзеркалити виділений об'єкт" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2057 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2073 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2089 +#: lib/Slic3r/GUI/Plater.pm:2251 lib/Slic3r/GUI/Plater.pm:2267 +#: lib/Slic3r/GUI/Plater.pm:2283 msgid "Along X axis…" msgstr "Уздовж осі X…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2057 +#: lib/Slic3r/GUI/Plater.pm:2251 msgid "Mirror the selected object along the X axis" msgstr "Віддзеркалити виділений об'єкт уздовж осі Х" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2060 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2076 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2092 +#: lib/Slic3r/GUI/Plater.pm:2254 lib/Slic3r/GUI/Plater.pm:2270 +#: lib/Slic3r/GUI/Plater.pm:2286 msgid "Along Y axis…" msgstr "Уздовж осі Y…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2060 +#: lib/Slic3r/GUI/Plater.pm:2254 msgid "Mirror the selected object along the Y axis" msgstr "Віддзеркалити виділений об'єкт уздовж осі Y" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2063 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2079 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2095 +#: lib/Slic3r/GUI/Plater.pm:2257 lib/Slic3r/GUI/Plater.pm:2273 +#: lib/Slic3r/GUI/Plater.pm:2289 msgid "Along Z axis…" msgstr "Уздовж осі Z…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2063 +#: lib/Slic3r/GUI/Plater.pm:2257 msgid "Mirror the selected object along the Z axis" msgstr "Віддзеркалити виділений об'єкт уздовж осі Z" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2068 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2084 +#: lib/Slic3r/GUI/Plater.pm:2262 lib/Slic3r/GUI/Plater.pm:2278 msgid "Scale the selected object along a single axis" msgstr "Масштабувати виділений об'єкт уздовж осі" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2070 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2086 +#: lib/Slic3r/GUI/Plater.pm:2264 lib/Slic3r/GUI/Plater.pm:2280 msgid "Uniformly…" msgstr "Рівномірно…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2070 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2086 +#: lib/Slic3r/GUI/Plater.pm:2264 lib/Slic3r/GUI/Plater.pm:2280 msgid "Scale the selected object along the XYZ axes" msgstr "Масштабувати виділений об'єкт уздовж осей XYZ" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2073 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2089 +#: lib/Slic3r/GUI/Plater.pm:2267 lib/Slic3r/GUI/Plater.pm:2283 msgid "Scale the selected object along the X axis" msgstr "Масштабувати виділений об'єкт уздовж осі X" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2076 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2092 +#: lib/Slic3r/GUI/Plater.pm:2270 lib/Slic3r/GUI/Plater.pm:2286 msgid "Scale the selected object along the Y axis" msgstr "Масштабувати виділений об'єкт уздовж осі Y" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2079 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2095 +#: lib/Slic3r/GUI/Plater.pm:2273 lib/Slic3r/GUI/Plater.pm:2289 msgid "Scale the selected object along the Z axis" msgstr "Масштабувати виділений об'єкт уздовж осі Z" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2084 +#: lib/Slic3r/GUI/Plater.pm:2278 msgid "Scale to size" msgstr "Масштабувати до розміру" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2099 +#: lib/Slic3r/GUI/Plater.pm:2293 msgid "Split the selected object into individual parts" msgstr "Розділити вибраний об'єкт на окремі частини" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2102 +#: lib/Slic3r/GUI/Plater.pm:2296 msgid "Open the 3D cutting tool" msgstr "Відкрити інструмент 3D-нарізки" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2106 +#: lib/Slic3r/GUI/Plater.pm:2300 msgid "Open the object editor dialog" msgstr "Відкрити діалог редактора об'єктів" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2110 +#: lib/Slic3r/GUI/Plater.pm:2304 msgid "Reload from Disk" msgstr "Перезавантажити з диска" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2110 +#: lib/Slic3r/GUI/Plater.pm:2304 msgid "Reload the selected file from Disk" msgstr "Перезавантажити вибраний файл із диска" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2113 +#: lib/Slic3r/GUI/Plater.pm:2307 msgid "Export object as STL…" msgstr "Експортувати об'єкт як STL…" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater.pm:2113 +#: lib/Slic3r/GUI/Plater.pm:2307 msgid "Export this single object as STL file" msgstr "Експортувати цей окремий об'єкт як STL-файл" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\2D.pm:131 +#: lib/Slic3r/GUI/Plater.pm:2311 +msgid "Fix STL through Netfabb" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:2311 +msgid "" +"Fix the model by sending it to a Netfabb cloud service through Windows 10 API" +msgstr "" + +#: lib/Slic3r/GUI/Plater/2D.pm:131 msgid "What do you want to print today? ™" msgstr "Що б Ви хотіли надрукувати сьогодні? ™" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\2D.pm:132 +#: lib/Slic3r/GUI/Plater/2D.pm:132 msgid "Drag your objects here" msgstr "Перетягніть сюди свій об'єкт" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:63 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:69 msgid "1 Layer" msgstr "1 Шар" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:65 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:71 msgid "View" msgstr "Вид" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:75 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:78 +msgid "Volumetric flow rate" +msgstr "" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:85 msgid "Show" msgstr "Показати" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:78 -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:79 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:88 lib/Slic3r/GUI/Plater/3DPreview.pm:89 msgid "Feature types" msgstr "Типи ознак" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:96 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:106 msgid "Retractions" msgstr "Переривання" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:97 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:107 msgid "Unretractions" msgstr "Непереривання" -#: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:98 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:108 msgid "Shells" msgstr "Оболонки" + +#~ msgid "&Localization" +#~ msgstr "Локалізація" + +#~ msgid "" +#~ "The Wipe Tower currently supports only:\n" +#~ "- first layer height 0.2mm\n" +#~ "- layer height from 0.15mm to 0.35mm\n" +#~ "\n" +#~ "Shall I adjust those settings in order to enable the Wipe Tower?" +#~ msgstr "" +#~ "Вичіщуюча веж в даний час підтримує тільки:\n" +#~ "- висота першого шару 0,2 мм\n" +#~ "- висота шару від 0,15 мм до 0,35 мм\n" +#~ "\n" +#~ "Чи потрібно коригувати ці налаштування, щоб увімкнути вичіщуючу веж?" + +#~ msgid "" +#~ "The supplied name is not valid; the following characters are not allowed:" +#~ msgstr "Надане ім'я недійсне; такі символи не допускаються:" + +#~ msgid "Minimum extrusion length" +#~ msgstr "Мінімальна довжина екструзії" + +#~ msgid "Host or IP" +#~ msgstr "Host або IP" + +#~ msgid "" +#~ "Slic3r can upload G-code files to OctoPrint. This field should contain " +#~ "the hostname or IP address of the OctoPrint instance." +#~ msgstr "" +#~ "Slic3r може завантажувати файли G-коду в OctoPrint. Це поле повинно " +#~ "містити ім'я хоста або IP-адресу екземпляру OctoPrint." + +#~ msgid "Per color change depth" +#~ msgstr "Змінити глибину за кольором" + +#~ msgid "" +#~ "Depth of a wipe color per color change. For N colors, there will be " +#~ "maximum (N-1) tool switches performed, therefore the total depth of the " +#~ "wipe tower will be (N-1) times this value." +#~ msgstr "" +#~ "Глибина вичищення кольору для кожної зміни кольору. Для N кольорів буде " +#~ "виконано максимум (N-1) інструментальних перемикачів, тому загальна " +#~ "глибина вичищуючої вежі буде (N-1) разів до цього значення." + +#~ msgid "No Bonjour device found" +#~ msgstr "Немає пристрою Bonjour" + +#~ msgid "Device Browser" +#~ msgstr "Браузер(список) пристроїв" + +#~ msgid "I wasn't able to connect to OctoPrint (" +#~ msgstr "Не можливо підключитися до OctoPrint (" + +#~ msgid "" +#~ "). Check hostname and OctoPrint version (at least 1.1.0 is required)." +#~ msgstr "" +#~ ") Перевірте версію хоста та OctoPrint (принаймні 1.1.0 - обов'язкова)." + +#~ msgid "Open STL/OBJ/AMF…\tCtrl+O" +#~ msgstr "Відкрити STL/OBJ/AMF…\tCtrl+O" + +#~ msgid "Preferences…\tCtrl+," +#~ msgstr "Преференції…\tCtrl+," + +#~ msgid "&Configuration " +#~ msgstr "Конфігурація " + +#~ msgid "Run Configuration " +#~ msgstr "Запустити конфігурацію " + +#~ msgid "Estimated printing time" +#~ msgstr "Приблизний час друку" + +#~ msgid "G-code file successfully uploaded to the OctoPrint server" +#~ msgstr "Файл G-коду успішно завантажений на сервер OctoPrint" + +#~ msgid "Error while uploading to the OctoPrint server: " +#~ msgstr "Помилка під час завантаження на сервер OctoPrint: " diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index af7022f2ba..9c62fe8ebf 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -250,6 +250,7 @@ bool select_language(wxArrayString & names, g_wxLocale->AddCatalogLookupPathPrefix(wxPathOnly(localization_dir())); g_wxLocale->AddCatalog(g_wxApp->GetAppName()); wxSetlocale(LC_NUMERIC, "C"); + Preset::update_suffix_modified(); return true; } return false; @@ -275,6 +276,7 @@ bool load_language() g_wxLocale->AddCatalogLookupPathPrefix(wxPathOnly(localization_dir())); g_wxLocale->AddCatalog(g_wxApp->GetAppName()); wxSetlocale(LC_NUMERIC, "C"); + Preset::update_suffix_modified(); return true; } } diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 54a5c90fe4..49e235146c 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -146,6 +146,11 @@ const std::string& Preset::suffix_modified() { return g_suffix_modified; } + +void Preset::update_suffix_modified() +{ + g_suffix_modified = (" (" + _(L("modified")) + ")").ToUTF8().data(); +} // Remove an optional "(modified)" suffix from a name. // This converts a UI name to a unique preset identifier. std::string Preset::remove_suffix_modified(const std::string &name) diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index a2ee1d2eb0..0d00cae485 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -167,6 +167,7 @@ public: static const std::vector& printer_options(); // Nozzle options of the printer options. static const std::vector& nozzle_options(); + static void update_suffix_modified(); protected: friend class PresetCollection; @@ -260,7 +261,7 @@ public: // used to update preset_choice from Tab const std::deque& get_presets() { return m_presets; } int get_idx_selected() { return m_idx_selected; } - const std::string& get_suffix_modified(); + static const std::string& get_suffix_modified(); // Return a preset possibly with modifications. Preset& default_preset() { return m_presets.front(); } diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 5d1c51eb3b..187030f31b 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -2728,7 +2728,7 @@ void SavePresetWindow::accept() const char* unusable_symbols = "<>:/\\|?*\""; bool is_unusable_symbol = false; bool is_unusable_postfix = false; - const std::string unusable_postfix = "(modified)"; + const std::string unusable_postfix = PresetCollection::get_suffix_modified();//"(modified)"; for (size_t i = 0; i < std::strlen(unusable_symbols); i++){ if (m_chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos){ is_unusable_symbol = true; @@ -2743,8 +2743,9 @@ void SavePresetWindow::accept() _(L("the following characters are not allowed:")) + " <>:/\\|?*\""); } else if (is_unusable_postfix){ - show_error(this, _(L("The supplied name is not valid;")) + "\n" + - _(L("the following postfix are not allowed:")) + "\n\t" + unusable_postfix); + show_error(this,_(L("The supplied name is not valid;")) + "\n" + + _(L("the following postfix are not allowed:")) + "\n\t" + //unusable_postfix); + wxString::FromUTF8(unusable_postfix.c_str())); } else if (m_chosen_name.compare("- default -") == 0) { show_error(this, _(L("The supplied name is not available."))); From cac4b2915389cfb3ecfb04a9ebef7a995d47ed36 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 23 Jul 2018 15:58:08 +0200 Subject: [PATCH 011/119] Fixed crash when generating gcode of multimaterial objects with some object out of the bed --- xs/src/libslic3r/GCode.cpp | 44 ++++++++++++++----------- xs/src/libslic3r/GCode/ToolOrdering.cpp | 6 ++-- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index f0b37ade32..89a72a7251 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -309,10 +309,12 @@ std::vector>> GCode::collec size_t object_idx; size_t layer_idx; }; - std::vector> per_object(print.objects.size(), std::vector()); + + PrintObjectPtrs printable_objects = print.get_printable_objects(); + std::vector> per_object(printable_objects.size(), std::vector()); std::vector ordering; - for (size_t i = 0; i < print.objects.size(); ++ i) { - per_object[i] = collect_layers_to_print(*print.objects[i]); + for (size_t i = 0; i < printable_objects.size(); ++i) { + per_object[i] = collect_layers_to_print(*printable_objects[i]); OrderingItem ordering_item; ordering_item.object_idx = i; ordering.reserve(ordering.size() + per_object[i].size()); @@ -337,8 +339,8 @@ std::vector>> GCode::collec std::pair> merged; // Assign an average print_z to the set of layers with nearly equal print_z. merged.first = 0.5 * (ordering[i].print_z + ordering[j-1].print_z); - merged.second.assign(print.objects.size(), LayerToPrint()); - for (; i < j; ++ i) { + merged.second.assign(printable_objects.size(), LayerToPrint()); + for (; i < j; ++i) { const OrderingItem &oi = ordering[i]; assert(merged.second[oi.object_idx].layer() == nullptr); merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]); @@ -472,9 +474,10 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) // How many times will be change_layer() called? // change_layer() in turn increments the progress bar status. m_layer_count = 0; + PrintObjectPtrs printable_objects = print.get_printable_objects(); if (print.config.complete_objects.value) { // Add each of the object's layers separately. - for (auto object : print.objects) { + for (auto object : printable_objects) { std::vector zs; zs.reserve(object->layers.size() + object->support_layers.size()); for (auto layer : object->layers) @@ -487,7 +490,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) } else { // Print all objects with the same print_z together. std::vector zs; - for (auto object : print.objects) { + for (auto object : printable_objects) { zs.reserve(zs.size() + object->layers.size() + object->support_layers.size()); for (auto layer : object->layers) zs.push_back(layer->print_z); @@ -506,8 +509,8 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) { // get the minimum cross-section used in the print std::vector mm3_per_mm; - for (auto object : print.objects) { - for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) { + for (auto object : printable_objects) { + for (size_t region_id = 0; region_id < print.regions.size(); ++region_id) { auto region = print.regions[region_id]; for (auto layer : object->layers) { auto layerm = layer->regions[region_id]; @@ -567,7 +570,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) _write(file, "\n"); } // Write some terse information on the slicing parameters. - const PrintObject *first_object = print.objects.front(); + const PrintObject *first_object = printable_objects.front(); const double layer_height = first_object->config.layer_height.value; const double first_layer_height = first_object->config.first_layer_height.get_abs_value(layer_height); for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) { @@ -596,13 +599,14 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) size_t initial_print_object_id = 0; bool has_wipe_tower = false; if (print.config.complete_objects.value) { - // Find the 1st printing object, find its tool ordering and the initial extruder ID. - for (; initial_print_object_id < print.objects.size(); ++initial_print_object_id) { - tool_ordering = ToolOrdering(*print.objects[initial_print_object_id], initial_extruder_id); - if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1) - break; - } - } else { + // Find the 1st printing object, find its tool ordering and the initial extruder ID. + for (; initial_print_object_id < printable_objects.size(); ++initial_print_object_id) { + tool_ordering = ToolOrdering(*printable_objects[initial_print_object_id], initial_extruder_id); + if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1) + break; + } + } + else { // Find tool ordering for all the objects at once, and the initial extruder ID. // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it. tool_ordering = print.m_tool_ordering.empty() ? @@ -676,7 +680,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) // Collect outer contours of all objects over all layers. // Discard objects only containing thin walls (offset would fail on an empty polygon). Polygons islands; - for (const PrintObject *object : print.objects) + for (const PrintObject *object : printable_objects) for (const Layer *layer : object->layers) for (const ExPolygon &expoly : layer->slices.expolygons) for (const Point © : object->_shifted_copies) { @@ -724,7 +728,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) if (print.config.complete_objects.value) { // Print objects from the smallest to the tallest to avoid collisions // when moving onto next object starting point. - std::vector objects(print.objects); + std::vector objects(printable_objects); std::sort(objects.begin(), objects.end(), [](const PrintObject* po1, const PrintObject* po2) { return po1->size.z < po2->size.z; }); size_t finished_objects = 0; for (size_t object_id = initial_print_object_id; object_id < objects.size(); ++ object_id) { @@ -788,7 +792,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) PrintObjectPtrs printable_objects = print.get_printable_objects(); for (PrintObject *object : printable_objects) object_reference_points.push_back(object->_shifted_copies.front()); - Slic3r::Geometry::chained_path(object_reference_points, object_indices); + Slic3r::Geometry::chained_path(object_reference_points, object_indices); // Sort layers by Z. // All extrusion moves with the same top layer height are extruded uninterrupted. std::vector>> layers_to_print = collect_layers_to_print(print); diff --git a/xs/src/libslic3r/GCode/ToolOrdering.cpp b/xs/src/libslic3r/GCode/ToolOrdering.cpp index 9b3f2694fb..189a94d496 100644 --- a/xs/src/libslic3r/GCode/ToolOrdering.cpp +++ b/xs/src/libslic3r/GCode/ToolOrdering.cpp @@ -451,10 +451,9 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int return volume_to_wipe; // Soluble filament cannot be wiped in a random infill, neither the filament after it // we will sort objects so that dedicated for wiping are at the beginning: - PrintObjectPtrs object_list = print.objects; + PrintObjectPtrs object_list = print.get_printable_objects(); std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config.wipe_into_objects; }); - // We will now iterate through // - first the dedicated objects to mark perimeters or infills (depending on infill_first) // - second through the dedicated ones again to mark infills or perimeters (depending on infill_first) @@ -548,7 +547,8 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config); unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config); - for (const PrintObject* object : print.objects) { + PrintObjectPtrs printable_objects = print.get_printable_objects(); + for (const PrintObject* object : printable_objects) { // Finds this layer: auto this_layer_it = std::find_if(object->layers.begin(), object->layers.end(), [<](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)layers.end()) From 2df6e08eedf410a321e4f8c5a636804bd70162ed Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 24 Jul 2018 10:33:14 +0200 Subject: [PATCH 012/119] Reduced size of MK3 bed textures for compatibility with OpenGL 1.1 --- resources/icons/bed/mk3_bottom.png | Bin 174128 -> 23501 bytes resources/icons/bed/mk3_top.png | Bin 190302 -> 30647 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/icons/bed/mk3_bottom.png b/resources/icons/bed/mk3_bottom.png index 072c14dae57753e48f59b33f090d1a7142971115..3f12e7efba0e992696dbb39f1c26b5195519b7dc 100644 GIT binary patch literal 23501 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

hhv8|05%o>S9Er_}u9c

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 diff --git a/resources/icons/bed/mk3_top.png b/resources/icons/bed/mk3_top.png index 846ca53a5d73fdacdeb3ba14e1cf9a049c0a6e9d..b44007ad440da38ff179263ee1f2f4ec2f56e229 100644 GIT binary patch literal 30647 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 literal 190302 zcmZU*cRZE<8$W(a(LgELD+!hCb<8wK8L8eS$t<$>o~4p4D%+_LC1hof!Z~DRXUj1% zPxd_Le6L%d@Ar@2@4O#x9k+YD#x$-!lYpc^AWIu=?2))LYOE(Z?KYZPf?5BZ$ z@-sTc;2#>(YwDK}3i{vM%8VE|a=_urU1tP2bPWBU3Q0&}g@d#%8d{fW$7osTj#4cc zu`448FQRct^_EBf+|a#9gWlJyqyP0*jfg&oi59y1O6aEK8!6Q%9;UPhXbzZ<;ddG< znlIwSO`8Lc@@jL|oUCQN_Z6S_FjDv~|K*py#IifwVQ1E}Keb~1)4Gi%E1!=l>$7sp z?f1?tTI+Mw{%ud5v@bi%qa4<2z#1-q{7zct(D zzp#R@te(wpGcy0RUNSdTHAi8a+-Xz#L*ZJH{6N|k-7e@HA*^5zhfKfSVA%F;EuV9} zw$x9*H6-e-)PXzENW_jyv#k`I5}%=z+t3~6;HWjk>gLvdq4;+tv0ISoL%b7uB^xVP zR~JiiCo)&Z^Yi4-tOZV*^skdV=SIm+G#AWej&y_xUY4uNg$az z&~V+P==a-Pj#V%7#zu{)u`kjmv_xN~rD$#~r``9CS(8c~d!5#*y;MLydzhAFV7dJ` z4RdmOL{g|XWBNeM_5{xvg=J{tQGB;h1ORFb{zjBK-{$<#dxsl#?^2+k| z;cX>al8wd|Now)$Jd49wa5`I`g#{>PM61 z=jEf_L`?^MKTDOKvlQW8O^7THdgVq@(cpUNa;_=gBBfA7JDL(SPn|w}wquU|)JOW5 z9Id;!;kUhK8i^@xk#4`_70(dEXR~q2PpT^P7P-4F(6Mk|pz?Wi8n@=MWcYG<8VS>35UE|Tj$K** z(Gi&B&voYYRUh)^)Z!wZM}3n_+Fry`mi;5{7mOUtLZ2)9IXcdEaeyfDSP`3m2Su{eED6 z#srCbUxOg^hsd)!m!ey3-7)nHh;W-B#~Qn!=l%R0F3U;Ro++1yn_E%@$wue-7-bgg zq-vE$Yu7&Zj(ImTJ3EoD#l>bOCMOO)yTHlGSr;Ry^S=F2(k3}I1s}8O>5!TdBQ>`j z>6IL#7%1D=|I*y=RAYul?{EG#TCJ^}?ld#$=?bmoIid+hR*NfYyzkE4o|Uw==h~9E z&hejx6iTLJ%A~bgH><{MPGQ?UP5I(Zn+X=Nf*&0_A}DFT_8o(7zJB0d+p=h^we%|HZ}Zhz z+BR>D&!9vMKZ~26pD*#8qS$x%ea*~#gseZ`{MP6lNKJCX4a*w5P4a#U=noJndP!0RP>ZSB!!pUb!VPu*IL1ltxXOwo4 zMD$nT<1@Fgm|#MVo@EXNJx*;X68Rs6@Wf5qzngag4H}Lt#=OFGgOb56I)>;;J_yPZ+JK z_`!&F#syV^hlcKdqcXN>F)yHuVEwi%eOB7l9bH`b#gGTLg_#M7BRl34f<;?OeWc$? z>PBCipMV?^mJZtm9^%r@;`ioQBR@8kjc-fvn%)ubb3>S!ON`o>uXUt{;?qhMAl$kxoPOiv1}+uAr?;UP5E~M9FC%9_;)S=jQgSD z9d~v3b+pWJ_h0}=+e!Empy+`9TH@APeeoC~@bca`3wjO&X6V6+i^l@88qO^pLJ)@` z40>$$gBktb7X%p(`2Rf){&t>0?*Eare6f3hEKq?k{C90HU2kX$F(Qbp%nCq=s*yMY>+(ki$D77o%h#; z_QNi|&4njU)Gq~*x1l4phoV_&Cg%Xz8wk%?b(GB zSJQ8FSxG+z-KM3bZGR-S#Z#=G4Yr1v6~W!e>TaD2mQHN>H8R2izk>&L{}ALz5Bv8> zCeaZz@^`rSL?;h829MS5J7)IwZ3>T&hR*}@xUDU(=GNA=<)+qFZ6DICQ6z*X-YoQp zS*iEB-L0%{GMuVgx7ZrgNuraUc8W#mk&|d`F+^Y z!QpKX6|b`j#7OA%b|Y0>XV0ExWX!9s=BcKTh^c)xDbt=JvyTzu>nX5Bs8dr=P=K4I zc3ag4@%VXcav_GktN-az8_$j3Jo9>szUlk-3l*D_iD2?BB`JTdsHz&S)&rX)#7tQ! zY~I}5-01JE5)eW(KL-VG5K{Wa#!M@gTNR${skyY&G_5Q!Hh2=@BYR|lNQdzA;}M7B zt+E)9i(x8BB@nK6v1Z8Vr&n#Vau9Xs=JpOrNM6pjD;@wpYietIPFq`hY<#><-~o=N zTu&G`IA{P_k({F9oa{j5J)W1s5g>OxJv|Ua)q#z7k>@g#0DFIYU|<0J0u$WQVz^Oz zN&MG)Pp+$PjUOVq@*Nn zl+|(0MIP49zLSr@-Un;?@Zl-_?Au+ltAAYN$Ku4?D*bbFa@Mf6J+SBF0s{ldQ&(g~2Ik#h>sP$vmR*bU;%pR|L+kbQ^jeV+;%oeBU2e3PTWW zV`sPKt?FvF)t?7(FAk>$OW=%oXg57Q@4G&^(%!CHsGpsA>&#f(msXIPyow4=m>FrY z?~#zfrT)5RB*Nz%G;-1#)4FKOiI@-@g? zK)>-IV`yB_Q%48u+uYJpf6u=1`0J~ul(+t*LW(j|$7Ggw=N>jGsm{G>Ie7yDA)e~( zAtH!i$CTM2k`f`g1I{j&>H=vGk8zA_s0j-T zU%FJ6svbF3dst;IHyrmMe+L30GYbpBg`sj5)MVA4Giy1O8(oMf&s2iO01^{tNj=$b(rV+E=lGc|+4u{KA4=b6eZMfJTW3 z#KInbM^!5*EOZqV9^B^UbNxa<&g7H}0CnNs2D&wnuKKs zv0zOWn~ca3ZfA?d>nAnb^9liu4Q&!rGL#g~^LNlstcjS1#f^E1f!o zwfTX?EF;nxZWX^d?SluLujy18Mxq;&Tml|<%V@Dt5W6DUDyA3#Nm1F5-OjQK#TiT@ z4pmu3e?{c)5YeyLU#~5WQ<>SDWlw0m>0p`b*A-zIpEY#Va&@L)?BQfYXyi$^w4h4}H~>JFMp5wdct zmL?;ZT;1z<4u@BP=L}p`ukUiif?iTlRKd`L=LeZ{dGARFQx%t#Jo()0)9oygv)cMrF0-saL3r)z>Z+chp|bPUq!mZu*d_}cf1lU4 zXV0GL-MyS=sp-P24QMN|!*M)nRBV$k8=c@Yh

yUt7LI_ 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�{gNZn0IrO5y8xTtTq~u}RzgN8 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�qLFYfK1|2)ZeJ##J_#EfDQhi50tOf4eWX6FxQ?OIU<+5T<@juQJ_T99$}Z7r{xp| z`t(fc@5@}?te(EB|I{k$N=InOiTVbUyTXd>CFdw#Q-TwVipJv`?0Id?K9pJ6>^tz` ztX;+h#RRQfHe30+kPIG8f2vG-qZAcSU1^R;<}i-5)Akd{T*)(XOD3$n@3-{R=eyQ{ zuS|s<>m@B(Ja>s<+0@hPCeahw7bb+=WX;V&Gvd-SEv|N_`>B>Js&?2#8I+gk1DN`U z$%=MSuZRI&Pu`pE$+?M){5InPzD}i~up_0usljBv6FQA;>dy(i{#O?Tu34Fx{c}RF zzWm=j-S~e3FOOKTwd&@w$;w4}TzRv7?`q5E1Z8L5C$E%+{MULKt)uflt-TQ*oAw0{ z*l~~Sv-1U48$@6yj>)GP{U!NFS1<1%%WfY!7fB7x14SOWrUhHlh>Lk0+t7x#)1Pi!54;#w)sW zHe_X=6Sgvt*$)q|?L-50s4;Ug7+IlT5P8@&_N6-%YiU|p8PH+@kDwImBrm;a=?nI4uUN+H4We&GgSAc< zO&m+Zjpd}LX|ozk7N-AqqTts<$5lGagSqk|AcYGwE>JEVBbsu-L!c9$BeS2IM#cfj z1Ewqt*c-ngyW;5F*!zns`7)PN(C*)SU+}93hZg?6^u4|5jK3j(vcz@;E@D0mmY3Ts zU-v)oVK4{agUI7{=c%K{zvrq`g4)C7j6$&{%^i`!H1;L~vHQL=whG^S8PTBxuuSjU z;Sav0)G9*R>bODW?0fViflNRa?bS+C0h*XVP|j@!0CE1J6dz>3hK`Y?>DFxEk^db2 z^e2X~yV9Wi>u5l31f!`<``QUjQy;5S*cvwh{FDsmId#qUihEjaB5_qmz6>2 z>Ty2o$QYc^Ob8LPD;sVeI9GafVyz<{LCpzhX@MWLMxiU+h`h-`i!IzWC9wJJM+Vby z6bH=06|j>?5tl{p`aNka@CAY_)Rc$eQGe$G0Jy|r2Tui`*Nu=u?z_U&bqxN}eNiG# z)k(xkZJyv`N9ZKD6YFNesyG`icxWnJ@&AX?~*r!IJE(y!{-jsXs_DC#3{LVlU zm>jvSMu!1riB;~w1>VO=jRtYsSeVgafczTHS$FSDS_!BT`T#OTmCn!*>ra?57;8ECFYFbv9C3sWx#D^_sL`il4?~qbIyZkx>)h zS?jy~pXUgw6&7t}k!hee;Mk0%9UQ@BX;37&#lNbe^uaPo-xT?~&XcJvIv3mTxsHh=54Ry&OIumLwco?LN1}`(f zh2#MtTfl+6b~!b7Kj4QaxAyl^11$Hg4VAMkBnnFdl57KxrV+(+}R^EcqHM7KO`H6p8F?Ovc zvnxOJ6)-6IH$2}~Wlv$Q|}#!W!wJ&pGK&RlA)#;)E@Wkgj9gY`$X@UF(DQrW*YijB!!^$H zIFI9doabkK4LWTkPuUxgJ)5nd@?k%h3@g6^USqC94%jAaJS8uCgRU6`cUyYUUJiug zndo{VXs=oXaF2fl6+*qE&vP@A<9LqEo2+3;0WxxYg>R2`MPg!`>+Fis&T#4f_ft;> z$W`SJv4JEV+FC|TeJyy@cVgo5;yXF-uA59OzxqDlpa_?B8S*N;h1u9UJ8GuSu=Imv+s1}>y z`+o#J;DmoPdM-0iQB*TU7b?>2mGN->E5{>y8nTq2Si|Y)OlB;%V|DAsQGb~$gi1Ry zJp*v=o$B-7f>$MS>95=7UvdDt9x2@^zQDW<|ekrqXU#VG=L=0;)o>6 zTS38FK#q^h{G->>JJUmGfn)m7P?9BDd@MVZGiXpg%7y-#}!`kn~ zH$mEC%0AbXX`rmg^e^p|GZ<#KCb-MEquk%~3%WF5Dm*l!_sIgoIaH|yNAnI9WECpz z@kwz`&F@yIU@LD`%cr#FgMZMHfEyLgBq6XznfR*3IMEpgW7+Q5pZ3Xpn#ILk3d0T; z_9(eZri2T@0zL1B<`14@roNee=wX$Lf}|V^0D?;{U%X;;;4Uent!1cG=sCW&Z6AB# z)rW6=1m-Pfe*Gz3x0#`(gxd^5+T^H>xM|a1K_AxDBlus-_Z~zWD2FJVPLZ9V(-WBY z_&htToj%}%P%}^gLL8KeK!{awe-V{nCkP6m(PClm6%AeUJ9k9kN$k%(#mD*wpqVO& z3$4UhX^#|_E3T&q_+9n_d=7(K)*)xl?pH>I&a`v3s9gz8>OvC_``+VB2(z)JBhNLm4dyRTWAh4(ium-H6K z%d58!=>aGK&ueoh1aLffy>C`aO=iZfHNbqJ8UrJ@Iy?uTOLzX~*o{9YSMdS!IeKH9+v3*SYfC#D z*}IX2JBk@wXIeJhla@(Qx~ivzh0%jEG{y#@C%`M5ps$<|X~*x*sJfKQ^!Q5R?)|WxQoQ|WS&{No-bBu> zU-vFyW_7(_+hcL43*clBt3%WRh!g=7{h6s8yyUMZDyQ7iFCRPG`eEAc*$}~{hj>ky z0H42n&#z=IW~%@?KP2l;Gyz)bHW-m`UZlT}w8bvExtd&TEwJvVvGNBnJwV?e$)s8_ zxyHG3ExCwA{aRi|?(zqp*|H>OXhu-rHZ$5<3{RJUe}Q5MkSf%{6Li02cbjiK6BUum zQ*S23*2?G%NPqQ~oyyysjN+oD0znZI$l;J=JvTSyUWwSnL0ld;yY=aarD?f4ekLDM zL^jt=nfn{rtDTDF!^hw_Ah6)_#K^5ut7qj(7yV0Z=1o56iuS#=-Yzk)n`#{K{^Rp5 zYCJ9^1VrW#xh_{c&X%=OGT9@g>*#kaKteZ#=R^f*ymOSyu-ufVTONP`F5>SOpKd*4 z-zeBVb1||kXD;!jQ|jG-6T1#@twGoipb-Mm_ykQR1sH>xAjlXfnFkc13bq)+np$ZI_kw+AUATjLzMR&VMpG zbxBDlNPmn;e?+af=U=Agf-YZ}IoO&6)9E{*E@N=a#3SpB?j3o%UB#9it}*WU96!Ii zbh*&@Ag_mGXIrx++Q6+@Xw&|OPq#tKyuqA~sGOl*_fV0sqn9AU7SSS^;}D&`EYf%@ zV~{PfPcY)sj>x8W_$88A_bN~nFbQBz1}*D?JHav^*UdE`z2E8j_-77asY0LB6~f$|#wfaKY{ z6ZI)qy)=t!EGOk|d6b)a%Euwu=^>X7bJwn4mv{5J+gUC#7k{^RzN2I}*`TE)tb3}Y zwIy=L6Ved=cD?|hmL2-fX6^L4EPfX%wdwqvX0X_X^BWT?ohfiz%SjDbG;sz&Js>2( z?yGH|T%P&FHnv1IPW=jVoe(PelHJOp_Krb)z^$R&6B^Gyw`$?U5WLFC%?)bNFRyu; zdV?XeED|b!*UHQ_z=5G2@X%u~HIFaZh_PPC>UaVeGio9Vb3rdviHrG}fR=ic6-*@f zJ^CaNZo>e!O)m$j%4R@Uv%N%j5GW%pJa`6<0=T}i5W-Llh6>-`Rn#t^yt8Nm_4kOn z@`eGdX=q^g$yrF%X`OV!F|Q{Kz6RjAFa!}Ih^g*-M@Fr9X%6@{763LyX*4i_U=fA` zmZZT)6l>@JK;M6h#8U_1GWT3$pl>p2GXuT;)(O63x&ruR_+i1F0?4J1U#kh|?QBZgxJKTGw}@5%Eb zE(&G<(m^SK>A}5Igj%hFGBbEp2{H{h)qkK9L2Lw+$b~9u9yIjE@5Vb4thyr*K>=93 zIxI<$AilfWI8Z|aO9?h2q(O#m@m{L~o~YOA0>q0jwAN&1o|ty2`TPU`NJs-iiCBmw zK(MO0ScvDKcU|%>gA*5T8k?FAo972WlGK)=o|VEPoUEX42ujjq&q3Meqxfp+=KB(J%F0v6 zq`yWH9KP^}9nXUT3|lyyHkuZQXTd%7`O}m7uP`y92bXGG_GT!t&=R)n7XWah8B^5+ zJtM#+0UMgNKf{na)2~>aS2~9uY6gfrx!Jx07aXCf(7Cxl0`VNK$iUEi9GJ z9GUC*s%JeL@6wu+p(FO<`G@!Pv1V^vOV-nZ9Y5uH{8d3}44F`%atGMi?d+cUhreS)o#$>;!C;+t zKT)W3{GX)_Z^FEF@_tQ|1CPJFCwecAHaJguxVuY-){f-sF`NEt=5oWE^kQ4du5iFX zCisr%bu%*_`b#FYd5+KY1WlJeac1%;zlQrEn2^xM2E0|A-O&?@qY0zoWi9NBFUq7E z0{BW};SwOxE?~G(Ak-CrMIXd6(vZc_k=rb6x1j0Y96R>TKxskts~nh~>ZWF(ax!o$ zgdn^gNJfOCs@{CQ?r7{OO(J@AE_+{V@+mikU!#I85=(+f^8|<~09h{*UO8VS^mujp zGy>rYYZ28SxFv$FPb0s7JCDGV$RJIOY6QOPMqkODcI}-qAcfFeLi7gEpvYEonde2^ zZKe28TAigvS?!X$KRfL_0+InZ9?7etu9nde}Xb+ng+ki4O3HY z`bVj;R>Wp|L%*F{`K_&QU1BE4)rTxSkY;=Bw*|B(mH<10UY9X`r4c_6XD#nUTSE!#ABL1o+Nh?~mgC4&B7U?`E7*1@C8)P{*$vddWvtQ>-l|T zr%iKUR>7S8@4x?0j+kMdDfHX;@>^#)cKPA8t%N#Rt1NGUmrs3p{k*-`ujD+}-Kj*w zI*}TnN-CXcU=`}&Z^gp+AEwMCM=7_23&{K|p@W9l>%2F>g6tf0(1_)SS=2}W) zf8yun73eXz$wj;*S|F&e6(Wb_m3Ft5z7Gt5$1bV2SKO)4uS@pitaoyfJH&^rtgc!? zb=~tpF|!SR?=OL?&7+X&!o_JHd?-516rjcf3RNH*mR#g@oG_ z57C$&vcNEZ?gr;hun(a7POzhqfV-`BUi0Xzn0m9iDNK(6FG~b678o5O{5hBcvnA}< zw|o7lAq#{%JKz88;qpK`aaY#<#+IM9;~lo0tsNy98X~Tc--+G}lpX5SjU@P1dDa!7 z_h#k%9j;Pf4V8;}{)>1k*@?ndr7&=_2rnl}2(H`ZCdxs)z$zk=NOjO`O&HN_R@T-N zMvjB$WI=^O!?chP zblHH{UHg7rqQl>P#62~jBd4`xV^y$P-wYb41Bs>ADH`Q!?Tn|!THdJQtqtMkH5B}> zYcqwdBarAMiSXOFn_WERew#9TeOBN>42=j}_$a>vo1f;W+jqE4Km?2?|3Di$t|729 zTV4$f@XP-($LXlkPwN9-C)^HboCrWu@tU?5qe(+<>%hi($VIgbltpVIo zJj#(E34kiU04>!Kb9Vokc#J391OXEVZ21B>ci{Vde-?c?X?YE{ewbdU?>su(*U;8O z3TfPn|G>%pFx5Tjt=I#BVn<=2*+Oe3*e%C@BfHJ)?Cj4hMO3mNsVsbZ+i%*nNdfQs z&m8){kS4a#G~Y`a!FAgeq1_Z-?X|v`nb)4k8`+R}t72=h@n^pQ>m9v$(2o4CIr{?i zX~7+-o3Q|Q-`)*+A25)(C4WQodbubLNe#0Sc zs~&KIXxl<%0Te3#`Sdf1|9~}uiaF?b%KW$GOv~?$*)tJwNBDNu{JQs=`yd7#p2Xm; z71q?jBC7)p(^KeMeo9gj4WVpvso6iGR&%82us6JmGw7k~9D>y`G zS<}qA`j*b@nuZ+c8G!p_)t#L5)PN_|cknC(+^B1*1G&UwxW9xH(>3EH@Xk)VJmBoG zk%Gr<8nr#KQ9;3oNs>O?V!*5Z{{(90C4(jH_smiL>TdZ)3M&bUYG%POud9>%3?y6D zWAeuHQw8Vu;r0CLyMC?yfjjM>p^m0C-9dG5)k?2XDr)aNI!*c}Ria&p?xbCE!<--h z63-fM$qHn`L)DB~WcN>#ieS9*8TJUPiJ#zIGJ5p^00&rri;a)$3qiA!YAb$CxN)q& zr(i#L%;6I$%BBu!TA)FYi8b5^@7F2hv~bI z^GWrK9jq-WPu3Bx6;E_)>#f(x4#>Hj>$=?_I(5gLv{e(^)~X60PE&vCM~)JEJv9Y( zz>WV77HwAY)Vv8 zsl4SIcNCym6(aSkpu}s@IcN7P^p{8A{;7w=Dn)Q61WsF-GYz|fa#L4<-3g1k;VOlt zjsH~O7E-BL{aDDjczNP`>tC%o)%C)V<3a%u$45enLRcXls$-x-eYdg~cdGnD9LU-~ zZqxo1T8fcO9!YA^#*v9NCs1P($18=bGdA~oQy~jUpq7w8Rf-zOVa;j|mC$pyAcT4k zK#lHnrbrrhpaeamNFRK#xdiv!2Bg76G?VlGSZk9rEI)hatbL4WA-3m%9Ci8dR_$TT z90*m!ai#;oNlZz(y;XAvbB_X<^03@Ar*uXFBl^}S!AZHQ4IINWO9m+^;cH}K@3t-p zmAt5b(ft(Z*s)6AjvME76WJqlWN0rT9$DW4uu6B{L1%k~ye0#CTfQGcgGjq^TfkA$qg3v|$wx>Nf`Ar;tS5dDP2roV0j<_}Io zcwAMO`6`z3GA!|XdO*W`ywG#ZPA!fFu`y4sFEVTGgGj40PpBpBoTd*0^9>6BQS`a& zoND7Ew7}J{$Wjg8oqrL~kQ>aXwKsbD+A$qPlZ)EI*303P_Kfup5;!`ClFTMTEIg+5 zDyrh+qCUA;Sez9;X6?f+yLS=my}?!Tmzb*bMO^jRPi+quCN2ARIVP={TeqY1{=w=y z3=K|wJu_*xXmEy}iX#6}(yT4EM_WgH9DR6>#{6TeI=F~>XWuHO#Ve?X9UHaQds|-E zc>4d9>+X+`={{EJu$XpeSg+{CJw_}pR!Lu!2Ivcn|BlEoISsIZDg$muNHmoTKSpwC zF*FKT$zgWK+qaNaqt08O#_mpMs1Y7`3Z871ziMY2C)QIzSf#^7Zl!Nx>W7 z%P^z+;S1mU_h%2qulJl4x6e_{QGd@2WFc@2_#X=@K*iU`8NL!b^~6Vv)hLQ>*4E{& zQp?vA@O&AEMW=4lW)lqDt_OVY2>(>v6&0OV*p_m26>)>n;+oVEbZNZS-0Y56+*Q4I zQDitj83}KvwnmkTmJ2?{19ZUOV;X}YCz|N04S+%_?YlL1b6e}M`XBf>AZdX740R1W zrO2nrBEJB<2mBzNd*CSx9PkG4WBv&Uf%?^4Nqc+7K+>pCF4K zGmh)~{*JCWu{5Mf=6T35=cGpG_s|Fht7ibd@62~_*wo@GWv%(~V_+;}eeAP8pAy(A zw0OlHSGS*M&rJ+|j7_9Q!O*XCJU&;I7$xz>y;Grgxg9Qeq2ZY~EQRm5?r$N+Su_Za z$%&jL8q+>S@UlMz+{yS8SYQ0ywfsHJO#Gv6W_*!Y^o0M$Bp6nl4Q6Q&?Gtz|!d|DNV~=QrHg&vt(+ANDW~-wYMo=>6K(H}I8^LP^dDB@ zw-_*2=}H~BBP`6!W&42q<@mozd_Pa4K_Cd^?Hr)(&-TG?)DQ0)J#b?*cY->YBqI}=K6BurOO0Gp&-&y!g+xxEm*%Lp4+bXDiTNPf=Wfgh zXO3#KjCCGp)Zu)Q&YjncLOU3!qf#c|w6R?L=lxK%N0X0%i41Yh(SS;jy`&+Zv4`lz zw@11{_h`z%Ff^lsbItXtrPw)|BLo=1FE(kF^mKBci^zj*h3Jgrh{_XiT?80iuINh` zf*YU-%KNAdX3alo_6N#FVI8oQ3Fr2mNMLW4>CSYw;c7YG4D<7Uf31bThPv)MQTVOX z%baHYtEh;7fjkeC{M*HY2CLfKbr}HyH$>?QOY+Ff*_Y2m>+KDkNINX$kb@dnxzwH* z1(Usbk73wXD~Wgr-Y}sTZG` znQYxmg_VKa9&LoC)6Xc zOW^J6!go}}b9t%F6TG2&x)ud*9ky=%_gDi>E(T30PLd-fOJMj({`kyss?CHg(4Bni zoz$kBu{ZDBb}+Lx^b9+JMH|jFpd(SyJyJ{^)LVP$*vmA2<)p9`33Ib*g&FHjxz9U# z!tq7W8}OQo9@YP@!TBRPpez8b4`KAY#9RZ0R93tsONlRcaijT~l$E9R4&6}P&`SdL z_~)OfhZyGERaSHqi#r4BF4$M@$gsm;JM?S=`BbGM+i3jWcATJE-&1jw#)9j?a{UbY z4Ce%&{q?P;A5#m%GT_!^Z`%x3NCN%ai6(Lun~~rm#JMh28)7Ou8!VgGIPb$G$M$o= zdP+JKWKe0UCLG^{{rr*roGr?H`Kj-!6A9d8 z=ElpBpVXTr?cIx)Lrf2uue7Kw)1wP&QvgoCP4*mhEH^nALKOF$JJFZ9TW(f=@cWow zU%a+hF_if{)dgK zP#>B+?~5H_J5=ou%DZ_@9MnG@Z#-)lec0X;tR?+5D+K!E9xMw!w*>cpjmWRe2B*`Z z)-vwJI60n%tjk;$fZv+t2})Q^K}Q47mBUlq$eH`_DMkvl^|pV>3j{yS8=-8 zL-P#N+{D<<0rl0A4ej2A~*eY%2m^ngpvaX{GDG z=e}%L+z(_f1a!;s2>$4SFMu8_INcI%{8C$rZdU&+{X9pl*nsI^UmyE;T$Q<4&M~$j z_y}%+htj2RIshft+3^Ux_0fQ*Puf(SM)6;BpzdvXZEd6_7+?+$TYzhpo3?g-W!yy}eopPo{veW@aj9#)B3g=tp{bdT=1L1(nyp^VoYv zlk3+x{6pWGi12J)h|No3I~@s(0)&u&3j{qlUV43#E0UD}d>X)CsHt$oUr<-!W1XyW zP=k_Ag%U97s9ON2@&S^D(V8WR1D=P#O@PPfTB|}r5Qhx?=RxZ`H18wX6H7-f^Yn9IR1u5|SP4k2;fe3bjZnoKa8Q5SV_eL1z0Hu} zuOM%@daoQ|oB(zH9I}@|)I;z@MoENeK+p6tqOCuCkSen_F*Yv9stdPzca>wO=w7*! z8^b~CWd+Ta3czX0QZ?p0;X9^$=Dp>W@n-fg*?j#V+>K3t9#o!N$WAVWXzqjeS=^ejNk`-*Bix{PXV7Vc{8x#Tzy8w(BD|j#7T>IU{=A93x z2(92Zw*3)N2B?+PmVJXWQ--u$1KZkg)kCn4%sKO>K{~>>kY*3pUw|i!K+X$_uzAR! z^!q?HQ!~Pe0L4L;AcS}WDfq$oblw0*byd~bt+hI36l`uKJC003ke!Z{)UQujr;4)0 zATt6Q>*JPd!}n}$8?v4fkwzl|m=8o80rsR`WUT1^%j&c#cO9toy|&g|09dR8CgJzL zrIu%mb2XJtF8%&ljV9uSn&0;yNPBq?3;;+9(-rVlXwpExYYJ-)V%QmiFCiEt7T0ue{-t6!9!Y_eg9L)cGK$>K;pmnbbk)8l&0dvOb^UY23=`aLj7(5$_yn%~deRb9X^H9*isLIw;?3ow$1Qt4nR`Y3M2P_UuF(K9j*N=e>6 z_NvVWj1M#x=AgNIL2o~SWne8Ev?7&bp{?%` zuu-jb#lZtP(&7NGy7QhO`>Ex&ck_*j*3%8gfTr40G%VlC|NMD#H{Uah;F8?)Gms&; z+WkUk-^s@3qet^EQ@iwi*!KxjBQUKU-Ttwg7GQRU$tLWBYGS72N{P<1PA!LPhc#zS zf(y5mC_TSA-kvLWJ9%T!n_ERgSWCivO=O~MwaG`T>!l}ici5=+S=d>iw(n4jGbpuQ zTw0C3uC|Nsf&If}eQ|7ODMvF>tGg%%0tR7wFe!CCf%?lMCK_44u3gKod)dTNTwH8r zYiqkkhe3JeR`%zF1)(IKjhXL`@&2CYv}@jpy(i2Q|eFw}U#P|X21nF9NWW48T z@#HAp2sO)s6^Q05h#;1o1>g@H7-0350RhTrslw;y7Z(dgvaL?;`JEt$sVLy_IJkL` z4+@d&<^MdcCW*0yHJskxu)K`aL{JiTjuESdtO;zU4(eOuw^6GUAV6_7$qBnn0%1c}pk%!*1%N+Nm0POFZd9-4E&(8MJq z#5}ZSje`8mZDJX^=>YJ5_vqu7s}NcU9=czt^FUo~Ji1L>C@C&xf#A=4kUcQ)D1}3{ z`;*srVxY7J-hq090HF@Mp8sGufiQnHiYbO81IB|?zB(8N8jz5+X12NPPkr3}Mu)@T z1c2&5H3J4@+^vOu95EYEMNv(FLxvqtu)CcI?J~zf0I;3-0_vY7#J@&lu$|z+-5dMQ z5XGY*n;SYgxVigobt$}R5Ac@+nT1D=#fT9_C1j+rLG)IhE8(E+r88(mO1zhDqmDcQ zyQ=|pV5M3c3qejqq%wdmK1WjncDw@);ZH}R24~1IsLtcZ1iL_6M66^a3=mv8mQ>$G zO@a!)-xhQ)v-0uzyz7Q4zn=286u3;o>%#LPx(E^XYY@Ff0;}3bnQ<}bAGVi(q>HlS z5UCPk5t$!XCg=;jtNb@QQn^1Zgg8($gW1;wZWY$XL<3Gn%379`mHB2LDuWZ;T8t%x zgQM&kpX6K@HA8^X_MgiOjf{0rL|appqriD4-4rLS?aJ{Jam2u=aY_=!)&r665gnM) z=8U9x6Kgeey*n@6R2|HK!q=WGi>nK_Cd;!R1q+Iyt5_QeLveqEr2H}{d&s^Cp=%uk z*-!^@?SwD$AQeDV)NT+{d);ry)k4b!ZU~SR1xYSo>pZ9%#QuAj4r3(93U0PV`HyyS z%c7+Y06&s5EW9<_zPRNY*)S^oPI-5|6$r&<$wx`~KR`7EmyRc3%LSI~6N{Fz1qcSq z2Wj5kSnm(KyO0hl;8Pi+pUPkMpR>NG)UcV0*K>%`gFAxq@^ZMn z@znVmw2a*Cj~bYrx!R1gFvZdiMw5aA01kmTCisoZ&Id@+9}lIi8#aDb)VEf zgs8ofJ#20Y0?OfaThnk?^50T;ybn(~ms3E1nYruuP>W}1Vt@1P4#`L26CV@qZOIPo znXdIX-H7wUg>g0cn+kSC38y68G;#%*2n(digk?*TjY1@kx|KvK1lyf=k>6;&$q@$C zV3a4S$hg`4%f9g1hxsq##5F6{l9}`hWN}orcgk?=4{K|{+6*!Zn63FT0;Q4LnQ_}E z8UpH5&j^rW7A$Q|S@28GekuO6c2D(KQ5=Mb(f_JkQ?{mG+`G3W{>$XWcQ0d(u-8^K z;=A061!RF8Cs}{q(%#5mFl$Jq73VJg%A}mtVD_CiFTYN^#ptM+8<%}XX;aC$;yz)xKErU$_2c8RV=X)<#Zu1cbE-z*o_jAb8#XyEP}K4g`9o(q`m<)c zT)7vHT*lGmR%Hs?o5#!>u`Er5s*|3Svxyb!HRgYQ#Pw<{Ye}0ke?iRGNzyc(rdvgp zp_iDs5N1M6I%GO$>Ztu@U*iX*&|BimVct5|R#+pNx`_o5nnL)5`*trb50JNcNoCa@ zj*L1Ve01Ekcg4OyUM{NHerY30aHgH9D&dWMI#J*b1{XY>zx(0DP?(aeZ^+;u1mgDYPy_H;ks~S zr89baJmtRQMm*q3Ad>N8SykSUzGN$fF$u}$L|?fSlt1}f`>93|F+JbLG**0qjHEA| zKFO|jny>Zb^}*Axo>@3vsGtZREMz=CybF5%#54M?^nRgHf~0v#bI#BcOp5fM2Xc+o zDkyk|$SyTouSX^^vT9RcSMHKNdQ;GfGC{^jw&iJ>E2mbU2e1^%=<4V^0rQ49q~4G} zWl}v}TXVR{77Jv#eB?zW0Kx)zbYkz1qS3Al8?RD^f7!9L2U6)wb&d!0o4q4I7C+Nd z&t>5{?vmG2C_`TFW^QSN;U1IsztCipaq2N)kS% zv*h6Gq&hv?^iOWrxhT4ZiF~DeVhMo8saX7l!;hu`GsW`dp&=aWubg@kK7I;6z}#F%v^Gi7HR zrx`~%IX0?@;V+v9bwdhs$48p! zWW!7c9Kr-N9vsqwSMl}_S_ZJCx*?+=z_Y*4t?@T|4pM$s@%&X1!F#Vf4Sw0NA1`Bcc zu`{cz=}b+l5tFhQGg;14Y~9|jnMwbhLS52xN%Dubm{7E5tNzCYq5b5@W!LbPgJDP1 z`J*irHO9+8hwT#_JK*?LMe<^PzEoCvf<6BTbzILgd(Lh<>GjpP7BG}x7L|EKd9 zx)%RjB&QTB(SQgTF;rJ_B2;>cqv13+X<>tkauLm%*Rp|+rWRf^WDoj&G@FPTYLhk) zKG`1!Wn;Xsh|!!0MrJWr@v9Uw*CmX0@GV>3H&{IS2g!{%?z;lsF5^5*+jI*C6dN6Y zPs@NJg9!xb<)M!bbd$)I*jq(Y%vrQnZJ(osIP6JlZtPzrVwS@UJJF_s~<`4 zWzeF>{;97%_ch@PfWuj-ZjI$#?U?@9wsFPfzdHpSW!mW7QF1Je*;m%P*oa(ZeZ}5K z3KiJ2FHfSWgGhLd&=c6o(c#(_$EE&0qgM-Axp0~->~xs^*-yM=YI#osOKG3EIVBEt z6D14`t+xKJ#^=Oa{dFeo6#G-xPW}ZDw^Shv1Z9TG# zniUGAwu%JQ$3XT&xa$f@C!HMsywS$YV{BlKvYcTu=m~>L1$K$)$!`hSKPNqO!^YO&`-Sf0 zS`nZ=3OBeV^^(K-s3e}>cY@(vgz8rr5w<(JR>E*$?9O6BcBy1g8nMblN)T67?-^Jq zlgc$tj{Q4E&CIYfHc&adPItwg@O*V|<-zQIAPX)! z*w5)~IWc!yXOD@(2{Bi7a~xd7wut`m)B6u= zxg}IRK8~sUx5Qa$wX@c6lq~H>j6z+?4U;*VD^XM!2g4CDfv1#D#(JZY*DUUULwr!o zyR>-ga|zCvBi-0La=DG00u=xOUN&;f2ln!$@*mfC;0h{EKCt^e{HdgfFGN`2A6{>2GIx4cL?n`% z{hWJTD^?A4O~sNP{tN#7bt1x`MF69MaS=P7-suSR^5T5qVFC^Hh&NzS`PFeb&YZKN znBYXD%CZfnZ$okNfGXPM%HrS%>9ET8zT#ilT1#pe-sr&arBxntOQ*xWXTdcW;(MgCFxEKW$83>0pcw*RQQ5gqL%dNkUrYpW)iy7 z%|GAdU8NGTuCR*u(2ACWzB7dp2U#UI9ri3bSQ49J4a||kH0odfhNG-y^r0R9K+b^) zi1s|4uRmIQI9xM((N}4WfN>he(jLw0pFQy4Q5lmsv}>`vR_QB~Ri{N{AhvsP2I%4e zSodYFTc~?c{;6bJl{2iNIHF;pr%pGEkJT>j(s=Y}Sh}pE_VbfHjMbJab}pO&sdIPb zMzWcOoBfYRE+$Jf*=`TUv#xdok1a4BC(_=i4uNj+bQ+p_w`t+?aWnQdqSD)hTG z5?Q0#bxGWJIJr3kG_@Oev>0qcpJZ#~WH{tf%w~bnn7?ruM0P?o6 zvi#xlgoP1n)2LKWp1av(=I|Ko^rM6k$tR)Yg!a%L9Q*OZ1f?8-tQq+gfJumgtbc2U z!+J{DV5)Ng$soe|0glY8*g#l&TuKUaTKN8T2y;QP)1da}1AHWFa%9eAz}P(p*!-`@`j)^{9I z-ZOJ^1vivcKm;WDF z1aWVirk7F5lk&%xLuLSlp}caWZU$5c0`YsIGQ{KN9IXBQcc7UvJo=*)Y!U+FrvtA;@p#7K3Hnc|gU}aU6 z(#$H?mZ$peZdHS8d+JkV> zir7-2@C|?Zjn#)0*4K_KLf@4V*RhL0hpl}ztT+r`ynQxtytP%1J8aF3_{9CW_|uhX zTew(o+Ndco5J3pQ13Bo2Z`?9G?M;t!mIA)vcsyqqHn%}TKH3UnfNX>az%eQF5{F!u z4TghJ{>`Kuylr>Uhv_Tb-bpL=%5u8ueyQ}#!PH?@hNj*pDi$K{)chryz#u_EFjQ@M zk`XW=HB|jDU>?6Sph=RM{jMXa%dqnaz*Tg}Wy{^yU4FG^`m@|TOc1t`Z_MLZkVet*Jr-mbCRritTC1Z5m^We0upkYm};?HS7P6%=^}D( zTWfRq=Jucf;UfUi2>3(Z5Y(Fi<~C3Q;F7|1Dk|lOG!+V3KWR%7Pm(dyxEwIPZH#}_ z2G<#s;fS$A{S63h4n`HJGoxxlE%l zO7#L97iv)sRPbtm6HIGF?;sBzDtd{DT1V?L44ubiGuS`GTEDh8y+9w(zWVZiog1Jw zq5OSRQy}W;2!m@FPVXPNI^pUK=v4(S05sD}ycR6sqku4~v#Szb_hX#6p;}{|qS(O(bg!^g;iP2SDB)j-umckqlxb9Q(2CpHm_`6G zi-^hOdyfrn{;tD`yi7@t?1Sde`QM+uY-E+HrFVjW64<%+% zv1R$6y1etuH;rT?93xPO2pcHsDC!UzEv2*GeoYZ7r(B;9ob~#@!f{922|d@uWmL4$ zU;?`cL(`k@_9TOj%(myHMJu!EpTfADiScW@GwM;?wV7J6pmMH3S%xsLpi*v%*Spb6 z>NcBn4lSul`}0e?1AP_n4S52QG9Ny4en`p9*?*!pv2eUT!>*_!L?@+4=*bXRItUhj z(ya3Ke3HN_WPyJ5Zi9vD;>pM8Fo1%DV_xRj;Ylt@@_pYAhH>za!@dM0pfd6uL6--p z;WvVx9g9Relu5}UpgR!$>-Bs1EC~LSSJ;4;!@*91s87If3xSCI`DDN+NrC?QyQ^TS z_WuGqF!WkE?Y}*3hIq`*Y4=oPjeG(>wG!Npp)Uc30-*DaGgveV|Kjre3G}&;MZ5**@s-|*St=^vvhd#JMMbD50-`SAYv5KuT3nSA-**#E z0ocU;JIoPl%~f+?BS7N?G{@-R$sXb;ULDZ`;AxRSy+;62O+AxxH>Aa)!SOn55wMr< z;qCJBZ|R$0*98gv9)k-$M&N&8E-2k4J;g<)bN~s2_ygF1_NX|(!C{;B=d2aGL03p1 z4DGH%c>>H@6qRz=kh{9r#{WRq3Pk*tx=n1XN9wtby+sW~Hj2z$DFWA%92EvjTo8GA z_TIkFNj!|BZ-q; zQq_*YN&#O2OAaIf^Jg$lg@cxV_I4&@p#Ta6U2R~vxM8R$lpodolzakfcW+itWG`nB zej{hi?pEtAQxRtjeLHH`5_~|d{W=&oQ7J*4S|H5sXmk>~=Yd&(EUjzHc>$++t=7Qm zG-9p1c@2i>pqd(d!CuQ1)Upi9p(q6Gt<^!e9h8Cx_%_&T(Qm;C#tjSzV|3D9-2p1x zcl9%>t%_s~>EP-@783BfER@gt=~YMuq1;B`QAhfRc0Q`ral*tQZLjX#W~p7Mc}UGL zLan@Ze_JjX))k^*QCoMAAXt$#T%+Fk$+NSk(*Zvy1zCeFSlvLugRbT(Vb_nW;pBQv z)BlnQp9bEPg03EjZ&lp?ku%-)Chm5(2~nP)*ij%#Sf zAT+s{1+iJ+3$9{44+0f*}L%|C1C?#so+fY&v=3h(qsdFADh^rdwj9i7td zd)h>^d>8G(W2!rkFVil)?U@dyuf74_?7A;^`&OCa#_;Dy4Fm$Lot?OwTcwVs6R92Q zc@K^>E_ACca40|Tu3cD-8+~)nP*Dyq)L|k+Rrl7S=FXOQ{HUy@?4TF0D|MCyhGDn- zsogl2feUsOAjTmLCMuKB^s74YJ^SnW3|(`0EzH}+Ec@2So9!?oi>;{YyZ*x9*D-+E)!DOL+; zR^@BKr!vm`F_mH8Jp=7#dIGoE#IG>)WmNeRH)z&vvFNzVKl$s4eCai~3zz8eVzFaG zQ!JHbBL6s9218%xmtCE_^RMAC6l8+NI5MmOhmGdFWs7oqvgL9_K3p?!aU$%0&$U27 zeC4{TAH-jeqtyS7y=>-6rzO3=>UM!F(3ctjx;OTikgSd(00Ye&Pdx!F;j$8<)Px5lkHbzs1%8f(wjsJ7mh)^9i7pRt|g;bs%Y)k9Vl5 zZx2q~1NYCnd)d5uPliP=WoH^3Bv)zMq}A79$5igqh#7EB!TWOe-M8x!nPLgVFL6z0 zlroc1G~h40rf+^iqbRB}h@bWm-Ca(+z$jlM{W zeOMTu{tX~NEUKQxWvDP|iP0a$jZniUsIbZ2d1Tjt>HmJf_S2jbV>;8->N$K1??z!x zusBRi6{m5IE93$YE&-7d2OO0!m98Z&tWrsFf6_wWGNVc)MUpgtSo#itf~f5GR-p(~ zq}szm#SEk}RHSj)5GA)*lTrHO2jevgZ<`6IGHAw^pq?d za1pFl#$3d09QTLpy&j7wjrUyp_G#`~G5jdl$`H4$!GU1!^Im(;DLTfN>tt3*_?&2X;vdj#f;ZGn7Kb zTQ-mqU{4I8j9JmqJ|!w_#254xmR9>k@%B7=)&zlFFUr7<9E+JQ(uAy~&^%S(n~4Yn zo}~?+Gf3BAtK%mDKM^j@>;2PcEN-vtu|>n1<8sB$7g__Pn8 z@6a%o%J)8XD1Fk!MmV8^$cNdnwb$siP>6Z?K^_$)qIeXN40y>OPJCEygU^J?GuySu zJ-pV3la{`vL_u56^_Q?yP2VHzR3k3XU^A^)w?=1s(rl@N63ZC~H~)(*sxkF589Kws zx(jl9MA)tzpoc7>jP&2qE}D>C+hW(; zaS;k4T3mzE_Fn1IoXg}Px?!>D@!$Gk4F??;&aTfzgQ;AF_l%!Cc8e_-j@b{qOIZ8TdjM3k$q_s}$M*G0 z`o1f70v5g%l`J!g*BDWLm4&pH1mjnMWsHEZt0Zh)AEZN|EK|tuFT@krcSB59&oyLP z%ya@vSQk($Z)F0;0wp70R*i__$;!$9r`O>swJuYZS5!aKfxIsa=n{)y%~J zq zb)J56IgI1i(#Ryi0GQ*th$Em3JdRrp`nwcg<0xxooMl#^70#P+*~R3o1$lP){Fe?u z++k{Mm_F|rO#fB>WV-aN8+_sja|C3x(VGvDQ7rJ--F^yAGZy$|OX$njrl%=F{$$XHLFAiL8(a_J)0iH^c5!p1Rs_bUUm;a*u4>5GAA3pN#9Nk1LRhO z0$Z`wKNxev^=*pLIrs4oWm(^i$h$HSGF=-6^bxDtHhI?@3Ila3?c+oDHRD==@+H|g zKAJ)}d=*tB`NHG(GM2iDjWCbYx#)T)3OG%;D83i!S>5;`bIu*eo=^c5ncpJ^oxI}r z%Yqps^<9KYUr}3G3xPY7>sC_FVv41ujsHVADtBqLh_JM5I6oC2kr1+ud;G|xhv}9% zC-a%Sqy5h;loC4tw;jZQ(Ho0gbQ+cCjD)o>NvKL9Dp4Fc57iU5-_rN6W|OTOJ4_TN zEiZA{EZ%x!*T4B9F6e%>eFh>h5fNBmc!%@;be!z^T+^hMm)PE zBU3;2ujmJ|K-V7edOtBLh&aok9PcPWs{1@JTJ#+~C7o)OCSG2-T zj)OzbIv!2s;ifj$eVF#S3ciVxB6m8q$uM+HVl=V1`fZ4EMfCfDaf!;8i^2y{p}q1U zyX{2eqXJ{#CxO`dA^Pe$74|iC{(B)B58!Fkn(TvZYTwI5f~)G3KS6B^7{~SEB#!q2 z>zSCD&ixuH~jkW2PLgos(3NMug*H z16!?3S_xTT;YZ~jU@ATDOCNJpev8Ri}3XADIxv%GeTDq8*vIyT<@mVzgH4SZg7}Vby4%>2x&i-=707y-IU7kOpEnj(T9Tq_AJRS^Se=~ zU}b|lZP|>A*KcYgJ~DS{F$bmojIag*BHUnhWS786!4E4eoGa0Dieg!0yr*?;KfA@r zy+ziokv5so=Szi437|kEV<5{_XTD=u>~6ha=aD_byQ*Xl1U)&1KSvR0nFz$1iu(%l zW>~rLR;3YTr9TCxe&_2Kq$KCE*U81dvx*-9& zrvTxTw-pjPkS-ja9X2;RzUMWHF*i>vSP9Kv-Te>vhE~gwkOi9v zg-tLqa{sdso!X0}x2%H$iJ@fPfJU1&^2HF_!@V z*Uhq`Wj<5~I2fp-#wCLQuiHstuA6~}ZieQuzx;%`E728Tla-YfN~>`Gf?kKdtb@iZ z@c0eNaWXbHZGn9L&lz+WU=;AKeFAm39|HqLGLf%4e$~4j$g#Lro?^s&r8;_`jWerf zy4Q@DZ2=GR0Irbo16@i%h1g4NwLCL%~<@+}QS9z5_PoORDS zMO}#ttH0nZ?lgG|ASlv7PqrI-4nnNOUJJeGooq% zI4m;cKhY!cM8)&C!=-UFV>_x}UFMWhfC;*d(HBzvzYqs)?(t?a!w zrAT&C_DDt9+1Xhc$H+J~*_lc9dOo+lzyI?*=hf?5U(UJDeP82qUDtbjK27%UKOA}$ zv~WlVF{}~5Fc+ParnPu!089gL2*5OuDt@KDGWoUNjCW_v72b}5{*W&VQba(>cM{xRmEUnj%Zj3y05Ajq)*Exo(guT2Pc$CN zlEbME#hDHfMfPr*KD78LfSBvPz|}MW4+DAU04Uy1kUJ0*wpHkB1Vj)HnfrWEf9(?h zm;~1fh(ZBGFfuV|ENxXi1u54c$Da|J@C4wPYGO(DZuxdQcpQl_A?;JzXJo_c;POyw zL-6k!6kp$WxAs;z-vPKVz?lolijZ?2zV2z1y(w74wPOc+d_ zYnD_byGC?2l-2n&!(NfU&q~Ue>+hQ7@s7m5rl)s=3B5=B4?O)7{(6QV44{Hy=!CJY zMu7=P&_eI_pdl~UStU!xTe0pSN%qiF_Vmrb2B)4)^PWxNCEpv{+fyyEsIkC)5^5ta zK~}BPJHe(7A3AEsTz{0(u2*6Syvfx5mJRrjV8Ux};fN>TSeG3XfjkA>WQ%dvi#0L{8J zh^wLo=Ya~(TJ5^QFxOj_mF99Bi8k&>&QMz8T37O2Mkhcn8lVCNihrXpD*Oc)96;41 z*e9}~)mfI>gX?BJ9{d*i?e}a$TW&`kCiigRa&%D*wEp1^4hj||XqJS;d77X0k?r+0 zZ~B$Da(l3d%~sL#?+VHYqUah*Oajidd%BeD;QPJv?}LUi+y$(%v0r>24fx6~U8&nh zo{C_P?M5+h;CMb9`d!eHR@Oe#J%clM=vUn}9Ms{s>!WpR3Ct5Hss}JQ%Hi!NC&L7W zv@HdPc+Wu|om%8J=wYW8@}(!b&fzt$rQ@IuCsx?uHQ8vhHl70;4%o1=b zfjQ*LQD>&Ka=R1ws4rM6MbDD(Y0=*RC>wQ`z>G$x=hpdX z*YK9y?)EXm!$kk}KPZs?5z;PM0Ee}zSrzqoFI<@QyLRwHn<5-I0)56&jb6i!Hr|f7!h5z9aOxpwOJG27KBoyu0 z*DnDQ6lz>oj~u2n)p`prVHC>Sr@Un%T^7@^mOmCn(vmLRy5(yPxuP84PV)7YO~r1s z-6slh?2Dg{Rq5OJ>J*OO+x73rdiAixXJymp2HM+zyn#`{D8bW9xyn^{9qjt6yk0sFyl@^8lN$isM8i^NdM2_<77AJ9Si zscyyq#jngVfG_Sp|1x3|-yzgw>~N_8^7H|q{Y$u&6EvDO>>M17JI`job75aoA&1fj z%4$ylSGo$YENn+6VlD*0t)PXi1$_^^s{04vcEJSj;>CfXO*I;zjh7}A2h-Gj6we@1&kI{jc0RS@E6_6&f3tHm~ z|1vO)?d@S+tkMh&tMMkkVSJUssD+u1n(a{Fe`4Ytt~4w{)M^P73V&D$%&EK)CoWg!}Nj#PxIBsYATe@*TKdLot)@cgK|9R0@7Df(b< z)0;B9@hzM>uwAQn*UTq*^Xb<464&XG0B@r;0}bsyj`*AUroFa^-0o~S9+OO1L)1*C zAXkSrCgHEQoQOuN9lqorOER0D=G3VSWr~mcgP*k9p1dB2@O8LE(heG{W~UrXRDLSe6L=3@z_+vz)H- zuAi!8&GEf>a&fpe!Bz1dsqt+MZAZmgH!-Z(y>f-0;PNyC_tSdMu z9{=SmoQO_jScA|FGuSH5dngP3nxv&TTDJT8)WM|J*~=ruj5B4yJ!f4p<=8oc|9~;@ ze^#sYSTKvB<_uu+8jT6zzg!|B^r-v;4I02L!am_3J@4G&oL3_3qnxlsQX&sAr$>=a zGhVPMRr?lk4glxuw!e-;Sv{blIB@MK)dRfxZY~~LRttE#gBafG?rGuXYY1#Qp<)}X zoujGBoDn{`Rj?JgbxRb!NhRr$t3tAf5{Pc1uW6vA&d@4VN87K45J%x=*H@M~Q8LNAjA!rjMG1fW?DzN@CRG ztm6=#+B!O#(A}!`bxUJ1I1s=tZ}QLdks1bAP@s}PzjH45oMR$M%wFujBy)yzT(n%LN1`oBE>cCj)tJsvGgGG9;%W-w>tl-mjM$B`}fv$=c&pGn;tqr3*Re$JQJopwf;#7&?5?3 zVGD!Ws!+m>V%2x0l(Cp>y7GX1>4B2gh9A4eWs}Nfn@&D;+4DNNkeU-!d_dVVbd0D4 zo;A;X(BZ&m8@Hl3y5JV*?Hi!m&A+&y;A0E?73u+VS<*(sB>Ln&RZ-JdJF;@Ee|IZp6{Pf(XqtRTwyQBzemD*dAICO-Xg!SNX%E#S*k9GZ0)o9sMw0yfliM1os z^GDXX=G|CZ5P6|PAG~-+PQ7NUejPcUjy0WG!FjsoEcZjfu_j(mZ*Isi;Ig-!eCUoU zh=yP%14NueaX{GGU}%(Lo1ph*` zm~@4@caz}fTE+S9jJBk?+4G z&Vfm_Q(~5;WeR;fSk-lvzRZ=t-mH!!v0#ba0twXVefU@TkP4LoiipI|7lo#^ z-|BAbQq8TGa}0~-mzivTa&s(sYI`16eXx#-3VnA=e1l;?9qp$Y<x%!P) z=-wMQs%@|9?!|*pgfV_>&%|r%XCNG?lxz^#imgT^?=c^s!`9$p^C}Z7(FDXhI4GmE zJ?f|Z^|zVnoGm(9(fr0GLrW4*RN^@iDlqy&S$vxr zjVlK_9YZRD=96}CZt__u@yR@NM4hd^+i!h^>}_~N-=IuBoT||#KYzG-9^j@I(~xtO z0Zpob-nT=PRy*w|$UKi89;7dO6ExMoiLnX0fB(}!9_3F*ji6j@n)p8GiZ}+zNnuK$ zsgYrrR}19w^!Zazpr(9~BB0jR;W9_r>xl)_Xc0_fz(IT<3w0wnMzcbNjJF|`cs(E> zdk|S3g6JaixMMw9ffddgs7~{!N+g_gtk_KwI^gD4iF|((`O_m8eb&r3rUrSwSc?~F zFLkaT8pY_suEnL?d(j2?G6Z`CSj&C(>bc56Vhr2jF>{uH0fA(HVx>PGCnvcM6Chg( zWgVI$;Yzm$Ls5bk`rgJ^RS06U`d#-hbTOVX=U)q6$b8ym&K zAN^Kic;5=`wwH*z2q#J>lT+6%a*1w9X2K58@qB)p32cBQ7<+$g;~E}`K@QU>fiQP* zsPCE}A5tdHACCon&@!%fB4%GW;fKYjvsO##Jn#B{}K>+ z4U0D>G~43X!uoFKNBKIIhVgR*fBM8QP94SLf!lFgi#2LWi@EnkOh#AC=KzT3p6-o! zkZ>F4#|U?7VmfMZEu079(g$0iRtTp`Q}P^j+XUr>msg6LlNA`fS$QU{2SL=J=@cR2 zgUJp)Sr5iXdv+F6-jb&CffE`E?5fqEESveewf%1YcGCS~A#1%>*OnOnOTMpH)L4^L z-iQvnvD+{X8P?2@a;h%8E25YZ_1h*fcNg@h0|O7J@;h`GE!;J*k_*MqRb<<}~eWgvJDPM}93RzMYC4@}4ma^soI$(Aq5Lz#GhP;32>MG$u~ zyEh;Spp7Juf0m++3BgDHFpOs%d3I2lJQBiI4AO&6O>k>cAhegw_4?{bfPg~@fQD@{ zl%UIdg0DbD1lJ(>oZ8z3U!?^E-qr7^x9vsQs9L|&EA0V-&JqkHyHeDe3~GM>oX_E!HQyWFbJYIneff%O6q&|+o7a7k!)cGm}50sLzzps?N4~A9`HVn95SlA z5jPzE>>QamnweAj8wC?GG_TqqN;$@oqsDspGY-+4)T;#B7ms+4np1b%@lx2x{?)n% zf5`DaVEkhQPAUexn@FClhN^>ZsoN2>FIn6Cek$P$)r&^MRY!Wg8CqVlVBJd;5+S$! zuHrcQuX>hJ6)~ZN!Ml_6yM+_vehz)zM7}kxm_S)yvZ>MD7irsQhxEz% z!TaE48iudKnTy9#3%18~3gE?ni%E!(6)a4_7^~7Ss|6~5J4=5Ta$zoZXMEtwWzGG_ zU!AJ$NcYcxmVe?E69Nud#KN4`RiUL8k8JIx!jbt#DwXX@I%;*5JBO2NGdEV<1J4o` zb;&g}P$4N639FL8dkQ{(*ZEFd-!;7NBM{vM*LKd z>~7b&h~Sp>sAnlv7~)j2f@_)=-nm;_AJJnkZ^_iFU{Gq01`EDc#N7-$dK$nJAL|l) zX0dcb*dh2KIA1>}2(-8(Jx^8Ey_$q#izvX}^q;ws_;O5ZjaI79lli6Y8Rvs72CQ@a ztGAW1^!^PEsz4);9Z?cQvhtErG-S3RXJ7G`lXgRsL8kpaug zS;ePRe6RV0z}$@9j~3Ba_~JSpLRv-mGAmI=4n&0+mE%0Z!W{u|83i_`|6Xc-s768Q z<(J=Kf-S-vwVLoK{Oe}TrmNQs;HHUZD-Za3M`t2MS+(*qk;2^bE zHwniJ_#b=EeOb!ySpu-Yn$%Gc45r)Y@dV30s<%pldvvmZ10}4%A$fUL?mHJF9@3Hk z%pP!eKP#yJ^98=V-NMaiJ^qU6b8VH85`Moan2TnGZT#_3F3<02RP%|`G;GR-_-g(E zNfVnEz|SC(?v4yX%J!(84tap3pFWz+h+ojp+9F!8E-HNU;?K>FRPzTz&Ubr1QFIbN zy~2h1010#YIgHuy9M8chUfW^s^)+g4o$^ZW2fYkx*WA%;gL#ZnpAGy9YLRoDkge^R zaDdUg1V}x4p3Ly5UF)!~!bfEfh@P1PFsJaU`PB&`1C0(48D!v7elBa`6yGh}!kgh! z6El4+0;(y6BUctV*@cE2649w+AjJ}MI3FURa*A4)Xv0Hnl>lsua6yB2f`2I_YAOOg z--%=36MO1Shb(e&mL18K60s`9_CZ3r0pDcUv5#RCjq(r+oo+ne$lP}u9IoJgMu1Xh zrjT;raKX&}ZgV3*juaT2xfIf<5fZT@besF9WC(eOmtw%!2}WYsD;pM$=MXn8_b?EC zWqh8}kve)dzx#t;h@ir~Fwk8{R^dO&OzZv803sR7BTWwKN<2G+K(L$|i+Xsl>hc`f{$_KOX+i*ff zzwgOBE%feMyeTPRgn}ZLX=3z8YLOb4o#68{q?{>mC+$rI=WY>GL^P^~`hGpGzoyoW z-NV;@?*5)nXc-AO7SDOYBi8+CI58`N)PZ~OCCg1REE~PN5GuyeNncz&Bi&LDiYfn*J9M(fMxhq;hjP2YUH;r&+Thnx{}1o;bWKW#|p+foIRPl9E44)WAE9HN2+&!*o~T68E2sipO; zHFzd@>J_c^C2ZAt)jr2o;zrhp=|XH0PlxQ;<7Tn3KAWcQ)e^JbN<7qfg)@xSw1n=b zEt%Z~YGNMg68L$8>}u-_DWWLHti^jXaxT|hG4e!X>fwe33}6GjwDpa-J0GM*C%e2% z^zeJ0BIBE0fv|FJ$l!x;ijC6HP!Hcg3Ex0`SICw?+uVkydZfd2hR+$A*Qdkv`?*iY z@fJg2xct8_FF!&gyvvyo0$3r3a6Fm~qK7ksM!*P$0WzKkG9zQm%e1q0W_W$FGrSvN zmtVj;e8$q7EAb3W6|o?ZL&8qdA#rL}_`iw7@hL;34;v_9#g-hM-1LqsAV&JjNcF?@P2~y1AfmcoF|Dvo`%%8@TR~Q{b59WB=P;4PJOI@emu_|hV`PxGAXik ztsUM2FLZcp91o0a@9LTK7QG4jGsGoRE8`(tK()pDsKxxk1#J24CQkT$YqZqaBZ3W( zwr{dYwMjMP5AX2g*)1q3yM!F3;-wJZz7Wi!{h0kJ!3!?=qtpq1Y&3K6KHf0yXNoUKHi?lDlVayI}v$WlOGXk579*@dzZdS|Ub(#cZ0{)>16A@e61)V11 zo>9+0bgeD=Q}j*9?UjiWo6kud2S+p?iFyc7YtfH1MbI^t@5ODYHr1SWX4)-uEo-;g zCDo*VXsGb*pp+-R=F{mxm|TUc^R?3FVf1lt@NBOhqW7Pal94E`fVnDMs{=Us}2VwY&=8$C+STP z1F2_w@B$Pfhbnss8)nH*c4_a`mF6BFNUw}~)+k(n9k~x5rG_u|nA4Q8C$JaSiN5 z-n~z37_w`z3Rh`KKEuDMPDZ*8(S}3Pw7WTSNt5hh`I-u;Dss=I3~}1zp~hzXBLPwu zE=S2fm%8xa>e&giNrj8l!C)V>(Ykgm`p#00qLKNHL;en0ffXEPelXaWnex-pPm{1Z z_w0SaIO88W^qIX^*9P86GN}y3OQ)(_*JoZS)~~i|uf@8-qn$l@yfG~_&SXuvzqj{+ z$hAv{#R4qYnAlm7snB+)iy*I`EFg1{VtUOTzW>-H(-?7J-NP9n&k(*Dp-G!r@`p0- z3~A{wn4YaI!RMi&%xrI4uTY)*R9&tAT8d^*Wiez4515K%fJ#EL~XP~2*UtCn< zczYiXv#*-pPprCKSDj$`Vvm86^&Xy{y<=m!a3^PjZ^P4`w2nXd{ApuT6CXc+@#TO# zmP92>BO@trsTAc8jErOl1reVMNR?y$#%OHl`$_HURjQM83=F;F<0)68UxKx$KaGL; z)e}j{w6rujM#eXWwAv3J-sg^G7UYKtU$-w(gocDHSyaa?pRn*}H&MOaqV{&B(6F$pk#ap7j8<3*fpA$z z>nydaboBJy{r!+|QzhH|24zsjy-(jcN&d*5@>JlDe{ePW9y4WEXJ^E{Pp_ZWGUXS< zdO`wJOEWXMZ7)42@KNMtzf0#=x%unPhj)>Y6u@Kja&*SfRK75tbmmU($V^L<*45Q5 z(hrwOqobqyv9(nSc^rwp^7l2ufP*o4nUa{4ge0S&;660aFoP-NS<+Mh>g}`IRp%v&-GbJ*!xA;v-=D!J`OcovhBn~M+qb=4U8zY)NrTlm9FC8ZvnHXKyzbs7 zwM;|Gpj%6u&{0Z>!Vk*B@*XbvXL}Z%17Qv$+5l0_W|05>r(KN4;LO22j< z*22&*)kToZ5(dhh53c~%f6rsu&IVIbll`-HwuOVky)$Rd@V?i4L>utqSD~=5@ZX;M z%I5H6=z`DcnQi7o3lp(Crc~ig>)^VYhuS%hCGuei!pxip+>V<>I#%JMI_*23oYKm< zq5ggqFtvo8m(Y92Qx=u!Ln1Ky6bj~bh^~de=(eox8yNU>iiQK?{gp_$-~FZ5E|v&q zvXc||X^#8fgsE3lQi3D%`Cgvg)K}rAdo}tm;K{Z^zn@f!%|MRAqI6q$PoE6^!Xp@G z?9|=BUt7AG0rO)cL~g2^b-oT78zO+G_G@O+)2A1}0OhiD<;u{C!naXALpG`(FlGmb zhSq`DfhM#qtHtkw9?&hdy9pWaU>e+qgs*rt9~D^AfS$TW88io)1xWVGJ2ps8LRgfa zp9*uL+;6{)r(+IMwp!TQnkqf)*N3SKtqbCU^q`=i!4-&Mkl4@G{SOJ<%8dX+Gb<|v zR;7eaTi@M>l|b~jnG%(`ez?~K>AG#c{M@X1=pjN(iwr?ZG zia!&wamwqvM*9Gat#3QtX5QM?!~_7mGR*d`E=zG>;;g?fW)&j+E0>`RZR)wXPVfmg z8HQs82$j<2Zq1B(!VyPJwYhVNjMuNz91Gn zoZ&I<(q0BMG#hRj)>79o?-G}qQ6w0yql8+=I~#FgY52fZSMzq_x-AXhWL} zFGhCG&1FDWv&zRMC*#2Jnz=dVs}K29^78pV-n();O*J$;93)QSonY>^4iDg)(b7^@ z2H4r-J%Hv`t%=|Aip4*6n`1jVEFnapg_=xEOx)Uvu%e=gN|_}k8W7W9;pl+`30cqu zpR=<=g{&_G%qVg}u%2I8!A(uQ|J?f-)Q#wT{Gu_WL-Qe*{x958$>p&pU>exjd1?t8 z2H`|?Sa`(ueoMnwIdc4D%7IRcOGr@Tj_naysYnA};`;1pPj@J+K$j_l2C>{=N^VrlSUppTkJ>Ur@C2=2N>jCVF#pB1f0piZ^X5mPh zUxSIfb9fkoXU_(q(#A#!l6B$$)z)NV`0BjyhLDhuS3p3P`&SJJ0t>%t!wBGDck7|=R4!5J{~Wa-bJFK|Z+Dj1+s9=N)eK;=2$YgBf# zc>3RV%of4s1;i(j0>XCS75vcsYw*z4%4!ZWRY1&V66D9p6b!f+8a@$ggn6428cGT= z+0xw|UG)eWets3?=7!t&3A_23oO1bSV%PnHN4G0+(G z3=L%hL1OOhotPpBkCG0F5a)n|of;z>sq%gZ(;Suu=w?_-F0%2%lAfKD0~;TRzE2XG zo-8jfU$->mc-swJcUfKU6RFgcurM;H1T_aN3qgGRrUX(e`JMWWU9@5jYtGRrSI~#g=*&!1@2<*0 zrEO`W*?mTQS0d^V`Asg~Njdh)_#wOMCQio0rhFZa0`af)3JzQQ?5Bg+FAD&0wKTl&c zr%PH_VWKNKM;9P8l7OLXZ@gshL{rCLdD(CvSrQIx=bfEa@D8q8#B@V)w$7}CNU=uU zDy%rEg*no;)ECB(e5zgWnZ%l1N;EBrT1_%BIgu$7*QN&Cy?~n)%WZ%lWG~FJxH7L0 z<`smczQNlQ$o;9fe4@tJ?U>{l>bv}m=#i@57y;)3~v9P~ko<$_305t(X@nHfF>fGZ1fCH(C zgdE92l50!>ynj<3G+smuT&F~)gurwJUK0wzuV#qZHzRqYF!gicR^#ZW5f#}&5pP|>!ImA)w|RG$@oSR*T?f+Wp-0kQ zE+hsg%b<)Pd*v*D?Cs!ZY^iuPlTwjV^x10t$Zy=iF-|}kklnureLs5o;GTymY77Bq zfRZ~?@Smes4ax#|y_BUnhFBW&*wJ9L&BgHz^zgCmXV1M&@i7d?L#ii2h<@LIvu&Q{ zE0uQ-8ZXfc9={mVky2`?OW=5}#&AHN{}A8R@)FmhYP`IH07ED;;+OW65H({sb0&9P z-8~gp+GcKa0{Ojr0JF->>o1cn!FX;_Bp(F=00i2EqDn1Vyj@r61pupMvw3kII`Rw+ zNn(?==i+zonuLvxVH0uugPPEo#Ty7+8axv_^J}IzM-Q;*%Aj~1dLAi9v6}yTq0jZ3 zJUr=U<~Yv;f?$V@;a7Vv%Coy#jL#sy`NUtw?jM&lh6AnuqhhI_oKWbewYH9zca3gX(7$^-^tleiXM8=?)}SK8|Jnrtx=>%av6V|v-inV zICMfr%F)@nnVF1^VPcdf2$}4ZSh$431<))H>eMnH%v;pRI%>a@KaI?%!n5SvkZb9( z0GA-jHx%R8hZd;Ono`ik=x-6^O;;P*XXqX z%X34YNcw%H17qovOaA|cD`2`(#EtLtBLQVXBxZ$BmY|S7r_POD7C8We#CSJ0o)nWE z@E#L#8_@%lFXM5ZXOc7tUf(SME85I?UtThOw#AQ>-3oDN|F$^bAJc=4U0q3V2EYWDwji{E36# z!fZXyefoN-+mf#6O$;7tCp3UOXNcgIXrNAD0)?bEFOp|)45`tBdnfbqHGzAxpn=;d zm4((a7X8)~PfIBa|DUq$_Uat6SOu=lgO_XCw#LN&re~wH5gGEElISN@5TGc}#lGJA zM1JD*B=L%aJz)KaWD*P%6nqK393;d>zR#B{|Ewm#L3>ILH0H=^HfnxPL()A*);45!7{^CM@}W}^hoc0F ze#!8ZNIwnlhm;3)Ozk=_Ci<_TD*)IX?Ot!3L#K;hVB4va>~RM8M1bd3!)$>E(4^mw znT=TRgPs$-vrB^b!V{)Q-ePbXic;YwgZca2lgt=p#1IFWaWnMsxj0IXL2qLa=os{t zF$@rdBpE~%@qUjbd@LVem>@!!4#hF{&kA5n68&BohFOMw8UhOhK1kHZ@&TK^>Eun4k;x1PvDeM(jp5%3V@@@ca@j zFS&=8$_v4##bd_cf`vxgsc#x(w)Sl&e1%F|@IPE5kZT0VQU-olKg7k~vmm4G%7T8W=m+=E6o zLgFc`8vnKBe-P#e$w%zRYL*u+p^ajYOvrURGY#)J(%B{!;7y1wT(AT__2;2j6__1^v~v$Va|yYgNkF7QcKO@| z_Qc=Db|4Ta>yogH_y&OHB1jOq-6MdeA;d>4M699BQ`2=DX0WtQ;Rbb}DS**vK%Wjr z$Rv{P90ltskwq?K9u_l$HA25j&yRJ*LlV@5i++8H9gDcENCPEfOAP)K#Gb%U7rq#c z*8jZ!Ypt@)la<3dO4HLq&c9*#*JuJd6{t+I3EBX`U_|WW=xWg>eaXz3hjl zUirg(HtQQaaTKhiROPsX)aj#;NeTnV=jfLfHH+#FV4)D15*{7brXJUaM`OOp^hS`N zS&&#r+guk$tl*t8LCNdiVTSbGMgob^7?0#VV7kYA4C(y3`*+5-E@_0W#@y#tW~e-5 zO&l~vwrGytguW4On?mXgf?n9|`(mDH7Xjjy7*bH;R;}-U3_5nhB0}VttSKWT5(f4{ z@TwV-!byN&o>o5JZz$Ki#{?Z!eX--h#}w-N<+W1nY|6W^*pxdK0?AK>w7@(X+t!)J{}T|F=2%q4@G> zgSXseeR`(gaEAA2l726*1zUApIscu6RkySMmfc_$WbxE12iN>dK5(79mj%SY;E7Cr z{~wDW$8t0B1z}9L*>kS%f{(;YejK0!6xc@cHb>%i&=tUtb9*7%fgB{_jxm$V6my~;K#XlO`-5#-jlbDQXiu5QEr!)FB~uZ7&6;gMI) z%nk~X1&H6cd*90hBRqDWzr)gj(R6jl(#F4Hm#OK(>a9!wl>h+@k+q;Rk`=u<@}qD# za#gO%J7Sf04dZCd+}LIg;UD{m^JnElxz_1FZ$dE}v4P@NqsvhDmZQ@xf|jCgUs*VR zbMU*SlSHzBzNX)r8>O`ECP4WLOgdA-i#JAlIA?|l-P>=@iev?iE_K5RDwd#vKB|Xn zv_1lpAp9}9@nQmwGk&s(VR3nuce2a-`uD*(M#q7n0%Es7R>Vz{IY26P(6?^es9DhI z1}#Y+$5ryXD=#tq;X^j`LJ2E8Ko3kbF;0f6urb$h#&j^|M&_|%$Z!s}JW0=`p414^ z24d7pO~b?{=EAniNp!E(9({+P z{t0&dd{N3SUBE{wTR2)7C#~c)m5vz;vaZco8F{8rWesHN5nCTV2Q>Ej_3J3QM@vE} zbc$XPP&R-({{Ahj+L^z0ot?eMCtR9u;YSwM|XaSy);5;4E#%+Y(cl zR}EM-9Gn#u6wC|^Bmp&qlR2OY{SKT#I}HY9Isj|O#l&<1t_*^%P(%G)>&&M>Y);-$|}AclGf0JW|z zRj4S{Fu8htm4#-knUe&nGy}vAz%AT*CeRgtkZNbc37%3QxXmB^gaKy;e5k=F|Ene9 zGG-hQ5WvgN|FNiu4{n%^{6tS9bJld|3@wI^4o)lgpaL~@A#F9_3jnC04MIZF;{!bo zH=qpn$xY%v+yvxF)~$Bavb^8od^gMEkrZ2%z=5tPpPvuzd@Kc!ull;Zk+U{(`k~55MnH<-xKI z&7kY<+Ip2zWo{*|DaVD3d)-)!En=8<$j<;BbRWrr9*P zBR+kV2cvo`4}s>Al??<08%7(-U);KwZ;4-{XPh?bX)`Tm_IJ~jo+h;MkDh)0pMque z(#7?C0f~Uj^mN(D$w?T-MNduEyli-VHsS);^>+0nJkvJETE6Dtb_@0^b2m-1EBdh2 zCX7;GX3ErLze*Q+6+4HQ10xILUp=O(U zAKo>~HNEFpfZUAoHLa^Ts7^lQVAG71@T`2RzG;@Zo!q{kzAfL^U9)KeAR8rQk~=IReN~s4lPtbpbj9er;BO0E*SPM7*i#GBQ}MB`g=D0p zK+je8wOum5i^-lM54A96<$K=Cm!>CfIZZxj?qkl6I5|04nH$;I+TtKPTy*mzs9aS% zHEz@HzSz^H{wGalHyY@pWgUS-9jLhuvznPwRYT(~kVG)5f#!Oxz~U-;@U`o})LBn^ zz;~(f{b*`s;@U0NqWA8R!i?dTPEKl(a<_nJ1D3OC;8`ypZoVugF>a{2EO5lJQpUHc zYd5xa+G1OYjp}n~?b3GG8FdHROwSj+`9`eOg_M~drYC0Zs{-H+OhFG&ClH3Js>GvP zAH%e_asT=>+R<0U^p*O&T4s?$8m3cGZ(z>qVsZzLqdjI#fc!)TLL3q?%T{R{JHl;<^eya@xLC4_cm^bLJz`BR zbWqY+@#aY`?F9rBtcVOEE(!oq10P%@{BcyL&>_d{aTxDldbVD0q4sIP?16>sR>&#} ztO-OTTz&nsAHSTInola_zU>jIb8yJ6u^D|d>s`V_xEc2l)~`=$cP?GJbdunhwyth3 zBu~t4&g$yWUcn5@SK1a87ZwN%=}Khjtqgmybai*%uf~&n1soo%870QuTsO=fmPM%8 zMivwxgJl_3>wez3x!r7~>(mFp0s*)WRd>{26#$zITzq6rvO?7>i@MU~)WuBw4ke2D zzmYu<+Y8W-!1O|r z)u%9DK_U-5YNFL^GQ3qFD`Ads!6?1SY$U< zV@0*HjyMzR?^!u|BofE~)E>A~0NUZry~~}24y*Mo3+7G+3sdAn^PU597oYS7=dW+9 z{L>bo^8UKII#^M(_4Gy^x;m9AKv=1M1EU>O5*;(B6`VD7Z2h46-X}AltzZ#=g&{6J z{=RtfSfqe*TpOivjDLtm z3^NYNs&1KQ&+@NHuBNJ7<^`s|&wipF`v za1ByKoufZ?k_+e>VB;Ym06`RV%jj;^$Xw@`QkW6zN`{8M+X!MXEd~MHu`B_;6_#z3 zfh{X58#H#<{Cc6*BJEW?9o8bMEIRn7ddH=#<)xum*W-b8hOp9KcO0~U=t75U5ekZ? z8(zj0e`Wn@&WVL~f$l`PN8cNa;lLI3wKwxi{H(j<@1N*BVB=%Vhx@|4kvN)KJmi)G zZ2j9-AmxCQPyy}*;_H`z#U$ll`P%rr!khW=0$+pt6Fr7oHb)Ok+Z_9&qtEJAd1(R9 z2UfzCN8AtTkgO!%e(S0H@{UsTjFNl!n$`isOWweY0d;?enNnL@`^WHb!qw=OumHwP zqBV6(*=qt1G~F50*W6fV=|d+<%J5QvQvc^5j#D~pZhxt%E#I5J#2 zziCt0XnU}aovg#y&YZc|<|tE;RDA!EP^D1gyiir|g1$@q@9g#w4}F}M+CI2``M9`J zf$P7nd|Y7D(UHW%-o)~8n8>G3**YXH4*W)Zv+yMMV8h8ti_VlH*m}q@f0?$z$>$ht zE_Th|HWo4Ng)uU+U=#0K4U-l_W@%@q0*fJRKyW~;K>W6VAu(ajmz(FozcfYXpU7n` zK-0fa$=ksUX;@uFqfJ|zm4JHP-`#WuI&1KvLD8u~RqjW#VV`C6bGte^=j=YH%Dxy8%Ag*yx?KJ090itwX}@pgxWHbV!3VsW_MguZS5~I@7Wri$48V1PhJwwy)8T>l zasOJe^ZBN%Z(No!Uxv;u{hiI{A)|Ayu|DuEFrJ4L0$)AWKSQo>-E!%I(S8>mmZd7{ zYcxrVI+&7Ii+v)JW0{et<^|LXCMGgqhJF*rmo?g8?8=Zg6ua`s+iuOr_D~!4MbMAG zoW5?j0xt!LHbHj4Xf6LN%gZ{Gh)7*OKC%2`_To7uT)a%~W%3h1$HBqq*9ZOU;bxH-2c1 z;4e)LxikhwpUQWs3Lnv&U2})c<@MxT0oe3|7xoS;6c0COGnLgGqQ*Q{`G)Ize{~o$ z&cz*H2W!>`unFqMt1gcA8ecwNaDR8;9IIabj#!(vow2brgq*LCM13hOY_a~EeAv%N z*_%dL!sqZqeTrfG?b+rmi0Oldu~7bPukEW?Lk|kkcg%tAgmny_9oCr#f|z3Oz~n@p3YkM&=g=b0DueVlg}VnP+~-Cr z-0!2bC{)DL@{Efe+Wz@~Z26J0Yk}>6`=8Kl%Xr5n=*nNfDng^=c1R~IM`QIg5~ph8 zC1g_J+yDUq4!9_@55#I=MTOO`i_t@(spn>UTs9p9updo$j0G_{-b#+Jorf?3j(Vu1 zsaA4scxj>7W7d{up6SMBHdbP7e*QDWRj>s*&$cr(8JEXy=h7JWwG4 z8ZJ~jyV?JUB?<<%O9{*3-SqCFrg7?%$B#{@fS9PI<1b^D0Y-6tL!-vb_ z{;iMMv0?#yU0Qo>ERb8^^FMC5ps62JFx-mlJr59s36Z~bQ!O?GJ`Tx?Ut>*(U*bQJ zOyS>vD#T&$==&^VAeId>kW7iTTh7CNAC6-REYgygwppyRK1);=Bz1*zXRSSXE8N$; zPwy32>MC3eVZDTS3hJy$oo~~FF%Yp7H}Y6v^?KX-NrkP4u2#s=Nuv(7q1P2n^Qx+E zfg;U8=rQ-X58dWZCyW%RgBu1f;eLj=6W*+v)PX<=H8kOV-uTwV!3yssUVTZK*X0y% z0vCz@mV4flp{1IJWn(U?lkR`qO+uQo!^s0G?MvOoc}hlBZY*-O$LdDbcgDZ37@eFq z^bR)^{Te(rlm0=_o&7>_PUvFy=pxQ8*^?)^G6*tHmg|_V`JTD!x^Ph_O~Clao}5%^ zYi0aum$t06Qq+o@MjdB*B93$BK!F6kcJFAY{KTA(DBwLEmq=bYuJGIaT4<_`JW)pf z9wX_Yf#GJX9B|U4No=xp90pCL7z@?OfrAh!tI$-TbfJ1@eN5ag$#AGVS?6$r?b^@l z+iAP@-xD@IhCSJ>6vmK6@w{5W%;}7d)+>-LbJ$MFoVJ~Dh63Dr<(TmJ4!3bDcAEpD zcB>r245E?Q(X3zqV`fF*milBiJ^4t(mm9~!^fOC{Ye!hfjNBr)cn_cK3Vw?(Q(zxy z_^}salE+Ue>$ew~K;+ZmKJFv-NXfGxJx~1I*_a|GNawR#uO`T| zIjQCSPzVlFOc$WtNS|=mP2(;PqkX4i>~`DN69~4&^ZyT@0u_S*bUo3b(N2kQ z>l`WKrz~K@Z~KaAi-@6YwDFbul6U}cTSc4Z8bN()v~|f9GB_6fR&ARK`$yCw2o<3BTL?`i zo`WrXxgQXdRD;a3X3vjB+Mn36^!>?+8v;WF60wQY7}lVe?Ga0qB6rLGAJc{eejkKdzk>_Z5FbI~@Gp44`5NpY zA@af*)Zvmmf=7@x(m7!^x61#9O2AKo^KGRrJzk#7F*9sgn-?Kk2n*=MBeH)6((BbE zplI?<^e%NU1{^$Z@d`)1OhO6JE`*i>5jRE{5-m;zLlr@QhPMU$6913wXCpfoh&a+w z@nh+L80Iw&AKg3s-Tk{al0?`9@Ht`!_7uNk6r?9_y}b3(8WPnZl53lwYyYN4>Tnxn zlt}FlmPW*nh=24^vMRJB(1cvyfiz8EjAE^UlAa1X!oSBSk41h9kc!vjPR{3nHbiz$78~gj?3M6y=T^3s z57^pudZOH(ORj!@8Xk&O^Io`OI$3+un1oUpK}`wpQno(6CzyVjj%hUg7CB2_$X=%T=F@(Mip^=O{@i2(bIOG)BdZ{_X@Z+E9pO&It@b9CE~P{_aRH z@&3#I232W5-69oW3845GybUZo@J_(?QISJGBnC79IRh}PB+4J4KTgex_*J75@px1{ zhU&#Z9FAEUx=Up99NeT|W!yG!$OvL422i>r1MhwK;bO=IFem{e%S{r-X7_tCf{?1F z8u)|X8jv~oK*pJLt28gQ6fdTu|J+>!+$!1W+S#C(wj(Frphpykz|vr83yj$85l{Nh z+CVv#tI3_-b#nLA+5k+Y^ebdPbBE~>Ka-h}MHrnHF_KI*UW8uiryRub5KmP4kY{^b z0KKuJY`QRFI^hYV;%)9>c$cYx8JzF1gkKtnBq2mwC4^Jvc7D|xmX0=c`#b+g9}uDd z=_B`4uoNNw03-$a=F1?Oabm1qaz2$$P{ai0(=GR8ym4jdIK%v@i!|ETp)!_V<#EY0 za8jekw%&M-+j4pay*xs5;D~q?BZ@pyCJb zLGQg67oZMbN|AjnIZ~VxQnw7nyx#Z%fEYnDxs33 zLROL_36;G`NRmp5WQS1pUZF@~p?KKK28 zpL3n-y3RR7y9?UY1h{DPuTfSX#EpYGDnJ^Z?@vdo^1nZpQ00JvSu4s#OI$nRe=`ii zpGk!6#wjo$T~A_}&$lvk7XO;~v%Z;DuNJeXJCK3;K0(8rIfy-dp7Fb@R-C-CWt8+$ zIrel99=bV>%)jIUk&}c;-+t8eM)=lGS4b2Q)Bm(rLK=-!+xz(W7D$rBKD4{NbIiY4 zrU%aQ)E1X%lg~w1e{$jo*1I!ESZwr~H=U))G*aF2nc>^g6E!wDts_goqM-p_z9l9 z+d-cSYWzpvR&wR4~ccc*ZdA zB>JKV!PHAjH(s-g5XQVpXfEFZ2flsz5@?Xgu(Cso8bAZKC05I@gO9EGsn>F(@Zws- z>WzuIN>GD(}L(!euhUgHR+2DZMDZ*vCzO*lL6ityv}6D#3eFxrmeEIxZSs~_OM-FWM9 z8-;Mf#e4t$)|=Uy-u>m+N{+H$ori84xfW&!vz`>T;N^X>ubO=u2WcYMy(0=;oOg@o z=jY#~q?}`6VF9l#zK9{8&gd~Y&d1lr%MRUfN^40b^O}UZAF(tC24y(68Ch9-PHI5k zLgfLj6RgJKkbxUYx^LeeqXuG7;r-8DN=vybH7#xTy+gS|xnt2%Z+Bkw)hE8yfBs>0 z_Ru$z`|&&4yyoCFF;~WZiIAf5^F_(SC_XV2m&I)KywKd61|QJB|Mgw7w9F_dDEJOa zA2ctaf}!M;l#~R;bbTrp_D+|XKCj<@iwKJ_7D?Q;urLJykD_^94-5I{|2JAN|`o=b6A^FHS*&^K#o}em&vB}9R@I?RDYw;R`Js?n^k;3hq z3W_!mD(3W8@Y!MXeSu<4(qr`+3f~o*8m)h7$5pQRh22`knU{FzZnGpNT{^now`pda z(KnNiz8NeA(ZykFvBW420u99P%1L8<&5n)^^zc8Y z3;HRncH$hBT>PWQwMN}c(Nq;`$?9gGJj`0cxrh{U81X!iQAwS~19>v+VRf4}17Gf*`Hs0dZwQOOy?xGT2t6TzK;=bW(vqMwCD~>)7=hx1iM$o3 z4%Z052)|5r(tvJr8SpeQ_}Q3*;wueKtW9sMTQ*Pgo8!Q)si2>%rcpgjV_%R$4!q=;F>0dAS}Y+U zk%}M7qb<&4uJNhzYrB=j+iOF7p6z*!_rL&RZb@rjUtc;scfD;nDt9a&BWRbB9(o)V zq8SBpt$@Xk8M%K(`qqw)NP>OH%}qpX)#e>(zyBU;aAxL-7%6ux*u`}Z2(wa>5r4k0 zU=O?fv8ei1-bxqewmT3cV=?muY!dkCX(t1n94$2BeMj6}Xi_{dSvR4pnfpVZ3x zcnY_O&>%e5=i($gE5Lh0%M=h5O-_n+QHIh6%`at0AYScFuE}jjtVTj8h7tsQ54QGk z42WHxi`z&=geAQBxbMepS(_m2gzLDRg{0AFeIrc+4S(R#4cGoQdP==_6!nukh_R`+ z3=CdA+pvf;huR7*tOZ>oW25(-vYXgLmENSKoyYbn1g7ox<-kPaT4EWLAeKfUl-P;Y zcmE}IWq98EDDFl^(z@TCPf7aWp5a@Fv}&G!4y z&xlTBGMbd>IswXr*%V6te(^w5&aj?`Q85xg%jV6a9~_Y z{<(Z-2-;#Xwn)Ng@WbBy1QQ8El=cvR>%1iiF=1?Ks+IU?_6`oM?v`MZUn8-8=&3;H z1KEmUw*Q`RrT-pusbq8?v=q#1@A`u=LO0A=^Bt#kZg;z3?BO{#<|=fslx(a`LyAbb zvqc77Tv$oP7r)-!=id+6w1@8jO_mF~f(Lq@}|LvR$ z+NJCfp}^Gp(hsLTK!@S}{Y^77@2ab-iO99qzsvJ0_R{q}@v*7kCvqWr1-EoxNNITs z{6#d~pee*1LqUlj20!khs~O^GfBer2W51ydb@<41uTHG<@P0zYxgfdl1ggH7-^3l{ zIJ7a{f?@&71kpZ8H24s^v0{7U87RpJQ7S$@-q-Mls^MuQI&{(DW*lE@Kd{#db6a=xEh5EW z|5*#sw$*MDH;Sp>&E@5r&rp(9@+A+FR+D;6jEJ)0?hRl4U-ow5I>32j&ruWva*1oTtuHvY#WkIax#7CBm*fx@zn>%eEo=2FYh}*lcJf1O*=KuvTXQ?e^)`0beqrLqo0+E>e@_W4F1oaiF}fRhUHwvMlBN?lJ~s9ZBYyzLE8K69 z_?es-*DMrAr_LXnHAQQw5ZYs5kQl>;*2u`nZ)mei!a%gjrsqE-GIqI?Qj+^@_p2am zQNRawvjb-c_>1vx8%f8mri+xkuqZLn(CPIE8U!tFlW%crm+iR!k=U=;rTMRMGcB!t zZkY2_?yw>!Ae%N`h|5y9V@^SV3!7mw03#kfj1gJ$B}Ztt#faG}ql+|n?y=8%=d|=C zmu{)^r z(c3|OAanqJUy|av>(`@VU8h9Xejbr-!b?gkjE>=_N^tnm)zd==mur}E7(Udfk!MzV zV6E5GGf18tz#2`5gh2>>s0#tQt8zp|GnrxX`e@OmWP!uva3L)BER`e?1L@HA*AX6I z3^M?J^{!1h5_TeDYu$Im@_}0YlKqYFH!R5&hbg$tc^6_53dD6{-5jox+2y#+dN)-j z+RjrD#}LiSL#xe*#1}50uh~#nDyhq?D6+-0+3bt6%~xPkba>$xW>)S)E*|wA(KOK3oAeJKIyRBxaSHjnVVRpX>?6xRJA zhBSI+TI9i}usG@ZA680ENvU&?5)u@A z-6*}Ws^&o@lU<_?c`7BN(qU~7u^|i{(n-XhSQQ4ALx^^2+P`9K66X-98^XV?OM7Ht35=We% zT|Wm(k3Ll%1>&AwsAjVKP;j{`9R@69-VW9y{Z`iqSUNpzLYjP>q=S4%OuQWbTeUGc zw~BSQtw2BTG7QN-_MgFUlC2q)Wg=P>hTyuer_PFf@AlFBk=SPG)e33A%=#wgKez|;qTd_;?6wWD2 zgzq&HubGP+>CZ054=?v!yt{cl0fz;5VN z2YM;;aim&jKF(I8JrIcq7l9 zE5y5>nsRvJNR43(2>p<@O-|I%xx2f&Oy;x^lnH*q6`P*b89(Xo7~fmU6{9-mJ3WBt z?=l==V$my$l7y%wV0i(CHAPWORLU5df$$EW4lujLh6H|V8T(Kb3DC8Yb%7{YOie#E zRSlw)0H7Kj43RcwTiYloEL^}s4Kw_&w`2r8+62NJhx}ZkT|;vEM|gdZInBIjLMo}B z`n)lz&l4zSdBk(w1ctURt(#_XY>1ho?&x5Lu^=5aFMfe=!$ItE9$|sc3ccM(tudmu z3gNsrzTqD6c!nVEW7ESKLe-Q2kP!yJc#|Hd0ZYdmjqFe3uD3z(TAR{`$Sq z!;v^!#PhM}wU&f;gUEFRmLxoWwMGQxqy^?;)=3+e3zwL1Fng{S5oqnYhYzLSJ&3%9KOcMguTwMLh)hoY@MN*H8Z*~=b-uGAC| zWBpb?W@}!O`N$$6jZd@_X9d$Dey76AVuN!`9O4=j-04&AsDToIPzYd*0Rc4^)Mis) z1;!}o-sX=V?+%sP;syVM%Rx-S1Z@by5w<%pk9a09-=RRpfrDfEMv>D+Jg}_Qiy}a$ z#3Y0e&s9Ur@?E=rS=SRcE|^6;(NtjU*L01+i5N*&4WbVnNPAa{D3JstSS|l7!)rq< zy!Z+*m!kHm<7O^cIO0ISTks9m02db*A{SuG!@N#l;L~`;M>63gTH_a|xj`(cEdG-qJg=mqf`a4W~LEQ`% z7D7zfl=n!sPbJZ7@t+qUlakEzOy~5Okoi;OQ^lJzm)+j3mRp5RxlcK_&Pk3udGcgN zr!!i_dNW495Mz*&4fl{`9_Ao@Iyf4{Axi(i$0&p>=TOdJ*+(q2WU@45x<)7W<5WDl za-$2r1&nGZ;p#B3uvGo}^(#@;4||a9HxA%G=SCW5f7b4a7PH?Dr)E*fh1Zw*F+>(1 zw36U3axrh68AB~joIIH|>5@`wL<_qE)+WGxfRV-cDqP2p54r16xn1Ah*OZ`$x#Xh! ziw4>4GyQ%CjEA1YMMjpR{^IB7zgt#Tw$mYVe`B0<4aQ$Ib$16vMlzz;J)x*5x@~E6 z!9}~oZ9kuGiNwj>{a|QO>h4eae)0MsD7%CgFZ60AZRdv1(~_7!uKgGoU?+Yg0f8s> zx5Cu9w}vW_73&}?QK5_B@nc$4J2qlVIxKte`R~TZAH)Ws;h*{VnwS&twW(=SX>V!5 zau;I|Iesa>rro-AE7!4Ob)WTbXln-|9##z59Apf7+_ZLps^UdrA~_cJ6h#+#$&e7) z@$f$W_`#5o59W;o9FLvN`&FYwys&Hdcb0Xs`op|l4<`J*=aG>eIYM|g_q*@(=E|I5 zqanT$UBHmqHqyYX2!Ofucx&4EUd4{^=HA}8D{)78d8@Bm7FV4kiDLY-=8Dt5UBB!M zRB{PTGMr&$di*b5zTAwEgjhJTieV0Zmy=Zc;VXW!^)B0k;|Dj9WSM7!_Q_5B-tZk@U#f7K_=SbTTb2+J`!Ep3X?gyrHZMI2CP@K7!C9MV z5oT(P0%k?*Ub)9x6?=m*95ch#iHmZOB zJ~=&&6utl8&%QpkRS#*r^G#v3m4EZ_GoiL9hXK*?c=kC>&HXBz&ClkH#X1jM7gA4Y z*^WWUIQXZ-^x0WM29Yorz`xrtpUpT|(a)dJ2ta0PZayeDj3YXL)VE`OxvlihpVfKB zMGRrbdWB6e^AH2O$^X-s>G#sp6MGVh4(&X0~F#USN3&&<|J#`urVyr}%6%$Lr z{J+o1DeJdlPPW!gX~}wa;Oy&5n0H_E{rg_?SBaOBzi@L$|G1ue+0*UR<5~{f-5IP{ zVFa+wz|F5TpXQ+pxQJYCKT4Z!eWEjv+yVqyU|?V$#2;cdxEIcwucMuftn4Oc>YDV1 z5Wk(pIw8x;E~=sWJaiQhm8?MLq(*OKoRX3XCI)hs)9bpGopa(0RkB+BGt2UH$3T{| zU&W>fmE0jgj5nEal0bQz8h8216??W;_V&O%9;FBFo2$y6T8 zys@HU+#Y|90rZE73Enyb-gp3m!li$A>J&MZag*()B>PlXcN)tU7HqxQa&;^(?)mAl zM}=l(=hK8w?$>F*KdWrYJe_sM`-SuU7r7ilNr)eSCq)+3BXj-OmM#hU*2efWYX}AL zM2L0fI)1MZnCba+?zNb8w>Y#1`Lr0F^jG7$M+`RyGgWlx13tyB%6L~D9aaLs%oT*o zvwMM-BLg$R%rk9sHg)*Bnzdn7?gs050B7B;c1-Do$G4Hq}}=j8Jx z%4g1O-M(Xo=Iwc0j1R6$yd&A-F{(c-a)mo$m6er6PKiUViqFp81NFgZ-<6^gDYx}i zQ9_dY%SyI+)5C`kv$%K5(Nsol-A1zclyb|4a^S^}LqweX>Edya(wi-q70C>vWrpoB z7v^O8jY40@i_=_>T~Br05hkvsftA&xtJ}M9|J2phnLFH__!%mJfBJ3mokZ=qj~&}} zFT1VZst>5AC_M(p7!^BHU{KJ|suW7C*(B*T|Gb1mY!FaLm{nTnMl(XsO%CcUxM-=P z8=LyD2*lLVvSw_Id*shYPENL9x>+Y?YzL@FyPoId|H(q=y6P#?gb-5B2lo}B3?32^ zFtwi#8EJ!}g8$t$o}!Zi*ayXYCr{j@SMtOOFTC@f>9m(G4X)nvgVUOei%V}GiE7ub zL0ENXT#8fUu3Wm5H9k=169^p_+XY8fu?E0;WpOg~q~xaF?Gyk#B6Y9$skS6EIlXEn z;W0U_CvbjZQQv%*jndmSkmJExE>7oIBU$@FS(*$MDt;=|2~9tKd~%-b=wJk3o&`TP z8*Kd97l#m)3f6Hv6)X@Af>Ut`h1TiG?|4XxSFh5eoDyx^^V8;U`fG||>_I3)5OEf5 zK*(Q51v#me)`u?uIF8bN#^z5;Yb$H=N%+OCU3&oNf>C_T%O==uz{SUBVy}f!eAw+p z3zBf=uUo3S!s|=%Z{9rkcBoVq%8ncOvZ70Q825KAt@U(vVqld4k-jrjxbeL>!;U5$ z-bQ`uWCWG7vvaheEE~BUde7GHJF=`>zvc7Dt zV{gmE$f%~HC~xu4+<6{bxXNN&J{RG1cM3fLk8~HH{uzo#NiDk&cMj% z-kcUTaz1|i_)v={?qrY0%5CB#FPkK%rry)Z=(Q=1P=4Jv>-6sZ`+IogsOcMi??*?6 zI(gsDiow~BLpIi84Gg}pL)_Qql@@3e5*GFYAu`K&8HLMa_xi46b?7){?W}-Gh( z%hA%zK8i&@35A7BUNU;udqb69^9u^z$1bkp8Hk#XlwbRqQ5~W*up~ea0d)TTDvHNz z85=Vrz@H8iCE3^R?)r$w0`z$C+NB;W2+>6^2SN@=d%PKcQK-#}r64l}4$=t`(7AAM zom;qKV`8chd8$|P9m)amVx``&K+ZW zH&7dPj*f?D{YfO$-EP;M34DlBxQO`^;t}4D-|`JS zHyv9GH0YGP?iP%yCtzn0FRlNJjEr62p7Bor<&dpMiW&?unIm2D@$#{g4Tp7L@8%-# zO~@PiK}r&z(31FSuxSa$fC4C#cJ>j9h7dLrIAlxJ|#;d zUIRq_;luN}0wh>C8nD;U;KKAYWL^>L0WuT!%l~0NG`=0$Xh7>R7*7-K{_c-vIpw2v zHo=Wjah4ZiHa_N2OXqd&jH<7WJX~ffP;QVp!@b{%W>^e+iNL`S_c=$!Pn)*}dqaYN zy;;Or78!l@)ljGaFt<96%>Ct;l8W_jxS^?uIsJQ4>{c^Yp)1b^Q`Bq}-qR}j!3=jS zs8A=vByG_B`R?uaIozCY@|@=k1DZ`Wxr{7gmcz&%`rzu~Q;QPMn>V*YHY1hntG(@T zfIXD*WCTyr^l`i<)ugZ%eHKcxKAZfL?woW1Ws?qv7^t$0uW|!U1NDN>@p#c&idfS; zDu46lP@*j`I^;y+U_)QI7Bj{DbUQZnF0df-;L^)-?VaL^B8!_tw`+BZHk~YuB`0dx zSp#;-TQrp(o$M?RN60@XK3X~Pn2lPll=BVW9^CR?g%eN1!{;9Q!pTt7Og-{=bo7>! z1gp;OA}}FN*jbwBJ9+V@iwh&+3MswH{q!ZG|EbB_CnN4*N$=IGZLvMQC}$;DchUNj zrR)*q;|n>p@3*X09DXn3Ozm!FZSmjI4;Jw3nUz8vb*Y@Iy;RbM+h z3(FQMC@7jbIv&1!`SSQiE*8MzgOQOeY2X(sA|uiI8|l`)of!5IVy3oLh%PQl))h-xNk5!B*?zEIkWAJ*Drj!UHdfnH7YNfHCBROx)S&^X{sb~6 zKy~sQr~CA1=JboD>&O_PO7}44v8%v-2Qpyl+7;gLL0C3WdzKFj48&*NLRp8=qVEz% zg5}wruzZc&?=yzw!Y`noqm^g(Q;Uag@4kKgfKdYa#Rp#{6!*>xeY&}KCF(03f(Amw z*HOC&(>3k>rIpzc>5cu+8|4HTyX_`p*4r@xwV@;rqYzvC4}{c!>F}0edp9)#sS{4V-yEY_!~G{zi6h#TJzu-wujpnkwj+j*lsE=>D0Wl( z2^Wcbi=>*+fu_%w500lIky*=As2Hp~s)(})?U?(uc=M9to|Puc+Tl0dyOukAkFju< zWd!X?sK>NJv@|zFe(1Mi00q4=tmgLj_hED>>Ya?ZcDuza=jDqRB>aJ*%Ym!|f<2J0 zdFXa9Q&WuF;}}85ch%u$KaXYF<^B8&zU}Yj@k#C?Ky^+^*|{%*igg0lEwWV{Jekae z3%jsbw~+59d)9q@hP_z5oSGWg5>FeXa6&}ngDk0U!3CSB>XxOD-QT99G-1yV>}8Sf zO($*NEd6K~S-9oCbeu?Pk#<xPUD#& zV=!e=|5@|EovlRwW1Bx-4R!_rc(1V2)*DMeFvt2!!+O&0AO+{isDIJ^YhpQESC2LQ z_^GldWnz&|pDI^rCLUv;8rDvgqoHu0;{20+i=3pzY;geE{$zMU#a06opTkEJZA=2b z2Y32d@aH)^?~$PSo|J5PASOSu;J%Px$JQk<0YD zuP*2~ZKtkMVrS^^Xx9(+cEiP}=WU*>0ad24JzmF4IvmiBd!sk;FK1!qiBKixhg?lQ zMTW%vEPUD4YHcCEK*1vUY8-WxY;>z@BaHDa=01eJ_?m>YL*A?|sme;*o+jN|4 zY;1I+cLTz6hWVe`cbh%*ZsUkg<48i8K7H#RbgRjLGXfucEhY-U1d3AC_wV1mbj^Wo zAhl%a?D3bs*1Qw~=;7e-EN9m~I=XVR99Cs7no!9zXU;?i>yWN#Yj@FKY*DQq`2E|a zA>ZKk?VZ4+qCtAn6Xt>;HQBdrJ+DWM`a`G@_^Mqc?o4HRGivSIX3~7JGBZPSG~7={ z$fJrV@1IF5jf;pV1O787PXzn+W76S){7d11Ks{^g-Kc-NTBd0z$-uJwyX}+Mz&)+3 zu?=-hR6Tiid(lu4T))Ka788riE`EhPjji#~sz>D6n-WJHsVdm^Ztq787Ue;X%Cen+ z>*G`B(Eg;azX@!|xu_8zoeV`RW`1>PlsqaNFJDKe`qwY6=`U2MkS-C^tjcl>y0mb)L)C(uIq^wMQR3!kB(n`Ay^Do@NosMe1NVT?MStHk%ub3X4LX=`bD zV<Lcm^voJQ{^^_4Vxc%s3EY8+I-lLOnHr+t-&hlhyD02w*b znmlyaLM-Ojup3Vw72cb-Zw-oh8H1*@hPUQx2P4n+iTD1c-n+djks8A#yik3wf&poW zpp=bQ>+5#xC6A_t3c>^A`ju2hYL=fv_?$~YN`J(MeK!1twbmXAKelV{J2K9=XRa^ z9=m$v1N47c_Rv14cs+J^lX8D50Fm}veY}%r!tDFqp*^8_^o2Ti=7$d-;_2whBuDX3 zeoQW^6wIKGBz7hW_?w&scGODqtuiS4{CRtkj%DKcGRx~9}fT%foMSK;5Of+IR3f3xPY%K%>hat0MKC+ zoY@am!|H~(czAxQ>|{dT78J~x-s}hS-Ji8}6^?HjpwO+Qnq^;MHy}V&^mrJC0l8bh z8((l?pdxP=|7OBoIR%|d_eFBD|0%PQ*RQw2Q1d)GTH;;#`3RL~{x1zHM&|TEywr_A z*g?am0#-_-U)Pzl!>*~Gk==f`aPrf=WMj}qao64>8ANVjZ! zVF0iUktEts)nziVc@%s*DXjJ$)*OTOHgIxc0INkIHsmhm9gS}NnKY8Ik&#iI)Vr*# z&Ea+Ttwg?Xlkgek!2kjXnfAUrEx@=_>QCR6BDhte*Sdym^!4=g+ANfkn#(MNPVxVJvsJ_V*)FL+B$#RH z=hNEU@1nLPM3Is9JXBq~JZ84Q%Yr(O0b!#RQw>*6W+X`M78NxH&l*p|Gd3Y zGws>Wo;-Pxo=$C$`RC)pl~AQ8wL%)$(rG24@_KvrT^!9PPh^tS(=FTD#0kC)I9m9; zt`fyiRG1P6GN+oF8W3|ZqB#=c$*(*p&+to)YcIGx{2$O;jUr4BCOR(R1mi>E=uIyj zics;^f4(z}wyY-u-p+&Jb;#7%^Qvs0k*$aPA{KL}+xwFqKi(HRGxg=thL_SXeuB4R zpz>9@aqewfGz(B8DrkT-JS9YI1i5i$fy!2)=dr@z+`$3h=R~!_hPeU9+k_^OW@X~l zKK5J5@P;1({T|nlwf4o>*4VIup9&ERfCb1+m>JDi#&Zrz8@dTiMlTOG?+*R0)81|* z!Rn`)>`-`wBDZdq`UxQ)--J1y?x(kZ_dQ%8?c zhZbrb>%^2;kg7O3CGvKdMzNDZIchQ<@Za(9zT>kSvwz(X{bW#}m}`(Dgu$+|1Lu>X z^&;lmTaSG|s#*wVu1US`GbIlGlP5Qq_J(+Sdmqm@28FJoUj}JaG&z{RCYa?kirJBT z=pJDE{WyI>AZkI2KN)-?cYS8{ukFKX54_SqLdwFC8H(9T`qMf(VkJUU%+AwGKi00N@Pt;jy&DoRptix z8vqG%E~^Qd?tNaKA$ZuUOCh6zOB#m3EoNsR7vOhnE^!`mcZgmuNh=9&WR9)NHbGd& zuzC9Fl9j1~l9{nJNpQg!FHkl!!#}<@Y9~fSvp+DPQ4smx9W))~eK`~wd-v|e;Q;Nx zh8iCe*FO~$H*cl{d3la3Cmi4m$P$qPe;3y1dvfvia$Cg`^rln5^#Ii{EiS%n<70+~ zTvV5EwI6_HsOYD|TZS$mf+{3dsG-**$b=C7GL*D6-FD5O)}4JKX*`o2{ax$ z3Ko$QGBkMVg;AakYg`r3P`GFjpVCp{K)yT(-4$K-9ie_KiM#A@vhmIEz5t|4^(W7} z?w=AoG;=$xA146@Ui+x2tEVX;#-s4?8s}~RAFl_$l*3OCh$Ue4 zdTMf0Ud5e7FRUMT`(1m42;8f&7-V1oUkPK(gLnV)0yK8U>NDVxH~ii)`uLpsom)$z zINYcR;4!KwxPVV_2F4)H`*X4tMknJDycZ%-=b^S}IC00#jTtbPKSJetddg25QD`GC zG*Xg_I(8q%D~1PtH#kg~biRC1QDu2nD5#6^9nqq;HUis=f>Zc8&2u_fc98I20IMT&Tl(Clj85h!OLG+OYH-koAHD)V zYU%4wR(J%Zkhfqls9)AZY=iBrn)nn~s!#grqnd)cXZ^h17-1DCTl|=pgVvkQwzj-U z9?HU7=D@I!nsJ;#i2`ET-HAGk+~)I9cYb**k^=(+gA7#l`EpGGivqgz#*qq;AIaUh zvfNL-A!g}h_<_*EZi-V|m_u1Wt-(T2TDtMEZAJXkT0v?m74isL9#S7(Gnl*oy!(zK zk{V7i5CM3R*}k$ZfN77Ov)7$@RBKcYF$mHTV31WGotfVz@O}_I%FE@TD(M>=`%YLz zF=auHVhB<|qzF^9i-H8!U%b?F+R}15-LfOZYz4^<%|bXNoBrf-E`rU%3Trt-qi5H6 z=m2!S#2*d{YM<5zbOU_@X2g19eN8e?78AR#@jSCvcUmSr4zMG?y9UDVAjquPi-2u! zTE?PdOkQ0|juuy~GO(`R?F^SMgj{Pec4QLppLu-LEHGq%AUVnaMHiB(s6Xk@j|dzs zh!yC|&^&kQRg5n#GhA#{UM>VA1)15fw*{pv3@Sr=V;dS8%7<)lQ+->UAE{n-`))qU z>ihRp+lnN@LjXPB036Rn!v+~`QJ2duNw7BF%4J6R8L9*@XT3ast7v0=rARq*erahC zhJ^Q5JMZD5V*7{5H$BUxFPiM3c!L0MFOk+EjS=MwmLnjFAY^TVPXX}kGnFruNF@qzkq$FeyyMkg&D%u#WJ7pci^Nd+`2XT zY&-;=HUscpq{bb3s*G*sqHZ%~6xYQzbh{gGKf4mF&w!eb0ELK$e_%{=TFQfB1BGNu z1PdpCC~r7`8?gF|ygYi8V0J>GTTzuEYy<3%XXW1hL_&e6w6(B)t320Uv>{aj+@7}+ zCqRS0y!Y6>X)A9Sw6GTAaMd(5nlmtV1&=Awp^F^!@OZS=Ex(;Cly@?YSM*yYO7-i% zz3BAlnx>|gYEpvFC<~U^?4}|=HJa!@3C0xdG4B9L{A@d;6Ljs#@MKfd)4ws~*NdVd zsfoF`VoQLjz(er2r*HbefOvJe=KbIr8wxb1pos1F*-iHEY;I>lX&Ow;g!TlO-f;g@ zHL$&i0(*dhhlho{C2e8UyDky=@ z>BkOv0^h2wbQBdys9(Kmt0N3T1WBmDHWxe@nAUq0`Wl;LwBD@4Na%hvN!IOLdTS|F zg$zb+0`x(XsV3~ZI!y_9njqqUbH9w9Izg2^_@L^&As}e{yLUABG_ZB}w3rR8cEU@B zd6wUFFgP>w#40SMcM&G)jqCh<&Ze{&SCUY-fvv4Z69J+nbRL4w|BEyFqeaC9u?Sx( zdcZe1`6y^3o1SyPP<2Dzz0)T9NXT{2x?UzF`8B8s6LO_NCQ+K5ff)vI|J9|Ve7lvo zpUSa^eng%_v0%NnY+nR6@uNf(q7-~Puy+Lyx8ONIpJ92k1YmA8`t2~j=AC6%oN`>4 zK`@5Vb6Quf?1Mw7_(BhNPNn{^{;ger)KaO4Hs{hphaSvrR znjO_Y8&YuXeW;RF2OvbdXkpkqJ63&_Gwh=JFm=b{3*A=?%45iAk-$7(QtH>i&@LZr1Y$c_yTHvP4S2FFHZ}X&cy6VX!_+P zdZ6zZ=7@5dxGE#GK$J(xep}og@mPg(59$QAr^?9kY8o2kU>(P$6crS#)|MB(#Q*U4 zm_&wNJ9!S;T7Nx`HEPFWhx7nTU-?O<^6>J$EG}jNq)4@&Mq>{A5;JgD>zSJVlIbc$ zqHZ$n`Yj`Q9wrpfCYDjZog{F%8lJ>=nn_;Q)`p2qcJp0#w~bLMfzJI`n{Ni<#N$WN z%Wk(qaDz+gS+;s@%G_C9UEKyU67Dtfm1Xe)9cYxiNp(7Zd8AnqDWT~>R@O;`37t6l zAO*P1j>E3;q+vl6Iti}l7LV1z11Mv-lH!g7$g8W9Mb-PFT)tU(7Kx8H{8$)^UfyPY zLB^eojEo{Ht>D_HsTg!J=<)u+eO%W$1eQ0i6M6eA>Ep=_7%^1*X{F3R+Bv+2aQvbB zPFIoh9^4O#v3yi(7_bUJl$StDbN~MRXicw0P`cg(WsJ(KOTb(UK+n}x{7z3%ZN4h# zL`Z0riG#!g`uI^sVS*{723SE)fU)WO_j_mp-PwNd|4mShwq{UB_nhoH3doIg6%mDT8PVfc6ReJiL&*+u-8bqE>aPqhR~3m7(UrHCyCfC z{m}<%Ss6d`PeR?oLWy+LUC1CE`fLw`zH?|4& z%{59NDIAu?k;Rh+OGkMA@HdClt)8v(#H&%JX$0UKh{UMMCtL*l2BIJYc=$<1(SH4Ud#*6Fckfl?Z>%7%P)6m7bO9m5O5t)sqoSlq^tQ#ml zrn>Tdz_qn@lp$Ob_Yfy^hDaWz)2QY_A6OYbV1hdgA7a`O%pVyz4T77Kg-z=WXjI@A zFUG;1C&jm6%YwyIwVW!dOeR zRP)6n3{(U7cc42f0CylvM&=x!Jsgv!lBQ9i$8Tg7(+e&cde`T6iC5~}ghl75FNWWh zfu5PpIm5Nr8!QH(W(o8zl225 z@rwZv#GvzfImd#=A*##k#+0L@qh-45zv)o|>&!vysYu)}09b2{u2hhCcmALpVkBIZ zT^}~#w4Dp*P1{qCO+vV8fK7%@PJcd4Ncwl>9vCv>qSP#CGR4SGW`;v|EDUfOX`*NZ=BUE6^ApijNS((X7Z z1h?{^+6x|8ui5%>8a}uza8F;Ng6RR;Hq$+SQ1a;5{53Xau;!Z47JwF8;<*@SJICC{ z$s+7P`!|t7U6n{OG>SA2tqxPay#Y75djvdRY!A{RV38pX-eiZM&c!pXUNUT-oF5wS z!YBZ`RzLHa!x_p#oc`(ZhuYA9X0Xk%4D5n&_HE}FP$1A0nmRiJ0HLP-io3VO!US(# zy_x9!wJPk^G&yaXuMH}hpMmPSSQ$bbQEr}$P=WwMAePJZM9l7J5=>GW-_4J9;Q zBgGnC$lIcwj-L?_flUj(|3O5h_S>S6aAMq*HlT`&m$!$l3uZRpb71GxmnGU5dp(nf z4klAGvyS;C*f!hJZxAL_C(lIKHQxt*L=6Hb==! zQ|Q0xmtHhp!|g!$#FkGIuBSn%Oi_%`_=8=E_kjJQTMzXRDh~j^>llB-m7`w5W$Blb z_u`FwZELF;{d$y}n|KMx*-Mii7)^pFEPDC%(!v7l3GCyiazS9B&%L3e6-6pmLQ*8g z?RGu&th?0n0C)h2dFnYd60E}BTaCzWn%-~_hw=}m9{78rBeIGxdv+DO(gQvGQ}f9( zuknnGtfP>YUTzARiJ5jYx#2QB`NbT9FS(Cb3oF!+8F-RYFVBONgfr3hVIq)1wCYH3 z?9dy?s;3+9jazLpVhYAdEi77-o7>K zZp8OP57P-z(R>r{^G}uUm;Q|^#rn{T2{MMAt< z>EtnoSLQ{D!aKSBB;vG7dR1N&Hy1aP_9hO$4UDuu?c7cf7(kgiOY$eo3Fs-31#^fY zFfMwR#KLSx^}yv?VHO0lv`4g{WVRBA9Gt+!ZUbmolL0J{T|jo)=!m1vMD4mDg^@G_ z{S9kFj@=nVe-t%%1UEN1o&JLzqJ&Sux#nUk#U{A@5iL$XbP@t< zKy>f}pAZ>y)D|5J&@DM={q0}-A!fo22GEv8`<(FcbM4pzMi4C|Nz_vW-HsU0GcSo6 zh46bgPIc8PKLU0G!WBxbhhZPANA{RsR9Y2!^99Aqwr?X|NLIA=HnX}lQ@oL^Dxb~% z7b(Z;$gBl$!=iU*eas8hzLY$#CfVsBe|l*9Q)RdW?xR~7Y#{M1&?fq~P*4FcGp&!Ft-v8#wn@_Z7C zb7*|86Dn?TH))<6BMG~w=X_DJ=iuK z*2JNl_0B-pom4mNtl$U02X5pbIodv0A5O~j?tu2UyS&N_n z@l&~G?fQ$R>bnGJhpvF8m3&(YUANh-phdHmwSDyoe4qRPa=ZVM*pa8ow|{k_K*q5w z(|aVwkh$Oz9(flY>z_P?%hka)n6O+^OJn14TYQ*2yC{G1VT>9XDRY3l0r9ONrUu&n zbz78F9naW6XAJ(xG=w_-pl0xc^W1QWKU`L1fD$Nv%sU(qZ(!1ReJLWWLdjoVZakO= zu$}0UBBZED@2gyU2hcC7;OHm}?tF2c6&!WO{02y1bY*wuI)m~7?^13mkQJ;uYC+~Y zDspf!7`Yr%1zy7Kh~rr{F)@+uX#(twpQrUlPItCMb~{x<6D5=e0G@ETCX&KScTBg8 z%#}ef!DPVnA**i3bIY6%gX-A7YQsW-qYTf(>X&fA$1``3A z!6wSZf6eW{*CFyZoCY`TKaVZAppcVr{(UcSWFJ2E$TD`AknjP}5CGj&hhBd}jH;p! z&j_?bXS1^k2QA?^0}qVvxjP9v$cD}m+%5JLwV}1JcX%N&_FjLtZ z_+I@Vdc$?UZ9ajN{A+Tup3-Q)Z`rqRv;^wUutlo#<=HmsfJyS-^B<($_QN~Hd;ITL z4-XDs#(Xq}76}F|>L6siXfkyYZAS6m5IqDc6|FBI+9q;O?EsC4i;2SDRDki9UBOO@ znCqxO?-9MW_)ETZLmlC7{urIyhWLZ(U!x{XjY}RL1r)&!Jfs&ciJgOJ0{AsLDvREZ zlxBkXAgp4ntP0rYlD%?@feIHSx(#Cr*b%ql)r=--&M$?6wIf`79gj6q&R}4F?x8#W zK4)NF1N0>T3>_W3eF5^m@cE$T`7*oxFt7(zrGg*iv35 zCK^{4!%@#Ft_LQt}M8U^H!w~^%xdE~L=96lO~BBg;|Kgb#~{ill?`2*rbgo-iq4A46v7#HbG#}>Dt*^X#_ z!(MMfsM^dd1iBNYIs_z07PxDAnb({`QlK1Gef&rQE?dm8Q{ip>>o>ICZ2Jmj7;0#2 z%?^!=3xPz0ALu2FKTB?WaD8OAt6jY4h4a;8s|$$|nt_QYo)SJ3etu1pXr#Kt}%5@lzogPB^UiN`A(VN`$1EOSw|ddX5mp^XhC3o=opodK`4z5u(78@K|-~ zG}(m|1dX$^;VaU)+NDe0@Sa%G7$t2Ly^|Luwlj0VN##5$3wkkz#` z$|@Su0doRg9301ukQYWsqo)&^tQrQT;&uU^h}trPn1FLwAE>@2?y0J|6zm~zM-XHH z@oGcQBLbrP`GfUfbqVut==3~%e73AV0p(%qFoksFIf9CJ9a#|czfn#`kU|24sVYn= z`*iHvD%VNy#PGQgZ2vGZZqv#>Xpw-E$9#@ArlA1Rk;qgcrGZHO@1xY&3nTmrIx$(O(}3!dW$Q z?*T+rw6fiU$pySXAG+<)5jHrn3)(88x`Lvg1X#7&V5kO@(VJV&yw3u6aV z0<0eJ^P%QMEi>^WQ*saKRn;NXK?wUuIX9LHVN%1J#$mQj-vQ4txhp1PodMWo@U2bV zVrw6Kgog4RJ_raZoGRzjjQw-v|)Lsg^8XyJULvU3{yC ziaEIAb;`+0DDRg?CnoH=FUP>dRDnL7Vbbq~Pf2O9r8xT_a#nt`0%{Tc7|t;Y3AJad zDPhM%Nt3$4JNW{K&C+h81U+Q1%liT|w~iC24^-sDSvnvIf80Ht-D~K<2>0>dA=L{9 z0d&t;c2?hmNK zqkC_%pPqyEtisPrnwt5%m(Y##C`VpC5|0vu?M~?nCiDg1QY@jGwM#i(DpA{A_r{6!f+0%OOqa|U^?7hzy zQI=YTgjOyJbK+)K_Jf-;fItk$Be;yoJupS$GEA61oB*Y$LtO@O(MMtlcNKRV5rn|5 z8^8HB%AMKb(xKV*Sex|j zI+7XD&5JBu6fFSgxib7C7=fF8_kLfmDuec`uC}?Y}r0s<2{U0d#lgZMt}&h~F8F zcg)PZs|-XRty7{28(-~E^R8xxT7DIUnYrcC`DvS4;M#Gw*oMf=@Wb@q484G!9yy`9y3IF{}E$Dc|=i)b$+p`p@{ zvKmTCN?U26O(oGH(LjqRk)%n}ND(RtNh(QYC95SNm8|gpT)n@4hvPkZOP=Ss@9Vy< z^Zc&!e!`IMhxKNqXN;Esz@vAxsc&Ql5$ZeKGDxKY_cBYzEwwa}Ow5M%899%It1vV}5_wVhd zgQ5^FOgAzzTe&jf(>Tk>ohzTMMO1b4_;KtHtAZvy1ywBDzB%9o#l6j$E9)H`dWbvZ zm_XSR)!C>oIShErK$Lrira1qa779?(afu2{Er6U~+q+74H?fLN(mo_apO}+0uie{%rI&U0xxI5#=cN1#13DgDQl1`qY2%IS*OwTiT8}Pf>jTP@<2`(Th*UBCsBt>`H{*;c+ERZ}pq5k2!vfa%IJ@j1{L;>Ar-l%el%1 z9Vgh>%P&M2?kO>xrRs~E;#19X%!l?5x2#{EbNXtP4G0i5sGeNY997>7c*EILwJD@S z4wyEqTx#P}zHRmfowfT{`#TSDA%X_z=$fF$Np*=9{Z}x8u=6dr;Sy(6Wb+tiNq; zE$d(^aDOQ~iI%ohVmoQGZH1f;+I_7CO)gcA97B`LJ{IoL6A7Wxymce#-$n(#xZkzF zVnp_Z?j)hpCqRd-b9tYk9*nhjMP?=_2vD+VsC`BiwU{he$eu-^4WnMcm0*s2H!)X& zi}^S(-Ha=*&xR!S!r5WNAdl9P{fUqA%Vx+nrcgZ-{IK&yTc_+Dz@&>qBv@-MW!@|A zv*^vnanRV|n_{-*Tq$0*Q7ErNvnm?iS8oiev$&rS5~8{ycmE&xW7Ct&H-3xY!)p&} z1P%Y<6sUpf739gjA|*7eX>$bf@3P*&^U&Gj|62Uh*KSdF?$Nnjv15Gg?+=$U)tPnV zCc?+^e-;46(3ef&mGYdCiU?{~pkM?I94A6V2$5%#%5s+=EWO z+5^saYu{>{1wKZb7TE*zJ{eNgA(+RH(fq=HMXdpw)K-8fdJ3h~zKp__5?j9aR zHO#nA({+y~ui3o$_?Vh6d%c|b1fT}QwA{>9gbPRaDgsbWoH(KPz{odkdr^?m9#k=t z@py4EnO~C2ayVvh9vFmf$VTg*?Um*hLQ!yBbhV#;H=$EtuelT#cV^rf=Ivr_Y)w$X zTt7Lb51oL0!AmZ^t)wSI z+kuL*|F>q6(;~5=<0D)X0;!{m@#5^>a6{$dzt8b#KzKNBIE`Er=WXk*?p6i5n_}|K zXgRTBIb>tD{emB4GjlCprU9LMyHJe`OrK(2{+vs!Ao% z(|*JctgmYfcK`0cae@?gKgZg>6)Y1TnQD!R53~--F+FEzY1=>5Zt=|#J{yNCr+n!5sNFuP%q_Yj zbswE+J!zlHB-b}Jftf0*o8|7wm_Ep8IlG^fr7)cdh`-EGojW)FDaJBf;Fl4SU2gaD zW19b>_w;fVU!&qRdZr)mutKy=R7;lzwr989`t_BP1>sA?CglIj{ZFnjFu$908foFh z!qMzhH;N5V*|cZcr)|BDJ-Mf!nU1m?U2xt>ID;%d{h?Xv}n8TzL3 zV>3=3y0}a4Hqlb$=H^m$YYvEv=n)y#TwCu{{-=}R;@)mVx0rov%(A6Re-OIZqvw&q zo2}|66_A^ANg=K3|6Exn~xX|&c@#pkM#9-n{{LEmqC zWUh@Umyne`4C!i2HI}OfkgCKqxyyPh=zg(P{*&0)lULhDK)0hJ>H+#eLW}?)W;CJm zIn2>{3C$gcpWxFLUp@zM%PF>AA0I$&!8A9dF+I zJk{=E!G8JW)wc$D`dl!P@0LEMTN7^7{RDaR*LTwX7Fz*8tG)ZTI;PDdw6X_Wq7e$L zMqpv4Ho-~1FuKY_b_?R1#3+O88-)XFO}EfZKs@W8qwv4#8waSMy`!=}382mEmbvw# z9z#7cz;59OUmKbbwBo|X{W>cv`+?TEWve!g?Mv5W*klSN3cq!j%YWd#3(66)hXbyX zNpPtfySd=Ct@JkUSC>1T-Xlw;aC#o$uL7h(Tza(zuzezTS5sH_tatQ`3v*WyXQJ7* znbccYF8lcyudtv<_91X{!beY6JbXIkgNV$=@+$r$vPyzylg} zxQ(ffL+Pd`@z%D+bfwitS8>RuWs6qeKSsClISSvpx>B#~?+eC(j|!PTZ(#L~yE(i? zx^ho;odfMV>Yb?)tR4#O`;C?HoAd(SU*W)sqqj?S8-O zgiOj1^YR%NQ$15Jjq2j*q5cL{J^?A0^iOg&Yi})*kTx6ZU7=a3F@Ah6#4ivXX5m5S z&cRshu*lFT+^n;P(Ma@x*aX(SDW5lQ-j6r)=Ujm9E)HhIt=Pf5D*omN31k`BMgGKk zpHrs}vK$>2-|y(QPu#oB%XM{hj_|vyzJoZ(5eZ1^eL1~;7>=gr@_CWRbDi`RP{3L( z-L+{{@3|{i21iu<{Ppu^1UJ|}Z26KUkCKxO-!xJZ(&n1Ad^o4Khi{UBa(NX!6ujk| zgPS7mGF40GTUlihTM8d~YrIid6P0T@^U{mkOK-8;9LWoVl)<0%#CgqM$7L{=6#8$P zLr6<9H#dL(VezF*!!EBKCI)F{ckvVnB>3vQ9itIy@N6Zvn9iO(4cE-P`SYhfvv+ZE zd2A4>*umf5{}FRC4v=+l=sxONht6HP9IO}#KreFgkbBzcjTq9Ca}vGIG@J)#GE^ZY z7Tt&>FI>yJd(SqK67{)>+kB4rD2?2lK{QJ|=&_-Oavf)^^J;Jm`ap8F0X} zEbr{u{(zJE#jOSr`#zR)7@fQM`9VVM2{9PGrnYNQ%cA;ejiweO7Ef9Hz8%?BkJd&_ zdk{vc1UUD6)X6)Rxvhj&S=Bd1*D;W4b;Gn9^uO#bQ&a=#PWYMGCOyL_@>9Cs0SVYo zM($PLH|UUzj`yF;7WVjIJdBIC{ry2T(#p19gwhWUgF{UM3zqGye^;Z&`F=|pypRLo z0nDrZlwlcZ_+kfQHB2d<>5ZN#B}JM2%AK>jx|c>)9#|8TPl4H%s`K*~b^uf^+!zgQJKwQ=S+AqDeNFc-ap5heD-fQrgL06DChhTWa+BiKgt}<}zbLL&Hs^kCzaUt+{*|E-B_) zP6)dZ*qYBTa*g{>+^!yCT*{_{_QN)PYLelRHEW_1SMB`n`<`2mK0-6-6XifA2=4d~ zI|r>EHhtF2nXf1VZU+rN$Q0>9zck09M#(`-8PR9Q-kbU z<^6PmT1OM1#S+2tUORr2Q;;vmHUC*sR4;_*IIt z7fH*-6{r3DqBV3{56%OrVDrj;F#S^Ic^>-_P3PXcSsMMeVt2 zpQkD-E9X`4CIuOQKGNvHq7kPrA%)sCLGii6kDdV^z|h4VPVtRLUqvtfFj(?jnbdkj5QeACX) zyJi{I3($mqTP|znW@M;{e(#e4Y}cu^V0%bSI!rn1AEwa1|IyeA8L3+-a_Z{18j+s% zTCuYgl|xuz8cCO!%C23NuSF3H9@edmAG@+;N1yH5{Rz@D*(rG>Sh~i>OYN+Ha7d;| zd6O_}Ed>^&>hjtp(*F>Vjp#(2W>sC8tN!DXLQ@AK{><|I5g!0R=)b$u2hAYJ_S1}G z(+6RJ+FE z66!V>lH+AUJBK;oK7qDuWw{F;340!z#i>)E71jDJD(*cvoRzVG%~7p?tt{6&bPe z?N1B+fPYZX?4A2AgM;PdEHP=-lN}Oms!rVBTVY|y^wqwd7%i>}dW^0Eb?;rPHM{Ya zEAbd1ap_fORpi&ux&Dr^GgMIW6c-nJTdCc?eOr)dCr@_gg>G&Lq`gKpHA+)6QX|+* z_JIGh-|(G8)%C3Ia10&;dVA2gaSjFBSg$7n0zB{B8B3w1NIe9y%@%K`H}Hr~(7a@Q zb>Dp+|NR1sYX;=GL=XOL^7?rEq4dEh{_~lW6a_?1Z z&>4u3BeCKeZc^CN08cX+wtI)Kb#5z3-QKr_!7YE+ef<1cnNsXqjjN(y&IG~57LL&U zP->}X+&02@r}8!Qlrpw7a0q>TO!n#L{XhFU`)s+pYk%5sFIsgYCDm{iJK9HQ4No^T?4FB@pqbSBUYwblTPl{Jc7THVDK^ZoJaS^3ZXzbTt8;_gB60zr=sh!NB_%azkq<)Pj7!!D|cuuZXTLq zu}OJuxEr}5JQbjm*o31OFDfH1w8D#s9!D!sLnWM+4++QR%a=inl*Wva=ktnhz7(e2 z#blz{RUU@^+FKr}U==us=e`MPY`P?(+V z&az6mQ}EqXZq~%hSFTLcGuWvEi}4rj>ts}OB&1*ScNxj^&VU3#`9pyVa|FGjw{ahm z+-}|+4jdw)fS_x!H<_tzA%cvr?j5I1QN6cgBCxXn4{={=&K_lhMr$gF#qZrsaoL>(6FSOs8!0X8S!TGW1rTF&}L13F;d z*1>{-24}$8oEW}JcO+n^6ic1SG9%Y3UCCL43v1EJ9nJj{A$x?9%y1fY)7Vw4cFYh)w6}?!i+lXM1=0Rd0aBEAmS4Z~q{R@A4YM+}v;xytM)(dWJ zp-Z9sT5@^|&5Y2Z-?{SkF=v4mNCXTOiL#g#A9&`y;*KmMwJ>^Bld=-Pt5c7c^O ztiQhrEwwFN@wSUvj5?hmxD^#meso1k8`P0fuZYc4yFaTx@h4f{6vlG)6YRA6BMX{7Z-DzJgm2`##m2^tyYYG9 z(1kPRo}p^xSQ_4Q*6Y0)+*Q7@9eW&B>)wCj^&Gx+7hTop(Q=L^cloS8?a>wUd<%uI z|Jk!g%c7`@FF-t5ThJUs!=xNK@z^WYU&=6EMy=Bj^IwBAO?S?nGCV!`{tUyy;4clS z2F=|#yb69)J^S>t$ghHma0V!Sb2{3jfvqCUP>qTSebipj!(!sj`uq2&Te0o`wE!Ls zqjV#teV@K~w(3%}IATQ8Y`p`(_4ss@b`ADkiF0La-`US^s855nRb}OeKaJ-IU9PL!`6kWoKi>WFgl$%1N(UZy zcHELnw2@iD${^F2YtfH4$CUcc(FcY4;@40{DZcUU#GKDt*8EhEh~XAOQ8udvOHC@8 zGvo+y*9uRw4{f}QK0~QuA!s0&9Yv#-ZasyM&`djc8B%Nu#_LT2Xc4^7-HHm4&?vg! zc4HXk3Mb)i(>5WCB2*|eL^t1p=3p{t8wsX@Jp$?$mRndkOf+KC8>)s)XPl^t=S#{Q z`yvaVl6X_SM=?WXPsCCz=)nJds&gWR23OyC;`f{GFAj48HM*==Jj^tu`*WyMRN&!Du2aL5EJ9=Y!Pizc$4Hq;V?tenn-2$TCe*Y zEeKSxf0byNbl6XCTx?8w^gGlx>4Q7QwD za=g%>3mRxh?hs3vym!& zx$aI^Elh(pdm4G02k49-e5c=92DC*%b-y(IB22Al<$rao+}Qy1{{ySSgzw*errOd= zZ9LP#Z6Al9tzNtJQ3w_Bd%4ojMAU>aMLuB6Isqz1)aFuG@+%@is5p2Bf?Qu+s55wQ zTbibSaZyOc+@(CI)nL>Q6*%wfP}EH#o!DX7%tN|Qoo3wZ=jq{5(XpHD!j&r(KDe|v z^NrsN6I}9R=Cch_@zj02olkQ9cy(aH}`E~gw5f_#o4t20XuPrvc@$@a_pXz@^LBGHI0jAFUt z>;p-Avv9|MFq+VC?>APz*6=9uqY*AG$|8;$+o4;-P~Fj@f-0**U08iMIQSg>*Fb$I zkClwRVqlBDY>~Qe8YQ0mFFQ$fG?$+9w>U}0;6Sd;@tr}Gg4~}stGZ5>B@Pnm@Ii&u zV2*Jl$RKb3TE9&gc7gV(#$rqNPlOb~HCv5M&i&>~(**%yeTi6d00z}HXTZ$qFGzAV z8omtI4ycLF=kETz-S;~@dJOb{(ymRNy2i`2?NC((3q=9%kf_UPtLMXM(-X63E zQV+SrA(|ZVIF)o;#)Cxg*UUHPr|;gTDN%emVAMXHR9&J8F?Krg+-rOH}Tm^rX7`$w_-@ThX3o3*Sp1Gdj zF}7uUfZPNZAdbq1Rn+1wlAb=h1B^ zuv`(lihyr%W_GV>Ondrt67a;Egns`0_gWC^Nx{e6%YLDT7=cpPbw&7`Jmc@jgf)oH zLfq9sOmk4G|LVS`gmH&68n!bcF^RKGz-@mVSl&Ee1G45ITe=5UtV39 zs=e!{3S$YfycKWZ-y{G)v<^!3X!VPTZBL;z&OYiO8ei>GTls*M94Fjyzb=EOB@Ysy zpKfxI_3FQ`ef(v7Td75u`+0h-FGc9i9L)`x!-zuI^DWg|qHQk__>%L9c4#dCPm7%2!^Bin4#P ze|~6IAG* zR$?d79>G`o1O*MqVm^iWcv~%`61~7Jj|v;2=@Q8kLPhohxs^867P{Q5Sa%b_#=EAo z?6PcMI@dM8m{UNz`$TTJmbspOu^UL*f%{tj5mTy`MTaP=rd)t@@7z3Jy*rDq+m2=) z(qwV$IO?un{=Svs@z(pdI5uwx?QH7dv8KV;WR~eFwY^0qhKBK8@F#fR7%zaD$qNd_ zlJ-Mbo9Jcp8)4wj)01=TNb998C+th~zz$$DW--!Ur`d3b(7(FuFNsIu!4qL`G+0TM zN4?LS=}4_mS%{36`)m4ib61LVgg69)KL!#dP(rl)9P6p$9tiUZ9_9$&Sn>u+tQS&d z=V(|Ax-y^NyfGeoYf-;(FBDFlI1yR-F3Z)}$1r?m1w=j7*QGG^u6P2J>Q%X$gmxWi zFsH!;6b5!zSmsRVuqg77`WrroCXx+R4I@e{pI-K(v*KsZdzb5KoK(@j@#&|ZSLJup zwV)|i%2!ky6J*-!?t{5E-cphk`t|t8uE^{yO14si=?|ib|ERkEtE-rewRv~1PBd)@ zKZROh_H4MgzI4g%nd^@>KH&TknyY*EU%DdZmw)(6I68s!c|K^z!2Xy|bLha?IaM{g zGdTn~XV2rb2Sr98bZkgVkhYz2$Y`s(ONFm5JiBqpvzeCR#r*dT{;&jPqitgH#SjnNGubTmi%%Jw=!VevmFKlOx1xmoW>-A3u!ziNK# zPP(r0mRB=9@VgfjFCzY!J4YtpsrC9Dr`REIZ^+>P@D#HB=-5>+uT&?Yjuruqq$4+T zQ^OZGMFF7+uC$u`8D&6F%YJ>1ut)SnXxaxVal(u%4w{mE>wA@KEKc6xsNNhj#xmeT z*c}yR<%=)ak`N+_n0+25Ay{Bzw_dwebf2MF(}Er~D{-tL^+u`!7$?Shh?z>k!E?Uu z@&m-d%>Ec&E3WAceJOkPmRWfdgG`+m6$)r_eBuzY7_;@{L^72K)M0)?T=w8?jSI08 z!K)R0`w8n?^2oJ~n7;y6LZx)kRQ9iTcKJbiEy8@bON^Hi2MaGg%_M{NRbZ9$#R(#4 zjjowno-%g2U91J4;_&Y2cL1#zpYps`ey8_+er;BG%RcvM+u*TPu?3rju>=fu$dDoB zpZi2E>93n#o7pJ+zGZg;E5MGyMEg4E)zHoN+N2akd$3i4iWV>TEJzh+M4>* z7`IZJIbZn@!mG{!g{|ojtSuJ~`4uj*Ls_pX90@&wyA)R(Ym<=KpawhCrXNQbX7RfY z<$WMLNOkhCEpeo8r-~4#81Pg4X309mIkuNG?wM1(!oHJuSl;fJ?cL5ycR}kY1kx-x zp|L=PgQ{=)#O2Q?_jh%1)y+ahMjvOZA&*KPfSYFIxzLXGtQrd_a_o*mw}2NK8T!|U>zWqCc;V2&+xt{w2Fs!+jEZY9Id1Z{%yF(P2 zd-q6L=~k^nm@CveL;Djo5dT<&Wn^(^M?#@Xu?7s5RWuLE>b_KP;09^aDZ z+(ui}`*-vT=b;^1+fEgNL(Esj{{a@WQ%(O0808hvIK-C zZRu#S%_2sg-N>6NuHIR+eIoyjV1@}i$p3V`H0Yf2`DfNCtkDXrCUB{keemhCXHyUi z!4P=+`5gl9`!ClBCBh69pQ5h&srn(dI8IpP_}_uig?0fZg9B@SqNLwl`Yo1O5etUy z4J=~Hag#g`2mlf3&BoEKjimL4&)X-)TzO6>EF8@|Dy#FWC-t+5$}O3<2!LONGKg6u zl!Q5j(%)5ou2;lw>hY0IyFvHT6z{$t{~|OkyEhS$7LjR1iwS zGa{9bb6*&gbWwe6oJ97={xs{3{VZZv^pmT5Kp!`*X&4VPN&}5-O%NH8FhObg__G>} z8!~bbU~k1`6rUp#{>s6M31da*VM;ER^r6XJy= zkghX^sCJepDjy!bL{O8_nTFlZrC4GwVD+K-ao*Z*``x8ocquXIYyWVbny6g9++?CS zUbzf%(H-^VNVWiJz3lJtSBX~l^c@}ql7X{l>$I~77||<7U7R)a zlqK{4D;j+-?aPuSOLFEm@JMW~U)?P8%l1B*-#D9x40#wl>HL$mu@+$i9L`lO0_YA! zce(TS zaby)l2YHkzZM9x7+Iek4VQ_?_(}O>&$*w1Pl-UU|p8NkJ5C9$A)(n!J#1YatG%H~2 zn#b5Ft6W-iD9uS>bn~rY>4U0@1GHg0C!iQwMxMj?-lM=L|LL?eC$V&e#p~6pxlaun zce@Va{{;~vExbvIt4eSEi8SC}A-B9~95`tOtS~!*lB*A~;fy^Vz%39Ov%^P@@Yr>t zWd!PgL=xu)n=m4D@C{*hp@RYcSJ>k%Q!$*Skay+Y<5TN*oLc9%;oUJqL(j*nIv#dc zFs*E!yJ}jeA5Bw>E0zv0PJ2<|*q(=c#-&WD#!Z-nmMOdggMHGr^{s(ju1eo&aWyve zO7&eJ-xL|W&~?%g3a_FJp?Mv;@W%%y{gugS`WJUf4ca^&#T|b)u72+r7b|ux2${fv z%WOW60n(riz+H$|-+i*_BR; zrfF_6+dLf?=Rf{)(01H^-zENQHdb84l-^RZ!DFo_K54JE+;hoDIsU@#J4e-=-Tjgr z4|#aV9W#<7_fhbW*YPnlOj~Yn`hWTV&GGE z5D#i)cQzUVke)bHV`64RiCT=7ZS&=2V9~0RCUAHzWfPQH+~TQmsA^m z3g(x#F~8Ji9&Xn5ReY#jHq)^C!1g)GPEQ|p<>RX`dWO0W_r2SKXLmaWUdb*e6NWY= zH91ELP=2a=*J4CneZ*|$5@rxn#alnNakChKtaBV{(5{#yq;lQhP;lwLJGKl*hlu+{ zUFwnI)c;|wV;nt((4aA7$IWI6g_D3y{(fI)ki=N?{jkEHQ)kZuHQ?Z#KE0j1RVV5g z;RDQE@85gbR+uITIRB;rGap8F@!hw+w)oHU>Lkw*A%j^Z%atxB`_8;FW7e$wiXEJK zKRmeG?8-bP*dI1>6YtqPcLb^$A>@=OjvLnl%)!KewqF@X8aW?a*in2e(Jj zyE{R1@^bstwTvmGugq(^cq=zDEbhaUrA!9qu>72=!BB{^3q0ocOWOW^cV7t4kkX!c zUnljGzpAj|SH0PY(_=;FGI;hxgUX>I* zc<%;d6U0a}KQt>h9yLj_?i({oBS2w---qQ^y+a14Z@+zVUC7|_C)Pu=OlG}1c1MRE z1u2B~4AGZdUVo`xa;)jB^~|Z0W&J~*`x+*18<4%XtENW4aTBA96_5UDbzfPp4z+&f zmAqZWQDuYeQn@T|509_Ly;Efy3k>f#c20^r!4Evwy|ZM-Tz8+Zb|w2%AFes`>5PYm zZD&oMyNWw@og*J8DT~eQ`xjZg0E)*uN7|3;pCemw?xnWQM?=GtPsJ0NQ>B{sqVDKX z@zYfJX^&Q&@YsK*U}1=Pm}icue131s6)MV>!R{4Zx{d-R)Hy%q zW!DMax(&V^q{K0rckAB0{o?1a6?lx%*B#W=#&Ko9+SubOAF3$(tr6dG&aSiFUn9eR zuoH-fpVLpJ4tfv{DEH= z$YoXz%fD08YCJnjZxgGfjtN4bQ%*;5)bp5d&)_Ynv}dzK`2>jnZq=60CLF4-vSfW@t<_iI$@V zld?y$2wUEFwn?|y_)bc zHZ45(TWOR5-+ zvWoahspEDPqxC^7$0fugV{b37*h zMRE?N+yiu9Y+$NSdeWLtRgu#)nD_eMPM^H6lgX?+=X&pw{W_ON8N99>n!(kc=)>lm z!6S+JqawYhKyAIe#weA5kZ!AS>A$taKp*5o-qlA=rnX2i(9ur+qe_=@Bg~+^CNtrHHME2$Gdr{UW1juK~LO=>mXZ@*CYDfH)SEG8VL* zOr?4-T$z_rQGW~376%LMSOWU#1Fa->l{1%dsYomU$zD$x5D?`=dR;A;-PI zEZE$Jx6v^qP6U!bP=BL`jFj`lpdcU>dc6Zu&NlB{+q7q7aG#jCdBm$QDUCLQyN+mp z!p1ZI%ixP&|E&|=o1vlC0EL+DJA zMQk4@F6DzOD6UZOaV-43?HSz)Wu~Ljxn;c#bWL5CgzQ+k^fs1)gDi=(*M+MS+}Vwm z_>~^BdL#}P{q>4H72^ow zpY{M?fQ)RF%~j!y(8w;AcXSaxb&`yzl0`8OGFI7c(#fyUuEvj!i+yN(yNj}o6UC*; z&&t?!Cp_t)OlIYf{Qis8j?-Zz6?dqQr6!Euc;~aGW^q+vE#BAWbJsN7M> zbg6vB2q)RJ2F={p-X6P7c&NvgPk(o!x_sDZ4bP?l9P?(%9EONG^F=?u9tblqgh?Y!O8huur{J zZ;AK5<0@(!JRD_A?~7WGUzw$kJvS*|EMWK_qNg#JAJ#G`4pK#-`A2);ov?r(Bqim` ziNRkCg0-dplXX+PsdG)C?m7J{Djd+^RGMAvI*(TbvgQRENXfwy{=9>s-4luA;&Kn) zcPDSw`)Ik}co)#iS96Y>kU~TrcanSZ+GN(uCr-vUdwH&}CER>%wD9ybDRaw=a zgEGTP@)yZxiG$a6tlnWG$=ut18$Ku1owR*-!tmr#(SR|I=chfh7H_<5$SI%WX?XjZ z<5M6Spm#x6?>B0rI}`;E18}WW6=zC>uA{`YONyF?C~TT#6wY+ZU$3mpPczi9?Oa;h zXg*%fY@W}|E33=Z5(*XUN}P1i%FoUYrX`BmQYaGg9uG7}ZR{NokY+OJq{r=a6-DKx z3hg}%<8SCjnM9dhE^tj$AD(U4JytjB_gJwblPyuZ=TM8Hw^O$;N zP546fkEm@$_>S2+Ai#t;=6yGroGB_)^^W?6gDBJf zIC!jATm6Fua_}HP5DI-bZ=s)k`t<3(>6fHUA59QWC@sO?fSL@(&%gTI zIqtRyA%yl5xQ|JQ#vE_RBsc)35O#(VF~p~WT;#vJch2#4w*<`sPa-6&=gxJFf2}7X z{CP)g!HMM@+mB6;(r)naihh@6i8rW!4s8QaKbHF)CM#~|S9Hso#?r*kry{{pBL;y8 zTD4%aOgjVmOQGpZNHAE^Oe2qlN&l?wE@pLi<5nRs0|6E4LGWzM{LrXJ{{HN1E!lk1lgvqb@o8wzbQ>-g&P zzE4V3Exa>Sycin8O%p^t%GX2sTkhTWM>7YK+r38*f*lliJFJ9ecY~~fd=ef-EQrdd8yv#K>p#ATETwe5bU2I`(K%TXBm-5x;1a+i%HYs zt@!%NSLdN1=MWOXaoOQ31u>}Ht`(jwHeQhngMKy9&Slk+2B8$8;$frsc1S;*fomvd zg^u!`MOaZ(9~LDT7ShkEyoC{z?A#q;T4d$Pp6!2hk;1~oi%ThCfR_b$3)Ipr>w2BS zJoWBGNlZofz)BP%L253KY}{_`e?13P5bGA+17*zs3Y@@(<6B5LQ#P+*N1iUqwlGiJv1ff35ex>BvyPU9YySQ`uYzq zwZ^;dl3X=q=&3_`i-=Dt`B9uicJT8vl6|Ngh1633kZe=5%&6K#tkDH<-j7u**>+wx z#D#>}>wWfp*UM2a)`E(s9#Vb7DVvG9g#?A24^{wb0B``T3Zr5h30)3Im>5?<$1TS1 zwA-(=Q9m4(!7qx>%7MIjCssH^H(mvwNPpD0LahMyl&sslz7I+l1D-dLm_l$zigR|C z!wT)_#N@c;9n#mCVA|M;kbms18H@rc~}bKVsT-W zL>@AanUF*aHl+r+A_c{oq+4viM*Z!7bvZlTIv_wsBaQqm)c`~AP@05J+d z78o#Vl}USukH>wl?RlJu?+=|Dw-Q*@UvKyJslSH1-fBQ#1cuW2!3wYNhYcMGx|^Ee z%1sp}b`E^@nszbeE4@(mQj!aKBEB!MQPS;n6fO zI?M3R{AXj+$5s5+9S+Zmauxzbo}HrK#xL;N!j*;jsWkS>xlTIQR6DeWRUTyeqgVIt zioEnaw~6nXpJK>-Z2CIv=1oB*qhgHzsl#Zil=!NL;$yF#>f7SAcZ=}rRcTnPdUA=r zj_2pcyJcHMKM`0dbJ$RsgF%+!8VU0ot`@JLj$B=ZGmdF^j6ErSb8Wux&sa5|8#G`RN|o_{9~gGDK-XEm z*9bT5&eCJ!w=%1l=?=3$Ilwh=6<7V3I{Zd$D`8a;ela+1jNLH{Ua~h;^CscmhU~Ox zHMmzh$<~P3lwjv=S2xNToL!(u8=CONc!p7)g~18uY04MtSR5k4gtU7v;e1>eLkSx{ zZfDUJ0vknC99n?Y;bImIUS^7!r<+7wi>i5-Tj#ojsO%)$vcg%t z1ODejwhZk3HT+iX12%m~yb>W& zlMbg-DT>&6#LU3FP2n))x0bp59aX3H{y>vV%jfBh`NjH$yIs@(`goZBkud`sf2`ME zRu$j#NDAUhp`PE0DClbN;#-SE2oUOk1JJ!2Orlpz==A-SaEJ+ZG;!7wEq`N}Ohd!S zlKpm+8~i#vZPlz=BegUNF33(XlTVUpf1h)Is;Wtw*7CCvh6qpFuQOOm|8q7>Q56k| z05}&6wbJ*}rxt{bS*&>{ql#ycpBM>ijqC$yCt-N>Sx1-`{A%*bXSpI(TV6g z8Bd=+;_3>r8e+`wxf=Z?2g&;6TCKnrLKuU|k}Z@?d(yv1w>$|MmW`Ku_(fVdPjDn~r{Hp=XI z*N_W7ZubM&*Nj-y+Rx=20Cfx|@n-c<-3gCZ!(gzGkL7;`Yi z-1M`i>qgB64&ay=RCyV%*#T6}knjhLTB-CA2YhC9(E+8*%EW~Sfr*-xE8gjMZJc0e zSS^Bivdj0kRBVeCQ7fbxa-51l(MPrNK}cN$n@+uuY@CBST3C@d&ZQ@;Y@*7ASCTfr ztDxmyg;N5Rtj z-K-6+5bf<>WldD)Cok`ZqE#HbTwm{O9tYHYuReS@&PRjW2FJr>DV#!-gVe$x{T=HI zsF`$I{{F&y-suCZaSCGE9~|mMTGgz^M2%d92er6-g=mC#jkgWPfd_ejZwxH%5B&Ah zI!gr6MtXdG6?3da{|Wg0@h*v(>{wDe+7MmJ-!9z|bGX8X=m(L>I;^FQLn+p|?2o@D zNhXm)#QMM}ui#j-_kR&BUiuB{-!>#5%Jw zR{wIrAjw^EXWqs*hv$f4pyDXV+60(Hf8If5EKaBD>guasLPfESq#?+9On?bgW1d4s z_^FwL*}9WV&%;Y`Uj%uL8pu4S5Sh8KhE~h8{Ez`|vCet>LYxHbOFuG=6mA6^J%yi$ z>IQ4j(<-PoWJ`auU7C9F;zXo;ZcwNI>t4N`KZ?#=8t~uXVs}xl z-Mx!kgPF9KFf8yOn58#4vJ4+{E-Qcjg}hIJjcK*Zc-aob8~z-*I^93xizs*X@r*~f znj);jjo3-&&knSj+sow8GdD#fwvmWPVljtF$PI;< zNAh-)&&QR^r(XP+9%vcqzw2a9ScHlE;L8?cULEzhrSP)BZqug#5B0ff0w8RuR%mgh z>~;u$fEZk8Zw=qijAJVlp1)-Vq#mEkhX-8s3KgfHezm98XA4wY$OJR=^Q5jdA1x@yt(k?7$5IU+mJ@3^pRN zk~rwzId3_V;#SyHxdXYRO#JKk^XC;+)K#$K6{E{qy*M=E`ST)l&p3e6=f)Q_vmaGdnV$>mkPuxi#=*+p+$y)lF@ld zVf)t{_lIu9?5pyUewvmh9hiYOsGrd5n`H?I2`dQezvHs@RHV~~uj{vAnEGDux1v@r zTh>*X2It4UY;<+88|k>O-=x;>jqSP(nRmx=)}rC5rW*O4<(JD$CT5jwA6;?d;rAQO z-JG)mOhZS$zFao;maq14y+11TY0=ipLWXZDyyw|H#kuWb?;O`X)q9E)H6~kpKi^?W zVa8Ea&B>M%wA1Zh?y8J^Kk1{CUBa^yoq8;=aX3+5oq;?f?1s)SwNh2rT> z%>)w1(0-8wu=#Z}(KCZrw~>_~36?={@WUSMEkQKb20nPO41|~sal!c8oT+4ZEZA0< z!Dh3ZQ5tWnN}e3+$7Oo9UVbO?1ckkIO6PQUUCe8@Y2!w!!OR1ZO1V49b0(HLz)V3K z9n`H`8gIW`yMBF&k&*j1D{w}@!uhi$;_+aEI{haq{k9nDS9)aR!x=kG7+nKmf#J4& zbk%TLNnlbc5HoZ0fZr=oJMnuve77P4KiQxu%b+-D#|PtG8M{uWpBlSCW%=1|zZjHI z>e5o{qRC6oRhKgMIXPjoirkRHr7iPQ$_UG&;@MChJ6y2)sG=@@D^>NC<`9)uGpB)~ zTf6?Qnkj>XPaWNoIEv74*z32}uGTR!X(2Y+i+c6k((2JFpCxLZ=Zu@ImRm1dYks@G zXU7dYE_S!OxV-bH83#9H4^RJ_KPKJVqe>4I@X7Inoa|@I#Q>U>u6a^kgSthv%=x3tb($+k zryDCS=#3xmz9~Cw&HV}L>7(1VZ;!xgHg&G5iUmtwKp&(;EUe7z>J)Ex^-se^b(k&{ zy`Ijsn_QTRMfbj-;Om^|#5~Sms@?-tdziT?Mhw+8c9m-V&-oQ>ps={b(o;B)P{NvK z+Du}+ge#e}erwt~JxO)2tA8JLKata&t z+n-Pr*JJvdfxA|hg$7%4b1&#~`{~9G1T(IO@gN6$d;T}QfcqC@_W(5-`CX7}#`n6k z^6haXqUrj$zI)AL9kezs&WIQuC7=G%a@7j|Z70|C@llDK^pid6aEZtH{Ua9jl3D7# z%*6Igsr~9F8;pH&LVnq#NB*(sTsvpjsp>P^ZXD4mO|f1aF@J57)%Rx|?xw28Z@c{X zx9810^(i_8A77XqWim!#rQ_)y=_6VuD5iUtnrNoz3{PFRh1rT|aOdisF*pUH;M7nS zTIcRk=YAj+iwqqdOjE&uonjmaSBw_MW6yIUJ&}ZB@FiLLdiaLOC8S7(?(W~jk(4(m zPNwfkDRhj~pYA?-pQE3zf?^ZV8M<6xxKVf{1uQ?7O?Z?X7I+x?0A zvRCZsf>4Q{z9Bm{ocKBfd#c|xRE}s`h3b9(@KoyIgC5i3mdSkA($_zZk>G$-cm5SL z6YJw`zEt-yX5H6^-R|Ug$Cn@>7SQ@6z8t8yRgkU|8yu_U&!5P9Dp#lqpwg^V4$3 ztY-z4AEk-^i=3F616yR@b^gAFr@Ksw8>XxN{+Jyy^@42v_EwfvK@L8~Ua_^uj_&EG z)9a(QZ()Ps_h$?JN~3}jp58u6W8b+;mrrlJh@Ni`BV0Ul8x48QAQ6i3%(3C z@S-fer@x#a0bCSf@Px0b1c`eo<>qyxHH73NW4(C!;bP{vP|Mu-Fy4t`oW+2VD6L_a zXp2UVz8JRwEg$IONhyiBpO|~YOQV2Qy`O-PUCcP(JWM8Ethi=3u5#bw8M!yjd19}x zxH7PV6k|rZ@0dARm2gL~`G6mcshlv)%#do1=d-It>wQa|7I^qOAkba8w|>p*((hw- z9S5Irs{eG_++|MV8dfI=5dDYo)h}wMXZLY{rYg9I;6bWu#>rn!ljK^w@80pCaFhv` zd(9^|MQexEht&_${!J5mw8p%jeO6j+nuwCA{P?b$#^3$CC1P?S+0o{H^z-t~>uXE$ zUN$}o*<({%b**5k;} z`w+8q*_;~ls3B1{`a7;aRLlPp@~3{Vu^aE^#oGN)(L4Z;%@%CTxb|nlm?2-bH!V_f zi?nE{QQN3ZEq z-p}@*BLMauKwN6_j9niW#Z3%*mwn~N>1z$o7Ss&$-8=s6y_2&a%*b{ZL()#2VjN6Y z&>i}c2?m58o>-pssAxCtwUfA8P7~`YYvytH_kmF)T;mz>y zVVZM8N>{7etx@`H8IXMQ+Gqc3PY!Q)cDY^GwN5M6r290EO!|XQF8K0vl*eTG5A%&R z4XK{5WsmH2ud3CW=&F%_e!>0I4DzWHLC29puOBctsvQ7yqG<>UUT8ilNI-Z%6p&PU~?|b-+M#6advR!*;(#P zk^L7Cp4#*e9F4LE%5VyA;Zb6Pp?7JQP`f>~n8sugUuIadcJsx))l3J6BXh zZq+_(Kl-GKu?WZ9<+}x~0n$iu^sg`HML=|f8J3od4nPQph zC)2g7Dhy+x>Sh5sGaN(6ltC`2`y(PFUD+_!lny(s@|v_O?Vy(jq=YPx&Ee*j8{P9y z{@JN3tu~ip4?EeX?Fo(Zh(3?Fch5s~KQIazo09Bz?$rL0-~PKh4lAMGB~(YcW+pQs ziA?0hL_rS3A$Az;khrbyrlPd-G<=d?sLpIgLK3{Ghkg+B%%)hK5ukygB zt=gTBr1-o#ol>!uga!8GVRIhZM-4muNGtHzS*M;??WMPGE3lZq=lH5o9l)ps?y_l9 zr;F;$lezk#abn=e(S;eOGYT&+lMJ!$Q~CWb#i1E!57(PgnC$ss(vo_Uiyg0cX9Ud( zdb<6`u#qE0-kOL_Sibz0OX0Jf4$7|h~82Qw3>ZXy;)U&@7+^Cf}1v-2uW;#E= zQ)t#T>orwjUI#0K4_9CJFzG4vHo50RnE?|?3qFpy0LULg-MR68#l9|ubImW~ zwXdtpHDe}b$9d1*RvlGofDH>^htgKbSPrPClW*F|Z5Kvm8(uVda$<_}=P6Drb9I@+ zCw3VC69xv5hRt@nrgWn3u)HI;x~z*cTXA(ZF%})nx?`Vj=bm&yMZePGUi$bC8Xv2A zC=Z+w;~)Abcb?uHMqXmn()kmMYfDro6pnl$E;qG$9<^~@%4>bg+Jm;F$0JS$4`5I$ zeDu$WU5d=#B|YdLIL5XyGNpkTUSihT{reUh%2WED{{HsYmxiE@ZW8=A_}=&{OQ<}! zC%WgF$1zQH&kueZ-)Z66jce46mA$<@AgFX|F%>e^$kfoRA<{kIY$?8W64~k-)<+BGf(bi>1!6nz%xNVUYU;4T zeOBS)W~?Mg-rFi2&I|HEDjbJxG7133=k%cxwcYxcPHwx@vfC{?)+U8? zditx0A(up#3;)-P8Mb1f2wBeP`({Ig@M7;RmlSI~XQY)^kRuQ$@btp1J1q zD8~v3FE&Ey`rLjy4t^bG9~*aXZq@j=jlHIcEXA3V6w^n8^D{teTg~pc$)n%4j5K_Z z{<~w0O;43h6HXL5|6f<%0gmy&E+RW{7{>==*v@~sSeo3(R`KwL=(6RGu|zQ5`W!4=e+tR8Lp)Zx?J66VPKljYC+Y^WLzF z*@(;ZNFZo_(95*{4z6M8PA-3I6mqJeDax`as*p3+%+B1>UGQ_d)7IRWI&RVCnbw>O zJ*|Pq2d=B6HoY62-YIBN-q(;{esZQ(b=rPJu=(X(-64PJrF2AuFtIf3{OGtf@1)p~ z*RMlu+Jtn?(uI80wp8~Pc*k%*@M~%6LOO8PocQKI!GJm4mGjrSb)-cVV=f)@5f&08 z1I0%~-o023LIvU-jXdF z<-y4yx^04?>IKyYH8~)nLXz1Vnu7Cfa`GyqWboWy2m2V%_(3oEAeG;MYA+Tm2yT?qzfTqokz-0Ea-Wl9EZ(@7jHDO)dYix_D9R;CsuB zGyk?_zrb52C%=W20N`Sm^&hy~u1aja4a6UT3r?)LGU}k|FuNyW7LWx1ClIbjK)~6{ zm;I){R+&9sfA>9}4t0G6ly=BtTetO{5ImxFtE%@909w2xJP6+wiQTpL-}MmDR~MSz z%mpiGt?k7|j+e&{J%$=;Uc8a8OMaQzA=O|6dA7fQo`oCV3JN%`cuC}IVSjN?g$}xdCyXt~OeT6GkamCJZ-j$v`Xt}##LUGN4B(vcK#?$F#=hksaYaYos8=5 z-_D2TXJHc$lst|=e_qp=m!*W|i0}`y63BP5btQW3nVFl%e=ll_$u;`2Q0OA_=uMGy zZd**#*f3BA0{7u$2Tf$`X<`h}*ilgep?$a*G;-~pjc zXzf7Kq(OI^H5?%>y)54$#~r%(sb>ti4Q<~Y8%s-5j2d=T2F^=Xsc!F9Ug$BqtX+P)IA_zyYL`u!jqi4aX!by95PsOL+jcu`V zZc~;flVPt7zbC~(o&A(RF@c~T0LPk{$ea#|Q z2FIhs1U7{!Rt~?{d?82GRKQ<{z*C0A@$Ahb1FFHda1HQiEZ{5!d!*w$zP!+H&y~v{ zr$Ng_+h|b*cYm6V&Xk#oTsbZzE?aoJtW>Pt84`nO-pfuzKa%ko5Yi0pxP-@JlB4Ge zw3uvKzb-T)T!Ev7KbHf~nnUe&FEAZZ%Ai(pw@qNAa57;M{oTI7wX77|2il+`DWUqy z^nC&+X=rH3D7Pl(%=rR3wqxIFRI;1>ztStz;Ayzo!hrr(O$&rDs4^3q=pX$EVW1<{ zbWR5s9vkAYYNWuil%SYjr;<+J6xjJ5X=IbQzjC}w-E*^({eqW63gn}xRU|(^yVc8B zz|k;x#*w+nM&Y5l08&Y?;)%{3!3=29#8Bg@>t==FD`6I6Kr%#~{aFf{h7}*?-g$qP za9sfYZ?buJNn-O3I)3!lLmIU;DDm;=PRFrUMF}V&NyY-DCZt_nHRq~+-wKPFoClUe z{9#F72N(i~jR^0+*G9bN+#7b1cr)M=;9t|N4g0bh*El|!criEA+}!Lin2XR7Qy&~j zSWi^EjN!JlwB+~CWn`pq_#Kfdx+k94JQU`pjgVZnV6{qejsZ1A<@*Uo6kDhqe)jWx zd!ro8b6P{^IuPX8vxpzV9t)|Jp(#g9fWyO z#INr+Vy=GfAK%WtmNa_>{R=TQY-cZ6S!rD8%xc@%D_*`CFp%Rr{nW6H=I2xqcjwZ1 z5A}a4(ye^H;{42pbgE1A>LK&2Q1jt!y~gXMiiXcbYFm#)_I@b3Kl1v@;P$HfRu2s} z`Z&BT+-hPF?tgWvvG(+nOsZ5lw^E!?Ukrcbpxd#RcVsYA!DW@?4!mW)FgH6(Y6LX% z;;bx|yMJ#{bH>+T$3%yz#4hJnM9a}~?wd&y6DF`cPoBf%Ee5NNDVX$Z=wV&wc*@8~ zSK~E1!}d`IiXyYU8ifMV;zjTs;(<#KwaO75AL; zb;P$7DgDVoXQ~fWa?jk;AxoV@hT)9TyxxJj82IbJgzKAh=Zt1U_Qc!*=9w}&ElYBv z2P1KVMTC`ER1}C}K4$!~X))v+wIkAzEy;r1sZ7mVUr;&X8cbgB@V>e@TUd=d*D~ zw<H7-vp;Z)~4subVKdAq7V_o4?~jzU#|py&DIGhGXuDF95#ZM`vqldWgIIN z4`x$$as^@M#fri{jx!d(gBKz>Qq6$DD8Q8+g}kk(xe_O8xE&y64T}Z(&}2XEECZ$o ziDnh|A2g2*i&{$9;aKLny*%kpz)U86-E;k&0Eq0cTjL54ULPZ)hV;1kr1K95A2@L!HhG$8zDHEbAc9izalhToqe`nSvTm+D`H1kP z{GEhC@+JB|mrfZSw2GqT%hODNe0#lp?E2}N0BQUxv6<~UaC&IMqJO!ew*&DR0ja;e zFB1;%V7ZWzGRh4IsG4>b-UX2wHX`SHLxzuD4NwkgDl7pPY;Yk~r*IYJXfQ)=+w)li zFss4IlG%D(v(pWrOSER<;N|kbO?CQvICTfLhTRCs4Ot24Ohnin5Kg3cxPdV{Ki@AO zG}nqecktOY>L&hhPlbdg!41Ck7t~*GiVZp;^z_Ag_)$<0P$k>nUL=O-d@6xSF0tiS&HM8kQ0ieTs4qxJ)5( z;WfJ`Sa1!gZaR2H6r^2T3@0yjVrZj_4`{IiQq_|`%)?wVG4UdMY-_=Wn=rw|nba;H zUmxN4-tkX|xZm%$Eqa>3YYnqjf1!{No?%3EroI?JkQ1+h`qis12Yr}ZKQF{A;nWV& z*!|0HUz=F7b?*6VQB1HJ*dL{(r4n@mM5+xB_r(D5Pa*( z?jx-o5qwwnW4|NP5pdSpr+54E;_W^V$(j9tQkiH^xUp@-QEb&TEeG(gN28%rHoez!geL@nvN|JtV}wOX(x|F z*ETHJ`L)kh276!4k|^f{ zow3^){LxTG96bQ=ND-N<|84BJYJK89W#bAubqZ({KcOM+8 zH%p6i>XerAhzIeZ7&~*(WfCE)^2jdz2Vkb@9{ASuUtj=F!eM%OYD!ELta2FAd?u))u;<_2Nts5PTBeA$SwL9BZ+zNsmJ)(=gSgZ@F_w)0rMXMh}}T z#P9x(5)biAO@P82_FD;iX!nY#%Vhc>zflOn$`!64M!VN~-P6&S?VX$LB@VSWpM_wlK7 zST8W}SNdZ9ako*PCvoCtqyQ=w&Ug$>9tfF5gH07U&IwanH>2;QIktnb?jtA(8BMP3 z!qvcGSsHd3yDjoNPn!(=j+r`G!y=9|47&)TP6J=%8PSvNm%-3LI@>$x4qp_?}eN(MvLCF{zB>HjKVu8 zKc#(fr9KhTFUAx_c@>km+={Ai0}MgWMjqZWAUDMEOA%m!Au$4j>2GtEGH-I|UaO_P zyHW5{+0nhi(&`i;+dc0~qVp~&Dk;?+esfsKvKiWQKA$O$X2&F5_^!NxC++Xl;@O=@ z!TDw7PBAlJ47*m#|E4o`9oW&-d7qy#Xsc}HpwJ}Z?j{XmuebMXuNLsD66FoJk&hl! zVL;%s8}p)?bd;8#%aQl`Y7IY$Tg%n#yXZfGQ=7aNX@h|TSbs|fK%K0jqDNEi#9uqb zXH7|N)Ghhh`k;Rr8$-l5!I--T&2tERj{fiWe>x+&hNOu#&|v*4FIAH^^lW~f+d+a= zK!@7b5pm#hIwI6Rb=AxM;ziY~D;9yJIZyn1f3WEj>iQ+z7Wc7_1ga-|BXk@5_-l$kNsjst0KGNL$G20)3#X}xl6O$k?zaN9}O?z`0 zjFzuo1B&OXij`DUR2n*wZKfpd{AaN~dkriV48OfIye_EE*4aMi%X*$spGr1tkRQ_hIG2q^^LlTfBGmXMCAzFT!+xx1|d zSb|{OM*sXm5U9@in$B;7O{w6M(zRPVy%R9U1VMa7I!L4nh@i@KZM^1yntR(tRC7oh zD^wA2vAVZ54owo|0ZBNa=|N)terQr8h-uk41!rbA$z4%o(Hm+6?->H|w|_rbLmaH& zONm^SF~|_U+rKO1l|fV>-f;+UX|npv3?Q;~L&4%aR<<$c^{o6kh{163F;2pei|pZq zf=h~zpT=X{qoK&e?ua+BBhnseNA<~lq&MBjh*fW~*IOR2LBZ!^V_gLp6S;|&zWyEb zkuU`V1y%Q%>San9sQ?p$C^TXZpOB#Y*4B6cY%suS0j7JBzI6QCYDQ1dV(huIouy42 zrwR)T?auURL0%DV)nG&gXlr`*<*Ds!=uve+%zhPY7$TJh7KIk?uuOtg7i?LSO-xK0 z*yw?h!Fn@#@>~kyGElS$o%Y3ahB4w6s2lYeSLFxOHbElw7R6r7%ICAvD6J!>FDos* z_3IJ8>LDOw4g8E09Hg+ky3~HoRD`5j! z-@0Ks2VV*)stm;DU0LX&3GjkIQSu7|$JvV9K;^;-2G|RL2C%&y?G2ByZVwDZ0kjbIgyb%v796QRMx>;j$%(>W@{Xs<(81}s zYwrPBH!v7M^}?=yYfzQ=#1N1k@HS!`lv~l@^N$S-Y5S89IJ?V7iYorCq#Pl@C z{d>D?Z~!BULD&%L`U9TxYv}PTC9PYH17xj3wyhWILFQ<>kLPMT9VB`B8x%rcLivH%Y`+`Xi^I^a{-$@! zv)G;LNjxGkMfY2FYhQdh&_chl?ZC)+kGz887TZ*#nZh-tvSsDxocC95cg+P z2WZhBNIx}lb%25^5_;1p4g~7}Pq*xF4~8ZYaHI%4CP-yrp=@e1y#C9H?JT&Wj%$v5$GjmMD2n*0 zvu7ZWcr#Ycb#bWa(5p^r`-AGO2Hj&o6dnNC~8y03>YXw@4_`4zrTlb>g+qB zO|XoRdn0v|c9GdC<33A(wYs!uob6j>U2h?BzbV@fm|5=kw-rrp9f|fR7^A-ALF(3u z9d{8|V*>;=TNXVBp3Z*-KjaPU>sJ=u&Er=kj{L+}9T|9Nu{5qqC){}f(TIGwoB{D| z<8gVio?ADuM`BI2=h^XD#T?bkAnjo4sW;ype_@#K!=@CzV+Y1&^4=tTe!MS?b}N_5 z_!sziJG?a$GKb99mlx5?7-S4*3A?5L5d;u*9*Yj`5NIZ*F5R#R8rr&T+i}-*G87=c zrdO^=^o|bSMv;9Ujd0-Mn0J5LVHcJ9ZDaB8m8kkf&=^i#|5x3EI%LXeus<^%1akLOoI8* zqobP`TtSF@ulX{nCd=p&*$v0s|7n^{GCQhHEo*+j&j`0v^5k%x_5&y&k=z!_AoOjh z8k9X2e2ZvD$WmErFOEj-1k@t(0e%f5U*&kDMU0E%@=I({QBf|FI?JMfsLuISh)gfG zZ5xsAp#NXTsVP0n8zCo6*;vY^1C&(dK7mCK`YUN7I4H>8>hMWVp~g}-y$|Q> zP@Y>>VqDWDy<+Gdf`7eYzHK2MsT=ejWP(k(&4rvRYR=^hnj`kfk2$z2< z)O?HfrQQ7CdQ|DDoPLf6zsU2JC~vjPTMttY!_w~Du=CH-A6*?l;&PRXV0Q$6jc;vk z4FyJ`Zx*K7V2*nT1WugJsAfPnjSL3c2QUnbEKqJG>-`j6s8igbqCqH{+uVga~h+L8oPu&)&lc1*Z+fhtLCPzx{E*1Ger+xJl%S zpcY;hC-y%D9|Nfb(gj?bsj2e)D6MjJL z7mCTBpheTdu28PI(XL|2pg7+>o1(yTBJ*`@ElSoH93?IP%kQ6@xL^`&=4o@_`RY}x zkkIrPn*bT@AMbT9@P^4TaoNUlh9Z3(H0~gkb`LrO=bMd3F&G^t7=yM!@{huP7uq%| zLzZ9^`8*@z{FU}a1j#=q26T+_Y>DeG`q!ZQ_H862aQ!GV)6z_|FZg|cX2sz*N+LJEc#LYLXj$f4( zcqu)w6)>PsYZsz{8U#0)9P1S?4n$BgSJO$YP!qYM`q$8Q!7sX}ChuJ(V4<4(-)yHp z^co2i+qcgYm*z$NeLd=&+cPgy1Q1J8HcR1C)3B!=BiIJ;H86n`5wpT^6jW(gEMT+| z!w}vQO1L+%%?c$>LAwOT^uT2WFo%Q#3cu*JlI)a@|7+;LZdq{ zs=TnBGDKim_&^gw0UYZIRHWSCUl9i<1PB|3rK50q15m#{UC;^m zjD*#ht(HjYl-=EBaryBNM<0V0JNa-paX5zOPUDokyY0F+M1X5>kx0P{XdhxHBySie zG>t-gr@tVy#KT2CinfwYZ}Lmy@}rjv-If-n#}?;CH|iC4kI(!hoec`7JKA!Z0S>(* zFf1CHGbC!p93q3m#f1nFhG$4D196lR-4~*GJ0D5>*U+XU_2o3_4MU9i5hajXH@;u5 zJCcoC=xBd^p7Vx+f~o}MO#@Jbl7pE%3Q7-L)g5ooqgW3hhP*9YWkGJxW7|d8|0FeV z#tvKG*ti1Pu0I4$v{qyV#>ma-M`5FQ=h-SMO&E32pIYo;LM(3=E!re$=alwAJYm7Q zG0jWY7bFi7SOFM_(M)VtIUUSTGk4m(zq_EIK+i@G9LLf<5F^-18nM$}0M;PJ%o)vs z8mHFWJ_P2i+oKoE;HN{73g$c*#=V0}ElwP6+n$_A`j)Xvm6gPOkS`O=Ax_%_(JOW$ zY)J@F=x->zH8BaNAwDH-;$YtbP!Sa}gz#7y!TgHhy|OM=u8uG*L3aQAQ-;A7coV2` zwN=f1smf0K@S8tahv;cW!Tr)hysXR9m2B)RH>5Oiqk6bTJVm;CesDZ zlM+GVJ2jB6!iRDt47YZ80nb%ZIue*W2|OT9J9n>7Bv+8Z?|MkUKBk0rhAr&p%xQ!gtvtg9w7o#5H?WX65jtn zl0QI+DEnGkX!Slc{KoMLW?H~)_{{D=4T7CBLy4DHyJF|XB!lvcpO2IuEVaAso%E^V zVqi07*_A=eGA&0d<;2DW%L5>8g8lIu9dUmC9y90IjBm=1Z_1JoDal+FthV~VI%0_? z)D(StoI~W*3{(G}+n3v@RSklwtVrXKp7Z#cwL5JMPzHaNBz2_M4os9RZjfnYMqjd( zp*NbUouzJ>v}fd@L3run{T8$l@&3C(vS#ey^slxvj?`n_^9fhJ?61n609G?!w=};V zXahL@PbRwla88i%pb-ES$2yZA24XWCWi5B8gFSI*BLH9NYq;jh!9Ou60%YO;*mACg za$Q;AU>yxEy@|^dT){wzV=d+D_V(7EhP@YVk*4A+N2c3vk8c!Nl}+ViU%SP7Kie7x z<~0nz6IfX49Ril;qEe`sr~({T?B1ZiV80)rb;LTLk+zK}OLCukV(}3r7|>WRy+pE$ zh{c@ndqT|J!K)(M)haLQJ?=<#0Zdv5Y|v$t<7nWpV}X4Xiy?wKT^aWsex6Aar9W6I zc5d)iOVxmx@lscx{@IF6q=s=gnu&FXIoyYlucIDK48aj;Q$wj}OZW!H^(68FQivd` z!+rWp;9e;N+2lAQEIMiFD*BIFICo%8grhz)KmQ~B$9kwJ*8n`kgc+M)5fMSvz)N-e z3I~x_2UVjjHy$(8{$Uk461l(&945<#zr3^5?xDAXqS;d3Ht=1u4JF~aNB?md4{~({ zl(CwEsA6-#9V?mD=+_I@ce`ax&ha#W4gkk+oE!4Eph-Gxy^#P=*NKRb-3a!iiHxKq zMs76uSPq68{FLLurvTLr+dYfDFO>9Ba*$Uk$K|sKlAQXYiC=!yh?vSSi%ooo_>xLCxzAuh0mk zOt#iGZTn|=-trFJ=<$nYn^=eVc7*d)1^cq#$ojuGU*?eI--J$+q4A7vm4g*^%K_~y z|E4rQ%PQSdSqE6cIwap`-7#4D^CbHU22c8)L;M?#$$qcDUAmYx@kOFHi0QRuRq2$v z(u9nEBX`CoC!K*(X@3{VpzXYqQ`L+2$1m}!1u{~G_aN`&oeG*T(qPU1d06-0NTfly zQfWk#!^Q6T+b!~}vaz@rd@Ne_>XdIKhojq>>*+33S=LdH@tQ@yC!=zix7Gv$>Lf!4 z^DtQy_}`Wg*GzJoNE_oIn<;Lk7?hIgy;ug_rDe8c_rNXA%TG{@jU@pK<8K)yCEf4N zohl;b&Uf*(e4V2EoX@lvL@$5Ee|gSg8LO|As()sdyMNOY^{CQDE~u`kuFt6xoITpPQ^ogp#7}YZ73_?8tHO@lN`uhiQd9 z`6`v{%{$0P721%#k2P%n^7h4ei*wzdXNSqXbnCL0J38kxnUQG~)vHRd4z{gKjqx80UPR-KG4zYIwrRt>G5>Jbkw^RH@Wa;~N7ucW=oD z0&y#(?snZ4S4wDlmTa4~o+V7+e_vF#BJW>fyekG1VRF?6OmT#XMTPoiX|h(`Q#*fc z`>V^E**;|dlr-M^L*Ui<%A$2Hmv(9mO!^IPlgbc?`%Xq@#d>KCkvlh{CNncblz2bC zZ{UKf<(lYXW)`s@&v>`ROKHNZ(Z-_vuo7@rg&V6M8C0k$trJo$4C4QMm|RWXFBg-6^14c;$VzeZU5_QJcID;=Ro)F;0xB$XDzDD-nnj^B$@?WE zpNA!^cK)^l=GG@N4RJHnc!3iqNz%^CFKNQerkq4BN};lF#T+5T6rzpa?cg1?<+d(EKphe(aIz zdS(_S#F`}+uH2e6zgAkGZ5c^7-@r*K4AQ4+cm%juu_pT#IUnrD1W_n_HjR?Fs@ zX+P@WxCT5HJ9fU1rc$Vkq#$04H?9yI_HSm^KO8t}PkXBs1i)gKMRxu2)ztl-$80ER zwC=nVMszH2*G-c?*`a{~O%DJ3*N^D!Vk$U-_s)xG%mk`G|& zTzqt59`y#ioo_(W}H(N1#wA6(MpQT>$Jp*M`2o?{+#@b(8rzMM+$r*fxt!Hc8 zLUM@GoRqRdv;eR6wy0Kld;jfm-x*#iA1`H`db{n7;hoz%(RzSarMBKIGvlBIV=!{M9Y|tjwX5Uq*%^a~&vrM)xv+%sh9qQh|19KY84B=zACH4Dw?jZL;aOuc=R{+!)J7N9uF5A;;R}pU3I_RdzVos+zXhz zXh&CHlR4}0+p@w+^W9#qR6|Y6@v{BKZ2f!6pw8iHedd?UEfsWjo8dY!zAV{|ZNGz4 zta83O$bE3`|JLwhQ4#Ul_I^a>2*W@jL|}*j2?!@YYnR?pmtcO?8Jrk7Q}`@rYzAGW<>pdHX`f{BHDb+M+nb+wIutJE{RUW8zU zlsY(zdpMPZ*wqY8gxw9ZO{nU_8n=C%z$9ei58CJRm8O>ep!bK%_Fj(ZJ=Rq9P4-`< ziwy){b$rN^o}QVg&F0@1$R@UYz@6~5yD$1jsK zZ;ik6c^;YU*>w6t!n?A-(52El=IYmJANh0Uky2UPZV>A5saYwd-{1UfhpJgrZT!4r zx{nwg4?d5Ap}9p=t-SSjSF1&Hb<3LYF{d9^2Vb4tvWCHcN2;YY&}F4`PmGzcia-~~ zGb>2ZrUG0bBCB-es}h@*v&+pRbW;G5!UQlndDekP?RYp1Q8CFr(E}1KpVJ%t&(zdr ztMA$zKpcQkR6_4JQYgVPblI%%H&}4cL;~)pZ*O0x8r=P3wDNU?VS)9q_%$+3T*67Kl1J@$+e@t&v{cZ6oLQn}lDjN0A-$Zm<^t;H3FU`R_({0ZK98Cj5ip zNB8gG!%`1x&XEcb2z-X!E{JXkiNbXvDO;%4G3HEh3yik#S$ogp@rmQ9bJ3!i*2M->gKy*e} z2f2Ww;pZcY%&;7Vu+s-P&8q?K!6$D`2~n=SeHLjh6gLzQDWno>Gv!WEIS3;fj9uac zN7#|S;)lyez~6few^I^^4_As!rNcf1VK}UsdOm*?%E`mDc!GfifCUoX`_`QEL>OH9 zHMHose~lrMl3#>VgGPjrV#ojPHPq9)4QT{YSyU+R-wp;v+zSRs5GSGd2-rRIggsCg zV20~p3`0yNP`yCEEm7m|kJM-PL!%A8ARS1=kOcwj0;6eTqRu5ilOTJJjm*af$@vz6 z8&T<52sk;muA-q*{2u$VJPtz+T3|MH@4f2n%nbuqP{E{7AcPU8jR4`Xs|KFfntDrfG>C18f=PheU`J$ z9FM`(YdBy}Cq8G-&Oir&H$Wk@(V%keYXAWt1k?33J?i^wT`h=dIKV1v`-#QpQ0M7ty?jzW(=40Z4$YQ$!f>0hH^~wsP ziR{f*GR?^FgMGCTwH#KEw}gCKT3SdGC^RPNvS<-3XB|vUNda8_M`s9h7b|+twogNo zP!vGT^5&FeP@dQ;O^$-uhuIs~-bVW?)DOe*bRIjPAlu768&CAq7VmGYus^;X<-sMWofgQTz`-#Ops)&E$$eL^VIwq3=N$4nov0}Tl@pdn1J-n>V6vlBw4d}#O} zbUw?)2P7*X*+RaREW=rCTgUeAMMO5l$yTthveu2LJ$j3VqAQqLw9*|owb(J+QODoI zX~BwY zLO^4`$s7xa7~f(PE=X*Kisc)mVQ)4H>8g2cs;>XQb7=I%E?>A`3DWzhb&KfmfS%LK3! z19?OwzbYC5+=Od);{XE%&?V{3{T51#j9Px)xHshyV3=dW!pz(bln{cw2cl=*p$zik zl+`=Nb8k}6VWh!FIUd$JP-Lsomz0;UN;fX_1(kpUQED%Io>oF_s!Ac|g{VDM=dZ=R zN7#=~byW8cms z%uqJfQMt2i>`TsdVa=ipMS<&S=w4nODceEQTD^L8z2*KY1MDT`BL7fKwdTOkEGRhL zM}4k;%%9Hm;s}g+Vl-!*!EXSa2(43&eaxofClvC+f>fHTQ;b(o#)n~T2iK>Vwy>|l z6wbo${#A99Od&A_PrYo)D$nRuo>l>ZV8ei!)y`dERZTHC(h+=eB`4$+bJ5il3Z-4e zF#M}jbFlr7BONPBM84Cnqfjz6eB^@G7pwFCG%lgy<=zG3@_6fl{*KF+Oa-&#Fz7(hZa! zZk8-2_}waz+-YmLcllwSliJJwFNyEJjxSYmc=;)|!W#eIUr(}^#k`}PWWe380`NU> zi+G1#33$Eu<=@DEwo>pdEN>k<|K|Z|5B~fge{rVZQ)ROJh+)JRKW`YGZ@&q@PyYF+ zKmXlu(5_w@^;{dOshYix`aKQ+-=GA|G%!$K`gMmx@>B}xSxj$nH2v$1CK6Q z@t8bm`SxeDO1~#a(3$EPJ=(Ns;c66xva(qfn24udSx9{ZZxJQ4cE0S?CdyFUpXZ>n zC#IUlqu(bltSWi^T~BchMgRUMe}Vm!%t21RT1sZJ^9o88nSsJiYbl@uP0YFPT2Z2n zpVqDN`ZZ>{huMT;;DbKy0cD|aLgi+EQ>fio;2=s@RiGL4Zb};UE=+jrIVt*=oI7aQ z&iP_9rO=gOMY$Gs7#xA1#r#2%YVyrTZ$fMS?Ma#DTwMCMIDS-XvZL{p!-UIthxwOR zn%3u=+=7a(PvrEy)M*+@Ez%iuO8#Qf_{!t6Q|gzgi*pz5FYdA0rssaw#p-~KbV`wl zUX=c1(?z|9IkR?^`;SX2_l@+Y)WuA_fZu?d0l zdTsKlu{B&ODdlkot;9y$p8DrE&U#de?Ymq(g#NsjUh>wB{~ASmTl{DS^0K3&qevUg znCsJw+u&~rn>2No;!yVouWiCFdbw1Tr1iLOsBl#Cf3(5lL8Mc)tNW#>Pb1r|_VHiM zm7a8dK3jaTqCaisP-Xiw>$GE=OwI)qUY#3B6BFx|jNKO$mAgwXHl(3KyU%|-@UF|; z{$44)4An@LXRl7jT-R#aXk%m3L%h~)ZTCz?%hd7JjkkcOHvcANai2}#fnCORmkL4` zv;^0u?A`wFE6AU zI(29rs5rLpL)f9l(EPC4qv(ef34x5sZ8_C4dDg{764Pl>e9fDBwB_P!=Tku8#{qHW zU`$ro3vZlRTICOE{gug=S^Y}yeY%!jD-%~Mw)gV9reXhS{@p2D;~PKrX#eUD<pA{!I?_#6Aik}rQu9us}pSyyGJZPpVvWPSB>^5Ae#sKJBR zMIqUmxz%4ZFV$vSrpIJ!%Dk4pBe+mkTU*I}PN;4ihpy^)ql>%q=hqiwUJk$RQ66+P zs2tmO*-fV+Lhq~bliJ@!|Gu`Wd~N)7-~7RTYwbGsg!HeU&U9)TMj1Np%GH!NdGU2{ z>&dIl!aB8L)SRg^f_2Xww%QmEr$)^slt-nwN51ZR@nJs8Iz8%OXM~fSr9oPBzsvB* zNLatwoy{S1{a{Yu*ozbL70>#Ztxl&PL^0s^{$v=fxBZFh8&Dw!d`q;O^C0lzWf12~keqZ5}Va8x-q>#vJ_P$!Ej4Qf#>ot?3> z*S+4F4!`jyk6lX{%;^0*(7zVD@6)NCIbF&>cn>h0jp6u#Mw&fsGvFV0!|x*tK0ZgZ zO7ZD?K#5ON6i8h~G5uWn27xGy>cFBv7}v`fWu1H4_-wg*9mPNiXFUc*QbvZll!3wt z0_ZrQIKV^2E!^ds#a4S7-mx#tXoUz`+h+qMnlCm}jzt=A z!&9aA&}Isi%Ul@&O~3{QN{NVl1h%P~b2{%SnGKvFG($}F3%PBZS9yk&M}W?W4Q#Xg z^?7fI>B6UDwQAOty!K+Ic#BcIca#5S9`sn8_rdp9=|O?ljxZP&rcI!qNf`a_xI@Z< zLUaAlWH&g8W4%=mKFD4BeL@DbWl;$1TP?$GSh5d#^9dZdh88 X-*Vfp^5q2)@(9% Date: Tue, 24 Jul 2018 10:50:03 +0200 Subject: [PATCH 013/119] Fix application shutdown on incompatible profile exit --- lib/Slic3r/GUI.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index cbfe0e5fe9..0b95962613 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -141,7 +141,7 @@ sub OnInit { $self->CallAfter(sub { eval { if (! $self->{preset_updater}->config_update()) { - exit 0; + $self->{mainframe}->Close; } }; if ($@) { From c5448514ace5bc5329554fb45365b961a903ec89 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 24 Jul 2018 11:20:29 +0200 Subject: [PATCH 014/119] Fixed an issue with MM and supports layering --- xs/src/libslic3r/GCode/ToolOrdering.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xs/src/libslic3r/GCode/ToolOrdering.hpp b/xs/src/libslic3r/GCode/ToolOrdering.hpp index a7263abda3..4dcf6516ae 100644 --- a/xs/src/libslic3r/GCode/ToolOrdering.hpp +++ b/xs/src/libslic3r/GCode/ToolOrdering.hpp @@ -66,8 +66,10 @@ public: wipe_tower_partitions(0), wipe_tower_layer_height(0.) {} - bool operator< (const LayerTools &rhs) const { return print_z - EPSILON < rhs.print_z; } - bool operator==(const LayerTools &rhs) const { return std::abs(print_z - rhs.print_z) < EPSILON; } + // Changing these operators to epsilon version can make a problem in cases where support and object layers get close to each other. + // In case someone tries to do it, make sure you know what you're doing and test it properly (slice multiple objects at once with supports). + bool operator< (const LayerTools &rhs) const { return print_z < rhs.print_z; } + bool operator==(const LayerTools &rhs) const { return print_z == rhs.print_z; } bool is_extruder_order(unsigned int a, unsigned int b) const; From 21a59ce710e7cf7348e092baccb7a6fdab0c062a Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 24 Jul 2018 12:17:26 +0200 Subject: [PATCH 015/119] Shifted the MM priming lines inside a bit (for the out-of-bed detection) --- xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index b49c2856b7..f466fc4f65 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -490,7 +490,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // box_coordinates cleaning_box(xy(0.5f, - 1.5f), m_wipe_tower_width, wipe_area); const float prime_section_width = std::min(240.f / tools.size(), 60.f); - box_coordinates cleaning_box(xy(5.f, 0.f), prime_section_width, 100.f); + box_coordinates cleaning_box(xy(5.f, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f); PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width); writer.set_extrusion_flow(m_extrusion_flow) From d4adcd4077d2514059da880cb11a6f1a1760d0c7 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 24 Jul 2018 13:39:17 +0200 Subject: [PATCH 016/119] Out of print volume detection for toolpaths --- lib/Slic3r/GUI/Plater/3DPreview.pm | 11 +- xs/src/slic3r/GUI/3DScene.cpp | 22 +- xs/src/slic3r/GUI/3DScene.hpp | 4 +- xs/src/slic3r/GUI/GLCanvas3D.cpp | 801 +++++++++++++----------- xs/src/slic3r/GUI/GLCanvas3D.hpp | 22 +- xs/src/slic3r/GUI/GLCanvas3DManager.cpp | 31 +- xs/src/slic3r/GUI/GLCanvas3DManager.hpp | 4 +- xs/xsp/GUI_3DScene.xsp | 30 +- 8 files changed, 473 insertions(+), 452 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/3DPreview.pm b/lib/Slic3r/GUI/Plater/3DPreview.pm index 9ed2374ec4..09c2f0b8cf 100644 --- a/lib/Slic3r/GUI/Plater/3DPreview.pm +++ b/lib/Slic3r/GUI/Plater/3DPreview.pm @@ -25,6 +25,7 @@ sub new { # init GUI elements my $canvas = Slic3r::GUI::3DScene->new($self); Slic3r::GUI::_3DScene::enable_shader($canvas, 1); + Slic3r::GUI::_3DScene::set_config($canvas, $config); $self->canvas($canvas); my $slider_low = Wx::Slider->new( $self, -1, @@ -365,16 +366,8 @@ sub load_print { if ($self->gcode_preview_data->empty) { # load skirt and brim Slic3r::GUI::_3DScene::set_print($self->canvas, $self->print); - Slic3r::GUI::_3DScene::load_print_toolpaths($self->canvas); - Slic3r::GUI::_3DScene::load_wipe_tower_toolpaths($self->canvas, \@colors); - foreach my $object (@{$self->print->objects}) { - Slic3r::GUI::_3DScene::load_print_object_toolpaths($self->canvas, $object, \@colors); - # Show the objects in very transparent color. - #my @volume_ids = $self->canvas->load_object($object->model_object); - #$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids; - } + Slic3r::GUI::_3DScene::load_preview($self->canvas, \@colors); $self->show_hide_ui_elements('simple'); - Slic3r::GUI::_3DScene::reset_legend_texture(); } else { $self->{force_sliders_full_range} = (Slic3r::GUI::_3DScene::get_volumes_count($self->canvas) == 0); Slic3r::GUI::_3DScene::set_print($self->canvas, $self->print); diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 62659033ad..e23641cb18 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -250,7 +250,7 @@ void GLVolume::set_render_color() set_render_color(is_outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4); else if (hover) set_render_color(HOVER_COLOR, 4); - else if (is_outside) + else if (is_outside && outside_printer_detection_enabled) set_render_color(OUTSIDE_COLOR, 4); else set_render_color(color, 4); @@ -1968,26 +1968,16 @@ void _3DScene::reload_scene(wxGLCanvas* canvas, bool force) s_canvas_mgr.reload_scene(canvas, force); } -void _3DScene::load_print_toolpaths(wxGLCanvas* canvas) -{ - s_canvas_mgr.load_print_toolpaths(canvas); -} - -void _3DScene::load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector& str_tool_colors) -{ - s_canvas_mgr.load_print_object_toolpaths(canvas, print_object, str_tool_colors); -} - -void _3DScene::load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector& str_tool_colors) -{ - s_canvas_mgr.load_wipe_tower_toolpaths(canvas, str_tool_colors); -} - void _3DScene::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector& str_tool_colors) { s_canvas_mgr.load_gcode_preview(canvas, preview_data, str_tool_colors); } +void _3DScene::load_preview(wxGLCanvas* canvas, const std::vector& str_tool_colors) +{ + s_canvas_mgr.load_preview(canvas, str_tool_colors); +} + void _3DScene::reset_legend_texture() { s_canvas_mgr.reset_legend_texture(); diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index 5409b95883..0cd2d81f0e 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -536,10 +536,8 @@ public: static void reload_scene(wxGLCanvas* canvas, bool force); - static void load_print_toolpaths(wxGLCanvas* canvas); - static void load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector& str_tool_colors); - static void load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector& str_tool_colors); static void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector& str_tool_colors); + static void load_preview(wxGLCanvas* canvas, const std::vector& str_tool_colors); static void reset_legend_texture(); diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 722f1c1124..819f26609d 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -2326,372 +2326,6 @@ void GLCanvas3D::reload_scene(bool force) } } -void GLCanvas3D::load_print_toolpaths() -{ - // ensures this canvas is current - if (!set_current()) - return; - - if (m_print == nullptr) - return; - - if (!m_print->state.is_done(psSkirt) || !m_print->state.is_done(psBrim)) - return; - - if (!m_print->has_skirt() && (m_print->config.brim_width.value == 0)) - return; - - const float color[] = { 0.5f, 1.0f, 0.5f, 1.0f }; // greenish - - // number of skirt layers - size_t total_layer_count = 0; - for (const PrintObject* print_object : m_print->objects) - { - total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); - } - size_t skirt_height = m_print->has_infinite_skirt() ? total_layer_count : std::min(m_print->config.skirt_height.value, total_layer_count); - if ((skirt_height == 0) && (m_print->config.brim_width.value > 0)) - skirt_height = 1; - - // get first skirt_height layers (maybe this should be moved to a PrintObject method?) - const PrintObject* object0 = m_print->objects.front(); - std::vector print_zs; - print_zs.reserve(skirt_height * 2); - for (size_t i = 0; i < std::min(skirt_height, object0->layers.size()); ++i) - { - print_zs.push_back(float(object0->layers[i]->print_z)); - } - //FIXME why there are support layers? - for (size_t i = 0; i < std::min(skirt_height, object0->support_layers.size()); ++i) - { - print_zs.push_back(float(object0->support_layers[i]->print_z)); - } - sort_remove_duplicates(print_zs); - if (print_zs.size() > skirt_height) - print_zs.erase(print_zs.begin() + skirt_height, print_zs.end()); - - m_volumes.volumes.emplace_back(new GLVolume(color)); - GLVolume& volume = *m_volumes.volumes.back(); - for (size_t i = 0; i < skirt_height; ++i) { - volume.print_zs.push_back(print_zs[i]); - volume.offsets.push_back(volume.indexed_vertex_array.quad_indices.size()); - volume.offsets.push_back(volume.indexed_vertex_array.triangle_indices.size()); - if (i == 0) - _3DScene::extrusionentity_to_verts(m_print->brim, print_zs[i], Point(0, 0), volume); - - _3DScene::extrusionentity_to_verts(m_print->skirt, print_zs[i], Point(0, 0), volume); - } - volume.bounding_box = volume.indexed_vertex_array.bounding_box(); - volume.indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); -} - -void GLCanvas3D::load_print_object_toolpaths(const PrintObject& print_object, const std::vector& str_tool_colors) -{ - std::vector tool_colors = _parse_colors(str_tool_colors); - - struct Ctxt - { - const Points *shifted_copies; - std::vector layers; - bool has_perimeters; - bool has_infill; - bool has_support; - const std::vector* tool_colors; - - // Number of vertices (each vertex is 6x4=24 bytes long) - static const size_t alloc_size_max() { return 131072; } // 3.15MB - // static const size_t alloc_size_max () { return 65536; } // 1.57MB - // static const size_t alloc_size_max () { return 32768; } // 786kB - static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } - - static const float* color_perimeters() { static float color[4] = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow - static const float* color_infill() { static float color[4] = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish - static const float* color_support() { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish - - // For cloring by a tool, return a parsed color. - bool color_by_tool() const { return tool_colors != nullptr; } - size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } - const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } - int volume_idx(int extruder, int feature) const - { - return this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(extruder - 1, 0)) : feature; - } - } ctxt; - - ctxt.shifted_copies = &print_object._shifted_copies; - - // order layers by print_z - ctxt.layers.reserve(print_object.layers.size() + print_object.support_layers.size()); - for (const Layer *layer : print_object.layers) - ctxt.layers.push_back(layer); - for (const Layer *layer : print_object.support_layers) - ctxt.layers.push_back(layer); - std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; }); - - // Maximum size of an allocation block: 32MB / sizeof(float) - ctxt.has_perimeters = print_object.state.is_done(posPerimeters); - ctxt.has_infill = print_object.state.is_done(posInfill); - ctxt.has_support = print_object.state.is_done(posSupportMaterial); - ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start"; - - //FIXME Improve the heuristics for a grain size. - size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); - tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { - auto *volume = new GLVolume(color); - new_volume_mutex.lock(); - volume->outside_printer_detection_enabled = false; - m_volumes.volumes.emplace_back(volume); - new_volume_mutex.unlock(); - return volume; - }; - const size_t volumes_cnt_initial = m_volumes.volumes.size(); - std::vector volumes_per_thread(ctxt.layers.size()); - tbb::parallel_for( - tbb::blocked_range(0, ctxt.layers.size(), grain_size), - [&ctxt, &new_volume](const tbb::blocked_range& range) { - std::vector vols; - if (ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) - vols.emplace_back(new_volume(ctxt.color_tool(i))); - } - else - vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; - for (GLVolume *vol : vols) - vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { - const Layer *layer = ctxt.layers[idx_layer]; - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.print_zs.empty() || vol.print_zs.back() != layer->print_z) { - vol.print_zs.push_back(layer->print_z); - vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); - vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); - } - } - for (const Point © : *ctxt.shifted_copies) { - for (const LayerRegion *layerm : layer->regions) { - if (ctxt.has_perimeters) - _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, - *vols[ctxt.volume_idx(layerm->region()->config.perimeter_extruder.value, 0)]); - if (ctxt.has_infill) { - for (const ExtrusionEntity *ee : layerm->fills.entities) { - // fill represents infill extrusions of a single island. - const auto *fill = dynamic_cast(ee); - if (!fill->entities.empty()) - _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, - *vols[ctxt.volume_idx( - is_solid_infill(fill->entities.front()->role()) ? - layerm->region()->config.solid_infill_extruder : - layerm->region()->config.infill_extruder, - 1)]); - } - } - } - if (ctxt.has_support) { - const SupportLayer *support_layer = dynamic_cast(layer); - if (support_layer) { - for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) - _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, - *vols[ctxt.volume_idx( - (extrusion_entity->role() == erSupportMaterial) ? - support_layer->object()->config.support_material_extruder : - support_layer->object()->config.support_material_interface_extruder, - 2)]); - } - } - } - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { - // Store the vertex arrays and restart their containers, - vols[i] = new_volume(vol.color); - GLVolume &vol_new = *vols[i]; - // Assign the large pre-allocated buffers to the new GLVolume. - vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); - // Copy the content back to the old GLVolume. - vol.indexed_vertex_array = vol_new.indexed_vertex_array; - // Finalize a bounding box of the old GLVolume. - vol.bounding_box = vol.indexed_vertex_array.bounding_box(); - // Clear the buffers, but keep them pre-allocated. - vol_new.indexed_vertex_array.clear(); - // Just make sure that clear did not clear the reserved memory. - vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - } - } - } - for (GLVolume *vol : vols) { - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); - vol->indexed_vertex_array.shrink_to_fit(); - } - }); - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results"; - // Remove empty volumes from the newly added volumes. - m_volumes.volumes.erase( - std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), - [](const GLVolume *volume) { return volume->empty(); }), - m_volumes.volumes.end()); - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) - m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); - - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; -} - -void GLCanvas3D::load_wipe_tower_toolpaths(const std::vector& str_tool_colors) -{ - if ((m_print == nullptr) || m_print->m_wipe_tower_tool_changes.empty()) - return; - - if (!m_print->state.is_done(psWipeTower)) - return; - - std::vector tool_colors = _parse_colors(str_tool_colors); - - struct Ctxt - { - const Print *print; - const std::vector *tool_colors; - - // Number of vertices (each vertex is 6x4=24 bytes long) - static const size_t alloc_size_max() { return 131072; } // 3.15MB - static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } - - static const float* color_support() { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish - - // For cloring by a tool, return a parsed color. - bool color_by_tool() const { return tool_colors != nullptr; } - size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } - const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } - int volume_idx(int tool, int feature) const - { - return this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(tool, 0)) : feature; - } - - const std::vector& tool_change(size_t idx) { - return priming.empty() ? - ((idx == print->m_wipe_tower_tool_changes.size()) ? final : print->m_wipe_tower_tool_changes[idx]) : - ((idx == 0) ? priming : (idx == print->m_wipe_tower_tool_changes.size() + 1) ? final : print->m_wipe_tower_tool_changes[idx - 1]); - } - std::vector priming; - std::vector final; - } ctxt; - - ctxt.print = m_print; - ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - if (m_print->m_wipe_tower_priming) - ctxt.priming.emplace_back(*m_print->m_wipe_tower_priming.get()); - if (m_print->m_wipe_tower_final_purge) - ctxt.final.emplace_back(*m_print->m_wipe_tower_final_purge.get()); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start"; - - //FIXME Improve the heuristics for a grain size. - size_t n_items = m_print->m_wipe_tower_tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); - size_t grain_size = std::max(n_items / 128, size_t(1)); - tbb::spin_mutex new_volume_mutex; - auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { - auto *volume = new GLVolume(color); - new_volume_mutex.lock(); - volume->outside_printer_detection_enabled = false; - m_volumes.volumes.emplace_back(volume); - new_volume_mutex.unlock(); - return volume; - }; - const size_t volumes_cnt_initial = m_volumes.volumes.size(); - std::vector volumes_per_thread(n_items); - tbb::parallel_for( - tbb::blocked_range(0, n_items, grain_size), - [&ctxt, &new_volume](const tbb::blocked_range& range) { - // Bounding box of this slab of a wipe tower. - std::vector vols; - if (ctxt.color_by_tool()) { - for (size_t i = 0; i < ctxt.number_tools(); ++i) - vols.emplace_back(new_volume(ctxt.color_tool(i))); - } - else - vols = { new_volume(ctxt.color_support()) }; - for (GLVolume *volume : vols) - volume->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { - const std::vector &layer = ctxt.tool_change(idx_layer); - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { - vol.print_zs.push_back(layer.front().print_z); - vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); - vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); - } - } - for (const WipeTower::ToolChangeResult &extrusions : layer) { - for (size_t i = 1; i < extrusions.extrusions.size();) { - const WipeTower::Extrusion &e = extrusions.extrusions[i]; - if (e.width == 0.) { - ++i; - continue; - } - size_t j = i + 1; - if (ctxt.color_by_tool()) - for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++j); - else - for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++j); - size_t n_lines = j - i; - Lines lines; - std::vector widths; - std::vector heights; - lines.reserve(n_lines); - widths.reserve(n_lines); - heights.assign(n_lines, extrusions.layer_height); - for (; i < j; ++i) { - const WipeTower::Extrusion &e = extrusions.extrusions[i]; - assert(e.width > 0.f); - const WipeTower::Extrusion &e_prev = *(&e - 1); - lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y)); - widths.emplace_back(e.width); - } - _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, - *vols[ctxt.volume_idx(e.tool, 0)]); - } - } - } - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { - // Store the vertex arrays and restart their containers, - vols[i] = new_volume(vol.color); - GLVolume &vol_new = *vols[i]; - // Assign the large pre-allocated buffers to the new GLVolume. - vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); - // Copy the content back to the old GLVolume. - vol.indexed_vertex_array = vol_new.indexed_vertex_array; - // Finalize a bounding box of the old GLVolume. - vol.bounding_box = vol.indexed_vertex_array.bounding_box(); - // Clear the buffers, but keep them pre-allocated. - vol_new.indexed_vertex_array.clear(); - // Just make sure that clear did not clear the reserved memory. - vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - } - } - for (GLVolume *vol : vols) { - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); - vol->indexed_vertex_array.shrink_to_fit(); - } - }); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results"; - // Remove empty volumes from the newly added volumes. - m_volumes.volumes.erase( - std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), - [](const GLVolume *volume) { return volume->empty(); }), - m_volumes.volumes.end()); - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) - m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); - - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; -} - void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const std::vector& str_tool_colors) { if ((m_canvas != nullptr) && (m_print != nullptr)) @@ -2723,12 +2357,31 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const _load_shells(); } + _update_toolpath_volumes_outside_state(); } _update_gcode_volumes_visibility(preview_data); + _show_warning_texture_if_needed(); } } +void GLCanvas3D::load_preview(const std::vector& str_tool_colors) +{ + if (m_print == nullptr) + return; + + _load_print_toolpaths(); + _load_wipe_tower_toolpaths(str_tool_colors); + for (const PrintObject* object : m_print->objects) + { + if (object != nullptr) + _load_print_object_toolpaths(*object, str_tool_colors); + } + _update_toolpath_volumes_outside_state(); + _show_warning_texture_if_needed(); + reset_legend_texture(); +} + void GLCanvas3D::register_on_viewport_changed_callback(void* callback) { if (callback != nullptr) @@ -4112,6 +3765,372 @@ int GLCanvas3D::_get_first_selected_volume_id() const return -1; } +void GLCanvas3D::_load_print_toolpaths() +{ + // ensures this canvas is current + if (!set_current()) + return; + + if (m_print == nullptr) + return; + + if (!m_print->state.is_done(psSkirt) || !m_print->state.is_done(psBrim)) + return; + + if (!m_print->has_skirt() && (m_print->config.brim_width.value == 0)) + return; + + const float color[] = { 0.5f, 1.0f, 0.5f, 1.0f }; // greenish + + // number of skirt layers + size_t total_layer_count = 0; + for (const PrintObject* print_object : m_print->objects) + { + total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); + } + size_t skirt_height = m_print->has_infinite_skirt() ? total_layer_count : std::min(m_print->config.skirt_height.value, total_layer_count); + if ((skirt_height == 0) && (m_print->config.brim_width.value > 0)) + skirt_height = 1; + + // get first skirt_height layers (maybe this should be moved to a PrintObject method?) + const PrintObject* object0 = m_print->objects.front(); + std::vector print_zs; + print_zs.reserve(skirt_height * 2); + for (size_t i = 0; i < std::min(skirt_height, object0->layers.size()); ++i) + { + print_zs.push_back(float(object0->layers[i]->print_z)); + } + //FIXME why there are support layers? + for (size_t i = 0; i < std::min(skirt_height, object0->support_layers.size()); ++i) + { + print_zs.push_back(float(object0->support_layers[i]->print_z)); + } + sort_remove_duplicates(print_zs); + if (print_zs.size() > skirt_height) + print_zs.erase(print_zs.begin() + skirt_height, print_zs.end()); + + m_volumes.volumes.emplace_back(new GLVolume(color)); + GLVolume& volume = *m_volumes.volumes.back(); + for (size_t i = 0; i < skirt_height; ++i) { + volume.print_zs.push_back(print_zs[i]); + volume.offsets.push_back(volume.indexed_vertex_array.quad_indices.size()); + volume.offsets.push_back(volume.indexed_vertex_array.triangle_indices.size()); + if (i == 0) + _3DScene::extrusionentity_to_verts(m_print->brim, print_zs[i], Point(0, 0), volume); + + _3DScene::extrusionentity_to_verts(m_print->skirt, print_zs[i], Point(0, 0), volume); + } + volume.bounding_box = volume.indexed_vertex_array.bounding_box(); + volume.indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); +} + +void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const std::vector& str_tool_colors) +{ + std::vector tool_colors = _parse_colors(str_tool_colors); + + struct Ctxt + { + const Points *shifted_copies; + std::vector layers; + bool has_perimeters; + bool has_infill; + bool has_support; + const std::vector* tool_colors; + + // Number of vertices (each vertex is 6x4=24 bytes long) + static const size_t alloc_size_max() { return 131072; } // 3.15MB + // static const size_t alloc_size_max () { return 65536; } // 1.57MB + // static const size_t alloc_size_max () { return 32768; } // 786kB + static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } + + static const float* color_perimeters() { static float color[4] = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow + static const float* color_infill() { static float color[4] = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish + static const float* color_support() { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish + + // For cloring by a tool, return a parsed color. + bool color_by_tool() const { return tool_colors != nullptr; } + size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } + const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } + int volume_idx(int extruder, int feature) const + { + return this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(extruder - 1, 0)) : feature; + } + } ctxt; + + ctxt.shifted_copies = &print_object._shifted_copies; + + // order layers by print_z + ctxt.layers.reserve(print_object.layers.size() + print_object.support_layers.size()); + for (const Layer *layer : print_object.layers) + ctxt.layers.push_back(layer); + for (const Layer *layer : print_object.support_layers) + ctxt.layers.push_back(layer); + std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; }); + + // Maximum size of an allocation block: 32MB / sizeof(float) + ctxt.has_perimeters = print_object.state.is_done(posPerimeters); + ctxt.has_infill = print_object.state.is_done(posInfill); + ctxt.has_support = print_object.state.is_done(posSupportMaterial); + ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; + + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start"; + + //FIXME Improve the heuristics for a grain size. + size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); + tbb::spin_mutex new_volume_mutex; + auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { + auto *volume = new GLVolume(color); + new_volume_mutex.lock(); + volume->outside_printer_detection_enabled = false; + m_volumes.volumes.emplace_back(volume); + new_volume_mutex.unlock(); + return volume; + }; + const size_t volumes_cnt_initial = m_volumes.volumes.size(); + std::vector volumes_per_thread(ctxt.layers.size()); + tbb::parallel_for( + tbb::blocked_range(0, ctxt.layers.size(), grain_size), + [&ctxt, &new_volume](const tbb::blocked_range& range) { + std::vector vols; + if (ctxt.color_by_tool()) { + for (size_t i = 0; i < ctxt.number_tools(); ++i) + vols.emplace_back(new_volume(ctxt.color_tool(i))); + } + else + vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; + for (GLVolume *vol : vols) + vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { + const Layer *layer = ctxt.layers[idx_layer]; + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.print_zs.empty() || vol.print_zs.back() != layer->print_z) { + vol.print_zs.push_back(layer->print_z); + vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); + vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); + } + } + for (const Point © : *ctxt.shifted_copies) { + for (const LayerRegion *layerm : layer->regions) { + if (ctxt.has_perimeters) + _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, + *vols[ctxt.volume_idx(layerm->region()->config.perimeter_extruder.value, 0)]); + if (ctxt.has_infill) { + for (const ExtrusionEntity *ee : layerm->fills.entities) { + // fill represents infill extrusions of a single island. + const auto *fill = dynamic_cast(ee); + if (!fill->entities.empty()) + _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, + *vols[ctxt.volume_idx( + is_solid_infill(fill->entities.front()->role()) ? + layerm->region()->config.solid_infill_extruder : + layerm->region()->config.infill_extruder, + 1)]); + } + } + } + if (ctxt.has_support) { + const SupportLayer *support_layer = dynamic_cast(layer); + if (support_layer) { + for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) + _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, + *vols[ctxt.volume_idx( + (extrusion_entity->role() == erSupportMaterial) ? + support_layer->object()->config.support_material_extruder : + support_layer->object()->config.support_material_interface_extruder, + 2)]); + } + } + } + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { + // Store the vertex arrays and restart their containers, + vols[i] = new_volume(vol.color); + GLVolume &vol_new = *vols[i]; + // Assign the large pre-allocated buffers to the new GLVolume. + vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); + // Copy the content back to the old GLVolume. + vol.indexed_vertex_array = vol_new.indexed_vertex_array; + // Finalize a bounding box of the old GLVolume. + vol.bounding_box = vol.indexed_vertex_array.bounding_box(); + // Clear the buffers, but keep them pre-allocated. + vol_new.indexed_vertex_array.clear(); + // Just make sure that clear did not clear the reserved memory. + vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + } + } + } + for (GLVolume *vol : vols) { + vol->bounding_box = vol->indexed_vertex_array.bounding_box(); + vol->indexed_vertex_array.shrink_to_fit(); + } + }); + + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results"; + // Remove empty volumes from the newly added volumes. + m_volumes.volumes.erase( + std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), + [](const GLVolume *volume) { return volume->empty(); }), + m_volumes.volumes.end()); + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; +} + +void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_tool_colors) +{ + if ((m_print == nullptr) || m_print->m_wipe_tower_tool_changes.empty()) + return; + + if (!m_print->state.is_done(psWipeTower)) + return; + + std::vector tool_colors = _parse_colors(str_tool_colors); + + struct Ctxt + { + const Print *print; + const std::vector *tool_colors; + + // Number of vertices (each vertex is 6x4=24 bytes long) + static const size_t alloc_size_max() { return 131072; } // 3.15MB + static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } + + static const float* color_support() { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish + + // For cloring by a tool, return a parsed color. + bool color_by_tool() const { return tool_colors != nullptr; } + size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } + const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } + int volume_idx(int tool, int feature) const + { + return this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(tool, 0)) : feature; + } + + const std::vector& tool_change(size_t idx) { + return priming.empty() ? + ((idx == print->m_wipe_tower_tool_changes.size()) ? final : print->m_wipe_tower_tool_changes[idx]) : + ((idx == 0) ? priming : (idx == print->m_wipe_tower_tool_changes.size() + 1) ? final : print->m_wipe_tower_tool_changes[idx - 1]); + } + std::vector priming; + std::vector final; + } ctxt; + + ctxt.print = m_print; + ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; + if (m_print->m_wipe_tower_priming) + ctxt.priming.emplace_back(*m_print->m_wipe_tower_priming.get()); + if (m_print->m_wipe_tower_final_purge) + ctxt.final.emplace_back(*m_print->m_wipe_tower_final_purge.get()); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start"; + + //FIXME Improve the heuristics for a grain size. + size_t n_items = m_print->m_wipe_tower_tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); + size_t grain_size = std::max(n_items / 128, size_t(1)); + tbb::spin_mutex new_volume_mutex; + auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { + auto *volume = new GLVolume(color); + new_volume_mutex.lock(); + volume->outside_printer_detection_enabled = false; + m_volumes.volumes.emplace_back(volume); + new_volume_mutex.unlock(); + return volume; + }; + const size_t volumes_cnt_initial = m_volumes.volumes.size(); + std::vector volumes_per_thread(n_items); + tbb::parallel_for( + tbb::blocked_range(0, n_items, grain_size), + [&ctxt, &new_volume](const tbb::blocked_range& range) { + // Bounding box of this slab of a wipe tower. + std::vector vols; + if (ctxt.color_by_tool()) { + for (size_t i = 0; i < ctxt.number_tools(); ++i) + vols.emplace_back(new_volume(ctxt.color_tool(i))); + } + else + vols = { new_volume(ctxt.color_support()) }; + for (GLVolume *volume : vols) + volume->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { + const std::vector &layer = ctxt.tool_change(idx_layer); + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { + vol.print_zs.push_back(layer.front().print_z); + vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); + vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); + } + } + for (const WipeTower::ToolChangeResult &extrusions : layer) { + for (size_t i = 1; i < extrusions.extrusions.size();) { + const WipeTower::Extrusion &e = extrusions.extrusions[i]; + if (e.width == 0.) { + ++i; + continue; + } + size_t j = i + 1; + if (ctxt.color_by_tool()) + for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++j); + else + for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++j); + size_t n_lines = j - i; + Lines lines; + std::vector widths; + std::vector heights; + lines.reserve(n_lines); + widths.reserve(n_lines); + heights.assign(n_lines, extrusions.layer_height); + for (; i < j; ++i) { + const WipeTower::Extrusion &e = extrusions.extrusions[i]; + assert(e.width > 0.f); + const WipeTower::Extrusion &e_prev = *(&e - 1); + lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y)); + widths.emplace_back(e.width); + } + _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, + *vols[ctxt.volume_idx(e.tool, 0)]); + } + } + } + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { + // Store the vertex arrays and restart their containers, + vols[i] = new_volume(vol.color); + GLVolume &vol_new = *vols[i]; + // Assign the large pre-allocated buffers to the new GLVolume. + vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); + // Copy the content back to the old GLVolume. + vol.indexed_vertex_array = vol_new.indexed_vertex_array; + // Finalize a bounding box of the old GLVolume. + vol.bounding_box = vol.indexed_vertex_array.bounding_box(); + // Clear the buffers, but keep them pre-allocated. + vol_new.indexed_vertex_array.clear(); + // Just make sure that clear did not clear the reserved memory. + vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + } + } + for (GLVolume *vol : vols) { + vol->bounding_box = vol->indexed_vertex_array.bounding_box(); + vol->indexed_vertex_array.shrink_to_fit(); + } + }); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results"; + // Remove empty volumes from the newly added volumes. + m_volumes.volumes.erase( + std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), + [](const GLVolume *volume) { return volume->empty(); }), + m_volumes.volumes.end()); + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; +} + static inline int hex_digit_to_int(const char c) { return @@ -4643,6 +4662,7 @@ void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& previe for (std::vector::iterator it = begin; it != end; ++it) { GLVolume* volume = *it; + // to avoid the shader to change the color of this volume if outside the print volume volume->outside_printer_detection_enabled = false; switch (m_gcode_preview_volume_index.first_volumes[i].type) @@ -4691,6 +4711,55 @@ void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& previe } } +void GLCanvas3D::_update_toolpath_volumes_outside_state() +{ + // tolerance to avoid false detection at bed edges + static const coordf_t tolerance_x = 0.05; + static const coordf_t tolerance_y = 0.05; + + BoundingBoxf3 print_volume; + if (m_config != nullptr) + { + const ConfigOptionPoints* opt = dynamic_cast(m_config->option("bed_shape")); + if (opt != nullptr) + { + BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); + print_volume = BoundingBoxf3(Pointf3(unscale(bed_box_2D.min.x) - tolerance_x, unscale(bed_box_2D.min.y) - tolerance_y, 0.0), Pointf3(unscale(bed_box_2D.max.x) + tolerance_x, unscale(bed_box_2D.max.y) + tolerance_y, m_config->opt_float("max_print_height"))); + // Allow the objects to protrude below the print bed + print_volume.min.z = -1e10; + } + } + + for (GLVolume* volume : m_volumes.volumes) + { + volume->is_outside = (print_volume.radius() > 0.0) ? !print_volume.contains(volume->transformed_bounding_box()) : false; + } +} + +void GLCanvas3D::_show_warning_texture_if_needed() +{ + bool detected_outside = false; + for (const GLVolume* volume : m_volumes.volumes) + { + if ((volume != nullptr) && volume->is_outside) + { + detected_outside = true; + break; + } + } + + if (detected_outside) + { + enable_warning_texture(true); + _generate_warning_texture(L("Detected toolpath outside print volume")); + } + else + { + enable_warning_texture(false); + _reset_warning_texture(); + } +} + void GLCanvas3D::_on_move(const std::vector& volume_idxs) { if (m_model == nullptr) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index a9a6117ecc..d52d69eebf 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -559,16 +559,8 @@ public: void reload_scene(bool force); - // Create 3D thick extrusion lines for a skirt and brim. - // Adds a new Slic3r::GUI::3DScene::Volume to volumes. - void load_print_toolpaths(); - // Create 3D thick extrusion lines for object forming extrusions. - // Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes, - // one for perimeters, one for infill and one for supports. - void load_print_object_toolpaths(const PrintObject& print_object, const std::vector& str_tool_colors); - // Create 3D thick extrusion lines for wipe tower extrusions - void load_wipe_tower_toolpaths(const std::vector& str_tool_colors); void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector& str_tool_colors); + void load_preview(const std::vector& str_tool_colors); void register_on_viewport_changed_callback(void* callback); void register_on_double_click_callback(void* callback); @@ -652,6 +644,16 @@ private: int _get_first_selected_object_id() const; int _get_first_selected_volume_id() const; + // Create 3D thick extrusion lines for a skirt and brim. + // Adds a new Slic3r::GUI::3DScene::Volume to volumes. + void _load_print_toolpaths(); + // Create 3D thick extrusion lines for object forming extrusions. + // Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes, + // one for perimeters, one for infill and one for supports. + void _load_print_object_toolpaths(const PrintObject& print_object, const std::vector& str_tool_colors); + // Create 3D thick extrusion lines for wipe tower extrusions + void _load_wipe_tower_toolpaths(const std::vector& str_tool_colors); + // generates gcode extrusion paths geometry void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector& tool_colors); // generates gcode travel paths geometry @@ -667,6 +669,8 @@ private: void _load_shells(); // sets gcode geometry visibility according to user selection void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data); + void _update_toolpath_volumes_outside_state(); + void _show_warning_texture_if_needed(); void _on_move(const std::vector& volume_idxs); void _on_select(int volume_idx); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp index 5e9048d541..25733c7a26 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -516,30 +516,6 @@ void GLCanvas3DManager::reload_scene(wxGLCanvas* canvas, bool force) it->second->reload_scene(force); } -void GLCanvas3DManager::load_print_toolpaths(wxGLCanvas* canvas) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->load_print_toolpaths(); -} - -void GLCanvas3DManager::load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector& tool_colors) -{ - if (print_object == nullptr) - return; - - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->load_print_object_toolpaths(*print_object, tool_colors); -} - -void GLCanvas3DManager::load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector& str_tool_colors) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->load_wipe_tower_toolpaths(str_tool_colors); -} - void GLCanvas3DManager::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector& str_tool_colors) { if (preview_data == nullptr) @@ -550,6 +526,13 @@ void GLCanvas3DManager::load_gcode_preview(wxGLCanvas* canvas, const GCodePrevie it->second->load_gcode_preview(*preview_data, str_tool_colors); } +void GLCanvas3DManager::load_preview(wxGLCanvas* canvas, const std::vector& str_tool_colors) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->load_preview(str_tool_colors); +} + void GLCanvas3DManager::reset_legend_texture() { for (CanvasesMap::value_type& canvas : m_canvases) diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp index 32aa712d35..f27293273e 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -132,10 +132,8 @@ public: void reload_scene(wxGLCanvas* canvas, bool force); - void load_print_toolpaths(wxGLCanvas* canvas); - void load_print_object_toolpaths(wxGLCanvas* canvas, const PrintObject* print_object, const std::vector& tool_colors); - void load_wipe_tower_toolpaths(wxGLCanvas* canvas, const std::vector& str_tool_colors); void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector& str_tool_colors); + void load_preview(wxGLCanvas* canvas, const std::vector& str_tool_colors); void reset_legend_texture(); diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 5c2f7df854..e094941462 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -657,27 +657,6 @@ reload_scene(canvas, force) CODE: _3DScene::reload_scene((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), force); -void -load_print_toolpaths(canvas) - SV *canvas; - CODE: - _3DScene::load_print_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas")); - -void -load_print_object_toolpaths(canvas, print_object, tool_colors) - SV *canvas; - PrintObject *print_object; - std::vector tool_colors; - CODE: - _3DScene::load_print_object_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), print_object, tool_colors); - -void -load_wipe_tower_toolpaths(canvas, tool_colors) - SV *canvas; - std::vector tool_colors; - CODE: - _3DScene::load_wipe_tower_toolpaths((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), tool_colors); - void load_gcode_preview(canvas, preview_data, str_tool_colors) SV *canvas; @@ -685,5 +664,12 @@ load_gcode_preview(canvas, preview_data, str_tool_colors) std::vector str_tool_colors; CODE: _3DScene::load_gcode_preview((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), preview_data, str_tool_colors); - + +void +load_preview(canvas, str_tool_colors) + SV *canvas; + std::vector str_tool_colors; + CODE: + _3DScene::load_preview((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), str_tool_colors); + %} From b49bfadd87795d217af9a682ea79f2b4d8a40575 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 24 Jul 2018 15:27:31 +0200 Subject: [PATCH 017/119] PresetUpdater: Fail harder on bundle version not present in index --- lib/Slic3r/GUI.pm | 4 ++-- xs/src/slic3r/Utils/PresetUpdater.cpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 0b95962613..483fd36f95 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -145,8 +145,8 @@ sub OnInit { } }; if ($@) { - warn $@ . "\n"; - fatal_error(undef, $@); + show_error(undef, $@); + $self->{mainframe}->Close; } }); diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index c962a2c823..6e23ab4219 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -322,8 +322,9 @@ Updates PresetUpdater::priv::get_config_updates() const const auto ver_current = idx.find(vp.config_version); if (ver_current == idx.end()) { - BOOST_LOG_TRIVIAL(error) << boost::format("Preset bundle (`%1%`) version not found in index: %2%") % idx.vendor() % vp.config_version.to_string(); - continue; + auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str(); + BOOST_LOG_TRIVIAL(error) << message; + throw std::runtime_error(message); } // Getting a recommended version from the latest index, wich may have been downloaded From d8f5daf345d09e61a6e6fd48ad1d09d6d1b073fb Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 24 Jul 2018 15:32:44 +0200 Subject: [PATCH 018/119] Fixed selection of object modified by gizmo --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 33 +++++++++----------------------- xs/src/slic3r/GUI/GLCanvas3D.hpp | 2 +- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 819f26609d..193ccd0687 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -2715,7 +2715,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { update_gizmos_data(); m_gizmos.start_dragging(); - m_mouse.drag.gizmo_volume_idx = _get_first_selected_volume_id(); + m_mouse.drag.gizmo_volume_idx = _get_first_selected_volume_id(selected_object_idx); m_dirty = true; } else @@ -3736,32 +3736,17 @@ int GLCanvas3D::_get_first_selected_object_id() const return -1; } -int GLCanvas3D::_get_first_selected_volume_id() const +int GLCanvas3D::_get_first_selected_volume_id(int object_id) const { - if (m_print != nullptr) - { - int objects_count = (int)m_print->objects.size(); + int volume_id = -1; - for (const GLVolume* vol : m_volumes.volumes) - { - if ((vol != nullptr) && vol->selected) - { - int object_id = vol->select_group_id / 1000000; - // Objects with object_id >= 1000 have a specific meaning, for example the wipe tower proxy. - if ((object_id < 10000) && (object_id < objects_count)) - { - int volume_id = 0; - for (int i = 0; i < object_id; ++i) - { - const PrintObject* obj = m_print->objects[i]; - const ModelObject* model = obj->model_object(); - volume_id += model->instances.size(); - } - return volume_id; - } - } - } + for (const GLVolume* vol : m_volumes.volumes) + { + ++volume_id; + if ((vol != nullptr) && vol->selected && (object_id == vol->select_group_id / 1000000)) + return volume_id; } + return -1; } diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index d52d69eebf..0d8cf8855e 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -642,7 +642,7 @@ private: void _stop_timer(); int _get_first_selected_object_id() const; - int _get_first_selected_volume_id() const; + int _get_first_selected_volume_id(int object_id) const; // Create 3D thick extrusion lines for a skirt and brim. // Adds a new Slic3r::GUI::3DScene::Volume to volumes. From 07ae905150d026b3fdd47dd609668cc36bf8880b Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 24 Jul 2018 15:47:13 +0200 Subject: [PATCH 019/119] Sync PrusaResearch.idx --- resources/profiles/PrusaResearch.idx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index bdb372d814..522243e6b2 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,8 +1,13 @@ min_slic3r_version = 1.41.0-alpha 0.2.0-alpha2 -0.2.0-alpha1 -0.2.0-alpha +0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 +0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters min_slic3r_version = 1.40.0 +0.1.11 fw version changed to 3.3.1 +0.1.10 MK3 jerk and acceleration update +0.1.9 edited support extrusion width for 0.25 and 0.6 nozzles +0.1.8 extrusion width for 0,25, 0.6 and variable layer height fixes +0.1.7 Fixed errors in 0.25mm and 0.6mm profiles 0.1.6 Split the MK2.5 profile from the MK2S min_slic3r_version = 1.40.0-beta 0.1.5 fixed printer_variant fields for the i3 MK3 0.25 and 0.6mm nozzles From e0e6a238107b3614fde83d79a6e6056d0a378e2a Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 24 Jul 2018 15:53:17 +0200 Subject: [PATCH 020/119] PrusaResearch.idx: Comment 0.2.0-alpha2 --- resources/profiles/PrusaResearch.idx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 522243e6b2..5c06353caa 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,5 +1,5 @@ min_slic3r_version = 1.41.0-alpha -0.2.0-alpha2 +0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material 0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters min_slic3r_version = 1.40.0 From 2107ea7702d56810c584f3fcafff5555458a86f9 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 25 Jul 2018 08:40:34 +0200 Subject: [PATCH 021/119] Fixed selection of multimaterial objects --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 193ccd0687..3ee1fdb02c 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -2816,7 +2816,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { if (v != nullptr) { - if ((m_mouse.drag.move_with_shift && (v->select_group_id == group_id)) || (v->drag_group_id == group_id)) + if ((m_mouse.drag.move_with_shift && (v->select_group_id == group_id)) || (!m_mouse.drag.move_with_shift && (v->drag_group_id == group_id))) volumes.push_back(v); } } From dd724e9dab1c79b1e4d7e92940c4f33a28f18843 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 25 Jul 2018 09:19:20 +0200 Subject: [PATCH 022/119] M73 lines emitted to gcode only for Marlin firmare. Fixes #1071 --- xs/src/libslic3r/GCode.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 89a72a7251..94634f4e42 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -377,10 +377,13 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ } fclose(file); - m_normal_time_estimator.post_process_remaining_times(path_tmp, 60.0f); + if (print->config.gcode_flavor.value == gcfMarlin) + { + m_normal_time_estimator.post_process_remaining_times(path_tmp, 60.0f); - if (m_silent_time_estimator_enabled) - m_silent_time_estimator.post_process_remaining_times(path_tmp, 60.0f); + if (m_silent_time_estimator_enabled) + m_silent_time_estimator.post_process_remaining_times(path_tmp, 60.0f); + } if (! this->m_placeholder_parser_failed_templates.empty()) { // G-code export proceeded, but some of the PlaceholderParser substitutions failed. From 4243c7d84aff757108d365c147955210c7058b86 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 25 Jul 2018 09:42:03 +0200 Subject: [PATCH 023/119] Removed 2D panel --- lib/Slic3r/GUI/Plater.pm | 157 ++++++++++++++++++++------------------- 1 file changed, 79 insertions(+), 78 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index c0718c77be..0433deac66 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -128,8 +128,8 @@ sub new { } $_->set_scaling_factor($scale) for @{ $model_object->instances }; - $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); - $object->transform_thumbnail($self->{model}, $obj_idx); + $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); +# $object->transform_thumbnail($self->{model}, $obj_idx); #update print and start background processing $self->{print}->add_model_object($model_object, $obj_idx); @@ -203,13 +203,13 @@ sub new { Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{canvas3D}, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{preview3D}->canvas, $self->{canvas3D}); }); } - # Initialize 2D preview canvas - $self->{canvas} = Slic3r::GUI::Plater::2D->new($self->{preview_notebook}, wxDefaultSize, $self->{objects}, $self->{model}, $self->{config}); - $self->{preview_notebook}->AddPage($self->{canvas}, L('2D')); - $self->{canvas}->on_select_object($on_select_object); - $self->{canvas}->on_double_click($on_double_click); - $self->{canvas}->on_right_click(sub { $on_right_click->($self->{canvas}, @_); }); - $self->{canvas}->on_instances_moved($on_instances_moved); +# # Initialize 2D preview canvas +# $self->{canvas} = Slic3r::GUI::Plater::2D->new($self->{preview_notebook}, wxDefaultSize, $self->{objects}, $self->{model}, $self->{config}); +# $self->{preview_notebook}->AddPage($self->{canvas}, L('2D')); +# $self->{canvas}->on_select_object($on_select_object); +# $self->{canvas}->on_double_click($on_double_click); +# $self->{canvas}->on_right_click(sub { $on_right_click->($self->{canvas}, @_); }); +# $self->{canvas}->on_instances_moved($on_instances_moved); # Initialize 3D toolpaths preview if ($Slic3r::GUI::have_OpenGL) { @@ -401,7 +401,8 @@ sub new { $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self)) for grep defined($_), - $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}, $self->{list}; + $self, $self->{canvas3D}, $self->{preview3D}, $self->{list}; +# $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}, $self->{list}; EVT_COMMAND($self, -1, $PROGRESS_BAR_EVENT, sub { my ($self, $event) = @_; @@ -432,7 +433,7 @@ sub new { }); } - $self->{canvas}->update_bed_size; +# $self->{canvas}->update_bed_size; if ($self->{canvas3D}) { Slic3r::GUI::_3DScene::set_bed_shape($self->{canvas3D}, $self->{config}->bed_shape); Slic3r::GUI::_3DScene::zoom_to_bed($self->{canvas3D}); @@ -847,8 +848,8 @@ sub load_model_objects { $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); - - $self->reset_thumbnail($obj_idx); + +# $self->reset_thumbnail($obj_idx); } $self->arrange if $need_arrange; $self->update; @@ -1057,7 +1058,7 @@ sub rotate { $inst->set_rotation($rotation); Slic3r::GUI::_3DScene::update_gizmos_data($self->{canvas3D}) if ($self->{canvas3D}); } - $object->transform_thumbnail($self->{model}, $obj_idx); +# $object->transform_thumbnail($self->{model}, $obj_idx); } else { # rotation around X and Y needs to be performed on mesh # so we first apply any Z rotation @@ -1067,9 +1068,9 @@ sub rotate { } $model_object->rotate(deg2rad($angle), $axis); - # realign object to Z = 0 - $model_object->center_around_origin; - $self->reset_thumbnail($obj_idx); +# # realign object to Z = 0 +# $model_object->center_around_origin; +# $self->reset_thumbnail($obj_idx); } # update print and start background processing @@ -1097,9 +1098,9 @@ sub mirror { $model_object->mirror($axis); - # realign object to Z = 0 - $model_object->center_around_origin; - $self->reset_thumbnail($obj_idx); +# # realign object to Z = 0 +# $model_object->center_around_origin; +# $self->reset_thumbnail($obj_idx); # update print and start background processing $self->stop_background_process; @@ -1149,7 +1150,7 @@ sub changescale { #FIXME Scale the layer height profile when $axis == Z? #FIXME Scale the layer height ranges $axis == Z? # object was already aligned to Z = 0, so no need to realign it - $self->reset_thumbnail($obj_idx); +# $self->reset_thumbnail($obj_idx); } else { my $scale; if ($tosize) { @@ -1173,7 +1174,7 @@ sub changescale { $range->[1] *= $variation; } $_->set_scaling_factor($scale) for @{ $model_object->instances }; - $object->transform_thumbnail($self->{model}, $obj_idx); +# $object->transform_thumbnail($self->{model}, $obj_idx); } # update print and start background processing @@ -1804,10 +1805,10 @@ sub _get_export_file { return $output_file; } -sub reset_thumbnail { - my ($self, $obj_idx) = @_; - $self->{objects}[$obj_idx]->thumbnail(undef); -} +#sub reset_thumbnail { +# my ($self, $obj_idx) = @_; +# $self->{objects}[$obj_idx]->thumbnail(undef); +#} # this method gets called whenever print center is changed or the objects' bounding box changes # (i.e. when an object is added/removed/moved/rotated/scaled) @@ -1831,7 +1832,7 @@ sub update { $self->resume_background_process; } - $self->{canvas}->reload_scene if $self->{canvas}; +# $self->{canvas}->reload_scene if $self->{canvas}; my $selections = $self->collect_selections; Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 0); @@ -1888,7 +1889,7 @@ sub on_config_change { foreach my $opt_key (@{$self->{config}->diff($config)}) { $self->{config}->set($opt_key, $config->get($opt_key)); if ($opt_key eq 'bed_shape') { - $self->{canvas}->update_bed_size; +# $self->{canvas}->update_bed_size; Slic3r::GUI::_3DScene::set_bed_shape($self->{canvas3D}, $self->{config}->bed_shape) if $self->{canvas3D}; Slic3r::GUI::_3DScene::set_bed_shape($self->{preview3D}->canvas, $self->{config}->bed_shape) if $self->{preview3D}; $update_scheduled = 1; @@ -1948,7 +1949,7 @@ sub list_item_deselected { $self->{_lecursor} = Wx::BusyCursor->new(); if ($self->{list}->GetFirstSelected == -1) { $self->select_object(undef); - $self->{canvas}->Refresh; +# $self->{canvas}->Refresh; Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas3D}) if $self->{canvas3D}; Slic3r::GUI::_3DScene::render($self->{canvas3D}) if $self->{canvas3D}; } @@ -1961,7 +1962,7 @@ sub list_item_selected { $self->{_lecursor} = Wx::BusyCursor->new(); my $obj_idx = $event->GetIndex; $self->select_object($obj_idx); - $self->{canvas}->Refresh; +# $self->{canvas}->Refresh; if ($self->{canvas3D}) { my $selections = $self->collect_selections; Slic3r::GUI::_3DScene::update_volumes_selection($self->{canvas3D}, \@$selections); @@ -2058,19 +2059,19 @@ sub object_settings_dialog { $self->pause_background_process; $dlg->ShowModal; - # update thumbnail since parts may have changed - if ($dlg->PartsChanged) { - # recenter and re-align to Z = 0 - $model_object->center_around_origin; - $self->reset_thumbnail($obj_idx); - } +# # update thumbnail since parts may have changed +# if ($dlg->PartsChanged) { +# # recenter and re-align to Z = 0 +# $model_object->center_around_origin; +# $self->reset_thumbnail($obj_idx); +# } # update print if ($dlg->PartsChanged || $dlg->PartSettingsChanged) { $self->stop_background_process; $self->{print}->reload_object($obj_idx); $self->schedule_background_process; - $self->{canvas}->reload_scene if $self->{canvas}; +# $self->{canvas}->reload_scene if $self->{canvas}; my $selections = $self->collect_selections; Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 0); @@ -2356,48 +2357,48 @@ package Slic3r::GUI::Plater::Object; use Moo; has 'name' => (is => 'rw', required => 1); -has 'thumbnail' => (is => 'rw'); # ExPolygon::Collection in scaled model units with no transforms -has 'transformed_thumbnail' => (is => 'rw'); -has 'instance_thumbnails' => (is => 'ro', default => sub { [] }); # array of ExPolygon::Collection objects, each one representing the actual placed thumbnail of each instance in pixel units +#has 'thumbnail' => (is => 'rw'); # ExPolygon::Collection in scaled model units with no transforms +#has 'transformed_thumbnail' => (is => 'rw'); +#has 'instance_thumbnails' => (is => 'ro', default => sub { [] }); # array of ExPolygon::Collection objects, each one representing the actual placed thumbnail of each instance in pixel units has 'selected' => (is => 'rw', default => sub { 0 }); -sub make_thumbnail { - my ($self, $model, $obj_idx) = @_; - # make method idempotent - $self->thumbnail->clear; - # raw_mesh is the non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. - my $mesh = $model->objects->[$obj_idx]->raw_mesh; -#FIXME The "correct" variant could be extremely slow. -# if ($mesh->facets_count <= 5000) { -# # remove polygons with area <= 1mm -# my $area_threshold = Slic3r::Geometry::scale 1; -# $self->thumbnail->append( -# grep $_->area >= $area_threshold, -# @{ $mesh->horizontal_projection }, # horizontal_projection returns scaled expolygons -# ); -# $self->thumbnail->simplify(0.5); -# } else { - my $convex_hull = Slic3r::ExPolygon->new($mesh->convex_hull); - $self->thumbnail->append($convex_hull); -# } - return $self->thumbnail; -} - -sub transform_thumbnail { - my ($self, $model, $obj_idx) = @_; - - return unless defined $self->thumbnail; - - my $model_object = $model->objects->[$obj_idx]; - my $model_instance = $model_object->instances->[0]; - - # the order of these transformations MUST be the same everywhere, including - # in Slic3r::Print->add_model_object() - my $t = $self->thumbnail->clone; - $t->rotate($model_instance->rotation, Slic3r::Point->new(0,0)); - $t->scale($model_instance->scaling_factor); - - $self->transformed_thumbnail($t); -} +#sub make_thumbnail { +# my ($self, $model, $obj_idx) = @_; +# # make method idempotent +# $self->thumbnail->clear; +# # raw_mesh is the non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. +# my $mesh = $model->objects->[$obj_idx]->raw_mesh; +##FIXME The "correct" variant could be extremely slow. +## if ($mesh->facets_count <= 5000) { +## # remove polygons with area <= 1mm +## my $area_threshold = Slic3r::Geometry::scale 1; +## $self->thumbnail->append( +## grep $_->area >= $area_threshold, +## @{ $mesh->horizontal_projection }, # horizontal_projection returns scaled expolygons +## ); +## $self->thumbnail->simplify(0.5); +## } else { +# my $convex_hull = Slic3r::ExPolygon->new($mesh->convex_hull); +# $self->thumbnail->append($convex_hull); +## } +# return $self->thumbnail; +#} +# +#sub transform_thumbnail { +# my ($self, $model, $obj_idx) = @_; +# +# return unless defined $self->thumbnail; +# +# my $model_object = $model->objects->[$obj_idx]; +# my $model_instance = $model_object->instances->[0]; +# +# # the order of these transformations MUST be the same everywhere, including +# # in Slic3r::Print->add_model_object() +# my $t = $self->thumbnail->clone; +# $t->rotate($model_instance->rotation, Slic3r::Point->new(0,0)); +# $t->scale($model_instance->scaling_factor); +# +# $self->transformed_thumbnail($t); +#} 1; From efbc1cce2511c0a47d0dee7fd45df897e27ca982 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 25 Jul 2018 11:49:38 +0200 Subject: [PATCH 024/119] Fixed rotate gizmo update with multimaterial objects --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 42 ++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 3ee1fdb02c..5ad9da9c4e 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -2887,7 +2887,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (!volumes.empty()) { - const BoundingBoxf3& bb = volumes[0]->transformed_bounding_box(); + BoundingBoxf3 bb; + for (const GLVolume* volume : volumes) + { + bb.merge(volume->transformed_bounding_box()); + } const Pointf3& size = bb.size(); m_on_update_geometry_info_callback.call(size.x, size.y, size.z, m_gizmos.get_scale()); } @@ -3155,11 +3159,45 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box() const BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const { BoundingBoxf3 bb; + + std::vector selected_volumes; for (const GLVolume* volume : m_volumes.volumes) { if ((volume != nullptr) && !volume->is_wipe_tower && volume->selected) - bb.merge(volume->transformed_bounding_box()); + selected_volumes.push_back(volume); } + + bool use_drag_group_id = selected_volumes.size() > 1; + if (use_drag_group_id) + { + int drag_group_id = selected_volumes[0]->drag_group_id; + for (const GLVolume* volume : selected_volumes) + { + if (drag_group_id != volume->drag_group_id) + { + use_drag_group_id = false; + break; + } + } + } + + if (use_drag_group_id) + { + for (const GLVolume* volume : selected_volumes) + { + bb.merge(volume->bounding_box); + } + + bb = bb.transformed(selected_volumes[0]->world_matrix()); + } + else + { + for (const GLVolume* volume : selected_volumes) + { + bb.merge(volume->transformed_bounding_box()); + } + } + return bb; } From dd014136b00569f9ad7f1ba72676b9f8d3d8fea6 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 26 Jul 2018 12:51:31 +0200 Subject: [PATCH 025/119] Remove hovering on objects when mouse leaves 3D scene --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 5ad9da9c4e..79ec73625d 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -2678,6 +2678,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.set_start_position_2D_as_invalid(); #endif } + else if (evt.Leaving()) + { + // to remove hover when mouse goes out of this canvas + m_mouse.position = Pointf((coordf_t)pos.x, (coordf_t)pos.y); + render(); + } else if (evt.LeftDClick() && (m_hover_volume_id != -1)) m_on_double_click_callback.call(); else if (evt.LeftDown() || evt.RightDown()) From 629108265b5c9c1e6f4496a73fe6b1c0d0f83d6d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 26 Jul 2018 12:57:47 +0200 Subject: [PATCH 026/119] Fix for SPE-421 and emergency fix for SPE-422 (needs further investigation) --- xs/src/libnest2d/libnest2d/selections/firstfit.hpp | 3 +-- xs/src/libslic3r/Model.cpp | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index b6e80520c9..5185014a81 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -61,8 +61,7 @@ public: while (it != store_.end()) { Placer p(bin); if(!p.pack(*it)) { - auto itmp = it++; - store_.erase(itmp); + it = store_.erase(it); } else it++; } } diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index b2e439e5d4..8054d6a69c 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -549,7 +549,7 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, auto& sh = pile.back(); // We retrieve the reference point of this item - auto rv = Nfp::referenceVertex(sh); + auto rv = ShapeLike::boundingBox(sh).center(); // We get the distance of the reference point from the center of the // heat bed @@ -558,7 +558,7 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // The score will be the normalized distance which will be minimized, // effectively creating a circle shaped pile of items - double score = double(d)/norm; + double score = d/norm; // If it does not fit into the print bed we will beat it // with a large penality. If we would not do this, there would be only From b5b7894a6f6ac57b3a3f2b2546fc1c98b77f4935 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 26 Jul 2018 13:12:09 +0200 Subject: [PATCH 027/119] Fixed color of all toolpaths when detected as out of print bed volume --- xs/src/slic3r/GUI/3DScene.cpp | 10 +++++----- xs/src/slic3r/GUI/3DScene.hpp | 4 ++-- xs/src/slic3r/GUI/GLCanvas3D.cpp | 4 ---- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index e23641cb18..2d405e54ad 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -210,7 +210,7 @@ GLVolume::GLVolume(float r, float g, float b, float a) , selected(false) , is_active(true) , zoom_to_volumes(true) - , outside_printer_detection_enabled(true) + , shader_outside_printer_detection_enabled(false) , is_outside(false) , hover(false) , is_modifier(false) @@ -250,7 +250,7 @@ void GLVolume::set_render_color() set_render_color(is_outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4); else if (hover) set_render_color(HOVER_COLOR, 4); - else if (is_outside && outside_printer_detection_enabled) + else if (is_outside && shader_outside_printer_detection_enabled) set_render_color(OUTSIDE_COLOR, 4); else set_render_color(color, 4); @@ -441,7 +441,7 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c ::glColor4f(render_color[0], render_color[1], render_color[2], render_color[3]); if (detection_id != -1) - ::glUniform1i(detection_id, outside_printer_detection_enabled ? 1 : 0); + ::glUniform1i(detection_id, shader_outside_printer_detection_enabled ? 1 : 0); if (worldmatrix_id != -1) ::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().data()); @@ -460,7 +460,7 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c ::glColor4f(render_color[0], render_color[1], render_color[2], render_color[3]); if (detection_id != -1) - ::glUniform1i(detection_id, outside_printer_detection_enabled ? 1 : 0); + ::glUniform1i(detection_id, shader_outside_printer_detection_enabled ? 1 : 0); if (worldmatrix_id != -1) ::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().data()); @@ -633,7 +633,7 @@ std::vector GLVolumeCollection::load_object( v.extruder_id = extruder_id; } v.is_modifier = model_volume->modifier; - v.outside_printer_detection_enabled = !model_volume->modifier; + v.shader_outside_printer_detection_enabled = !model_volume->modifier; v.set_origin(Pointf3(instance->offset.x, instance->offset.y, 0.0)); v.set_angle_z(instance->rotation); v.set_scale_factor(instance->scaling_factor); diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index 0cd2d81f0e..242487195e 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -289,8 +289,8 @@ public: bool is_active; // Whether or not to use this volume when applying zoom_to_volumes() bool zoom_to_volumes; - // Wheter or not this volume is enabled for outside print volume detection. - bool outside_printer_detection_enabled; + // Wheter or not this volume is enabled for outside print volume detection in shader. + bool shader_outside_printer_detection_enabled; // Wheter or not this volume is outside print volume. bool is_outside; // Boolean: Is mouse over this object? diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 79ec73625d..6bca173755 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -3910,7 +3910,6 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { auto *volume = new GLVolume(color); new_volume_mutex.lock(); - volume->outside_printer_detection_enabled = false; m_volumes.volumes.emplace_back(volume); new_volume_mutex.unlock(); return volume; @@ -4063,7 +4062,6 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { auto *volume = new GLVolume(color); new_volume_mutex.lock(); - volume->outside_printer_detection_enabled = false; m_volumes.volumes.emplace_back(volume); new_volume_mutex.unlock(); return volume; @@ -4691,8 +4689,6 @@ void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& previe for (std::vector::iterator it = begin; it != end; ++it) { GLVolume* volume = *it; - // to avoid the shader to change the color of this volume if outside the print volume - volume->outside_printer_detection_enabled = false; switch (m_gcode_preview_volume_index.first_volumes[i].type) { From c2ab8c2ae3af6bd5489a7f1525b65fb51862b724 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 27 Jul 2018 08:49:58 +0200 Subject: [PATCH 028/119] Out of print volume detection for extrusion toolpaths only --- xs/src/slic3r/GUI/3DScene.cpp | 1 + xs/src/slic3r/GUI/3DScene.hpp | 2 ++ xs/src/slic3r/GUI/GLCanvas3D.cpp | 13 +++++++++++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 2d405e54ad..601d10aaa0 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -215,6 +215,7 @@ GLVolume::GLVolume(float r, float g, float b, float a) , hover(false) , is_modifier(false) , is_wipe_tower(false) + , is_extrusion_path(false) , tverts_range(0, size_t(-1)) , qverts_range(0, size_t(-1)) { diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index 242487195e..a1227d8ddb 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -299,6 +299,8 @@ public: bool is_modifier; // Wheter or not this volume has been generated from the wipe tower bool is_wipe_tower; + // Wheter or not this volume has been generated from an extrusion path + bool is_extrusion_path; // Interleaved triangles & normals with indexed triangles & quads. GLIndexedVertexArray indexed_vertex_array; diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 6bca173755..bcd4e65ba6 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1828,7 +1828,6 @@ unsigned int GLCanvas3D::get_volumes_count() const void GLCanvas3D::reset_volumes() { - if (!m_volumes.empty()) { // ensures this canvas is current @@ -1839,6 +1838,9 @@ void GLCanvas3D::reset_volumes() m_volumes.clear(); m_dirty = true; } + + enable_warning_texture(false); + _reset_warning_texture(); } void GLCanvas3D::deselect_volumes() @@ -2377,6 +2379,12 @@ void GLCanvas3D::load_preview(const std::vector& str_tool_colors) if (object != nullptr) _load_print_object_toolpaths(*object, str_tool_colors); } + + for (GLVolume* volume : m_volumes.volumes) + { + volume->is_extrusion_path = true; + } + _update_toolpath_volumes_outside_state(); _show_warning_texture_if_needed(); reset_legend_texture(); @@ -4276,6 +4284,7 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat if (volume != nullptr) { filter.volume = volume; + volume->is_extrusion_path = true; m_volumes.volumes.emplace_back(volume); } else @@ -4757,7 +4766,7 @@ void GLCanvas3D::_update_toolpath_volumes_outside_state() for (GLVolume* volume : m_volumes.volumes) { - volume->is_outside = (print_volume.radius() > 0.0) ? !print_volume.contains(volume->transformed_bounding_box()) : false; + volume->is_outside = ((print_volume.radius() > 0.0) && volume->is_extrusion_path) ? !print_volume.contains(volume->bounding_box) : false; } } From 3a1ec8285eef3fd1709b40abd0504778a370fcbb Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 27 Jul 2018 09:38:39 +0200 Subject: [PATCH 029/119] Reddish background when detected out of print volume toolpaths --- lib/Slic3r/GUI/Plater.pm | 1 + xs/src/slic3r/GUI/3DScene.cpp | 5 +++ xs/src/slic3r/GUI/3DScene.hpp | 1 + xs/src/slic3r/GUI/GLCanvas3D.cpp | 49 ++++++++++++++++--------- xs/src/slic3r/GUI/GLCanvas3D.hpp | 4 ++ xs/src/slic3r/GUI/GLCanvas3DManager.cpp | 7 ++++ xs/src/slic3r/GUI/GLCanvas3DManager.hpp | 1 + xs/xsp/GUI_3DScene.xsp | 7 ++++ 8 files changed, 57 insertions(+), 18 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 0433deac66..3bcf3b3e6b 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -215,6 +215,7 @@ sub new { if ($Slic3r::GUI::have_OpenGL) { $self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print}, $self->{gcode_preview_data}, $self->{config}); Slic3r::GUI::_3DScene::enable_legend_texture($self->{preview3D}->canvas, 1); + Slic3r::GUI::_3DScene::enable_dynamic_background($self->{preview3D}->canvas, 1); Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{preview3D}->canvas, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{canvas3D}, $self->{preview3D}->canvas); }); $self->{preview_notebook}->AddPage($self->{preview3D}, L('Preview')); $self->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1; diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 601d10aaa0..6ec3391539 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -1787,6 +1787,11 @@ void _3DScene::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable) s_canvas_mgr.enable_force_zoom_to_bed(canvas, enable); } +void _3DScene::enable_dynamic_background(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_dynamic_background(canvas, enable); +} + void _3DScene::allow_multisample(wxGLCanvas* canvas, bool allow) { s_canvas_mgr.allow_multisample(canvas, allow); diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index a1227d8ddb..a2050e5a15 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -499,6 +499,7 @@ public: static void enable_gizmos(wxGLCanvas* canvas, bool enable); static void enable_shader(wxGLCanvas* canvas, bool enable); static void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable); + static void enable_dynamic_background(wxGLCanvas* canvas, bool enable); static void allow_multisample(wxGLCanvas* canvas, bool allow); static void zoom_to_bed(wxGLCanvas* canvas); diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index bcd4e65ba6..d53436969c 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -51,6 +51,9 @@ static const float UNIT_MATRIX[] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; +static const float DEFAULT_BG_COLOR[3] = { 10.0f / 255.0f, 98.0f / 255.0f, 144.0f / 255.0f }; +static const float ERROR_BG_COLOR[3] = { 144.0f / 255.0f, 49.0f / 255.0f, 10.0f / 255.0f }; + namespace Slic3r { namespace GUI { @@ -1703,6 +1706,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_picking_enabled(false) , m_moving_enabled(false) , m_shader_enabled(false) + , m_dynamic_background_enabled(false) , m_multisample_allowed(false) , m_color_by("volume") , m_select_by("object") @@ -2067,6 +2071,11 @@ void GLCanvas3D::enable_force_zoom_to_bed(bool enable) m_force_zoom_to_bed_enabled = enable; } +void GLCanvas3D::enable_dynamic_background(bool enable) +{ + m_dynamic_background_enabled = enable; +} + void GLCanvas3D::allow_multisample(bool allow) { m_multisample_allowed = allow; @@ -3431,8 +3440,6 @@ void GLCanvas3D::_render_background() const { ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - static const float COLOR[3] = { 10.0f / 255.0f, 98.0f / 255.0f, 144.0f / 255.0f }; - ::glPushMatrix(); ::glLoadIdentity(); ::glMatrixMode(GL_PROJECTION); @@ -3444,11 +3451,16 @@ void GLCanvas3D::_render_background() const ::glBegin(GL_QUADS); ::glColor3f(0.0f, 0.0f, 0.0f); - ::glVertex3f(-1.0f, -1.0f, 1.0f); - ::glVertex3f(1.0f, -1.0f, 1.0f); - ::glColor3f(COLOR[0], COLOR[1], COLOR[2]); - ::glVertex3f(1.0f, 1.0f, 1.0f); - ::glVertex3f(-1.0f, 1.0f, 1.0f); + ::glVertex2f(-1.0f, -1.0f); + ::glVertex2f(1.0f, -1.0f); + + if (m_dynamic_background_enabled && _is_any_volume_outside()) + ::glColor3f(ERROR_BG_COLOR[0], ERROR_BG_COLOR[1], ERROR_BG_COLOR[2]); + else + ::glColor3f(DEFAULT_BG_COLOR[0], DEFAULT_BG_COLOR[1], DEFAULT_BG_COLOR[2]); + + ::glVertex2f(1.0f, 1.0f); + ::glVertex2f(-1.0f, 1.0f); ::glEnd(); ::glEnable(GL_DEPTH_TEST); @@ -4772,17 +4784,7 @@ void GLCanvas3D::_update_toolpath_volumes_outside_state() void GLCanvas3D::_show_warning_texture_if_needed() { - bool detected_outside = false; - for (const GLVolume* volume : m_volumes.volumes) - { - if ((volume != nullptr) && volume->is_outside) - { - detected_outside = true; - break; - } - } - - if (detected_outside) + if (_is_any_volume_outside()) { enable_warning_texture(true); _generate_warning_texture(L("Detected toolpath outside print volume")); @@ -4899,5 +4901,16 @@ void GLCanvas3D::_reset_warning_texture() m_warning_texture.reset(); } +bool GLCanvas3D::_is_any_volume_outside() const +{ + for (const GLVolume* volume : m_volumes.volumes) + { + if ((volume != nullptr) && volume->is_outside) + return true; + } + + return false; +} + } // namespace GUI } // namespace Slic3r diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index 0d8cf8855e..a874862613 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -449,6 +449,7 @@ private: bool m_picking_enabled; bool m_moving_enabled; bool m_shader_enabled; + bool m_dynamic_background_enabled; bool m_multisample_allowed; std::string m_color_by; @@ -539,6 +540,7 @@ public: void enable_gizmos(bool enable); void enable_shader(bool enable); void enable_force_zoom_to_bed(bool enable); + void enable_dynamic_background(bool enable); void allow_multisample(bool allow); void zoom_to_bed(); @@ -682,6 +684,8 @@ private: void _generate_warning_texture(const std::string& msg); void _reset_warning_texture(); + bool _is_any_volume_outside() const; + static std::vector _parse_colors(const std::vector& colors); }; diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp index 25733c7a26..5249f6dc4f 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -418,6 +418,13 @@ void GLCanvas3DManager::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable it->second->enable_force_zoom_to_bed(enable); } +void GLCanvas3DManager::enable_dynamic_background(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_dynamic_background(enable); +} + void GLCanvas3DManager::allow_multisample(wxGLCanvas* canvas, bool allow) { CanvasesMap::iterator it = _get_canvas(canvas); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp index f27293273e..55c42fda69 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -112,6 +112,7 @@ public: void enable_gizmos(wxGLCanvas* canvas, bool enable); void enable_shader(wxGLCanvas* canvas, bool enable); void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable); + void enable_dynamic_background(wxGLCanvas* canvas, bool enable); void allow_multisample(wxGLCanvas* canvas, bool allow); void zoom_to_bed(wxGLCanvas* canvas); diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index e094941462..a91f32d293 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -430,6 +430,13 @@ enable_force_zoom_to_bed(canvas, enable) CODE: _3DScene::enable_force_zoom_to_bed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); +void +enable_dynamic_background(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_dynamic_background((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + void allow_multisample(canvas, allow) SV *canvas; From c2291e54f4ab34c2cdce9b8fc1a1e93c4779b1f7 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 27 Jul 2018 09:53:12 +0200 Subject: [PATCH 030/119] Fixes crash when loading a config with zero number of default_filament_profile values. Fixes SPE-427 --- xs/src/slic3r/GUI/PresetBundle.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 58553e1bcf..94baa0e0ef 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -552,6 +552,8 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool std::string &inherits = Preset::inherits(config); compatible_printers_condition_values.resize(num_extruders + 2, std::string()); inherits_values.resize(num_extruders + 2, std::string()); + // The "default_filament_profile" will be later extracted into the printer profile. + config.option("default_filament_profile", true)->values.resize(num_extruders, std::string()); // 1) Create a name from the file name. // Keep the suffix (.ini, .gcode, .amf, .3mf etc) to differentiate it from the normal profiles. @@ -576,7 +578,6 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool // 3) Now load the filaments. If there are multiple filament presets, split them and load them. auto old_filament_profile_names = config.option("filament_settings_id", true); old_filament_profile_names->values.resize(num_extruders, std::string()); - config.option("default_filament_profile", true)->values.resize(num_extruders, std::string()); if (num_extruders <= 1) { // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets. From 84f97e1f64adc8b0f6f3c31fe1e22ba2e97e4572 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 Jul 2018 12:28:14 +0200 Subject: [PATCH 031/119] Improved libnest2d caching --- xs/src/libnest2d/CMakeLists.txt | 20 +- xs/src/libnest2d/cmake_modules/FindGMP.cmake | 35 -- xs/src/libnest2d/examples/main.cpp | 59 ++- xs/src/libnest2d/libnest2d.h | 2 +- xs/src/libnest2d/libnest2d/boost_alg.hpp | 19 +- .../clipper_backend/clipper_backend.cpp | 58 --- .../clipper_backend/clipper_backend.hpp | 86 ++-- xs/src/libnest2d/libnest2d/common.hpp | 41 +- .../libnest2d/libnest2d/geometry_traits.hpp | 112 ++--- .../libnest2d/geometry_traits_nfp.hpp | 475 +++++++++++++----- xs/src/libnest2d/libnest2d/libnest2d.hpp | 131 +++-- xs/src/libnest2d/libnest2d/metaloop.hpp | 227 +++++++++ xs/src/libnest2d/libnest2d/optimizer.hpp | 207 +------- .../optimizers/nlopt_boilerplate.hpp | 24 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 260 ++++++++-- .../libnest2d/selections/djd_heuristic.hpp | 24 +- .../libnest2d/selections/firstfit.hpp | 2 +- xs/src/libnest2d/tests/test.cpp | 15 +- xs/src/libnest2d/tools/libnfpglue.cpp | 79 +-- xs/src/libnest2d/tools/libnfpglue.hpp | 28 +- xs/src/libnest2d/tools/svgtools.hpp | 8 +- xs/src/libslic3r/Model.cpp | 34 +- 22 files changed, 1178 insertions(+), 768 deletions(-) delete mode 100644 xs/src/libnest2d/cmake_modules/FindGMP.cmake delete mode 100644 xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.cpp create mode 100644 xs/src/libnest2d/libnest2d/metaloop.hpp diff --git a/xs/src/libnest2d/CMakeLists.txt b/xs/src/libnest2d/CMakeLists.txt index bfdb551fc5..835e8311d3 100644 --- a/xs/src/libnest2d/CMakeLists.txt +++ b/xs/src/libnest2d/CMakeLists.txt @@ -2,8 +2,6 @@ cmake_minimum_required(VERSION 2.8) project(Libnest2D) -enable_testing() - if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) # Update if necessary set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long ") @@ -32,6 +30,7 @@ set(LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/geometry_traits.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/common.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizer.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/metaloop.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/placer_boilerplate.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/bottomleftplacer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/nfpplacer.hpp @@ -60,8 +59,7 @@ if(LIBNEST2D_GEOMETRIES_BACKEND STREQUAL "clipper") include_directories(BEFORE ${CLIPPER_INCLUDE_DIRS}) include_directories(${Boost_INCLUDE_DIRS}) - list(APPEND LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.hpp + list(APPEND LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/boost_alg.hpp) list(APPEND LIBNEST2D_LIBRARIES ${CLIPPER_LIBRARIES}) list(APPEND LIBNEST2D_HEADERS ${CLIPPER_INCLUDE_DIRS} @@ -81,22 +79,12 @@ if(LIBNEST2D_OPTIMIZER_BACKEND STREQUAL "nlopt") ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/subplex.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/genetic.hpp ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/nlopt_boilerplate.hpp) - list(APPEND LIBNEST2D_LIBRARIES ${NLopt_LIBS} -# Threads::Threads - ) + list(APPEND LIBNEST2D_LIBRARIES ${NLopt_LIBS}) list(APPEND LIBNEST2D_HEADERS ${NLopt_INCLUDE_DIR}) endif() -# Currently we are outsourcing the non-convex NFP implementation from -# libnfporb and it needs libgmp to work -#find_package(GMP) -#if(GMP_FOUND) -# list(APPEND LIBNEST2D_LIBRARIES ${GMP_LIBRARIES}) -# list(APPEND LIBNEST2D_HEADERS ${GMP_INCLUDE_DIR}) -# add_definitions(-DLIBNFP_USE_RATIONAL) -#endif() - if(LIBNEST2D_UNITTESTS) + enable_testing() add_subdirectory(tests) endif() diff --git a/xs/src/libnest2d/cmake_modules/FindGMP.cmake b/xs/src/libnest2d/cmake_modules/FindGMP.cmake deleted file mode 100644 index db173bc90d..0000000000 --- a/xs/src/libnest2d/cmake_modules/FindGMP.cmake +++ /dev/null @@ -1,35 +0,0 @@ -# Try to find the GMP libraries: -# GMP_FOUND - System has GMP lib -# GMP_INCLUDE_DIR - The GMP include directory -# GMP_LIBRARIES - Libraries needed to use GMP - -if (GMP_INCLUDE_DIR AND GMP_LIBRARIES) - # Force search at every time, in case configuration changes - unset(GMP_INCLUDE_DIR CACHE) - unset(GMP_LIBRARIES CACHE) -endif (GMP_INCLUDE_DIR AND GMP_LIBRARIES) - -find_path(GMP_INCLUDE_DIR NAMES gmp.h) - -if(WIN32) - find_library(GMP_LIBRARIES NAMES libgmp.a gmp gmp.lib mpir mpir.lib) -else(WIN32) - if(STBIN) - message(STATUS "STBIN: ${STBIN}") - find_library(GMP_LIBRARIES NAMES libgmp.a gmp) - else(STBIN) - find_library(GMP_LIBRARIES NAMES libgmp.so gmp) - endif(STBIN) -endif(WIN32) - -if(GMP_INCLUDE_DIR AND GMP_LIBRARIES) - set(GMP_FOUND TRUE) -endif(GMP_INCLUDE_DIR AND GMP_LIBRARIES) - -if(GMP_FOUND) - message(STATUS "Configured GMP: ${GMP_LIBRARIES}") -else(GMP_FOUND) - message(STATUS "Could NOT find GMP") -endif(GMP_FOUND) - -mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES) \ No newline at end of file diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index e5a47161eb..a97618578c 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -535,17 +535,18 @@ void arrangeRectangles() { proba[0].rotate(Pi/3); proba[1].rotate(Pi-Pi/3); +// std::vector input(25, Rectangle(70*SCALE, 10*SCALE)); std::vector input; input.insert(input.end(), prusaParts().begin(), prusaParts().end()); // input.insert(input.end(), prusaExParts().begin(), prusaExParts().end()); - input.insert(input.end(), stegoParts().begin(), stegoParts().end()); +// input.insert(input.end(), stegoParts().begin(), stegoParts().end()); // input.insert(input.end(), rects.begin(), rects.end()); - input.insert(input.end(), proba.begin(), proba.end()); +// input.insert(input.end(), proba.begin(), proba.end()); // input.insert(input.end(), crasher.begin(), crasher.end()); Box bin(250*SCALE, 210*SCALE); - Coord min_obj_distance = 6*SCALE; + auto min_obj_distance = static_cast(0*SCALE); using Placer = NfpPlacer; using Packer = Arranger; @@ -554,21 +555,45 @@ void arrangeRectangles() { Packer::PlacementConfig pconf; pconf.alignment = Placer::Config::Alignment::CENTER; - pconf.starting_point = Placer::Config::Alignment::CENTER; + pconf.starting_point = Placer::Config::Alignment::BOTTOM_LEFT; pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; - pconf.object_function = [&bin](Placer::Pile pile, double area, - double norm, double penality) { + + double norm_2 = std::nan(""); + pconf.object_function = [&bin, &norm_2](Placer::Pile pile, const Item& item, + double /*area*/, double norm, double penality) { + + using pl = PointLike; auto bb = ShapeLike::boundingBox(pile); + auto ibb = item.boundingBox(); + auto minc = ibb.minCorner(); + auto maxc = ibb.maxCorner(); - auto& sh = pile.back(); - auto rv = Nfp::referenceVertex(sh); - auto c = bin.center(); - auto d = PointLike::distance(rv, c); - double score = double(d)/norm; + if(std::isnan(norm_2)) norm_2 = pow(norm, 2); + + // We get the distance of the reference point from the center of the + // heat bed + auto cc = bb.center(); + auto top_left = PointImpl{getX(minc), getY(maxc)}; + auto bottom_right = PointImpl{getX(maxc), getY(minc)}; + + auto a = pl::distance(ibb.maxCorner(), cc); + auto b = pl::distance(ibb.minCorner(), cc); + auto c = pl::distance(ibb.center(), cc); + auto d = pl::distance(top_left, cc); + auto e = pl::distance(bottom_right, cc); + + auto area = bb.width() * bb.height() / norm_2; + + auto min_dist = std::min({a, b, c, d, e}) / norm; + + // The score will be the normalized distance which will be minimized, + // effectively creating a circle shaped pile of items + double score = 0.8*min_dist + 0.2*area; // If it does not fit into the print bed we will beat it - // with a large penality + // with a large penality. If we would not do this, there would be only + // one big pile that doesn't care whether it fits onto the print bed. if(!NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score; return score; @@ -577,7 +602,7 @@ void arrangeRectangles() { Packer::SelectionConfig sconf; // sconf.allow_parallel = false; // sconf.force_parallel = false; -// sconf.try_triplets = false; +// sconf.try_triplets = true; // sconf.try_reverse_order = true; // sconf.waste_increment = 0.005; @@ -630,7 +655,7 @@ void arrangeRectangles() { << " %" << std::endl; std::cout << "Bin usage: ("; - unsigned total = 0; + size_t total = 0; for(auto& r : result) { std::cout << r.size() << " "; total += r.size(); } std::cout << ") Total: " << total << std::endl; @@ -643,9 +668,11 @@ void arrangeRectangles() { << input.size() - total << " elements!" << std::endl; - svg::SVGWriter::Config conf; + using SVGWriter = svg::SVGWriter; + + SVGWriter::Config conf; conf.mm_in_coord_units = SCALE; - svg::SVGWriter svgw(conf); + SVGWriter svgw(conf); svgw.setSize(bin); svgw.writePackGroup(result); // std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);}); diff --git a/xs/src/libnest2d/libnest2d.h b/xs/src/libnest2d/libnest2d.h index 1e0a98f6a2..e0ad05c413 100644 --- a/xs/src/libnest2d/libnest2d.h +++ b/xs/src/libnest2d/libnest2d.h @@ -6,7 +6,7 @@ #include // We include the stock optimizers for local and global optimization -#include // Local subplex for NfpPlacer +#include // Local simplex for NfpPlacer #include // Genetic for min. bounding box #include diff --git a/xs/src/libnest2d/libnest2d/boost_alg.hpp b/xs/src/libnest2d/libnest2d/boost_alg.hpp index a50b397d33..422616d203 100644 --- a/xs/src/libnest2d/libnest2d/boost_alg.hpp +++ b/xs/src/libnest2d/libnest2d/boost_alg.hpp @@ -8,8 +8,16 @@ #ifdef __clang__ #undef _MSC_EXTENSIONS #endif -#include +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif // this should be removed to not confuse the compiler // #include @@ -461,15 +469,6 @@ inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes, } #endif -//#ifndef DISABLE_BOOST_MINKOWSKI_ADD -//template<> -//inline PolygonImpl& Nfp::minkowskiAdd(PolygonImpl& sh, -// const PolygonImpl& /*other*/) -//{ -// return sh; -//} -//#endif - #ifndef DISABLE_BOOST_SERIALIZE template<> inline std::string ShapeLike::serialize( const PolygonImpl& sh, double scale) diff --git a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.cpp b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.cpp deleted file mode 100644 index 830d235a34..0000000000 --- a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.cpp +++ /dev/null @@ -1,58 +0,0 @@ -//#include "clipper_backend.hpp" -//#include - -//namespace libnest2d { - -//namespace { - -//class SpinLock { -// std::atomic_flag& lck_; -//public: - -// inline SpinLock(std::atomic_flag& flg): lck_(flg) {} - -// inline void lock() { -// while(lck_.test_and_set(std::memory_order_acquire)) {} -// } - -// inline void unlock() { lck_.clear(std::memory_order_release); } -//}; - -//class HoleCache { -// friend struct libnest2d::ShapeLike; - -// std::unordered_map< const PolygonImpl*, ClipperLib::Paths> map; - -// ClipperLib::Paths& _getHoles(const PolygonImpl* p) { -// static std::atomic_flag flg = ATOMIC_FLAG_INIT; -// SpinLock lock(flg); - -// lock.lock(); -// ClipperLib::Paths& paths = map[p]; -// lock.unlock(); - -// if(paths.size() != p->Childs.size()) { -// paths.reserve(p->Childs.size()); - -// for(auto np : p->Childs) { -// paths.emplace_back(np->Contour); -// } -// } - -// return paths; -// } - -// ClipperLib::Paths& getHoles(PolygonImpl& p) { -// return _getHoles(&p); -// } - -// const ClipperLib::Paths& getHoles(const PolygonImpl& p) { -// return _getHoles(&p); -// } -//}; -//} - -//HoleCache holeCache; - -//} - diff --git a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp index 8cc27573aa..15ceb15767 100644 --- a/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp +++ b/xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp @@ -21,7 +21,7 @@ struct PolygonImpl { PathImpl Contour; HoleStore Holes; - inline PolygonImpl() {} + inline PolygonImpl() = default; inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {} inline explicit PolygonImpl(const HoleStore& holes): @@ -66,6 +66,19 @@ inline PointImpl operator-(const PointImpl& p1, const PointImpl& p2) { ret -= p2; return ret; } + +inline PointImpl& operator *=(PointImpl& p, const PointImpl& pa ) { + p.X *= pa.X; + p.Y *= pa.Y; + return p; +} + +inline PointImpl operator*(const PointImpl& p1, const PointImpl& p2) { + PointImpl ret = p1; + ret *= p2; + return ret; +} + } namespace libnest2d { @@ -135,7 +148,7 @@ inline void ShapeLike::reserve(PolygonImpl& sh, size_t vertex_capacity) namespace _smartarea { template -inline double area(const PolygonImpl& sh) { +inline double area(const PolygonImpl& /*sh*/) { return std::nan(""); } @@ -220,22 +233,6 @@ inline void ShapeLike::offset(PolygonImpl& sh, TCoord distance) { } } -//template<> // TODO make it support holes if this method will ever be needed. -//inline PolygonImpl Nfp::minkowskiDiff(const PolygonImpl& sh, -// const PolygonImpl& other) -//{ -// #define DISABLE_BOOST_MINKOWSKI_ADD - -// ClipperLib::Paths solution; - -// ClipperLib::MinkowskiDiff(sh.Contour, other.Contour, solution); - -// PolygonImpl ret; -// ret.Contour = solution.front(); - -// return sh; -//} - // Tell libnest2d how to make string out of a ClipperPolygon object template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) { std::stringstream ss; @@ -406,35 +403,12 @@ inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) } #define DISABLE_BOOST_NFP_MERGE -template<> inline Nfp::Shapes -Nfp::merge(const Nfp::Shapes& shapes, const PolygonImpl& sh) -{ +inline Nfp::Shapes _merge(ClipperLib::Clipper& clipper) { Nfp::Shapes retv; - ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); - - bool closed = true; - bool valid = false; - - valid = clipper.AddPath(sh.Contour, ClipperLib::ptSubject, closed); - - for(auto& hole : sh.Holes) { - valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed); - } - - for(auto& path : shapes) { - valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); - - for(auto& hole : path.Holes) { - valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed); - } - } - - if(!valid) throw GeometryException(GeomErr::MERGE); - ClipperLib::PolyTree result; - clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero); - retv.reserve(result.Total()); + clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative); + retv.reserve(static_cast(result.Total())); std::function processHole; @@ -445,7 +419,8 @@ Nfp::merge(const Nfp::Shapes& shapes, const PolygonImpl& sh) retv.push_back(poly); }; - processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly) { + processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly) + { poly.Holes.push_back(pptr->Contour); poly.Holes.back().push_back(poly.Holes.back().front()); for(auto c : pptr->Childs) processPoly(c); @@ -463,6 +438,27 @@ Nfp::merge(const Nfp::Shapes& shapes, const PolygonImpl& sh) return retv; } +template<> inline Nfp::Shapes +Nfp::merge(const Nfp::Shapes& shapes) +{ + ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); + + bool closed = true; + bool valid = true; + + for(auto& path : shapes) { + valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); + + for(auto& hole : path.Holes) { + valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed); + } + } + + if(!valid) throw GeometryException(GeomErr::MERGE); + + return _merge(clipper); +} + } //#define DISABLE_BOOST_SERIALIZE diff --git a/xs/src/libnest2d/libnest2d/common.hpp b/xs/src/libnest2d/libnest2d/common.hpp index 18f313712c..6867f76f3e 100644 --- a/xs/src/libnest2d/libnest2d/common.hpp +++ b/xs/src/libnest2d/libnest2d/common.hpp @@ -13,6 +13,7 @@ #if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L #define BP2D_NOEXCEPT #define BP2D_CONSTEXPR + #define BP2D_COMPILER_MSVC12 #elif __cplusplus >= 201103L #define BP2D_NOEXCEPT noexcept #define BP2D_CONSTEXPR constexpr @@ -84,44 +85,6 @@ struct invoke_result { template using invoke_result_t = typename invoke_result::type; -/* ************************************************************************** */ -/* C++14 std::index_sequence implementation: */ -/* ************************************************************************** */ - -/** - * \brief C++11 conformant implementation of the index_sequence type from C++14 - */ -template struct index_sequence { - using value_type = size_t; - BP2D_CONSTEXPR value_type size() const { return sizeof...(Ints); } -}; - -// A Help structure to generate the integer list -template struct genSeq; - -// Recursive template to generate the list -template struct genSeq { - // Type will contain a genSeq with Nseq appended by one element - using Type = typename genSeq< I - 1, I - 1, Nseq...>::Type; -}; - -// Terminating recursion -template struct genSeq<0, Nseq...> { - // If I is zero, Type will contain index_sequence with the fuly generated - // integer list. - using Type = index_sequence; -}; - -/// Helper alias to make an index sequence from 0 to N -template using make_index_sequence = typename genSeq::Type; - -/// Helper alias to make an index sequence for a parameter pack -template -using index_sequence_for = make_index_sequence; - - -/* ************************************************************************** */ - /** * A useful little tool for triggering static_assert error messages e.g. when * a mandatory template specialization (implementation) is missing. @@ -229,7 +192,7 @@ public: GeomErr errcode() const { return errcode_; } - virtual const char * what() const BP2D_NOEXCEPT override { + const char * what() const BP2D_NOEXCEPT override { return errorstr(errcode_).c_str(); } }; diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index dbd609201b..568c0a7662 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -68,7 +68,7 @@ class _Box: PointPair { using PointPair::p2; public: - inline _Box() {} + inline _Box() = default; inline _Box(const RawPoint& p, const RawPoint& pp): PointPair({p, pp}) {} @@ -97,7 +97,7 @@ class _Segment: PointPair { mutable Radians angletox_ = std::nan(""); public: - inline _Segment() {} + inline _Segment() = default; inline _Segment(const RawPoint& p, const RawPoint& pp): PointPair({p, pp}) {} @@ -188,7 +188,7 @@ struct PointLike { if( (y < y1 && y < y2) || (y > y1 && y > y2) ) return {0, false}; - else if ((y == y1 && y == y2) && (x > x1 && x > x2)) + if ((y == y1 && y == y2) && (x > x1 && x > x2)) ret = std::min( x-x1, x -x2); else if( (y == y1 && y == y2) && (x < x1 && x < x2)) ret = -std::min(x1 - x, x2 - x); @@ -214,7 +214,7 @@ struct PointLike { if( (x < x1 && x < x2) || (x > x1 && x > x2) ) return {0, false}; - else if ((x == x1 && x == x2) && (y > y1 && y > y2)) + if ((x == x1 && x == x2) && (y > y1 && y > y2)) ret = std::min( y-y1, y -y2); else if( (x == x1 && x == x2) && (y < y1 && y < y2)) ret = -std::min(y1 - y, y2 - y); @@ -329,7 +329,7 @@ enum class Formats { }; // This struct serves as a namespace. The only difference is that it can be -// used in friend declarations. +// used in friend declarations and can be aliased at class scope. struct ShapeLike { template @@ -361,6 +361,51 @@ struct ShapeLike { return create(contour, {}); } + template + static THolesContainer& holes(RawShape& /*sh*/) + { + static THolesContainer empty; + return empty; + } + + template + static const THolesContainer& holes(const RawShape& /*sh*/) + { + static THolesContainer empty; + return empty; + } + + template + static TContour& getHole(RawShape& sh, unsigned long idx) + { + return holes(sh)[idx]; + } + + template + static const TContour& getHole(const RawShape& sh, + unsigned long idx) + { + return holes(sh)[idx]; + } + + template + static size_t holeCount(const RawShape& sh) + { + return holes(sh).size(); + } + + template + static TContour& getContour(RawShape& sh) + { + return sh; + } + + template + static const TContour& getContour(const RawShape& sh) + { + return sh; + } + // Optional, does nothing by default template static void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {} @@ -402,7 +447,7 @@ struct ShapeLike { } template - static std::string serialize(const RawShape& /*sh*/, double scale=1) + static std::string serialize(const RawShape& /*sh*/, double /*scale*/=1) { static_assert(always_false::value, "ShapeLike::serialize() unimplemented!"); @@ -498,51 +543,6 @@ struct ShapeLike { return RawShape(); } - template - static THolesContainer& holes(RawShape& /*sh*/) - { - static THolesContainer empty; - return empty; - } - - template - static const THolesContainer& holes(const RawShape& /*sh*/) - { - static THolesContainer empty; - return empty; - } - - template - static TContour& getHole(RawShape& sh, unsigned long idx) - { - return holes(sh)[idx]; - } - - template - static const TContour& getHole(const RawShape& sh, - unsigned long idx) - { - return holes(sh)[idx]; - } - - template - static size_t holeCount(const RawShape& sh) - { - return holes(sh).size(); - } - - template - static TContour& getContour(RawShape& sh) - { - return sh; - } - - template - static const TContour& getContour(const RawShape& sh) - { - return sh; - } - template static void rotate(RawShape& /*sh*/, const Radians& /*rads*/) { @@ -621,14 +621,12 @@ struct ShapeLike { } template - static double area(const Shapes& shapes) + static inline double area(const Shapes& shapes) { - double ret = 0; - std::accumulate(shapes.first(), shapes.end(), - [](const RawShape& a, const RawShape& b) { - return area(a) + area(b); + return std::accumulate(shapes.begin(), shapes.end(), 0.0, + [](double a, const RawShape& b) { + return a += area(b); }); - return ret; } template // Potential O(1) implementation may exist diff --git a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp index 581b6bed08..90cf21be5b 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits_nfp.hpp @@ -3,7 +3,9 @@ #include "geometry_traits.hpp" #include +#include #include +#include namespace libnest2d { @@ -23,64 +25,22 @@ struct Nfp { template using Shapes = typename ShapeLike::Shapes; -/// Minkowski addition (not used yet) +/** + * Merge a bunch of polygons with the specified additional polygon. + * + * \tparam RawShape the Polygon data type. + * \param shc The pile of polygons that will be unified with sh. + * \param sh A single polygon to unify with shc. + * + * \return A set of polygons that is the union of the input polygons. Note that + * mostly it will be a set containing only one big polygon but if the input + * polygons are disjuct than the resulting set will contain more polygons. + */ template -static RawShape minkowskiDiff(const RawShape& sh, const RawShape& cother) +static Shapes merge(const Shapes& /*shc*/) { - using Vertex = TPoint; - //using Coord = TCoord; - using Edge = _Segment; - using sl = ShapeLike; - using std::signbit; - - // Copy the orbiter (controur only), we will have to work on it - RawShape orbiter = sl::create(sl::getContour(cother)); - - // Make the orbiter reverse oriented - for(auto &v : sl::getContour(orbiter)) v = -v; - - // An egde with additional data for marking it - struct MarkedEdge { Edge e; Radians turn_angle; bool is_turning_point; }; - - // Container for marked edges - using EdgeList = std::vector; - - EdgeList A, B; - - auto fillEdgeList = [](EdgeList& L, const RawShape& poly) { - L.reserve(sl::contourVertexCount(poly)); - - auto it = sl::cbegin(poly); - auto nextit = std::next(it); - - L.emplace_back({Edge(*it, *nextit), 0, false}); - it++; nextit++; - - while(nextit != sl::cend(poly)) { - Edge e(*it, *nextit); - auto& L_prev = L.back(); - auto phi = L_prev.e.angleToXaxis(); - auto phi_prev = e.angleToXaxis(); - auto turn_angle = phi-phi_prev; - if(turn_angle > Pi) turn_angle -= 2*Pi; - L.emplace_back({ - e, - turn_angle, - signbit(turn_angle) != signbit(L_prev.turn_angle) - }); - it++; nextit++; - } - - L.front().turn_angle = L.front().e.angleToXaxis() - - L.back().e.angleToXaxis(); - - if(L.front().turn_angle > Pi) L.front().turn_angle -= 2*Pi; - }; - - fillEdgeList(A, sh); - fillEdgeList(B, orbiter); - - return sh; + static_assert(always_false::value, + "Nfp::merge(shapes, shape) unimplemented!"); } /** @@ -95,10 +55,12 @@ static RawShape minkowskiDiff(const RawShape& sh, const RawShape& cother) * polygons are disjuct than the resulting set will contain more polygons. */ template -static Shapes merge(const Shapes& shc, const RawShape& sh) +static Shapes merge(const Shapes& shc, + const RawShape& sh) { - static_assert(always_false::value, - "Nfp::merge(shapes, shape) unimplemented!"); + auto m = merge(shc); + m.push_back(sh); + return merge(m); } /** @@ -139,16 +101,20 @@ template static TPoint rightmostUpVertex(const RawShape& sh) { - // find min x and min y vertex + // find max x and max y vertex auto it = std::max_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh), _vsort); return *it; } +template +using NfpResult = std::pair>; + /// Helper function to get the NFP template -static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) +static NfpResult noFitPolygon(const RawShape& sh, + const RawShape& other) { NfpImpl nfp; return nfp(sh, other); @@ -167,44 +133,46 @@ static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) * \tparam RawShape the Polygon data type. * \param sh The stationary polygon * \param cother The orbiting polygon - * \return Returns the NFP of the two input polygons which have to be strictly - * convex. The resulting NFP is proven to be convex as well in this case. + * \return Returns a pair of the NFP and its reference vertex of the two input + * polygons which have to be strictly convex. The resulting NFP is proven to be + * convex as well in this case. * */ template -static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother) +static NfpResult nfpConvexOnly(const RawShape& sh, + const RawShape& other) { using Vertex = TPoint; using Edge = _Segment; - - RawShape other = cother; - - // Make the other polygon counter-clockwise - std::reverse(ShapeLike::begin(other), ShapeLike::end(other)); + using sl = ShapeLike; RawShape rsh; // Final nfp placeholder + Vertex top_nfp; std::vector edgelist; - auto cap = ShapeLike::contourVertexCount(sh) + - ShapeLike::contourVertexCount(other); + auto cap = sl::contourVertexCount(sh) + sl::contourVertexCount(other); // Reserve the needed memory edgelist.reserve(cap); - ShapeLike::reserve(rsh, static_cast(cap)); + sl::reserve(rsh, static_cast(cap)); { // place all edges from sh into edgelist - auto first = ShapeLike::cbegin(sh); - auto next = first + 1; - auto endit = ShapeLike::cend(sh); + auto first = sl::cbegin(sh); + auto next = std::next(first); - while(next != endit) edgelist.emplace_back(*(first++), *(next++)); + while(next != sl::cend(sh)) { + edgelist.emplace_back(*(first), *(next)); + ++first; ++next; + } } { // place all edges from other into edgelist - auto first = ShapeLike::cbegin(other); - auto next = first + 1; - auto endit = ShapeLike::cend(other); + auto first = sl::cbegin(other); + auto next = std::next(first); - while(next != endit) edgelist.emplace_back(*(first++), *(next++)); + while(next != sl::cend(other)) { + edgelist.emplace_back(*(next), *(first)); + ++first; ++next; + } } // Sort the edges by angle to X axis. @@ -215,10 +183,16 @@ static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother) }); // Add the two vertices from the first edge into the final polygon. - ShapeLike::addVertex(rsh, edgelist.front().first()); - ShapeLike::addVertex(rsh, edgelist.front().second()); + sl::addVertex(rsh, edgelist.front().first()); + sl::addVertex(rsh, edgelist.front().second()); - auto tmp = std::next(ShapeLike::begin(rsh)); + // Sorting function for the nfp reference vertex search + auto& cmp = _vsort; + + // the reference (rightmost top) vertex so far + top_nfp = *std::max_element(sl::cbegin(rsh), sl::cend(rsh), cmp ); + + auto tmp = std::next(sl::begin(rsh)); // Construct final nfp by placing each edge to the end of the previous for(auto eit = std::next(edgelist.begin()); @@ -226,56 +200,325 @@ static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother) ++eit) { auto d = *tmp - eit->first(); - auto p = eit->second() + d; + Vertex p = eit->second() + d; - ShapeLike::addVertex(rsh, p); + sl::addVertex(rsh, p); + + // Set the new reference vertex + if(cmp(top_nfp, p)) top_nfp = p; tmp = std::next(tmp); } - // Now we have an nfp somewhere in the dark. We need to get it - // to the right position around the stationary shape. - // This is done by choosing the leftmost lowest vertex of the - // orbiting polygon to be touched with the rightmost upper - // vertex of the stationary polygon. In this configuration, the - // reference vertex of the orbiting polygon (which can be dragged around - // the nfp) will be its rightmost upper vertex that coincides with the - // rightmost upper vertex of the nfp. No proof provided other than Jonas - // Lindmark's reasoning about the reference vertex of nfp in his thesis - // ("No fit polygon problem" - section 2.1.9) + return {rsh, top_nfp}; +} - // TODO: dont do this here. Cache the rmu and lmd in Item and get translate - // the nfp after this call +template +static NfpResult nfpSimpleSimple(const RawShape& cstationary, + const RawShape& cother) +{ - auto csh = sh; // Copy sh, we will sort the verices in the copy - auto& cmp = _vsort; - std::sort(ShapeLike::begin(csh), ShapeLike::end(csh), cmp); - std::sort(ShapeLike::begin(other), ShapeLike::end(other), cmp); + // Algorithms are from the original algorithm proposed in paper: + // https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf - // leftmost lower vertex of the stationary polygon - auto& touch_sh = *(std::prev(ShapeLike::end(csh))); - // rightmost upper vertex of the orbiting polygon - auto& touch_other = *(ShapeLike::begin(other)); + // ///////////////////////////////////////////////////////////////////////// + // Algorithm 1: Obtaining the minkowski sum + // ///////////////////////////////////////////////////////////////////////// - // Calculate the difference and move the orbiter to the touch position. - auto dtouch = touch_sh - touch_other; - auto top_other = *(std::prev(ShapeLike::end(other))) + dtouch; + // I guess this is not a full minkowski sum of the two input polygons by + // definition. This yields a subset that is compatible with the next 2 + // algorithms. - // Get the righmost upper vertex of the nfp and move it to the RMU of - // the orbiter because they should coincide. - auto&& top_nfp = rightmostUpVertex(rsh); - auto dnfp = top_other - top_nfp; - std::for_each(ShapeLike::begin(rsh), ShapeLike::end(rsh), - [&dnfp](Vertex& v) { v+= dnfp; } ); + using Result = NfpResult; + using Vertex = TPoint; + using Coord = TCoord; + using Edge = _Segment; + using sl = ShapeLike; + using std::signbit; + using std::sort; + using std::vector; + using std::ref; + using std::reference_wrapper; - return rsh; + // TODO The original algorithms expects the stationary polygon in + // counter clockwise and the orbiter in clockwise order. + // So for preventing any further complication, I will make the input + // the way it should be, than make my way around the orientations. + + // Reverse the stationary contour to counter clockwise + auto stcont = sl::getContour(cstationary); + std::reverse(stcont.begin(), stcont.end()); + RawShape stationary; + sl::getContour(stationary) = stcont; + + // Reverse the orbiter contour to counter clockwise + auto orbcont = sl::getContour(cother); + + std::reverse(orbcont.begin(), orbcont.end()); + + // Copy the orbiter (contour only), we will have to work on it + RawShape orbiter; + sl::getContour(orbiter) = orbcont; + + // Step 1: Make the orbiter reverse oriented + for(auto &v : sl::getContour(orbiter)) v = -v; + + // An egde with additional data for marking it + struct MarkedEdge { + Edge e; Radians turn_angle = 0; bool is_turning_point = false; + MarkedEdge() = default; + MarkedEdge(const Edge& ed, Radians ta, bool tp): + e(ed), turn_angle(ta), is_turning_point(tp) {} + }; + + // Container for marked edges + using EdgeList = vector; + + EdgeList A, B; + + // This is how an edge list is created from the polygons + auto fillEdgeList = [](EdgeList& L, const RawShape& poly, int dir) { + L.reserve(sl::contourVertexCount(poly)); + + auto it = sl::cbegin(poly); + auto nextit = std::next(it); + + double turn_angle = 0; + bool is_turn_point = false; + + while(nextit != sl::cend(poly)) { + L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); + it++; nextit++; + } + + auto getTurnAngle = [](const Edge& e1, const Edge& e2) { + auto phi = e1.angleToXaxis(); + auto phi_prev = e2.angleToXaxis(); + auto TwoPi = 2.0*Pi; + if(phi > Pi) phi -= TwoPi; + if(phi_prev > Pi) phi_prev -= TwoPi; + auto turn_angle = phi-phi_prev; + if(turn_angle > Pi) turn_angle -= TwoPi; + return phi-phi_prev; + }; + + if(dir > 0) { + auto eit = L.begin(); + auto enext = std::next(eit); + + eit->turn_angle = getTurnAngle(L.front().e, L.back().e); + + while(enext != L.end()) { + enext->turn_angle = getTurnAngle( enext->e, eit->e); + enext->is_turning_point = + signbit(enext->turn_angle) != signbit(eit->turn_angle); + ++eit; ++enext; + } + + L.front().is_turning_point = signbit(L.front().turn_angle) != + signbit(L.back().turn_angle); + } else { + std::cout << L.size() << std::endl; + + auto eit = L.rbegin(); + auto enext = std::next(eit); + + eit->turn_angle = getTurnAngle(L.back().e, L.front().e); + + while(enext != L.rend()) { + enext->turn_angle = getTurnAngle(enext->e, eit->e); + enext->is_turning_point = + signbit(enext->turn_angle) != signbit(eit->turn_angle); + std::cout << enext->is_turning_point << " " << enext->turn_angle << std::endl; + + ++eit; ++enext; + } + + L.back().is_turning_point = signbit(L.back().turn_angle) != + signbit(L.front().turn_angle); + } + }; + + // Step 2: Fill the edgelists + fillEdgeList(A, stationary, 1); + fillEdgeList(B, orbiter, -1); + + // A reference to a marked edge that also knows its container + struct MarkedEdgeRef { + reference_wrapper eref; + reference_wrapper> container; + Coord dir = 1; // Direction modifier + + inline Radians angleX() const { return eref.get().e.angleToXaxis(); } + inline const Edge& edge() const { return eref.get().e; } + inline Edge& edge() { return eref.get().e; } + inline bool isTurningPoint() const { + return eref.get().is_turning_point; + } + inline bool isFrom(const vector& cont ) { + return &(container.get()) == &cont; + } + inline bool eq(const MarkedEdgeRef& mr) { + return &(eref.get()) == &(mr.eref.get()); + } + + MarkedEdgeRef(reference_wrapper er, + reference_wrapper> ec): + eref(er), container(ec), dir(1) {} + + MarkedEdgeRef(reference_wrapper er, + reference_wrapper> ec, + Coord d): + eref(er), container(ec), dir(d) {} + }; + + using EdgeRefList = vector; + + // Comparing two marked edges + auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) { + return e1.angleX() < e2.angleX(); + }; + + EdgeRefList Aref, Bref; // We create containers for the references + Aref.reserve(A.size()); Bref.reserve(B.size()); + + // Fill reference container for the stationary polygon + std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) { + Aref.emplace_back( ref(me), ref(Aref) ); + }); + + // Fill reference container for the orbiting polygon + std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) { + Bref.emplace_back( ref(me), ref(Bref) ); + }); + + struct EdgeGroup { typename EdgeRefList::const_iterator first, last; }; + + auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure + (const EdgeGroup& Q, const EdgeGroup& R, bool positive) + { + + // Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)" + // Sort the containers of edge references and merge them. + // Q could be sorted only once and be reused here but we would still + // need to merge it with sorted(R). + + EdgeRefList merged; + EdgeRefList S, seq; + merged.reserve((Q.last - Q.first) + (R.last - R.first)); + + merged.insert(merged.end(), Q.first, Q.last); + merged.insert(merged.end(), R.first, R.last); + sort(merged.begin(), merged.end(), sortfn); + + // Step 2 "set i = 1, k = 1, direction = 1, s1 = q1" + // we dont use i, instead, q is an iterator into Q. k would be an index + // into the merged sequence but we use "it" as an iterator for that + + // here we obtain references for the containers for later comparisons + const auto& Rcont = R.first->container.get(); + const auto& Qcont = Q.first->container.get(); + + // Set the intial direction + Coord dir = positive? 1 : -1; + + // roughly i = 1 (so q = Q.first) and s1 = q1 so S[0] = q; + auto q = Q.first; + S.push_back(*q++); + + // Roughly step 3 + while(q != Q.last) { + auto it = merged.begin(); + while(it != merged.end() && !(it->eq(*(Q.first))) ) { + if(it->isFrom(Rcont)) { + auto s = *it; + s.dir = dir; + S.push_back(s); + } + if(it->eq(*q)) { + S.push_back(*q); + if(it->isTurningPoint()) dir = -dir; + if(q != Q.first) it += dir; + } + else it += dir; + } + ++q; // "Set i = i + 1" + } + + // Step 4: + + // "Let starting edge r1 be in position si in sequence" + // whaaat? I guess this means the following: + S[0] = *R.first; + auto it = S.begin(); + + // "Set j = 1, next = 2, direction = 1, seq1 = si" + // we dont use j, seq is expanded dynamically. + dir = 1; auto next = std::next(R.first); + + // Step 5: + // "If all si edges have been allocated to seqj" should mean that + // we loop until seq has equal size with S + while(seq.size() < S.size()) { + ++it; if(it == S.end()) it = S.begin(); + + if(it->isFrom(Qcont)) { + seq.push_back(*it); // "If si is from Q, j = j + 1, seqj = si" + + // "If si is a turning point in Q, + // direction = - direction, next = next + direction" + if(it->isTurningPoint()) { dir = -dir; next += dir; } + } + + if(it->eq(*next) && dir == next->dir) { // "If si = direction.rnext" + // "j = j + 1, seqj = si, next = next + direction" + seq.push_back(*it); next += dir; + } + } + + return seq; + }; + + EdgeGroup R{ Bref.begin(), Bref.begin() }, Q{ Aref.begin(), Aref.end() }; + auto it = Bref.begin(); + bool orientation = true; + EdgeRefList seqlist; + seqlist.reserve(3*(Aref.size() + Bref.size())); + + while(it != Bref.end()) // This is step 3 and step 4 in one loop + if(it->isTurningPoint()) { + R = {R.last, it++}; + auto seq = mink(Q, R, orientation); + + // TODO step 6 (should be 5 shouldn't it?): linking edges from A + // I don't get this step + + seqlist.insert(seqlist.end(), seq.begin(), seq.end()); + orientation = !orientation; + } else ++it; + + if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true); + + // ///////////////////////////////////////////////////////////////////////// + // Algorithm 2: breaking Minkowski sums into track line trips + // ///////////////////////////////////////////////////////////////////////// + + + // ///////////////////////////////////////////////////////////////////////// + // Algorithm 3: finding the boundary of the NFP from track line trips + // ///////////////////////////////////////////////////////////////////////// + + + + return Result(stationary, Vertex()); } // Specializable NFP implementation class. Specialize it if you have a faster // or better NFP implementation template struct NfpImpl { - RawShape operator()(const RawShape& sh, const RawShape& other) { + NfpResult operator()(const RawShape& sh, const RawShape& other) + { static_assert(nfptype == NfpLevel::CONVEX_ONLY, "Nfp::noFitPolygon() unimplemented!"); diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index 96316c3449..37b5fea955 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -9,6 +9,7 @@ #include #include "geometry_traits.hpp" +#include "optimizer.hpp" namespace libnest2d { @@ -27,6 +28,7 @@ class _Item { using Coord = TCoord>; using Vertex = TPoint; using Box = _Box; + using sl = ShapeLike; // The original shape that gets encapsulated. RawShape sh_; @@ -56,6 +58,13 @@ class _Item { }; mutable Convexity convexity_ = Convexity::UNCHECKED; + mutable TVertexConstIterator rmt_; // rightmost top vertex + mutable TVertexConstIterator lmb_; // leftmost bottom vertex + mutable bool rmt_valid_ = false, lmb_valid_ = false; + mutable struct BBCache { + Box bb; bool valid; Vertex tr; + BBCache(): valid(false), tr(0, 0) {} + } bb_cache_; public: @@ -104,15 +113,15 @@ public: * @param il The initializer list of vertices. */ inline _Item(const std::initializer_list< Vertex >& il): - sh_(ShapeLike::create(il)) {} + sh_(sl::create(il)) {} inline _Item(const TContour& contour, const THolesContainer& holes = {}): - sh_(ShapeLike::create(contour, holes)) {} + sh_(sl::create(contour, holes)) {} inline _Item(TContour&& contour, THolesContainer&& holes): - sh_(ShapeLike::create(std::move(contour), + sh_(sl::create(std::move(contour), std::move(holes))) {} /** @@ -122,31 +131,31 @@ public: */ inline std::string toString() const { - return ShapeLike::toString(sh_); + return sl::toString(sh_); } /// Iterator tho the first contour vertex in the polygon. inline Iterator begin() const { - return ShapeLike::cbegin(sh_); + return sl::cbegin(sh_); } /// Alias to begin() inline Iterator cbegin() const { - return ShapeLike::cbegin(sh_); + return sl::cbegin(sh_); } /// Iterator to the last contour vertex. inline Iterator end() const { - return ShapeLike::cend(sh_); + return sl::cend(sh_); } /// Alias to end() inline Iterator cend() const { - return ShapeLike::cend(sh_); + return sl::cend(sh_); } /** @@ -161,7 +170,7 @@ public: */ inline Vertex vertex(unsigned long idx) const { - return ShapeLike::vertex(sh_, idx); + return sl::vertex(sh_, idx); } /** @@ -176,7 +185,7 @@ public: inline void setVertex(unsigned long idx, const Vertex& v ) { invalidateCache(); - ShapeLike::vertex(sh_, idx) = v; + sl::vertex(sh_, idx) = v; } /** @@ -191,7 +200,7 @@ public: double ret ; if(area_cache_valid_) ret = area_cache_; else { - ret = ShapeLike::area(offsettedShape()); + ret = sl::area(offsettedShape()); area_cache_ = ret; area_cache_valid_ = true; } @@ -203,7 +212,7 @@ public: switch(convexity_) { case Convexity::UNCHECKED: - ret = ShapeLike::isConvex(ShapeLike::getContour(transformedShape())); + ret = sl::isConvex(sl::getContour(transformedShape())); convexity_ = ret? Convexity::TRUE : Convexity::FALSE; break; case Convexity::TRUE: ret = true; break; @@ -213,7 +222,7 @@ public: return ret; } - inline bool isHoleConvex(unsigned holeidx) const { + inline bool isHoleConvex(unsigned /*holeidx*/) const { return false; } @@ -223,11 +232,11 @@ public: /// The number of the outer ring vertices. inline size_t vertexCount() const { - return ShapeLike::contourVertexCount(sh_); + return sl::contourVertexCount(sh_); } inline size_t holeCount() const { - return ShapeLike::holeCount(sh_); + return sl::holeCount(sh_); } /** @@ -235,36 +244,33 @@ public: * @param p * @return */ - inline bool isPointInside(const Vertex& p) + inline bool isPointInside(const Vertex& p) const { - return ShapeLike::isInside(p, sh_); + return sl::isInside(p, transformedShape()); } inline bool isInside(const _Item& sh) const { - return ShapeLike::isInside(transformedShape(), sh.transformedShape()); + return sl::isInside(transformedShape(), sh.transformedShape()); } - inline bool isInside(const _Box>& box); + inline bool isInside(const _Box>& box) const; inline void translate(const Vertex& d) BP2D_NOEXCEPT { - translation_ += d; has_translation_ = true; - tr_cache_valid_ = false; + translation(translation() + d); } inline void rotate(const Radians& rads) BP2D_NOEXCEPT { - rotation_ += rads; - has_rotation_ = true; - tr_cache_valid_ = false; + rotation(rotation() + rads); } inline void addOffset(Coord distance) BP2D_NOEXCEPT { offset_distance_ = distance; has_offset_ = true; - offset_cache_valid_ = false; + invalidateCache(); } inline void removeOffset() BP2D_NOEXCEPT { @@ -286,6 +292,8 @@ public: { if(rotation_ != rot) { rotation_ = rot; has_rotation_ = true; tr_cache_valid_ = false; + rmt_valid_ = false; lmb_valid_ = false; + bb_cache_.valid = false; } } @@ -293,6 +301,7 @@ public: { if(translation_ != tr) { translation_ = tr; has_translation_ = true; tr_cache_valid_ = false; + bb_cache_.valid = false; } } @@ -301,9 +310,10 @@ public: if(tr_cache_valid_) return tr_cache_; RawShape cpy = offsettedShape(); - if(has_rotation_) ShapeLike::rotate(cpy, rotation_); - if(has_translation_) ShapeLike::translate(cpy, translation_); + if(has_rotation_) sl::rotate(cpy, rotation_); + if(has_translation_) sl::translate(cpy, translation_); tr_cache_ = cpy; tr_cache_valid_ = true; + rmt_valid_ = false; lmb_valid_ = false; return tr_cache_; } @@ -321,23 +331,53 @@ public: inline void resetTransformation() BP2D_NOEXCEPT { has_translation_ = false; has_rotation_ = false; has_offset_ = false; + invalidateCache(); } inline Box boundingBox() const { - return ShapeLike::boundingBox(transformedShape()); + if(!bb_cache_.valid) { + bb_cache_.bb = sl::boundingBox(transformedShape()); + bb_cache_.tr = {0, 0}; + bb_cache_.valid = true; + } + + auto &bb = bb_cache_.bb; auto &tr = bb_cache_.tr; + return {bb.minCorner() + tr, bb.maxCorner() + tr}; + } + + inline Vertex referenceVertex() const { + return rightmostTopVertex(); + } + + inline Vertex rightmostTopVertex() const { + if(!rmt_valid_ || !tr_cache_valid_) { // find max x and max y vertex + auto& tsh = transformedShape(); + rmt_ = std::max_element(sl::cbegin(tsh), sl::cend(tsh), vsort); + rmt_valid_ = true; + } + return *rmt_; + } + + inline Vertex leftmostBottomVertex() const { + if(!lmb_valid_ || !tr_cache_valid_) { // find min x and min y vertex + auto& tsh = transformedShape(); + lmb_ = std::min_element(sl::cbegin(tsh), sl::cend(tsh), vsort); + lmb_valid_ = true; + } + return *lmb_; } //Static methods: inline static bool intersects(const _Item& sh1, const _Item& sh2) { - return ShapeLike::intersects(sh1.transformedShape(), + return sl::intersects(sh1.transformedShape(), sh2.transformedShape()); } inline static bool touches(const _Item& sh1, const _Item& sh2) { - return ShapeLike::touches(sh1.transformedShape(), + return sl::touches(sh1.transformedShape(), sh2.transformedShape()); } @@ -346,12 +386,11 @@ private: inline const RawShape& offsettedShape() const { if(has_offset_ ) { if(offset_cache_valid_) return offset_cache_; - else { - offset_cache_ = sh_; - ShapeLike::offset(offset_cache_, offset_distance_); - offset_cache_valid_ = true; - return offset_cache_; - } + + offset_cache_ = sh_; + sl::offset(offset_cache_, offset_distance_); + offset_cache_valid_ = true; + return offset_cache_; } return sh_; } @@ -359,10 +398,23 @@ private: inline void invalidateCache() const BP2D_NOEXCEPT { tr_cache_valid_ = false; + lmb_valid_ = false; rmt_valid_ = false; area_cache_valid_ = false; offset_cache_valid_ = false; + bb_cache_.valid = false; convexity_ = Convexity::UNCHECKED; } + + static inline bool vsort(const Vertex& v1, const Vertex& v2) + { + Coord &&x1 = getX(v1), &&x2 = getX(v2); + Coord &&y1 = getY(v1), &&y2 = getY(v2); + auto diff = y1 - y2; + if(std::abs(diff) <= std::numeric_limits::epsilon()) + return x1 < x2; + + return diff < 0; + } }; /** @@ -370,7 +422,6 @@ private: */ template class _Rectangle: public _Item { - RawShape sh_; using _Item::vertex; using TO = Orientation; public: @@ -415,7 +466,7 @@ public: }; template -inline bool _Item::isInside(const _Box>& box) { +inline bool _Item::isInside(const _Box>& box) const { _Rectangle rect(box.width(), box.height()); return _Item::isInside(rect); } @@ -874,9 +925,8 @@ private: Radians findBestRotation(Item& item) { opt::StopCriteria stopcr; - stopcr.stoplimit = 0.01; + stopcr.absolute_score_difference = 0.01; stopcr.max_iterations = 10000; - stopcr.type = opt::StopLimitType::RELATIVE; opt::TOptimizer solver(stopcr); auto orig_rot = item.rotation(); @@ -910,7 +960,6 @@ private: if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) { item.removeOffset(); }); - } }; diff --git a/xs/src/libnest2d/libnest2d/metaloop.hpp b/xs/src/libnest2d/libnest2d/metaloop.hpp new file mode 100644 index 0000000000..18755525c3 --- /dev/null +++ b/xs/src/libnest2d/libnest2d/metaloop.hpp @@ -0,0 +1,227 @@ +#ifndef METALOOP_HPP +#define METALOOP_HPP + +#include "common.hpp" +#include +#include + +namespace libnest2d { + +/* ************************************************************************** */ +/* C++14 std::index_sequence implementation: */ +/* ************************************************************************** */ + +/** + * \brief C++11 conformant implementation of the index_sequence type from C++14 + */ +template struct index_sequence { + using value_type = size_t; + BP2D_CONSTEXPR value_type size() const { return sizeof...(Ints); } +}; + +// A Help structure to generate the integer list +template struct genSeq; + +// Recursive template to generate the list +template struct genSeq { + // Type will contain a genSeq with Nseq appended by one element + using Type = typename genSeq< I - 1, I - 1, Nseq...>::Type; +}; + +// Terminating recursion +template struct genSeq<0, Nseq...> { + // If I is zero, Type will contain index_sequence with the fuly generated + // integer list. + using Type = index_sequence; +}; + +/// Helper alias to make an index sequence from 0 to N +template using make_index_sequence = typename genSeq::Type; + +/// Helper alias to make an index sequence for a parameter pack +template +using index_sequence_for = make_index_sequence; + + +/* ************************************************************************** */ + +namespace opt { + +using std::forward; +using std::tuple; +using std::get; +using std::tuple_element; + +/** + * @brief Helper class to be able to loop over a parameter pack's elements. + */ +class metaloop { + +// The implementation is based on partial struct template specializations. +// Basically we need a template type that is callable and takes an integer +// non-type template parameter which can be used to implement recursive calls. +// +// C++11 will not allow the usage of a plain template function that is why we +// use struct with overloaded call operator. At the same time C++11 prohibits +// partial template specialization with a non type parameter such as int. We +// need to wrap that in a type (see metaloop::Int). + +/* + * A helper alias to create integer values wrapped as a type. It is nessecary + * because a non type template parameter (such as int) would be prohibited in + * a partial specialization. Also for the same reason we have to use a class + * _Metaloop instead of a simple function as a functor. A function cannot be + * partially specialized in a way that is neccesary for this trick. + */ +template using Int = std::integral_constant; + +/* + * Helper class to implement in-place functors. + * + * We want to be able to use inline functors like a lambda to keep the code + * as clear as possible. + */ +template class MapFn { + Fn&& fn_; +public: + + // It takes the real functor that can be specified in-place but only + // with C++14 because the second parameter's type will depend on the + // type of the parameter pack element that is processed. In C++14 we can + // specify this second parameter type as auto in the lamda parameter list. + inline MapFn(Fn&& fn): fn_(forward(fn)) {} + + template void operator ()(T&& pack_element) { + // We provide the index as the first parameter and the pack (or tuple) + // element as the second parameter to the functor. + fn_(N, forward(pack_element)); + } +}; + +/* + * Implementation of the template loop trick. + * We create a mechanism for looping over a parameter pack in compile time. + * \tparam Idx is the loop index which will be decremented at each recursion. + * \tparam Args The parameter pack that will be processed. + * + */ +template +class _MetaLoop {}; + +// Implementation for the first element of Args... +template +class _MetaLoop, Args...> { +public: + + const static BP2D_CONSTEXPR int N = 0; + const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1; + + template + void run( Tup&& valtup, Fn&& fn) { + MapFn {forward(fn)} (get(valtup)); + } +}; + +// Implementation for the N-th element of Args... +template +class _MetaLoop, Args...> { +public: + + const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1; + + template + void run(Tup&& valtup, Fn&& fn) { + MapFn {forward(fn)} (std::get(valtup)); + + // Recursive call to process the next element of Args + _MetaLoop, Args...> ().run(forward(valtup), + forward(fn)); + } +}; + +/* + * Instantiation: We must instantiate the template with the last index because + * the generalized version calls the decremented instantiations recursively. + * Once the instantiation with the first index is called, the terminating + * version of run is called which does not call itself anymore. + * + * If you are utterly annoyed, at least you have learned a super crazy + * functional metaprogramming pattern. + */ +template +using MetaLoop = _MetaLoop, Args...>; + +public: + +/** + * \brief The final usable function template. + * + * This is similar to what varags was on C but in compile time C++11. + * You can call: + * apply(, ); + * For example: + * + * struct mapfunc { + * template void operator()(int N, T&& element) { + * std::cout << "The value of the parameter "<< N <<": " + * << element << std::endl; + * } + * }; + * + * apply(mapfunc(), 'a', 10, 151.545); + * + * C++14: + * apply([](int N, auto&& element){ + * std::cout << "The value of the parameter "<< N <<": " + * << element << std::endl; + * }, 'a', 10, 151.545); + * + * This yields the output: + * The value of the parameter 0: a + * The value of the parameter 1: 10 + * The value of the parameter 2: 151.545 + * + * As an addition, the function can be called with a tuple as the second + * parameter holding the arguments instead of a parameter pack. + * + */ +template +inline static void apply(Fn&& fn, Args&&...args) { + MetaLoop().run(tuple(forward(args)...), + forward(fn)); +} + +/// The version of apply with a tuple rvalue reference. +template +inline static void apply(Fn&& fn, tuple&& tup) { + MetaLoop().run(std::move(tup), forward(fn)); +} + +/// The version of apply with a tuple lvalue reference. +template +inline static void apply(Fn&& fn, tuple& tup) { + MetaLoop().run(tup, forward(fn)); +} + +/// The version of apply with a tuple const reference. +template +inline static void apply(Fn&& fn, const tuple& tup) { + MetaLoop().run(tup, forward(fn)); +} + +/** + * Call a function with its arguments encapsualted in a tuple. + */ +template +inline static auto +callFunWithTuple(Fn&& fn, Tup&& tup, index_sequence) -> + decltype(fn(std::get(tup)...)) +{ + return fn(std::get(tup)...); +} + +}; +} +} + +#endif // METALOOP_HPP diff --git a/xs/src/libnest2d/libnest2d/optimizer.hpp b/xs/src/libnest2d/libnest2d/optimizer.hpp index 52e67f7d57..c8ed2e378a 100644 --- a/xs/src/libnest2d/libnest2d/optimizer.hpp +++ b/xs/src/libnest2d/libnest2d/optimizer.hpp @@ -10,8 +10,7 @@ namespace libnest2d { namespace opt { using std::forward; using std::tuple; -using std::get; -using std::tuple_element; +using std::make_tuple; /// A Type trait for upper and lower limit of a numeric type. template @@ -51,176 +50,7 @@ inline Bound bound(const T& min, const T& max) { return Bound(min, max); } template using Input = tuple; template -inline tuple initvals(Args...args) { return std::make_tuple(args...); } - -/** - * @brief Helper class to be able to loop over a parameter pack's elements. - */ -class metaloop { -// The implementation is based on partial struct template specializations. -// Basically we need a template type that is callable and takes an integer -// non-type template parameter which can be used to implement recursive calls. -// -// C++11 will not allow the usage of a plain template function that is why we -// use struct with overloaded call operator. At the same time C++11 prohibits -// partial template specialization with a non type parameter such as int. We -// need to wrap that in a type (see metaloop::Int). - -/* - * A helper alias to create integer values wrapped as a type. It is nessecary - * because a non type template parameter (such as int) would be prohibited in - * a partial specialization. Also for the same reason we have to use a class - * _Metaloop instead of a simple function as a functor. A function cannot be - * partially specialized in a way that is neccesary for this trick. - */ -template using Int = std::integral_constant; - -/* - * Helper class to implement in-place functors. - * - * We want to be able to use inline functors like a lambda to keep the code - * as clear as possible. - */ -template class MapFn { - Fn&& fn_; -public: - - // It takes the real functor that can be specified in-place but only - // with C++14 because the second parameter's type will depend on the - // type of the parameter pack element that is processed. In C++14 we can - // specify this second parameter type as auto in the lamda parameter list. - inline MapFn(Fn&& fn): fn_(forward(fn)) {} - - template void operator ()(T&& pack_element) { - // We provide the index as the first parameter and the pack (or tuple) - // element as the second parameter to the functor. - fn_(N, forward(pack_element)); - } -}; - -/* - * Implementation of the template loop trick. - * We create a mechanism for looping over a parameter pack in compile time. - * \tparam Idx is the loop index which will be decremented at each recursion. - * \tparam Args The parameter pack that will be processed. - * - */ -template -class _MetaLoop {}; - -// Implementation for the first element of Args... -template -class _MetaLoop, Args...> { -public: - - const static BP2D_CONSTEXPR int N = 0; - const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1; - - template - void run( Tup&& valtup, Fn&& fn) { - MapFn {forward(fn)} (get(valtup)); - } -}; - -// Implementation for the N-th element of Args... -template -class _MetaLoop, Args...> { -public: - - const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1; - - template - void run(Tup&& valtup, Fn&& fn) { - MapFn {forward(fn)} (std::get(valtup)); - - // Recursive call to process the next element of Args - _MetaLoop, Args...> ().run(forward(valtup), - forward(fn)); - } -}; - -/* - * Instantiation: We must instantiate the template with the last index because - * the generalized version calls the decremented instantiations recursively. - * Once the instantiation with the first index is called, the terminating - * version of run is called which does not call itself anymore. - * - * If you are utterly annoyed, at least you have learned a super crazy - * functional metaprogramming pattern. - */ -template -using MetaLoop = _MetaLoop, Args...>; - -public: - -/** - * \brief The final usable function template. - * - * This is similar to what varags was on C but in compile time C++11. - * You can call: - * apply(, ); - * For example: - * - * struct mapfunc { - * template void operator()(int N, T&& element) { - * std::cout << "The value of the parameter "<< N <<": " - * << element << std::endl; - * } - * }; - * - * apply(mapfunc(), 'a', 10, 151.545); - * - * C++14: - * apply([](int N, auto&& element){ - * std::cout << "The value of the parameter "<< N <<": " - * << element << std::endl; - * }, 'a', 10, 151.545); - * - * This yields the output: - * The value of the parameter 0: a - * The value of the parameter 1: 10 - * The value of the parameter 2: 151.545 - * - * As an addition, the function can be called with a tuple as the second - * parameter holding the arguments instead of a parameter pack. - * - */ -template -inline static void apply(Fn&& fn, Args&&...args) { - MetaLoop().run(tuple(forward(args)...), - forward(fn)); -} - -/// The version of apply with a tuple rvalue reference. -template -inline static void apply(Fn&& fn, tuple&& tup) { - MetaLoop().run(std::move(tup), forward(fn)); -} - -/// The version of apply with a tuple lvalue reference. -template -inline static void apply(Fn&& fn, tuple& tup) { - MetaLoop().run(tup, forward(fn)); -} - -/// The version of apply with a tuple const reference. -template -inline static void apply(Fn&& fn, const tuple& tup) { - MetaLoop().run(tup, forward(fn)); -} - -/** - * Call a function with its arguments encapsualted in a tuple. - */ -template -inline static auto -callFunWithTuple(Fn&& fn, Tup&& tup, index_sequence) -> - decltype(fn(std::get(tup)...)) -{ - return fn(std::get(tup)...); -} - -}; +inline tuple initvals(Args...args) { return make_tuple(args...); } /** * @brief Specific optimization methods for which a default optimizer @@ -257,29 +87,20 @@ enum ResultCodes { template struct Result { ResultCodes resultcode; - std::tuple optimum; + tuple optimum; double score; }; -/** - * @brief The stop limit can be specified as the absolute error or as the - * relative error, just like in nlopt. - */ -enum class StopLimitType { - ABSOLUTE, - RELATIVE -}; - /** * @brief A type for specifying the stop criteria. */ struct StopCriteria { - /// Relative or absolute termination error - StopLimitType type = StopLimitType::RELATIVE; + /// If the absolute value difference between two scores. + double absolute_score_difference = std::nan(""); - /// The error value that is interpredted depending on the type property. - double stoplimit = 0.0001; + /// If the relative value difference between two scores. + double relative_score_difference = std::nan(""); unsigned max_iterations = 0; }; @@ -310,11 +131,11 @@ public: * \return Returns a Result structure. * An example call would be: * auto result = opt.optimize_min( - * [](std::tuple x) // object function + * [](tuple x) // object function * { * return std::pow(std::get<0>(x), 2); * }, - * std::make_tuple(-0.5), // initial value + * make_tuple(-0.5), // initial value * {-1.0, 1.0} // search space bounds * ); */ @@ -390,10 +211,14 @@ public: static_assert(always_false::value, "Optimizer unimplemented!"); } + DummyOptimizer(const StopCriteria&) { + static_assert(always_false::value, "Optimizer unimplemented!"); + } + template - Result optimize(Func&& func, - std::tuple initvals, - Bound... args) + Result optimize(Func&& /*func*/, + tuple /*initvals*/, + Bound... /*args*/) { return Result(); } diff --git a/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp b/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp index 798bf96223..737ca6e3c0 100644 --- a/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/optimizers/nlopt_boilerplate.hpp @@ -1,15 +1,25 @@ #ifndef NLOPT_BOILERPLATE_HPP #define NLOPT_BOILERPLATE_HPP +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #include #include +#include "libnest2d/metaloop.hpp" #include namespace libnest2d { namespace opt { -nlopt::algorithm method2nloptAlg(Method m) { +inline nlopt::algorithm method2nloptAlg(Method m) { switch(m) { case Method::L_SIMPLEX: return nlopt::LN_NELDERMEAD; @@ -87,7 +97,7 @@ protected: template static double optfunc(const std::vector& params, - std::vector& grad, + std::vector& /*grad*/, void *data) { auto fnptr = static_cast*>(data); @@ -132,12 +142,10 @@ protected: default: ; } - switch(this->stopcr_.type) { - case StopLimitType::ABSOLUTE: - opt_.set_ftol_abs(stopcr_.stoplimit); break; - case StopLimitType::RELATIVE: - opt_.set_ftol_rel(stopcr_.stoplimit); break; - } + auto abs_diff = stopcr_.absolute_score_difference; + auto rel_diff = stopcr_.relative_score_difference; + if(!std::isnan(abs_diff)) opt_.set_ftol_abs(abs_diff); + if(!std::isnan(rel_diff)) opt_.set_ftol_rel(rel_diff); if(this->stopcr_.max_iterations > 0) opt_.set_maxeval(this->stopcr_.max_iterations ); diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 89848eb538..9e8b3be91b 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -6,6 +6,10 @@ #endif #include "placer_boilerplate.hpp" #include "../geometry_traits_nfp.hpp" +#include "libnest2d/optimizer.hpp" +#include + +#include "tools/svgtools.hpp" namespace libnest2d { namespace strategies { @@ -20,15 +24,62 @@ struct NfpPConfig { TOP_RIGHT, }; - /// Which angles to try out for better results + /// Which angles to try out for better results. std::vector rotations; - /// Where to align the resulting packed pile + /// Where to align the resulting packed pile. Alignment alignment; + /// Where to start putting objects in the bin. Alignment starting_point; - std::function&, double, double, double)> + /** + * @brief A function object representing the fitting function in the + * placement optimization process. (Optional) + * + * This is the most versatile tool to configure the placer. The fitting + * function is evaluated many times when a new item is being placed into the + * bin. The output should be a rated score of the new item's position. + * + * This is not a mandatory option as there is a default fitting function + * that will optimize for the best pack efficiency. With a custom fitting + * function you can e.g. influence the shape of the arranged pile. + * + * \param shapes The first parameter is a container with all the placed + * polygons including the current candidate. You can calculate a bounding + * box or convex hull on this pile of polygons. + * + * \param item The second parameter is the candidate item. Note that + * calling transformedShape() on this second argument returns an identical + * shape as calling shapes.back(). These would not be the same objects only + * identical shapes! Using the second parameter is a lot faster due to + * caching some properties of the polygon (area, etc...) + * + * \param occupied_area The third parameter is the sum of areas of the + * items in the first parameter so you don't have to iterate through them + * if you only need their area. + * + * \param norm A norming factor for physical dimensions. E.g. if your score + * is the distance between the item and the bin center, you should divide + * that distance with the norming factor. If the score is an area than + * divide it with the square of the norming factor. Imagine it as a unit of + * distance. + * + * \param penality The fifth parameter is the amount of minimum penality if + * the arranged pile would't fit into the bin. You can use the wouldFit() + * function to check this. Note that the pile can be outside the bin's + * boundaries while the placement algorithm is running. Your job is only to + * check if the pile could be translated into a position in the bin where + * all the items would be inside. For a box shaped bin you can use the + * pile's bounding box to check whether it's width and height is small + * enough. If the pile would not fit, you have to make sure that the + * resulting score will be higher then the penality value. A good solution + * would be to set score = 2*penality-score in case the pile wouldn't fit + * into the bin. + * + */ + std::function&, const _Item&, + double, double, double)> object_function; /** @@ -38,11 +89,30 @@ struct NfpPConfig { */ float accuracy = 1.0; + /** + * @brief If you want to see items inside other item's holes, you have to + * turn this switch on. + * + * This will only work if a suitable nfp implementation is provided. + * The library has no such implementation right now. + */ + bool explore_holes = false; + NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}), alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {} }; -// A class for getting a point on the circumference of the polygon (in log time) +/** + * A class for getting a point on the circumference of the polygon (in log time) + * + * This is a transformation of the provided polygon to be able to pinpoint + * locations on the circumference. The optimizer will pass a floating point + * value e.g. within <0,1> and we have to transform this value quickly into a + * coordinate on the circumference. By definition 0 should yield the first + * vertex and 1.0 would be the last (which should coincide with first). + * + * We also have to make this work for the holes of the captured polygon. + */ template class EdgeCache { using Vertex = TPoint; using Coord = TCoord; @@ -176,24 +246,64 @@ public: return holes_[hidx].full_distance; } + /// Get the normalized distance values for each vertex inline const std::vector& corners() const BP2D_NOEXCEPT { fetchCorners(); return contour_.corners; } + /// corners for a specific hole inline const std::vector& corners(unsigned holeidx) const BP2D_NOEXCEPT { fetchHoleCorners(holeidx); return holes_[holeidx].corners; } - inline unsigned holeCount() const BP2D_NOEXCEPT { return holes_.size(); } + /// The number of holes in the abstracted polygon + inline size_t holeCount() const BP2D_NOEXCEPT { return holes_.size(); } }; template struct Lvl { static const NfpLevel value = lvl; }; +template +inline void correctNfpPosition(Nfp::NfpResult& nfp, + const _Item& stationary, + const _Item& orbiter) +{ + // The provided nfp is somewhere in the dark. We need to get it + // to the right position around the stationary shape. + // This is done by choosing the leftmost lowest vertex of the + // orbiting polygon to be touched with the rightmost upper + // vertex of the stationary polygon. In this configuration, the + // reference vertex of the orbiting polygon (which can be dragged around + // the nfp) will be its rightmost upper vertex that coincides with the + // rightmost upper vertex of the nfp. No proof provided other than Jonas + // Lindmark's reasoning about the reference vertex of nfp in his thesis + // ("No fit polygon problem" - section 2.1.9) + + auto touch_sh = stationary.rightmostTopVertex(); + auto touch_other = orbiter.leftmostBottomVertex(); + auto dtouch = touch_sh - touch_other; + auto top_other = orbiter.rightmostTopVertex() + dtouch; + auto dnfp = top_other - nfp.second; // nfp.second is the nfp reference point + ShapeLike::translate(nfp.first, dnfp); +} + +template +inline void correctNfpPosition(Nfp::NfpResult& nfp, + const RawShape& stationary, + const _Item& orbiter) +{ + auto touch_sh = Nfp::rightmostUpVertex(stationary); + auto touch_other = orbiter.leftmostBottomVertex(); + auto dtouch = touch_sh - touch_other; + auto top_other = orbiter.rightmostTopVertex() + dtouch; + auto dnfp = top_other - nfp.second; + ShapeLike::translate(nfp.first, dnfp); +} + template Nfp::Shapes nfp( const Container& polygons, const _Item& trsh, @@ -203,18 +313,35 @@ Nfp::Shapes nfp( const Container& polygons, Nfp::Shapes nfps; + //int pi = 0; for(Item& sh : polygons) { - auto subnfp = Nfp::noFitPolygon( - sh.transformedShape(), trsh.transformedShape()); + auto subnfp_r = Nfp::noFitPolygon( + sh.transformedShape(), trsh.transformedShape()); #ifndef NDEBUG auto vv = ShapeLike::isValid(sh.transformedShape()); assert(vv.first); - auto vnfp = ShapeLike::isValid(subnfp); + auto vnfp = ShapeLike::isValid(subnfp_r.first); assert(vnfp.first); #endif - nfps = Nfp::merge(nfps, subnfp); + correctNfpPosition(subnfp_r, sh, trsh); + + nfps = Nfp::merge(nfps, subnfp_r.first); + +// double SCALE = 1000000; +// using SVGWriter = svg::SVGWriter; +// SVGWriter::Config conf; +// conf.mm_in_coord_units = SCALE; +// SVGWriter svgw(conf); +// Box bin(250*SCALE, 210*SCALE); +// svgw.setSize(bin); +// for(int i = 0; i <= pi; i++) svgw.writeItem(polygons[i]); +// svgw.writeItem(trsh); +//// svgw.writeItem(Item(subnfp_r.first)); +// for(auto& n : nfps) svgw.writeItem(Item(n)); +// svgw.save("nfpout"); +// pi++; } return nfps; @@ -227,42 +354,65 @@ Nfp::Shapes nfp( const Container& polygons, { using Item = _Item; - Nfp::Shapes nfps, stationary; + Nfp::Shapes nfps; + + auto& orb = trsh.transformedShape(); + bool orbconvex = trsh.isContourConvex(); for(Item& sh : polygons) { - stationary = Nfp::merge(stationary, sh.transformedShape()); - } + Nfp::NfpResult subnfp; + auto& stat = sh.transformedShape(); - std::cout << "pile size: " << stationary.size() << std::endl; - for(RawShape& sh : stationary) { + if(sh.isContourConvex() && orbconvex) + subnfp = Nfp::noFitPolygon(stat, orb); + else if(orbconvex) + subnfp = Nfp::noFitPolygon(stat, orb); + else + subnfp = Nfp::noFitPolygon(stat, orb); - RawShape subnfp; -// if(sh.isContourConvex() && trsh.isContourConvex()) { -// subnfp = Nfp::noFitPolygon( -// sh.transformedShape(), trsh.transformedShape()); -// } else { - subnfp = Nfp::noFitPolygon( sh/*.transformedShape()*/, - trsh.transformedShape()); -// } + correctNfpPosition(subnfp, sh, trsh); -// #ifndef NDEBUG -// auto vv = ShapeLike::isValid(sh.transformedShape()); -// assert(vv.first); - -// auto vnfp = ShapeLike::isValid(subnfp); -// assert(vnfp.first); -// #endif - -// auto vnfp = ShapeLike::isValid(subnfp); -// if(!vnfp.first) { -// std::cout << vnfp.second << std::endl; -// std::cout << ShapeLike::toString(subnfp) << std::endl; -// } - - nfps = Nfp::merge(nfps, subnfp); + nfps = Nfp::merge(nfps, subnfp.first); } return nfps; + + +// using Item = _Item; +// using sl = ShapeLike; + +// Nfp::Shapes nfps, stationary; + +// for(Item& sh : polygons) { +// stationary = Nfp::merge(stationary, sh.transformedShape()); +// } + +// for(RawShape& sh : stationary) { + +//// auto vv = sl::isValid(sh); +//// std::cout << vv.second << std::endl; + + +// Nfp::NfpResult subnfp; +// bool shconvex = sl::isConvex(sl::getContour(sh)); +// if(shconvex && trsh.isContourConvex()) { +// subnfp = Nfp::noFitPolygon( +// sh, trsh.transformedShape()); +// } else if(trsh.isContourConvex()) { +// subnfp = Nfp::noFitPolygon( +// sh, trsh.transformedShape()); +// } +// else { +// subnfp = Nfp::noFitPolygon( sh, +// trsh.transformedShape()); +// } + +// correctNfpPosition(subnfp, sh, trsh); + +// nfps = Nfp::merge(nfps, subnfp.first); +// } + +// return nfps; } template @@ -290,6 +440,14 @@ public: norm_(std::sqrt(ShapeLike::area(bin))), penality_(1e6*norm_) {} + _NofitPolyPlacer(const _NofitPolyPlacer&) = default; + _NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default; + +#ifndef BP2D_COMPILER_MSVC12 // MSVC2013 does not support default move ctors + _NofitPolyPlacer(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default; + _NofitPolyPlacer& operator=(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default; +#endif + bool static inline wouldFit(const RawShape& chull, const RawShape& bin) { auto bbch = ShapeLike::boundingBox(chull); auto bbin = ShapeLike::boundingBox(bin); @@ -363,7 +521,7 @@ public: auto getNfpPoint = [&ecache](const Optimum& opt) { return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) : - ecache[opt.nfpidx].coords(opt.nfpidx, opt.relpos); + ecache[opt.nfpidx].coords(opt.hidx, opt.relpos); }; Nfp::Shapes pile; @@ -378,8 +536,9 @@ public: // customizable by the library client auto _objfunc = config_.object_function? config_.object_function : - [this](const Nfp::Shapes& pile, double occupied_area, - double /*norm*/, double penality) + [this](const Nfp::Shapes& pile, Item, + double occupied_area, double /*norm*/, + double penality) { auto ch = ShapeLike::convexHull(pile); @@ -410,7 +569,7 @@ public: double occupied_area = pile_area + item.area(); - double score = _objfunc(pile, occupied_area, + double score = _objfunc(pile, item, occupied_area, norm_, penality_); pile.pop_back(); @@ -420,8 +579,8 @@ public: opt::StopCriteria stopcr; stopcr.max_iterations = 1000; - stopcr.stoplimit = 0.001; - stopcr.type = opt::StopLimitType::RELATIVE; + stopcr.absolute_score_difference = 1e-20*norm_; +// stopcr.relative_score_difference = 1e-20; opt::TOptimizer solver(stopcr); Optimum optimum(0, 0); @@ -458,6 +617,14 @@ public: } catch(std::exception& e) { derr() << "ERROR: " << e.what() << "\n"; } + +// auto sc = contour_ofn(pos); +// if(sc < best_score) { +// best_score = sc; +// optimum.relpos = pos; +// optimum.nfpidx = ch; +// optimum.hidx = -1; +// } }); for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) { @@ -490,6 +657,13 @@ public: } catch(std::exception& e) { derr() << "ERROR: " << e.what() << "\n"; } +// auto sc = hole_ofn(pos); +// if(sc < best_score) { +// best_score = sc; +// optimum.relpos = pos; +// optimum.nfpidx = ch; +// optimum.hidx = hidx; +// } }); } } diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index 1d233cf356..17ac1167df 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -256,14 +256,14 @@ public: if(not_packed.size() < 2) return false; // No group of two items - else { - double largest_area = not_packed.front().get().area(); - auto itmp = not_packed.begin(); itmp++; - double second_largest = itmp->get().area(); - if( free_area - second_largest - largest_area > waste) - return false; // If even the largest two items do not fill - // the bin to the desired waste than we can end here. - } + + double largest_area = not_packed.front().get().area(); + auto itmp = not_packed.begin(); itmp++; + double second_largest = itmp->get().area(); + if( free_area - second_largest - largest_area > waste) + return false; // If even the largest two items do not fill + // the bin to the desired waste than we can end here. + bool ret = false; auto it = not_packed.begin(); @@ -481,7 +481,7 @@ public: { std::array packed = {false}; - for(auto id : idx) packed[id] = + for(auto id : idx) packed.at(id) = placer.pack(candidates[id]); bool check = @@ -537,8 +537,7 @@ public: while (it != store_.end()) { Placer p(bin); if(!p.pack(*it)) { - auto itmp = it++; - store_.erase(itmp); + it = store_.erase(it); } else it++; } } @@ -605,8 +604,7 @@ public: if(placer.pack(*it)) { filled_area += it->get().area(); free_area = bin_area - filled_area; - auto itmp = it++; - not_packed.erase(itmp); + it = not_packed.erase(it); makeProgress(placer, idx, 1); } else it++; } diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index 5185014a81..f34961f803 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -52,7 +52,7 @@ public: auto total = last-first; auto makeProgress = [this, &total](Placer& placer, size_t idx) { packed_bins_[idx] = placer.getItems(); - this->progress_(--total); + this->progress_(static_cast(--total)); }; // Safety test: try to pack each item into an empty bin. If it fails diff --git a/xs/src/libnest2d/tests/test.cpp b/xs/src/libnest2d/tests/test.cpp index b37274f841..39315ff1ab 100644 --- a/xs/src/libnest2d/tests/test.cpp +++ b/xs/src/libnest2d/tests/test.cpp @@ -682,7 +682,9 @@ void testNfp(const std::vector& testdata) { auto&& nfp = Nfp::noFitPolygon(stationary.rawShape(), orbiter.transformedShape()); - auto v = ShapeLike::isValid(nfp); + strategies::correctNfpPosition(nfp, stationary, orbiter); + + auto v = ShapeLike::isValid(nfp.first); if(!v.first) { std::cout << v.second << std::endl; @@ -690,7 +692,7 @@ void testNfp(const std::vector& testdata) { ASSERT_TRUE(v.first); - Item infp(nfp); + Item infp(nfp.first); int i = 0; auto rorbiter = orbiter.transformedShape(); @@ -742,6 +744,15 @@ TEST(GeometryAlgorithms, nfpConvexConvex) { // testNfp(nfp_concave_testdata); //} +TEST(GeometryAlgorithms, nfpConcaveConcave) { + using namespace libnest2d; + +// Rectangle r1(10, 10); +// Rectangle r2(20, 20); +// auto result = Nfp::nfpSimpleSimple(r1.transformedShape(), +// r2.transformedShape()); +} + TEST(GeometryAlgorithms, pointOnPolygonContour) { using namespace libnest2d; diff --git a/xs/src/libnest2d/tools/libnfpglue.cpp b/xs/src/libnest2d/tools/libnfpglue.cpp index 4cbdb5442a..18656fd402 100644 --- a/xs/src/libnest2d/tools/libnfpglue.cpp +++ b/xs/src/libnest2d/tools/libnfpglue.cpp @@ -49,18 +49,18 @@ libnfporb::point_t scale(const libnfporb::point_t& p, long double factor) { long double px = p.x_.val(); long double py = p.y_.val(); #endif - return libnfporb::point_t(px*factor, py*factor); + return {px*factor, py*factor}; } } -PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother) +NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother) { using Vertex = PointImpl; - PolygonImpl ret; + NfpR ret; -// try { + try { libnfporb::polygon_t pstat, porb; boost::geometry::convert(sh, pstat); @@ -85,7 +85,7 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother) // this can throw auto nfp = libnfporb::generateNFP(pstat, porb, true); - auto &ct = ShapeLike::getContour(ret); + auto &ct = ShapeLike::getContour(ret.first); ct.reserve(nfp.front().size()+1); for(auto v : nfp.front()) { v = scale(v, refactor); @@ -94,10 +94,10 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother) ct.push_back(ct.front()); std::reverse(ct.begin(), ct.end()); - auto &rholes = ShapeLike::holes(ret); + auto &rholes = ShapeLike::holes(ret.first); for(size_t hidx = 1; hidx < nfp.size(); ++hidx) { if(nfp[hidx].size() >= 3) { - rholes.push_back({}); + rholes.emplace_back(); auto& h = rholes.back(); h.reserve(nfp[hidx].size()+1); @@ -110,73 +110,48 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother) } } - auto& cmp = vsort; - std::sort(pstat.outer().begin(), pstat.outer().end(), cmp); - std::sort(porb.outer().begin(), porb.outer().end(), cmp); + ret.second = Nfp::referenceVertex(ret.first); - // leftmost lower vertex of the stationary polygon - auto& touch_sh = scale(pstat.outer().back(), refactor); - // rightmost upper vertex of the orbiting polygon - auto& touch_other = scale(porb.outer().front(), refactor); - - // Calculate the difference and move the orbiter to the touch position. - auto dtouch = touch_sh - touch_other; - auto _top_other = scale(porb.outer().back(), refactor) + dtouch; - - Vertex top_other(getX(_top_other), getY(_top_other)); - - // Get the righmost upper vertex of the nfp and move it to the RMU of - // the orbiter because they should coincide. - auto&& top_nfp = Nfp::rightmostUpVertex(ret); - auto dnfp = top_other - top_nfp; - - std::for_each(ShapeLike::begin(ret), ShapeLike::end(ret), - [&dnfp](Vertex& v) { v+= dnfp; } ); - - for(auto& h : ShapeLike::holes(ret)) - std::for_each( h.begin(), h.end(), - [&dnfp](Vertex& v) { v += dnfp; } ); - -// } catch(std::exception& e) { -// std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl; + } catch(std::exception& e) { + std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl; // auto ch_stat = ShapeLike::convexHull(sh); // auto ch_orb = ShapeLike::convexHull(cother); -// ret = Nfp::nfpConvexOnly(ch_stat, ch_orb); -// } + ret = Nfp::nfpConvexOnly(sh, cother); + } return ret; } -PolygonImpl Nfp::NfpImpl::operator()( +NfpR Nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother);//nfpConvexOnly(sh, cother); } -PolygonImpl Nfp::NfpImpl::operator()( +NfpR Nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother); } -PolygonImpl Nfp::NfpImpl::operator()( +NfpR Nfp::NfpImpl::operator()( const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) { return _nfp(sh, cother); } -PolygonImpl -Nfp::NfpImpl::operator()( - const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) -{ - return _nfp(sh, cother); -} +//PolygonImpl +//Nfp::NfpImpl::operator()( +// const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) +//{ +// return _nfp(sh, cother); +//} -PolygonImpl -Nfp::NfpImpl::operator()( - const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) -{ - return _nfp(sh, cother); -} +//PolygonImpl +//Nfp::NfpImpl::operator()( +// const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother) +//{ +// return _nfp(sh, cother); +//} } diff --git a/xs/src/libnest2d/tools/libnfpglue.hpp b/xs/src/libnest2d/tools/libnfpglue.hpp index 87b0e0833a..75f6394451 100644 --- a/xs/src/libnest2d/tools/libnfpglue.hpp +++ b/xs/src/libnest2d/tools/libnfpglue.hpp @@ -5,37 +5,39 @@ namespace libnest2d { -PolygonImpl _nfp(const PolygonImpl& sh, const PolygonImpl& cother); +using NfpR = Nfp::NfpResult; + +NfpR _nfp(const PolygonImpl& sh, const PolygonImpl& cother); template<> struct Nfp::NfpImpl { - PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; template<> struct Nfp::NfpImpl { - PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; template<> struct Nfp::NfpImpl { - PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); + NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother); }; -template<> -struct Nfp::NfpImpl { - PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); -}; +//template<> +//struct Nfp::NfpImpl { +// NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother); +//}; -template<> -struct Nfp::NfpImpl { - PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother); -}; +//template<> +//struct Nfp::NfpImpl { +// NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother); +//}; template<> struct Nfp::MaxNfpLevel { static const BP2D_CONSTEXPR NfpLevel value = // NfpLevel::CONVEX_ONLY; - NfpLevel::BOTH_CONCAVE_WITH_HOLES; + NfpLevel::BOTH_CONCAVE; }; } diff --git a/xs/src/libnest2d/tools/svgtools.hpp b/xs/src/libnest2d/tools/svgtools.hpp index 273ecabac9..3a83caa707 100644 --- a/xs/src/libnest2d/tools/svgtools.hpp +++ b/xs/src/libnest2d/tools/svgtools.hpp @@ -5,11 +5,17 @@ #include #include -#include +#include namespace libnest2d { namespace svg { +template class SVGWriter { + using Item = _Item; + using Coord = TCoord>; + using Box = _Box>; + using PackGroup = _PackGroup; + public: enum OrigoLocation { diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index be0765ea78..4b61e1c9ae 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -529,6 +529,7 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // handle different rotations // arranger.useMinimumBoundigBoxRotation(); pcfg.rotations = { 0.0 }; + double norm_2 = std::nan(""); // Magic: we will specify what is the goal of arrangement... In this case // we override the default object function to make the larger items go into @@ -537,33 +538,46 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // We alse sacrafice a bit of pack efficiency for this to work. As a side // effect, the arrange procedure is a lot faster (we do not need to // calculate the convex hulls) - pcfg.object_function = [bin, hasbin]( + pcfg.object_function = [bin, hasbin, &norm_2]( NfpPlacer::Pile pile, // The currently arranged pile + Item item, double /*area*/, // Sum area of items (not needed) double norm, // A norming factor for physical dimensions double penality) // Min penality in case of bad arrangement { + using pl = PointLike; + auto bb = ShapeLike::boundingBox(pile); + auto ibb = item.boundingBox(); + auto minc = ibb.minCorner(); + auto maxc = ibb.maxCorner(); - // We get the current item that's being evaluated. - auto& sh = pile.back(); - - // We retrieve the reference point of this item - auto rv = ShapeLike::boundingBox(sh).center(); + if(std::isnan(norm_2)) norm_2 = pow(norm, 2); // We get the distance of the reference point from the center of the // heat bed - auto c = bin.center(); - auto d = PointLike::distance(rv, c); + auto cc = bb.center(); + auto top_left = PointImpl{getX(minc), getY(maxc)}; + auto bottom_right = PointImpl{getX(maxc), getY(minc)}; + + auto a = pl::distance(ibb.maxCorner(), cc); + auto b = pl::distance(ibb.minCorner(), cc); + auto c = pl::distance(ibb.center(), cc); + auto d = pl::distance(top_left, cc); + auto e = pl::distance(bottom_right, cc); + + auto area = bb.width() * bb.height() / norm_2; + + auto min_dist = std::min({a, b, c, d, e}) / norm; // The score will be the normalized distance which will be minimized, // effectively creating a circle shaped pile of items - double score = d/norm; + double score = 0.8*min_dist + 0.2*area; // If it does not fit into the print bed we will beat it // with a large penality. If we would not do this, there would be only // one big pile that doesn't care whether it fits onto the print bed. - if(hasbin && !NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score; + if(!NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score; return score; }; From 6b801f250a75515660a2588492022444de74069a Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 25 Jun 2018 18:19:51 +0200 Subject: [PATCH 032/119] avrdude: use sections instead of offsets --- xs/src/avrdude/fileio.c | 56 +++++++++++++++++++--------- xs/src/avrdude/libavrdude.h | 6 +-- xs/src/avrdude/main.c | 2 +- xs/src/avrdude/update.c | 20 +++++----- xs/src/slic3r/GUI/FirmwareDialog.cpp | 23 ++++-------- 5 files changed, 61 insertions(+), 46 deletions(-) diff --git a/xs/src/avrdude/fileio.c b/xs/src/avrdude/fileio.c index aa57f55870..7081592951 100644 --- a/xs/src/avrdude/fileio.c +++ b/xs/src/avrdude/fileio.c @@ -98,11 +98,11 @@ static int fileio_num(struct fioparms * fio, char * filename, FILE * f, AVRMEM * mem, int size, FILEFMT fmt); -static int fmt_autodetect(char * fname, size_t offset); +static int fmt_autodetect(char * fname, unsigned section); -static FILE *fopen_and_seek(const char *filename, const char *mode, size_t offset) +static FILE *fopen_and_seek(const char *filename, const char *mode, unsigned section) { FILE *file; // On Windows we need to convert the filename to UTF-16 @@ -118,16 +118,38 @@ static FILE *fopen_and_seek(const char *filename, const char *mode, size_t offse file = fopen(filename, mode); #endif - if (file != NULL) { - // Some systems allow seeking past the end of file, so we need check for that first and disallow - if (fseek(file, 0, SEEK_END) != 0 - || offset >= ftell(file) - || fseek(file, offset, SEEK_SET) != 0 - ) { - fclose(file); - file = NULL; - errno = EINVAL; + if (file == NULL) { + return NULL; + } + + // Seek to the specified 'section' + static const char *hex_terminator = ":00000001FF\r"; + unsigned terms_seen = 0; + char buffer[MAX_LINE_LEN + 1]; + + while (terms_seen < section && fgets(buffer, MAX_LINE_LEN, file) != NULL) { + size_t len = strlen(buffer); + + if (buffer[len - 1] == '\n') { + len--; + buffer[len] = 0; } + if (buffer[len - 1] != '\r') { + buffer[len] = '\r'; + len++; + buffer[len] = 0; + } + + if (strcmp(buffer, hex_terminator) == 0) { + // Found a section terminator + terms_seen++; + } + } + + if (feof(file)) { + // Section not found + fclose(file); + return NULL; } return file; @@ -1392,7 +1414,7 @@ int fileio_setparms(int op, struct fioparms * fp, -static int fmt_autodetect(char * fname, size_t offset) +static int fmt_autodetect(char * fname, unsigned section) { FILE * f; unsigned char buf[MAX_LINE_LEN]; @@ -1402,9 +1424,9 @@ static int fmt_autodetect(char * fname, size_t offset) int first = 1; #if defined(WIN32NATIVE) - f = fopen_and_seek(fname, "r", offset); + f = fopen_and_seek(fname, "r", section); #else - f = fopen_and_seek(fname, "rb", offset); + f = fopen_and_seek(fname, "rb", section); #endif if (f == NULL) { @@ -1480,7 +1502,7 @@ static int fmt_autodetect(char * fname, size_t offset) int fileio(int op, char * filename, FILEFMT format, - struct avrpart * p, char * memtype, int size, size_t offset) + struct avrpart * p, char * memtype, int size, unsigned section) { int rc; FILE * f; @@ -1539,7 +1561,7 @@ int fileio(int op, char * filename, FILEFMT format, return -1; } - format_detect = fmt_autodetect(fname, offset); + format_detect = fmt_autodetect(fname, section); if (format_detect < 0) { avrdude_message(MSG_INFO, "%s: can't determine file format for %s, specify explicitly\n", progname, fname); @@ -1570,7 +1592,7 @@ int fileio(int op, char * filename, FILEFMT format, if (format != FMT_IMM) { if (!using_stdio) { - f = fopen_and_seek(fname, fio.mode, offset); + f = fopen_and_seek(fname, fio.mode, section); if (f == NULL) { avrdude_message(MSG_INFO, "%s: can't open %s file %s: %s\n", progname, fio.iodesc, fname, strerror(errno)); diff --git a/xs/src/avrdude/libavrdude.h b/xs/src/avrdude/libavrdude.h index 536f1a2f79..aef792476a 100644 --- a/xs/src/avrdude/libavrdude.h +++ b/xs/src/avrdude/libavrdude.h @@ -821,7 +821,7 @@ extern "C" { char * fmtstr(FILEFMT format); int fileio(int op, char * filename, FILEFMT format, - struct avrpart * p, char * memtype, int size, size_t offset); + struct avrpart * p, char * memtype, int size, unsigned section); #ifdef __cplusplus } @@ -870,7 +870,7 @@ enum updateflags { typedef struct update_t { char * memtype; int op; - size_t offset; + unsigned section; char * filename; int format; } UPDATE; @@ -882,7 +882,7 @@ extern "C" { extern UPDATE * parse_op(char * s); extern UPDATE * dup_update(UPDATE * upd); extern UPDATE * new_update(int op, char * memtype, int filefmt, - char * filename, size_t offset); + char * filename, unsigned section); extern void free_update(UPDATE * upd); extern int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags flags); diff --git a/xs/src/avrdude/main.c b/xs/src/avrdude/main.c index d4c34fe44c..5d73403b09 100644 --- a/xs/src/avrdude/main.c +++ b/xs/src/avrdude/main.c @@ -194,7 +194,7 @@ static void usage(void) " -F Override invalid signature check.\n" " -e Perform a chip erase.\n" " -O Perform RC oscillator calibration (see AVR053). \n" - " -U :r|w|v::[:format]\n" + " -U :r|w|v:

:[:format]\n" " Memory operation specification.\n" " Multiple -U options are allowed, each request\n" " is performed in the order specified.\n" diff --git a/xs/src/avrdude/update.c b/xs/src/avrdude/update.c index e9dd6e3251..417cbf71d4 100644 --- a/xs/src/avrdude/update.c +++ b/xs/src/avrdude/update.c @@ -101,22 +101,22 @@ UPDATE * parse_op(char * s) p++; - // Extension: Parse file contents offset - size_t offset = 0; + // Extension: Parse file section number + unsigned section = 0; for (; *p != ':'; p++) { if (*p >= '0' && *p <= '9') { - offset *= 10; - offset += *p - 0x30; + section *= 10; + section += *p - 0x30; } else { - avrdude_message(MSG_INFO, "%s: invalid update specification: offset is not a number\n", progname); + avrdude_message(MSG_INFO, "%s: invalid update specification:
is not a number\n", progname); free(upd->memtype); free(upd); return NULL; } } - upd->offset = offset; + upd->section = section; p++; /* @@ -194,7 +194,7 @@ UPDATE * dup_update(UPDATE * upd) return u; } -UPDATE * new_update(int op, char * memtype, int filefmt, char * filename, size_t offset) +UPDATE * new_update(int op, char * memtype, int filefmt, char * filename, unsigned section) { UPDATE * u; @@ -208,7 +208,7 @@ UPDATE * new_update(int op, char * memtype, int filefmt, char * filename, size_t u->filename = strdup(filename); u->op = op; u->format = filefmt; - u->offset = offset; + u->section = section; return u; } @@ -286,7 +286,7 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f progname, strcmp(upd->filename, "-")==0 ? "" : upd->filename); } - rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->offset); + rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->section); if (rc < 0) { avrdude_message(MSG_INFO, "%s: read from file '%s' failed\n", progname, upd->filename); @@ -351,7 +351,7 @@ int do_op(PROGRAMMER * pgm, struct avrpart * p, UPDATE * upd, enum updateflags f progname, mem->desc, upd->filename); } - rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->offset); + rc = fileio(FIO_READ, upd->filename, upd->format, p, upd->memtype, -1, upd->section); if (rc < 0) { avrdude_message(MSG_INFO, "%s: read from file '%s' failed\n", progname, upd->filename); diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index d747430554..17ff422450 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -95,7 +95,7 @@ struct FirmwareDialog::priv void find_serial_ports(); void flashing_start(bool flashing_l10n); void flashing_done(AvrDudeComplete complete); - size_t hex_lang_offset(const wxString &path); + size_t hex_num_sections(const wxString &path); void perform_upload(); void cancel(); void on_avrdude(const wxCommandEvent &evt); @@ -158,7 +158,7 @@ void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete) } } -size_t FirmwareDialog::priv::hex_lang_offset(const wxString &path) +size_t FirmwareDialog::priv::hex_num_sections(const wxString &path) { fs::ifstream file(fs::path(path.wx_str())); if (! file.good()) { @@ -175,18 +175,11 @@ size_t FirmwareDialog::priv::hex_lang_offset(const wxString &path) } if (line == hex_terminator) { - if (res == 0) { - // This is the first terminator seen, save the position - res = file.tellg(); - } else { - // We've found another terminator, return the offset just after the first one - // which is the start of the second 'section'. - return res; - } + res++; } } - return 0; + return res; } void FirmwareDialog::priv::perform_upload() @@ -202,10 +195,10 @@ void FirmwareDialog::priv::perform_upload() if (filename.IsEmpty() || port.empty()) { return; } const bool extra_verbose = false; // For debugging - const auto lang_offset = hex_lang_offset(filename); + const auto num_secions = hex_num_sections(filename); const auto filename_utf8 = filename.utf8_str(); - flashing_start(lang_offset > 0); + flashing_start(num_secions > 1); // It is ok here to use the q-pointer to the FirmwareDialog // because the dialog ensures it doesn't exit before the background thread is done. @@ -236,7 +229,7 @@ void FirmwareDialog::priv::perform_upload() avrdude.push_args(std::move(args)); - if (lang_offset > 0) { + if (num_secions > 1) { // The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy) // This is done via another avrdude invocation, here we build arg list for that: std::vector args_l10n {{ @@ -249,7 +242,7 @@ void FirmwareDialog::priv::perform_upload() "-b", "115200", "-D", "-u", // disable safe mode - "-U", (boost::format("flash:w:%1%:%2%:i") % lang_offset % filename_utf8.data()).str(), + "-U", (boost::format("flash:w:1:%1%:i") % filename_utf8.data()).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: " From 3c2170acf8406bbe26c923c5b0fefde3b4d7d8e8 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 27 Jun 2018 15:02:32 +0200 Subject: [PATCH 033/119] avrdude: Standalone binary --- xs/src/avrdude/CMakeLists.txt | 114 +++++++++++++++-------------- xs/src/avrdude/Makefile.standalone | 54 ++++++++++++++ xs/src/avrdude/main-standalone.c | 9 +++ 3 files changed, 124 insertions(+), 53 deletions(-) create mode 100644 xs/src/avrdude/Makefile.standalone create mode 100644 xs/src/avrdude/main-standalone.c diff --git a/xs/src/avrdude/CMakeLists.txt b/xs/src/avrdude/CMakeLists.txt index 043f8fb7b5..d885633689 100644 --- a/xs/src/avrdude/CMakeLists.txt +++ b/xs/src/avrdude/CMakeLists.txt @@ -1,3 +1,4 @@ +cmake_minimum_required(VERSION 3.0) add_definitions(-D_BSD_SOURCE -D_DEFAULT_SOURCE) # To enable various useful macros and functions on Unices @@ -13,67 +14,74 @@ endif() set(AVRDUDE_SOURCES - ${LIBDIR}/avrdude/arduino.c - ${LIBDIR}/avrdude/avr.c - # ${LIBDIR}/avrdude/avrftdi.c - # ${LIBDIR}/avrdude/avrftdi_tpi.c - ${LIBDIR}/avrdude/avrpart.c - ${LIBDIR}/avrdude/avr910.c - ${LIBDIR}/avrdude/bitbang.c - ${LIBDIR}/avrdude/buspirate.c - ${LIBDIR}/avrdude/butterfly.c - ${LIBDIR}/avrdude/config.c - ${LIBDIR}/avrdude/config_gram.c - # ${LIBDIR}/avrdude/confwin.c - ${LIBDIR}/avrdude/crc16.c - # ${LIBDIR}/avrdude/dfu.c - ${LIBDIR}/avrdude/fileio.c - # ${LIBDIR}/avrdude/flip1.c - # ${LIBDIR}/avrdude/flip2.c - # ${LIBDIR}/avrdude/ft245r.c - # ${LIBDIR}/avrdude/jtagmkI.c - # ${LIBDIR}/avrdude/jtagmkII.c - # ${LIBDIR}/avrdude/jtag3.c - ${LIBDIR}/avrdude/lexer.c - ${LIBDIR}/avrdude/linuxgpio.c - ${LIBDIR}/avrdude/lists.c - # ${LIBDIR}/avrdude/par.c - ${LIBDIR}/avrdude/pgm.c - ${LIBDIR}/avrdude/pgm_type.c - ${LIBDIR}/avrdude/pickit2.c - ${LIBDIR}/avrdude/pindefs.c - # ${LIBDIR}/avrdude/ppi.c - # ${LIBDIR}/avrdude/ppiwin.c - ${LIBDIR}/avrdude/safemode.c - ${LIBDIR}/avrdude/ser_avrdoper.c - ${LIBDIR}/avrdude/serbb_posix.c - ${LIBDIR}/avrdude/serbb_win32.c - ${LIBDIR}/avrdude/ser_posix.c - ${LIBDIR}/avrdude/ser_win32.c - ${LIBDIR}/avrdude/stk500.c - ${LIBDIR}/avrdude/stk500generic.c - ${LIBDIR}/avrdude/stk500v2.c - ${LIBDIR}/avrdude/term.c - ${LIBDIR}/avrdude/update.c - # ${LIBDIR}/avrdude/usbasp.c - # ${LIBDIR}/avrdude/usb_hidapi.c - # ${LIBDIR}/avrdude/usb_libusb.c - # ${LIBDIR}/avrdude/usbtiny.c - ${LIBDIR}/avrdude/wiring.c + arduino.c + avr.c + # avrftdi.c + # avrftdi_tpi.c + avrpart.c + avr910.c + bitbang.c + buspirate.c + butterfly.c + config.c + config_gram.c + # confwin.c + crc16.c + # dfu.c + fileio.c + # flip1.c + # flip2.c + # ft245r.c + # jtagmkI.c + # jtagmkII.c + # jtag3.c + lexer.c + linuxgpio.c + lists.c + # par.c + pgm.c + pgm_type.c + pickit2.c + pindefs.c + # ppi.c + # ppiwin.c + safemode.c + ser_avrdoper.c + serbb_posix.c + serbb_win32.c + ser_posix.c + ser_win32.c + stk500.c + stk500generic.c + stk500v2.c + term.c + update.c + # usbasp.c + # usb_hidapi.c + # usb_libusb.c + # usbtiny.c + wiring.c - ${LIBDIR}/avrdude/main.c - ${LIBDIR}/avrdude/avrdude-slic3r.hpp - ${LIBDIR}/avrdude/avrdude-slic3r.cpp + main.c + avrdude-slic3r.hpp + avrdude-slic3r.cpp ) if (WIN32) set(AVRDUDE_SOURCES ${AVRDUDE_SOURCES} - ${LIBDIR}/avrdude/windows/unistd.cpp - ${LIBDIR}/avrdude/windows/getopt.c + windows/unistd.cpp + windows/getopt.c ) endif() add_library(avrdude STATIC ${AVRDUDE_SOURCES}) +set(STANDALONE_SOURCES + main-standalone.c +) +add_executable(avrdude-slic3r ${STANDALONE_SOURCES}) +target_link_libraries(avrdude-slic3r avrdude) +set_target_properties(avrdude-slic3r PROPERTIES EXCLUDE_FROM_ALL TRUE) + if (WIN32) target_compile_definitions(avrdude PRIVATE WIN32NATIVE=1) - target_include_directories(avrdude SYSTEM PRIVATE ${LIBDIR}/avrdude/windows) # So that sources find the getopt.h windows drop-in + target_include_directories(avrdude SYSTEM PRIVATE windows) # So that sources find the getopt.h windows drop-in endif() diff --git a/xs/src/avrdude/Makefile.standalone b/xs/src/avrdude/Makefile.standalone new file mode 100644 index 0000000000..d9a773771e --- /dev/null +++ b/xs/src/avrdude/Makefile.standalone @@ -0,0 +1,54 @@ + +TARGET = avrdude-slic3r + +SOURCES = \ + arduino.c \ + avr.c \ + avrpart.c \ + avr910.c \ + bitbang.c \ + buspirate.c \ + butterfly.c \ + config.c \ + config_gram.c \ + crc16.c \ + fileio.c \ + lexer.c \ + linuxgpio.c \ + lists.c \ + pgm.c \ + pgm_type.c \ + pickit2.c \ + pindefs.c \ + safemode.c \ + ser_avrdoper.c \ + serbb_posix.c \ + serbb_win32.c \ + ser_posix.c \ + ser_win32.c \ + stk500.c \ + stk500generic.c \ + stk500v2.c \ + term.c \ + update.c \ + wiring.c \ + main.c \ + main-standalone.c + +OBJECTS = $(SOURCES:.c=.o) +CFLAGS = -std=c99 -Wall -D_BSD_SOURCE -D_DEFAULT_SOURCE -O3 -DNDEBUG -fPIC +LDFLAGS = -lm + +CC = gcc +RM = rm + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) -o ./$@ $(OBJECTS) $(LDFLAGS) + +$(OBJECTS): %.o: %.c + $(CC) $(CFLAGS) -o $@ -c $< + +clean: + $(RM) -f $(OBJECTS) $(TARGET) diff --git a/xs/src/avrdude/main-standalone.c b/xs/src/avrdude/main-standalone.c new file mode 100644 index 0000000000..359a055ca8 --- /dev/null +++ b/xs/src/avrdude/main-standalone.c @@ -0,0 +1,9 @@ +#include "avrdude.h" + + +static const char* SYS_CONFIG = "/etc/avrdude-slic3r.conf"; + +int main(int argc, char *argv[]) +{ + return avrdude_main(argc, argv, SYS_CONFIG); +} From a7eaf38853c1c804c4f4c7d5e30801f196713727 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 24 Jul 2018 17:41:57 +0200 Subject: [PATCH 034/119] Utils: Serial port printer communication abstraction --- xs/src/libslic3r/GCodeSender.cpp | 17 +-- xs/src/slic3r/Utils/Serial.cpp | 238 +++++++++++++++++++++++++++++++ xs/src/slic3r/Utils/Serial.hpp | 52 ++++++- 3 files changed, 294 insertions(+), 13 deletions(-) diff --git a/xs/src/libslic3r/GCodeSender.cpp b/xs/src/libslic3r/GCodeSender.cpp index c3530e00f7..0988091ce3 100644 --- a/xs/src/libslic3r/GCodeSender.cpp +++ b/xs/src/libslic3r/GCodeSender.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -568,16 +569,12 @@ GCodeSender::set_DTR(bool on) void GCodeSender::reset() { - this->set_DTR(false); - boost::this_thread::sleep(boost::posix_time::milliseconds(200)); - this->set_DTR(true); - boost::this_thread::sleep(boost::posix_time::milliseconds(200)); - this->set_DTR(false); - boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); - { - boost::lock_guard l(this->queue_mutex); - this->can_send = true; - } + set_DTR(false); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + set_DTR(true); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + set_DTR(false); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); } } // namespace Slic3r diff --git a/xs/src/slic3r/Utils/Serial.cpp b/xs/src/slic3r/Utils/Serial.cpp index 32b6bb160c..8a454b1195 100644 --- a/xs/src/slic3r/Utils/Serial.cpp +++ b/xs/src/slic3r/Utils/Serial.cpp @@ -3,7 +3,10 @@ #include #include #include +#include +#include #include +#include #include #include @@ -34,6 +37,21 @@ #include #endif +#ifndef _WIN32 + #include + #include + #include + #include +#endif + +#if defined(__APPLE__) || defined(__OpenBSD__) + #include +#elif defined __linux__ + #include + #include +#endif + + namespace Slic3r { namespace Utils { @@ -189,5 +207,225 @@ std::vector scan_serial_ports() return output; } + + +// Class Serial + +namespace asio = boost::asio; +using boost::system::error_code; + +Serial::Serial(asio::io_service& io_service) : + asio::serial_port(io_service) +{} + +Serial::Serial(asio::io_service& io_service, const std::string &name, unsigned baud_rate) : + asio::serial_port(io_service, name) +{ + printer_setup(baud_rate); +} + +Serial::~Serial() {} + +void Serial::set_baud_rate(unsigned baud_rate) +{ + try { + // This does not support speeds > 115200 + set_option(boost::asio::serial_port_base::baud_rate(baud_rate)); + } catch (boost::system::system_error &) { + auto handle = native_handle(); + + auto handle_errno = [](int retval) { + if (retval != 0) { + throw std::runtime_error( + (boost::format("Could not set baud rate: %1%") % strerror(errno)).str() + ); + } + }; + +#if __APPLE__ + termios ios; + handle_errno(::tcgetattr(handle, &ios)); + handle_errno(::cfsetspeed(&ios, baud_rate)); + speed_t newSpeed = baud_rate; + handle_errno(::ioctl(handle, IOSSIOSPEED, &newSpeed)); + handle_errno(::tcsetattr(handle, TCSANOW, &ios)); +#elif __linux + + /* The following definitions are kindly borrowed from: + /usr/include/asm-generic/termbits.h + Unfortunately we cannot just include that one because + it would redefine the "struct termios" already defined + the already included by Boost.ASIO. */ +#define K_NCCS 19 + struct termios2 { + tcflag_t c_iflag; + tcflag_t c_oflag; + tcflag_t c_cflag; + tcflag_t c_lflag; + cc_t c_line; + cc_t c_cc[K_NCCS]; + speed_t c_ispeed; + speed_t c_ospeed; + }; +#define BOTHER CBAUDEX + + termios2 ios; + handle_errno(::ioctl(handle, TCGETS2, &ios)); + ios.c_ispeed = ios.c_ospeed = baud_rate; + ios.c_cflag &= ~CBAUD; + ios.c_cflag |= BOTHER | CLOCAL | CREAD; + ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read + ios.c_cc[VTIME] = 1; + handle_errno(::ioctl(handle, TCSETS2, &ios)); + +#elif __OpenBSD__ + struct termios ios; + handle_errno(::tcgetattr(handle, &ios)); + handle_errno(::cfsetspeed(&ios, baud_rate)); + handle_errno(::tcsetattr(handle, TCSAFLUSH, &ios)); +#else + throw std::runtime_error("Custom baud rates are not currently supported on this OS"); +#endif + } +} + +void Serial::set_DTR(bool on) +{ + auto handle = native_handle(); +#if defined(_WIN32) && !defined(__SYMBIAN32__) + if (! EscapeCommFunction(handle, on ? SETDTR : CLRDTR)) { + throw std::runtime_error("Could not set serial port DTR"); + } +#else + int status; + if (::ioctl(handle, TIOCMGET, &status) == 0) { + on ? status |= TIOCM_DTR : status &= ~TIOCM_DTR; + if (::ioctl(handle, TIOCMSET, &status) == 0) { + return; + } + } + + throw std::runtime_error( + (boost::format("Could not set serial port DTR: %1%") % strerror(errno)).str() + ); +#endif +} + +void Serial::reset_line_num() +{ + // See https://github.com/MarlinFirmware/Marlin/wiki/M110 + printer_write_line("M110 N0", 0); + m_line_num = 0; +} + +bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec) +{ + auto &io_service = get_io_service(); + asio::deadline_timer timer(io_service); + char c = 0; + bool fail = false; + + while (true) { + io_service.reset(); + + asio::async_read(*this, boost::asio::buffer(&c, 1), [&](const error_code &read_ec, size_t size) { + if (ec || size == 0) { + fail = true; + ec = read_ec; + } + timer.cancel(); + }); + + if (timeout > 0) { + timer.expires_from_now(boost::posix_time::milliseconds(timeout)); + timer.async_wait([&](const error_code &ec) { + // Ignore timer aborts + if (!ec) { + fail = true; + this->cancel(); + } + }); + } + + io_service.run(); + + if (fail) { + return false; + } else if (c != '\n') { + line += c; + } else { + return true; + } + } +} + +void Serial::printer_setup(unsigned baud_rate) +{ + set_baud_rate(baud_rate); + printer_reset(); + write_string("\r\r\r\r\r\r\r\r\r\r"); // Gets rid of line noise, if any +} + +size_t Serial::write_string(const std::string &str) +{ + // TODO: might be wise to timeout here as well + return asio::write(*this, asio::buffer(str)); +} + +bool Serial::printer_ready_wait(unsigned retries, unsigned timeout) +{ + std::string line; + error_code ec; + + for (; retries > 0; retries--) { + reset_line_num(); + + while (read_line(timeout, line, ec)) { + if (line == "ok") { + return true; + } + line.clear(); + } + line.clear(); + } + + return false; +} + +size_t Serial::printer_write_line(const std::string &line, unsigned line_num) +{ + const auto formatted_line = Utils::Serial::printer_format_line(line, line_num); + return write_string(formatted_line); +} + +size_t Serial::printer_write_line(const std::string &line) +{ + m_line_num++; + return printer_write_line(line, m_line_num); +} + +void Serial::printer_reset() +{ + this->set_DTR(false); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + this->set_DTR(true); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + this->set_DTR(false); + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); +} + +std::string Serial::printer_format_line(const std::string &line, unsigned line_num) +{ + const auto line_num_str = std::to_string(line_num); + + unsigned checksum = 'N'; + for (auto c : line_num_str) { checksum ^= c; } + checksum ^= ' '; + for (auto c : line) { checksum ^= c; } + + return (boost::format("N%1% %2%*%3%\n") % line_num_str % line % checksum).str(); +} + + } // namespace Utils } // namespace Slic3r diff --git a/xs/src/slic3r/Utils/Serial.hpp b/xs/src/slic3r/Utils/Serial.hpp index 9381ab3987..5df33916f0 100644 --- a/xs/src/slic3r/Utils/Serial.hpp +++ b/xs/src/slic3r/Utils/Serial.hpp @@ -1,10 +1,11 @@ #ifndef slic3r_GUI_Utils_Serial_hpp_ #define slic3r_GUI_Utils_Serial_hpp_ -#include #include #include -#include +#include +#include + namespace Slic3r { namespace Utils { @@ -16,7 +17,7 @@ struct SerialPortInfo { bool is_printer = false; }; -inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2) +inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2) { return sp1.port == sp2.port && sp1.hardware_id == sp2.hardware_id && @@ -26,6 +27,51 @@ inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2) extern std::vector scan_serial_ports(); extern std::vector scan_serial_ports_extended(); + +class Serial : public boost::asio::serial_port +{ +public: + Serial(boost::asio::io_service &io_service); + // This c-tor opens the port for communication with a printer - it sets a baud rate and calls printer_reset() + Serial(boost::asio::io_service &io_service, const std::string &name, unsigned baud_rate); + Serial(const Serial &) = delete; + Serial &operator=(const Serial &) = delete; + ~Serial(); + + void set_baud_rate(unsigned baud_rate); + void set_DTR(bool on); + + // Resets the line number both internally as well as with the firmware using M110 + void reset_line_num(); + + // Reads a line or times out, the timeout is in milliseconds + bool read_line(unsigned timeout, std::string &line, boost::system::error_code &ec); + + // Perform setup for communicating with a printer + // Sets a baud rate and calls printer_reset() + void printer_setup(unsigned baud_rate); + + // Write data from a string + size_t write_string(const std::string &str); + + bool printer_ready_wait(unsigned retries, unsigned timeout); + + // Write Marlin-formatted line, with a line number and a checksum + size_t printer_write_line(const std::string &line, unsigned line_num); + + // Same as above, but with internally-managed line number + size_t printer_write_line(const std::string &line); + + // Toggles DTR to reset the printer + void printer_reset(); + + // Formats a line Marlin-style, ie. with a sequential number and a checksum + static std::string printer_format_line(const std::string &line, unsigned line_num); +private: + unsigned m_line_num = 0; +}; + + } // Utils } // Slic3r From a32bd17b752317450e35c398b61356cdbe13998d Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 24 Jul 2018 17:42:12 +0200 Subject: [PATCH 035/119] FirmwareUpdater: MMU 2.0 / Caterina flashing --- xs/CMakeLists.txt | 4 +- xs/src/avrdude/avrdude-slic3r.cpp | 15 +- xs/src/avrdude/avrdude-slic3r.hpp | 5 +- xs/src/slic3r/GUI/FirmwareDialog.cpp | 339 ++++++++++++++++++++------- xs/src/slic3r/Utils/HexFile.cpp | 107 +++++++++ xs/src/slic3r/Utils/HexFile.hpp | 32 +++ xs/src/slic3r/Utils/Serial.cpp | 84 ++++++- xs/src/slic3r/Utils/Serial.hpp | 19 +- 8 files changed, 496 insertions(+), 109 deletions(-) create mode 100644 xs/src/slic3r/Utils/HexFile.cpp create mode 100644 xs/src/slic3r/Utils/HexFile.hpp diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 976943d64d..95ff990aae 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -26,7 +26,7 @@ include_directories(${LIBDIR}/libslic3r) if(WIN32) # BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking. - add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB) + add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x601) # -D_ITERATOR_DEBUG_LEVEL) if(WIN10SDK_PATH) message("Building with Win10 Netfabb STL fixing service support") @@ -258,6 +258,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/Utils/PresetUpdater.hpp ${LIBDIR}/slic3r/Utils/Time.cpp ${LIBDIR}/slic3r/Utils/Time.hpp + ${LIBDIR}/slic3r/Utils/HexFile.cpp + ${LIBDIR}/slic3r/Utils/HexFile.hpp ${LIBDIR}/slic3r/IProgressIndicator.hpp ${LIBDIR}/slic3r/AppController.hpp ${LIBDIR}/slic3r/AppController.cpp diff --git a/xs/src/avrdude/avrdude-slic3r.cpp b/xs/src/avrdude/avrdude-slic3r.cpp index 030353413c..4a7f22d6e5 100644 --- a/xs/src/avrdude/avrdude-slic3r.cpp +++ b/xs/src/avrdude/avrdude-slic3r.cpp @@ -36,6 +36,7 @@ struct AvrDude::priv std::string sys_config; std::deque> args; size_t current_args_set = 0; + bool cancelled = false; RunFn run_fn; MessageFn message_fn; ProgressFn progress_fn; @@ -141,11 +142,16 @@ AvrDude::Ptr AvrDude::run() if (self->p) { auto avrdude_thread = std::thread([self]() { + bool cancel = false; + int res = -1; + if (self->p->run_fn) { - self->p->run_fn(); + self->p->run_fn(*self); } - auto res = self->p->run(); + if (! self->p->cancelled) { + res = self->p->run(); + } if (self->p->complete_fn) { self->p->complete_fn(res, self->p->current_args_set); @@ -160,7 +166,10 @@ AvrDude::Ptr AvrDude::run() void AvrDude::cancel() { - ::avrdude_cancel(); + if (p) { + p->cancelled = true; + ::avrdude_cancel(); + } } void AvrDude::join() diff --git a/xs/src/avrdude/avrdude-slic3r.hpp b/xs/src/avrdude/avrdude-slic3r.hpp index 273aa23782..399df2358f 100644 --- a/xs/src/avrdude/avrdude-slic3r.hpp +++ b/xs/src/avrdude/avrdude-slic3r.hpp @@ -12,7 +12,7 @@ class AvrDude { public: typedef std::shared_ptr Ptr; - typedef std::function RunFn; + typedef std::function RunFn; typedef std::function MessageFn; typedef std::function ProgressFn; typedef std::function CompleteFn; @@ -31,7 +31,8 @@ public: AvrDude& push_args(std::vector args); // Set a callback to be called just after run() before avrdude is ran - // This can be used to perform any needed setup tasks from the background thread. + // This can be used to perform any needed setup tasks from the background thread, + // and, optionally, to cancel by writing true to the `cancel` argument. // This has no effect when using run_sync(). AvrDude& on_run(RunFn fn); diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index 17ff422450..38c2937fc5 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -1,12 +1,23 @@ -#include "FirmwareDialog.hpp" - #include #include +#include +#include #include +#include #include #include #include +#include "libslic3r/Utils.hpp" +#include "avrdude/avrdude-slic3r.hpp" +#include "GUI.hpp" +#include "MsgDialog.hpp" +#include "../Utils/HexFile.hpp" +#include "../Utils/Serial.hpp" + +// wx includes need to come after asio because of the WinSock.h problem +#include "FirmwareDialog.hpp" + #include #include #include @@ -22,16 +33,18 @@ #include #include -#include "libslic3r/Utils.hpp" -#include "avrdude/avrdude-slic3r.hpp" -#include "GUI.hpp" -#include "../Utils/Serial.hpp" namespace fs = boost::filesystem; +namespace asio = boost::asio; +using boost::system::error_code; namespace Slic3r { +using Utils::HexFile; +using Utils::SerialPortInfo; +using Utils::Serial; + // This enum discriminates the kind of information in EVT_AVRDUDE, // it's stored in the ExtraLong field of wxCommandEvent. @@ -40,6 +53,7 @@ enum AvrdudeEvent AE_MESSAGE, AE_PROGRESS, AE_EXIT, + AE_ERROR, }; wxDECLARE_EVENT(EVT_AVRDUDE, wxCommandEvent); @@ -52,7 +66,6 @@ struct FirmwareDialog::priv { enum AvrDudeComplete { - AC_NONE, AC_SUCCESS, AC_FAILURE, AC_CANCEL, @@ -61,7 +74,7 @@ struct FirmwareDialog::priv FirmwareDialog *q; // PIMPL back pointer ("Q-Pointer") wxComboBox *port_picker; - std::vector ports; + std::vector ports; wxFilePickerCtrl *hex_picker; wxStaticText *txt_status; wxGauge *progressbar; @@ -80,7 +93,9 @@ struct FirmwareDialog::priv AvrDude::Ptr avrdude; std::string avrdude_config; unsigned progress_tasks_done; + unsigned progress_tasks_bar; bool cancelled; + const bool extra_verbose; // For debugging priv(FirmwareDialog *q) : q(q), @@ -89,16 +104,25 @@ struct FirmwareDialog::priv timer_pulse(q), avrdude_config((fs::path(::Slic3r::resources_dir()) / "avrdude" / "avrdude.conf").string()), progress_tasks_done(0), - cancelled(false) + progress_tasks_bar(0), + cancelled(false), + extra_verbose(false) {} void find_serial_ports(); - void flashing_start(bool flashing_l10n); + void flashing_start(unsigned tasks); void flashing_done(AvrDudeComplete complete); - size_t hex_num_sections(const wxString &path); + void check_model_id(const HexFile &metadata, const SerialPortInfo &port); + + void prepare_common(AvrDude &, const SerialPortInfo &port); + void prepare_mk2(AvrDude &, const SerialPortInfo &port); + void prepare_mk3(AvrDude &, const SerialPortInfo &port); + void prepare_mm_control(AvrDude &, const SerialPortInfo &port); void perform_upload(); + void cancel(); void on_avrdude(const wxCommandEvent &evt); + void ensure_joined(); }; void FirmwareDialog::priv::find_serial_ports() @@ -122,7 +146,7 @@ void FirmwareDialog::priv::find_serial_ports() } } -void FirmwareDialog::priv::flashing_start(bool flashing_l10n) +void FirmwareDialog::priv::flashing_start(unsigned tasks) { txt_stdout->Clear(); txt_status->SetLabel(_(L("Flashing in progress. Please do not disconnect the printer!"))); @@ -132,9 +156,10 @@ void FirmwareDialog::priv::flashing_start(bool flashing_l10n) hex_picker->Disable(); btn_close->Disable(); btn_flash->SetLabel(btn_flash_label_flashing); - progressbar->SetRange(flashing_l10n ? 500 : 200); // See progress callback below + progressbar->SetRange(200 * tasks); // See progress callback below progressbar->SetValue(0); progress_tasks_done = 0; + progress_tasks_bar = 0; cancelled = false; timer_pulse.Start(50); } @@ -158,56 +183,51 @@ void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete) } } -size_t FirmwareDialog::priv::hex_num_sections(const wxString &path) +void FirmwareDialog::priv::check_model_id(const HexFile &metadata, const SerialPortInfo &port) { - fs::ifstream file(fs::path(path.wx_str())); - if (! file.good()) { - return 0; + if (metadata.model_id.empty()) { + // No data to check against + return; + } + + asio::io_service io; + Serial serial(io, port.port, 115200); + serial.printer_setup(); + + enum { + TIMEOUT = 1000, + RETREIES = 3, + }; + + if (! serial.printer_ready_wait(RETREIES, TIMEOUT)) { + throw wxString::Format(_(L("Could not connect to the printer at %s")), port.port); } - static const char *hex_terminator = ":00000001FF\r"; - size_t res = 0; std::string line; - while (getline(file, line, '\n').good()) { - // Account for LF vs CRLF - if (!line.empty() && line.back() != '\r') { - line.push_back('\r'); + error_code ec; + serial.printer_write_line("PRUSA Rev"); + while (serial.read_line(TIMEOUT, line, ec)) { + if (ec) { throw wxString::Format(_(L("Could not connect to the printer at %s")), port.port); } + if (line == "ok") { continue; } + + if (line == metadata.model_id) { + return; + } else { + throw wxString::Format(_(L( + "The firmware hex file does not match the printer model.\n" + "The hex file is intended for:\n %s\n" + "Printer reports:\n %s" + )), metadata.model_id, line); } - if (line == hex_terminator) { - res++; - } + line.clear(); } - - return res; } -void FirmwareDialog::priv::perform_upload() +void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo &port) { auto filename = hex_picker->GetPath(); - std::string port = port_picker->GetValue().ToStdString(); - int selection = port_picker->GetSelection(); - if (selection != -1) { - // Verify whether the combo box list selection equals to the combo box edit value. - if (this->ports[selection].friendly_name == port) - port = this->ports[selection].port; - } - if (filename.IsEmpty() || port.empty()) { return; } - const bool extra_verbose = false; // For debugging - const auto num_secions = hex_num_sections(filename); - const auto filename_utf8 = filename.utf8_str(); - - flashing_start(num_secions > 1); - - // It is ok here to use the q-pointer to the FirmwareDialog - // because the dialog ensures it doesn't exit before the background thread is done. - auto q = this->q; - - // Init the avrdude object - AvrDude avrdude(avrdude_config); - - // Build argument list(s) std::vector args {{ extra_verbose ? "-vvvvv" : "-v", "-p", "atmega2560", @@ -215,11 +235,10 @@ void FirmwareDialog::priv::perform_upload() // The Prusa's avrdude is patched to never send semicolons inside the data packets, as the USB to serial chip // is flashed with a buggy firmware. "-c", "wiring", - "-P", port, + "-P", port.port, "-b", "115200", // TODO: Allow other rates? Ditto below. "-D", - // XXX: Safe mode? - "-U", (boost::format("flash:w:0:%1%:i") % filename_utf8.data()).str(), + "-U", (boost::format("flash:w:0:%1%:i") % filename.utf8_str().data()).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " @@ -228,32 +247,172 @@ void FirmwareDialog::priv::perform_upload() }); avrdude.push_args(std::move(args)); - - if (num_secions > 1) { - // The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy) - // This is done via another avrdude invocation, here we build arg list for that: - std::vector args_l10n {{ - extra_verbose ? "-vvvvv" : "-v", - "-p", "atmega2560", - // Using the "Arduino" mode to program Einsy's external flash with languages, using the STK500 protocol (not the STK500v2). - // The Prusa's avrdude is patched again to never send semicolons inside the data packets. - "-c", "arduino", - "-P", port, - "-b", "115200", - "-D", - "-u", // disable safe mode - "-U", (boost::format("flash:w:1:%1%:i") % filename_utf8.data()).str(), - }}; +} - BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: " - << std::accumulate(std::next(args_l10n.begin()), args_l10n.end(), args_l10n[0], [](std::string a, const std::string &b) { - return a + ' ' + b; - }); +void FirmwareDialog::priv::prepare_mk2(AvrDude &avrdude, const SerialPortInfo &port) +{ + flashing_start(1); + prepare_common(avrdude, port); +} - avrdude.push_args(std::move(args_l10n)); +void FirmwareDialog::priv::prepare_mk3(AvrDude &avrdude, const SerialPortInfo &port) +{ + flashing_start(2); + prepare_common(avrdude, port); + + auto filename = hex_picker->GetPath(); + + // The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy) + // This is done via another avrdude invocation, here we build arg list for that: + std::vector args_l10n {{ + extra_verbose ? "-vvvvv" : "-v", + "-p", "atmega2560", + // Using the "Arduino" mode to program Einsy's external flash with languages, using the STK500 protocol (not the STK500v2). + // The Prusa's avrdude is patched again to never send semicolons inside the data packets. + "-c", "arduino", + "-P", port.port, + "-b", "115200", + "-D", + "-u", // disable safe mode + "-U", (boost::format("flash:w:1:%1%:i") % filename.utf8_str().data()).str(), + }}; + + BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: " + << std::accumulate(std::next(args_l10n.begin()), args_l10n.end(), args_l10n[0], [](std::string a, const std::string &b) { + return a + ' ' + b; + }); + + avrdude.push_args(std::move(args_l10n)); +} + +void FirmwareDialog::priv::prepare_mm_control(AvrDude &avrdude, const SerialPortInfo &port_in) +{ + // Check if the port has the PID/VID of 0x2c99/3 + // If not, check if it is the MMU (0x2c99/4) and reboot the by opening @ 1200 bauds + BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ..."; + SerialPortInfo port = port_in; + if (! port.id_match(0x2c99, 3)) { + if (! port.id_match(0x2c99, 4)) { + // This is not a Prusa MMU 2.0 device + BOOST_LOG_TRIVIAL(error) << boost::format("Not a Prusa MMU 2.0 device: `%1%`") % port.port; + throw wxString::Format(_(L("The device at `%s` is not am Original Prusa i3 MMU 2.0 device")), port.port); + } + + BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % port.port; + + { + asio::io_service io; + Serial serial(io, port.port, 1200); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + // Wait for the bootloader to show up + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + + // Look for the rebooted device + BOOST_LOG_TRIVIAL(info) << "... looking for VID/PID 0x2c99/3 ..."; + auto new_ports = Utils::scan_serial_ports_extended(); + unsigned hits = 0; + for (auto &&new_port : new_ports) { + if (new_port.id_match(0x2c99, 3)) { + hits++; + port = std::move(new_port); + } + } + + if (hits == 0) { + BOOST_LOG_TRIVIAL(error) << "No VID/PID 0x2c99/3 device found after rebooting the MMU 2.0"; + throw wxString::Format(_(L("Failed to reboot the device at `%s` for programming")), port.port); + } else if (hits > 1) { + // We found multiple 0x2c99/3 devices, this is bad, because there's no way to find out + // which one is the one user wants to flash. + BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found after rebooting the MMU 2.0"; + throw wxString::Format(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")), port.port); + } } - + + BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port.port; + + auto filename = hex_picker->GetPath(); + + std::vector args {{ + extra_verbose ? "-vvvvv" : "-v", + "-p", "atmega32u4", + "-c", "avr109", + "-P", port.port, + "-b", "57600", + "-D", + "-U", (boost::format("flash:w:0:%1%:i") % filename.utf8_str().data()).str(), + }}; + + BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " + << std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) { + return a + ' ' + b; + }); + + avrdude.push_args(std::move(args)); +} + + +void FirmwareDialog::priv::perform_upload() +{ + auto filename = hex_picker->GetPath(); + if (filename.IsEmpty()) { return; } + + int selection = port_picker->GetSelection(); + if (selection == wxNOT_FOUND) { return; } + + std::string port_selected = port_picker->GetValue().ToStdString(); + const SerialPortInfo &port = this->ports[selection]; + // Verify whether the combo box list selection equals to the combo box edit value. + if (this->ports[selection].friendly_name != port_selected) { return; } + + const bool extra_verbose = false; // For debugging + HexFile metadata(filename.wx_str()); + // const auto filename_utf8 = filename.utf8_str(); + + flashing_start(metadata.device == HexFile::DEV_MK3 ? 2 : 1); + + // Init the avrdude object + AvrDude avrdude(avrdude_config); + + // It is ok here to use the q-pointer to the FirmwareDialog + // because the dialog ensures it doesn't exit before the background thread is done. + auto q = this->q; + this->avrdude = avrdude + .on_run([this, metadata, port](AvrDude &avrdude) { + auto queue_error = [&](wxString message) { + avrdude.cancel(); + + auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId()); + evt->SetExtraLong(AE_ERROR); + evt->SetString(std::move(message)); + wxQueueEvent(this->q, evt); + }; + + try { + switch (metadata.device) { + case HexFile::DEV_MK3: + this->check_model_id(metadata, port); + this->prepare_mk3(avrdude, port); + break; + + case HexFile::DEV_MM_CONTROL: + this->check_model_id(metadata, port); + this->prepare_mm_control(avrdude, port); + break; + + default: + this->prepare_mk2(avrdude, port); + break; + } + } catch (const wxString &message) { + queue_error(message); + } catch (const std::exception &ex) { + queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port.port, ex.what())); + } + }) .on_message(std::move([q, extra_verbose](const char *msg, unsigned /* size */) { if (extra_verbose) { BOOST_LOG_TRIVIAL(debug) << "avrdude: " << msg; @@ -306,12 +465,15 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) // and then display overall progress during the latter tasks. if (progress_tasks_done > 0) { - progressbar->SetValue(progress_tasks_done - 100 + evt.GetInt()); + progressbar->SetValue(progress_tasks_bar + evt.GetInt()); } if (evt.GetInt() == 100) { timer_pulse.Stop(); - progress_tasks_done += 100; + if (progress_tasks_done % 3 != 0) { + progress_tasks_bar += 100; + } + progress_tasks_done++; } break; @@ -321,11 +483,17 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) complete_kind = cancelled ? AC_CANCEL : (evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE); flashing_done(complete_kind); + ensure_joined(); + break; - // Make sure the background thread is collected and the AvrDude object reset - if (avrdude) { avrdude->join(); } - avrdude.reset(); - + case AE_ERROR: + txt_stdout->AppendText(evt.GetString()); + flashing_done(AC_FAILURE); + ensure_joined(); + { + GUI::ErrorDialog dlg(this->q, evt.GetString()); + dlg.ShowModal(); + } break; default: @@ -333,6 +501,13 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) } } +void FirmwareDialog::priv::ensure_joined() +{ + // Make sure the background thread is collected and the AvrDude object reset + if (avrdude) { avrdude->join(); } + avrdude.reset(); +} + // Public diff --git a/xs/src/slic3r/Utils/HexFile.cpp b/xs/src/slic3r/Utils/HexFile.cpp new file mode 100644 index 0000000000..ed26ddf37e --- /dev/null +++ b/xs/src/slic3r/Utils/HexFile.cpp @@ -0,0 +1,107 @@ +#include "HexFile.hpp" + +#include +#include +#include +#include + +namespace fs = boost::filesystem; +namespace pt = boost::property_tree; + + +namespace Slic3r { +namespace Utils { + + +static HexFile::DeviceKind parse_device_kind(const std::string &str) +{ + if (str == "mk2") { return HexFile::DEV_MK2; } + else if (str == "mk3") { return HexFile::DEV_MK3; } + else if (str == "mm-control") { return HexFile::DEV_MM_CONTROL; } + else { return HexFile::DEV_GENERIC; } +} + +static size_t hex_num_sections(fs::ifstream &file) +{ + file.seekg(0); + if (! file.good()) { + return 0; + } + + static const char *hex_terminator = ":00000001FF\r"; + size_t res = 0; + std::string line; + while (getline(file, line, '\n').good()) { + // Account for LF vs CRLF + if (!line.empty() && line.back() != '\r') { + line.push_back('\r'); + } + + if (line == hex_terminator) { + res++; + } + } + + return res; +} + +HexFile::HexFile(fs::path path) : + path(std::move(path)), + device(DEV_GENERIC) +{ + fs::ifstream file(this->path); + if (! file.good()) { + return; + } + + std::string line; + std::stringstream header_ini; + while (std::getline(file, line, '\n').good()) { + if (line.empty()) { + continue; + } + + // Account for LF vs CRLF + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + + if (line.front() == ';') { + line.front() = ' '; + header_ini << line << std::endl; + } else if (line.front() == ':') { + break; + } + } + + pt::ptree ptree; + try { + pt::read_ini(header_ini, ptree); + } catch (std::exception &e) { + return; + } + + bool has_device_meta = false; + const auto device = ptree.find("device"); + if (device != ptree.not_found()) { + this->device = parse_device_kind(device->second.data()); + has_device_meta = true; + } + + const auto model_id = ptree.find("model_id"); + if (model_id != ptree.not_found()) { + this->model_id = model_id->second.data(); + } + + if (! has_device_meta) { + // No device metadata, look at the number of 'sections' + if (hex_num_sections(file) == 2) { + // Looks like a pre-metadata l10n firmware for the MK3, assume that's the case + this->device = DEV_MK3; + } + } +} + + +} +} diff --git a/xs/src/slic3r/Utils/HexFile.hpp b/xs/src/slic3r/Utils/HexFile.hpp new file mode 100644 index 0000000000..d8d1e09abd --- /dev/null +++ b/xs/src/slic3r/Utils/HexFile.hpp @@ -0,0 +1,32 @@ +#ifndef slic3r_Hex_hpp_ +#define slic3r_Hex_hpp_ + +#include +#include + + +namespace Slic3r { +namespace Utils { + + +struct HexFile +{ + enum DeviceKind { + DEV_GENERIC, + DEV_MK2, + DEV_MK3, + DEV_MM_CONTROL, + }; + + boost::filesystem::path path; + DeviceKind device; + std::string model_id; + + HexFile(boost::filesystem::path path); +}; + + +} +} + +#endif diff --git a/xs/src/slic3r/Utils/Serial.cpp b/xs/src/slic3r/Utils/Serial.cpp index 8a454b1195..c3c16b314e 100644 --- a/xs/src/slic3r/Utils/Serial.cpp +++ b/xs/src/slic3r/Utils/Serial.cpp @@ -11,12 +11,14 @@ #include #include #include +#include #if _WIN32 #include #include #include #include + #include // Undefine min/max macros incompatible with the standard library // For example, std::numeric_limits::max() // produces some weird errors @@ -51,6 +53,8 @@ #include #endif +using boost::optional; + namespace Slic3r { namespace Utils { @@ -60,15 +64,43 @@ static bool looks_like_printer(const std::string &friendly_name) return friendly_name.find("Original Prusa") != std::string::npos; } -#ifdef __linux__ -static std::string get_tty_friendly_name(const std::string &path, const std::string &name) +#if _WIN32 +void parse_hardware_id(const std::string &hardware_id, SerialPortInfo &spi) { - const auto sysfs_product = (boost::format("/sys/class/tty/%1%/device/../product") % name).str(); - std::ifstream file(sysfs_product); - std::string product; + unsigned vid, pid; + std::regex pattern("USB\\\\.*VID_([[:xdigit:]]+)&PID_([[:xdigit:]]+).*"); + std::smatch matches; + if (std::regex_match(hardware_id, matches, pattern)) { + try { + vid = std::stoul(matches[1].str(), 0, 16); + pid = std::stoul(matches[2].str(), 0, 16); + spi.id_vendor = vid; + spi.id_product = pid; + } + catch (...) {} + } +} +#endif - std::getline(file, product); - return file.good() ? (boost::format("%1% (%2%)") % product % path).str() : path; +#ifdef __linux__ +optional sysfs_tty_prop(const std::string &tty_dev, const std::string &name) +{ + const auto prop_path = (boost::format("/sys/class/tty/%1%/device/../%2%") % tty_dev % name).str(); + std::ifstream file(prop_path); + std::string res; + + std::getline(file, res); + if (file.good()) { return res; } + else { return boost::none; } +} + +optional sysfs_tty_prop_hex(const std::string &tty_dev, const std::string &name) +{ + auto prop = sysfs_tty_prop(tty_dev, name); + if (!prop) { return boost::none; } + + try { return std::stoul(*prop, 0, 16); } + catch (...) { return boost::none; } } #endif @@ -98,6 +130,7 @@ std::vector scan_serial_ports_extended() if (port_info.port.empty()) continue; } + // Find the size required to hold the device info. DWORD regDataType; DWORD reqSize = 0; @@ -106,7 +139,8 @@ std::vector scan_serial_ports_extended() // Now store it in a buffer. if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, ®DataType, (BYTE*)hardware_id.data(), reqSize, nullptr)) continue; - port_info.hardware_id = boost::nowide::narrow(hardware_id.data()); + parse_hardware_id(boost::nowide::narrow(hardware_id.data()), port_info); + // Find the size required to hold the friendly name. reqSize = 0; SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, nullptr, 0, &reqSize); @@ -138,6 +172,8 @@ std::vector scan_serial_ports_extended() if (result) { SerialPortInfo port_info; port_info.port = path; + + // Attempt to read out the device friendly name if ((cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("USB Interface Name"), kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents)) || @@ -159,6 +195,23 @@ std::vector scan_serial_ports_extended() } if (port_info.friendly_name.empty()) port_info.friendly_name = port_info.port; + + // Attempt to read out the VID & PID + int vid, pid; + auto cf_vendor = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idVendor"), + kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents); + auto cf_product = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idProduct"), + kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents); + if (cf_vendor && cf_product) { + if (CFNumberGetValue((CFNumberRef)cf_vendor, kCFNumberIntType, &vid) && + CFNumberGetValue((CFNumberRef)cf_product, kCFNumberIntType, &pid)) { + port_info.id_vendor = vid; + port_info.id_product = pid; + } + } + if (cf_vendor) { CFRelease(cf_vendor); } + if (cf_product) { CFRelease(cf_product); } + output.emplace_back(std::move(port_info)); } } @@ -176,9 +229,15 @@ std::vector scan_serial_ports_extended() const auto path = dir_entry.path().string(); SerialPortInfo spi; spi.port = path; - spi.hardware_id = path; #ifdef __linux__ - spi.friendly_name = get_tty_friendly_name(path, name); + auto friendly_name = sysfs_tty_prop(name, "product"); + spi.friendly_name = friendly_name ? (boost::format("%1% (%2%)") % *friendly_name % path).str() : path; + auto vid = sysfs_tty_prop_hex(name, "idVendor"); + auto pid = sysfs_tty_prop_hex(name, "idProduct"); + if (vid && pid) { + spi.id_vendor = *vid; + spi.id_product = *pid; + } #else spi.friendly_name = path; #endif @@ -221,7 +280,7 @@ Serial::Serial(asio::io_service& io_service) : Serial::Serial(asio::io_service& io_service, const std::string &name, unsigned baud_rate) : asio::serial_port(io_service, name) { - printer_setup(baud_rate); + set_baud_rate(baud_rate); } Serial::~Serial() {} @@ -359,9 +418,8 @@ bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec) } } -void Serial::printer_setup(unsigned baud_rate) +void Serial::printer_setup() { - set_baud_rate(baud_rate); printer_reset(); write_string("\r\r\r\r\r\r\r\r\r\r"); // Gets rid of line noise, if any } diff --git a/xs/src/slic3r/Utils/Serial.hpp b/xs/src/slic3r/Utils/Serial.hpp index 5df33916f0..d15f249c07 100644 --- a/xs/src/slic3r/Utils/Serial.hpp +++ b/xs/src/slic3r/Utils/Serial.hpp @@ -12,16 +12,21 @@ namespace Utils { struct SerialPortInfo { std::string port; - std::string hardware_id; + unsigned id_vendor = -1; + unsigned id_product = -1; std::string friendly_name; - bool is_printer = false; + bool is_printer = false; + + bool id_match(unsigned id_vendor, unsigned id_product) const { return id_vendor == this->id_vendor && id_product == this->id_product; } }; inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2) { - return sp1.port == sp2.port && - sp1.hardware_id == sp2.hardware_id && - sp1.is_printer == sp2.is_printer; + return + sp1.port == sp2.port && + sp1.id_vendor == sp2.id_vendor && + sp1.id_product == sp2.id_product && + sp1.is_printer == sp2.is_printer; } extern std::vector scan_serial_ports(); @@ -32,7 +37,6 @@ class Serial : public boost::asio::serial_port { public: Serial(boost::asio::io_service &io_service); - // This c-tor opens the port for communication with a printer - it sets a baud rate and calls printer_reset() Serial(boost::asio::io_service &io_service, const std::string &name, unsigned baud_rate); Serial(const Serial &) = delete; Serial &operator=(const Serial &) = delete; @@ -48,8 +52,7 @@ public: bool read_line(unsigned timeout, std::string &line, boost::system::error_code &ec); // Perform setup for communicating with a printer - // Sets a baud rate and calls printer_reset() - void printer_setup(unsigned baud_rate); + void printer_setup(); // Write data from a string size_t write_string(const std::string &str); From f729ab4b1217f6acc22436e2c78e9bb600e289f7 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 25 Jul 2018 15:20:04 +0200 Subject: [PATCH 036/119] Fix: Race conditions --- xs/src/slic3r/GUI/FirmwareDialog.cpp | 44 ++++++++++++---------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index 38c2937fc5..e9499239ab 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -114,10 +114,10 @@ struct FirmwareDialog::priv void flashing_done(AvrDudeComplete complete); void check_model_id(const HexFile &metadata, const SerialPortInfo &port); - void prepare_common(AvrDude &, const SerialPortInfo &port); - void prepare_mk2(AvrDude &, const SerialPortInfo &port); - void prepare_mk3(AvrDude &, const SerialPortInfo &port); - void prepare_mm_control(AvrDude &, const SerialPortInfo &port); + void prepare_common(AvrDude &, const SerialPortInfo &port, const std::string &filename); + void prepare_mk2(AvrDude &, const SerialPortInfo &port, const std::string &filename); + void prepare_mk3(AvrDude &, const SerialPortInfo &port, const std::string &filename); + void prepare_mm_control(AvrDude &, const SerialPortInfo &port, const std::string &filename); void perform_upload(); void cancel(); @@ -224,10 +224,8 @@ void FirmwareDialog::priv::check_model_id(const HexFile &metadata, const SerialP } } -void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo &port) +void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) { - auto filename = hex_picker->GetPath(); - std::vector args {{ extra_verbose ? "-vvvvv" : "-v", "-p", "atmega2560", @@ -238,7 +236,7 @@ void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo "-P", port.port, "-b", "115200", // TODO: Allow other rates? Ditto below. "-D", - "-U", (boost::format("flash:w:0:%1%:i") % filename.utf8_str().data()).str(), + "-U", (boost::format("flash:w:0:%1%:i") % filename).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " @@ -249,18 +247,14 @@ void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo avrdude.push_args(std::move(args)); } -void FirmwareDialog::priv::prepare_mk2(AvrDude &avrdude, const SerialPortInfo &port) +void FirmwareDialog::priv::prepare_mk2(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) { - flashing_start(1); - prepare_common(avrdude, port); + prepare_common(avrdude, port, filename); } -void FirmwareDialog::priv::prepare_mk3(AvrDude &avrdude, const SerialPortInfo &port) +void FirmwareDialog::priv::prepare_mk3(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) { - flashing_start(2); - prepare_common(avrdude, port); - - auto filename = hex_picker->GetPath(); + prepare_common(avrdude, port, filename); // The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy) // This is done via another avrdude invocation, here we build arg list for that: @@ -274,7 +268,7 @@ void FirmwareDialog::priv::prepare_mk3(AvrDude &avrdude, const SerialPortInfo &p "-b", "115200", "-D", "-u", // disable safe mode - "-U", (boost::format("flash:w:1:%1%:i") % filename.utf8_str().data()).str(), + "-U", (boost::format("flash:w:1:%1%:i") % filename).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: " @@ -285,7 +279,7 @@ void FirmwareDialog::priv::prepare_mk3(AvrDude &avrdude, const SerialPortInfo &p avrdude.push_args(std::move(args_l10n)); } -void FirmwareDialog::priv::prepare_mm_control(AvrDude &avrdude, const SerialPortInfo &port_in) +void FirmwareDialog::priv::prepare_mm_control(AvrDude &avrdude, const SerialPortInfo &port_in, const std::string &filename) { // Check if the port has the PID/VID of 0x2c99/3 // If not, check if it is the MMU (0x2c99/4) and reboot the by opening @ 1200 bauds @@ -333,8 +327,6 @@ void FirmwareDialog::priv::prepare_mm_control(AvrDude &avrdude, const SerialPort BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port.port; - auto filename = hex_picker->GetPath(); - std::vector args {{ extra_verbose ? "-vvvvv" : "-v", "-p", "atmega32u4", @@ -342,7 +334,7 @@ void FirmwareDialog::priv::prepare_mm_control(AvrDude &avrdude, const SerialPort "-P", port.port, "-b", "57600", "-D", - "-U", (boost::format("flash:w:0:%1%:i") % filename.utf8_str().data()).str(), + "-U", (boost::format("flash:w:0:%1%:i") % filename).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " @@ -369,7 +361,7 @@ void FirmwareDialog::priv::perform_upload() const bool extra_verbose = false; // For debugging HexFile metadata(filename.wx_str()); - // const auto filename_utf8 = filename.utf8_str(); + const std::string filename_utf8(filename.utf8_str().data()); flashing_start(metadata.device == HexFile::DEV_MK3 ? 2 : 1); @@ -381,7 +373,7 @@ void FirmwareDialog::priv::perform_upload() auto q = this->q; this->avrdude = avrdude - .on_run([this, metadata, port](AvrDude &avrdude) { + .on_run([=](AvrDude &avrdude) { auto queue_error = [&](wxString message) { avrdude.cancel(); @@ -395,16 +387,16 @@ void FirmwareDialog::priv::perform_upload() switch (metadata.device) { case HexFile::DEV_MK3: this->check_model_id(metadata, port); - this->prepare_mk3(avrdude, port); + this->prepare_mk3(avrdude, port, filename_utf8); break; case HexFile::DEV_MM_CONTROL: this->check_model_id(metadata, port); - this->prepare_mm_control(avrdude, port); + this->prepare_mm_control(avrdude, port, filename_utf8); break; default: - this->prepare_mk2(avrdude, port); + this->prepare_mk2(avrdude, port, filename_utf8); break; } } catch (const wxString &message) { From a9aca4426ca1c389dec4d415659467569e87cfb2 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 25 Jul 2018 16:54:02 +0200 Subject: [PATCH 037/119] Fix: port friendly name encoding --- xs/src/slic3r/GUI/FirmwareDialog.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index e9499239ab..30339e3cb0 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -132,7 +132,7 @@ void FirmwareDialog::priv::find_serial_ports() this->ports = new_ports; port_picker->Clear(); for (const auto &port : this->ports) - port_picker->Append(port.friendly_name); + port_picker->Append(wxString::FromUTF8(port.friendly_name.data())); if (ports.size() > 0) { int idx = port_picker->GetValue().IsEmpty() ? 0 : -1; for (int i = 0; i < (int)this->ports.size(); ++ i) @@ -354,10 +354,11 @@ void FirmwareDialog::priv::perform_upload() int selection = port_picker->GetSelection(); if (selection == wxNOT_FOUND) { return; } - std::string port_selected = port_picker->GetValue().ToStdString(); const SerialPortInfo &port = this->ports[selection]; // Verify whether the combo box list selection equals to the combo box edit value. - if (this->ports[selection].friendly_name != port_selected) { return; } + if (wxString::FromUTF8(this->ports[selection].friendly_name.data()) != port_picker->GetValue()) { + return; + } const bool extra_verbose = false; // For debugging HexFile metadata(filename.wx_str()); From 81a229045a81337d36a845da7b497baab7b1a849 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 27 Jul 2018 15:10:25 +0200 Subject: [PATCH 038/119] avrdude: Fix: Stray winsock usage on Windows --- xs/src/avrdude/ser_win32.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xs/src/avrdude/ser_win32.c b/xs/src/avrdude/ser_win32.c index 20d085d13b..3a05cfa905 100644 --- a/xs/src/avrdude/ser_win32.c +++ b/xs/src/avrdude/ser_win32.c @@ -311,8 +311,10 @@ static int ser_open(char * port, union pinfo pinfo, union filedescriptor *fdp) static void ser_close(union filedescriptor *fd) { if (serial_over_ethernet) { +#ifdef HAVE_LIBWS2_32 closesocket(fd->ifd); WSACleanup(); +#endif } else { HANDLE hComPort=(HANDLE)fd->pfd; if (hComPort != INVALID_HANDLE_VALUE) From 78a8acfcf187d05cfea8a0521cf238937221a86a Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 27 Jul 2018 15:57:00 +0200 Subject: [PATCH 039/119] New PrusaResearch configuration, renamed and improved the MK3 MMU2 wizard picture. --- .../icons/printers/PrusaResearch_MK3MM2.png | Bin 41571 -> 0 bytes .../icons/printers/PrusaResearch_MK3MMU2.png | Bin 0 -> 71328 bytes resources/profiles/PrusaResearch.idx | 1 + resources/profiles/PrusaResearch.ini | 150 +++++++++++------- 4 files changed, 91 insertions(+), 60 deletions(-) delete mode 100644 resources/icons/printers/PrusaResearch_MK3MM2.png create mode 100644 resources/icons/printers/PrusaResearch_MK3MMU2.png diff --git a/resources/icons/printers/PrusaResearch_MK3MM2.png b/resources/icons/printers/PrusaResearch_MK3MM2.png deleted file mode 100644 index 1068bfc105f02d13e374764352e61ce76087ecb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41571 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%0DF%RgsF}R*<8&ArgN&BuY(A6?$Vu7QvDx< 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 diff --git a/resources/icons/printers/PrusaResearch_MK3MMU2.png b/resources/icons/printers/PrusaResearch_MK3MMU2.png new file mode 100644 index 0000000000000000000000000000000000000000..eb5dccf08c6492825f2706d64805e2ba526d354c GIT binary patch literal 71328 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

X=%+NZBI531A>dAY2^iq9WcOZK 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

JEdk^NC_(}C2HjTtCe_E1U^ zB~cjct|M}3(3)V#aU8s3no_UTX*L^d+PaVgU6CNCsT1k4msQ9XhBc#80F>v!+vo_ii*xu5go=f9ruW8a~?^BeT7UzDs_+P|h&o8F|OT{83*1MT9ZXYA@-luG&HrB@UklQE+calLX-cPtGseX ztzIL|5;ko)6Di=;qjcIOY-5D3MYtMcj-tzzp%g4!TP0}Rhj5D@!<&_r_ML`lET;C912D=I- zD}5D44IN5lbfHg%Q5ZD}Vnq1K+v>g6{F{(BjC}s+e2o$=@2i^TDnclgq`TDSoC~kI z0l2l(UU?-#jAr5(Tg}9g0&6_N$7E?89SKTblE`4V7=g7BUJj)x;P~+)yyTUy$NPwX z`1_A>_St9iu0MM(=bd{V|NR$#mal&O8~o+R{*E|KAK#$8_Y`HoYoB)Q6{Km35Q4NG z^}g|yFWqt9cfWPbmp}K}SG?fIUiS0jQm6g$l^xmSQ6roR4*pX6#6nGmM%z*Cl z3TJM;HooGj3-GQeosUx)MFG)(x}y0w{8)4(~7%MB~#Uv9B!0ec!UDwe3gtzl$SUkwsa{a%|^z)M-FlQjd!Aw8q>{5 zyf-+R(;MGJ*(uRF9nE+tMc8_~OFfciiqsNY7*v)J>u{mGDM3ag6BBfMEyUEt)U~F+ zG)J>OhVz16zr*r!j+GHR&e_hUb#)9u=tc~uepR2yyAw=x=v+sJ>%?kTz|hr51aA0V zLr;8y6sMu}#m;>rOLc8uz8%&cF}*S`8SoE1FlSwDb|B_Dp@UvYh-$upk&JpRjDevR71 z1PhCcquRw|tstB$1X-H0Yu7G5_qi|fm9KoAJHB@>S-t+gXkv_q_CBb;`_1crVcQwo zNRuj2%2H~z8gZmZvY4!%obYHgGmI8xDNB-Aamgi@#{gcM!kL^#Bc;}?W4%KvNtUIg zNkW{YWVIT#%Ba`Pu4j679pC-dXF0O}L1tz)hnawBttzp*An96`;bK&ttfJ+v#*-B_ zRZ4-gVJd*q0k(T+=YIM|Fg`v(lBF0Gv*+%w)4t=&GbXen{N6!mJ38P@RvkAPnlmNYKFkV=I2?B!6D5yDIGg3^XDB(W0M(v1eucRC#!6BAr;*+ooFHTj3X z|0tjR!e@B?3tr6mmp_p|{?p&%uG?;PGc(hVtIHvW$(@vf(pZijpX2z#Joi8F5LZ3v zi61@d!V9|dx#tU?|Ce7F^m~j=PLN~?NtO_&DM^-+XhkG7ky4{atRi$IO6MhUTqDh5 z4G<-�v634eL|tS&bw~NRpT|i&njJqrq5njCC6~bMGDBV*jo?nVec5NaR|N8g;!7 z22J%joC`l&6XbE7wbgTB`z?xqmr4=h(@v+$;&Pv{u?dE?1ur%Ce&?GQ9iuL|nxefD z;!*2x#!(1Z>=i68bm+AP^jZV*&LAW_aRW8IiDazK(%xgNeB&z&Zhb#DfBWk^uxls% zq7TxNCNZ@v!}x-w#bvtfUL_Hrs$C~MhC(|+40oO(FQN*wq&VUA!7f9SvDE5>o8p8U zWbL`|#wc z(BOu$Zs1&?zl?ZJQnPv8I^OjMZ{=;j^UJ*U)j!3DKl({l1|9y}ulx*Y67j?*JvqR3 zk2Ae%gu``veKu{}Kvrv11r*OYXP;Bvd;f!fc-QxC{k{7ixNr83+irW#)1LjDuO2+G z|Aafm8WA$};Zx4zl|YH8blxKGSLD>T-h@+S+tI=w=*n8*A=*dY2J1)g| zLmX?&prAh}a7xndmmFX0a%`nfQ5cl;bRT(y<6r+a`|di-^7M8NpM3@2oqPs|7Fq~j zk{2a-Phkqcp`|9z3l^7JVMIek1#mVztO=r#Zirj^P%QG!;|?*UK`DrdzAD-TZ+rCc0e;{|evCK#>hJQ6uY8$reB+C}=5;^8ul&+)^R9Qj zlh^*(ODF~dYW4c4n);Xq?LBhXsEQXvkpyXR!m_x$z=-`II+HD1H_=G5(hMx6gTmPqK*ze2S|PO~ zPD;`=CP_1Djeuy>>J7GTJriR~zH|LQ5i7{*&5;0&_d(vEDEbVwtw)Wum1jQjYd8U6 z08s$`ho4sv!mQGiP$ zv2LG*K}n$^bgXIbJI2Fz9pKQq3pq~A1K;@y5AVJYmDE58;xt7@idMJ9QhNyqe2}H3 zWd? i{Z8if)=)WQ!dfYmPKRv|X;JhluT%sU^XfQ_vltH$rDyu&+>DGk=zaKqTu zunh;@c8jUmO@l4l&;2}MH}3&`V1Q>}iV{->#-w)+YdvKdzV|XhM2aMi@Gek;m6YI0 zyfYvK!$RHWty{V2d*9*eYcA#XJ8tE-f9vg>eg64;=p%na(eLrjcmEZaJmGS`u($x? zq+RBurK^c5Hr%x!k)d(b2Y`Kfd4Zq#sn@*iYhV8CTkgI07C!NbkALnpKkZnLFHM5@P_#`)f z`(G%#i%d?ftArc_gyozI2Au~lt+hHKeq6hTrEP1%7z80j(9B@XHts?qq@=*u0wp7G zjwFd$SXgA=!#g?u>Z>{84b7s<#>tFCNe(Y{D3l0{omf*82JK<6aFm0awo`7skmUGb zcJ129^5P=vH*O|N6Y$XK_OR9v)niIiU`#=$Thj0M86O|RI=dQTgoAw)8ZksK@M82i zopGZbYIuXgYGHA0$RVm6LR7z8#k9k+&S0(M+>5XJ5-@C4>I{wAc|Rykz|VsW#c-rY za3UoV6-|&tDF{QQ1cpH3(FnOJ(_0J%tSl_@!H@m}&wJJjc*i^bkS~4xi~Po0f1B65 z`VIWapZ+PGe$F$W@l2MNmrrZZ65$;p>`5Mn5T2Q-2{vw+Wy88@Vx_p~qV27p`sttf zSQIt6?S@;NUoj z89MJ_m*-eW#_9Fus26h_I((G<2M;naF-e-Flx0XrTPtk_-9BTrafAy3I=x<>m6bMH zpX3-u(C}(Fv05G+eoiq8A*7P45MKqC2_Z*e#1Q`-q9h^wk|X?lNP6mZ+MIFLh5MW1 zlQ$At#o7cZvmP+O`+lDH1E<|tj14`%LJ%5divktKzg2(7gABX1w;p49_=@bdw6MU- zUi?am#d-e6Z~g+8UHSyx^Oqmu@=Kq{>t6p#Zu#!Fxb)&n81x5^rVE_3v^c>P1TJ$? zl!1J01A(XA>hKG{@P?oI>X*LqLpOZ!GZTOO$A9=U$@utksmkuYR2$ zd(F!q_2y0}BZMW5J=T^PK$0dtj%uVbCCdck%?yR2(Hvv**6lp_z+D`AnQRZDWOyKtSP04v}XRu z5pF)zBFQ4_AtKKLe(|KLE9=G&Nom#^BUHD{Zl-A!^Seze`S@sJv8P%FpyFf-a7{U9x4(zg3~CCCyEsK z94QN^#$HPyakvUf^~g2dTGN^x65>D-Q2dyaI{zt#}gX5ddp|2G0`qQ_6_*-B3 z`YX3>*-{1lqhNg%^Ve7rInq>Py=egGIJIfgKqoOel4ME7_{0QT&p3;t#}2Ub{%~pP!tZJQ=5X)Y8>Ej{qSdz*$dOSkjut5sDB;kIr-HCFg$UeeeH^SH0#nul>8D z?L)lrCtvgNCtZ4t(?WluDtDhyuu@7Jk0MeU;YAIgR;#J# zFb8CfVGP-5)_EeO*neP-eS7w@ecN8n*mf3;&D+DNt2aqEZf0fwZjRpiO_mmx*mw9C zah4G&MYrEY2}PFH$cqBJ8}Uz+RCr?v8UUVFYXOtz44b!h;#D@sOu)Y5T z<9Dk#U*Kp|Bqv|R)x%w&dc6>W?#eRLGn+c=H=p?>LL%7WMDLu3d&m}pJRg=I!Mkc$ zYN!opT|@XOyiaky#M&_O+SvfTn*yWisY!Xi(5#&fvh>L+vC9k=k= z&wYlUd-Jbw?Ngq?{QTUb(3+E%thOm}0+_EPWl>;_4e*K&cmDj`BF}&R(?9-^4}a** zQ&Zd7d1#(5fBpJ@_})FYzx&?1?~NH&CaMagK$OBuQZ&lC5ORz)yi8+Ml#ymlHg4Wb zVd0jWzJR8nRvQ~36Qhb4Vd|(G#*5BY{fH&j7>o_xdbrgNv-aUc0Ou{vh6`_pMV6`< zQ<~t7hf=`4+Q!3VuvH>cz1~2l<2 zTX}HzBg9dRA|NZ)2L0>Ogn5iwuL`#d8RQ!r0%uB!!Ez<%AXed8R-s!?^3=gQ8iY6C zEp~{}j$XEA=VgfQ49|zbSA{36LpZQyiT9qfFTUbSz`Xaq@ZNV=8#{;a10iH7Dh@}e zmaHC|M3ATw=li%S;kK@h^{OHl62^DH0lqBL^3wLj^_9* zx8M3zTz{U)$r+FWrNaDZS^4`RjWWz%sqPvV!VYiEC>|cFKsoP6UU0RHTfNa5OiU?F zo|klbJyg}f9K41c@e>5T!h&s3Y8sy;N}DXV1{~>o_Am7~cyNwRwi5`vX!nj^{C@xIzTB~!t zb|+MoAdWR9`Cx@8N-9h@gh#fDIY*NfhC8STM1N;(P(4NBysUBshpi?c4F7Hz9T8g4 zoGCFy9tc!66#krtUZ+jg81rYIckyQd9P)k@2l2h8C|hhOoU$LU-}7Yr``{Hhcwj zh0kh@AxmRo1-)*EW@9WscEjq|u$Z$Nu@cmNhZRXxk$wOXILyX6lpH3s5-BwZi7BfI zy>X0`k~m62^8t!(cOGXtBuR>KLp^M90=?j5PKL9gbU#8WZ7U~yK3RWd8`Rg znv|i*SA^mNgMN?mpK$HBl+wGcwL%Ed0RRa>_P%<~xnUw$NGVGxr3rt-nPCW|mBI=I z1jw+T=eV*9woPdYB(j1QT`021!1vKr_|O~l=yf~%(`P=*6<0oqU-`8+^G~1tG++GE zH}D>Q@t1#rYp%Zbsn37j55Ie6cKWU3V-wAw${A^*IA~pj zj*?_(YW&R4{KQ|~a_3DKyz`IW`BPV3cGaCaiMaW`o4EJRd)JSTO|fp>G&g_!#@BuG ztJnWaZ)xeA?Ps6OgM0S!&|NpNesT;M#UOmu?ai9ZzCt*>Lskv2s};Zs&^NY{-VLR2 zfgH9Coq$iDfaFw{-dIbcR%7GLB!fcuQa3GIp3!&m;K^1|MaLc`$&8=@9c zs_K|S__U^`jfzvEBtz;ba8Fc3uiIwB*6qt1ww(EO=bU!VwXLaoHsaULm@~?gc7)_%UcRd&jliul%je~xcRgYcw2_1VxZgU@uC;MgfD*O^Su5i zUdm6r{-?O{TQ`x$32*)NU**1C4{~JT*z~9V^;2&?ws3sMV;M7qp2Ab+jm8+VT1p&0 zri&js1O>=iEh~TN*M9MXZ~mn>efXb0^Utri|L!|q)W{moU0GU~-nem^o4)zAjo-cb zJ3oBQwNGL58E5d&{Wr62BBL;pcBhR}fjFeASip_KC0S+V9$De1-dAy^wN>b05H^UT zyCF2Mwgb%n7qX#ZX)rq4dQbiPHUd7CYyfCD3Mx+(} zeh=5RWLXWFWOO>ckieRVZnuMVVK%x}P|G6HBs3660oIl{7gc%0unT)gyA)MJvO2lXp@hbjfkN?^23^I@qPkjkctYYhqS36K>feYp zUx)jdVpSEt32Y&VeCsr;v)oKyy9b%@&R!No@;eD!bMhJ(CDj3W?OO0h8crNiCwG6Kc&dwQtvh zKfULko6cFkaU+94AE`82C1^Fks1j#GSBL_zp@CHhDgnR>oA$xeIOixzLt+B-WUa0G z+bl{5LNkQN`%1vTSzKu`&fu-7DvJW49BGm&c^w465ZE2$q{TQ+HTJJnG>a_-)RF4|qq*8H` z#*D8!n>)XE%QegU?w*;RnHn`y25}ey$3aT0TCM8&inKF~_B^4>LHH0GJMXIbhO!DX z+!|vWQcXvns5)AK@Cqj?NVEC~ag@-gH>r14FwRk!vI;RO{2R4KeaV9#Rxv#|a22*I zM8zo)71P%XoYW%(V$^{m120&sP$pK@>=>=1poApVXy@_-2+&v9`cNh~7-@);MfHOh zk48F19vdnSN7(B7-4>Ixn-=R6v)@}eq-*;)6y7~Ee zH#RoL_20anxBb@J&e*(uy)u9UYgjw18ij2k6ipOL2DD;`B&cfZH*QVhBRtNU zFt|~Kc6eKbAFeX}VbT;n*ec{EL>Rn|P#yK~z*5=mQ z+ak9MyI zSR@hN30xpoKej=y!8b=Q9G1UNv({E*Wo6LXdmjU-_dbjlIYi%F<$7C=mc!k_S8<}K zcBD$us1k9HbVRHp(ln-?)rjIs20{2owayJGo+t90t4km}5+8Pip>OgQ=gKugn3aKh zL)mLHz3qaXUM9Pi7LG?nQ7ju{3S&%etu35$r7^~O@2&UVE-x>;BS((-dcE#H_`whQ zy?gd@-BX@wN^2;~lBJ~;uDtRJUiZpp^T5tMeBgbb!FNjXL7$R*bnS!}I9paXS%5gL zvsKC6v9i(zr_oxmytGKaDDX&>)L3t;UQ%`1QsF4%VAV_e#~{p$lfDj(S_8^^9}&u> zvG+a$+}Pwqe(N{CMA4cf&FWRvYc(_wReY*sgcNxd#|({km2ozVPs0faVkQtehfNuj zPHmiHWgMWoNRG%&VY{v5zpBcg_YP6bso=Ep7ih@Fylx2=F9`6H$ z7rbSaU{yDmuZ%TRGDGaW%G(`woC|~u$)QpTsT6S(c86M)QBP|`N{>qXL&2OOCnabH zo(SU!AxGzP$a@(=RUUzq8YwI3FG8i!7KUtmeGxThZ(Eo@7WI4GZc(kWEK6gCPPw#| zQM={kWj7cM{9rKP3t#v`KUEAZz4yv{uYt%p z7b~Sa5Z!m%P3Nr~eS~_mj`gP6B15l8D5Uw-H}fIv5W-f*yb3=JjoMZ5q7Z(Bau{PV zrKK!Olq!P3NEZZPs#0O7=J^Wpk__9IoDKTBkxod`I?Y;9#pMx7_EBN=1ZfTZNuf zsz(72H>1Qjczijkc&>WwRZeu^uB?V#4p2yRl#-0Ezq{4yv^y({Vvy&>U@$0)q9}`^ zFhx<=q9|-xmUb{0xS}Y0tJQKvQTQ)>;S1hc>j665u8_4_&=${9mRpN_<|{WNorAi9 zSQ2F^Ns?9Zf8d=98_M>=gG3>P7!~fc)_5OU>_&6rg(s3an2H-#0#9SmYeSAI0&`+$ zaHn2k%|rSiw2Sv_lBtD&?MGgQ*xMZ6a*L!^&d#04lU)K`6#Ji`JSXO=$QA zLP&HahOTu)S>}``$C@xK(%B#>X1qs=>L&A{>0kHH#YQkdmKeMAdMqBGE9|C zMMUI0o({&Bgf=lMR-Xp7s$_*%-~&%)IJnAHaGs(Pr4|yQ1xiP#C_24|Uel>C?4ds4 z;-6-n;H8~&3W$|bd*_cI|KYoC{L0HG$D4RTCA(#+Sk#8CuQb@A#2Zspv_io`7%`f+ zB8A6!8R($+(G)ByTsBZ4-3ak;&fv`S`5?sWYoM%AX}TIKArR;&BCRz7Y$i2v z6cgnUd9KN&Knh1mprsmT@xq~`Lkw~0s+&!VFjZ9`*2r^JA&FE-snJn_(sB4+M|cro zoT&QqEcqZuN?my=hsRb4a`;5n8vL z_Xr2kup`9>aeVJQLP?yeb`d$m!zH%#^!s_Ju2yvdsTIzIk==)mAEdRs%voohO|nLZ z{FDa02HSAMKtpiqrqh!QSvco}_g;AKg|#+Hl4O6c*Sql6uYdNpV+~426lGC4m5O`e zL0ChT%7$38N)m@eBfCZ%1|MjS5~?0FftLa&UB!nEj5}+>yh0%aUU{Tj?UP%(+YFP$ zP^PZ7ZBfZo#eoN*bwm`$#8FI7DO5EavQUyD?^W1xh-F08<|G9uZ;_%Jo+CWcRZ5#u z;bnqUF*?f7QAUy^#BoBFWF)mQvf3C*$KmWt8F(wKcCQ#S)Ux(qhWSBA-ispexs@bL z-yM#(7J^6zK1rmcAL#K1Tb=&l_R3Px>2y}w?RKl%?Y4ToUVAVYbbGyCuiNbodc9sz z6h+zTbj-@iN`Cn8VLuoQR(-w@OioS;WlA!oF{Z3ErWM|ppk19vq!wTbTqU~}l9m!- z!w#cF2(X;DL26g1(46Z+2fFi;!$%Ht_|T!Sn>)|`NA_(vbK4n@ZO|tfuEb-d5|rgB z1wBr9@i1v~&Y`qk$_Ilj_kH)q_xQ43YfMZ~=0!+`oE$-B!V!ucqjI(w9qiECg=B5m z?8+*4qw0uIGHh2$+Nygc%qf>DsyZ_WSZ>vvAgitwMAdsI<){EK)LQe^>mpsv%dEO` zJr=1211Si6WfdxxUSJ%!O2$_X<5w(NdV*l6uELB6sZ(@ZL&c3M+(<~`lq9Q>q&3pC zMifOA8UnKFya~C3#8?}~*9nQOJaR}HeU&7yUJrcO6+$C}Axi|ELX?N*=JQT#X-l`$ zY4v(N(d+kJzuzAW27_*%7mHyOloJ z5^qa{3u5O=NUV1hCdO04dlPg7qc~6+N>7>Pu0LY8qctnm^Os^s`^HCp@*$Zm1u*o6%8~*X{%6I*rSxtIB7!|;hw&Owl2+f>iIN1RVg!MTYK;lKoGNCx z87d`5UKbR)LOgtR!-cZA9VJy^#9A18A%({a&4IaAbm++OC$yFqSXx@7+wC&w56XNn z$W3Vmg(-X1ntomuy;wxOOo{H3E;zrlZQGgCM~)q9*`hdDi{pH$-MQn_U;5%lmY0`( z6Cb1`qzJ-4RFi<6!@3SmBfhLA&^2!N^@0YWUORPg*s zoD#<=Dyk91VFaC$5s^xeDylqGD9(iZK#Z7%Fx*l6NVMH~QyFx?pojEK?`=qW1*A#L zp~XJOm)b&z&>xy+HB_XjHR_Qm%g8&|2s@Su{h!tnS~|BZvNWZoc(UI?(_Ek0s;{J|yo#B2|HN3lydwb_yTZk;auIX@pb~SCz!68hDd9 zMN}jGmX?mPu(*hbV!GWfwR)YI$#LTJaSVF+O@(X-=baV^qDcF0cVL=>f;2rb5O>ab z@4Yj|cpKQ@#u$@DX?pbRORxNgvNU6H8tq}{o}cO;p1+p%H0>k%)g~s!r({2_WwZ3Q zY@|Io!T8~$Ob>GU&e3y*!jzQW;ph@z~DPi-ZfUZE_`BSlW6EU|=0I;1y2U(FTRq8GfO z5J+bcN+7*PdP^iCw2o1+4)9ghAgN8Fvj#e@q2m}Wb$}|R8a4M0^9?+q?+-@&<`Ie^ zEW<7$YfaVJ9}Hv|893+363ueAVCVihI-NH8pvRy;pezcE3x%yXPLQ&iUtYGyN`YhwA+8gymVR(PSq zdGlUXT!WL1xiVyoG0ug+))-^@Wj?4cx4Y3GZp`_)qZhXJJ^VDi&}YzHX62zjoW#*!oa0BIAvj!|iX6hU$}k#U8D_(}jKkd^$1 zMmnsbRsoc$+SNu-oeD{^~8fbg-Krec{vJ`YX5eAG-bcJX7_!ikl4sQ=*Y1QWCis?VCgh zB3W39t% zN@3f0pWa-byN+AFYb5RRC|Y`Ywuhft$F|umq(=@=m;n!!F$e9TFqfkuRp_Bpk^WG!Vb=0M* zDx9n0EFQl`#i{C~##Kt00m11Bz?Zl|i7G&f93^wK4nvxh2se_}8liN+v~^V7xCSZ0 zX9>-}1o26410hO=lKGIll}x&kRQLP8QH z#9HH>MW+dk@o`qlKIX>z`Kj;R%@19Xz`YMd_uRPe;=ZaQkz_uci9%WmBdCd(dgPI^ zz}u3tEQrz+B}C8#i9(KotaclUkR3#dZm-YEay!76#emJ5&tPnPk_Yd;ho!kiw0;~M z`jpT5U?7b~?WAWBT8ER@@ArLKmd;x1@;tZJTGNWBj&8jB!+*2+rvLrMvUV=BS%mO? zWYNGjA&DbwXOV5jVNFa?SZcP%ah>s%<8#c-tvGO`}6QPA@sZ@X;M8W8T7hUTTnHjX*j1vP_g{nFzKzTmi7d~Q;5e!bts|LDU_M3 z8>$KsgwUfKE8I|_Kw#FKQEwc+TuTQS`W1@^(55n-NV7U;Y(IlEFKPel%}jsi9{M=G zx2MgwA34O%PD0kq=;lR8>YYW(h!Tx(234#OxiD3>0E`saiY{a$@KRD>@G{6mBtW~{ zj#f>f9DXn42_eq z67(wAaFFMtVgnPx0?6I+UMR|vSk}vA`H2vRCVf}?w+&v+;hz}=QqD^%V)eNj$@J}*?L?yx4v=U z$%Qi6Fgh35lfL+3X{sz@=V#c)RZQ;Rx^Gg?-|-$|YgL*2%#Qb-IBdv;#Fi$^&USXQ zX$-$TXrVW?`??Moh_b9ObqP&$EUOAr*BED;(X)`!2(R%`*q2!EC{0bIBc`UOxZ)yERXsuSx-As^hAxxdMsC-tNfVESz69xOhsRM zG!p9}af-}}Kx}CYL+eR>L>4I?`NAW#mM`+ESG|V3u0bhIoOqml^9%gIKllOu@Q?q1 zqAdUB4(&a3dtZ9|s;VlCF}^HIUlfG{#CSZm#u&!^&H0P*Rlk1U{OkMo|JCpQrx-^o zicrcE35)Y1ymPdoly)5RNMZQcC}*RtY5NL14c~C66-!{ng`NKv&xUW7iX9OdXqMa1 zRSX-OkZQzn%wRaA-|si=Ww`~A4_J<-%0c3y+5jNN(q`lgP;q zC#H$cVuuq?t_Ci3_U(&^OMd4QpVM4hgmlR_aDcE$G$wGC)V0CXRT%tjDn^3vnWYPG zQl({c@p__Givx!bv1@*oJKuLF?|Az=$tPd8uGl{Kkk^Kn%)qN6P2n}>w&$}r7-Np# z$6O@o_>5t(&aPz0H))A=p1R1{Ta58}Nt7k@+A|d861~lwQ?;cmhm0=P+;jImTzT#F z%%fC0~#vi9dJl92YNM^v$yD`u)DG zs*2%YV8;2F(<`Gh|KCQ7e7Hs{)7YY9T;?RLgxTI4sg9@x^sAEC7HH=fPf9xzjy&PF zZyKlYO)4}gJ5QqV7WUbDX6I({-m$(hz`LM%I6XB*tKBAwB9xMhh9frned@X<(wc6s zM^O}v$K$O!rywBvCIxLFwtV)@+dP4doZ=3-R^OIagl%b z@BTdt3;Vd`=9@2k)edcK;A?n7?Rls9BuPY)B$Q?8&!0c=25yDg^MqExZIjG2D~oTi zE<#S4G;%9nmqKvt`Wv|NiYxf^r#{6y{^U>D80O4$67unoJRf6~#t6aCdH#tV8eQTFbJG*|{#i@bkaO=O23DOTJ)vdD&lc%{3f7dX&Y*Md7`7qtS@* zc+6lhup8@Z5U1;eMOHG$V?l#<5^@afC5-qiqtXY~2nf%+NLuEJl;hAsQ!| zIQ7IKC++yHT)p{>JDu10W=h0wkFtf=Y}4C@uGplmydB|XBTh7l)RVk^a`9er0fl#F zNB*(({yTqe76G)<4d0(f3+Q&c-1fZZQB{Vwz4gEF*?aCq0=-s5Z4BoZm+>Bq4^um+{n3yAfV3^xikl%X}ZpJPXFWbcs$(pWKCU*~nN|L(Pa`V&9>rWd{N zg>N5@hD7m`1C+!T5WP7*|MpjZji3L8pL^05?0jvn*Ar=)qQVq`+}PZx^Ks7gH{3LP z>rFR4y0N-AlO`=jqcMZw5Mv!xT~n1sAW3wV+JuPcU6{-c)CB=A>vlvQAp)Aq+Q0*m zX6cqg$4Lx6aEU0XsOz9=8btvZw_7S%TRn8&{6D$s8Vdr%b{7d3hN7`t|jQ0-+9Q`o(?Qr8{QN-{iC@$GMZ7msvj`N8k} z2b{iej-uK;{bN7){r~i(FMZVq2gBjo+WH#3Zio5#g(p29`qi&~O*k1Uv}oV;uD|#y zd^uRZ-}n7~e{zUxfmGk|t>5`0hxQ(N({Q-ji*(G=;v%)RXcafzuX>B{MR*@dk%BNu zWPHN}Ps1hbc9h=)MP3jmF;`uA6~obp&5aF4!x52I?B2Zxnz=dC)Pw(2e$5>{Oz~BYiEx8#60_nk8j;S*&A#{+exI| zcG}7Jo~#Nx-)H;1#TVU5F-d;uD|v6f@Hu4j(?q z`SXj^&NfKD$p$;Cm zoay`%u$jVz0(Mup@kPvV1#Y|%9Y=^ppXIToQ(SfBk@vspM}O(N_U~O742L7mpFiJ- z!hU)7(Jxy9`3fd>+`4bR~lcj*~F+=$)e~A&W!4jp3sv2~`8pW^zL?El4BH#a(4{`SDoA|jm{=;8= z+3mOgg0)bU1!)BK=i|`e5P=jsBILe{=PwY&Ni+H% z0E7L(fQ^mK?In2!1kX0Pc^t=RDY0f!z6yx6&^!sPZqY0kvQg3YO=~%)qmkuA!S&vkj!-sb>*jOje3xo9M{qiO;ch$h8x4acBFf)uQolGbd8!{7cq9=zwHY~JgbAI;j~OWyDsKk(Yw zzxbBxZv61M3+EV)hs})T-^{)qhkoKVTMmYU>aG9zZNK;4_uqNX54`>d?%lU%j_>*I z@8aU>3Q-)B7vpC7qNb`#ob_b7MNyR)Q{lX$Z0=V|VsW@CFwIxSP*&B}EMe6!;#=29 zRM;>m8(O=;eOz;rZKDGoE8TF_h1}5jroBy=co9C@E)ml3amWCKl4_z@8yv1I%P=FS zcUB)Jslud6GdVm>`K&2NPKt8gIpQb^4F7e#P1mI)G6_-EG-QZM>!z+AH-iA7q0>%U zO>QYjk_2lFgMObVia2=aAgxx5&wlzdoI7`xBUc__&)z+F=ecnH0w+$KATLX#n&4f+ zCcP*N#^VAVCxL;;%Oa8@wIZIU*BuPT2(bh#^ciF1JTVqI)n1zJ}X6I%Zjz-*l_uY&~V~!j-jFOTwr_M0WN35)@Qsgyp9Dw#h zg!3>Q4yn(a;rzLCR7FV=H#K()NU%0^3>~3jHR;Ok%=J|RFK`W3Ml_l?PD+fBoIZb+ z>r2D_zyH1bljHZGKY2pD&-q?nF&LE8O)3#0(@uI=%a^z9`Hg0IkZNlb9zghz za;Sz2E>fXfCBpVUP;M!S#S;n1f$2GH3hTu<46n+NMku8kB1zluLTo#!lp(bUjUXD< zqcBb;Wtd)e4KMVh?!B`FhZHEG5mCsQr3e^{EKUP6YMwJ;8=U~3y!XUWgw8V3EF%kL zQ#_%roF*|9p2llbCu|ZiDlvTmFs8B)@H}%||0|ZJb$|_8^$2w~E z8BJc?knL1;6|mP&*t7-!mDu{6ax!P=gXFez9^*aBgRoim)^o+{krK>hK`x|N2>PsOW zTOakc^Sop{=!f&F6^qMDq)OB6PB9t<=649ZZ&J`8;sO(XlBSe(6^28bW!&Q_tBSlV z@iZ(rMKe3-9Ck+m;)&O}1p2dc7;Cw(xe>Z}s}G`-2|;Z~ zSu*e%PSO>ao13|0HGDFA%Bmb}udp7F$3J(^+s~al{ee!aGv6OS`jXz4gbk zoo{Ug+~((ob!1BpObJkt-hz>a3qWm&{UlMszWw{@Om%4`8ROvyDJ8piEs$mzWsx%) zkI_;xKQ+zj<~om^JV{+u6W9Tx@fh!6db&%m*CS0+#^ao_$f+99A^<6M2(xTgPA0Y! zN&vyRJYn-2(h>naMC?g5r3t7F;48oOZg?v@1p===*OAO6W#fAed8Woc+{ zNmR;p*Bs-_>LSMWMTGapggiY$q-wPAT}1?QojEc>ECL%PsYky3#4VN|OT z6EcLMQE40Qj^?!!y49WxSX?C}RzoR0H9^DMIgj*$NGMcOPLW9H^&*%*!( zj)%lLq9`jYkR%Z^)6=wCEy}87G#pZt}@~V+ncFyCPfmfu!+dvGYR8S_!vJ?p%IB<~J*|`RG0I^D_YU8Uq@CklB_Ebh_ zctR;}$L$k?m$~N1Rj=u4eqqp&9G$(EPk;Q=l)$lTZX&f~;<})6mVP~^FeRBzsV6OR zV;fF1MO7P&EpgUtZ3=xer0QH9@FGHCUD(7(1eJ3{0*+-FyW@nSDj4J=taoTUr4KcU zOe<>VxllUBgz0qEib=HKST2~U9Yf)$rDQ{RHZTke%aARpsL%~Lh5 zkfLAOl$k`pvJ48hSt|)jy^|B{cW~vwmg29q%{bTO=ZUyd!;0#>V{LtdrKM$F@(N25 z#UKQgffVP9BKJj6P*sy7_qEy6m2&Ki))SbTYW`ie+a0dE>gaEsJo5-%YRXz8+Zk!C zaFxSEBIImB5l6j-W6q(I7@Q5Wbbt^V;|1Q7NCFL&@nJ+hXz>}mw|E`sW~(Zs4i-fw zG&9a&eT8VJ^txT@s$@nx>Twmgfn!B2B-Ln)sSVTJjDv>{lO+k_>=J96LvW7KU_fa+ zrGd`wX?j_TuPxRG^d2@~uWM5?=@q(W=Wd$Pqf&~gPD+$!42L6%vH<1TzkfHjtdVt1 zuiGWD1|4b2stn|g(ROsSrlziZAUbT8FzFxh)YrrXC0kxhv$6LQBHi} zBx|edTTHYQkW*C|^p0|+YnWYyn&`vt>{`ljQ3{EQf~I?tWK7LW^H(4E5T84KFRyvk zt1)#IBu32N9fyV|cj}#Kr@M0Bk?+eyboIewcVNEoIC}gDdsKw6hIMBs2N6ap^2*Sz z6dS&xmN`l&>H-QJW06u@sKpp1A(Ba$KlC-00;>elOQJZ%);Mjp{`P9kJv$%edw433)q;`%-Ma*Yy+O_}*#mX|dgW3zCPDW%sbtH*|WY$64 z>(XwwNs$zlp>mEeN1&-}j=+;dahTalB39Q{*&K}+3=0nJ+0U)deKCLj-gi({IW4J4 zmBvpcTs(Ikv$le15QN@4Vy(l{?rA!Tg#3RC$=6Vc`R!#q=#i%N%%Zl83MhM2$h)PCS z6EQ3u{kq218NQGV8DoV36EiU2?UE-B4SWOhqxR zj$kHD&_&4?KJ`haTPY&yaZ{yv!Aiw{9xgMCV3-ey6{LwGOXF~nR5i|n(y~zi3zZh{ z3A}-m0pF%A9-?O8D=CVCxw%=4G3TtUl@PKOCrPYzl)U|q-YGWLH;AG}Nx%oKb){ft zW{O?Ac43U=^y$-qL|%mryq1BiveW4-uWzg`q^&HZJWWnMGds(B-}{$*_TJ-|CK~VA zzyGdYcY3_OzTTuPU%QE~{FJBUy(bRtU;Mlm{IlC{d(p4wt4sW=XP>8&+z*4WnL}H! z0g#W=U^K)IFyJDvI&k7S0jePbEF^dno+C7r8Y%&mr?#5PTS{AFjHAvS<4R+Ur;lZ$ zt}sQ+bZd&L9dZ2evpCI`G${#*_xKc{}@X3=W|EQ`e zEriU3AlkEMS1JVQ#>PeqXybj$d)IZI3~0r1BN4QHf~^_uh;_SiFl6 zK5J^5H3*MTiohll+OKFC9M~EJ9-$J1@Ca&zbI3?RB)~P4t;&Nd!u!c6+;2UggG6xQ zp%p<@f^C2{m4$K~#BZEnq%({&n7m|MM2vlmDIHZ&vw>uYz?Q)&m5VTWi)EWLBFDNe zrm87S&WFl2D^bQ2p^k0|lms(vi4~Hw{XSAeY{m(%WXS)x;Q;TyaFI8h*u<`F?W26#W9a8VZCN#@h^!p>c4^qh8UYCX4yP-17^rpGt=4au(=g;5u7rQ;K)9dwS z5}hvex}DjXx!K-mG@8Eh%EQ?&|Epj54Qu_{>dFdAKdB7LCHCxR-Sni7Jsl~>PP?RN zc-uGK_Uc=X-H_b#fxEa_4`@x5R2wrP+By#%ot~CoHqz1@Uj66ISI}n zw8A+7g$)QV+2o7XLZKQ2MbPjLWHJh&l7_+4AR-!x2?x1A_|Tye5=7z2~N# z?7@2;FpoWcmOaO=q<6A}^%c@o zjR#^ds;PB(@D<1yi15~9qX=&+ye~ip=`|-y@EV#qy9u>HP%4sO1;Pfdi@?Ftfbyjv zq(VUi-d6_El8Cf|x&SNiMiY4RYlIy^w}p%puZpI4-TEfKxi;q1V<))r4d20FWtRW` z*_^|1haY?1^I6|N#b5r^ojf)u`L7owZ-{f2Z9y+h>9pGoqpCwoO~GDXzv-sua$x?z_}~22+j#XgDYw6T8AK=G2%K#2(V#?46iMFx9?2hGd>;Ec zEn>aN-pn(VD1^-EwMI-O9`8n^ew`$?Olg=2oZe^&Ee%N`8WzuxhUf&K8=`ocgGhl+ zctIk<1>hA}2{OVCOUiMMNCMHL@J(CH>m~v_g!WYDEpFA&dXa)wi92H%e@Y>z74g9o z7a1^s@|2aORpk8a-+MNPZ@GgHz57mn{%yakw zdp~y%|ML@P$a-ydXB}z>kqqST{r-@RjXur^q9_JDX__|3yD<5!bwsb*{jZBli*FhZ zht=QKIse~bdNNb@blH)Ehq(1w&*sIq-@%E~r>gr-F0s2>LquzsAlMg{YT-c%qPT{$ zEByZAOL^Cg?`7|9Pu^dJs-~A1#+!XaKgSgXN^YWDNhA!j@sM7-!Ryd0tOB_WdaV^nTcXB(nr*Gm3^HxZm1S{@vr<#{>eZ8*F5my zySSp8F|d~3zWaXWfj@fdTgh(P%?m#NLjL4`eUt}2_5oUPOqQh7WzB_i7Z{Dkfyp$A z5K@q)DQTKAQDNwGIvX=Hvu~=Z>VFQ0!>8>$|6MWt>N{RcU6;K7u8%!sZ`y9R_|{jx zk{gd5V}5oH0hFf3CDts&8FySW0J(}Yt)`PygRx*0%q2XuXt}4lf$I)Su0OWILm$-` zopQy^imbDTGmj|pk>cPD0u{m1V{q;ytaQRY&zdHH`kHcFlkI`tyyN0yLmZp#fA=4IElD3|M|h3z4Vz|^6dVFBsTtqPGw_<_S) z@RcX(c|=B&X9qlkw{gP?3KvdYr0RL%k-?6JXux=bQl2D%o2({&^b{96Ij_Fsb{=@( z1jmh`({0o5k2rh!ELByLW*LIO(bwv<8X4iBfj&1k`|fVH`=dgL#XKMLbO*1$3#NrN z{LGL4G?hqs{IQ4eUt{6preoK$Z|`1aXJ)zL&>;>VJV;g742DCTbL^X*Vs*{98{*?! zy?YbJjdq?ZL3=PZV8N~A?$dBFc@Ed@gL+W2x#YO=*$Pp^`YF&gL^VXEV0gh(E_zzl zqjQ=WO@WdE=LALyuDwoC)e`42%Dl@p&yU!DOmp{pJ!K`4X{ekC4@7XGhTWsLbK~AI z4C6)xSv6Fp28;GtVm@BMx`keaNF81!cp~saNZQ={ApFVt z!;C-nZ+YOp`)17f#dIXsv#--A;bj@4%6KBbZ*_TxYXC;%9&IXNi;QYX*n(x;>&s zQ)M_B(I1V#d*UnrSdwm!%C_B&GpC4VN7(g*3E!=^5@JD=X0W`;UoS7vy7qSV9jQ6> z8G)$PW<#wapB@JB2Ceq zjB}@!nV;{Wg{HQi`GsBdre_-}Fi*SP{*2c8`|G-Tq^`|Zg&}?>nN~_M=nv3yGweGu zM=}1|_6){Y3ZEllLA#yNCcEVEnT6eSANoT7$gU00U7RyvIra&Y_N<5n>t}|1!o7mI z!&kBN;6-c=`>vK01Hoz$v^N|O$1q-p^&riGr{BcSuURQw7L)_AsYZfsC5D%Je^sAke5osfHJ}U900TqUI-Kmgwi%xKA@0%u1 z9Pz-q;n5oAVi|A`B48kd7etd8y=Ho3X10s-BJk`9YmbgWoCJ!Y`9f%*~@x?i_>Se254D`#GN@Yx#QC)9WyU<}?1w2B}f z^5LZ(_VDdob=2W2!G(t%XCDer_r_BykID(rT|sX*BngxQFr}w1A6~XtyOR7edNHG(&&oK5kR@aqYeoh9yF0A>`N1Zj&&X;;RZ@ z(&(`ixPGIx*z&mZ0(%DH>qUtALV*ifeQ3?`O~DF|IR{n*RN-V2L`}M;k}#tvG+=NB zl5WbO!$;V?u!r2#)DE&Jd0Ubs|ER8A9dOEDUAOI-YC3?UEOMbk+EzS8HT6q>NffcT zqS#xU{th~Q>}HRE&helO-YL{fhIwq62L`vZ_a!%B?K*=MuugF8^C8Iun@b*@!P#a^fpP9cy`So~O^j|k*p@UaU>e;-D_bmGaEA|6>8@WZ-=KDM9t57yZ@yF!u# z!A{Zea^xNgp}FedULHGnnrNJ(?sPYaUP8nnImA{@UxV&7g=t8qjfQP?#{RXNM{ChxYL1?t6&jI@Oe7P+Kb3VdF%H zyzW6dMHzMR?HSZu#(0=vqL{IW*t0)lv?e*eC^4xd?Z)&gU^g5;54|zOEr?nnMX5&+ zr{L$HKMGySV$|p8=x$zoP=_)iAe2DJ5J9VfC%Qrs$r?Yhh_*(=z|dkg6e1I3w|H#r zKn96mAB&K37z0}Jk#)tuE8ckIARpbG@Lg~DRsQ3z{SJTmmv^$alOTM+NvYcL;|C7$ zt-tug{Pof~-u-8vWPNp&x!Jj2RFVFbsVITY`_~lp;V#wYU*W0{;;ESi^h&ZXHyIfV&0)l%;?bwv09T%H#B12 zH(2PY&^*$L`xkRQjpcpU&Jo8YH}pF6rh3hsWS}}Ltl@}>c-gnSfU_4L<$GTDz2xIA z@Agy0`G;6Ae{~+EoVSDlpm6%6}V+kj@fb?l2suganPA`NqoQ) zMZh6KC$hAVXmAEwXIL{sRj!k(nEv#Lx#zU8(qU@TkOg^=;IXux6*2mZr92UlTsvsYMl3QFGuB@Rppi*qw3fN>os5Ddz z(jFL(uUm*#3v9qwu(<(IjA(W7J_nN{9jGkDQ6iHJQ4LV-6eUYyC+UbW3zdN=rKqaV zWYIdXIibKid{oi8IbwXTLG}cyYms|HQ^YHYl%9CT~qEv{h;`?_ndzM8tOX(bt2DF*qN zwb6j+8@#fy)3g&Ht~t!ER6Q;AOCNQR#o9qmJ^T{qdJyX{E=!Dv(x$%VfpOS8Ndd9g z#!DovP$`fCp%Q#mfGrVm9BRXL2}&c<1nL@*7kC{ZvUbRWbrSH-J_r&y5#fZwxd^JJ z;UU_vVel%VSoICQPJ6VX4PEM4AB+K8_JO#+NxlMNGuug?EtmLuz1)&_BM?YzLJbN88D z-|~CWsdxO4^Ks}5QZ&CxLL?htlEq8k@JZDmL(_%23Qi}3qL)aMklXqSRCtWqf@~r+ z7R(T`9(aSOOT2K1PSEsr)ff>6aFh>R1CTZIiay9H2NrN2(imYIGKP?T_*#I75WPlf z&JiNIF?i(=MGgjhy9L4`?Fct+@!Z${#J~Tc!=JtL_uu&^ciwv7&~LX_ zXKb^tG@DEBLMo(W0QY+p(j(K5xY(Kic5cvjz6LEqx`aTAA*1M;YBsz6FMst_SMm9K z?&Z|#GS|=V>ra^Zl?Wqe%G#|-6I0hzWf^3gpFn{nL}1tFtDwjFMlvm=25+#758;f1 zuXBw2OPTNvmrmtn^4y=x&VKMWWh%i}csE2a9U`DF0s)|n0+FBw5I7xt2w}mC(9f1p z5VTR?E84`K5T0g$RP7mr!XS+U&T0(EOR28+P&oUtfoghysL2n}bCbE!7J&l)}V5&e#6(vZW069*T zVZc*`jgv@v&B#=a*B-CZP^}P6)Rr;$I*3n59iqFZIR-8a;s!|6xv^+oUJOQ=Q~}Lt7J$n{X$e zz7a#|Ju-Xk z7^T;+@j*oACGW~=O<}t%>{;N4URdxm|Lu?HB^kHA zn5cCXNlQ_?f~fF{wn)v%2ip~@XE*Zcq^f(?T<9hH56ws~r}AQ{?aOhmQ|KvQRnvJn zo=K(XYKo3fc_xRxrB&WmL@inSNT`0`gpNu`ON3Df*+W=~XqSj2l-FF58#3zf~H8FKrF+A}(+M*$Oa7 zTcccwv=y2PZHA~qL1j?IDt;sp#Rf`@h|Hd*ek(o-?k1i5CG)^$s}Obs{Mi5&Grbk&qmgQ+U2yYZ<( zw_S=9>to$gU8gqMo_1SDX(#KdX=WvMeeHxF6`vQ+`}Pm2ea|^9`BJy133XZXE{oU%K!iX07*qoM6N<$g6i%i As{jB1 literal 0 HcmV?d00001 diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 5c06353caa..3652c0174c 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,5 @@ min_slic3r_version = 1.41.0-alpha +0.2.0-alpha3 0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material 0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index fe403263d4..6817c87127 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.2.0-alpha2 +config_version = 0.2.0-alpha3 # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -14,26 +14,27 @@ config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/ma #TODO: One day we may differentiate variants of the nozzles / hot ends, #for example by the melt zone size, or whether the nozzle is hardened. # Printer model name will be shown by the installation wizard. + [printer_model:MK3] name = Original Prusa i3 MK3 variants = 0.4; 0.25; 0.6 -[printer_model:MK2S] -name = Original Prusa i3 MK2S -variants = 0.4; 0.25; 0.6 - [printer_model:MK2.5] name = Original Prusa i3 MK2.5 variants = 0.4; 0.25; 0.6 -[printer_model:MK2SMM] -name = Original Prusa i3 MK2S Multi Material Upgrade -variants = 0.4; 0.6 +[printer_model:MK2S] +name = Original Prusa i3 MK2/S +variants = 0.4; 0.25; 0.6 -[printer_model:MK3MM2] -name = Original Prusa i3 MK3 Multi Material Upgrade 2.0 +[printer_model:MK3MMU2] +name = Original Prusa i3 MK3 MMU 2.0 variants = 0.4 +[printer_model:MK2SMM] +name = Original Prusa i3 MK2/S MMU 1.0 +variants = 0.4; 0.6 + # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -85,7 +86,7 @@ notes = overhangs = 0 only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 -output_filename_format = [input_filename_base].gcode +output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}.gcode perimeters = 2 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 @@ -131,8 +132,8 @@ wipe_tower = 1 wipe_tower_bridging = 10 wipe_tower_rotation_angle = 0 wipe_tower_width = 60 -wipe_tower_x = 180 -wipe_tower_y = 140 +wipe_tower_x = 200 +wipe_tower_y = 155 xy_size_compensation = 0 # Print parameters common to a 0.25mm diameter nozzle. @@ -259,7 +260,7 @@ bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material external_perimeter_speed = 35 fill_pattern = grid -infill_acceleration = 1500 +infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 perimeter_speed = 45 @@ -286,7 +287,7 @@ bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 external_perimeter_speed = 35 fill_pattern = grid -infill_acceleration = 1500 +infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 perimeter_speed = 45 @@ -299,7 +300,7 @@ bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 fill_pattern = grid -infill_acceleration = 1500 +infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 perimeter_speed = 45 @@ -362,10 +363,10 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and [print:0.15mm OPTIMAL MK3] inherits = *0.15mm* bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 external_perimeter_speed = 35 fill_pattern = grid -infill_acceleration = 1500 +infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 perimeter_speed = 45 @@ -397,7 +398,7 @@ bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 external_perimeter_speed = 35 fill_pattern = grid -infill_acceleration = 1500 +infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 perimeter_speed = 45 @@ -408,7 +409,7 @@ inherits = *common* bottom_solid_layers = 4 bridge_flow_ratio = 0.95 external_perimeter_speed = 40 -infill_acceleration = 2000 +infill_acceleration = 1250 infill_speed = 60 layer_height = 0.2 perimeter_acceleration = 800 @@ -423,39 +424,13 @@ bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 fill_pattern = grid -infill_acceleration = 1500 +infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 perimeter_speed = 45 solid_infill_speed = 200 top_solid_infill_speed = 50 -[print:0.15mm OPTIMAL MK3 MMU2] -inherits = 0.15mm OPTIMAL MK3 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material -bottom_solid_layers = 4 -external_perimeter_speed = 40 -fill_density = 10% -infill_overlap = 15% -perimeter_speed = 60 -small_perimeter_speed = 20 -support_material_threshold = 20 -top_solid_layers = 5 - -[print:0.20mm FAST MK3 MMU2] -inherits = 0.20mm FAST MK3 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material -bridge_flow_ratio = 0.8 -external_perimeter_speed = 40 -fill_density = 15% -infill_overlap = 35% -infill_speed = 150 -perimeter_speed = 50 -small_perimeter_speed = 20 -solid_infill_speed = 150 -wipe_tower_x = 169 -wipe_tower_y = 137 - # XXXXXXXXXXXXXXXXXXXX # XXX--- 0.20mm ---XXX # XXXXXXXXXXXXXXXXXXXX @@ -475,10 +450,10 @@ top_solid_infill_speed = 70 [print:0.20mm FAST MK3] inherits = *0.20mm* bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 external_perimeter_speed = 35 fill_pattern = grid -infill_acceleration = 1500 +infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 perimeter_speed = 45 @@ -516,7 +491,7 @@ bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 fill_pattern = grid -infill_acceleration = 1500 +infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 perimeter_speed = 45 @@ -680,6 +655,8 @@ inherits = *PLA* # For now, all but selected filaments are disabled for the MMU 2.0 compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) extrusion_multiplier = 1.2 +filament_cost = 80.65 +filament_density = 4 filament_colour = #804040 filament_max_volumetric_speed = 10 @@ -700,12 +677,16 @@ temperature = 270 [filament:ColorFabb PLA-PHA] inherits = *PLA* +filament_cost = 55.5 +filament_density = 1.24 [filament:ColorFabb Woodfil] inherits = *PLA* # For now, all but selected filaments are disabled for the MMU 2.0 compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) extrusion_multiplier = 1.2 +filament_cost = 62.9 +filament_density = 1.15 filament_colour = #804040 filament_max_volumetric_speed = 10 first_layer_temperature = 200 @@ -714,7 +695,9 @@ temperature = 200 [filament:ColorFabb XT] inherits = *PET* -filament_type = PLA +filament_type = PET +filament_cost = 62.9 +filament_density = 1.27 first_layer_bed_temperature = 90 first_layer_temperature = 260 temperature = 270 @@ -722,6 +705,8 @@ temperature = 270 [filament:ColorFabb XT-CF20] inherits = *PET* extrusion_multiplier = 1.2 +filament_cost = 80.65 +filament_density = 1.35 filament_colour = #804040 filament_max_volumetric_speed = 1 first_layer_bed_temperature = 90 @@ -731,6 +716,8 @@ temperature = 260 [filament:ColorFabb nGen] inherits = *PET* +filament_cost = 21.2 +filament_density = 1.2 bridge_fan_speed = 40 fan_always_on = 0 fan_below_layer_time = 10 @@ -741,6 +728,8 @@ min_fan_speed = 20 [filament:ColorFabb nGen flex] inherits = *FLEX* +filament_cost = 0 +filament_density = 1 bed_temperature = 85 bridge_fan_speed = 40 cooling = 1 @@ -756,26 +745,36 @@ temperature = 260 [filament:E3D Edge] inherits = *PET* +filament_cost = 0 +filament_density = 1.26 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" [filament:E3D PC-ABS] inherits = *ABS* +filament_cost = 0 +filament_density = 1.05 first_layer_temperature = 270 temperature = 270 [filament:Fillamentum ABS] inherits = *ABS* +filament_cost = 0 +filament_density = 1.04 first_layer_temperature = 240 temperature = 240 [filament:Fillamentum ASA] inherits = *ABS* +filament_cost = 0 +filament_density = 1.04 fan_always_on = 1 first_layer_temperature = 265 temperature = 265 [filament:Fillamentum CPE HG100 HM100] inherits = *PET* +filament_cost = 0 +filament_density = 1.25 filament_notes = "CPE HG100 , CPE HM100" first_layer_bed_temperature = 90 first_layer_temperature = 275 @@ -788,6 +787,8 @@ inherits = *PLA* # For now, all but selected filaments are disabled for the MMU 2.0 compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) extrusion_multiplier = 1.2 +filament_cost = 0 +filament_density = 1.15 filament_colour = #804040 filament_max_volumetric_speed = 10 first_layer_temperature = 190 @@ -796,14 +797,20 @@ temperature = 190 [filament:Generic ABS] inherits = *ABS* +filament_cost = 0 +filament_density = 1.04 filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladeč ABS" [filament:Generic PET] inherits = *PET* +filament_cost = 0 +filament_density = 1.24 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" [filament:Generic PLA] inherits = *PLA* +filament_cost = 0 +filament_density = 1.27 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" [filament:Polymaker PC-Max] @@ -830,8 +837,25 @@ temperature = 195 [filament:Prusa ABS] inherits = *ABS* +filament_cost = 27.82 +filament_density = 1.08 filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladeč ABS" +[filament:*ABS MMU2*] +inherits = Prusa ABS +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material +filament_cooling_final_speed = 50 +filament_cooling_initial_speed = 10 +filament_cooling_moves = 5 +filament_loading_speed = 14 +filament_ramming_parameters = "120 110 5.32258 5.45161 5.67742 6 6.48387 7.12903 7.90323 8.70968 9.3871 9.83871 10.0968 10.2258| 0.05 5.30967 0.45 5.50967 0.95 6.1871 1.45 7.39677 1.95 9.05484 2.45 10 2.95 10.3098 3.45 13.0839 3.95 7.6 4.45 7.6 4.95 7.6"; + +[filament:Generic ABS MMU2] +inherits = *ABS MMU2* + +[filament:Prusa ABS MMU2] +inherits = *ABS MMU2* + [filament:Prusa HIPS] inherits = *ABS* bridge_fan_speed = 50 @@ -850,10 +874,14 @@ temperature = 220 [filament:Prusa PET] inherits = *PET* +filament_cost = 27.82 +filament_density = 1.27 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladeč PETG" [filament:Prusa PLA] inherits = *PLA* +filament_cost = 25.4 +filament_density = 1.24 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladeč PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" [filament:*PLA MMU2*] @@ -873,6 +901,8 @@ inherits = *PLA MMU2* [filament:SemiFlex or Flexfill 98A] inherits = *FLEX* +filament_cost = 0 +filament_density = 1.22 [filament:Taulman Bridge] inherits = *common* @@ -956,7 +986,7 @@ extruder_offset = 0x0 gcode_flavor = marlin silent_mode = 0 machine_max_acceleration_e = 10000 -machine_max_acceleration_extruding = 1500 +machine_max_acceleration_extruding = 2000 machine_max_acceleration_retracting = 1500 machine_max_acceleration_x = 9000 machine_max_acceleration_y = 9000 @@ -1118,11 +1148,11 @@ start_gcode = M115 U3.2.1 ; tell printer latest fw version\nM83 ; extruder rela [printer:Original Prusa i3 MK3] inherits = *common* end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors -machine_max_acceleration_e = 9000,9000 -machine_max_acceleration_extruding = 1250,960 +machine_max_acceleration_e = 5000,5000 +machine_max_acceleration_extruding = 1250,1250 machine_max_acceleration_retracting = 1250,1250 -machine_max_acceleration_x = 1000,1000 -machine_max_acceleration_y = 1000,1000 +machine_max_acceleration_x = 1000,960 +machine_max_acceleration_y = 1000,960 machine_max_acceleration_z = 1000,1000 machine_max_feedrate_e = 120,120 machine_max_feedrate_x = 200,172 @@ -1167,8 +1197,8 @@ cooling_tube_retraction = 30 parking_pos_retraction = 85 retract_length_toolchange = 3 extra_loading_move = -13 -printer_model = MK3MM2 -default_print_profile = 0.15mm OPTIMAL MK3 MMU2 +printer_model = MK3MMU2 +default_print_profile = 0.15mm OPTIMAL MK3 default_filament_profile = Prusa PLA MMU2 [printer:Original Prusa i3 MK3 MMU2 Single] @@ -1182,11 +1212,11 @@ inherits = *mm2* # (for example the retract values) are duplicaed from the first value, so they do not need # to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 -extruder_colour = #FFFF00;#FFFFFF;#804040;#0000FF;#C0C0C0 +extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F start_gcode = M107\n\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = G1 E-15.0000 F3000\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors # The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. [obsolete_presets] -print="0.05mm DETAIL 0.25 nozzle";"0.05mm DETAIL MK3";"0.05mm DETAIL";"0.20mm NORMAL MK3";"0.35mm FAST MK3" +print="0.05mm DETAIL 0.25 nozzle";"0.05mm DETAIL MK3";"0.05mm DETAIL";"0.20mm NORMAL MK3";"0.35mm FAST MK3";"print:0.15mm OPTIMAL MK3 MMU2";"print:0.20mm FAST MK3 MMU2" filament="ColorFabb Brass Bronze 1.75mm";"ColorFabb HT 1.75mm";"ColorFabb nGen 1.75mm";"ColorFabb Woodfil 1.75mm";"ColorFabb XT 1.75mm";"ColorFabb XT-CF20 1.75mm";"E3D PC-ABS 1.75mm";"Fillamentum ABS 1.75mm";"Fillamentum ASA 1.75mm";"Generic ABS 1.75mm";"Generic PET 1.75mm";"Generic PLA 1.75mm";"Prusa ABS 1.75mm";"Prusa HIPS 1.75mm";"Prusa PET 1.75mm";"Prusa PLA 1.75mm";"Taulman Bridge 1.75mm";"Taulman T-Glase 1.75mm" From f364bd1884c318c68be9d0ee4529848919ced917 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 Jul 2018 17:31:30 +0200 Subject: [PATCH 040/119] New object function considering item size categories (big and small) --- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 26 ++--- xs/src/libslic3r/Model.cpp | 99 ++++++++++++++----- 2 files changed, 91 insertions(+), 34 deletions(-) diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 9e8b3be91b..61d923b873 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -78,7 +78,7 @@ struct NfpPConfig { * into the bin. * */ - std::function&, const _Item&, + std::function&, const _Item&, double, double, double)> object_function; @@ -163,18 +163,22 @@ template class EdgeCache { void fetchCorners() const { if(!contour_.corners.empty()) return; - // TODO Accuracy - contour_.corners = contour_.distances; - for(auto& d : contour_.corners) d /= contour_.full_distance; + contour_.corners.reserve(contour_.distances.size() / 3 + 1); + for(size_t i = 0; i < contour_.distances.size() - 1; i += 3) { + contour_.corners.emplace_back( + contour_.distances.at(i) / contour_.full_distance); + } } void fetchHoleCorners(unsigned hidx) const { auto& hc = holes_[hidx]; if(!hc.corners.empty()) return; - // TODO Accuracy - hc.corners = hc.distances; - for(auto& d : hc.corners) d /= hc.full_distance; + hc.corners.reserve(hc.distances.size() / 3 + 1); + for(size_t i = 0; i < hc.distances.size() - 1; i += 3) { + hc.corners.emplace_back( + hc.distances.at(i) / hc.full_distance); + } } inline Vertex coords(const ContourCache& cache, double distance) const { @@ -433,7 +437,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer, public: - using Pile = const Nfp::Shapes&; + using Pile = Nfp::Shapes; inline explicit _NofitPolyPlacer(const BinType& bin): Base(bin), @@ -536,7 +540,7 @@ public: // customizable by the library client auto _objfunc = config_.object_function? config_.object_function : - [this](const Nfp::Shapes& pile, Item, + [this](Nfp::Shapes& pile, Item, double occupied_area, double /*norm*/, double penality) { @@ -565,14 +569,14 @@ public: d += startpos; item.translation(d); - pile.emplace_back(item.transformedShape()); +// pile.emplace_back(item.transformedShape()); double occupied_area = pile_area + item.area(); double score = _objfunc(pile, item, occupied_area, norm_, penality_); - pile.pop_back(); +// pile.pop_back(); return score; }; diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 4b61e1c9ae..90ebface2a 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -529,7 +529,6 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // handle different rotations // arranger.useMinimumBoundigBoxRotation(); pcfg.rotations = { 0.0 }; - double norm_2 = std::nan(""); // Magic: we will specify what is the goal of arrangement... In this case // we override the default object function to make the larger items go into @@ -538,8 +537,8 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // We alse sacrafice a bit of pack efficiency for this to work. As a side // effect, the arrange procedure is a lot faster (we do not need to // calculate the convex hulls) - pcfg.object_function = [bin, hasbin, &norm_2]( - NfpPlacer::Pile pile, // The currently arranged pile + pcfg.object_function = [bin, hasbin]( + NfpPlacer::Pile& pile, // The currently arranged pile Item item, double /*area*/, // Sum area of items (not needed) double norm, // A norming factor for physical dimensions @@ -547,37 +546,91 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, { using pl = PointLike; - auto bb = ShapeLike::boundingBox(pile); + static const double BIG_ITEM_TRESHOLD = 0.2; + static const double GRAVITY_RATIO = 0.5; + static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO; + + // We will treat big items (compared to the print bed) differently + NfpPlacer::Pile bigs; + bigs.reserve(pile.size()); + for(auto& p : pile) { + auto pbb = ShapeLike::boundingBox(p); + auto na = std::sqrt(pbb.width()*pbb.height())/norm; + if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p); + } + + // Candidate item bounding box auto ibb = item.boundingBox(); - auto minc = ibb.minCorner(); - auto maxc = ibb.maxCorner(); - if(std::isnan(norm_2)) norm_2 = pow(norm, 2); + // Calculate the full bounding box of the pile with the candidate item + pile.emplace_back(item.transformedShape()); + auto fullbb = ShapeLike::boundingBox(pile); + pile.pop_back(); - // We get the distance of the reference point from the center of the - // heat bed - auto cc = bb.center(); - auto top_left = PointImpl{getX(minc), getY(maxc)}; - auto bottom_right = PointImpl{getX(maxc), getY(minc)}; + // The bounding box of the big items (they will accumulate in the center + // of the pile + auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs); - auto a = pl::distance(ibb.maxCorner(), cc); - auto b = pl::distance(ibb.minCorner(), cc); - auto c = pl::distance(ibb.center(), cc); - auto d = pl::distance(top_left, cc); - auto e = pl::distance(bottom_right, cc); + // The size indicator of the candidate item. This is not the area, + // but almost... + auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm; - auto area = bb.width() * bb.height() / norm_2; + // Will hold the resulting score + double score = 0; - auto min_dist = std::min({a, b, c, d, e}) / norm; + if(itemnormarea > BIG_ITEM_TRESHOLD) { + // This branch is for the bigger items.. + // Here we will use the closest point of the item bounding box to + // the already arranged pile. So not the bb center nor the a choosen + // corner but whichever is the closest to the center. This will + // prevent unwanted strange arrangements. - // The score will be the normalized distance which will be minimized, - // effectively creating a circle shaped pile of items - double score = 0.8*min_dist + 0.2*area; + auto minc = ibb.minCorner(); // bottom left corner + auto maxc = ibb.maxCorner(); // top right corner + + // top left and bottom right corners + auto top_left = PointImpl{getX(minc), getY(maxc)}; + auto bottom_right = PointImpl{getX(maxc), getY(minc)}; + + auto cc = fullbb.center(); // The gravity center + + // Now the distnce of the gravity center will be calculated to the + // five anchor points and the smallest will be chosen. + std::array dists; + dists[0] = pl::distance(minc, cc); + dists[1] = pl::distance(maxc, cc); + dists[2] = pl::distance(ibb.center(), cc); + dists[3] = pl::distance(top_left, cc); + dists[4] = pl::distance(bottom_right, cc); + + auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; + + // Density is the pack density: how big is the arranged pile + auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; + + // The score is a weighted sum of the distance from pile center + // and the pile size + score = GRAVITY_RATIO * dist + DENSITY_RATIO * density; + std::cout << "big " << std::endl; + + } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { + // If there are no big items, only small, we should consider the + // density here as well to not get silly results + auto bindist = pl::distance(ibb.center(), bin.center()) / norm; + auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; + score = GRAVITY_RATIO* bindist + DENSITY_RATIO * density; + } else { + // Here there are the small items that should be placed around the + // already processed bigger items. + // No need to play around with the anchor points, the center will be + // just fine for small items + score = pl::distance(ibb.center(), bigbb.center()) / norm; + } // If it does not fit into the print bed we will beat it // with a large penality. If we would not do this, there would be only // one big pile that doesn't care whether it fits onto the print bed. - if(!NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score; + if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score; return score; }; From db8ba5fb76c285bfde0e16620cf8d1b3cb0ca25b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 27 Jul 2018 22:19:46 +0200 Subject: [PATCH 041/119] New parameter "single_extruder_multi_material_priming" to be able to suppress the MM priming towers. The PrusaResearch.ini was modified for the MMU2 printers to correctly prime the initial extruder when single_extruder_multi_material_priming is disabled. --- resources/profiles/PrusaResearch.ini | 8 ++-- xs/src/libslic3r/GCode.cpp | 61 ++++++++++++++++------------ xs/src/libslic3r/Print.cpp | 7 +--- xs/src/libslic3r/PrintConfig.cpp | 6 +++ xs/src/libslic3r/PrintConfig.hpp | 2 + xs/src/slic3r/GUI/Preset.cpp | 4 +- xs/src/slic3r/GUI/Tab.cpp | 1 + 7 files changed, 51 insertions(+), 38 deletions(-) diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 6817c87127..8e0ab024b6 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.2.0-alpha3 +config_version = 0.2.0-alpha4 # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -1069,7 +1069,7 @@ end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes. extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 nozzle_diameter = 0.4,0.4,0.4,0.4 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN -start_gcode = M115 U3.1.0 ; tell printer latest fw version\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_single_extruder_multi_material_priming}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 variable_layer_height = 0 default_print_profile = 0.15mm OPTIMAL @@ -1213,8 +1213,8 @@ inherits = *mm2* # to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\n\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n -end_gcode = G1 E-15.0000 F3000\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +start_gcode = M107\n\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n{if not has_single_extruder_multi_material_priming}\n; go outside print area\nG1 Y-3.0 F1000.0 \nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X50 E15 F1073\nG1 X100 E10 F2000\nG1 Z0.3 F1000\nG92 E0.0\nG1 X240.0 E15.0 F2400.0 \nG1 Y-2.0 F1000.0\nG1 X100.0 E10 F1400.0 \nG1 Z0.20 F1000\nG1 X0.0 E4 F1000.0\nG92 E0.0\n{endif}\n\n;M221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n # The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. [obsolete_presets] diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 94634f4e42..308b1ea046 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -608,15 +608,18 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1) break; } - } - else { + } else { // Find tool ordering for all the objects at once, and the initial extruder ID. // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it. tool_ordering = print.m_tool_ordering.empty() ? ToolOrdering(print, initial_extruder_id) : print.m_tool_ordering; - initial_extruder_id = tool_ordering.first_extruder(); has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); + initial_extruder_id = (has_wipe_tower && ! print.config.single_extruder_multi_material_priming) ? + // The priming towers will be skipped. + tool_ordering.all_extruders().back() : + // Don't skip the priming towers. + tool_ordering.first_extruder(); } if (initial_extruder_id == (unsigned int)-1) { // Nothing to print! @@ -644,6 +647,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) m_placeholder_parser.set("current_object_idx", 0); // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); + m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config.single_extruder_multi_material_priming); std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id); // Set bed temperature if the start G-code does not contain any bed temp control G-codes. @@ -724,8 +728,11 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) } } - // Set initial extruder only after custom start G-code. - _write(file, this->set_extruder(initial_extruder_id)); + if (! (has_wipe_tower && print.config.single_extruder_multi_material_priming)) { + // Set initial extruder only after custom start G-code. + // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed. + _write(file, this->set_extruder(initial_extruder_id)); + } // Do all objects for each layer. if (print.config.complete_objects.value) { @@ -803,27 +810,29 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) if (has_wipe_tower && ! layers_to_print.empty()) { m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get())); _write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height")); - _write(file, m_wipe_tower->prime(*this)); - // Verify, whether the print overaps the priming extrusions. - BoundingBoxf bbox_print(get_print_extrusions_extents(print)); - coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; - for (const PrintObject *print_object : printable_objects) - bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz)); - bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz)); - BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print)); - bbox_prime.offset(0.5f); - // Beep for 500ms, tone 800Hz. Yet better, play some Morse. - _write(file, this->retract()); - _write(file, "M300 S800 P500\n"); - if (bbox_prime.overlap(bbox_print)) { - // Wait for the user to remove the priming extrusions, otherwise they would - // get covered by the print. - _write(file, "M1 Remove priming towers and click button.\n"); - } - else { - // Just wait for a bit to let the user check, that the priming succeeded. - //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. - _write(file, "M1 S10\n"); + if (print.config.single_extruder_multi_material_priming) { + _write(file, m_wipe_tower->prime(*this)); + // Verify, whether the print overaps the priming extrusions. + BoundingBoxf bbox_print(get_print_extrusions_extents(print)); + coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; + for (const PrintObject *print_object : printable_objects) + bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz)); + bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz)); + BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print)); + bbox_prime.offset(0.5f); + // Beep for 500ms, tone 800Hz. Yet better, play some Morse. + _write(file, this->retract()); + _write(file, "M300 S800 P500\n"); + if (bbox_prime.overlap(bbox_print)) { + // Wait for the user to remove the priming extrusions, otherwise they would + // get covered by the print. + _write(file, "M1 Remove priming towers and click button.\n"); + } + else { + // Just wait for a bit to let the user check, that the priming succeeded. + //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. + _write(file, "M1 S10\n"); + } } } // Extrude the layers. diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index e08ae1fc4b..8c91eb1925 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -155,6 +155,7 @@ bool Print::invalidate_state_by_config_options(const std::vectorplaceholder_parser.update_timestamp(); @@ -1225,7 +1222,6 @@ void Print::set_status(int percent, const std::string &message) printf("Print::status %d => %s\n", percent, message.c_str()); } - // Returns extruder this eec should be printed with, according to PrintRegion config int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion ®ion) { @@ -1233,5 +1229,4 @@ int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion std::max(region.config.perimeter_extruder.value - 1, 0); } - } diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 7064e19fec..d80347a4d8 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1623,6 +1623,12 @@ PrintConfigDef::PrintConfigDef() def->cli = "single-extruder-multi-material!"; def->default_value = new ConfigOptionBool(false); + def = this->add("single_extruder_multi_material_priming", coBool); + def->label = L("Prime all printing extruders"); + def->tooltip = L("If enabled, all printing extruders will be primed at the front edge of the print bed at the start of the print."); + def->cli = "single-extruder-multi-material-priming!"; + def->default_value = new ConfigOptionBool(true); + def = this->add("support_material", coBool); def->label = L("Generate support material"); def->category = L("Support material"); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index c530868a12..3848ba55bf 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -553,6 +553,7 @@ public: ConfigOptionString start_gcode; ConfigOptionStrings start_filament_gcode; ConfigOptionBool single_extruder_multi_material; + ConfigOptionBool single_extruder_multi_material_priming; ConfigOptionString toolchange_gcode; ConfigOptionFloat travel_speed; ConfigOptionBool use_firmware_retraction; @@ -612,6 +613,7 @@ protected: OPT_PTR(retract_restart_extra_toolchange); OPT_PTR(retract_speed); OPT_PTR(single_extruder_multi_material); + OPT_PTR(single_extruder_multi_material_priming); OPT_PTR(start_gcode); OPT_PTR(start_filament_gcode); OPT_PTR(toolchange_gcode); diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 49e235146c..de9b59a958 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -303,8 +303,8 @@ const std::vector& Preset::print_options() "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", - "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "compatible_printers", - "compatible_printers_condition","inherits" + "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "single_extruder_multi_material_priming", + "compatible_printers", "compatible_printers_condition","inherits" }; return s_opts; } diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 187030f31b..70f3bf8bee 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -919,6 +919,7 @@ void TabPrint::build() optgroup->append_single_option_line("wipe_tower_width"); optgroup->append_single_option_line("wipe_tower_rotation_angle"); optgroup->append_single_option_line("wipe_tower_bridging"); + optgroup->append_single_option_line("single_extruder_multi_material_priming"); optgroup = page->new_optgroup(_(L("Advanced"))); optgroup->append_single_option_line("interface_shells"); From 4a88075334de19b6861086bbfaae32a6130245ae Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 27 Jul 2018 22:31:24 +0200 Subject: [PATCH 042/119] Updated change log for the Prusa3D config index, bumped up the version to 1.41.0-alpha3 --- resources/profiles/PrusaResearch.idx | 2 +- xs/src/libslic3r/libslic3r.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 3652c0174c..9d214ce405 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,5 +1,5 @@ min_slic3r_version = 1.41.0-alpha -0.2.0-alpha3 +0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost, 0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material 0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 77006cebe0..2f07ca51a3 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -14,7 +14,7 @@ #include #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" -#define SLIC3R_VERSION "1.41.0-alpha2" +#define SLIC3R_VERSION "1.41.0-alpha3" #define SLIC3R_BUILD "UNKNOWN" typedef int32_t coord_t; From b6c5fa7f687ce4ff41ff20529ccbd99b6c8c72fb Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 27 Jul 2018 23:28:59 +0200 Subject: [PATCH 043/119] Added the alpha4 config to the index. --- resources/profiles/PrusaResearch.idx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 9d214ce405..db5a17b8dc 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,5 +1,6 @@ min_slic3r_version = 1.41.0-alpha -0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost, +0.2.0-alpha4 Extended the custom start/end G-codes of the MMU2.0 printers for no priming towers. +0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost 0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material 0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters From bf4871d7f8b5090ec9ce47a9ba168c6d7c09b8ee Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Jul 2018 09:09:14 +0200 Subject: [PATCH 044/119] Improved remove hovering on objects when mouse leaves 3D scene --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 6396233e81..46bcf4f445 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -2697,9 +2697,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } else if (evt.Leaving()) { - // to remove hover when mouse goes out of this canvas - m_mouse.position = Pointf((coordf_t)pos.x, (coordf_t)pos.y); - render(); + // to remove hover on objects when the mouse goes out of this canvas + m_mouse.position = Pointf(-1.0, -1.0); + m_dirty = true; } else if (evt.LeftDClick() && (m_hover_volume_id != -1)) m_on_double_click_callback.call(); @@ -3403,20 +3403,22 @@ void GLCanvas3D::_picking_pass() const if (m_multisample_allowed) ::glEnable(GL_MULTISAMPLE); - const Size& cnv_size = get_canvas_size(); - - GLubyte color[4]; - ::glReadPixels(pos.x, cnv_size.get_height() - pos.y - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color); - int volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256; - - m_hover_volume_id = -1; - + int volume_id = -1; for (GLVolume* vol : m_volumes.volumes) { vol->hover = false; } - if (volume_id < (int)m_volumes.volumes.size()) + GLubyte color[4] = { 0, 0, 0, 0 }; + const Size& cnv_size = get_canvas_size(); + bool inside = (0 <= pos.x) && (pos.x < cnv_size.get_width()) && (0 <= pos.y) && (pos.y < cnv_size.get_height()); + if (inside) + { + ::glReadPixels(pos.x, cnv_size.get_height() - pos.y - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color); + volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256; + } + + if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) { m_hover_volume_id = volume_id; m_volumes.volumes[volume_id]->hover = true; @@ -3432,7 +3434,10 @@ void GLCanvas3D::_picking_pass() const m_gizmos.set_hover_id(-1); } else - m_gizmos.set_hover_id(254 - (int)color[2]); + { + m_hover_volume_id = -1; + m_gizmos.set_hover_id(inside ? (254 - (int)color[2]) : -1); + } // updates gizmos overlay if (_get_first_selected_object_id() != -1) From 11b0325c66b7851be91a5564d33b1c3928051d70 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Jul 2018 10:03:17 +0200 Subject: [PATCH 045/119] Fixed calculation of bed origin in bed shape dialog --- xs/src/slic3r/GUI/BedShapeDialog.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/xs/src/slic3r/GUI/BedShapeDialog.cpp b/xs/src/slic3r/GUI/BedShapeDialog.cpp index 3dd60ef88a..d525355897 100644 --- a/xs/src/slic3r/GUI/BedShapeDialog.cpp +++ b/xs/src/slic3r/GUI/BedShapeDialog.cpp @@ -9,6 +9,8 @@ #include "Model.hpp" #include "boost/nowide/iostream.hpp" +#include + namespace Slic3r { namespace GUI { @@ -146,21 +148,18 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points) if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { // okay, it's a rectangle // find origin - // the || 0 hack prevents "-0" which might confuse the user - int x_min, x_max, y_min, y_max; - x_max = x_min = points->values[0].x; + coordf_t x_min, x_max, y_min, y_max; + x_max = x_min = points->values[0].x; y_max = y_min = points->values[0].y; - for (auto pt : points->values){ - if (x_min > pt.x) x_min = pt.x; - if (x_max < pt.x) x_max = pt.x; - if (y_min > pt.y) y_min = pt.y; - if (y_max < pt.y) y_max = pt.y; - } - if (x_min < 0) x_min = 0; - if (x_max < 0) x_max = 0; - if (y_min < 0) y_min = 0; - if (y_max < 0) y_max = 0; - auto origin = new ConfigOptionPoints{ Pointf(-x_min, -y_min) }; + for (auto pt : points->values) + { + x_min = std::min(x_min, pt.x); + x_max = std::max(x_max, pt.x); + y_min = std::min(y_min, pt.y); + y_max = std::max(y_max, pt.y); + } + + auto origin = new ConfigOptionPoints{ Pointf(-x_min, -y_min) }; m_shape_options_book->SetSelection(SHAPE_RECTANGULAR); auto optgroup = m_optgroups[SHAPE_RECTANGULAR]; From 3f6d3b903d836e78047af5b3824d7a2ffb526896 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Jul 2018 10:35:08 +0200 Subject: [PATCH 046/119] Fixed rotation of 3D view camera after change of bed data --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 46bcf4f445..872ae2dc67 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1947,6 +1947,12 @@ void GLCanvas3D::set_bed_shape(const Pointfs& shape) m_axes.origin = Pointf3(0.0, 0.0, (coordf_t)GROUND_Z); set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size()); + // forces the selection of the proper camera target + if (m_volumes.volumes.empty()) + zoom_to_bed(); + else + zoom_to_volumes(); + m_dirty = true; } From df201d65f406a23cbd85da159abbd93147640391 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Jul 2018 11:38:36 +0200 Subject: [PATCH 047/119] Minimum z of object to lay on the bed after rotations. Fixes #1093 --- xs/src/libslic3r/Model.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index d6f1f05c96..2f73cad200 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -1109,9 +1109,23 @@ void ModelObject::scale(const Pointf3 &versor) void ModelObject::rotate(float angle, const Axis &axis) { + float min_z = FLT_MAX; for (ModelVolume *v : this->volumes) + { v->mesh.rotate(angle, axis); - this->origin_translation = Pointf3(0,0,0); + min_z = std::min(min_z, v->mesh.stl.stats.min.z); + } + + if (min_z != 0.0f) + { + // translate the object so that its minimum z lays on the bed + for (ModelVolume *v : this->volumes) + { + v->mesh.translate(0.0f, 0.0f, -min_z); + } + } + + this->origin_translation = Pointf3(0, 0, 0); this->invalidate_bounding_box(); } From 8e433c32bf76af13ba55ded28ee208725e95e7eb Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Jul 2018 12:08:26 +0200 Subject: [PATCH 048/119] Time estimator: added placeholder to process gcode lines T --- xs/src/libslic3r/GCodeTimeEstimator.cpp | 36 +++++++++++++++++++++++++ xs/src/libslic3r/GCodeTimeEstimator.hpp | 8 ++++++ 2 files changed, 44 insertions(+) diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp index 5543b5cc99..909cdc26c1 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.cpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp @@ -535,6 +535,21 @@ namespace Slic3r { _state.g1_line_id = 0; } + void GCodeTimeEstimator::set_extruder_id(unsigned int id) + { + _state.extruder_id = id; + } + + unsigned int GCodeTimeEstimator::get_extruder_id() const + { + return _state.extruder_id; + } + + void GCodeTimeEstimator::reset_extruder_id() + { + _state.extruder_id = 0; + } + void GCodeTimeEstimator::add_additional_time(float timeSec) { PROFILE_FUNC(); @@ -613,6 +628,7 @@ namespace Slic3r { set_additional_time(0.0f); + reset_extruder_id(); reset_g1_line_id(); _g1_line_ids.clear(); @@ -780,6 +796,11 @@ namespace Slic3r { } } + break; + } + case 'T': // Select Tools + { + _processT(line); break; } } @@ -1223,6 +1244,21 @@ namespace Slic3r { set_axis_max_jerk(E, line.e() * MMMIN_TO_MMSEC); } + void GCodeTimeEstimator::_processT(const GCodeReader::GCodeLine& line) + { + std::string cmd = line.cmd(); + if (cmd.length() > 1) + { + unsigned int id = (unsigned int)::strtol(cmd.substr(1).c_str(), nullptr, 10); + if (get_extruder_id() != id) + { + set_extruder_id(id); + + // ADD PROCESSING HERE + } + } + } + void GCodeTimeEstimator::_simulate_st_synchronize() { PROFILE_FUNC(); diff --git a/xs/src/libslic3r/GCodeTimeEstimator.hpp b/xs/src/libslic3r/GCodeTimeEstimator.hpp index 2dfefda0b0..f6194a170f 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.hpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.hpp @@ -80,6 +80,7 @@ namespace Slic3r { float minimum_travel_feedrate; // mm/s float extrude_factor_override_percentage; unsigned int g1_line_id; + unsigned int extruder_id; }; public: @@ -300,6 +301,10 @@ namespace Slic3r { void increment_g1_line_id(); void reset_g1_line_id(); + void set_extruder_id(unsigned int id); + unsigned int get_extruder_id() const; + void reset_extruder_id(); + void add_additional_time(float timeSec); void set_additional_time(float timeSec); float get_additional_time() const; @@ -383,6 +388,9 @@ namespace Slic3r { // Set allowable instantaneous speed change void _processM566(const GCodeReader::GCodeLine& line); + // Processes T line (Select Tool) + void _processT(const GCodeReader::GCodeLine& line); + // Simulates firmware st_synchronize() call void _simulate_st_synchronize(); From 09de343e6592e8f0dc1210f5cff545e37d34c23b Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Jul 2018 13:54:54 +0200 Subject: [PATCH 049/119] Fixed slice info after re-export of gcode. Fixes #1081 --- lib/Slic3r/GUI/Plater.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index a5d5eaf532..5790965ffe 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1600,7 +1600,7 @@ sub print_info_box_show { my ($self, $show) = @_; my $scrolled_window_panel = $self->{scrolled_window_panel}; my $scrolled_window_sizer = $self->{scrolled_window_sizer}; - return if $scrolled_window_sizer->IsShown(2) == $show; + return if (!$show && ($scrolled_window_sizer->IsShown(2) == $show)); if ($show) { my $print_info_sizer = $self->{print_info_sizer}; @@ -1836,6 +1836,8 @@ sub update { $self->resume_background_process; } + $self->print_info_box_show(0); + # $self->{canvas}->reload_scene if $self->{canvas}; my $selections = $self->collect_selections; Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); From 2f7876b8522c83c0d04595d48f5f5a6efb22d729 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 30 Jul 2018 13:57:05 +0200 Subject: [PATCH 050/119] Fixed camera jump after object rotate --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 872ae2dc67..f8dd94fd4b 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1941,17 +1941,22 @@ void GLCanvas3D::set_model(Model* model) void GLCanvas3D::set_bed_shape(const Pointfs& shape) { - m_bed.set_shape(shape); + bool new_shape = (shape != m_bed.get_shape()); + if (new_shape) + m_bed.set_shape(shape); // Set the origin and size for painting of the coordinate system axes. m_axes.origin = Pointf3(0.0, 0.0, (coordf_t)GROUND_Z); set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size()); - // forces the selection of the proper camera target - if (m_volumes.volumes.empty()) - zoom_to_bed(); - else - zoom_to_volumes(); + if (new_shape) + { + // forces the selection of the proper camera target + if (m_volumes.volumes.empty()) + zoom_to_bed(); + else + zoom_to_volumes(); + } m_dirty = true; } From d136d61edded36ad36f17ee14b4b954a55d69db5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 30 Jul 2018 15:16:44 +0200 Subject: [PATCH 051/119] linest2d ready for arbitrary shaped beds. --- xs/src/libnest2d/examples/main.cpp | 123 +++++++++++++---- xs/src/libnest2d/libnest2d/boost_alg.hpp | 2 +- .../libnest2d/libnest2d/geometry_traits.hpp | 77 ++++++++++- xs/src/libnest2d/libnest2d/libnest2d.hpp | 11 ++ .../libnest2d/libnest2d/placers/nfpplacer.hpp | 130 ++++++++++++------ .../libnest2d/selections/djd_heuristic.hpp | 2 +- .../libnest2d/selections/firstfit.hpp | 2 +- xs/src/libslic3r/Model.cpp | 5 +- 8 files changed, 275 insertions(+), 77 deletions(-) diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp index a97618578c..883d12610b 100644 --- a/xs/src/libnest2d/examples/main.cpp +++ b/xs/src/libnest2d/examples/main.cpp @@ -544,57 +544,126 @@ void arrangeRectangles() { // input.insert(input.end(), proba.begin(), proba.end()); // input.insert(input.end(), crasher.begin(), crasher.end()); - Box bin(250*SCALE, 210*SCALE); +// Box bin(250*SCALE, 210*SCALE); + PolygonImpl bin = { + { + {25*SCALE, 0}, + {0, 25*SCALE}, + {0, 225*SCALE}, + {25*SCALE, 250*SCALE}, + {225*SCALE, 250*SCALE}, + {250*SCALE, 225*SCALE}, + {250*SCALE, 25*SCALE}, + {225*SCALE, 0}, + {25*SCALE, 0} + }, + {} + }; auto min_obj_distance = static_cast(0*SCALE); - using Placer = NfpPlacer; + using Placer = strategies::_NofitPolyPlacer; using Packer = Arranger; Packer arrange(bin, min_obj_distance); Packer::PlacementConfig pconf; pconf.alignment = Placer::Config::Alignment::CENTER; - pconf.starting_point = Placer::Config::Alignment::BOTTOM_LEFT; + pconf.starting_point = Placer::Config::Alignment::CENTER; pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/}; + pconf.accuracy = 1.0; - double norm_2 = std::nan(""); - pconf.object_function = [&bin, &norm_2](Placer::Pile pile, const Item& item, + auto bincenter = ShapeLike::boundingBox(bin).center(); + pconf.object_function = [&bin, bincenter]( + Placer::Pile pile, const Item& item, double /*area*/, double norm, double penality) { using pl = PointLike; - auto bb = ShapeLike::boundingBox(pile); + static const double BIG_ITEM_TRESHOLD = 0.2; + static const double GRAVITY_RATIO = 0.5; + static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO; + + // We will treat big items (compared to the print bed) differently + NfpPlacer::Pile bigs; + bigs.reserve(pile.size()); + for(auto& p : pile) { + auto pbb = ShapeLike::boundingBox(p); + auto na = std::sqrt(pbb.width()*pbb.height())/norm; + if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p); + } + + // Candidate item bounding box auto ibb = item.boundingBox(); - auto minc = ibb.minCorner(); - auto maxc = ibb.maxCorner(); - if(std::isnan(norm_2)) norm_2 = pow(norm, 2); + // Calculate the full bounding box of the pile with the candidate item + pile.emplace_back(item.transformedShape()); + auto fullbb = ShapeLike::boundingBox(pile); + pile.pop_back(); - // We get the distance of the reference point from the center of the - // heat bed - auto cc = bb.center(); - auto top_left = PointImpl{getX(minc), getY(maxc)}; - auto bottom_right = PointImpl{getX(maxc), getY(minc)}; + // The bounding box of the big items (they will accumulate in the center + // of the pile + auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs); - auto a = pl::distance(ibb.maxCorner(), cc); - auto b = pl::distance(ibb.minCorner(), cc); - auto c = pl::distance(ibb.center(), cc); - auto d = pl::distance(top_left, cc); - auto e = pl::distance(bottom_right, cc); + // The size indicator of the candidate item. This is not the area, + // but almost... + auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm; - auto area = bb.width() * bb.height() / norm_2; + // Will hold the resulting score + double score = 0; - auto min_dist = std::min({a, b, c, d, e}) / norm; + if(itemnormarea > BIG_ITEM_TRESHOLD) { + // This branch is for the bigger items.. + // Here we will use the closest point of the item bounding box to + // the already arranged pile. So not the bb center nor the a choosen + // corner but whichever is the closest to the center. This will + // prevent unwanted strange arrangements. - // The score will be the normalized distance which will be minimized, - // effectively creating a circle shaped pile of items - double score = 0.8*min_dist + 0.2*area; + auto minc = ibb.minCorner(); // bottom left corner + auto maxc = ibb.maxCorner(); // top right corner + + // top left and bottom right corners + auto top_left = PointImpl{getX(minc), getY(maxc)}; + auto bottom_right = PointImpl{getX(maxc), getY(minc)}; + + auto cc = fullbb.center(); // The gravity center + + // Now the distnce of the gravity center will be calculated to the + // five anchor points and the smallest will be chosen. + std::array dists; + dists[0] = pl::distance(minc, cc); + dists[1] = pl::distance(maxc, cc); + dists[2] = pl::distance(ibb.center(), cc); + dists[3] = pl::distance(top_left, cc); + dists[4] = pl::distance(bottom_right, cc); + + auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; + + // Density is the pack density: how big is the arranged pile + auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; + + // The score is a weighted sum of the distance from pile center + // and the pile size + score = GRAVITY_RATIO * dist + DENSITY_RATIO * density; + + } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { + // If there are no big items, only small, we should consider the + // density here as well to not get silly results + auto bindist = pl::distance(ibb.center(), bincenter) / norm; + auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; + score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density; + } else { + // Here there are the small items that should be placed around the + // already processed bigger items. + // No need to play around with the anchor points, the center will be + // just fine for small items + score = pl::distance(ibb.center(), bigbb.center()) / norm; + } // If it does not fit into the print bed we will beat it // with a large penality. If we would not do this, there would be only // one big pile that doesn't care whether it fits onto the print bed. - if(!NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score; + if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score; return score; }; @@ -638,7 +707,7 @@ void arrangeRectangles() { std::vector eff; eff.reserve(result.size()); - auto bin_area = double(bin.height()*bin.width()); + auto bin_area = ShapeLike::area(bin); for(auto& r : result) { double a = 0; std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); }); @@ -673,7 +742,7 @@ void arrangeRectangles() { SVGWriter::Config conf; conf.mm_in_coord_units = SCALE; SVGWriter svgw(conf); - svgw.setSize(bin); + svgw.setSize(Box(250*SCALE, 210*SCALE)); svgw.writePackGroup(result); // std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);}); svgw.save("out"); diff --git a/xs/src/libnest2d/libnest2d/boost_alg.hpp b/xs/src/libnest2d/libnest2d/boost_alg.hpp index 422616d203..67e19fcbdd 100644 --- a/xs/src/libnest2d/libnest2d/boost_alg.hpp +++ b/xs/src/libnest2d/libnest2d/boost_alg.hpp @@ -358,7 +358,7 @@ inline double ShapeLike::area(const PolygonImpl& shape) #endif template<> -inline bool ShapeLike::isInside(const PointImpl& point, +inline bool ShapeLike::isInside(const PointImpl& point, const PolygonImpl& shape) { return boost::geometry::within(point, shape); diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 568c0a7662..99511d7755 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -85,6 +86,31 @@ public: inline TCoord height() const BP2D_NOEXCEPT; inline RawPoint center() const BP2D_NOEXCEPT; + + inline double area() const BP2D_NOEXCEPT { + return double(width()*height()); + } +}; + +template +class _Circle { + RawPoint center_; + double radius_ = 0; +public: + + _Circle() = default; + + _Circle(const RawPoint& center, double r): center_(center), radius_(r) {} + + inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; } + inline const void center(const RawPoint& c) { center_ = c; } + + inline double radius() const BP2D_NOEXCEPT { return radius_; } + inline void radius(double r) { radius_ = r; } + + inline double area() const BP2D_NOEXCEPT { + return 2.0*Pi*radius_; + } }; /** @@ -288,8 +314,8 @@ inline RawPoint _Box::center() const BP2D_NOEXCEPT { using Coord = TCoord; RawPoint ret = { - static_cast( std::round((getX(minc) + getX(maxc))/2.0) ), - static_cast( std::round((getY(minc) + getY(maxc))/2.0) ) + static_cast( (getX(minc) + getX(maxc))/2.0 ), + static_cast( (getY(minc) + getY(maxc))/2.0 ) }; return ret; @@ -614,12 +640,34 @@ struct ShapeLike { return box; } + template + static inline _Box> boundingBox( + const _Circle>& circ) + { + using Coord = TCoord>; + TPoint pmin = { + static_cast(getX(circ.center()) - circ.radius()), + static_cast(getY(circ.center()) - circ.radius()) }; + + TPoint pmax = { + static_cast(getX(circ.center()) + circ.radius()), + static_cast(getY(circ.center()) + circ.radius()) }; + + return {pmin, pmax}; + } + template static inline double area(const _Box>& box) { return static_cast(box.width() * box.height()); } + template + static inline double area(const _Circle>& circ) + { + return circ.area(); + } + template static inline double area(const Shapes& shapes) { @@ -629,6 +677,31 @@ struct ShapeLike { }); } + template + static bool isInside(const TPoint& point, + const _Circle>& circ) + { + return PointLike::distance(point, circ.center()) < circ.radius(); + } + + template + static bool isInside(const RawShape& sh, + const _Circle>& circ) + { + return std::all_of(cbegin(sh), cend(sh), + [&circ](const TPoint& p){ + return isInside(p, circ); + }); + } + + template + static bool isInside(const _Box>& box, + const _Circle>& circ) + { + return isInside(box.minCorner(), circ) && + isInside(box.maxCorner(), circ); + } + template // Potential O(1) implementation may exist static inline TPoint& vertex(RawShape& sh, unsigned long idx) { diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index 37b5fea955..1aa6724477 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -254,7 +254,13 @@ public: return sl::isInside(transformedShape(), sh.transformedShape()); } + inline bool isInside(const RawShape& sh) const + { + return sl::isInside(transformedShape(), sh); + } + inline bool isInside(const _Box>& box) const; + inline bool isInside(const _Circle>& box) const; inline void translate(const Vertex& d) BP2D_NOEXCEPT { @@ -471,6 +477,11 @@ inline bool _Item::isInside(const _Box>& box) const { return _Item::isInside(rect); } +template inline bool +_Item::isInside(const _Circle>& circ) const { + return ShapeLike::isInside(transformedShape(), circ); +} + /** * \brief A wrapper interface (trait) class for any placement strategy provider. * diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 61d923b873..6ae71bb48e 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -46,14 +46,12 @@ struct NfpPConfig { * function you can e.g. influence the shape of the arranged pile. * * \param shapes The first parameter is a container with all the placed - * polygons including the current candidate. You can calculate a bounding - * box or convex hull on this pile of polygons. + * polygons excluding the current candidate. You can calculate a bounding + * box or convex hull on this pile of polygons without the candidate item + * or push back the candidate item into the container and then calculate + * some features. * - * \param item The second parameter is the candidate item. Note that - * calling transformedShape() on this second argument returns an identical - * shape as calling shapes.back(). These would not be the same objects only - * identical shapes! Using the second parameter is a lot faster due to - * caching some properties of the polygon (area, etc...) + * \param item The second parameter is the candidate item. * * \param occupied_area The third parameter is the sum of areas of the * items in the first parameter so you don't have to iterate through them @@ -127,6 +125,8 @@ template class EdgeCache { std::vector holes_; + double accuracy_ = 1.0; + void createCache(const RawShape& sh) { { // For the contour auto first = ShapeLike::cbegin(sh); @@ -160,11 +160,25 @@ template class EdgeCache { } } + size_t stride(const size_t N) const { + using std::ceil; + using std::round; + using std::pow; + + return static_cast( + round( N/(ceil(pow(accuracy_, 2)*(N-1)) + 1) ) + ); + } + void fetchCorners() const { if(!contour_.corners.empty()) return; - contour_.corners.reserve(contour_.distances.size() / 3 + 1); - for(size_t i = 0; i < contour_.distances.size() - 1; i += 3) { + const auto N = contour_.distances.size(); + const auto S = stride(N); + + contour_.corners.reserve(N / S + 1); + auto N_1 = N-1; + for(size_t i = 0; i < N_1; i += S) { contour_.corners.emplace_back( contour_.distances.at(i) / contour_.full_distance); } @@ -174,8 +188,11 @@ template class EdgeCache { auto& hc = holes_[hidx]; if(!hc.corners.empty()) return; - hc.corners.reserve(hc.distances.size() / 3 + 1); - for(size_t i = 0; i < hc.distances.size() - 1; i += 3) { + const auto N = hc.distances.size(); + const auto S = stride(N); + auto N_1 = N-1; + hc.corners.reserve(N / S + 1); + for(size_t i = 0; i < N_1; i += S) { hc.corners.emplace_back( hc.distances.at(i) / hc.full_distance); } @@ -224,6 +241,9 @@ public: createCache(sh); } + /// Resolution of returned corners. The stride is derived from this value. + void accuracy(double a /* within <0.0, 1.0>*/) { accuracy_ = a; } + /** * @brief Get a point on the circumference of a polygon. * @param distance A relative distance from the starting point to the end. @@ -419,12 +439,12 @@ Nfp::Shapes nfp( const Container& polygons, // return nfps; } -template -class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer, - RawShape, _Box>, NfpPConfig> { +template>> +class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer, + RawShape, TBin, NfpPConfig> { - using Base = PlacerBoilerplate<_NofitPolyPlacer, - RawShape, _Box>, NfpPConfig>; + using Base = PlacerBoilerplate<_NofitPolyPlacer, + RawShape, TBin, NfpPConfig>; DECLARE_PLACER(Base) @@ -434,6 +454,7 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer, const double penality_; using MaxNfpLevel = Nfp::MaxNfpLevel; + using sl = ShapeLike; public: @@ -441,7 +462,7 @@ public: inline explicit _NofitPolyPlacer(const BinType& bin): Base(bin), - norm_(std::sqrt(ShapeLike::area(bin))), + norm_(std::sqrt(sl::area(bin))), penality_(1e6*norm_) {} _NofitPolyPlacer(const _NofitPolyPlacer&) = default; @@ -452,18 +473,26 @@ public: _NofitPolyPlacer& operator=(_NofitPolyPlacer&&) BP2D_NOEXCEPT = default; #endif + bool static inline wouldFit(const Box& bb, const RawShape& bin) { + auto bbin = sl::boundingBox(bin); + auto d = bbin.center() - bb.center(); + _Rectangle rect(bb.width(), bb.height()); + rect.translate(bb.minCorner() + d); + return sl::isInside(rect.transformedShape(), bin); + } + bool static inline wouldFit(const RawShape& chull, const RawShape& bin) { - auto bbch = ShapeLike::boundingBox(chull); - auto bbin = ShapeLike::boundingBox(bin); - auto d = bbin.minCorner() - bbch.minCorner(); + auto bbch = sl::boundingBox(chull); + auto bbin = sl::boundingBox(bin); + auto d = bbin.center() - bbch.center(); auto chullcpy = chull; - ShapeLike::translate(chullcpy, d); - return ShapeLike::isInside(chullcpy, bbin); + sl::translate(chullcpy, d); + return sl::isInside(chullcpy, bin); } bool static inline wouldFit(const RawShape& chull, const Box& bin) { - auto bbch = ShapeLike::boundingBox(chull); + auto bbch = sl::boundingBox(chull); return wouldFit(bbch, bin); } @@ -472,6 +501,17 @@ public: return bb.width() <= bin.width() && bb.height() <= bin.height(); } + bool static inline wouldFit(const Box& bb, const _Circle& bin) + { + return sl::isInside(bb, bin); + } + + bool static inline wouldFit(const RawShape& chull, + const _Circle& bin) + { + return sl::isInside(chull, bin); + } + PackResult trypack(Item& item) { PackResult ret; @@ -510,7 +550,10 @@ public: std::vector> ecache; ecache.reserve(nfps.size()); - for(auto& nfp : nfps ) ecache.emplace_back(nfp); + for(auto& nfp : nfps ) { + ecache.emplace_back(nfp); + ecache.back().accuracy(config_.accuracy); + } struct Optimum { double relpos; @@ -540,14 +583,16 @@ public: // customizable by the library client auto _objfunc = config_.object_function? config_.object_function : - [this](Nfp::Shapes& pile, Item, + [this](Nfp::Shapes& pile, const Item& item, double occupied_area, double /*norm*/, double penality) { - auto ch = ShapeLike::convexHull(pile); + pile.emplace_back(item.transformedShape()); + auto ch = sl::convexHull(pile); + pile.pop_back(); // The pack ratio -- how much is the convex hull occupied - double pack_rate = occupied_area/ShapeLike::area(ch); + double pack_rate = occupied_area/sl::area(ch); // ratio of waste double waste = 1.0 - pack_rate; @@ -569,22 +614,17 @@ public: d += startpos; item.translation(d); -// pile.emplace_back(item.transformedShape()); - double occupied_area = pile_area + item.area(); double score = _objfunc(pile, item, occupied_area, norm_, penality_); -// pile.pop_back(); - return score; }; opt::StopCriteria stopcr; stopcr.max_iterations = 1000; stopcr.absolute_score_difference = 1e-20*norm_; -// stopcr.relative_score_difference = 1e-20; opt::TOptimizer solver(stopcr); Optimum optimum(0, 0); @@ -702,34 +742,35 @@ public: m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); - auto&& bb = ShapeLike::boundingBox(m); + auto&& bb = sl::boundingBox(m); Vertex ci, cb; + auto bbin = sl::boundingBox(bin_); switch(config_.alignment) { case Config::Alignment::CENTER: { ci = bb.center(); - cb = bin_.center(); + cb = bbin.center(); break; } case Config::Alignment::BOTTOM_LEFT: { ci = bb.minCorner(); - cb = bin_.minCorner(); + cb = bbin.minCorner(); break; } case Config::Alignment::BOTTOM_RIGHT: { ci = {getX(bb.maxCorner()), getY(bb.minCorner())}; - cb = {getX(bin_.maxCorner()), getY(bin_.minCorner())}; + cb = {getX(bbin.maxCorner()), getY(bbin.minCorner())}; break; } case Config::Alignment::TOP_LEFT: { ci = {getX(bb.minCorner()), getY(bb.maxCorner())}; - cb = {getX(bin_.minCorner()), getY(bin_.maxCorner())}; + cb = {getX(bbin.minCorner()), getY(bbin.maxCorner())}; break; } case Config::Alignment::TOP_RIGHT: { ci = bb.maxCorner(); - cb = bin_.maxCorner(); + cb = bbin.maxCorner(); break; } } @@ -745,31 +786,32 @@ private: void setInitialPosition(Item& item) { Box&& bb = item.boundingBox(); Vertex ci, cb; + auto bbin = sl::boundingBox(bin_); switch(config_.starting_point) { case Config::Alignment::CENTER: { ci = bb.center(); - cb = bin_.center(); + cb = bbin.center(); break; } case Config::Alignment::BOTTOM_LEFT: { ci = bb.minCorner(); - cb = bin_.minCorner(); + cb = bbin.minCorner(); break; } case Config::Alignment::BOTTOM_RIGHT: { ci = {getX(bb.maxCorner()), getY(bb.minCorner())}; - cb = {getX(bin_.maxCorner()), getY(bin_.minCorner())}; + cb = {getX(bbin.maxCorner()), getY(bbin.minCorner())}; break; } case Config::Alignment::TOP_LEFT: { ci = {getX(bb.minCorner()), getY(bb.maxCorner())}; - cb = {getX(bin_.minCorner()), getY(bin_.maxCorner())}; + cb = {getX(bbin.minCorner()), getY(bbin.maxCorner())}; break; } case Config::Alignment::TOP_RIGHT: { ci = bb.maxCorner(); - cb = bin_.maxCorner(); + cb = bbin.maxCorner(); break; } } @@ -780,7 +822,7 @@ private: void placeOutsideOfBin(Item& item) { auto&& bb = item.boundingBox(); - Box binbb = ShapeLike::boundingBox(bin_); + Box binbb = sl::boundingBox(bin_); Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) }; diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index 17ac1167df..e3ad97c100 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -535,7 +535,7 @@ public: // then it should be removed from the not_packed list { auto it = store_.begin(); while (it != store_.end()) { - Placer p(bin); + Placer p(bin); p.configure(pconfig); if(!p.pack(*it)) { it = store_.erase(it); } else it++; diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index f34961f803..665b9da9f2 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -59,7 +59,7 @@ public: // then it should be removed from the list { auto it = store_.begin(); while (it != store_.end()) { - Placer p(bin); + Placer p(bin); p.configure(pconfig); if(!p.pack(*it)) { it = store_.erase(it); } else it++; diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 87519d5e7e..5f9c7a7d40 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -530,6 +530,9 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // arranger.useMinimumBoundigBoxRotation(); pcfg.rotations = { 0.0 }; + // The accuracy of optimization. Goes from 0.0 to 1.0 and scales performance + pcfg.accuracy = 0.8; + // Magic: we will specify what is the goal of arrangement... In this case // we override the default object function to make the larger items go into // the center of the pile and smaller items orbit it so the resulting pile @@ -539,7 +542,7 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, // calculate the convex hulls) pcfg.object_function = [bin, hasbin]( NfpPlacer::Pile& pile, // The currently arranged pile - Item item, + const Item &item, double /*area*/, // Sum area of items (not needed) double norm, // A norming factor for physical dimensions double penality) // Min penality in case of bad arrangement From f49f8719926a19b07a99531329c122c863309654 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 31 Jul 2018 09:44:29 +0200 Subject: [PATCH 052/119] Added filament_load_time and filament_unload_time parameters to define the filament load / unload times spent in the MMU2.0 unit when performing the tool change code (the T code). --- xs/src/libslic3r/PrintConfig.cpp | 16 ++++++++++++++++ xs/src/libslic3r/PrintConfig.hpp | 4 ++++ xs/src/slic3r/GUI/Preset.cpp | 3 ++- xs/src/slic3r/GUI/Tab.cpp | 2 ++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 7064e19fec..d500282010 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -517,6 +517,14 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloats { 3.4f }; + def = this->add("filament_load_time", coFloats); + def->label = L("Filament load time"); + def->tooltip = L("Time for the printer firmware (or the Multi Material Unit 2.0) to load a new filament during a tool change (when executing the T code). This time is added to the total print time by the G-code time estimator."); + def->cli = "filament-load-time=i@"; + def->sidetext = L("s"); + def->min = 0; + def->default_value = new ConfigOptionFloats { 12.0f }; + def = this->add("filament_ramming_parameters", coStrings); def->label = L("Ramming parameters"); def->tooltip = L("This string is edited by RammingDialog and contains ramming specific parameters "); @@ -524,6 +532,14 @@ PrintConfigDef::PrintConfigDef() def->default_value = new ConfigOptionStrings { "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0|" " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" }; + def = this->add("filament_unload_time", coFloats); + def->label = L("Filament unload time"); + def->tooltip = L("Time for the printer firmware (or the Multi Material Unit 2.0) to unload a filament during a tool change (when executing the T code). This time is added to the total print time by the G-code time estimator."); + def->cli = "filament-unload-time=i@"; + def->sidetext = L("s"); + def->min = 0; + def->default_value = new ConfigOptionFloats { 11.0f }; + def = this->add("filament_diameter", coFloats); def->label = L("Diameter"); def->tooltip = L("Enter your filament diameter here. Good precision is required, so use a caliper " diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index c530868a12..0a5f70f787 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -528,8 +528,10 @@ public: ConfigOptionFloats filament_cost; ConfigOptionFloats filament_max_volumetric_speed; ConfigOptionFloats filament_loading_speed; + ConfigOptionFloats filament_load_time; ConfigOptionFloats filament_unloading_speed; ConfigOptionFloats filament_toolchange_delay; + ConfigOptionFloats filament_unload_time; ConfigOptionInts filament_cooling_moves; ConfigOptionFloats filament_cooling_initial_speed; ConfigOptionFloats filament_cooling_final_speed; @@ -589,7 +591,9 @@ protected: OPT_PTR(filament_cost); OPT_PTR(filament_max_volumetric_speed); OPT_PTR(filament_loading_speed); + OPT_PTR(filament_load_time); OPT_PTR(filament_unloading_speed); + OPT_PTR(filament_unload_time); OPT_PTR(filament_toolchange_delay); OPT_PTR(filament_cooling_moves); OPT_PTR(filament_cooling_initial_speed); diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 49e235146c..30741fc5cf 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -313,7 +313,8 @@ const std::vector& Preset::filament_options() { static std::vector s_opts { "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", - "extrusion_multiplier", "filament_density", "filament_cost", "filament_loading_speed", "filament_unloading_speed", "filament_toolchange_delay", + "extrusion_multiplier", "filament_density", "filament_cost", + "filament_loading_speed", "filament_load_time", "filament_unloading_speed", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 187030f31b..567f6d59c0 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -1291,6 +1291,8 @@ void TabFilament::build() optgroup = page->new_optgroup(_(L("Toolchange parameters with single extruder MM printers"))); optgroup->append_single_option_line("filament_loading_speed"); optgroup->append_single_option_line("filament_unloading_speed"); + optgroup->append_single_option_line("filament_load_time"); + optgroup->append_single_option_line("filament_unload_time"); optgroup->append_single_option_line("filament_toolchange_delay"); optgroup->append_single_option_line("filament_cooling_moves"); optgroup->append_single_option_line("filament_cooling_initial_speed"); From fa6a72ab2d0f7f3096e714c0f696a9cc816ae770 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 31 Jul 2018 09:46:39 +0200 Subject: [PATCH 053/119] Changed the filament_load_time / filament_unload_time defaults to zero. --- xs/src/libslic3r/PrintConfig.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index d500282010..01d0a73801 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -523,7 +523,7 @@ PrintConfigDef::PrintConfigDef() def->cli = "filament-load-time=i@"; def->sidetext = L("s"); def->min = 0; - def->default_value = new ConfigOptionFloats { 12.0f }; + def->default_value = new ConfigOptionFloats { 0.0f }; def = this->add("filament_ramming_parameters", coStrings); def->label = L("Ramming parameters"); @@ -538,7 +538,7 @@ PrintConfigDef::PrintConfigDef() def->cli = "filament-unload-time=i@"; def->sidetext = L("s"); def->min = 0; - def->default_value = new ConfigOptionFloats { 11.0f }; + def->default_value = new ConfigOptionFloats { 0.0f }; def = this->add("filament_diameter", coFloats); def->label = L("Diameter"); From 14c9ff174d25c88fff9593b83e60cdd856ddbcd4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 31 Jul 2018 10:42:37 +0200 Subject: [PATCH 054/119] Add variable name to tooltips --- xs/src/slic3r/GUI/Field.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 36a1c396f9..0885e041ba 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -95,9 +95,10 @@ namespace Slic3r { namespace GUI { wxString tooltip_text(""); wxString tooltip = _(m_opt.tooltip); if (tooltip.length() > 0) - tooltip_text = tooltip + "(" + _(L("default")) + ": " + - (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + - default_string + ")"; + tooltip_text = tooltip + "\n " + _(L("default value")) + "\t: " + + (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string + + (boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") + "\n " + + _(L("variable name")) + "\t: " + m_opt_id; return tooltip_text; } From b6d70f5fe8653297c6fa6f23019502101c6d655b Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 27 Jul 2018 11:55:11 +0200 Subject: [PATCH 055/119] FirmwareDialog: UI improvements, bugfixes --- xs/src/avrdude/avrdude-slic3r.cpp | 24 +- xs/src/avrdude/avrdude-slic3r.hpp | 8 +- xs/src/slic3r/GUI/FirmwareDialog.cpp | 507 ++++++++++++++++++--------- xs/src/slic3r/Utils/HexFile.cpp | 3 +- xs/src/slic3r/Utils/HexFile.hpp | 3 +- xs/src/slic3r/Utils/Serial.cpp | 9 +- xs/src/slic3r/Utils/Serial.hpp | 5 +- 7 files changed, 385 insertions(+), 174 deletions(-) diff --git a/xs/src/avrdude/avrdude-slic3r.cpp b/xs/src/avrdude/avrdude-slic3r.cpp index 4a7f22d6e5..0577fe6d07 100644 --- a/xs/src/avrdude/avrdude-slic3r.cpp +++ b/xs/src/avrdude/avrdude-slic3r.cpp @@ -35,8 +35,9 @@ struct AvrDude::priv { std::string sys_config; std::deque> args; - size_t current_args_set = 0; bool cancelled = false; + int exit_code = 0; + size_t current_args_set = 0; RunFn run_fn; MessageFn message_fn; ProgressFn progress_fn; @@ -146,15 +147,15 @@ AvrDude::Ptr AvrDude::run() int res = -1; if (self->p->run_fn) { - self->p->run_fn(*self); + self->p->run_fn(); } if (! self->p->cancelled) { - res = self->p->run(); + self->p->exit_code = self->p->run(); } if (self->p->complete_fn) { - self->p->complete_fn(res, self->p->current_args_set); + self->p->complete_fn(); } }); @@ -179,5 +180,20 @@ void AvrDude::join() } } +bool AvrDude::cancelled() +{ + return p ? p->cancelled : false; +} + +int AvrDude::exit_code() +{ + return p ? p->exit_code : 0; +} + +size_t AvrDude::last_args_set() +{ + return p ? p->current_args_set : 0; +} + } diff --git a/xs/src/avrdude/avrdude-slic3r.hpp b/xs/src/avrdude/avrdude-slic3r.hpp index 399df2358f..86e097034c 100644 --- a/xs/src/avrdude/avrdude-slic3r.hpp +++ b/xs/src/avrdude/avrdude-slic3r.hpp @@ -12,10 +12,10 @@ class AvrDude { public: typedef std::shared_ptr Ptr; - typedef std::function RunFn; + typedef std::function RunFn; typedef std::function MessageFn; typedef std::function ProgressFn; - typedef std::function CompleteFn; + typedef std::function CompleteFn; // Main c-tor, sys_config is the location of avrdude's main configuration file AvrDude(std::string sys_config); @@ -54,6 +54,10 @@ public: void cancel(); void join(); + + bool cancelled(); // Whether avrdude run was cancelled + int exit_code(); // The exit code of the last invocation + size_t last_args_set(); // Index of the last argument set that was processsed private: struct priv; std::unique_ptr p; diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index 30339e3cb0..c33a50e7d2 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -1,12 +1,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include "libslic3r/Utils.hpp" #include "avrdude/avrdude-slic3r.hpp" @@ -32,11 +34,13 @@ #include #include #include +#include namespace fs = boost::filesystem; namespace asio = boost::asio; using boost::system::error_code; +using boost::optional; namespace Slic3r { @@ -46,19 +50,31 @@ using Utils::SerialPortInfo; using Utils::Serial; +// USB IDs used to perform device lookup +enum { + USB_VID_PRUSA = 0x2c99, + USB_PID_MK2 = 1, + USB_PID_MK3 = 2, + USB_PID_MMU_BOOT = 3, + USB_PID_MMU_APP = 4, +}; + // This enum discriminates the kind of information in EVT_AVRDUDE, // it's stored in the ExtraLong field of wxCommandEvent. enum AvrdudeEvent { AE_MESSAGE, AE_PROGRESS, + AE_STATUS, AE_EXIT, - AE_ERROR, }; wxDECLARE_EVENT(EVT_AVRDUDE, wxCommandEvent); wxDEFINE_EVENT(EVT_AVRDUDE, wxCommandEvent); +wxDECLARE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent); +wxDEFINE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent); + // Private @@ -66,15 +82,17 @@ struct FirmwareDialog::priv { enum AvrDudeComplete { + AC_NONE, AC_SUCCESS, AC_FAILURE, - AC_CANCEL, + AC_USER_CANCELLED, }; FirmwareDialog *q; // PIMPL back pointer ("Q-Pointer") + // GUI elements wxComboBox *port_picker; - std::vector ports; + wxStaticText *port_autodetect; wxFilePickerCtrl *hex_picker; wxStaticText *txt_status; wxGauge *progressbar; @@ -85,43 +103,66 @@ struct FirmwareDialog::priv wxButton *btn_flash; wxString btn_flash_label_ready; wxString btn_flash_label_flashing; + wxString label_status_flashing; wxTimer timer_pulse; + // Async modal dialog during flashing + std::mutex mutex; + int modal_response; + std::condition_variable response_cv; + + // Data + std::vector ports; + optional port; + HexFile hex_file; + // This is a shared pointer holding the background AvrDude task // also serves as a status indication (it is set _iff_ the background task is running, otherwise it is reset). AvrDude::Ptr avrdude; std::string avrdude_config; unsigned progress_tasks_done; unsigned progress_tasks_bar; - bool cancelled; + bool user_cancelled; const bool extra_verbose; // For debugging priv(FirmwareDialog *q) : q(q), btn_flash_label_ready(_(L("Flash!"))), btn_flash_label_flashing(_(L("Cancel"))), + label_status_flashing(_(L("Flashing in progress. Please do not disconnect the printer!"))), timer_pulse(q), avrdude_config((fs::path(::Slic3r::resources_dir()) / "avrdude" / "avrdude.conf").string()), progress_tasks_done(0), progress_tasks_bar(0), - cancelled(false), + user_cancelled(false), extra_verbose(false) {} void find_serial_ports(); + void fit_no_shrink(); + void set_txt_status(const wxString &label); void flashing_start(unsigned tasks); void flashing_done(AvrDudeComplete complete); - void check_model_id(const HexFile &metadata, const SerialPortInfo &port); + void enable_port_picker(bool enable); + void load_hex_file(const wxString &path); + void queue_status(wxString message); + void queue_error(const wxString &message); - void prepare_common(AvrDude &, const SerialPortInfo &port, const std::string &filename); - void prepare_mk2(AvrDude &, const SerialPortInfo &port, const std::string &filename); - void prepare_mk3(AvrDude &, const SerialPortInfo &port, const std::string &filename); - void prepare_mm_control(AvrDude &, const SerialPortInfo &port, const std::string &filename); + bool ask_model_id_mismatch(const std::string &printer_model); + bool check_model_id(); + void wait_for_mmu_bootloader(unsigned retries); + void mmu_reboot(const SerialPortInfo &port); + void lookup_port_mmu(); + void prepare_common(); + void prepare_mk2(); + void prepare_mk3(); + void prepare_mm_control(); void perform_upload(); - void cancel(); + void user_cancel(); void on_avrdude(const wxCommandEvent &evt); + void on_async_dialog(const wxCommandEvent &evt); void ensure_joined(); }; @@ -146,10 +187,30 @@ void FirmwareDialog::priv::find_serial_ports() } } +void FirmwareDialog::priv::fit_no_shrink() +{ + // Ensure content fits into window and window is not shrinked + auto old_size = q->GetSize(); + q->Layout(); + q->Fit(); + auto new_size = q->GetSize(); + q->SetSize(std::max(old_size.GetWidth(), new_size.GetWidth()), std::max(old_size.GetHeight(), new_size.GetHeight())); +} + +void FirmwareDialog::priv::set_txt_status(const wxString &label) +{ + const auto width = txt_status->GetSize().GetWidth(); + txt_status->SetLabel(label); + txt_status->Wrap(width); + + fit_no_shrink(); +} + void FirmwareDialog::priv::flashing_start(unsigned tasks) { + modal_response = wxID_NONE; txt_stdout->Clear(); - txt_status->SetLabel(_(L("Flashing in progress. Please do not disconnect the printer!"))); + set_txt_status(label_status_flashing); txt_status->SetForegroundColour(GUI::get_label_clr_modified()); port_picker->Disable(); btn_rescan->Disable(); @@ -160,7 +221,7 @@ void FirmwareDialog::priv::flashing_start(unsigned tasks) progressbar->SetValue(0); progress_tasks_done = 0; progress_tasks_bar = 0; - cancelled = false; + user_cancelled = false; timer_pulse.Start(50); } @@ -177,54 +238,190 @@ void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete) progressbar->SetValue(progressbar->GetRange()); switch (complete) { - case AC_SUCCESS: txt_status->SetLabel(_(L("Flashing succeeded!"))); break; - case AC_FAILURE: txt_status->SetLabel(_(L("Flashing failed. Please see the avrdude log below."))); break; - case AC_CANCEL: txt_status->SetLabel(_(L("Flashing cancelled."))); break; + case AC_SUCCESS: set_txt_status(_(L("Flashing succeeded!"))); break; + case AC_FAILURE: set_txt_status(_(L("Flashing failed. Please see the avrdude log below."))); break; + case AC_USER_CANCELLED: set_txt_status(_(L("Flashing cancelled."))); break; + default: break; } } -void FirmwareDialog::priv::check_model_id(const HexFile &metadata, const SerialPortInfo &port) +void FirmwareDialog::priv::enable_port_picker(bool enable) { - if (metadata.model_id.empty()) { - // No data to check against - return; + port_picker->Show(enable); + btn_rescan->Show(enable); + port_autodetect->Show(! enable); + q->Layout(); + fit_no_shrink(); +} + +void FirmwareDialog::priv::load_hex_file(const wxString &path) +{ + hex_file = HexFile(path.wx_str()); + enable_port_picker(hex_file.device != HexFile::DEV_MM_CONTROL); +} + +void FirmwareDialog::priv::queue_status(wxString message) +{ + auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId()); + evt->SetExtraLong(AE_STATUS); + evt->SetString(std::move(message)); + wxQueueEvent(this->q, evt); +} + +void FirmwareDialog::priv::queue_error(const wxString &message) +{ + auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId()); + evt->SetExtraLong(AE_STATUS); + evt->SetString(wxString::Format(_(L("Flashing failed: %s")), message)); + + wxQueueEvent(this->q, evt); avrdude->cancel(); +} + +bool FirmwareDialog::priv::ask_model_id_mismatch(const std::string &printer_model) +{ + // model_id in the hex file doesn't match what the printer repoted. + // Ask the user if it should be flashed anyway. + + std::unique_lock lock(mutex); + + auto evt = new wxCommandEvent(EVT_ASYNC_DIALOG, this->q->GetId()); + evt->SetString(wxString::Format(_(L( + "This firmware hex file does not match the printer model.\n" + "The hex file is intended for: %s\n" + "Printer reported: %s\n\n" + "Do you want to continue and flash this hex file anyway?\n" + "Please only continue if you are sure this is the right thing to do.")), + hex_file.model_id, printer_model + )); + wxQueueEvent(this->q, evt); + + response_cv.wait(lock, [this]() { return this->modal_response != wxID_NONE; }); + + if (modal_response == wxID_YES) { + return true; + } else { + user_cancel(); + return false; } +} - asio::io_service io; - Serial serial(io, port.port, 115200); - serial.printer_setup(); +bool FirmwareDialog::priv::check_model_id() +{ + // XXX: The implementation in Serial doesn't currently work reliably enough to be used. + // Therefore, regretably, so far the check cannot be used and we just return true here. + // TODO: Rewrite Serial using more platform-native code. + return true; + + // if (hex_file.model_id.empty()) { + // // No data to check against, assume it's ok + // return true; + // } + // asio::io_service io; + // Serial serial(io, port->port, 115200); + // serial.printer_setup(); + + // enum { + // TIMEOUT = 2000, + // RETREIES = 5, + // }; + + // if (! serial.printer_ready_wait(RETREIES, TIMEOUT)) { + // queue_error(wxString::Format(_(L("Could not connect to the printer at %s")), port->port)); + // return false; + // } + + // std::string line; + // error_code ec; + // serial.printer_write_line("PRUSA Rev"); + // while (serial.read_line(TIMEOUT, line, ec)) { + // if (ec) { + // queue_error(wxString::Format(_(L("Could not connect to the printer at %s")), port->port)); + // return false; + // } + + // if (line == "ok") { continue; } + + // if (line == hex_file.model_id) { + // return true; + // } else { + // return ask_model_id_mismatch(line); + // } + + // line.clear(); + // } + + // return false; +} + +void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries) +{ enum { - TIMEOUT = 1000, - RETREIES = 3, + SLEEP_MS = 500, }; - if (! serial.printer_ready_wait(RETREIES, TIMEOUT)) { - throw wxString::Format(_(L("Could not connect to the printer at %s")), port.port); - } + for (unsigned i = 0; i < retries && !user_cancelled; i++) { + std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_MS)); - std::string line; - error_code ec; - serial.printer_write_line("PRUSA Rev"); - while (serial.read_line(TIMEOUT, line, ec)) { - if (ec) { throw wxString::Format(_(L("Could not connect to the printer at %s")), port.port); } - if (line == "ok") { continue; } + auto ports = Utils::scan_serial_ports_extended(); + ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { + return port.id_vendor != USB_VID_PRUSA && port.id_product != USB_PID_MMU_BOOT; + }), ports.end()); - if (line == metadata.model_id) { + if (ports.size() == 1) { + port = ports[0]; + return; + } else if (ports.size() > 1) { + BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found"; + queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing."))); return; - } else { - throw wxString::Format(_(L( - "The firmware hex file does not match the printer model.\n" - "The hex file is intended for:\n %s\n" - "Printer reports:\n %s" - )), metadata.model_id, line); } - - line.clear(); } } -void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) +void FirmwareDialog::priv::mmu_reboot(const SerialPortInfo &port) +{ + asio::io_service io; + Serial serial(io, port.port, 1200); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); +} + +void FirmwareDialog::priv::lookup_port_mmu() +{ + BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ..."; + + auto ports = Utils::scan_serial_ports_extended(); + ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { + return port.id_vendor != USB_VID_PRUSA && + port.id_product != USB_PID_MMU_BOOT && + port.id_product != USB_PID_MMU_APP; + }), ports.end()); + + if (ports.size() == 0) { + BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ..."; + + queue_status(_(L( + "The Multi Material Control device was not found.\n" + "If the device is connected, please press the Reset button next to the USB connector ..." + ))); + + wait_for_mmu_bootloader(30); + } else if (ports.size() > 1) { + BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found"; + queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing."))); + } else { + if (ports[0].id_product == USB_PID_MMU_APP) { + // The device needs to be rebooted into the bootloader mode + BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port; + mmu_reboot(ports[0]); + wait_for_mmu_bootloader(10); + } else { + port = ports[0]; + } + } +} + +void FirmwareDialog::priv::prepare_common() { std::vector args {{ extra_verbose ? "-vvvvv" : "-v", @@ -233,10 +430,10 @@ void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo // The Prusa's avrdude is patched to never send semicolons inside the data packets, as the USB to serial chip // is flashed with a buggy firmware. "-c", "wiring", - "-P", port.port, - "-b", "115200", // TODO: Allow other rates? Ditto below. + "-P", port->port, + "-b", "115200", // TODO: Allow other rates? Ditto elsewhere. "-D", - "-U", (boost::format("flash:w:0:%1%:i") % filename).str(), + "-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " @@ -244,97 +441,75 @@ void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo return a + ' ' + b; }); - avrdude.push_args(std::move(args)); + avrdude->push_args(std::move(args)); } -void FirmwareDialog::priv::prepare_mk2(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) +void FirmwareDialog::priv::prepare_mk2() { - prepare_common(avrdude, port, filename); + if (! port) { return; } + + if (! check_model_id()) { + avrdude->cancel(); + return; + } + + prepare_common(); } -void FirmwareDialog::priv::prepare_mk3(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename) +void FirmwareDialog::priv::prepare_mk3() { - prepare_common(avrdude, port, filename); + if (! port) { return; } + + if (! check_model_id()) { + avrdude->cancel(); + return; + } + + prepare_common(); // The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy) // This is done via another avrdude invocation, here we build arg list for that: - std::vector args_l10n {{ + std::vector args {{ extra_verbose ? "-vvvvv" : "-v", "-p", "atmega2560", // Using the "Arduino" mode to program Einsy's external flash with languages, using the STK500 protocol (not the STK500v2). // The Prusa's avrdude is patched again to never send semicolons inside the data packets. "-c", "arduino", - "-P", port.port, + "-P", port->port, "-b", "115200", "-D", "-u", // disable safe mode - "-U", (boost::format("flash:w:1:%1%:i") % filename).str(), + "-U", (boost::format("flash:w:1:%1%:i") % hex_file.path.string()).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: " - << std::accumulate(std::next(args_l10n.begin()), args_l10n.end(), args_l10n[0], [](std::string a, const std::string &b) { + << std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) { return a + ' ' + b; }); - avrdude.push_args(std::move(args_l10n)); + avrdude->push_args(std::move(args)); } -void FirmwareDialog::priv::prepare_mm_control(AvrDude &avrdude, const SerialPortInfo &port_in, const std::string &filename) +void FirmwareDialog::priv::prepare_mm_control() { - // Check if the port has the PID/VID of 0x2c99/3 - // If not, check if it is the MMU (0x2c99/4) and reboot the by opening @ 1200 bauds - BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ..."; - SerialPortInfo port = port_in; - if (! port.id_match(0x2c99, 3)) { - if (! port.id_match(0x2c99, 4)) { - // This is not a Prusa MMU 2.0 device - BOOST_LOG_TRIVIAL(error) << boost::format("Not a Prusa MMU 2.0 device: `%1%`") % port.port; - throw wxString::Format(_(L("The device at `%s` is not am Original Prusa i3 MMU 2.0 device")), port.port); - } - - BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % port.port; - - { - asio::io_service io; - Serial serial(io, port.port, 1200); - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } - - // Wait for the bootloader to show up - std::this_thread::sleep_for(std::chrono::milliseconds(2000)); - - // Look for the rebooted device - BOOST_LOG_TRIVIAL(info) << "... looking for VID/PID 0x2c99/3 ..."; - auto new_ports = Utils::scan_serial_ports_extended(); - unsigned hits = 0; - for (auto &&new_port : new_ports) { - if (new_port.id_match(0x2c99, 3)) { - hits++; - port = std::move(new_port); - } - } - - if (hits == 0) { - BOOST_LOG_TRIVIAL(error) << "No VID/PID 0x2c99/3 device found after rebooting the MMU 2.0"; - throw wxString::Format(_(L("Failed to reboot the device at `%s` for programming")), port.port); - } else if (hits > 1) { - // We found multiple 0x2c99/3 devices, this is bad, because there's no way to find out - // which one is the one user wants to flash. - BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found after rebooting the MMU 2.0"; - throw wxString::Format(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")), port.port); - } + port = boost::none; + lookup_port_mmu(); + if (! port) { + queue_error(_(L("The device could not have been found"))); + return; } - BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port.port; + BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port->port; + queue_status(label_status_flashing); std::vector args {{ extra_verbose ? "-vvvvv" : "-v", "-p", "atmega32u4", "-c", "avr109", - "-P", port.port, + "-P", port->port, "-b", "57600", "-D", - "-U", (boost::format("flash:w:0:%1%:i") % filename).str(), + "-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(), }}; BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: " @@ -342,7 +517,7 @@ void FirmwareDialog::priv::prepare_mm_control(AvrDude &avrdude, const SerialPort return a + ' ' + b; }); - avrdude.push_args(std::move(args)); + avrdude->push_args(std::move(args)); } @@ -351,20 +526,21 @@ void FirmwareDialog::priv::perform_upload() auto filename = hex_picker->GetPath(); if (filename.IsEmpty()) { return; } - int selection = port_picker->GetSelection(); - if (selection == wxNOT_FOUND) { return; } + load_hex_file(filename); // Might already be loaded, but we want to make sure it's fresh - const SerialPortInfo &port = this->ports[selection]; - // Verify whether the combo box list selection equals to the combo box edit value. - if (wxString::FromUTF8(this->ports[selection].friendly_name.data()) != port_picker->GetValue()) { - return; + int selection = port_picker->GetSelection(); + if (selection != wxNOT_FOUND) { + port = this->ports[selection]; + + // Verify whether the combo box list selection equals to the combo box edit value. + if (wxString::FromUTF8(port->friendly_name.data()) != port_picker->GetValue()) { + return; + } } const bool extra_verbose = false; // For debugging - HexFile metadata(filename.wx_str()); - const std::string filename_utf8(filename.utf8_str().data()); - flashing_start(metadata.device == HexFile::DEV_MK3 ? 2 : 1); + flashing_start(hex_file.device == HexFile::DEV_MK3 ? 2 : 1); // Init the avrdude object AvrDude avrdude(avrdude_config); @@ -374,36 +550,23 @@ void FirmwareDialog::priv::perform_upload() auto q = this->q; this->avrdude = avrdude - .on_run([=](AvrDude &avrdude) { - auto queue_error = [&](wxString message) { - avrdude.cancel(); - - auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId()); - evt->SetExtraLong(AE_ERROR); - evt->SetString(std::move(message)); - wxQueueEvent(this->q, evt); - }; - + .on_run([this]() { try { - switch (metadata.device) { + switch (this->hex_file.device) { case HexFile::DEV_MK3: - this->check_model_id(metadata, port); - this->prepare_mk3(avrdude, port, filename_utf8); + this->prepare_mk3(); break; case HexFile::DEV_MM_CONTROL: - this->check_model_id(metadata, port); - this->prepare_mm_control(avrdude, port, filename_utf8); + this->prepare_mm_control(); break; default: - this->prepare_mk2(avrdude, port, filename_utf8); + this->prepare_mk2(); break; } - } catch (const wxString &message) { - queue_error(message); } catch (const std::exception &ex) { - queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port.port, ex.what())); + queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port->port, ex.what())); } }) .on_message(std::move([q, extra_verbose](const char *msg, unsigned /* size */) { @@ -423,20 +586,19 @@ void FirmwareDialog::priv::perform_upload() evt->SetInt(progress); wxQueueEvent(q, evt); })) - .on_complete(std::move([q](int status, size_t /* args_id */) { - auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId()); + .on_complete(std::move([this]() { + auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId()); evt->SetExtraLong(AE_EXIT); - evt->SetInt(status); - wxQueueEvent(q, evt); + evt->SetInt(this->avrdude->exit_code()); + wxQueueEvent(this->q, evt); })) .run(); } -void FirmwareDialog::priv::cancel() +void FirmwareDialog::priv::user_cancel() { if (avrdude) { - cancelled = true; - txt_status->SetLabel(_(L("Cancelling..."))); + user_cancelled = true; avrdude->cancel(); } } @@ -474,19 +636,17 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) case AE_EXIT: BOOST_LOG_TRIVIAL(info) << "avrdude exit code: " << evt.GetInt(); - complete_kind = cancelled ? AC_CANCEL : (evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE); + // Figure out the exit state + if (user_cancelled) { complete_kind = AC_USER_CANCELLED; } + else if (avrdude->cancelled()) { complete_kind = AC_NONE; } // Ie. cancelled programatically + else { complete_kind = evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE; } + flashing_done(complete_kind); ensure_joined(); break; - case AE_ERROR: - txt_stdout->AppendText(evt.GetString()); - flashing_done(AC_FAILURE); - ensure_joined(); - { - GUI::ErrorDialog dlg(this->q, evt.GetString()); - dlg.ShowModal(); - } + case AE_STATUS: + set_txt_status(evt.GetString()); break; default: @@ -494,6 +654,16 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt) } } +void FirmwareDialog::priv::on_async_dialog(const wxCommandEvent &evt) +{ + wxMessageDialog dlg(this->q, evt.GetString(), wxMessageBoxCaptionStr, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); + { + std::lock_guard lock(mutex); + modal_response = dlg.ShowModal(); + } + response_cv.notify_all(); +} + void FirmwareDialog::priv::ensure_joined() { // Make sure the background thread is collected and the AvrDude object reset @@ -521,41 +691,47 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : wxFont mono_font(wxFontInfo().Family(wxFONTFAMILY_TELETYPE)); mono_font.MakeSmaller(); + // Create GUI components and layout + auto *panel = new wxPanel(this); wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL); panel->SetSizer(vsizer); + auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:"))); + p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY); + auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:"))); p->port_picker = new wxComboBox(panel, wxID_ANY); + p->port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected"))); p->btn_rescan = new wxButton(panel, wxID_ANY, _(L("Rescan"))); auto *port_sizer = new wxBoxSizer(wxHORIZONTAL); port_sizer->Add(p->port_picker, 1, wxEXPAND | wxRIGHT, SPACING); port_sizer->Add(p->btn_rescan, 0); + port_sizer->Add(p->port_autodetect, 1, wxEXPAND); + p->enable_port_picker(true); - auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:"))); - p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY); + auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:"))); + p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH); auto *label_status = new wxStaticText(panel, wxID_ANY, _(L("Status:"))); p->txt_status = new wxStaticText(panel, wxID_ANY, _(L("Ready"))); p->txt_status->SetFont(status_font); - auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:"))); - p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH); - auto *grid = new wxFlexGridSizer(2, SPACING, SPACING); grid->AddGrowableCol(1); - grid->Add(label_port_picker, 0, wxALIGN_CENTER_VERTICAL); - grid->Add(port_sizer, 0, wxEXPAND); grid->Add(label_hex_picker, 0, wxALIGN_CENTER_VERTICAL); grid->Add(p->hex_picker, 0, wxEXPAND); - grid->Add(label_status, 0, wxALIGN_CENTER_VERTICAL); - grid->Add(p->txt_status, 0, wxEXPAND); + grid->Add(label_port_picker, 0, wxALIGN_CENTER_VERTICAL); + grid->Add(port_sizer, 0, wxEXPAND); grid->Add(label_progress, 0, wxALIGN_CENTER_VERTICAL); grid->Add(p->progressbar, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL); + grid->Add(label_status, 0, wxALIGN_CENTER_VERTICAL); + grid->Add(p->txt_status, 0, wxEXPAND); + vsizer->Add(grid, 0, wxEXPAND | wxTOP | wxBOTTOM, SPACING); p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log"))); @@ -571,6 +747,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : p->btn_close = new wxButton(panel, wxID_CLOSE); p->btn_flash = new wxButton(panel, wxID_ANY, p->btn_flash_label_ready); + p->btn_flash->Disable(); auto *bsizer = new wxBoxSizer(wxHORIZONTAL); bsizer->Add(p->btn_close); bsizer->AddStretchSpacer(); @@ -585,6 +762,15 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : SetSize(std::max(size.GetWidth(), static_cast(MIN_WIDTH)), std::max(size.GetHeight(), static_cast(MIN_HEIGHT))); Layout(); + // Bind events + + p->hex_picker->Bind(wxEVT_FILEPICKER_CHANGED, [this](wxFileDirPickerEvent& evt) { + if (wxFileExists(evt.GetPath())) { + this->p->load_hex_file(evt.GetPath()); + this->p->btn_flash->Enable(); + } + }); + p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [this](wxCollapsiblePaneEvent &evt) { // Dialog size gets screwed up by wxCollapsiblePane, we need to fix it here if (evt.GetCollapsed()) { @@ -593,7 +779,6 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT_EXPANDED)); } - this->Fit(); this->Layout(); }); @@ -608,7 +793,10 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : _(L("Confirmation")), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() == wxID_YES) { - this->p->cancel(); + if (this->p->avrdude) { + this->p->set_txt_status(_(L("Cancelling..."))); + this->p->user_cancel(); + } } } else { // Start a flashing task @@ -619,6 +807,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->p->progressbar->Pulse(); }); Bind(EVT_AVRDUDE, [this](wxCommandEvent &evt) { this->p->on_avrdude(evt); }); + Bind(EVT_ASYNC_DIALOG, [this](wxCommandEvent &evt) { this->p->on_async_dialog(evt); }); Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt) { if (this->p->avrdude) { diff --git a/xs/src/slic3r/Utils/HexFile.cpp b/xs/src/slic3r/Utils/HexFile.cpp index ed26ddf37e..282c647bdc 100644 --- a/xs/src/slic3r/Utils/HexFile.cpp +++ b/xs/src/slic3r/Utils/HexFile.cpp @@ -46,8 +46,7 @@ static size_t hex_num_sections(fs::ifstream &file) } HexFile::HexFile(fs::path path) : - path(std::move(path)), - device(DEV_GENERIC) + path(std::move(path)) { fs::ifstream file(this->path); if (! file.good()) { diff --git a/xs/src/slic3r/Utils/HexFile.hpp b/xs/src/slic3r/Utils/HexFile.hpp index d8d1e09abd..1201d23a46 100644 --- a/xs/src/slic3r/Utils/HexFile.hpp +++ b/xs/src/slic3r/Utils/HexFile.hpp @@ -19,9 +19,10 @@ struct HexFile }; boost::filesystem::path path; - DeviceKind device; + DeviceKind device = DEV_GENERIC; std::string model_id; + HexFile() {} HexFile(boost::filesystem::path path); }; diff --git a/xs/src/slic3r/Utils/Serial.cpp b/xs/src/slic3r/Utils/Serial.cpp index c3c16b314e..183119b442 100644 --- a/xs/src/slic3r/Utils/Serial.cpp +++ b/xs/src/slic3r/Utils/Serial.cpp @@ -373,7 +373,7 @@ void Serial::set_DTR(bool on) void Serial::reset_line_num() { // See https://github.com/MarlinFirmware/Marlin/wiki/M110 - printer_write_line("M110 N0", 0); + write_string("M110 N0\n"); m_line_num = 0; } @@ -390,9 +390,9 @@ bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec) asio::async_read(*this, boost::asio::buffer(&c, 1), [&](const error_code &read_ec, size_t size) { if (ec || size == 0) { fail = true; - ec = read_ec; + ec = read_ec; // FIXME: only if operation not aborted } - timer.cancel(); + timer.cancel(); // FIXME: ditto }); if (timeout > 0) { @@ -444,6 +444,7 @@ bool Serial::printer_ready_wait(unsigned retries, unsigned timeout) } line.clear(); } + line.clear(); } @@ -469,7 +470,7 @@ void Serial::printer_reset() this->set_DTR(true); std::this_thread::sleep_for(std::chrono::milliseconds(200)); this->set_DTR(false); - std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } std::string Serial::printer_format_line(const std::string &line, unsigned line_num) diff --git a/xs/src/slic3r/Utils/Serial.hpp b/xs/src/slic3r/Utils/Serial.hpp index d15f249c07..e4a28de09d 100644 --- a/xs/src/slic3r/Utils/Serial.hpp +++ b/xs/src/slic3r/Utils/Serial.hpp @@ -51,12 +51,13 @@ public: // Reads a line or times out, the timeout is in milliseconds bool read_line(unsigned timeout, std::string &line, boost::system::error_code &ec); - // Perform setup for communicating with a printer + // Perform an initial setup for communicating with a printer void printer_setup(); // Write data from a string size_t write_string(const std::string &str); - + + // Attempts to reset the line numer and waits until the printer says "ok" bool printer_ready_wait(unsigned retries, unsigned timeout); // Write Marlin-formatted line, with a line number and a checksum From 675e4cfd2439aa31c8daeffd7e49e1ba02f45cac Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 30 Jul 2018 14:51:49 +0200 Subject: [PATCH 056/119] FirmwareDialog: Fix dialog resizing --- xs/src/slic3r/GUI/FirmwareDialog.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index c33a50e7d2..77e70c49b5 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -190,11 +190,13 @@ void FirmwareDialog::priv::find_serial_ports() void FirmwareDialog::priv::fit_no_shrink() { // Ensure content fits into window and window is not shrinked - auto old_size = q->GetSize(); + const auto old_size = q->GetSize(); q->Layout(); q->Fit(); - auto new_size = q->GetSize(); - q->SetSize(std::max(old_size.GetWidth(), new_size.GetWidth()), std::max(old_size.GetHeight(), new_size.GetHeight())); + const auto new_size = q->GetSize(); + const auto new_width = std::max(old_size.GetWidth(), new_size.GetWidth()); + const auto new_height = std::max(old_size.GetHeight(), new_size.GetHeight()); + q->SetSize(new_width, new_height); } void FirmwareDialog::priv::set_txt_status(const wxString &label) @@ -734,7 +736,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : vsizer->Add(grid, 0, wxEXPAND | wxTOP | wxBOTTOM, SPACING); - p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log"))); + p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log")), wxDefaultPosition, wxDefaultSize, wxCP_DEFAULT_STYLE | wxCP_NO_TLW_RESIZE); auto *spoiler_pane = p->spoiler->GetPane(); auto *spoiler_sizer = new wxBoxSizer(wxVERTICAL); p->txt_stdout = new wxTextCtrl(spoiler_pane, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY); @@ -772,14 +774,16 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : }); p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [this](wxCollapsiblePaneEvent &evt) { - // Dialog size gets screwed up by wxCollapsiblePane, we need to fix it here if (evt.GetCollapsed()) { this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT)); + const auto new_height = this->GetSize().GetHeight() - this->p->txt_stdout->GetSize().GetHeight(); + this->SetSize(this->GetSize().GetWidth(), new_height); } else { this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT_EXPANDED)); } this->Layout(); + this->p->fit_no_shrink(); }); p->btn_close->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { this->Close(); }); @@ -793,10 +797,8 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : _(L("Confirmation")), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() == wxID_YES) { - if (this->p->avrdude) { - this->p->set_txt_status(_(L("Cancelling..."))); - this->p->user_cancel(); - } + this->p->set_txt_status(_(L("Cancelling..."))); + this->p->user_cancel(); } } else { // Start a flashing task From 212ebc5615142ade23df909de05396d345942c90 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 1 Aug 2018 19:07:49 +0200 Subject: [PATCH 057/119] Updated the start G-codes for the MK3 MMU2 profiles: Implemented wiping line for MMU when the priming blocks are disabled, added initialization of the MMU2 unit with the filament types. --- resources/profiles/PrusaResearch.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 8e0ab024b6..446ee51480 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -1203,7 +1203,7 @@ default_filament_profile = Prusa PLA MMU2 [printer:Original Prusa i3 MK3 MMU2 Single] inherits = *mm2* -start_gcode = M107\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n ; go outside print area\nG1 Y-3.0 F1000.0 \nG1 Z0.4 F1000\n; select extruder\nT?\n; initial load\nG1 X50 E15 F1073\nG1 X100 E10 F2000\nG1 Z0.3 F1000\n\nG92 E0.0\nG1 X240.0 E15.0 F2400.0 \nG1 Y-2.0 F1000.0\nG1 X100.0 E10 F1400.0 \nG1 Z0.20 F1000\nG1 X0.0 E4 F1000.0\n\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK3 MMU2] @@ -1213,7 +1213,7 @@ inherits = *mm2* # to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\n\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n{if not has_single_extruder_multi_material_priming}\n; go outside print area\nG1 Y-3.0 F1000.0 \nG1 Z0.4 F1000\n; select extruder\nT[initial_tool]\n; initial load\nG1 X50 E15 F1073\nG1 X100 E10 F2000\nG1 Z0.3 F1000\nG92 E0.0\nG1 X240.0 E15.0 F2400.0 \nG1 Y-2.0 F1000.0\nG1 X100.0 E10 F1400.0 \nG1 Z0.20 F1000\nG1 X0.0 E4 F1000.0\nG92 E0.0\n{endif}\n\n;M221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\n\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\n;M221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n # The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. From 8fc11c2f14a04c5911cd6efc3a888cfc45467976 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 2 Aug 2018 09:11:11 +0200 Subject: [PATCH 058/119] Configuration improvements: Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2 --- resources/profiles/PrusaResearch.idx | 1 + resources/profiles/PrusaResearch.ini | 49 +++++++++++++--------------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index db5a17b8dc..9d1806c037 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,5 @@ min_slic3r_version = 1.41.0-alpha +0.2.0-alpha5 Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2 0.2.0-alpha4 Extended the custom start/end G-codes of the MMU2.0 printers for no priming towers. 0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost 0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 446ee51480..63f4bfe3f2 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.2.0-alpha4 +config_version = 0.2.0-alpha5 # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -95,6 +95,7 @@ print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest +single_extruder_multi_material_priming = 1 skirts = 1 skirt_distance = 2 skirt_height = 3 @@ -136,6 +137,10 @@ wipe_tower_x = 200 wipe_tower_y = 155 xy_size_compensation = 0 +[print:*MK3*] +fill_pattern = grid +single_extruder_multi_material_priming = 0 + # Print parameters common to a 0.25mm diameter nozzle. [print:*0.25nozzle*] external_perimeter_extrusion_width = 0.25 @@ -211,9 +216,8 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and infill_extrusion_width = 0.5 [print:0.05mm ULTRADETAIL MK3] -inherits = *0.05mm* +inherits = *0.05mm*, *MK3* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material -fill_pattern = grid top_infill_extrusion_width = 0.4 [print:0.05mm ULTRADETAIL 0.25 nozzle] @@ -228,9 +232,8 @@ solid_infill_speed = 20 support_material_speed = 20 [print:0.05mm ULTRADETAIL 0.25 nozzle MK3] -inherits = *0.05mm*; *0.25nozzle* +inherits = *0.05mm*; *0.25nozzle*, *MK3* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 -fill_pattern = grid # XXXXXXXXXXXXXXXXXXXX # XXX--- 0.10mm ---XXX @@ -255,11 +258,10 @@ perimeter_speed = 50 solid_infill_speed = 50 [print:0.10mm DETAIL MK3] -inherits = *0.10mm* +inherits = *0.10mm*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -282,11 +284,10 @@ solid_infill_speed = 40 top_solid_infill_speed = 30 [print:0.10mm DETAIL 0.25 nozzle MK3] -inherits = *0.10mm*; *0.25nozzle* +inherits = *0.10mm*; *0.25nozzle*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -295,11 +296,10 @@ solid_infill_speed = 200 top_solid_infill_speed = 50 [print:0.10mm DETAIL 0.6 nozzle MK3] -inherits = *0.10mm*; *0.6nozzle* +inherits = *0.10mm*; *0.6nozzle*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -361,11 +361,10 @@ inherits = *0.15mm*; *0.6nozzle* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 [print:0.15mm OPTIMAL MK3] -inherits = *0.15mm* +inherits = *0.15mm*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -393,11 +392,10 @@ support_material_with_sheath = 0 support_material_xy_spacing = 80% [print:0.15mm OPTIMAL 0.25 nozzle MK3] -inherits = *0.15mm*; *0.25nozzle* +inherits = *0.15mm*; *0.25nozzle*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -419,11 +417,10 @@ top_infill_extrusion_width = 0.4 top_solid_layers = 5 [print:0.15mm OPTIMAL 0.6 nozzle MK3] -inherits = *0.15mm*; *0.6nozzle* +inherits = *0.15mm*; *0.6nozzle*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -448,11 +445,10 @@ support_material_speed = 60 top_solid_infill_speed = 70 [print:0.20mm FAST MK3] -inherits = *0.20mm* +inherits = *0.20mm*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -486,11 +482,10 @@ support_material_with_sheath = 0 support_material_xy_spacing = 80% [print:0.20mm FAST 0.6 nozzle MK3] -inherits = *0.20mm*; *0.6nozzle* +inherits = *0.20mm*; *0.6nozzle*, *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 -fill_pattern = grid infill_acceleration = 1250 infill_speed = 200 max_print_speed = 200 @@ -1129,17 +1124,17 @@ default_print_profile = 0.20mm NORMAL 0.6 nozzle [printer:Original Prusa i3 MK2.5] inherits = Original Prusa i3 MK2 printer_model = MK2.5 -start_gcode = M115 U3.2.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 0.25 nozzle] inherits = Original Prusa i3 MK2 0.25 nozzle printer_model = MK2.5 -start_gcode = M115 U3.2.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 0.6 nozzle] inherits = Original Prusa i3 MK2 0.6 nozzle printer_model = MK2.5 -start_gcode = M115 U3.2.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 # XXXXXXXXXXXXXXXXX # XXX--- MK3 ---XXX @@ -1168,7 +1163,7 @@ silent_mode = 1 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 max_print_height = 210 -start_gcode = M115 U3.3.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} +start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} printer_model = MK3 default_print_profile = 0.15mm OPTIMAL MK3 @@ -1203,7 +1198,7 @@ default_filament_profile = Prusa PLA MMU2 [printer:Original Prusa i3 MK3 MMU2 Single] inherits = *mm2* -start_gcode = M107\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT?\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK3 MMU2] @@ -1213,7 +1208,7 @@ inherits = *mm2* # to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#0080FF;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\n\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\n;M221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\n;M221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n # The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. From d5f042b4b82d46592739e19bc20408622258458e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 27 Jul 2018 15:56:27 +0200 Subject: [PATCH 059/119] Wipe tower postprocessing, wipe tower block on 3D plate improved. - it renders red with one egde as indeterminate, the front edge is where the wipe tower will start - changing width changes depth of the block (as requested) - the block shows the brim of the wipe tower - after slicing, the block is rendered in usual dark green and takes the exact shape of the tower (also with brim) - moving or rotationg the block after slicing does not invalidate the wipe tower (and hence the exact block dimensions are preserved) - changing anything that invalidates the wipe tower reverts the block back to the "indeterminate" shape - the block is not shown after slicing, if the wipe tower is not actually generated (printing single color object with the wipe tower enabled) This required changes in the wipe tower generator, which now generates the tower at origin with no rotation. Resulting gcode is postprocessed and transformed during gcode export. This means the wipe tower needs not be invalidated when it is moved or rotated. --- lib/Slic3r/GUI/Plater.pm | 8 +++ xs/src/libslic3r/GCode.cpp | 77 +++++++++++++++++++-- xs/src/libslic3r/GCode.hpp | 12 +++- xs/src/libslic3r/GCode/PrintExtents.cpp | 10 +++ xs/src/libslic3r/GCode/WipeTower.hpp | 29 ++++++-- xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 66 +++++++++--------- xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp | 3 + xs/src/libslic3r/Print.cpp | 20 ++++-- xs/src/libslic3r/Print.hpp | 4 ++ xs/src/libslic3r/PrintObject.cpp | 4 +- xs/src/slic3r/GUI/3DScene.cpp | 62 ++++++++++++++--- xs/src/slic3r/GUI/3DScene.hpp | 2 +- xs/src/slic3r/GUI/GLCanvas3D.cpp | 42 ++++++++--- xs/xsp/GUI_3DScene.xsp | 2 +- 14 files changed, 264 insertions(+), 77 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index c0718c77be..719f98a485 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1281,6 +1281,11 @@ sub async_apply_config { $self->{gcode_preview_data}->reset; $self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $self->{preview3D}->reload_print if $self->{preview3D}; + + # We also need to reload 3D scene because of the wipe tower preview box + if ($self->{config}->wipe_tower) { + Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1) if $self->{canvas3D} + } } } @@ -1493,6 +1498,9 @@ sub on_process_completed { return if $error; $self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $self->{preview3D}->reload_print if $self->{preview3D}; + + # in case this was MM print, wipe tower bounding box on 3D tab might need redrawing with exact depth: + Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1); # if we have an export filename, start a new thread for exporting G-code if ($self->{export_gcode_output_file}) { diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 94634f4e42..290872151b 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -167,6 +167,18 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T { std::string gcode; + // Toolchangeresult.gcode assumes the wipe tower corner is at the origin + // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position + float alpha = m_wipe_tower_rotation/180.f * M_PI; + WipeTower::xy start_pos = tcr.start_pos; + WipeTower::xy end_pos = tcr.end_pos; + start_pos.rotate(alpha); + start_pos.translate(m_wipe_tower_pos); + end_pos.rotate(alpha); + end_pos.translate(m_wipe_tower_pos); + std::string tcr_rotated_gcode = rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha); + + // Disable linear advance for the wipe tower operations. gcode += "M900 K0\n"; // Move over the wipe tower. @@ -174,14 +186,14 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T gcode += gcodegen.retract(true); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; gcode += gcodegen.travel_to( - wipe_tower_point_to_object_point(gcodegen, tcr.start_pos), + wipe_tower_point_to_object_point(gcodegen, start_pos), erMixed, "Travel to a Wipe Tower"); gcode += gcodegen.unretract(); // Let the tool change be executed by the wipe tower class. // Inform the G-code writer about the changes done behind its back. - gcode += tcr.gcode; + gcode += tcr_rotated_gcode; // Let the m_writer know the current extruder_id, but ignore the generated G-code. if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) gcodegen.writer().toolchange(new_extruder_id); @@ -195,18 +207,18 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T check_add_eol(gcode); } // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y)); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos)); + gcodegen.writer().travel_to_xy(Pointf(end_pos.x, end_pos.y)); + gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); // Prepare a future wipe. gcodegen.m_wipe.path.points.clear(); if (new_extruder_id >= 0) { // Start the wipe at the current position. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos)); + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos)); // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - WipeTower::xy((std::abs(m_left - tcr.end_pos.x) < std::abs(m_right - tcr.end_pos.x)) ? m_right : m_left, - tcr.end_pos.y))); + WipeTower::xy((std::abs(m_left - end_pos.x) < std::abs(m_right - end_pos.x)) ? m_right : m_left, + end_pos.y))); } // Let the planner know we are traveling between objects. @@ -214,6 +226,57 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T return gcode; } +// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode +// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate) +std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const +{ + std::istringstream gcode_str(gcode_original); + std::string gcode_out; + std::string line; + WipeTower::xy pos = start_pos; + WipeTower::xy transformed_pos; + WipeTower::xy old_pos(-1000.1f, -1000.1f); + + while (gcode_str) { + std::getline(gcode_str, line); // we read the gcode line by line + if (line.find("G1 ") == 0) { + std::ostringstream line_out; + std::istringstream line_str(line); + line_str >> std::noskipws; // don't skip whitespace + char ch = 0; + while (line_str >> ch) { + if (ch == 'X') + line_str >> pos.x; + else + if (ch == 'Y') + line_str >> pos.y; + else + line_out << ch; + } + + transformed_pos = pos; + transformed_pos.rotate(angle); + transformed_pos.translate(translation); + + if (transformed_pos != old_pos) { + line = line_out.str(); + char buf[2048] = "G1"; + if (transformed_pos.x != old_pos.x) + sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x); + if (transformed_pos.y != old_pos.y) + sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y); + + line.replace(line.find("G1 "), 3, buf); + old_pos = transformed_pos; + } + } + gcode_out += line + "\n"; + } + return gcode_out; +} + + + std::string WipeTowerIntegration::prime(GCode &gcodegen) { assert(m_layer_idx == 0); diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index 8b40385e64..4953c39fef 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -83,8 +83,10 @@ public: const WipeTower::ToolChangeResult &priming, const std::vector> &tool_changes, const WipeTower::ToolChangeResult &final_purge) : - m_left(float(print_config.wipe_tower_x.value)), - m_right(float(print_config.wipe_tower_x.value + print_config.wipe_tower_width.value)), + m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f), + m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)), + m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)), + m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)), m_priming(priming), m_tool_changes(tool_changes), m_final_purge(final_purge), @@ -101,9 +103,14 @@ private: WipeTowerIntegration& operator=(const WipeTowerIntegration&); std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const; + // Postprocesses gcode: rotates and moves all G1 extrusions and returns result + std::string rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const; + // Left / right edges of the wipe tower, for the planning of wipe moves. const float m_left; const float m_right; + const WipeTower::xy m_wipe_tower_pos; + const float m_wipe_tower_rotation; // Reference to cached values at the Printer class. const WipeTower::ToolChangeResult &m_priming; const std::vector> &m_tool_changes; @@ -112,6 +119,7 @@ private: int m_layer_idx; int m_tool_change_idx; bool m_brim_done; + bool i_have_brim = false; }; class GCode { diff --git a/xs/src/libslic3r/GCode/PrintExtents.cpp b/xs/src/libslic3r/GCode/PrintExtents.cpp index 3c3f0f8d5c..37b79f3434 100644 --- a/xs/src/libslic3r/GCode/PrintExtents.cpp +++ b/xs/src/libslic3r/GCode/PrintExtents.cpp @@ -134,6 +134,11 @@ BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object // The projection does not contain the priming regions. BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z) { + // Wipe tower extrusions are saved as if the tower was at the origin with no rotation + // We need to get position and angle of the wipe tower to transform them to actual position. + Pointf wipe_tower_pos(print.config.wipe_tower_x.value, print.config.wipe_tower_y.value); + float wipe_tower_angle = print.config.wipe_tower_rotation_angle.value; + BoundingBoxf bbox; for (const std::vector &tool_changes : print.m_wipe_tower_tool_changes) { if (! tool_changes.empty() && tool_changes.front().print_z > max_print_z) @@ -144,6 +149,11 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_ if (e.width > 0) { Pointf p1((&e - 1)->pos.x, (&e - 1)->pos.y); Pointf p2(e.pos.x, e.pos.y); + p1.rotate(wipe_tower_angle); + p1.translate(wipe_tower_pos); + p2.rotate(wipe_tower_angle); + p2.translate(wipe_tower_pos); + bbox.merge(p1); coordf_t radius = 0.5 * e.width; bbox.min.x = std::min(bbox.min.x, std::min(p1.x, p2.x) - radius); diff --git a/xs/src/libslic3r/GCode/WipeTower.hpp b/xs/src/libslic3r/GCode/WipeTower.hpp index 36cebeb847..9bf3503282 100644 --- a/xs/src/libslic3r/GCode/WipeTower.hpp +++ b/xs/src/libslic3r/GCode/WipeTower.hpp @@ -25,18 +25,30 @@ public: bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; } bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; } - // Rotate the point around given point about given angle (in degrees) - // shifts the result so that point of rotation is in the middle of the tower - xy rotate(const xy& origin, float width, float depth, float angle) const { + // Rotate the point around center of the wipe tower about given angle (in degrees) + xy rotate(float width, float depth, float angle) const { xy out(0,0); float temp_x = x - width / 2.f; float temp_y = y - depth / 2.f; angle *= M_PI/180.; - out.x += (temp_x - origin.x) * cos(angle) - (temp_y - origin.y) * sin(angle); - out.y += (temp_x - origin.x) * sin(angle) + (temp_y - origin.y) * cos(angle); - return out + origin; + out.x += temp_x * cos(angle) - temp_y * sin(angle) + width / 2.f; + out.y += temp_x * sin(angle) + temp_y * cos(angle) + depth / 2.f; + + return out; } - + + // Rotate the point around origin about given angle in degrees + void rotate(float angle) { + float temp_x = x * cos(angle) - y * sin(angle); + y = x * sin(angle) + y * cos(angle); + x = temp_x; + } + + void translate(const xy& vect) { + x += vect.x; + y += vect.y; + } + float x; float y; }; @@ -104,6 +116,9 @@ public: // This is useful not only for the print time estimation, but also for the control of layer cooling. float elapsed_time; + // Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later) + bool priming; + // Sum the total length of the extrusion. float total_extrusion_length_in_plane() { float e_length = 0.f; diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index f466fc4f65..3d0dba07af 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -5,7 +5,7 @@ TODO LIST 1. cooling moves - DONE 2. account for perimeter and finish_layer extrusions and subtract it from last wipe - DONE -3. priming extrusions (last wipe must clear the color) +3. priming extrusions (last wipe must clear the color) - DONE 4. Peter's wipe tower - layer's are not exactly square 5. Peter's wipe tower - variable width for higher levels 6. Peter's wipe tower - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer) @@ -17,7 +17,6 @@ TODO LIST #include #include -#include #include #include #include @@ -68,8 +67,11 @@ public: return *this; } - Writer& set_initial_position(const WipeTower::xy &pos) { - m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg); + Writer& set_initial_position(const WipeTower::xy &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) { + m_wipe_tower_width = width; + m_wipe_tower_depth = depth; + m_internal_angle = internal_angle; + m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); m_current_pos = pos; return *this; } @@ -81,9 +83,6 @@ public: Writer& set_extrusion_flow(float flow) { m_extrusion_flow = flow; return *this; } - - Writer& set_rotation(WipeTower::xy& pos, float width, float depth, float angle) - { m_wipe_tower_pos = pos; m_wipe_tower_width = width; m_wipe_tower_depth=depth; m_angle_deg = angle; return (*this); } Writer& set_y_shift(float shift) { m_current_pos.y -= shift-m_y_shift; @@ -110,7 +109,7 @@ public: float y() const { return m_current_pos.y; } const WipeTower::xy& pos() const { return m_current_pos; } const WipeTower::xy start_pos_rotated() const { return m_start_pos; } - const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg); } + const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); } float elapsed_time() const { return m_elapsed_time; } // Extrude with an explicitely provided amount of extrusion. @@ -125,9 +124,9 @@ public: double len = sqrt(dx*dx+dy*dy); - // For rotated wipe tower, transform position to printer coordinates - WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg)); // this is where we are - WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg)); // this is where we want to go + // Now do the "internal rotation" with respect to the wipe tower center + WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we are + WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we want to go if (! m_preview_suppressed && e > 0.f && len > 0.) { // Width of a squished extrusion, corrected for the roundings of the squished extrusions. @@ -147,6 +146,7 @@ public: if (std::abs(rot.y - rotated_current_pos.y) > EPSILON) m_gcode += set_format_Y(rot.y); + if (e != 0.f) m_gcode += set_format_E(e); @@ -397,9 +397,8 @@ private: std::string m_gcode; std::vector m_extrusions; float m_elapsed_time; - float m_angle_deg = 0.f; + float m_internal_angle = 0.f; float m_y_shift = 0.f; - WipeTower::xy m_wipe_tower_pos; float m_wipe_tower_width = 0.f; float m_wipe_tower_depth = 0.f; float m_last_fan_speed = 0.f; @@ -539,6 +538,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( m_print_brim = true; ToolChangeResult result; + result.priming = true; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -575,7 +575,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo } box_coordinates cleaning_box( - m_wipe_tower_pos + xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f), + xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f), m_wipe_tower_width - m_perimeter_width, (tool != (unsigned int)(-1) ? /*m_layer_info->depth*/wipe_area+m_depth_traversed-0.5*m_perimeter_width : m_wipe_tower_depth-m_perimeter_width)); @@ -584,7 +584,6 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) - .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle) .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f)) .append(";--------------------\n" "; CP TOOLCHANGE START\n") @@ -594,7 +593,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo .speed_override(100); xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed); - writer.set_initial_position(initial_position); + writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); // Increase the extruder driver current to allow fast ramming. writer.set_extruder_trimpot(750); @@ -616,11 +615,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo if (last_change_in_layer) {// draw perimeter line writer.set_y_shift(m_y_shift); if (m_peters_wipe_tower) - writer.rectangle(m_wipe_tower_pos,m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth); + writer.rectangle(WipeTower::xy(0.f, 0.f),m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth); else { - writer.rectangle(m_wipe_tower_pos,m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); + writer.rectangle(WipeTower::xy(0.f, 0.f),m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle - writer.travel(m_wipe_tower_pos.x + (writer.x()> (m_wipe_tower_pos.x + m_wipe_tower_width) / 2.f ? 0.f : m_wipe_tower_width), writer.y()); + writer.travel(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); } } } @@ -634,6 +633,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo "\n\n"); ToolChangeResult result; + result.priming = false; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -647,7 +647,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, float y_offset) { const box_coordinates wipeTower_box( - m_wipe_tower_pos, + WipeTower::xy(0.f, 0.f), m_wipe_tower_width, m_wipe_tower_depth); @@ -655,12 +655,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo writer.set_extrusion_flow(m_extrusion_flow * 1.1f) .set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop. .set_initial_tool(m_current_tool) - .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle) .append(";-------------------------------------\n" "; CP WIPE TOWER FIRST LAYER BRIM START\n"); xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0); - writer.set_initial_position(initial_position); + writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower. 1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400); @@ -685,6 +684,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo m_print_brim = false; // Mark the brim as extruded ToolChangeResult result; + result.priming = false; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -724,7 +724,7 @@ void WipeTowerPrusaMM::toolchange_Unload( if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) { // this is y of the center of previous sparse infill border - float sparse_beginning_y = m_wipe_tower_pos.y; + float sparse_beginning_y = 0.f; if (m_current_shape == SHAPE_REVERSED) sparse_beginning_y += ((m_layer_info-1)->depth - (m_layer_info-1)->toolchanges_depth()) - ((m_layer_info)->depth-(m_layer_info)->toolchanges_depth()) ; @@ -742,7 +742,7 @@ void WipeTowerPrusaMM::toolchange_Unload( for (const auto& tch : m_layer_info->tool_changes) { // let's find this toolchange if (tch.old_tool == m_current_tool) { sum_of_depths += tch.ramming_depth; - float ramming_end_y = m_wipe_tower_pos.y + sum_of_depths; + float ramming_end_y = sum_of_depths; ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line // debugging: @@ -950,7 +950,7 @@ void WipeTowerPrusaMM::toolchange_Wipe( if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) { m_left_to_right = !m_left_to_right; writer.travel(writer.x(), writer.y() - dy) - .travel(m_wipe_tower_pos.x + (m_left_to_right ? m_wipe_tower_width : 0.f), writer.y()); + .travel(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y()); } writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. @@ -969,7 +969,6 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) - .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle) .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower ? m_layer_info->toolchanges_depth() : 0.f)) .append(";--------------------\n" "; CP EMPTY GRID START\n") @@ -978,14 +977,12 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() // Slow down on the 1st layer. float speed_factor = m_is_first_layer ? 0.5f : 1.f; float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); - box_coordinates fill_box(m_wipe_tower_pos + xy(m_perimeter_width, m_depth_traversed + m_perimeter_width), + box_coordinates fill_box(xy(m_perimeter_width, m_depth_traversed + m_perimeter_width), m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); - if (m_left_to_right) // so there is never a diagonal travel - writer.set_initial_position(fill_box.ru); - else - writer.set_initial_position(fill_box.lu); + writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), // so there is never a diagonal travel + m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); box_coordinates box = fill_box; for (int i=0;i<2;++i) { @@ -1044,6 +1041,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() m_depth_traversed = m_wipe_tower_depth-m_perimeter_width; ToolChangeResult result; + result.priming = false; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -1165,9 +1163,9 @@ void WipeTowerPrusaMM::generate(std::vectordepth < m_wipe_tower_depth - m_perimeter_width) m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f; @@ -1188,7 +1186,7 @@ void WipeTowerPrusaMM::generate(std::vector> &result); + float get_depth() const { return m_wipe_tower_depth; } + // Switch to a next layer. @@ -189,6 +191,7 @@ private: float m_wipe_tower_width; // Width of the wipe tower. float m_wipe_tower_depth = 0.f; // Depth of the wipe tower float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) + float m_internal_rotation = 0.f; float m_y_shift = 0.f; // y shift passed to writer float m_z_pos = 0.f; // Current Z position. float m_layer_height = 0.f; // Current layer height. diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index e08ae1fc4b..3f0646bc6f 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -166,7 +166,10 @@ bool Print::invalidate_state_by_config_options(const std::vector steps; @@ -175,7 +178,12 @@ bool Print::invalidate_state_by_config_options(const std::vectorhas_wipe_tower()) return; + m_wipe_tower_depth = 0.f; + // Get wiping matrix to get number of extruders and convert vector to vector: std::vector wiping_matrix((this->config.wiping_volumes_matrix.values).begin(),(this->config.wiping_volumes_matrix.values).end()); // Extract purging volumes for each extruder pair: @@ -1162,7 +1169,8 @@ void Print::_make_wipe_tower() // Generate the wipe tower layers. m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size()); wipe_tower.generate(m_wipe_tower_tool_changes); - + m_wipe_tower_depth = wipe_tower.get_depth(); + // Unload the current filament over the purge tower. coordf_t layer_height = this->objects.front()->config.layer_height.value; if (m_tool_ordering.back().wipe_tower_partitions > 0) { diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index bcd61ea023..e3430ad0e4 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -273,6 +273,7 @@ public: void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig config); + float get_wipe_tower_depth() const { return m_wipe_tower_depth; } bool has_infinite_skirt() const; bool has_skirt() const; // Returns an empty string if valid, otherwise returns an error message. @@ -326,6 +327,9 @@ private: bool invalidate_state_by_config_options(const std::vector &opt_keys); PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume); + // Depth of the wipe tower to pass to GLCanvas3D for exact bounding box: + float m_wipe_tower_depth = 0.f; + // Has the calculation been canceled? tbb::atomic m_canceled; }; diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 47495dad8b..7150ead596 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -75,6 +75,7 @@ bool PrintObject::delete_last_copy() bool PrintObject::set_copies(const Points &points) { + bool copies_num_changed = this->_copies.size() != points.size(); this->_copies = points; // order copies with a nearest neighbor search and translate them by _copies_shift @@ -93,7 +94,8 @@ bool PrintObject::set_copies(const Points &points) bool invalidated = this->_print->invalidate_step(psSkirt); invalidated |= this->_print->invalidate_step(psBrim); - invalidated |= this->_print->invalidate_step(psWipeTower); + if (copies_num_changed) + invalidated |= this->_print->invalidate_step(psWipeTower); return invalidated; } diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 62659033ad..c4cfa537ea 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -643,20 +643,62 @@ std::vector GLVolumeCollection::load_object( return volumes_idx; } -int GLVolumeCollection::load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs) -{ - float color[4] = { 0.5f, 0.5f, 0.0f, 0.5f }; - this->volumes.emplace_back(new GLVolume(color)); - GLVolume &v = *this->volumes.back(); +int GLVolumeCollection::load_wipe_tower_preview( + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width) +{ + if (depth < 0.01f) + return int(this->volumes.size() - 1); if (height == 0.0f) height = 0.1f; - - auto mesh = make_cube(width, depth, height); - mesh.translate(-width / 2.f, -depth / 2.f, 0.f); Point origin_of_rotation(0.f, 0.f); - mesh.rotate(rotation_angle,&origin_of_rotation); + TriangleMesh mesh; + float color[4] = { 0.5f, 0.5f, 0.0f, 1.f }; + + // In case we don't know precise dimensions of the wipe tower yet, we'll draw the box with different color with one side jagged: + if (size_unknown) { + color[0] = 1.f; + color[1] = 0.f; + + depth = std::max(depth, 10.f); // Too narrow tower would interfere with the teeth. The estimate is not precise anyway. + float min_width = 30.f; + // We'll now create the box with jagged edge. y-coordinates of the pre-generated model are shifted so that the front + // edge has y=0 and centerline of the back edge has y=depth: + Pointf3s points; + std::vector facets; + float out_points_idx[][3] = {{0, -depth, 0}, {0, 0, 0}, {38.453, 0, 0}, {61.547, 0, 0}, {100, 0, 0}, {100, -depth, 0}, {55.7735, -10, 0}, {44.2265, 10, 0}, + {38.453, 0, 1}, {0, 0, 1}, {0, -depth, 1}, {100, -depth, 1}, {100, 0, 1}, {61.547, 0, 1}, {55.7735, -10, 1}, {44.2265, 10, 1}}; + int out_facets_idx[][3] = {{0, 1, 2}, {3, 4, 5}, {6, 5, 0}, {3, 5, 6}, {6, 2, 7}, {6, 0, 2}, {8, 9, 10}, {11, 12, 13}, {10, 11, 14}, {14, 11, 13}, {15, 8, 14}, + {8, 10, 14}, {3, 12, 4}, {3, 13, 12}, {6, 13, 3}, {6, 14, 13}, {7, 14, 6}, {7, 15, 14}, {2, 15, 7}, {2, 8, 15}, {1, 8, 2}, {1, 9, 8}, + {0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11}}; + for (int i=0;i<16;++i) + points.push_back(Pointf3(out_points_idx[i][0] / (100.f/min_width), out_points_idx[i][1] + depth, out_points_idx[i][2])); + for (int i=0;i<28;++i) + facets.push_back(Point3(out_facets_idx[i][0], out_facets_idx[i][1], out_facets_idx[i][2])); + TriangleMesh tooth_mesh(points, facets); + + // We have the mesh ready. It has one tooth and width of min_width. We will now append several of these together until we are close to + // the required width of the block. Than we can scale it precisely. + size_t n = std::max(1, int(width/min_width)); // How many shall be merged? + for (size_t i=0;ivolumes.emplace_back(new GLVolume(color)); + GLVolume &v = *this->volumes.back(); if (use_VBOs) v.indexed_vertex_array.load_mesh_full_shading(mesh); diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index 5409b95883..ca51704d8b 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -399,7 +399,7 @@ public: bool use_VBOs); int load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs); + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width); // Render the volumes by OpenGL. void render_VBOs() const; diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 722f1c1124..957a3e4f21 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -2292,7 +2292,12 @@ void GLCanvas3D::reload_scene(bool force) float w = dynamic_cast(m_config->option("wipe_tower_width"))->value; float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value; - m_volumes.load_wipe_tower_preview(1000, x, y, w, 15.0f * (float)(extruders_count - 1), (float)height, a, m_use_VBOs && m_initialized); + float depth = m_print->get_wipe_tower_depth(); + if (!m_print->state.is_done(psWipeTower)) + depth = (900.f/w) * (float)(extruders_count - 1) ; + + m_volumes.load_wipe_tower_preview(1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !m_print->state.is_done(psWipeTower), + m_print->config.nozzle_diameter.values[0] * 1.25f * 4.5f); } } @@ -2554,6 +2559,8 @@ void GLCanvas3D::load_wipe_tower_toolpaths(const std::vector& str_t { const Print *print; const std::vector *tool_colors; + WipeTower::xy wipe_tower_pos; + float wipe_tower_angle; // Number of vertices (each vertex is 6x4=24 bytes long) static const size_t alloc_size_max() { return 131072; } // 3.15MB @@ -2586,6 +2593,9 @@ void GLCanvas3D::load_wipe_tower_toolpaths(const std::vector& str_t if (m_print->m_wipe_tower_final_purge) ctxt.final.emplace_back(*m_print->m_wipe_tower_final_purge.get()); + ctxt.wipe_tower_angle = ctxt.print->config.wipe_tower_rotation_angle.value/180.f * M_PI; + ctxt.wipe_tower_pos = WipeTower::xy(ctxt.print->config.wipe_tower_x.value, ctxt.print->config.wipe_tower_y.value); + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start"; //FIXME Improve the heuristics for a grain size. @@ -2644,12 +2654,25 @@ void GLCanvas3D::load_wipe_tower_toolpaths(const std::vector& str_t lines.reserve(n_lines); widths.reserve(n_lines); heights.assign(n_lines, extrusions.layer_height); + WipeTower::Extrusion e_prev = extrusions.extrusions[i-1]; + + if (!extrusions.priming) { // wipe tower extrusions describe the wipe tower at the origin with no rotation + e_prev.pos.rotate(ctxt.wipe_tower_angle); + e_prev.pos.translate(ctxt.wipe_tower_pos); + } + for (; i < j; ++i) { - const WipeTower::Extrusion &e = extrusions.extrusions[i]; + WipeTower::Extrusion e = extrusions.extrusions[i]; assert(e.width > 0.f); - const WipeTower::Extrusion &e_prev = *(&e - 1); + if (!extrusions.priming) { + e.pos.rotate(ctxt.wipe_tower_angle); + e.pos.translate(ctxt.wipe_tower_pos); + } + lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y)); widths.emplace_back(e.width); + + e_prev = e; } _3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, *vols[ctxt.volume_idx(e.tool, 0)]); @@ -3652,7 +3675,7 @@ void GLCanvas3D::_camera_tranform() const ::glMatrixMode(GL_MODELVIEW); ::glLoadIdentity(); - ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); //pitch + ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch ::glRotatef(m_camera.phi, 0.0f, 0.0f, 1.0f); // yaw Pointf3 neg_target = m_camera.target.negative(); @@ -4627,8 +4650,11 @@ void GLCanvas3D::_load_shells() const PrintConfig& config = m_print->config; unsigned int extruders_count = config.nozzle_diameter.size(); if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { - const float width_per_extruder = 15.0f; // a simple workaround after wipe_tower_per_color_wipe got obsolete - m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, width_per_extruder * (extruders_count - 1), max_z, config.wipe_tower_rotation_angle, m_use_VBOs && m_initialized); + float depth = m_print->get_wipe_tower_depth(); + if (!m_print->state.is_done(psWipeTower)) + depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; + m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, + m_use_VBOs && m_initialized, !m_print->state.is_done(psWipeTower), m_print->config.nozzle_diameter.values[0] * 1.25f * 4.5f); } } @@ -4696,7 +4722,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs) if (m_model == nullptr) return; - std::set done; //prevent moving instances twice + std::set done; // prevent moving instances twice bool object_moved = false; Pointf3 wipe_tower_origin(0.0, 0.0, 0.0); for (int volume_idx : volume_idxs) @@ -4705,7 +4731,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs) int obj_idx = volume->object_idx(); int instance_idx = volume->instance_idx(); - //prevent moving instances twice + // prevent moving instances twice char done_id[64]; ::sprintf(done_id, "%d_%d", obj_idx, instance_idx); if (done.find(done_id) != done.end()) diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 5c2f7df854..7c71904f34 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -89,7 +89,7 @@ std::vector load_object(ModelObject *object, int obj_idx, std::vector instance_idxs, std::string color_by, std::string select_by, std::string drag_by, bool use_VBOs); - int load_wipe_tower_preview(int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs); + int load_wipe_tower_preview(int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width); void erase() %code{% THIS->clear(); %}; From 76838703502be55a6cb67d72bf4990619a013ad6 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 1 Aug 2018 15:34:33 +0200 Subject: [PATCH 060/119] New perl callback to force reloading of 3d scene after Purging volumes are changed After the changes in previous commit, the 3D scene must be reloaded after the wipe tower is invalidated. This can mostly be done on the C++ side, but reloading after Purging volumes are changed required this C++ -> Perl call --- lib/Slic3r/GUI/Plater.pm | 7 +++++++ xs/src/slic3r/GUI/GUI.cpp | 5 +++++ xs/src/slic3r/GUI/GUI.hpp | 4 ++++ xs/xsp/GUI.xsp | 9 +++++++++ 4 files changed, 25 insertions(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 719f98a485..3bfd57bb30 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -144,6 +144,11 @@ sub new { my ($angle_z) = @_; $self->rotate(rad2deg($angle_z), Z, 'absolute'); }; + + # callback to call schedule_background_process + my $on_request_update = sub { + $self->schedule_background_process; + }; # callback to update object's geometry info while using gizmos my $on_update_geometry_info = sub { @@ -202,6 +207,8 @@ sub new { Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{canvas3D}, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{preview3D}->canvas, $self->{canvas3D}); }); } + + Slic3r::_GUI::register_on_request_update_callback($on_request_update); # Initialize 2D preview canvas $self->{canvas} = Slic3r::GUI::Plater::2D->new($self->{preview_notebook}, wxDefaultSize, $self->{objects}, $self->{model}, $self->{config}); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index af7022f2ba..8e351b05f8 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -901,6 +901,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl std::vector extruders = dlg.get_extruders(); (config.option("wiping_volumes_matrix"))->values = std::vector(matrix.begin(),matrix.end()); (config.option("wiping_volumes_extruders"))->values = std::vector(extruders.begin(),extruders.end()); + g_on_request_update_callback.call(); } })); return sizer; @@ -917,6 +918,10 @@ ConfigOptionsGroup* get_optgroup() return m_optgroup.get(); } +void register_on_request_update_callback(void* callback) { + if (callback != nullptr) + g_on_request_update_callback.register_callback(callback); +} wxButton* get_wiping_dialog_button() { diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index efb11b7dfa..c41ba2521d 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -4,6 +4,7 @@ #include #include #include "Config.hpp" +#include "../../libslic3r/Utils.hpp" #include #include @@ -171,6 +172,9 @@ wxString from_u8(const std::string &str); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); +static PerlCallback g_on_request_update_callback; +void register_on_request_update_callback(void* callback); + ConfigOptionsGroup* get_optgroup(); wxButton* get_wiping_dialog_button(); diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 6b05e9a67c..7872abc806 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -104,3 +104,12 @@ void fix_model_by_win10_sdk_gui(ModelObject *model_object_src, Print *print, Mod void set_3DScene(SV *scene) %code%{ Slic3r::GUI::set_3DScene((_3DScene *)wxPli_sv_2_object(aTHX_ scene, "Slic3r::Model::3DScene") ); %}; + +%package{Slic3r::_GUI}; +%{ +void +register_on_request_update_callback(callback) + SV *callback; + CODE: + Slic3r::GUI::register_on_request_update_callback((void*)callback); +%} From cd919d986a308fe3b5520cb6a1a475110078ab5a Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 2 Aug 2018 13:09:53 +0200 Subject: [PATCH 061/119] Fixed the *MK3* references in Prusa3D profiles --- resources/profiles/PrusaResearch.idx | 2 ++ resources/profiles/PrusaResearch.ini | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 9d1806c037..b122a13c5c 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,6 @@ min_slic3r_version = 1.41.0-alpha +0.2.0-alpha7 Fixed the *MK3* references +0.2.0-alpha6 0.2.0-alpha5 Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2 0.2.0-alpha4 Extended the custom start/end G-codes of the MMU2.0 printers for no priming towers. 0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 63f4bfe3f2..27ba179ad6 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.2.0-alpha5 +config_version = 0.2.0-alpha7 # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -216,7 +216,7 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and infill_extrusion_width = 0.5 [print:0.05mm ULTRADETAIL MK3] -inherits = *0.05mm*, *MK3* +inherits = *0.05mm*; *MK3* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material top_infill_extrusion_width = 0.4 @@ -232,7 +232,7 @@ solid_infill_speed = 20 support_material_speed = 20 [print:0.05mm ULTRADETAIL 0.25 nozzle MK3] -inherits = *0.05mm*; *0.25nozzle*, *MK3* +inherits = *0.05mm*; *0.25nozzle*; *MK3* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 # XXXXXXXXXXXXXXXXXXXX @@ -258,7 +258,7 @@ perimeter_speed = 50 solid_infill_speed = 50 [print:0.10mm DETAIL MK3] -inherits = *0.10mm*, *MK3* +inherits = *0.10mm*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material external_perimeter_speed = 35 @@ -284,7 +284,7 @@ solid_infill_speed = 40 top_solid_infill_speed = 30 [print:0.10mm DETAIL 0.25 nozzle MK3] -inherits = *0.10mm*; *0.25nozzle*, *MK3* +inherits = *0.10mm*; *0.25nozzle*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 external_perimeter_speed = 35 @@ -296,7 +296,7 @@ solid_infill_speed = 200 top_solid_infill_speed = 50 [print:0.10mm DETAIL 0.6 nozzle MK3] -inherits = *0.10mm*; *0.6nozzle*, *MK3* +inherits = *0.10mm*; *0.6nozzle*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 @@ -361,7 +361,7 @@ inherits = *0.15mm*; *0.6nozzle* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 [print:0.15mm OPTIMAL MK3] -inherits = *0.15mm*, *MK3* +inherits = *0.15mm*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 external_perimeter_speed = 35 @@ -392,7 +392,7 @@ support_material_with_sheath = 0 support_material_xy_spacing = 80% [print:0.15mm OPTIMAL 0.25 nozzle MK3] -inherits = *0.15mm*; *0.25nozzle*, *MK3* +inherits = *0.15mm*; *0.25nozzle*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 external_perimeter_speed = 35 @@ -417,7 +417,7 @@ top_infill_extrusion_width = 0.4 top_solid_layers = 5 [print:0.15mm OPTIMAL 0.6 nozzle MK3] -inherits = *0.15mm*; *0.6nozzle*, *MK3* +inherits = *0.15mm*; *0.6nozzle*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 @@ -445,7 +445,7 @@ support_material_speed = 60 top_solid_infill_speed = 70 [print:0.20mm FAST MK3] -inherits = *0.20mm*, *MK3* +inherits = *0.20mm*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 external_perimeter_speed = 35 @@ -482,7 +482,7 @@ support_material_with_sheath = 0 support_material_xy_spacing = 80% [print:0.20mm FAST 0.6 nozzle MK3] -inherits = *0.20mm*; *0.6nozzle*, *MK3* +inherits = *0.20mm*; *0.6nozzle*; *MK3* bridge_speed = 30 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 From a7ba51bd111a8a38fda5a98fc82e12145c793d35 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 2 Aug 2018 13:15:30 +0200 Subject: [PATCH 062/119] Fixing the "last item doesn't fit" problem. --- .../libnest2d/libnest2d/geometry_traits.hpp | 31 ++ xs/src/libnest2d/libnest2d/libnest2d.hpp | 3 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 75 +-- xs/src/libslic3r/ModelArrange.hpp | 490 +++++++++++------- xs/src/slic3r/AppController.cpp | 19 +- 5 files changed, 399 insertions(+), 219 deletions(-) diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 99511d7755..00740f30cf 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -684,6 +684,20 @@ struct ShapeLike { return PointLike::distance(point, circ.center()) < circ.radius(); } + template + static bool isInside(const TPoint& point, + const _Box>& box) + { + auto px = getX(point); + auto py = getY(point); + auto minx = getX(box.minCorner()); + auto miny = getY(box.minCorner()); + auto maxx = getX(box.maxCorner()); + auto maxy = getY(box.maxCorner()); + + return px > minx && px < maxx && py > miny && py < maxy; + } + template static bool isInside(const RawShape& sh, const _Circle>& circ) @@ -702,6 +716,23 @@ struct ShapeLike { isInside(box.maxCorner(), circ); } + template + static bool isInside(const _Box>& ibb, + const _Box>& box) + { + auto iminX = getX(ibb.minCorner()); + auto imaxX = getX(ibb.maxCorner()); + auto iminY = getY(ibb.minCorner()); + auto imaxY = getY(ibb.maxCorner()); + + auto minX = getX(box.minCorner()); + auto maxX = getX(box.maxCorner()); + auto minY = getY(box.minCorner()); + auto maxY = getY(box.maxCorner()); + + return iminX > minX && imaxX < maxX && iminY > minY && imaxY < maxY; + } + template // Potential O(1) implementation may exist static inline TPoint& vertex(RawShape& sh, unsigned long idx) { diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index fad38b9a36..7f23de3583 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -473,8 +473,7 @@ public: template inline bool _Item::isInside(const _Box>& box) const { - _Rectangle rect(box.width(), box.height()); - return _Item::isInside(rect); + return ShapeLike::isInside(boundingBox(), box); } template inline bool diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 06163b00a7..0c5776400a 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -166,7 +166,7 @@ template class EdgeCache { using std::pow; return static_cast( - round( N/(ceil(pow(accuracy_, 2)*(N-1)) + 1) ) + std::round(N/std::pow(N, std::pow(accuracy_, 1.0/3.0))) ); } @@ -178,6 +178,7 @@ template class EdgeCache { contour_.corners.reserve(N / S + 1); auto N_1 = N-1; + contour_.corners.emplace_back(0.0); for(size_t i = 0; i < N_1; i += S) { contour_.corners.emplace_back( contour_.distances.at(i) / contour_.full_distance); @@ -192,6 +193,7 @@ template class EdgeCache { const auto S = stride(N); auto N_1 = N-1; hc.corners.reserve(N / S + 1); + hc.corners.emplace_back(0.0); for(size_t i = 0; i < N_1; i += S) { hc.corners.emplace_back( hc.distances.at(i) / hc.full_distance); @@ -484,7 +486,7 @@ public: bool static inline wouldFit(const RawShape& chull, const RawShape& bin) { auto bbch = sl::boundingBox(chull); auto bbin = sl::boundingBox(bin); - auto d = bbin.center() - bbch.center(); + auto d = bbch.center() - bbin.center(); auto chullcpy = chull; sl::translate(chullcpy, d); return sl::isInside(chullcpy, bin); @@ -579,17 +581,21 @@ public: pile_area += mitem.area(); } + auto merged_pile = Nfp::merge(pile); + // This is the kernel part of the object function that is // customizable by the library client auto _objfunc = config_.object_function? config_.object_function : - [this](Nfp::Shapes& pile, const Item& item, - double occupied_area, double /*norm*/, - double penality) + [this, &merged_pile]( + Nfp::Shapes& /*pile*/, + const Item& item, + double occupied_area, double norm, + double /*penality*/) { - pile.emplace_back(item.transformedShape()); - auto ch = sl::convexHull(pile); - pile.pop_back(); + merged_pile.emplace_back(item.transformedShape()); + auto ch = sl::convexHull(merged_pile); + merged_pile.pop_back(); // The pack ratio -- how much is the convex hull occupied double pack_rate = occupied_area/sl::area(ch); @@ -602,7 +608,7 @@ public: // (larger) values. auto score = std::sqrt(waste); - if(!wouldFit(ch, bin_)) score = 2*penality - score; + if(!wouldFit(ch, bin_)) score += norm; return score; }; @@ -622,9 +628,22 @@ public: return score; }; + auto boundaryCheck = [&](const Optimum& o) { + auto v = getNfpPoint(o); + auto d = v - iv; + d += startpos; + item.translation(d); + + merged_pile.emplace_back(item.transformedShape()); + auto chull = sl::convexHull(merged_pile); + merged_pile.pop_back(); + + return wouldFit(chull, bin_); + }; + opt::StopCriteria stopcr; - stopcr.max_iterations = 1000; - stopcr.absolute_score_difference = 1e-20*norm_; + stopcr.max_iterations = 100; + stopcr.relative_score_difference = 1e-6; opt::TOptimizer solver(stopcr); Optimum optimum(0, 0); @@ -644,7 +663,7 @@ public: std::for_each(cache.corners().begin(), cache.corners().end(), [ch, &contour_ofn, &solver, &best_score, - &optimum] (double pos) + &optimum, &boundaryCheck] (double pos) { try { auto result = solver.optimize_min(contour_ofn, @@ -653,22 +672,15 @@ public: ); if(result.score < best_score) { - best_score = result.score; - optimum.relpos = std::get<0>(result.optimum); - optimum.nfpidx = ch; - optimum.hidx = -1; + Optimum o(std::get<0>(result.optimum), ch, -1); + if(boundaryCheck(o)) { + best_score = result.score; + optimum = o; + } } } catch(std::exception& e) { derr() << "ERROR: " << e.what() << "\n"; } - -// auto sc = contour_ofn(pos); -// if(sc < best_score) { -// best_score = sc; -// optimum.relpos = pos; -// optimum.nfpidx = ch; -// optimum.hidx = -1; -// } }); for(unsigned hidx = 0; hidx < cache.holeCount(); ++hidx) { @@ -683,7 +695,7 @@ public: std::for_each(cache.corners(hidx).begin(), cache.corners(hidx).end(), [&hole_ofn, &solver, &best_score, - &optimum, ch, hidx] + &optimum, ch, hidx, &boundaryCheck] (double pos) { try { @@ -693,21 +705,16 @@ public: ); if(result.score < best_score) { - best_score = result.score; Optimum o(std::get<0>(result.optimum), ch, hidx); - optimum = o; + if(boundaryCheck(o)) { + best_score = result.score; + optimum = o; + } } } catch(std::exception& e) { derr() << "ERROR: " << e.what() << "\n"; } -// auto sc = hole_ofn(pos); -// if(sc < best_score) { -// best_score = sc; -// optimum.relpos = pos; -// optimum.nfpidx = ch; -// optimum.hidx = hidx; -// } }); } } diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index af4bfcf703..73dc83c57d 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -93,6 +93,237 @@ void toSVG(SVG& svg, const Model& model) { } } +std::tuple +objfunc(const PointImpl& bincenter, + ShapeLike::Shapes& pile, // The currently arranged pile + const Item &item, + double norm // A norming factor for physical dimensions + ) +{ + using pl = PointLike; + + static const double BIG_ITEM_TRESHOLD = 0.2; + static const double ROUNDNESS_RATIO = 0.5; + static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; + + // We will treat big items (compared to the print bed) differently + NfpPlacer::Pile bigs; + bigs.reserve(pile.size()); + for(auto& p : pile) { + auto pbb = ShapeLike::boundingBox(p); + auto na = std::sqrt(pbb.width()*pbb.height())/norm; + if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p); + } + + // Candidate item bounding box + auto ibb = item.boundingBox(); + + // Calculate the full bounding box of the pile with the candidate item + pile.emplace_back(item.transformedShape()); + auto fullbb = ShapeLike::boundingBox(pile); + pile.pop_back(); + + // The bounding box of the big items (they will accumulate in the center + // of the pile + auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs); + + // The size indicator of the candidate item. This is not the area, + // but almost... + auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm; + + // Will hold the resulting score + double score = 0; + + if(itemnormarea > BIG_ITEM_TRESHOLD) { + // This branch is for the bigger items.. + // Here we will use the closest point of the item bounding box to + // the already arranged pile. So not the bb center nor the a choosen + // corner but whichever is the closest to the center. This will + // prevent unwanted strange arrangements. + + // Now the distance of the gravity center will be calculated to the + // five anchor points and the smallest will be chosen. + + auto minc = ibb.minCorner(); // bottom left corner + auto maxc = ibb.maxCorner(); // top right corner + + // top left and bottom right corners + auto top_left = PointImpl{getX(minc), getY(maxc)}; + auto bottom_right = PointImpl{getX(maxc), getY(minc)}; + + // Now the distnce of the gravity center will be calculated to the + // five anchor points and the smallest will be chosen. + std::array dists; + auto cc = fullbb.center(); // The gravity center + dists[0] = pl::distance(minc, cc); + dists[1] = pl::distance(maxc, cc); + dists[2] = pl::distance(ibb.center(), cc); + dists[3] = pl::distance(top_left, cc); + dists[4] = pl::distance(bottom_right, cc); + + auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; + + // Density is the pack density: how big is the arranged pile + auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; + + // The score is a weighted sum of the distance from pile center + // and the pile size + score = ROUNDNESS_RATIO * dist + DENSITY_RATIO * density; + + } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { + // If there are no big items, only small, we should consider the + // density here as well to not get silly results + auto bindist = pl::distance(ibb.center(), bincenter) / norm; + auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; + score = ROUNDNESS_RATIO * bindist + DENSITY_RATIO * density; + } else { + // Here there are the small items that should be placed around the + // already processed bigger items. + // No need to play around with the anchor points, the center will be + // just fine for small items + score = pl::distance(ibb.center(), bigbb.center()) / norm; + } + + return std::make_tuple(score, fullbb); +} + +template +void fillConfig(PConf& pcfg) { + + // Align the arranged pile into the center of the bin + pcfg.alignment = PConf::Alignment::CENTER; + + // Start placing the items from the center of the print bed + pcfg.starting_point = PConf::Alignment::CENTER; + + // TODO cannot use rotations until multiple objects of same geometry can + // handle different rotations + // arranger.useMinimumBoundigBoxRotation(); + pcfg.rotations = { 0.0 }; + + // The accuracy of optimization. + // Goes from 0.0 to 1.0 and scales performance as well + pcfg.accuracy = 0.35f; +} + +template +class AutoArranger {}; + +template +class _ArrBase { +protected: + using Placer = strategies::_NofitPolyPlacer; + using Selector = FirstFitSelection; + using Packer = Arranger; + using PConfig = typename Packer::PlacementConfig; + using Distance = TCoord; + using Pile = ShapeLike::Shapes; + + Packer pck_; + PConfig pconf_; // Placement configuration + +public: + + _ArrBase(const TBin& bin, Distance dist, + std::function progressind): + pck_(bin, dist) + { + fillConfig(pconf_); + pck_.progressIndicator(progressind); + } + + template inline IndexedPackGroup operator()(Args&&...args) { + return pck_.arrangeIndexed(std::forward(args)...); + } +}; + +template<> +class AutoArranger: public _ArrBase { +public: + + AutoArranger(const Box& bin, Distance dist, + std::function progressind): + _ArrBase(bin, dist, progressind) + { + pconf_.object_function = [bin] ( + Pile& pile, + const Item &item, + double /*occupied_area*/, + double norm, + double penality) { + + auto result = objfunc(bin.center(), pile, item, norm); + double score = std::get<0>(result); + auto& fullbb = std::get<1>(result); + + auto wdiff = fullbb.width() - bin.width(); + auto hdiff = fullbb.height() - bin.height(); + if(wdiff > 0) score += std::pow(wdiff, 2) / norm; + if(hdiff > 0) score += std::pow(hdiff, 2) / norm; + + return score; + }; + + pck_.configure(pconf_); + } +}; + +template<> +class AutoArranger: public _ArrBase { +public: + AutoArranger(const PolygonImpl& bin, Distance dist, + std::function progressind): + _ArrBase(bin, dist, progressind) + { + pconf_.object_function = [&bin] ( + Pile& pile, + const Item &item, + double /*area*/, + double norm, + double /*penality*/) { + + auto binbb = ShapeLike::boundingBox(bin); + auto result = objfunc(binbb.center(), pile, item, norm); + double score = std::get<0>(result); + + pile.emplace_back(item.transformedShape()); + auto chull = ShapeLike::convexHull(pile); + pile.pop_back(); + + // If it does not fit into the print bed we will beat it with a + // large penality. If we would not do this, there would be only one + // big pile that doesn't care whether it fits onto the print bed. + if(!Placer::wouldFit(chull, bin)) score += norm; + + return score; + }; + + pck_.configure(pconf_); + } +}; + +template<> // Specialization with no bin +class AutoArranger: public _ArrBase { +public: + + AutoArranger(Distance dist, std::function progressind): + _ArrBase(Box(0, 0), dist, progressind) + { + this->pconf_.object_function = [] ( + Pile& pile, + const Item &item, + double /*area*/, + double norm, + double /*penality*/) { + + auto result = objfunc({0, 0}, pile, item, norm); + return std::get<0>(result); + }; + + this->pck_.configure(pconf_); + } +}; + // A container which stores a pointer to the 3D object and its projected // 2D shape from top view. using ShapeData2D = @@ -147,6 +378,44 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) { return ret; } +enum BedShapeHint { + BOX, + CIRCLE, + IRREGULAR, + WHO_KNOWS +}; + +BedShapeHint bedShape(const Slic3r::Polyline& /*bed*/) { + // Determine the bed shape by hand + return BOX; +} + +void applyResult( + IndexedPackGroup::value_type& group, + Coord batch_offset, + ShapeData2D& shapemap) +{ + for(auto& r : group) { + auto idx = r.first; // get the original item index + Item& item = r.second; // get the item itself + + // Get the model instance from the shapemap using the index + ModelInstance *inst_ptr = shapemap[idx].first; + + // Get the tranformation data from the item object and scale it + // appropriately + auto off = item.translation(); + Radians rot = item.rotation(); + Pointf foff(off.X*SCALING_FACTOR + batch_offset, + off.Y*SCALING_FACTOR); + + // write the tranformation data into the model instance + inst_ptr->rotation = rot; + inst_ptr->offset = foff; + } +} + + /** * \brief Arranges the model objects on the screen. * @@ -170,7 +439,9 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) { * bed or leave them untouched (let the user arrange them by hand or remove * them). */ -bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, +bool arrange(Model &model, coordf_t min_obj_distance, + const Slic3r::Polyline& bed, + BedShapeHint bedhint, bool first_bin_only, std::function progressind) { @@ -178,215 +449,74 @@ bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb, bool ret = true; - // Create the arranger config - auto min_obj_distance = static_cast(dist/SCALING_FACTOR); - // Get the 2D projected shapes with their 3D model instance pointers auto shapemap = arr::projectModelFromTop(model); - bool hasbin = bb != nullptr && bb->defined; - double area_max = 0; - // Copy the references for the shapes only as the arranger expects a // sequence of objects convertible to Item or ClipperPolygon std::vector> shapes; shapes.reserve(shapemap.size()); std::for_each(shapemap.begin(), shapemap.end(), - [&shapes, min_obj_distance, &area_max, hasbin] - (ShapeData2D::value_type& it) + [&shapes] (ShapeData2D::value_type& it) { shapes.push_back(std::ref(it.second)); }); - Box bin; + IndexedPackGroup result; + BoundingBox bbb(bed.points); - if(hasbin) { - // Scale up the bounding box to clipper scale. - BoundingBoxf bbb = *bb; - bbb.scale(1.0/SCALING_FACTOR); + auto binbb = Box({ + static_cast(bbb.min.x), + static_cast(bbb.min.y) + }, + { + static_cast(bbb.max.x), + static_cast(bbb.max.y) + }); - bin = Box({ - static_cast(bbb.min.x), - static_cast(bbb.min.y) - }, - { - static_cast(bbb.max.x), - static_cast(bbb.max.y) - }); + switch(bedhint) { + case BOX: { + + // Create the arranger for the box shaped bed + AutoArranger arrange(binbb, min_obj_distance, progressind); + + // Arrange and return the items with their respective indices within the + // input sequence. + result = arrange(shapes.begin(), shapes.end()); + break; } + case CIRCLE: + break; + case IRREGULAR: + case WHO_KNOWS: { + using P = libnest2d::PolygonImpl; - // Will use the DJD selection heuristic with the BottomLeft placement - // strategy - using Arranger = Arranger; - using PConf = Arranger::PlacementConfig; - using SConf = Arranger::SelectionConfig; + auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); + P irrbed = ShapeLike::create(std::move(ctour)); - PConf pcfg; // Placement configuration - SConf scfg; // Selection configuration + std::cout << ShapeLike::toString(irrbed) << std::endl; - // Align the arranged pile into the center of the bin - pcfg.alignment = PConf::Alignment::CENTER; + AutoArranger

arrange(irrbed, min_obj_distance, progressind); - // Start placing the items from the center of the print bed - pcfg.starting_point = PConf::Alignment::CENTER; - - // TODO cannot use rotations until multiple objects of same geometry can - // handle different rotations - // arranger.useMinimumBoundigBoxRotation(); - pcfg.rotations = { 0.0 }; - - // The accuracy of optimization. Goes from 0.0 to 1.0 and scales performance - pcfg.accuracy = 0.4f; - - // Magic: we will specify what is the goal of arrangement... In this case - // we override the default object function to make the larger items go into - // the center of the pile and smaller items orbit it so the resulting pile - // has a circle-like shape. This is good for the print bed's heat profile. - // We alse sacrafice a bit of pack efficiency for this to work. As a side - // effect, the arrange procedure is a lot faster (we do not need to - // calculate the convex hulls) - pcfg.object_function = [bin, hasbin]( - NfpPlacer::Pile& pile, // The currently arranged pile - const Item &item, - double /*area*/, // Sum area of items (not needed) - double norm, // A norming factor for physical dimensions - double penality) // Min penality in case of bad arrangement - { - using pl = PointLike; - - static const double BIG_ITEM_TRESHOLD = 0.2; - static const double GRAVITY_RATIO = 0.5; - static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO; - - // We will treat big items (compared to the print bed) differently - NfpPlacer::Pile bigs; - bigs.reserve(pile.size()); - for(auto& p : pile) { - auto pbb = ShapeLike::boundingBox(p); - auto na = std::sqrt(pbb.width()*pbb.height())/norm; - if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p); - } - - // Candidate item bounding box - auto ibb = item.boundingBox(); - - // Calculate the full bounding box of the pile with the candidate item - pile.emplace_back(item.transformedShape()); - auto fullbb = ShapeLike::boundingBox(pile); - pile.pop_back(); - - // The bounding box of the big items (they will accumulate in the center - // of the pile - auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs); - - // The size indicator of the candidate item. This is not the area, - // but almost... - auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm; - - // Will hold the resulting score - double score = 0; - - if(itemnormarea > BIG_ITEM_TRESHOLD) { - // This branch is for the bigger items.. - // Here we will use the closest point of the item bounding box to - // the already arranged pile. So not the bb center nor the a choosen - // corner but whichever is the closest to the center. This will - // prevent unwanted strange arrangements. - - auto minc = ibb.minCorner(); // bottom left corner - auto maxc = ibb.maxCorner(); // top right corner - - // top left and bottom right corners - auto top_left = PointImpl{getX(minc), getY(maxc)}; - auto bottom_right = PointImpl{getX(maxc), getY(minc)}; - - auto cc = fullbb.center(); // The gravity center - - // Now the distnce of the gravity center will be calculated to the - // five anchor points and the smallest will be chosen. - std::array dists; - dists[0] = pl::distance(minc, cc); - dists[1] = pl::distance(maxc, cc); - dists[2] = pl::distance(ibb.center(), cc); - dists[3] = pl::distance(top_left, cc); - dists[4] = pl::distance(bottom_right, cc); - - auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; - - // Density is the pack density: how big is the arranged pile - auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; - - // The score is a weighted sum of the distance from pile center - // and the pile size - score = GRAVITY_RATIO * dist + DENSITY_RATIO * density; - - } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { - // If there are no big items, only small, we should consider the - // density here as well to not get silly results - auto bindist = pl::distance(ibb.center(), bin.center()) / norm; - auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; - score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density; - } else { - // Here there are the small items that should be placed around the - // already processed bigger items. - // No need to play around with the anchor points, the center will be - // just fine for small items - score = pl::distance(ibb.center(), bigbb.center()) / norm; - } - - // If it does not fit into the print bed we will beat it - // with a large penality. If we would not do this, there would be only - // one big pile that doesn't care whether it fits onto the print bed. - if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score; - - return score; - }; - - // Create the arranger object - Arranger arranger(bin, min_obj_distance, pcfg, scfg); - - // Set the progress indicator for the arranger. - arranger.progressIndicator(progressind); - - // Arrange and return the items with their respective indices within the - // input sequence. - auto result = arranger.arrangeIndexed(shapes.begin(), shapes.end()); - - auto applyResult = [&shapemap](ArrangeResult::value_type& group, - Coord batch_offset) - { - for(auto& r : group) { - auto idx = r.first; // get the original item index - Item& item = r.second; // get the item itself - - // Get the model instance from the shapemap using the index - ModelInstance *inst_ptr = shapemap[idx].first; - - // Get the tranformation data from the item object and scale it - // appropriately - auto off = item.translation(); - Radians rot = item.rotation(); - Pointf foff(off.X*SCALING_FACTOR + batch_offset, - off.Y*SCALING_FACTOR); - - // write the tranformation data into the model instance - inst_ptr->rotation = rot; - inst_ptr->offset = foff; - } + // Arrange and return the items with their respective indices within the + // input sequence. + result = arrange(shapes.begin(), shapes.end()); + break; + } }; if(first_bin_only) { - applyResult(result.front(), 0); + applyResult(result.front(), 0, shapemap); } else { const auto STRIDE_PADDING = 1.2; Coord stride = static_cast(STRIDE_PADDING* - bin.width()*SCALING_FACTOR); + binbb.width()*SCALING_FACTOR); Coord batch_offset = 0; for(auto& group : result) { - applyResult(group, batch_offset); + applyResult(group, batch_offset, shapemap); // Only the first pack group can be placed onto the print bed. The // other objects which could not fit will be placed next to the diff --git a/xs/src/slic3r/AppController.cpp b/xs/src/slic3r/AppController.cpp index 1d4b7d545d..58858f5fc4 100644 --- a/xs/src/slic3r/AppController.cpp +++ b/xs/src/slic3r/AppController.cpp @@ -294,6 +294,8 @@ void AppController::arrange_model() supports_asynch()? std::launch::async : std::launch::deferred, [this]() { + using Coord = libnest2d::TCoord; + unsigned count = 0; for(auto obj : model_->objects) count += obj->instances.size(); @@ -311,14 +313,25 @@ void AppController::arrange_model() auto dist = print_ctl()->config().min_object_distance(); + // Create the arranger config + auto min_obj_distance = static_cast(dist/SCALING_FACTOR); - BoundingBoxf bb(print_ctl()->config().bed_shape.values); + auto& bedpoints = print_ctl()->config().bed_shape.values; + Polyline bed; bed.points.reserve(bedpoints.size()); + for(auto& v : bedpoints) + bed.append(Point::new_scale(v.x, v.y)); if(pind) pind->update(0, _(L("Arranging objects..."))); try { - arr::arrange(*model_, dist, &bb, false, [pind, count](unsigned rem){ - if(pind) pind->update(count - rem, _(L("Arranging objects..."))); + arr::arrange(*model_, + min_obj_distance, + bed, + arr::BOX, + false, // create many piles not just one pile + [pind, count](unsigned rem) { + if(pind) + pind->update(count - rem, _(L("Arranging objects..."))); }); } catch(std::exception& e) { std::cerr << e.what() << std::endl; From 751fe864e2aa0024f763ef2a51030b618c202748 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 2 Aug 2018 14:04:50 +0200 Subject: [PATCH 063/119] Bugfix: priming lines for MM print were shown in preview even when disabled --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index f8dd94fd4b..1c73a69b84 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -3383,7 +3383,7 @@ void GLCanvas3D::_camera_tranform() const ::glMatrixMode(GL_MODELVIEW); ::glLoadIdentity(); - ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); //pitch + ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch ::glRotatef(m_camera.phi, 0.0f, 0.0f, 1.0f); // yaw Pointf3 neg_target = m_camera.target.negative(); @@ -4090,7 +4090,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ ctxt.print = m_print; ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; - if (m_print->m_wipe_tower_priming) + if (m_print->m_wipe_tower_priming && m_print->config.single_extruder_multi_material_priming) ctxt.priming.emplace_back(*m_print->m_wipe_tower_priming.get()); if (m_print->m_wipe_tower_final_purge) ctxt.final.emplace_back(*m_print->m_wipe_tower_final_purge.get()); @@ -4823,7 +4823,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs) if (m_model == nullptr) return; - std::set done; //prevent moving instances twice + std::set done; // prevent moving instances twice bool object_moved = false; Pointf3 wipe_tower_origin(0.0, 0.0, 0.0); for (int volume_idx : volume_idxs) @@ -4832,7 +4832,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs) int obj_idx = volume->object_idx(); int instance_idx = volume->instance_idx(); - //prevent moving instances twice + // prevent moving instances twice char done_id[64]; ::sprintf(done_id, "%d_%d", obj_idx, instance_idx); if (done.find(done_id) != done.end()) From cc2486104211cee5dfefe7d691281e5732a6054d Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 2 Aug 2018 15:14:12 +0200 Subject: [PATCH 064/119] Added a threshold for purging on the wipe tower (before it goes into infill/sacrificial object) --- xs/src/libslic3r/Print.cpp | 14 +++++++++++--- xs/src/libslic3r/PrintConfig.cpp | 16 ++++++++++++++-- xs/src/libslic3r/PrintConfig.hpp | 2 ++ xs/src/slic3r/GUI/Preset.cpp | 9 +++++---- xs/src/slic3r/GUI/Tab.cpp | 1 + 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 8c91eb1925..1709703a55 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -205,6 +205,7 @@ bool Print::invalidate_state_by_config_options(const std::vectoradd("filament_cooling_initial_speed", coFloats); def->label = L("Speed of the first cooling move"); def->tooltip = L("Cooling moves are gradually accelerating beginning at this speed. "); - def->cli = "filament-cooling-initial-speed=i@"; + def->cli = "filament-cooling-initial-speed=f@"; def->sidetext = L("mm/s"); def->min = 0; def->default_value = new ConfigOptionFloats { 2.2f }; + def = this->add("filament_minimal_purge_on_wipe_tower", coFloats); + def->label = L("Minimal purge on wipe tower"); + def->tooltip = L("After a toolchange, certain amount of filament is used for purging. This " + "can end up on the wipe tower, infill or sacrificial object. If there was " + "enough infill etc. available, this could result in bad quality at the beginning " + "of purging. This is a minimum that must be wiped on the wipe tower before " + "Slic3r considers moving elsewhere. "); + def->cli = "filament-minimal-purge-on-wipe-tower=f@"; + def->sidetext = L("mm³"); + def->min = 0; + def->default_value = new ConfigOptionFloats { 5.f }; + def = this->add("filament_cooling_final_speed", coFloats); def->label = L("Speed of the last cooling move"); def->tooltip = L("Cooling moves are gradually accelerating towards this speed. "); - def->cli = "filament-cooling-final-speed=i@"; + def->cli = "filament-cooling-final-speed=f@"; def->sidetext = L("mm/s"); def->min = 0; def->default_value = new ConfigOptionFloats { 3.4f }; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 3848ba55bf..89b39e2f4b 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -532,6 +532,7 @@ public: ConfigOptionFloats filament_toolchange_delay; ConfigOptionInts filament_cooling_moves; ConfigOptionFloats filament_cooling_initial_speed; + ConfigOptionFloats filament_minimal_purge_on_wipe_tower; ConfigOptionFloats filament_cooling_final_speed; ConfigOptionStrings filament_ramming_parameters; ConfigOptionBool gcode_comments; @@ -594,6 +595,7 @@ protected: OPT_PTR(filament_toolchange_delay); OPT_PTR(filament_cooling_moves); OPT_PTR(filament_cooling_initial_speed); + OPT_PTR(filament_minimal_purge_on_wipe_tower); OPT_PTR(filament_cooling_final_speed); OPT_PTR(filament_ramming_parameters); OPT_PTR(gcode_comments); diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index de9b59a958..ebc49e8707 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -314,10 +314,11 @@ const std::vector& Preset::filament_options() static std::vector s_opts { "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", "extrusion_multiplier", "filament_density", "filament_cost", "filament_loading_speed", "filament_unloading_speed", "filament_toolchange_delay", - "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "temperature", - "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", - "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", - "start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition", "inherits" + "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", + "filament_minimal_purge_on_wipe_tower", "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", + "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", + "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition", + "inherits" }; return s_opts; } diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 70f3bf8bee..5c036c1da8 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -1296,6 +1296,7 @@ void TabFilament::build() optgroup->append_single_option_line("filament_cooling_moves"); optgroup->append_single_option_line("filament_cooling_initial_speed"); optgroup->append_single_option_line("filament_cooling_final_speed"); + optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); line = { _(L("Ramming")), "" }; line.widget = [this](wxWindow* parent){ From c8370b5408cd367026faf976149967cd589f00c8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 2 Aug 2018 17:51:11 +0200 Subject: [PATCH 065/119] New approach to big items with calculating the best alignment with other big items. --- .../libnest2d/libnest2d/geometry_traits.hpp | 4 +- .../libnest2d/libnest2d/placers/nfpplacer.hpp | 3 +- xs/src/libslic3r/ModelArrange.hpp | 72 +++++++++++++------ 3 files changed, 56 insertions(+), 23 deletions(-) diff --git a/xs/src/libnest2d/libnest2d/geometry_traits.hpp b/xs/src/libnest2d/libnest2d/geometry_traits.hpp index 00740f30cf..058c47cd43 100644 --- a/xs/src/libnest2d/libnest2d/geometry_traits.hpp +++ b/xs/src/libnest2d/libnest2d/geometry_traits.hpp @@ -314,8 +314,8 @@ inline RawPoint _Box::center() const BP2D_NOEXCEPT { using Coord = TCoord; RawPoint ret = { - static_cast( (getX(minc) + getX(maxc))/2.0 ), - static_cast( (getY(minc) + getY(maxc))/2.0 ) + static_cast( std::round((getX(minc) + getX(maxc))/2.0) ), + static_cast( std::round((getY(minc) + getY(maxc))/2.0) ) }; return ret; diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp index 0c5776400a..5d09a61fcb 100644 --- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp +++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp @@ -590,7 +590,8 @@ public: [this, &merged_pile]( Nfp::Shapes& /*pile*/, const Item& item, - double occupied_area, double norm, + double occupied_area, + double norm, double /*penality*/) { merged_pile.emplace_back(item.transformedShape()); diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 73dc83c57d..5f1717f718 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -95,12 +95,16 @@ void toSVG(SVG& svg, const Model& model) { std::tuple objfunc(const PointImpl& bincenter, + double bin_area, ShapeLike::Shapes& pile, // The currently arranged pile + double pile_area, const Item &item, - double norm // A norming factor for physical dimensions + double norm, // A norming factor for physical dimensions + std::vector& areacache ) { using pl = PointLike; + using sl = ShapeLike; static const double BIG_ITEM_TRESHOLD = 0.2; static const double ROUNDNESS_RATIO = 0.5; @@ -109,10 +113,14 @@ objfunc(const PointImpl& bincenter, // We will treat big items (compared to the print bed) differently NfpPlacer::Pile bigs; bigs.reserve(pile.size()); + + int idx = 0; + if(pile.size() < areacache.size()) areacache.clear(); for(auto& p : pile) { - auto pbb = ShapeLike::boundingBox(p); - auto na = std::sqrt(pbb.width()*pbb.height())/norm; - if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p); + if(idx == areacache.size()) areacache.emplace_back(sl::area(p)); + if(std::sqrt(areacache[idx])/norm > BIG_ITEM_TRESHOLD) + bigs.emplace_back(p); + idx++; } // Candidate item bounding box @@ -166,9 +174,28 @@ objfunc(const PointImpl& bincenter, // Density is the pack density: how big is the arranged pile auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; - // The score is a weighted sum of the distance from pile center - // and the pile size - score = ROUNDNESS_RATIO * dist + DENSITY_RATIO * density; + auto alignment_score = std::numeric_limits::max(); + + auto& trsh = item.transformedShape(); + + idx = 0; + for(auto& p : pile) { + + auto parea = areacache[idx]; + if(std::sqrt(parea)/norm > BIG_ITEM_TRESHOLD) { + auto chull = sl::convexHull(sl::Shapes{p, trsh}); + auto carea = sl::area(chull); + + auto ascore = carea - (item.area() + parea); + ascore = std::sqrt(ascore) / norm; + + if(ascore < alignment_score) alignment_score = ascore; + } + idx++; + } + + auto C = 0.33; + score = C * dist + C * density + C * alignment_score; } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { // If there are no big items, only small, we should consider the @@ -203,7 +230,7 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 0.35f; + pcfg.accuracy = 0.4f; } template @@ -221,18 +248,20 @@ protected: Packer pck_; PConfig pconf_; // Placement configuration - + double bin_area_; + std::vector areacache_; public: _ArrBase(const TBin& bin, Distance dist, std::function progressind): - pck_(bin, dist) + pck_(bin, dist), bin_area_(ShapeLike::area(bin)) { fillConfig(pconf_); pck_.progressIndicator(progressind); } template inline IndexedPackGroup operator()(Args&&...args) { + areacache_.clear(); return pck_.arrangeIndexed(std::forward(args)...); } }; @@ -245,14 +274,15 @@ public: std::function progressind): _ArrBase(bin, dist, progressind) { - pconf_.object_function = [bin] ( + pconf_.object_function = [this, bin] ( Pile& pile, const Item &item, - double /*occupied_area*/, + double pile_area, double norm, - double penality) { + double /*penality*/) { - auto result = objfunc(bin.center(), pile, item, norm); + auto result = objfunc(bin.center(), bin_area_, pile, + pile_area, item, norm, areacache_); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); @@ -275,15 +305,16 @@ public: std::function progressind): _ArrBase(bin, dist, progressind) { - pconf_.object_function = [&bin] ( + pconf_.object_function = [this, &bin] ( Pile& pile, const Item &item, - double /*area*/, + double pile_area, double norm, double /*penality*/) { auto binbb = ShapeLike::boundingBox(bin); - auto result = objfunc(binbb.center(), pile, item, norm); + auto result = objfunc(binbb.center(), bin_area_, pile, + pile_area, item, norm, areacache_); double score = std::get<0>(result); pile.emplace_back(item.transformedShape()); @@ -309,14 +340,15 @@ public: AutoArranger(Distance dist, std::function progressind): _ArrBase(Box(0, 0), dist, progressind) { - this->pconf_.object_function = [] ( + this->pconf_.object_function = [this] ( Pile& pile, const Item &item, - double /*area*/, + double pile_area, double norm, double /*penality*/) { - auto result = objfunc({0, 0}, pile, item, norm); + auto result = objfunc({0, 0}, 0, pile, pile_area, + item, norm, areacache_); return std::get<0>(result); }; From 9172a69e27faf75c43013464c421a8cdf1b0760c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 2 Aug 2018 19:17:27 +0200 Subject: [PATCH 066/119] Nlopt build fix --- .../cmake_modules/DownloadNLopt.cmake | 3 ++- xs/src/libslic3r/ModelArrange.hpp | 22 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/xs/src/libnest2d/cmake_modules/DownloadNLopt.cmake b/xs/src/libnest2d/cmake_modules/DownloadNLopt.cmake index 814213b38a..0f5392596e 100644 --- a/xs/src/libnest2d/cmake_modules/DownloadNLopt.cmake +++ b/xs/src/libnest2d/cmake_modules/DownloadNLopt.cmake @@ -27,5 +27,6 @@ set(NLOPT_LINK_PYTHON OFF CACHE BOOL "" FORCE) add_subdirectory(${nlopt_SOURCE_DIR} ${nlopt_BINARY_DIR}) set(NLopt_LIBS nlopt) -set(NLopt_INCLUDE_DIR ${nlopt_BINARY_DIR}) +set(NLopt_INCLUDE_DIR ${nlopt_BINARY_DIR} + ${nlopt_BINARY_DIR}/src/api) set(SHARED_LIBS_STATE ${SHARED_STATE}) \ No newline at end of file diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 5f1717f718..baf31af4e0 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -95,9 +95,9 @@ void toSVG(SVG& svg, const Model& model) { std::tuple objfunc(const PointImpl& bincenter, - double bin_area, + double /*bin_area*/, ShapeLike::Shapes& pile, // The currently arranged pile - double pile_area, + double /*pile_area*/, const Item &item, double norm, // A norming factor for physical dimensions std::vector& areacache @@ -114,11 +114,14 @@ objfunc(const PointImpl& bincenter, NfpPlacer::Pile bigs; bigs.reserve(pile.size()); - int idx = 0; if(pile.size() < areacache.size()) areacache.clear(); + + auto normarea = [norm](double area) { return std::sqrt(area)/norm; }; + + int idx = 0; for(auto& p : pile) { if(idx == areacache.size()) areacache.emplace_back(sl::area(p)); - if(std::sqrt(areacache[idx])/norm > BIG_ITEM_TRESHOLD) + if( normarea(areacache[idx]) > BIG_ITEM_TRESHOLD) bigs.emplace_back(p); idx++; } @@ -137,12 +140,13 @@ objfunc(const PointImpl& bincenter, // The size indicator of the candidate item. This is not the area, // but almost... - auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm; // Will hold the resulting score double score = 0; - if(itemnormarea > BIG_ITEM_TRESHOLD) { + double item_normarea = normarea(item.area()); + + if(item_normarea > BIG_ITEM_TRESHOLD) { // This branch is for the bigger items.. // Here we will use the closest point of the item bounding box to // the already arranged pile. So not the bb center nor the a choosen @@ -182,7 +186,7 @@ objfunc(const PointImpl& bincenter, for(auto& p : pile) { auto parea = areacache[idx]; - if(std::sqrt(parea)/norm > BIG_ITEM_TRESHOLD) { + if(normarea(areacache[idx]) > BIG_ITEM_TRESHOLD) { auto chull = sl::convexHull(sl::Shapes{p, trsh}); auto carea = sl::area(chull); @@ -197,7 +201,7 @@ objfunc(const PointImpl& bincenter, auto C = 0.33; score = C * dist + C * density + C * alignment_score; - } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) { + } else if( item_normarea < BIG_ITEM_TRESHOLD && bigs.empty()) { // If there are no big items, only small, we should consider the // density here as well to not get silly results auto bindist = pl::distance(ibb.center(), bincenter) / norm; @@ -230,7 +234,7 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 0.4f; + pcfg.accuracy = 1.0f; } template From 8e516bc3e431a41ce4cfc41614db9e3c110e2e5a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 2 Aug 2018 19:25:19 +0200 Subject: [PATCH 067/119] reduce accuracy to acceptable performance --- xs/src/libslic3r/ModelArrange.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index baf31af4e0..b1e331eeb7 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -234,7 +234,7 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 1.0f; + pcfg.accuracy = 0.5f; } template From e7e212cb52ce916a4f18ff295760ecc0d9403f2a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 3 Aug 2018 12:37:27 +0200 Subject: [PATCH 068/119] Added a spatial index to speed up alignment score calculation. --- xs/src/libslic3r/ModelArrange.hpp | 94 ++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index b1e331eeb7..f2d399ac66 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -8,6 +8,8 @@ #include #include +#include + namespace Slic3r { namespace arr { @@ -93,6 +95,11 @@ void toSVG(SVG& svg, const Model& model) { } } +namespace bgi = boost::geometry::index; + +using SpatElement = std::pair; +using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; + std::tuple objfunc(const PointImpl& bincenter, double /*bin_area*/, @@ -100,7 +107,9 @@ objfunc(const PointImpl& bincenter, double /*pile_area*/, const Item &item, double norm, // A norming factor for physical dimensions - std::vector& areacache + std::vector& areacache, // pile item areas will be cached + // a spatial index to quickly get neighbors of the candidate item + SpatIndex& spatindex ) { using pl = PointLike; @@ -111,18 +120,23 @@ objfunc(const PointImpl& bincenter, static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; // We will treat big items (compared to the print bed) differently - NfpPlacer::Pile bigs; - bigs.reserve(pile.size()); - - if(pile.size() < areacache.size()) areacache.clear(); - auto normarea = [norm](double area) { return std::sqrt(area)/norm; }; + // If a new bin has been created: + if(pile.size() < areacache.size()) { + areacache.clear(); + spatindex.clear(); + } + + // We must fill the caches: int idx = 0; for(auto& p : pile) { - if(idx == areacache.size()) areacache.emplace_back(sl::area(p)); - if( normarea(areacache[idx]) > BIG_ITEM_TRESHOLD) - bigs.emplace_back(p); + if(idx == areacache.size()) { + areacache.emplace_back(sl::area(p)); + if(normarea(areacache[idx]) > BIG_ITEM_TRESHOLD) + spatindex.insert({sl::boundingBox(p), idx}); + } + idx++; } @@ -136,25 +150,26 @@ objfunc(const PointImpl& bincenter, // The bounding box of the big items (they will accumulate in the center // of the pile - auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs); + Box bigbb; + if(spatindex.empty()) bigbb = fullbb; + else { + auto boostbb = spatindex.bounds(); + boost::geometry::convert(boostbb, bigbb); + } // The size indicator of the candidate item. This is not the area, // but almost... + double item_normarea = normarea(item.area()); // Will hold the resulting score double score = 0; - double item_normarea = normarea(item.area()); - if(item_normarea > BIG_ITEM_TRESHOLD) { // This branch is for the bigger items.. // Here we will use the closest point of the item bounding box to // the already arranged pile. So not the bb center nor the a choosen // corner but whichever is the closest to the center. This will - // prevent unwanted strange arrangements. - - // Now the distance of the gravity center will be calculated to the - // five anchor points and the smallest will be chosen. + // prevent some unwanted strange arrangements. auto minc = ibb.minCorner(); // bottom left corner auto maxc = ibb.maxCorner(); // top right corner @@ -163,7 +178,7 @@ objfunc(const PointImpl& bincenter, auto top_left = PointImpl{getX(minc), getY(maxc)}; auto bottom_right = PointImpl{getX(maxc), getY(minc)}; - // Now the distnce of the gravity center will be calculated to the + // Now the distance of the gravity center will be calculated to the // five anchor points and the smallest will be chosen. std::array dists; auto cc = fullbb.center(); // The gravity center @@ -173,35 +188,45 @@ objfunc(const PointImpl& bincenter, dists[3] = pl::distance(top_left, cc); dists[4] = pl::distance(bottom_right, cc); + // The smalles distance from the arranged pile center: auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; // Density is the pack density: how big is the arranged pile auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm; + // Prepare a variable for the alignment score. + // This will indicate: how well is the candidate item aligned with + // its neighbors. We will check the aligment with all neighbors and + // return the score for the best alignment. So it is enough for the + // candidate to be aligned with only one item. auto alignment_score = std::numeric_limits::max(); auto& trsh = item.transformedShape(); - idx = 0; - for(auto& p : pile) { + auto querybb = item.boundingBox(); + // Query the spatial index for the neigbours + std::vector result; + spatindex.query(bgi::intersects(querybb), std::back_inserter(result)); + + for(auto& e : result) { // now get the score for the best alignment + auto idx = e.second; + auto& p = pile[idx]; auto parea = areacache[idx]; - if(normarea(areacache[idx]) > BIG_ITEM_TRESHOLD) { - auto chull = sl::convexHull(sl::Shapes{p, trsh}); - auto carea = sl::area(chull); + auto bb = sl::boundingBox(sl::Shapes{p, trsh}); + auto bbarea = bb.area(); + auto ascore = 1.0 - (item.area() + parea)/bbarea; - auto ascore = carea - (item.area() + parea); - ascore = std::sqrt(ascore) / norm; - - if(ascore < alignment_score) alignment_score = ascore; - } - idx++; + if(ascore < alignment_score) alignment_score = ascore; } + // The final mix of the score is the balance between the distance + // from the full pile center, the pack density and the + // alignment with the neigbours auto C = 0.33; score = C * dist + C * density + C * alignment_score; - } else if( item_normarea < BIG_ITEM_TRESHOLD && bigs.empty()) { + } else if( item_normarea < BIG_ITEM_TRESHOLD && spatindex.empty()) { // If there are no big items, only small, we should consider the // density here as well to not get silly results auto bindist = pl::distance(ibb.center(), bincenter) / norm; @@ -234,7 +259,7 @@ void fillConfig(PConf& pcfg) { // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 0.5f; + pcfg.accuracy = 0.6f; } template @@ -254,6 +279,7 @@ protected: PConfig pconf_; // Placement configuration double bin_area_; std::vector areacache_; + SpatIndex rtree_; public: _ArrBase(const TBin& bin, Distance dist, @@ -286,7 +312,7 @@ public: double /*penality*/) { auto result = objfunc(bin.center(), bin_area_, pile, - pile_area, item, norm, areacache_); + pile_area, item, norm, areacache_, rtree_); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); @@ -318,7 +344,7 @@ public: auto binbb = ShapeLike::boundingBox(bin); auto result = objfunc(binbb.center(), bin_area_, pile, - pile_area, item, norm, areacache_); + pile_area, item, norm, areacache_, rtree_); double score = std::get<0>(result); pile.emplace_back(item.transformedShape()); @@ -352,7 +378,7 @@ public: double /*penality*/) { auto result = objfunc({0, 0}, 0, pile, pile_area, - item, norm, areacache_); + item, norm, areacache_, rtree_); return std::get<0>(result); }; @@ -530,7 +556,7 @@ bool arrange(Model &model, coordf_t min_obj_distance, auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); P irrbed = ShapeLike::create(std::move(ctour)); - std::cout << ShapeLike::toString(irrbed) << std::endl; +// std::cout << ShapeLike::toString(irrbed) << std::endl; AutoArranger

arrange(irrbed, min_obj_distance, progressind); From 2fe26bfac7c5cb1d0cf0e9aa0cf27de6be852dc8 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 3 Aug 2018 15:36:47 +0200 Subject: [PATCH 069/119] Changed color of preliminary wipe tower block --- xs/src/slic3r/GUI/3DScene.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index ed66dad62e..33b3768ed3 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -658,8 +658,8 @@ int GLVolumeCollection::load_wipe_tower_preview( // In case we don't know precise dimensions of the wipe tower yet, we'll draw the box with different color with one side jagged: if (size_unknown) { - color[0] = 1.f; - color[1] = 0.f; + color[0] = 0.9f; + color[1] = 0.6f; depth = std::max(depth, 10.f); // Too narrow tower would interfere with the teeth. The estimate is not precise anyway. float min_width = 30.f; From 0454adc19415499635b5d268c5d928f5bbe3b71e Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 3 Aug 2018 16:26:28 +0200 Subject: [PATCH 070/119] Added support for the upstream Marlin interpretation of the M204 code. Fix of https://github.com/prusa3d/Slic3r/issues/1089 M204 S.. T..: T is interpreted by the firmware and Slic3r time estimator the old way (as acceleration when retracting) only if an S code is found at the same line. This allows PrusaResearch to interpret the legacy G-codes generated by our older Slic3r with older Slic3r profiles. M204 P.. R.. T..: T is ignored, P is interpreted as acceleration when extruding, R is interpreted as acceleration when retracting. This will be the format the Slic3r 1.41.0 will produce from the Machine Limits page. In the future both MK3 firmware and Slic3r will likely be extended to support the separate travel acceleration. This change is in sync with the Prusa3D firmware: https://github.com/prusa3d/Prusa-Firmware/commit/dd4c4b39b4359d61a3329c54bea58df119a731c6 Slic3r will now export M204 P[machine_max_acceleration_extruding] R[machine_max_acceleration_retracting] T[machine_max_acceleration_extruding] before the custom start G-code, which will be correctly interpreted by both the new Prusa3D firmware and the Slic3r's time estimator. To support our legacy MK2 firmware before we merge the commit above, we may just insert the following line into the custom start G-code section to override the block inserted by Slic3r automatically before the custom start G-code: M204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] --- xs/src/libslic3r/GCode.cpp | 5 +++-- xs/src/libslic3r/GCodeTimeEstimator.cpp | 22 ++++++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 308b1ea046..261c8e59d7 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -1012,9 +1012,10 @@ void GCode::print_machine_envelope(FILE *file, Print &print) int(print.config.machine_max_feedrate_y.values.front() + 0.5), int(print.config.machine_max_feedrate_z.values.front() + 0.5), int(print.config.machine_max_feedrate_e.values.front() + 0.5)); - fprintf(file, "M204 S%d T%d ; sets acceleration (S) and retract acceleration (T), mm/sec^2\n", + fprintf(file, "M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n", int(print.config.machine_max_acceleration_extruding.values.front() + 0.5), - int(print.config.machine_max_acceleration_retracting.values.front() + 0.5)); + int(print.config.machine_max_acceleration_retracting.values.front() + 0.5), + int(print.config.machine_max_acceleration_extruding.values.front() + 0.5)); fprintf(file, "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n", print.config.machine_max_jerk_x.values.front(), print.config.machine_max_jerk_y.values.front(), diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp index 5543b5cc99..749aac88b3 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.cpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp @@ -1164,11 +1164,25 @@ namespace Slic3r { { PROFILE_FUNC(); float value; - if (line.has_value('S', value)) + if (line.has_value('S', value)) { + // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware, + // and it is also generated by Slic3r to control acceleration per extrusion type + // (there is a separate acceleration settings in Slicer for perimeter, first layer etc). set_acceleration(value); - - if (line.has_value('T', value)) - set_retract_acceleration(value); + if (line.has_value('T', value)) + set_retract_acceleration(value); + } else { + // New acceleration format, compatible with the upstream Marlin. + if (line.has_value('P', value)) + set_acceleration(value); + if (line.has_value('R', value)) + set_retract_acceleration(value); + if (line.has_value('T', value)) { + // Interpret the T value as the travel acceleration in the new Marlin format. + //FIXME Prusa3D firmware currently does not support travel acceleration value independent from the extruding acceleration value. + // set_travel_acceleration(value); + } + } } void GCodeTimeEstimator::_processM205(const GCodeReader::GCodeLine& line) From 71b1e09af990b889488ab8797cbb9e5dcda5df5a Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sat, 4 Aug 2018 17:38:25 +0200 Subject: [PATCH 071/119] T1 and M702 C are now evaluated by the time estimator to add the new "filament_load_time" and "filament_unload_time" values to match the MK3 MMU2 behavior. Emitting of the remaining times into the output G-code was made optional through a new "remaining_times" configuration value, so the firmware flavors and versions, which do not know the M73 code, will not complain. Configuration changes: The wipe tower default position was shifted inwards after the wipe tower coordinate reference point was changed from the center to the left front corner. Added the "filament_load_time" and "filament_unload_time" values to the MK3 MMU filament profiles. Enabled "remaining_times" for the MK2.5, MK3 and MK3MMU2 printers. --- resources/profiles/PrusaResearch.idx | 1 + resources/profiles/PrusaResearch.ini | 15 ++++-- xs/src/libslic3r/GCode.cpp | 8 ++- xs/src/libslic3r/GCodeReader.cpp | 22 +++++++++ xs/src/libslic3r/GCodeReader.hpp | 1 + xs/src/libslic3r/GCodeTimeEstimator.cpp | 66 +++++++++++++++++++++++-- xs/src/libslic3r/GCodeTimeEstimator.hpp | 15 ++++++ xs/src/libslic3r/PrintConfig.cpp | 10 +++- xs/src/libslic3r/PrintConfig.hpp | 2 + xs/src/slic3r/GUI/Preset.cpp | 2 +- xs/src/slic3r/GUI/Tab.cpp | 1 + 11 files changed, 133 insertions(+), 10 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index b122a13c5c..21c2e518dc 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,5 @@ min_slic3r_version = 1.41.0-alpha +0.2.0-alpha8 Added filament_load/unload_time for the PLA/ABS MMU2 filament presets. 0.2.0-alpha7 Fixed the *MK3* references 0.2.0-alpha6 0.2.0-alpha5 Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2 diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 27ba179ad6..496425e0dc 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.2.0-alpha7 +config_version = 0.2.0-alpha8 # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -133,8 +133,8 @@ wipe_tower = 1 wipe_tower_bridging = 10 wipe_tower_rotation_angle = 0 wipe_tower_width = 60 -wipe_tower_x = 200 -wipe_tower_y = 155 +wipe_tower_x = 180 +wipe_tower_y = 135 xy_size_compensation = 0 [print:*MK3*] @@ -844,6 +844,8 @@ filament_cooling_initial_speed = 10 filament_cooling_moves = 5 filament_loading_speed = 14 filament_ramming_parameters = "120 110 5.32258 5.45161 5.67742 6 6.48387 7.12903 7.90323 8.70968 9.3871 9.83871 10.0968 10.2258| 0.05 5.30967 0.45 5.50967 0.95 6.1871 1.45 7.39677 1.95 9.05484 2.45 10 2.95 10.3098 3.45 13.0839 3.95 7.6 4.45 7.6 4.95 7.6"; +filament_load_time = 12 +filament_unload_time = 11 [filament:Generic ABS MMU2] inherits = *ABS MMU2* @@ -887,6 +889,8 @@ filament_cooling_initial_speed = 10 filament_cooling_moves = 7 filament_loading_speed = 14 filament_ramming_parameters = "120 110 4.03226 4.12903 4.25806 4.41935 4.58065 4.80645 5.35484 6.29032 7.58065 9.09677 10.5806 11.8387 12.6452 12.9677| 0.05 4.01935 0.45 4.15483 0.95 4.50968 1.45 4.94516 1.95 6.79677 2.45 9.87102 2.95 12.4388 3.45 13.0839 3.95 7.6 4.45 7.6 4.95 7.6" +filament_load_time = 12 +filament_unload_time = 11 [filament:Generic PLA MMU2] inherits = *PLA MMU2* @@ -980,6 +984,7 @@ extruder_colour = #FFFF00 extruder_offset = 0x0 gcode_flavor = marlin silent_mode = 0 +remaining_times = 0 machine_max_acceleration_e = 10000 machine_max_acceleration_extruding = 2000 machine_max_acceleration_retracting = 1500 @@ -1124,16 +1129,19 @@ default_print_profile = 0.20mm NORMAL 0.6 nozzle [printer:Original Prusa i3 MK2.5] inherits = Original Prusa i3 MK2 printer_model = MK2.5 +remaining_times = 1 start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 0.25 nozzle] inherits = Original Prusa i3 MK2 0.25 nozzle printer_model = MK2.5 +remaining_times = 1 start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 0.6 nozzle] inherits = Original Prusa i3 MK2 0.6 nozzle printer_model = MK2.5 +remaining_times = 1 start_gcode = M115 U3.3.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 # XXXXXXXXXXXXXXXXX @@ -1160,6 +1168,7 @@ machine_max_jerk_z = 0.4,0.4 machine_min_extruding_rate = 0,0 machine_min_travel_rate = 0,0 silent_mode = 1 +remaining_times = 1 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 max_print_height = 210 diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 1c1eeee6f1..7edd550770 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -440,10 +440,9 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ } fclose(file); - if (print->config.gcode_flavor.value == gcfMarlin) + if (print->config.remaining_times.value) { m_normal_time_estimator.post_process_remaining_times(path_tmp, 60.0f); - if (m_silent_time_estimator_enabled) m_silent_time_estimator.post_process_remaining_times(path_tmp, 60.0f); } @@ -525,8 +524,13 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, print.config.machine_max_jerk_y.values[1]); m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, print.config.machine_max_jerk_z.values[1]); m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, print.config.machine_max_jerk_e.values[1]); + m_silent_time_estimator.set_filament_load_times(print.config.filament_load_time.values); + m_silent_time_estimator.set_filament_unload_times(print.config.filament_unload_time.values); } } + // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. + m_normal_time_estimator.set_filament_load_times(print.config.filament_load_time.values); + m_normal_time_estimator.set_filament_unload_times(print.config.filament_unload_time.values); // resets analyzer m_analyzer.reset(); diff --git a/xs/src/libslic3r/GCodeReader.cpp b/xs/src/libslic3r/GCodeReader.cpp index 965b7ef8ec..79b6ed9707 100644 --- a/xs/src/libslic3r/GCodeReader.cpp +++ b/xs/src/libslic3r/GCodeReader.cpp @@ -114,6 +114,28 @@ void GCodeReader::parse_file(const std::string &file, callback_t callback) this->parse_line(line, callback); } +bool GCodeReader::GCodeLine::has(char axis) const +{ + const char *c = m_raw.c_str(); + // Skip the whitespaces. + c = skip_whitespaces(c); + // Skip the command. + c = skip_word(c); + // Up to the end of line or comment. + while (! is_end_of_gcode_line(*c)) { + // Skip whitespaces. + c = skip_whitespaces(c); + if (is_end_of_gcode_line(*c)) + break; + // Check the name of the axis. + if (*c == axis) + return true; + // Skip the rest of the word. + c = skip_word(c); + } + return false; +} + bool GCodeReader::GCodeLine::has_value(char axis, float &value) const { const char *c = m_raw.c_str(); diff --git a/xs/src/libslic3r/GCodeReader.hpp b/xs/src/libslic3r/GCodeReader.hpp index 102cbd27ad..84ed89a7ce 100644 --- a/xs/src/libslic3r/GCodeReader.hpp +++ b/xs/src/libslic3r/GCodeReader.hpp @@ -27,6 +27,7 @@ public: bool has(Axis axis) const { return (m_mask & (1 << int(axis))) != 0; } float value(Axis axis) const { return m_axis[axis]; } + bool has(char axis) const; bool has_value(char axis, float &value) const; float new_Z(const GCodeReader &reader) const { return this->has(Z) ? this->z() : reader.z(); } float new_E(const GCodeReader &reader) const { return this->has(E) ? this->e() : reader.e(); } diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp index 877695f39c..b51692fc6e 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.cpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp @@ -469,6 +469,40 @@ namespace Slic3r { return _state.minimum_travel_feedrate; } + void GCodeTimeEstimator::set_filament_load_times(const std::vector &filament_load_times) + { + _state.filament_load_times.clear(); + for (double t : filament_load_times) + _state.filament_load_times.push_back(t); + } + + void GCodeTimeEstimator::set_filament_unload_times(const std::vector &filament_unload_times) + { + _state.filament_unload_times.clear(); + for (double t : filament_unload_times) + _state.filament_unload_times.push_back(t); + } + + float GCodeTimeEstimator::get_filament_load_time(unsigned int id_extruder) + { + return + (_state.filament_load_times.empty() || id_extruder == _state.extruder_id_unloaded) ? + 0 : + (_state.filament_load_times.size() <= id_extruder) ? + _state.filament_load_times.front() : + _state.filament_load_times[id_extruder]; + } + + float GCodeTimeEstimator::get_filament_unload_time(unsigned int id_extruder) + { + return + (_state.filament_unload_times.empty() || id_extruder == _state.extruder_id_unloaded) ? + 0 : + (_state.filament_unload_times.size() <= id_extruder) ? + _state.filament_unload_times.front() : + _state.filament_unload_times[id_extruder]; + } + void GCodeTimeEstimator::set_extrude_factor_override_percentage(float percentage) { _state.extrude_factor_override_percentage = percentage; @@ -547,7 +581,9 @@ namespace Slic3r { void GCodeTimeEstimator::reset_extruder_id() { - _state.extruder_id = 0; + // Set the initial extruder ID to unknown. For the multi-material setup it means + // that all the filaments are parked in the MMU and no filament is loaded yet. + _state.extruder_id = _state.extruder_id_unloaded; } void GCodeTimeEstimator::add_additional_time(float timeSec) @@ -590,6 +626,9 @@ namespace Slic3r { set_axis_max_acceleration(axis, DEFAULT_AXIS_MAX_ACCELERATION[a]); set_axis_max_jerk(axis, DEFAULT_AXIS_MAX_JERK[a]); } + + _state.filament_load_times.clear(); + _state.filament_unload_times.clear(); } void GCodeTimeEstimator::reset() @@ -794,6 +833,11 @@ namespace Slic3r { _processM566(line); break; } + case 702: // MK3 MMU2: Process the final filament unload. + { + _processM702(line); + break; + } } break; @@ -1258,6 +1302,19 @@ namespace Slic3r { set_axis_max_jerk(E, line.e() * MMMIN_TO_MMSEC); } + void GCodeTimeEstimator::_processM702(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + if (line.has('C')) { + // MK3 MMU2 specific M code: + // M702 C is expected to be sent by the custom end G-code when finalizing a print. + // The MK3 unit shall unload and park the active filament into the MMU2 unit. + add_additional_time(get_filament_unload_time(get_extruder_id())); + reset_extruder_id(); + _simulate_st_synchronize(); + } + } + void GCodeTimeEstimator::_processT(const GCodeReader::GCodeLine& line) { std::string cmd = line.cmd(); @@ -1266,9 +1323,12 @@ namespace Slic3r { unsigned int id = (unsigned int)::strtol(cmd.substr(1).c_str(), nullptr, 10); if (get_extruder_id() != id) { + // Specific to the MK3 MMU2: The initial extruder ID is set to -1 indicating + // that the filament is parked in the MMU2 unit and there is nothing to be unloaded yet. + add_additional_time(get_filament_unload_time(get_extruder_id())); set_extruder_id(id); - - // ADD PROCESSING HERE + add_additional_time(get_filament_load_time(get_extruder_id())); + _simulate_st_synchronize(); } } } diff --git a/xs/src/libslic3r/GCodeTimeEstimator.hpp b/xs/src/libslic3r/GCodeTimeEstimator.hpp index f6194a170f..1fa74e304c 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.hpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.hpp @@ -79,7 +79,14 @@ namespace Slic3r { float minimum_feedrate; // mm/s float minimum_travel_feedrate; // mm/s float extrude_factor_override_percentage; + // Additional load / unload times for a filament exchange sequence. + std::vector filament_load_times; + std::vector filament_unload_times; unsigned int g1_line_id; + // extruder_id is currently used to correctly calculate filament load / unload times + // into the total print time. This is currently only really used by the MK3 MMU2: + // Extruder id (-1) means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit. + static const unsigned int extruder_id_unloaded = (unsigned int)-1; unsigned int extruder_id; }; @@ -282,6 +289,11 @@ namespace Slic3r { void set_minimum_travel_feedrate(float feedrate_mm_sec); float get_minimum_travel_feedrate() const; + void set_filament_load_times(const std::vector &filament_load_times); + void set_filament_unload_times(const std::vector &filament_unload_times); + float get_filament_load_time(unsigned int id_extruder); + float get_filament_unload_time(unsigned int id_extruder); + void set_extrude_factor_override_percentage(float percentage); float get_extrude_factor_override_percentage() const; @@ -388,6 +400,9 @@ namespace Slic3r { // Set allowable instantaneous speed change void _processM566(const GCodeReader::GCodeLine& line); + // Unload the current filament into the MK3 MMU2 unit at the end of print. + void _processM702(const GCodeReader::GCodeLine& line); + // Processes T line (Select Tool) void _processT(const GCodeReader::GCodeLine& line); diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 1035951eb0..fe1a757395 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -920,8 +920,16 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloat(0.3); + def = this->add("remaining_times", coBool); + def->label = L("Supports remaining times"); + def->tooltip = L("Emit M73 P[percent printed] R[remaining time in seconds] at 1 minute" + " intervals into the G-code to let the firmware show accurate remaining time." + " As of now only the Prusa i3 MK3 firmware recognizes M73." + " Also the i3 MK3 firmware supports M73 Qxx Sxx for the silent mode."); + def->default_value = new ConfigOptionBool(false); + def = this->add("silent_mode", coBool); - def->label = L("Support silent mode"); + def->label = L("Supports silent mode"); def->tooltip = L("Set silent mode for the G-code flavor"); def->default_value = new ConfigOptionBool(true); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 602f1f0aa4..b18603d877 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -566,6 +566,7 @@ public: ConfigOptionFloat cooling_tube_retraction; ConfigOptionFloat cooling_tube_length; ConfigOptionFloat parking_pos_retraction; + ConfigOptionBool remaining_times; ConfigOptionBool silent_mode; ConfigOptionFloat extra_loading_move; @@ -631,6 +632,7 @@ protected: OPT_PTR(cooling_tube_retraction); OPT_PTR(cooling_tube_length); OPT_PTR(parking_pos_retraction); + OPT_PTR(remaining_times); OPT_PTR(silent_mode); OPT_PTR(extra_loading_move); } diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 98ad64209f..9f51f7b978 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -334,7 +334,7 @@ const std::vector& Preset::printer_options() "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction", "cooling_tube_length", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits", - "silent_mode", "machine_max_acceleration_extruding", "machine_max_acceleration_retracting", + "remaining_times", "silent_mode", "machine_max_acceleration_extruding", "machine_max_acceleration_retracting", "machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e", "machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e", "machine_min_extruding_rate", "machine_min_travel_rate", diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index e7c3993cad..7c4322c5a0 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -1610,6 +1610,7 @@ void TabPrinter::build() optgroup = page->new_optgroup(_(L("Firmware"))); optgroup->append_single_option_line("gcode_flavor"); optgroup->append_single_option_line("silent_mode"); + optgroup->append_single_option_line("remaining_times"); optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value){ wxTheApp->CallAfter([this, opt_key, value](){ From c13cd284e4d9db9913214d5d55919441408dd35c Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 5 Aug 2018 22:52:38 +0200 Subject: [PATCH 072/119] Fix of a regression bug: Update the print bed texture when switching between printer profiles. --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 14 +++++++++----- xs/src/slic3r/GUI/GLCanvas3D.hpp | 3 ++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index a631c13bb0..f45e8f61ce 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -305,10 +305,14 @@ const Pointfs& GLCanvas3D::Bed::get_shape() const return m_shape; } -void GLCanvas3D::Bed::set_shape(const Pointfs& shape) +bool GLCanvas3D::Bed::set_shape(const Pointfs& shape) { + EType new_type = _detect_type(); + if (m_shape == shape && m_type == new_type) + // No change, no need to update the UI. + return false; m_shape = shape; - m_type = _detect_type(); + m_type = new_type; _calc_bounding_box(); @@ -324,6 +328,8 @@ void GLCanvas3D::Bed::set_shape(const Pointfs& shape) _calc_gridlines(poly, bed_bbox); m_polygon = offset_ex(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0].contour; + // Let the calee to update the UI. + return true; } const BoundingBoxf3& GLCanvas3D::Bed::get_bounding_box() const @@ -1941,9 +1947,7 @@ void GLCanvas3D::set_model(Model* model) void GLCanvas3D::set_bed_shape(const Pointfs& shape) { - bool new_shape = (shape != m_bed.get_shape()); - if (new_shape) - m_bed.set_shape(shape); + bool new_shape = m_bed.set_shape(shape); // Set the origin and size for painting of the coordinate system axes. m_axes.origin = Pointf3(0.0, 0.0, (coordf_t)GROUND_Z); diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index a874862613..ae20882fc6 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -162,7 +162,8 @@ public: bool is_custom() const; const Pointfs& get_shape() const; - void set_shape(const Pointfs& shape); + // Return true if the bed shape changed, so the calee will update the UI. + bool set_shape(const Pointfs& shape); const BoundingBoxf3& get_bounding_box() const; bool contains(const Point& point) const; From ea163edc41f1e3394e89bd187b25c338418e1433 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Sun, 5 Aug 2018 23:36:25 +0200 Subject: [PATCH 073/119] Deregister the update callback on end of the application. This fixes a crash on exit. --- lib/Slic3r/GUI/MainFrame.pm | 1 + lib/Slic3r/GUI/Plater.pm | 7 +------ xs/src/slic3r/GUI/GUI.cpp | 5 ----- xs/src/slic3r/GUI/GUI.hpp | 2 +- xs/xsp/GUI.xsp | 14 ++++++-------- 5 files changed, 9 insertions(+), 20 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 77d7956c93..6baefa545a 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -107,6 +107,7 @@ sub new { wxTheApp->{app_config}->save; $self->{plater}->{print} = undef if($self->{plater}); Slic3r::GUI::_3DScene::remove_all_canvases(); + Slic3r::GUI::deregister_on_request_update_callback(); # propagate event $event->Skip; }); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 340807f5f1..adaf101fb6 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -145,11 +145,6 @@ sub new { $self->rotate(rad2deg($angle_z), Z, 'absolute'); }; - # callback to call schedule_background_process - my $on_request_update = sub { - $self->schedule_background_process; - }; - # callback to update object's geometry info while using gizmos my $on_update_geometry_info = sub { my ($size_x, $size_y, $size_z, $scale_factor) = @_; @@ -208,7 +203,7 @@ sub new { Slic3r::GUI::_3DScene::register_on_viewport_changed_callback($self->{canvas3D}, sub { Slic3r::GUI::_3DScene::set_viewport_from_scene($self->{preview3D}->canvas, $self->{canvas3D}); }); } - Slic3r::_GUI::register_on_request_update_callback($on_request_update); + Slic3r::GUI::register_on_request_update_callback(sub { $self->schedule_background_process; }); # # Initialize 2D preview canvas # $self->{canvas} = Slic3r::GUI::Plater::2D->new($self->{preview_notebook}, wxDefaultSize, $self->{objects}, $self->{model}, $self->{config}); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 8f3c8144cc..8cd7ed7768 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -920,11 +920,6 @@ ConfigOptionsGroup* get_optgroup() return m_optgroup.get(); } -void register_on_request_update_callback(void* callback) { - if (callback != nullptr) - g_on_request_update_callback.register_callback(callback); -} - wxButton* get_wiping_dialog_button() { return g_wiping_dialog_button; diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index c41ba2521d..165288819b 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -172,8 +172,8 @@ wxString from_u8(const std::string &str); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); +// Callback to trigger a configuration update timer on the Plater. static PerlCallback g_on_request_update_callback; -void register_on_request_update_callback(void* callback); ConfigOptionsGroup* get_optgroup(); wxButton* get_wiping_dialog_button(); diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 7872abc806..c6eead1ad5 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -105,11 +105,9 @@ void fix_model_by_win10_sdk_gui(ModelObject *model_object_src, Print *print, Mod void set_3DScene(SV *scene) %code%{ Slic3r::GUI::set_3DScene((_3DScene *)wxPli_sv_2_object(aTHX_ scene, "Slic3r::Model::3DScene") ); %}; -%package{Slic3r::_GUI}; -%{ -void -register_on_request_update_callback(callback) - SV *callback; - CODE: - Slic3r::GUI::register_on_request_update_callback((void*)callback); -%} +void register_on_request_update_callback(SV* callback) + %code%{ Slic3r::GUI::g_on_request_update_callback.register_callback(callback); %}; + +void deregister_on_request_update_callback() + %code%{ Slic3r::GUI::g_on_request_update_callback.deregister_callback(); %}; + From 7edc1dd577c2de8cd04ef7e0390d3c819ab154dd Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 6 Aug 2018 15:47:03 +0200 Subject: [PATCH 074/119] Deleted empty line between "default value" and "variable name" --- xs/src/slic3r/GUI/Field.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 0885e041ba..67490820c0 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -95,9 +95,9 @@ namespace Slic3r { namespace GUI { wxString tooltip_text(""); wxString tooltip = _(m_opt.tooltip); if (tooltip.length() > 0) - tooltip_text = tooltip + "\n " + _(L("default value")) + "\t: " + + tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " + (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string + - (boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") + "\n " + + (boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") + _(L("variable name")) + "\t: " + m_opt_id; return tooltip_text; From 4f526010810d40a844a6d66dfb2655f0cfed69cd Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 6 Aug 2018 16:31:51 +0200 Subject: [PATCH 075/119] Minor tweaks of UI texts, optimization of the wipe tower invalidation, show collisions of the wipe tower with known dimensions. --- lib/Slic3r/GUI/Plater.pm | 2 +- xs/src/libslic3r/GCode.cpp | 16 ++++++++++++---- xs/src/libslic3r/Print.cpp | 11 +---------- xs/src/libslic3r/PrintConfig.cpp | 9 ++++----- xs/src/slic3r/GUI/3DScene.cpp | 1 + xs/src/slic3r/GUI/Field.cpp | 2 +- 6 files changed, 20 insertions(+), 21 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index adaf101fb6..a0eef72fea 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1291,7 +1291,7 @@ sub async_apply_config { # We also need to reload 3D scene because of the wipe tower preview box if ($self->{config}->wipe_tower) { - Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1) if $self->{canvas3D} + Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1) if $self->{canvas3D} } } } diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 7edd550770..b34ba54415 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -524,13 +524,21 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, print.config.machine_max_jerk_y.values[1]); m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, print.config.machine_max_jerk_z.values[1]); m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, print.config.machine_max_jerk_e.values[1]); - m_silent_time_estimator.set_filament_load_times(print.config.filament_load_time.values); - m_silent_time_estimator.set_filament_unload_times(print.config.filament_unload_time.values); + if (print.config.single_extruder_multi_material) { + // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they + // are considered to be active for the single extruder multi-material printers only. + m_silent_time_estimator.set_filament_load_times(print.config.filament_load_time.values); + m_silent_time_estimator.set_filament_unload_times(print.config.filament_unload_time.values); + } } } // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. - m_normal_time_estimator.set_filament_load_times(print.config.filament_load_time.values); - m_normal_time_estimator.set_filament_unload_times(print.config.filament_unload_time.values); + if (print.config.single_extruder_multi_material) { + // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they + // are considered to be active for the single extruder multi-material printers only. + m_normal_time_estimator.set_filament_load_times(print.config.filament_load_time.values); + m_normal_time_estimator.set_filament_unload_times(print.config.filament_unload_time.values); + } // resets analyzer m_analyzer.reset(); diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index ab5d139508..7d2906bcc1 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -128,7 +128,6 @@ bool Print::invalidate_state_by_config_options(const std::vector osteps; bool invalidated = false; - // Always invalidate the wipe tower. This is probably necessary because of the wipe_into_infill / wipe_into_objects - // features - nearly anything can influence what should (and could) be wiped into. - // Only these three parameters don't invalidate the wipe tower (they only affect the gcode export): - for (const t_config_option_key &opt_key : opt_keys) - if (opt_key != "wipe_tower_x" && opt_key != "wipe_tower_y" && opt_key != "wipe_tower_rotation_angle") { - steps.emplace_back(psWipeTower); - break; - } - for (const t_config_option_key &opt_key : opt_keys) { if (steps_ignore.find(opt_key) != steps_ignore.end()) { // These options only affect G-code export or they are just notes without influence on the generated G-code, @@ -218,6 +208,7 @@ bool Print::invalidate_state_by_config_options(const std::vectoradd("filament_minimal_purge_on_wipe_tower", coFloats); def->label = L("Minimal purge on wipe tower"); - def->tooltip = L("After a toolchange, certain amount of filament is used for purging. This " - "can end up on the wipe tower, infill or sacrificial object. If there was " - "enough infill etc. available, this could result in bad quality at the beginning " - "of purging. This is a minimum that must be wiped on the wipe tower before " - "Slic3r considers moving elsewhere. "); + def->tooltip = L("After a tool change, the exact position of the newly loaded filament inside " + "the nozzle may not be known, and the filament pressure is likely not yet stable. " + "Before purging the print head into an infill or a sacrificial object, Slic3r will always prime " + "this amount of material into the wipe tower to produce successive infill or sacrificial object extrusions reliably."); def->cli = "filament-minimal-purge-on-wipe-tower=f@"; def->sidetext = L("mm³"); def->min = 0; diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 33b3768ed3..3f01eb20c1 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -715,6 +715,7 @@ int GLVolumeCollection::load_wipe_tower_preview( v.select_group_id = obj_idx * 1000000; v.drag_group_id = obj_idx * 1000; v.is_wipe_tower = true; + v.shader_outside_printer_detection_enabled = ! size_unknown; return int(this->volumes.size() - 1); } diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 67490820c0..85fa790a5e 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -98,7 +98,7 @@ namespace Slic3r { namespace GUI { tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " + (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string + (boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") + - _(L("variable name")) + "\t: " + m_opt_id; + _(L("parameter name")) + "\t: " + m_opt_id; return tooltip_text; } From a8cef5bf509cf1675152054a70e4408da5135bf9 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 6 Aug 2018 16:37:41 +0200 Subject: [PATCH 076/119] Changed checkbox labels for purge into infill/object feature --- xs/src/libslic3r/PrintConfig.cpp | 8 ++++---- xs/src/slic3r/GUI/GLCanvas3D.cpp | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index fe1a757395..b69a418e1b 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -2035,8 +2035,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("wipe_into_infill", coBool); def->category = L("Extruders"); - def->label = L("Purging into infill"); - def->tooltip = L("Wiping after toolchange will be preferentially done inside infills. " + def->label = L("Purge into this object's infill"); + def->tooltip = L("Purging after toolchange will done inside this object's infills. " "This lowers the amount of waste but may result in longer print time " " due to additional travel moves."); def->cli = "wipe-into-infill!"; @@ -2044,8 +2044,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("wipe_into_objects", coBool); def->category = L("Extruders"); - def->label = L("Purging into objects"); - def->tooltip = L("Objects will be used to wipe the nozzle after a toolchange to save material " + def->label = L("Purge into this object"); + def->tooltip = L("Object will be used to purge the nozzle after a toolchange to save material " "that would otherwise end up in the wipe tower and decrease print time. " "Colours of the objects will be mixed as a result."); def->cli = "wipe-into-objects!"; diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index f45e8f61ce..ab4095e6fe 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -3392,7 +3392,7 @@ void GLCanvas3D::_camera_tranform() const ::glMatrixMode(GL_MODELVIEW); ::glLoadIdentity(); - ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch + ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch ::glRotatef(m_camera.phi, 0.0f, 0.0f, 1.0f); // yaw Pointf3 neg_target = m_camera.target.negative(); @@ -4853,7 +4853,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs) if (m_model == nullptr) return; - std::set done; // prevent moving instances twice + std::set done; // prevent moving instances twice bool object_moved = false; Pointf3 wipe_tower_origin(0.0, 0.0, 0.0); for (int volume_idx : volume_idxs) @@ -4862,7 +4862,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs) int obj_idx = volume->object_idx(); int instance_idx = volume->instance_idx(); - // prevent moving instances twice + // prevent moving instances twice char done_id[64]; ::sprintf(done_id, "%d_%d", obj_idx, instance_idx); if (done.find(done_id) != done.end()) From 1e8d646586736a54b056b3fa0e692139aa653233 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 6 Aug 2018 18:04:35 +0200 Subject: [PATCH 077/119] Updated MK3MM2 presets, updated start G-codes of the MK2 printer as it does not support the new M204 format. --- resources/profiles/PrusaResearch.idx | 1 + resources/profiles/PrusaResearch.ini | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 21c2e518dc..ba4123588c 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,5 @@ min_slic3r_version = 1.41.0-alpha +0.2.0-beta Removed limit on the MK3MMU2 height, added legacy M204 S T format to the MK2 profiles 0.2.0-alpha8 Added filament_load/unload_time for the PLA/ABS MMU2 filament presets. 0.2.0-alpha7 Fixed the *MK3* references 0.2.0-alpha6 diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 496425e0dc..32ec800e7f 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.2.0-alpha8 +config_version = 0.2.0-beta # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -563,6 +563,7 @@ filament_cooling_moves = 4 filament_cooling_initial_speed = 2.2 filament_cooling_final_speed = 3.4 filament_ramming_parameters = "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_minimal_purge_on_wipe_tower = 5 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 @@ -1024,7 +1025,7 @@ retract_speed = 35 serial_port = serial_speed = 250000 single_extruder_multi_material = 0 -start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 @@ -1060,7 +1061,7 @@ printer_model = MK2SMM inherits = *multimaterial* end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; turn off fan\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN -start_gcode = M115 U3.1.0 ; tell printer latest fw version\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 default_print_profile = 0.15mm OPTIMAL [printer:*mm-multi*] @@ -1069,7 +1070,7 @@ end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes. extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 nozzle_diameter = 0.4,0.4,0.4,0.4 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN -start_gcode = M115 U3.1.0 ; tell printer latest fw version\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_single_extruder_multi_material_priming}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_single_extruder_multi_material_priming}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 variable_layer_height = 0 default_print_profile = 0.15mm OPTIMAL @@ -1195,7 +1196,6 @@ default_print_profile = 0.15mm OPTIMAL 0.6 nozzle MK3 [printer:*mm2*] inherits = Original Prusa i3 MK3 single_extruder_multi_material = 1 -max_print_height = 200 cooling_tube_length = 10 cooling_tube_retraction = 30 parking_pos_retraction = 85 From 288cd58ee2f077a4a8d416620a2ac8e68d4cabca Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 6 Aug 2018 18:09:52 +0200 Subject: [PATCH 078/119] Bumped up version number. --- xs/src/libslic3r/libslic3r.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 2f07ca51a3..34f61cb127 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -14,7 +14,7 @@ #include #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" -#define SLIC3R_VERSION "1.41.0-alpha3" +#define SLIC3R_VERSION "1.41.0-beta" #define SLIC3R_BUILD "UNKNOWN" typedef int32_t coord_t; From 705ccbe331cd61e305a27bc4c461f0e0aafcb6c5 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 7 Aug 2018 11:15:47 +0200 Subject: [PATCH 079/119] Added qhull library to xs/src and cmake --- xs/CMakeLists.txt | 6 +- xs/src/qhull/Announce.txt | 47 + xs/src/qhull/CMakeLists.txt | 128 + xs/src/qhull/COPYING.txt | 38 + xs/src/qhull/README.txt | 623 +++ xs/src/qhull/REGISTER.txt | 32 + xs/src/qhull/html/index.htm | 935 ++++ .../html/normal_voronoi_knauss_oesterle.jpg | Bin 0 -> 23924 bytes xs/src/qhull/html/qconvex.htm | 630 +++ xs/src/qhull/html/qdelau_f.htm | 416 ++ xs/src/qhull/html/qdelaun.htm | 628 +++ xs/src/qhull/html/qh--4d.gif | Bin 0 -> 4372 bytes xs/src/qhull/html/qh--cone.gif | Bin 0 -> 2946 bytes xs/src/qhull/html/qh--dt.gif | Bin 0 -> 3772 bytes xs/src/qhull/html/qh--geom.gif | Bin 0 -> 318 bytes xs/src/qhull/html/qh--half.gif | Bin 0 -> 2537 bytes xs/src/qhull/html/qh--rand.gif | Bin 0 -> 3875 bytes xs/src/qhull/html/qh-code.htm | 1062 +++++ xs/src/qhull/html/qh-eg.htm | 693 +++ xs/src/qhull/html/qh-faq.htm | 1547 +++++++ xs/src/qhull/html/qh-get.htm | 106 + xs/src/qhull/html/qh-impre.htm | 826 ++++ xs/src/qhull/html/qh-optc.htm | 292 ++ xs/src/qhull/html/qh-optf.htm | 736 +++ xs/src/qhull/html/qh-optg.htm | 274 ++ xs/src/qhull/html/qh-opto.htm | 353 ++ xs/src/qhull/html/qh-optp.htm | 253 + xs/src/qhull/html/qh-optq.htm | 731 +++ xs/src/qhull/html/qh-optt.htm | 278 ++ xs/src/qhull/html/qh-quick.htm | 495 ++ xs/src/qhull/html/qhalf.htm | 626 +++ xs/src/qhull/html/qhull-cpp.xml | 214 + xs/src/qhull/html/qhull.htm | 473 ++ xs/src/qhull/html/qhull.man | 1008 ++++ xs/src/qhull/html/qhull.txt | 1263 +++++ xs/src/qhull/html/qvoron_f.htm | 396 ++ xs/src/qhull/html/qvoronoi.htm | 667 +++ xs/src/qhull/html/rbox.htm | 277 ++ xs/src/qhull/html/rbox.man | 176 + xs/src/qhull/html/rbox.txt | 195 + xs/src/qhull/index.htm | 284 ++ xs/src/qhull/origCMakeLists.txt | 426 ++ xs/src/qhull/src/Changes.txt | 2129 +++++++++ xs/src/qhull/src/libqhull/DEPRECATED.txt | 29 + xs/src/qhull/src/libqhull/Makefile | 240 + xs/src/qhull/src/libqhull/Mborland | 206 + xs/src/qhull/src/libqhull/geom.c | 1234 +++++ xs/src/qhull/src/libqhull/geom.h | 176 + xs/src/qhull/src/libqhull/geom2.c | 2094 +++++++++ xs/src/qhull/src/libqhull/global.c | 2217 +++++++++ xs/src/qhull/src/libqhull/index.htm | 264 ++ xs/src/qhull/src/libqhull/io.c | 4062 +++++++++++++++++ xs/src/qhull/src/libqhull/io.h | 159 + xs/src/qhull/src/libqhull/libqhull.c | 1403 ++++++ xs/src/qhull/src/libqhull/libqhull.h | 1140 +++++ xs/src/qhull/src/libqhull/libqhull.pro | 67 + xs/src/qhull/src/libqhull/mem.c | 576 +++ xs/src/qhull/src/libqhull/mem.h | 222 + xs/src/qhull/src/libqhull/merge.c | 3628 +++++++++++++++ xs/src/qhull/src/libqhull/merge.h | 178 + xs/src/qhull/src/libqhull/poly.c | 1205 +++++ xs/src/qhull/src/libqhull/poly.h | 296 ++ xs/src/qhull/src/libqhull/poly2.c | 3222 +++++++++++++ xs/src/qhull/src/libqhull/qh-geom.htm | 295 ++ xs/src/qhull/src/libqhull/qh-globa.htm | 165 + xs/src/qhull/src/libqhull/qh-io.htm | 305 ++ xs/src/qhull/src/libqhull/qh-mem.htm | 145 + xs/src/qhull/src/libqhull/qh-merge.htm | 366 ++ xs/src/qhull/src/libqhull/qh-poly.htm | 485 ++ xs/src/qhull/src/libqhull/qh-qhull.htm | 279 ++ xs/src/qhull/src/libqhull/qh-set.htm | 308 ++ xs/src/qhull/src/libqhull/qh-stat.htm | 163 + xs/src/qhull/src/libqhull/qh-user.htm | 271 ++ xs/src/qhull/src/libqhull/qhull-exports.def | 417 ++ xs/src/qhull/src/libqhull/qhull_a.h | 150 + xs/src/qhull/src/libqhull/qhull_p-exports.def | 418 ++ xs/src/qhull/src/libqhull/qset.c | 1340 ++++++ xs/src/qhull/src/libqhull/qset.h | 490 ++ xs/src/qhull/src/libqhull/random.c | 245 + xs/src/qhull/src/libqhull/random.h | 34 + xs/src/qhull/src/libqhull/rboxlib.c | 870 ++++ xs/src/qhull/src/libqhull/stat.c | 717 +++ xs/src/qhull/src/libqhull/stat.h | 543 +++ xs/src/qhull/src/libqhull/user.c | 538 +++ xs/src/qhull/src/libqhull/user.h | 909 ++++ xs/src/qhull/src/libqhull/usermem.c | 94 + xs/src/qhull/src/libqhull/userprintf.c | 66 + xs/src/qhull/src/libqhull/userprintf_rbox.c | 53 + xs/src/qhull/src/libqhull_r/Makefile | 240 + xs/src/qhull/src/libqhull_r/geom2_r.c | 2096 +++++++++ xs/src/qhull/src/libqhull_r/geom_r.c | 1234 +++++ xs/src/qhull/src/libqhull_r/geom_r.h | 184 + xs/src/qhull/src/libqhull_r/global_r.c | 2100 +++++++++ xs/src/qhull/src/libqhull_r/index.htm | 266 ++ xs/src/qhull/src/libqhull_r/io_r.c | 4062 +++++++++++++++++ xs/src/qhull/src/libqhull_r/io_r.h | 167 + xs/src/qhull/src/libqhull_r/libqhull_r.c | 1403 ++++++ xs/src/qhull/src/libqhull_r/libqhull_r.h | 1134 +++++ xs/src/qhull/src/libqhull_r/libqhull_r.pro | 67 + xs/src/qhull/src/libqhull_r/mem_r.c | 562 +++ xs/src/qhull/src/libqhull_r/mem_r.h | 234 + xs/src/qhull/src/libqhull_r/merge_r.c | 3627 +++++++++++++++ xs/src/qhull/src/libqhull_r/merge_r.h | 186 + xs/src/qhull/src/libqhull_r/poly2_r.c | 3222 +++++++++++++ xs/src/qhull/src/libqhull_r/poly_r.c | 1205 +++++ xs/src/qhull/src/libqhull_r/poly_r.h | 303 ++ xs/src/qhull/src/libqhull_r/qh-geom_r.htm | 295 ++ xs/src/qhull/src/libqhull_r/qh-globa_r.htm | 163 + xs/src/qhull/src/libqhull_r/qh-io_r.htm | 305 ++ xs/src/qhull/src/libqhull_r/qh-mem_r.htm | 145 + xs/src/qhull/src/libqhull_r/qh-merge_r.htm | 366 ++ xs/src/qhull/src/libqhull_r/qh-poly_r.htm | 485 ++ xs/src/qhull/src/libqhull_r/qh-qhull_r.htm | 279 ++ xs/src/qhull/src/libqhull_r/qh-set_r.htm | 308 ++ xs/src/qhull/src/libqhull_r/qh-stat_r.htm | 161 + xs/src/qhull/src/libqhull_r/qh-user_r.htm | 271 ++ .../qhull/src/libqhull_r/qhull_r-exports.def | 404 ++ xs/src/qhull/src/libqhull_r/qhull_ra.h | 158 + xs/src/qhull/src/libqhull_r/qset_r.c | 1340 ++++++ xs/src/qhull/src/libqhull_r/qset_r.h | 502 ++ xs/src/qhull/src/libqhull_r/random_r.c | 247 + xs/src/qhull/src/libqhull_r/random_r.h | 41 + xs/src/qhull/src/libqhull_r/rboxlib_r.c | 842 ++++ xs/src/qhull/src/libqhull_r/stat_r.c | 682 +++ xs/src/qhull/src/libqhull_r/stat_r.h | 533 +++ xs/src/qhull/src/libqhull_r/user_r.c | 527 +++ xs/src/qhull/src/libqhull_r/user_r.h | 882 ++++ xs/src/qhull/src/libqhull_r/usermem_r.c | 94 + xs/src/qhull/src/libqhull_r/userprintf_r.c | 65 + .../qhull/src/libqhull_r/userprintf_rbox_r.c | 53 + xs/src/qhull/src/libqhullcpp/Coordinates.cpp | 198 + xs/src/qhull/src/libqhullcpp/Coordinates.h | 303 ++ .../src/libqhullcpp/PointCoordinates.cpp | 348 ++ .../qhull/src/libqhullcpp/PointCoordinates.h | 161 + xs/src/qhull/src/libqhullcpp/Qhull.cpp | 352 ++ xs/src/qhull/src/libqhullcpp/Qhull.h | 132 + xs/src/qhull/src/libqhullcpp/QhullError.h | 62 + xs/src/qhull/src/libqhullcpp/QhullFacet.cpp | 519 +++ xs/src/qhull/src/libqhullcpp/QhullFacet.h | 151 + .../qhull/src/libqhullcpp/QhullFacetList.cpp | 174 + xs/src/qhull/src/libqhullcpp/QhullFacetList.h | 106 + .../qhull/src/libqhullcpp/QhullFacetSet.cpp | 147 + xs/src/qhull/src/libqhullcpp/QhullFacetSet.h | 97 + .../qhull/src/libqhullcpp/QhullHyperplane.cpp | 187 + .../qhull/src/libqhullcpp/QhullHyperplane.h | 123 + xs/src/qhull/src/libqhullcpp/QhullIterator.h | 173 + .../qhull/src/libqhullcpp/QhullLinkedList.h | 388 ++ xs/src/qhull/src/libqhullcpp/QhullPoint.cpp | 203 + xs/src/qhull/src/libqhullcpp/QhullPoint.h | 136 + .../qhull/src/libqhullcpp/QhullPointSet.cpp | 62 + xs/src/qhull/src/libqhullcpp/QhullPointSet.h | 77 + xs/src/qhull/src/libqhullcpp/QhullPoints.cpp | 320 ++ xs/src/qhull/src/libqhullcpp/QhullPoints.h | 266 ++ xs/src/qhull/src/libqhullcpp/QhullQh.cpp | 237 + xs/src/qhull/src/libqhullcpp/QhullQh.h | 110 + xs/src/qhull/src/libqhullcpp/QhullRidge.cpp | 124 + xs/src/qhull/src/libqhullcpp/QhullRidge.h | 112 + xs/src/qhull/src/libqhullcpp/QhullSet.cpp | 62 + xs/src/qhull/src/libqhullcpp/QhullSet.h | 462 ++ xs/src/qhull/src/libqhullcpp/QhullSets.h | 27 + xs/src/qhull/src/libqhullcpp/QhullStat.cpp | 42 + xs/src/qhull/src/libqhullcpp/QhullStat.h | 49 + xs/src/qhull/src/libqhullcpp/QhullVertex.cpp | 112 + xs/src/qhull/src/libqhullcpp/QhullVertex.h | 104 + .../qhull/src/libqhullcpp/QhullVertexSet.cpp | 161 + xs/src/qhull/src/libqhullcpp/QhullVertexSet.h | 86 + xs/src/qhull/src/libqhullcpp/RboxPoints.cpp | 224 + xs/src/qhull/src/libqhullcpp/RboxPoints.h | 69 + xs/src/qhull/src/libqhullcpp/RoadError.cpp | 158 + xs/src/qhull/src/libqhullcpp/RoadError.h | 88 + xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp | 122 + xs/src/qhull/src/libqhullcpp/RoadLogEvent.h | 77 + .../qhull/src/libqhullcpp/functionObjects.h | 67 + xs/src/qhull/src/libqhullcpp/libqhullcpp.pro | 71 + xs/src/qhull/src/libqhullcpp/qt-qhull.cpp | 130 + .../qhull/src/libqhullcpp/usermem_r-cpp.cpp | 93 + .../src/libqhullstatic/libqhullstatic.pro | 19 + .../src/libqhullstatic_r/libqhullstatic_r.pro | 21 + xs/src/qhull/src/qconvex/qconvex.c | 326 ++ xs/src/qhull/src/qconvex/qconvex.pro | 9 + xs/src/qhull/src/qconvex/qconvex_r.c | 328 ++ xs/src/qhull/src/qdelaunay/qdelaun.c | 315 ++ xs/src/qhull/src/qdelaunay/qdelaun_r.c | 317 ++ xs/src/qhull/src/qdelaunay/qdelaunay.pro | 9 + xs/src/qhull/src/qhalf/qhalf.c | 316 ++ xs/src/qhull/src/qhalf/qhalf.pro | 9 + xs/src/qhull/src/qhalf/qhalf_r.c | 318 ++ xs/src/qhull/src/qhull-all.pro | 94 + xs/src/qhull/src/qhull-app-c.pri | 24 + xs/src/qhull/src/qhull-app-c_r.pri | 26 + xs/src/qhull/src/qhull-app-cpp.pri | 23 + xs/src/qhull/src/qhull-app-shared.pri | 27 + xs/src/qhull/src/qhull-app-shared_r.pri | 29 + xs/src/qhull/src/qhull-libqhull-src.pri | 39 + xs/src/qhull/src/qhull-libqhull-src_r.pri | 39 + xs/src/qhull/src/qhull-warn.pri | 57 + xs/src/qhull/src/qhull/qhull.pro | 9 + xs/src/qhull/src/qhull/unix.c | 372 ++ xs/src/qhull/src/qhull/unix_r.c | 374 ++ .../qhull/src/qhulltest/Coordinates_test.cpp | 539 +++ .../src/qhulltest/PointCoordinates_test.cpp | 478 ++ .../src/qhulltest/QhullFacetList_test.cpp | 196 + .../src/qhulltest/QhullFacetSet_test.cpp | 153 + .../qhull/src/qhulltest/QhullFacet_test.cpp | 283 ++ .../src/qhulltest/QhullHyperplane_test.cpp | 429 ++ .../src/qhulltest/QhullLinkedList_test.cpp | 330 ++ .../src/qhulltest/QhullPointSet_test.cpp | 378 ++ .../qhull/src/qhulltest/QhullPoint_test.cpp | 437 ++ .../qhull/src/qhulltest/QhullPoints_test.cpp | 561 +++ .../qhull/src/qhulltest/QhullRidge_test.cpp | 159 + xs/src/qhull/src/qhulltest/QhullSet_test.cpp | 434 ++ .../src/qhulltest/QhullVertexSet_test.cpp | 152 + .../qhull/src/qhulltest/QhullVertex_test.cpp | 184 + xs/src/qhull/src/qhulltest/Qhull_test.cpp | 360 ++ .../qhull/src/qhulltest/RboxPoints_test.cpp | 215 + xs/src/qhull/src/qhulltest/RoadTest.cpp | 94 + xs/src/qhull/src/qhulltest/RoadTest.h | 102 + xs/src/qhull/src/qhulltest/qhulltest.cpp | 94 + xs/src/qhull/src/qhulltest/qhulltest.pro | 36 + xs/src/qhull/src/qvoronoi/qvoronoi.c | 303 ++ xs/src/qhull/src/qvoronoi/qvoronoi.pro | 9 + xs/src/qhull/src/qvoronoi/qvoronoi_r.c | 305 ++ xs/src/qhull/src/rbox/rbox.c | 88 + xs/src/qhull/src/rbox/rbox.pro | 9 + xs/src/qhull/src/rbox/rbox_r.c | 78 + xs/src/qhull/src/testqset/testqset.c | 891 ++++ xs/src/qhull/src/testqset/testqset.pro | 30 + xs/src/qhull/src/testqset_r/testqset_r.c | 890 ++++ xs/src/qhull/src/testqset_r/testqset_r.pro | 30 + xs/src/qhull/src/user_eg/user_eg.c | 330 ++ xs/src/qhull/src/user_eg/user_eg.pro | 11 + xs/src/qhull/src/user_eg/user_eg_r.c | 326 ++ xs/src/qhull/src/user_eg2/user_eg2.c | 746 +++ xs/src/qhull/src/user_eg2/user_eg2.pro | 11 + xs/src/qhull/src/user_eg2/user_eg2_r.c | 742 +++ xs/src/qhull/src/user_eg3/user_eg3.pro | 12 + xs/src/qhull/src/user_eg3/user_eg3_r.cpp | 162 + 237 files changed, 104145 insertions(+), 1 deletion(-) create mode 100644 xs/src/qhull/Announce.txt create mode 100644 xs/src/qhull/CMakeLists.txt create mode 100644 xs/src/qhull/COPYING.txt create mode 100644 xs/src/qhull/README.txt create mode 100644 xs/src/qhull/REGISTER.txt create mode 100644 xs/src/qhull/html/index.htm create mode 100644 xs/src/qhull/html/normal_voronoi_knauss_oesterle.jpg create mode 100644 xs/src/qhull/html/qconvex.htm create mode 100644 xs/src/qhull/html/qdelau_f.htm create mode 100644 xs/src/qhull/html/qdelaun.htm create mode 100644 xs/src/qhull/html/qh--4d.gif create mode 100644 xs/src/qhull/html/qh--cone.gif create mode 100644 xs/src/qhull/html/qh--dt.gif create mode 100644 xs/src/qhull/html/qh--geom.gif create mode 100644 xs/src/qhull/html/qh--half.gif create mode 100644 xs/src/qhull/html/qh--rand.gif create mode 100644 xs/src/qhull/html/qh-code.htm create mode 100644 xs/src/qhull/html/qh-eg.htm create mode 100644 xs/src/qhull/html/qh-faq.htm create mode 100644 xs/src/qhull/html/qh-get.htm create mode 100644 xs/src/qhull/html/qh-impre.htm create mode 100644 xs/src/qhull/html/qh-optc.htm create mode 100644 xs/src/qhull/html/qh-optf.htm create mode 100644 xs/src/qhull/html/qh-optg.htm create mode 100644 xs/src/qhull/html/qh-opto.htm create mode 100644 xs/src/qhull/html/qh-optp.htm create mode 100644 xs/src/qhull/html/qh-optq.htm create mode 100644 xs/src/qhull/html/qh-optt.htm create mode 100644 xs/src/qhull/html/qh-quick.htm create mode 100644 xs/src/qhull/html/qhalf.htm create mode 100644 xs/src/qhull/html/qhull-cpp.xml create mode 100644 xs/src/qhull/html/qhull.htm create mode 100644 xs/src/qhull/html/qhull.man create mode 100644 xs/src/qhull/html/qhull.txt create mode 100644 xs/src/qhull/html/qvoron_f.htm create mode 100644 xs/src/qhull/html/qvoronoi.htm create mode 100644 xs/src/qhull/html/rbox.htm create mode 100644 xs/src/qhull/html/rbox.man create mode 100644 xs/src/qhull/html/rbox.txt create mode 100644 xs/src/qhull/index.htm create mode 100644 xs/src/qhull/origCMakeLists.txt create mode 100644 xs/src/qhull/src/Changes.txt create mode 100644 xs/src/qhull/src/libqhull/DEPRECATED.txt create mode 100644 xs/src/qhull/src/libqhull/Makefile create mode 100644 xs/src/qhull/src/libqhull/Mborland create mode 100644 xs/src/qhull/src/libqhull/geom.c create mode 100644 xs/src/qhull/src/libqhull/geom.h create mode 100644 xs/src/qhull/src/libqhull/geom2.c create mode 100644 xs/src/qhull/src/libqhull/global.c create mode 100644 xs/src/qhull/src/libqhull/index.htm create mode 100644 xs/src/qhull/src/libqhull/io.c create mode 100644 xs/src/qhull/src/libqhull/io.h create mode 100644 xs/src/qhull/src/libqhull/libqhull.c create mode 100644 xs/src/qhull/src/libqhull/libqhull.h create mode 100644 xs/src/qhull/src/libqhull/libqhull.pro create mode 100644 xs/src/qhull/src/libqhull/mem.c create mode 100644 xs/src/qhull/src/libqhull/mem.h create mode 100644 xs/src/qhull/src/libqhull/merge.c create mode 100644 xs/src/qhull/src/libqhull/merge.h create mode 100644 xs/src/qhull/src/libqhull/poly.c create mode 100644 xs/src/qhull/src/libqhull/poly.h create mode 100644 xs/src/qhull/src/libqhull/poly2.c create mode 100644 xs/src/qhull/src/libqhull/qh-geom.htm create mode 100644 xs/src/qhull/src/libqhull/qh-globa.htm create mode 100644 xs/src/qhull/src/libqhull/qh-io.htm create mode 100644 xs/src/qhull/src/libqhull/qh-mem.htm create mode 100644 xs/src/qhull/src/libqhull/qh-merge.htm create mode 100644 xs/src/qhull/src/libqhull/qh-poly.htm create mode 100644 xs/src/qhull/src/libqhull/qh-qhull.htm create mode 100644 xs/src/qhull/src/libqhull/qh-set.htm create mode 100644 xs/src/qhull/src/libqhull/qh-stat.htm create mode 100644 xs/src/qhull/src/libqhull/qh-user.htm create mode 100644 xs/src/qhull/src/libqhull/qhull-exports.def create mode 100644 xs/src/qhull/src/libqhull/qhull_a.h create mode 100644 xs/src/qhull/src/libqhull/qhull_p-exports.def create mode 100644 xs/src/qhull/src/libqhull/qset.c create mode 100644 xs/src/qhull/src/libqhull/qset.h create mode 100644 xs/src/qhull/src/libqhull/random.c create mode 100644 xs/src/qhull/src/libqhull/random.h create mode 100644 xs/src/qhull/src/libqhull/rboxlib.c create mode 100644 xs/src/qhull/src/libqhull/stat.c create mode 100644 xs/src/qhull/src/libqhull/stat.h create mode 100644 xs/src/qhull/src/libqhull/user.c create mode 100644 xs/src/qhull/src/libqhull/user.h create mode 100644 xs/src/qhull/src/libqhull/usermem.c create mode 100644 xs/src/qhull/src/libqhull/userprintf.c create mode 100644 xs/src/qhull/src/libqhull/userprintf_rbox.c create mode 100644 xs/src/qhull/src/libqhull_r/Makefile create mode 100644 xs/src/qhull/src/libqhull_r/geom2_r.c create mode 100644 xs/src/qhull/src/libqhull_r/geom_r.c create mode 100644 xs/src/qhull/src/libqhull_r/geom_r.h create mode 100644 xs/src/qhull/src/libqhull_r/global_r.c create mode 100644 xs/src/qhull/src/libqhull_r/index.htm create mode 100644 xs/src/qhull/src/libqhull_r/io_r.c create mode 100644 xs/src/qhull/src/libqhull_r/io_r.h create mode 100644 xs/src/qhull/src/libqhull_r/libqhull_r.c create mode 100644 xs/src/qhull/src/libqhull_r/libqhull_r.h create mode 100644 xs/src/qhull/src/libqhull_r/libqhull_r.pro create mode 100644 xs/src/qhull/src/libqhull_r/mem_r.c create mode 100644 xs/src/qhull/src/libqhull_r/mem_r.h create mode 100644 xs/src/qhull/src/libqhull_r/merge_r.c create mode 100644 xs/src/qhull/src/libqhull_r/merge_r.h create mode 100644 xs/src/qhull/src/libqhull_r/poly2_r.c create mode 100644 xs/src/qhull/src/libqhull_r/poly_r.c create mode 100644 xs/src/qhull/src/libqhull_r/poly_r.h create mode 100644 xs/src/qhull/src/libqhull_r/qh-geom_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-globa_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-io_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-mem_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-merge_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-poly_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-qhull_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-set_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-stat_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-user_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qhull_r-exports.def create mode 100644 xs/src/qhull/src/libqhull_r/qhull_ra.h create mode 100644 xs/src/qhull/src/libqhull_r/qset_r.c create mode 100644 xs/src/qhull/src/libqhull_r/qset_r.h create mode 100644 xs/src/qhull/src/libqhull_r/random_r.c create mode 100644 xs/src/qhull/src/libqhull_r/random_r.h create mode 100644 xs/src/qhull/src/libqhull_r/rboxlib_r.c create mode 100644 xs/src/qhull/src/libqhull_r/stat_r.c create mode 100644 xs/src/qhull/src/libqhull_r/stat_r.h create mode 100644 xs/src/qhull/src/libqhull_r/user_r.c create mode 100644 xs/src/qhull/src/libqhull_r/user_r.h create mode 100644 xs/src/qhull/src/libqhull_r/usermem_r.c create mode 100644 xs/src/qhull/src/libqhull_r/userprintf_r.c create mode 100644 xs/src/qhull/src/libqhull_r/userprintf_rbox_r.c create mode 100644 xs/src/qhull/src/libqhullcpp/Coordinates.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/Coordinates.h create mode 100644 xs/src/qhull/src/libqhullcpp/PointCoordinates.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/PointCoordinates.h create mode 100644 xs/src/qhull/src/libqhullcpp/Qhull.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/Qhull.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullError.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacet.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacet.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacetList.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacetList.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacetSet.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacetSet.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullHyperplane.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullHyperplane.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullIterator.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullLinkedList.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPoint.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPoint.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPointSet.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPointSet.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPoints.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPoints.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullQh.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullQh.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullRidge.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullRidge.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullSet.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullSet.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullSets.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullStat.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullStat.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullVertex.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullVertex.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullVertexSet.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullVertexSet.h create mode 100644 xs/src/qhull/src/libqhullcpp/RboxPoints.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/RboxPoints.h create mode 100644 xs/src/qhull/src/libqhullcpp/RoadError.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/RoadError.h create mode 100644 xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/RoadLogEvent.h create mode 100644 xs/src/qhull/src/libqhullcpp/functionObjects.h create mode 100644 xs/src/qhull/src/libqhullcpp/libqhullcpp.pro create mode 100644 xs/src/qhull/src/libqhullcpp/qt-qhull.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/usermem_r-cpp.cpp create mode 100644 xs/src/qhull/src/libqhullstatic/libqhullstatic.pro create mode 100644 xs/src/qhull/src/libqhullstatic_r/libqhullstatic_r.pro create mode 100644 xs/src/qhull/src/qconvex/qconvex.c create mode 100644 xs/src/qhull/src/qconvex/qconvex.pro create mode 100644 xs/src/qhull/src/qconvex/qconvex_r.c create mode 100644 xs/src/qhull/src/qdelaunay/qdelaun.c create mode 100644 xs/src/qhull/src/qdelaunay/qdelaun_r.c create mode 100644 xs/src/qhull/src/qdelaunay/qdelaunay.pro create mode 100644 xs/src/qhull/src/qhalf/qhalf.c create mode 100644 xs/src/qhull/src/qhalf/qhalf.pro create mode 100644 xs/src/qhull/src/qhalf/qhalf_r.c create mode 100644 xs/src/qhull/src/qhull-all.pro create mode 100644 xs/src/qhull/src/qhull-app-c.pri create mode 100644 xs/src/qhull/src/qhull-app-c_r.pri create mode 100644 xs/src/qhull/src/qhull-app-cpp.pri create mode 100644 xs/src/qhull/src/qhull-app-shared.pri create mode 100644 xs/src/qhull/src/qhull-app-shared_r.pri create mode 100644 xs/src/qhull/src/qhull-libqhull-src.pri create mode 100644 xs/src/qhull/src/qhull-libqhull-src_r.pri create mode 100644 xs/src/qhull/src/qhull-warn.pri create mode 100644 xs/src/qhull/src/qhull/qhull.pro create mode 100644 xs/src/qhull/src/qhull/unix.c create mode 100644 xs/src/qhull/src/qhull/unix_r.c create mode 100644 xs/src/qhull/src/qhulltest/Coordinates_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/PointCoordinates_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullFacetList_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullFacetSet_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullFacet_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullHyperplane_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullLinkedList_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullPointSet_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullPoint_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullPoints_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullRidge_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullSet_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullVertexSet_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullVertex_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/Qhull_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/RboxPoints_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/RoadTest.cpp create mode 100644 xs/src/qhull/src/qhulltest/RoadTest.h create mode 100644 xs/src/qhull/src/qhulltest/qhulltest.cpp create mode 100644 xs/src/qhull/src/qhulltest/qhulltest.pro create mode 100644 xs/src/qhull/src/qvoronoi/qvoronoi.c create mode 100644 xs/src/qhull/src/qvoronoi/qvoronoi.pro create mode 100644 xs/src/qhull/src/qvoronoi/qvoronoi_r.c create mode 100644 xs/src/qhull/src/rbox/rbox.c create mode 100644 xs/src/qhull/src/rbox/rbox.pro create mode 100644 xs/src/qhull/src/rbox/rbox_r.c create mode 100644 xs/src/qhull/src/testqset/testqset.c create mode 100644 xs/src/qhull/src/testqset/testqset.pro create mode 100644 xs/src/qhull/src/testqset_r/testqset_r.c create mode 100644 xs/src/qhull/src/testqset_r/testqset_r.pro create mode 100644 xs/src/qhull/src/user_eg/user_eg.c create mode 100644 xs/src/qhull/src/user_eg/user_eg.pro create mode 100644 xs/src/qhull/src/user_eg/user_eg_r.c create mode 100644 xs/src/qhull/src/user_eg2/user_eg2.c create mode 100644 xs/src/qhull/src/user_eg2/user_eg2.pro create mode 100644 xs/src/qhull/src/user_eg2/user_eg2_r.c create mode 100644 xs/src/qhull/src/user_eg3/user_eg3.pro create mode 100644 xs/src/qhull/src/user_eg3/user_eg3_r.cpp diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index d41b4c13a9..f1609bc14a 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -459,7 +459,7 @@ if(APPLE) # Ignore undefined symbols of the perl interpreter, they will be found in the caller image. target_link_libraries(XS "-undefined dynamic_lookup") endif() -target_link_libraries(XS libslic3r libslic3r_gui admesh miniz clipper nowide polypartition poly2tri semver avrdude) +target_link_libraries(XS libslic3r libslic3r_gui admesh miniz clipper nowide polypartition poly2tri semver avrdude qhull) if(SLIC3R_PROFILE) target_link_libraries(XS Shiny) endif() @@ -540,6 +540,10 @@ endif() add_subdirectory(src/avrdude) +add_subdirectory(src/qhull) +include_directories(${LIBDIR}/qhull/src) +message(STATUS ${LIBDIR}/qhull/src) + ## REQUIRED packages # Find and configure boost diff --git a/xs/src/qhull/Announce.txt b/xs/src/qhull/Announce.txt new file mode 100644 index 0000000000..635cff1afb --- /dev/null +++ b/xs/src/qhull/Announce.txt @@ -0,0 +1,47 @@ + + Qhull 2015.2 2016/01/18 + + http://www.qhull.org + git@github.com:qhull/qhull.git + http://www.geomview.org + +Qhull computes convex hulls, Delaunay triangulations, Voronoi diagrams, +furthest-site Voronoi diagrams, and halfspace intersections about a point. +It runs in 2-d, 3-d, 4-d, or higher. It implements the Quickhull algorithm +for computing convex hulls. Qhull handles round-off errors from floating +point arithmetic. It can approximate a convex hull. + +The program includes options for hull volume, facet area, partial hulls, +input transformations, randomization, tracing, multiple output formats, and +execution statistics. The program can be called from within your application. +You can view the results in 2-d, 3-d and 4-d with Geomview. + +To download Qhull: + http://www.qhull.org/download + git@github.com:qhull/qhull.git + +Download qhull-96.ps for: + + Barber, C. B., D.P. Dobkin, and H.T. Huhdanpaa, "The + Quickhull Algorithm for Convex Hulls," ACM Trans. on + Mathematical Software, 22(4):469-483, Dec. 1996. + http://www.acm.org/pubs/citations/journals/toms/1996-22-4/p469-barber/ + http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.117.405 + +Abstract: + +The convex hull of a set of points is the smallest convex set that contains +the points. This article presents a practical convex hull algorithm that +combines the two-dimensional Quickhull Algorithm with the general dimension +Beneath-Beyond Algorithm. It is similar to the randomized, incremental +algorithms for convex hull and Delaunay triangulation. We provide empirical +evidence that the algorithm runs faster when the input contains non-extreme +points, and that it uses less memory. + +Computational geometry algorithms have traditionally assumed that input sets +are well behaved. When an algorithm is implemented with floating point +arithmetic, this assumption can lead to serious errors. We briefly describe +a solution to this problem when computing the convex hull in two, three, or +four dimensions. The output is a set of "thick" facets that contain all +possible exact convex hulls of the input. A variation is effective in five +or more dimensions. diff --git a/xs/src/qhull/CMakeLists.txt b/xs/src/qhull/CMakeLists.txt new file mode 100644 index 0000000000..d798b018fa --- /dev/null +++ b/xs/src/qhull/CMakeLists.txt @@ -0,0 +1,128 @@ + +# This CMake file is written specifically to integrate qhull library with Slic3rPE +# (see https://github.com/prusa3d/Slic3r for more information about the project) +# +# Only original libraries qhullstatic_r and qhullcpp are included. +# They are built as a single statically linked library. +# +# Created by modification of the original qhull CMakeLists. +# Lukas Matena (25.7.2018), lukasmatena@seznam.cz + + +project(qhull) +cmake_minimum_required(VERSION 2.6) + +# Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, qhull-warn.pri +set(qhull_VERSION2 "2015.2 2016/01/18") # not used, See global.c, global_r.c, rbox.c, rbox_r.c +set(qhull_VERSION "7.2.0") # Advance every release + +#include(CMakeModules/CheckLFS.cmake) +#option(WITH_LFS "Enable Large File Support" ON) +#check_lfs(WITH_LFS) + + +message(STATUS "qhull Version: ${qhull_VERSION} (static linking)") + + +set(libqhull_HEADERS + # reentrant qhull HEADERS: + src/libqhull_r/libqhull_r.h + src/libqhull_r/geom_r.h + src/libqhull_r/io_r.h + src/libqhull_r/mem_r.h + src/libqhull_r/merge_r.h + src/libqhull_r/poly_r.h + src/libqhull_r/qhull_ra.h + src/libqhull_r/qset_r.h + src/libqhull_r/random_r.h + src/libqhull_r/stat_r.h + src/libqhull_r/user_r.h + + # C++ interface to reentrant Qhull HEADERS: + src/libqhullcpp/Coordinates.h + src/libqhullcpp/functionObjects.h + src/libqhullcpp/PointCoordinates.h + src/libqhullcpp/Qhull.h + src/libqhullcpp/QhullError.h + src/libqhullcpp/QhullFacet.h + src/libqhullcpp/QhullFacetList.h + src/libqhullcpp/QhullFacetSet.h + src/libqhullcpp/QhullHyperplane.h + src/libqhullcpp/QhullIterator.h + src/libqhullcpp/QhullLinkedList.h + src/libqhullcpp/QhullPoint.h + src/libqhullcpp/QhullPoints.h + src/libqhullcpp/QhullPointSet.h + src/libqhullcpp/QhullQh.h + src/libqhullcpp/QhullRidge.h + src/libqhullcpp/QhullSet.h + src/libqhullcpp/QhullSets.h + src/libqhullcpp/QhullStat.h + src/libqhullcpp/QhullVertex.h + src/libqhullcpp/QhullVertexSet.h + src/libqhullcpp/RboxPoints.h + src/libqhullcpp/RoadError.h + src/libqhullcpp/RoadLogEvent.h + src/qhulltest/RoadTest.h +) + +set(libqhull_SOURCES + # reentrant qhull SOURCES: + src/libqhull_r/global_r.c + src/libqhull_r/stat_r.c + src/libqhull_r/geom2_r.c + src/libqhull_r/poly2_r.c + src/libqhull_r/merge_r.c + src/libqhull_r/libqhull_r.c + src/libqhull_r/geom_r.c + src/libqhull_r/poly_r.c + src/libqhull_r/qset_r.c + src/libqhull_r/mem_r.c + src/libqhull_r/random_r.c + src/libqhull_r/usermem_r.c + src/libqhull_r/userprintf_r.c + src/libqhull_r/io_r.c + src/libqhull_r/user_r.c + src/libqhull_r/rboxlib_r.c + src/libqhull_r/userprintf_rbox_r.c + + # C++ interface to reentrant Qhull SOURCES: + src/libqhullcpp/Coordinates.cpp + src/libqhullcpp/PointCoordinates.cpp + src/libqhullcpp/Qhull.cpp + src/libqhullcpp/QhullFacet.cpp + src/libqhullcpp/QhullFacetList.cpp + src/libqhullcpp/QhullFacetSet.cpp + src/libqhullcpp/QhullHyperplane.cpp + src/libqhullcpp/QhullPoint.cpp + src/libqhullcpp/QhullPointSet.cpp + src/libqhullcpp/QhullPoints.cpp + src/libqhullcpp/QhullQh.cpp + src/libqhullcpp/QhullRidge.cpp + src/libqhullcpp/QhullSet.cpp + src/libqhullcpp/QhullStat.cpp + src/libqhullcpp/QhullVertex.cpp + src/libqhullcpp/QhullVertexSet.cpp + src/libqhullcpp/RboxPoints.cpp + src/libqhullcpp/RoadError.cpp + src/libqhullcpp/RoadLogEvent.cpp + + # headers for both (libqhullr and libqhullcpp: + ${libqhull_HEADERS} +) + + +################################################## +# combined library (reentrant qhull and qhullcpp) for Slic3r: +set(qhull_STATIC qhull) +add_library(${qhull_STATIC} STATIC ${libqhull_SOURCES}) +set_target_properties(${qhull_STATIC} PROPERTIES + VERSION ${qhull_VERSION}) + +if(UNIX) + target_link_libraries(${qhull_STATIC} m) +endif(UNIX) +################################################## + +# LIBDIR is defined in the main xs CMake file: +target_include_directories(${qhull_STATIC} PRIVATE ${LIBDIR}/qhull/src) diff --git a/xs/src/qhull/COPYING.txt b/xs/src/qhull/COPYING.txt new file mode 100644 index 0000000000..2895ec6a32 --- /dev/null +++ b/xs/src/qhull/COPYING.txt @@ -0,0 +1,38 @@ + Qhull, Copyright (c) 1993-2015 + + C.B. Barber + Arlington, MA + + and + + The National Science and Technology Research Center for + Computation and Visualization of Geometric Structures + (The Geometry Center) + University of Minnesota + + email: qhull@qhull.org + +This software includes Qhull from C.B. Barber and The Geometry Center. +Qhull is copyrighted as noted above. Qhull is free software and may +be obtained via http from www.qhull.org. It may be freely copied, modified, +and redistributed under the following conditions: + +1. All copyright notices must remain intact in all files. + +2. A copy of this text file must be distributed along with any copies + of Qhull that you redistribute; this includes copies that you have + modified, or copies of programs or other software products that + include Qhull. + +3. If you modify Qhull, you must include a notice giving the + name of the person performing the modification, the date of + modification, and the reason for such modification. + +4. When distributing modified versions of Qhull, or other software + products that include Qhull, you must provide notice that the original + source code may be obtained as noted above. + +5. There is no warranty or other guarantee of fitness for Qhull, it is + provided solely "as is". Bug reports or fixes may be sent to + qhull_bug@qhull.org; the authors may or may not act on them as + they desire. diff --git a/xs/src/qhull/README.txt b/xs/src/qhull/README.txt new file mode 100644 index 0000000000..f4c7a3b220 --- /dev/null +++ b/xs/src/qhull/README.txt @@ -0,0 +1,623 @@ +This distribution of qhull library is only meant for interfacing qhull with Slic3rPE +(https://github.com/prusa3d/Slic3r). + +The qhull source file was acquired from https://github.com/qhull/qhull at revision +f0bd8ceeb84b554d7cdde9bbfae7d3351270478c. + +No changes to the qhull library were made, except for +- setting REALfloat=1 in user_r.h to enforce calculations in floats +- modifying CMakeLists.txt (the original was renamed to origCMakeLists.txt) + +Many thanks to C. Bradford Barber and all contributors. + +Lukas Matena (lukasmatena@seznam.cz) +25.7.2018 + + +See original contents of the README file below. + +====================================================================================== +====================================================================================== +====================================================================================== + + +Name + + qhull, rbox 2015.2 2016/01/18 + +Convex hull, Delaunay triangulation, Voronoi diagrams, Halfspace intersection + + Documentation: + html/index.htm + + + Available from: + + + (git@github.com:qhull/qhull.git) + + News and a paper: + + + + Version 1 (simplicial only): + + +Purpose + + Qhull is a general dimension convex hull program that reads a set + of points from stdin, and outputs the smallest convex set that contains + the points to stdout. It also generates Delaunay triangulations, Voronoi + diagrams, furthest-site Voronoi diagrams, and halfspace intersections + about a point. + + Rbox is a useful tool in generating input for Qhull; it generates + hypercubes, diamonds, cones, circles, simplices, spirals, + lattices, and random points. + + Qhull produces graphical output for Geomview. This helps with + understanding the output. + +Environment requirements + + Qhull and rbox should run on all 32-bit and 64-bit computers. Use + an ANSI C or C++ compiler to compile the program. The software is + self-contained. It comes with examples and test scripts. + + Qhull's C++ interface uses the STL. The C++ test program uses QTestLib + from the Qt Framework. Qhull's C++ interface may change without + notice. Eventually, it will move into the qhull shared library. + + Qhull is copyrighted software. Please read COPYING.txt and REGISTER.txt + before using or distributing Qhull. + +To cite Qhull, please use + + Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., "The Quickhull + algorithm for convex hulls," ACM Trans. on Mathematical Software, + 22(4):469-483, Dec 1996, http://www.qhull.org. + +To modify Qhull, particularly the C++ interface + + Qhull is on GitHub + (http://github.com/qhull/qhull, git@github.com:qhull/qhull.git) + + For internal documentation, see html/qh-code.htm + +To install Qhull + + Qhull is precompiled for Windows 32-bit, otherwise it needs compilation. + + Qhull includes Makefiles for gcc and other targets, CMakeLists.txt for CMake, + .sln/.vcproj/.vcxproj files for Microsoft Visual Studio, and .pro files + for Qt Creator. It compiles under Windows with mingw. + + Install and build instructions follow. + + See the end of this document for a list of distributed files. + +----------------- +Installing Qhull on Windows 10, 8, 7 (32- or 64-bit), Windows XP, and Windows NT + + The zip file contains rbox.exe, qhull.exe, qconvex.exe, qdelaunay.exe, + qhalf.exe, qvoronoi.exe, testqset.exe, user_eg*.exe, documentation files, + and source files. Qhull.exe and user-eg3.exe are compiled with the reentrant + library while the other executables use the non-reentrant library. + + To install Qhull: + - Unzip the files into a directory (e.g., named 'qhull') + - Click on QHULL-GO or open a command window into Qhull's bin directory. + - Test with 'rbox D4 | qhull' + + To uninstall Qhull + - Delete the qhull directory + + To learn about Qhull: + - Execute 'qconvex' for a synopsis and examples. + - Execute 'rbox 10 | qconvex' to compute the convex hull of 10 random points. + - Execute 'rbox 10 | qconvex i TO file' to write results to 'file'. + - Browse the documentation: qhull\html\index.htm + - If an error occurs, Windows sends the error to stdout instead of stderr. + Use 'TO xxx' to send normal output to xxx + + To improve the command window + - Double-click the window bar to increase the size of the window + - Right-click the window bar + - Select Properties + - Check QuickEdit Mode + Select text with right-click or Enter + Paste text with right-click + - Change Font to Lucinda Console + - Change Layout to Screen Buffer Height 999, Window Size Height 55 + - Change Colors to Screen Background White, Screen Text Black + - Click OK + - Select 'Modify shortcut that started this window', then OK + + If you use qhull a lot, install a bash shell such as + MSYS (www.mingw.org/wiki/msys), Road Bash (www.qhull.org/bash), + or Cygwin (www.cygwin.com). + +----------------- +Installing Qhull on Unix with gcc + + To build Qhull, static libraries, shared library, and C++ interface + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - make + - export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH + + The Makefiles may be edited for other compilers. + If 'testqset' exits with an error, qhull is broken + + A simple Makefile for Qhull is in src/libqhull and src/libqhull_r. + To build the Qhull executables and libqhullstatic + - Extract Qhull from qhull...tgz or qhull...zip + - cd src/libqhull_r # cd src/libqhull + - make + + +----------------- +Installing Qhull with CMake 2.6 or later + + See CMakeLists.txt for examples and further build instructions + + To build Qhull, static libraries, shared library, and C++ interface + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - cd build + - cmake --help # List build generators + - make -G "" .. && cmake .. + - cmake .. + - make + - make install + + The ".." is important. It refers to the parent directory (i.e., qhull/) + + On Windows, CMake installs to C:/Program Files/qhull. 64-bit generators + have a "Win64" tag. + + If creating a qhull package, please include a pkg-config file based on build/qhull*.pc.in + + If cmake fails with "No CMAKE_C_COMPILER could be found" + - cmake was not able to find the build environment specified by -G "..." + +----------------- +Installing Qhull with Qt + + To build Qhull, including its C++ test (qhulltest) + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Load src/qhull-all.pro into QtCreator + - Build + +------------------- +Working with Qhull's C++ interface + + See html/qh-code.htm#cpp for calling Qhull from C++ programs + + See html/qh-code.htm#reentrant for converting from Qhull-2012 + + Examples of using the C++ interface + user_eg3_r.cpp + qhulltest/*_test.cpp + + Qhull's C++ interface is likely to change. Stay current with GitHub. + + To clone Qhull's next branch from http://github.com/qhull/qhull + git init + git clone git@github.com:qhull/qhull.git + cd qhull + git checkout next + ... + git pull origin next + + Compile qhullcpp and libqhullstatic_r with the same compiler. Both libraries + use the C routines setjmp() and longjmp() for error handling. They must + be compiled with the same compiler. + +------------------- +Calling Qhull from C programs + + See html/qh-code.htm#library for calling Qhull from C programs + + See html/qh-code.htm#reentrant for converting from Qhull-2012 + + Warning: You will need to understand Qhull's data structures and read the + code. Most users will find it easier to call Qhull as an external command. + + The new, reentrant 'C' code (src/libqhull_r), passes a pointer to qhT + to most Qhull routines. This allows multiple instances of Qhull to run + at the same time. It simplifies the C++ interface. + + The non-reentrant 'C' code (src/libqhull) looks unusual. It refers to + Qhull's global data structure, qhT, through a 'qh' macro (e.g., 'qh ferr'). + This allows the same code to use static memory or heap memory. + If qh_QHpointer is defined, qh_qh is a pointer to an allocated qhT; + otherwise qh_qh is a global static data structure of type qhT. + +------------------ +Compiling Qhull with Microsoft Visual C++ + + To compile 32-bit Qhull with Microsoft Visual C++ 2010 and later + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Load solution build/qhull-32.sln + - Build target 'Win32' + - Project qhulltest requires Qt for DevStudio (http://www.qt.io) + Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/5.2.0/5.2.0/msvc2012) + If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' + + To compile 64-bit Qhull with Microsoft Visual C++ 2010 and later + - 64-bit Qhull has larger data structures due to 64-bit pointers + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Load solution build/qhull-64.sln + - Build target 'Win32' + - Project qhulltest requires Qt for DevStudio (http://www.qt.io) + Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/5.2.0/5.2.0/msvc2012_64) + If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' + + To compile Qhull with Microsoft Visual C++ 2005 (vcproj files) + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Load solution build/qhull.sln + - Build target 'win32' (not 'x64') + - Project qhulltest requires Qt for DevStudio (http://www.qt.io) + Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/4.7.4) + If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' + +----------------- +Compiling Qhull with Qt Creator + + Qt (http://www.qt.io) is a C++ framework for Windows, Linux, and Macintosh + + Qhull uses QTestLib to test qhull's C++ interface (see src/qhulltest/) + + To compile Qhull with Qt Creator + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Download the Qt SDK + - Start Qt Creator + - Load src/qhull-all.pro + - Build + +----------------- +Compiling Qhull with mingw on Windows + + To compile Qhull with MINGW + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Install Road Bash (http://www.qhull.org/bash) + or install MSYS (http://www.mingw.org/wiki/msys) + - Install MINGW-w64 (http://sourceforge.net/projects/mingw-w64). + Mingw is included with Qt SDK. + - make + +----------------- +Compiling Qhull with cygwin on Windows + + To compile Qhull with cygwin + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Install cygwin (http://www.cygwin.com) + - Include packages for gcc, make, ar, and ln + - make + +----------------- +Compiling from Makfile without gcc + + The file, qhull-src.tgz, contains documentation and source files for + qhull and rbox. + + To unpack the tgz file + - tar zxf qhull-src.tgz + - cd qhull + - Use qhull/Makefile + Simpler Makefiles are qhull/src/libqhull/Makefile and qhull/src/libqhull_r/Makefile + + Compiling qhull and rbox with Makefile + - in Makefile, check the CC, CCOPTS1, PRINTMAN, and PRINTC defines + - the defaults are gcc and enscript + - CCOPTS1 should include the ANSI flag. It defines __STDC__ + - in user.h, check the definitions of qh_SECticks and qh_CPUclock. + - use '#define qh_CLOCKtype 2' for timing runs longer than 1 hour + - type: make + - this builds: qhull qconvex qdelaunay qhalf qvoronoi rbox libqhull.a libqhull_r.a + - type: make doc + - this prints the man page + - See also qhull/html/index.htm + - if your compiler reports many errors, it is probably not a ANSI C compiler + - you will need to set the -ansi switch or find another compiler + - if your compiler warns about missing prototypes for fprintf() etc. + - this is ok, your compiler should have these in stdio.h + - if your compiler warns about missing prototypes for memset() etc. + - include memory.h in qhull_a.h + - if your compiler reports "global.c: storage size of 'qh_qh' isn't known" + - delete the initializer "={0}" in global.c, stat.c and mem.c + - if your compiler warns about "stat.c: improper initializer" + - this is ok, the initializer is not used + - if you have trouble building libqhull.a with 'ar' + - try 'make -f Makefile.txt qhullx' + - if the code compiles, the qhull test case will automatically execute + - if an error occurs, there's an incompatibility between machines + - If you can, try a different compiler + - You can turn off the Qhull memory manager with qh_NOmem in mem.h + - You can turn off compiler optimization (-O2 in Makefile) + - If you find the source of the problem, please let us know + - to install the programs and their man pages: + - define MANDIR and BINDIR + - type 'make install' + + - if you have Geomview (www.geomview.org) + - try 'rbox 100 | qconvex G >a' and load 'a' into Geomview + - run 'q_eg' for Geomview examples of Qhull output (see qh-eg.htm) + +------------------ +Compiling on other machines and compilers + + Qhull may compile with Borland C++ 5.0 bcc32. A Makefile is included. + Execute 'cd src/libqhull; make -f Mborland'. If you use the Borland IDE, set + the ANSI option in Options:Project:Compiler:Source:Language-compliance. + + Qhull may compile with Borland C++ 4.02 for Win32 and DOS Power Pack. + Use 'cd src/libqhull; make -f Mborland -D_DPMI'. Qhull 1.0 compiles with + Borland C++ 4.02. For rbox 1.0, use "bcc32 -WX -w- -O2-e -erbox -lc rbox.c". + Use the same options for Qhull 1.0. [D. Zwick] + + If you have troubles with the memory manager, you can turn it off by + defining qh_NOmem in mem.h. + +----------------- +Distributed files + + README.txt // Instructions for installing Qhull + REGISTER.txt // Qhull registration + COPYING.txt // Copyright notice + QHULL-GO.lnk // Windows icon for eg/qhull-go.bat + Announce.txt // Announcement + CMakeLists.txt // CMake build file (2.6 or later) + CMakeModules/CheckLFS.cmake // enables Large File Support in cmake + File_id.diz // Package descriptor + index.htm // Home page + Makefile // Makefile for gcc and other compilers + qhull*.md5sum // md5sum for all files + + bin/* // Qhull executables and dll (.zip only) + build/qhull*.pc.in // pkg-config templates for qhull_r, qhull, and qhull_p + build/qhull-32.sln // 32-bit DevStudio solution and project files (2010 and later) + build/*-32.vcxproj + build/qhull-64.sln // 64-bit DevStudio solution and project files (2010 and later) + build/*-64.vcxproj + build/qhull.sln // DevStudio solution and project files (2005 and 2009) + build/*.vcproj + eg/* // Test scripts and geomview files from q_eg + html/index.htm // Manual + html/qh-faq.htm // Frequently asked questions + html/qh-get.htm // Download page + html/qhull-cpp.xml // C++ style notes as a Road FAQ (www.qhull.org/road) + src/Changes.txt // Change history for Qhull and rbox + src/qhull-all.pro // Qt project + +eg/ + q_eg // shell script for Geomview examples (eg.01.cube) + q_egtest // shell script for Geomview test examples + q_test // shell script to test qhull + q_test-ok.txt // output from q_test + qhulltest-ok.txt // output from qhulltest (Qt only) + make-vcproj.sh // bash shell script to create vcproj and vcxprog files + qhull-zip.sh // bash shell script for distribution files + +rbox consists of (bin, html): + rbox.exe // Win32 executable (.zip only) + rbox.htm // html manual + rbox.man // Unix man page + rbox.txt + +qhull consists of (bin, html): + qconvex.exe // Win32 executables and dlls (.zip download only) + qhull.exe // Built with the reentrant library (about 2% slower) + qdelaunay.exe + qhalf.exe + qvoronoi.exe + qhull_r.dll + qhull-go.bat // command window + qconvex.htm // html manual + qdelaun.htm + qdelau_f.htm + qhalf.htm + qvoronoi.htm + qvoron_f.htm + qh-eg.htm + qh-code.htm + qh-impre.htm + index.htm + qh-opt*.htm + qh-quick.htm + qh--*.gif // images for manual + normal_voronoi_knauss_oesterle.jpg + qhull.man // Unix man page + qhull.txt + +bin/ + msvcr80.dll // Visual C++ redistributable file (.zip download only) + +src/ + qhull/unix.c // Qhull and rbox applications using non-reentrant libqhullstatic.a + rbox/rbox.c + qconvex/qconvex.c + qhalf/qhalf.c + qdelaunay/qdelaunay.c + qvoronoi/qvoronoi.c + + qhull/unix_r.c // Qhull and rbox applications using reentrant libqhullstatic_r.a + rbox/rbox_r.c + qconvex/qconvex_r.c // Qhull applications built with reentrant libqhull_r/Makefile + qhalf/qhalf_r.c + qdelaunay/qdelaun_r.c + qvoronoi/qvoronoi_r.c + + user_eg/user_eg_r.c // example of using qhull_r.dll from a user program + user_eg2/user_eg2_r.c // example of using libqhullstatic_r.a from a user program + user_eg3/user_eg3_r.cpp // example of Qhull's C++ interface libqhullcpp with libqhullstatic_r.a + qhulltest/qhulltest.cpp // Test of Qhull's C++ interface using Qt's QTestLib + qhull-*.pri // Include files for Qt projects + testqset_r/testqset_r.c // Test of reentrant qset_r.c and mem_r.c + testqset/testqset.c // Test of non-rentrant qset.c and mem.c + + +src/libqhull + libqhull.pro // Qt project for non-rentrant, shared library (qhull.dll) + index.htm // design documentation for libqhull + qh-*.htm + qhull-exports.def // Export Definition file for Visual C++ + Makefile // Simple gcc Makefile for qhull and libqhullstatic.a + Mborland // Makefile for Borland C++ 5.0 + + libqhull.h // header file for qhull + user.h // header file of user definable constants + libqhull.c // Quickhull algorithm with partitioning + user.c // user re-definable functions + usermem.c + userprintf.c + userprintf_rbox.c + + qhull_a.h // include files for libqhull/*.c + geom.c // geometric routines + geom2.c + geom.h + global.c // global variables + io.c // input-output routines + io.h + mem.c // memory routines, this is stand-alone code + mem.h + merge.c // merging of non-convex facets + merge.h + poly.c // polyhedron routines + poly2.c + poly.h + qset.c // set routines, this only depends on mem.c + qset.h + random.c // utilities w/ Park & Miller's random number generator + random.h + rboxlib.c // point set generator for rbox + stat.c // statistics + stat.h + +src/libqhull_r + libqhull_r.pro // Qt project for rentrant, shared library (qhull_r.dll) + index.htm // design documentation for libqhull_r + qh-*_r.htm + qhull-exports_r.def // Export Definition file for Visual C++ + Makefile // Simple gcc Makefile for qhull and libqhullstatic.a + + libqhull_r.h // header file for qhull + user_r.h // header file of user definable constants + libqhull_r.c // Quickhull algorithm wi_r.hpartitioning + user_r.c // user re-definable functions + usermem.c + userprintf.c + userprintf_rbox.c + qhull_ra.h // include files for libqhull/*_r.c + geom_r.c // geometric routines + geom2.c + geom_r.h + global_r.c // global variables + io_r.c // input-output routines + io_r.h + mem_r.c // memory routines, this is stand-alone code + mem.h + merge_r.c // merging of non-convex facets + merge.h + poly_r.c // polyhedron routines + poly2.c + poly_r.h + qset_r.c // set routines, this only depends on mem_r.c + qset.h + random_r.c // utilities w/ Park & Miller's random number generator + random.h + rboxlib_r.c // point set generator for rbox + stat_r.c // statistics + stat.h + +src/libqhullcpp/ + libqhullcpp.pro // Qt project for renentrant, static C++ library + Qhull.cpp // Calls libqhull_r.c from C++ + Qhull.h + qt-qhull.cpp // Supporting methods for Qt + + Coordinates.cpp // input classes + Coordinates.h + + PointCoordinates.cpp + PointCoordinates.h + RboxPoints.cpp // call rboxlib.c from C++ + RboxPoints.h + + QhullFacet.cpp // data structure classes + QhullFacet.h + QhullHyperplane.cpp + QhullHyperplane.h + QhullPoint.cpp + QhullPoint.h + QhullQh.cpp + QhullRidge.cpp + QhullRidge.h + QhullVertex.cpp + QhullVertex.h + + QhullFacetList.cpp // collection classes + QhullFacetList.h + QhullFacetSet.cpp + QhullFacetSet.h + QhullIterator.h + QhullLinkedList.h + QhullPoints.cpp + QhullPoints.h + QhullPointSet.cpp + QhullPointSet.h + QhullSet.cpp + QhullSet.h + QhullSets.h + QhullVertexSet.cpp + QhullVertexSet.h + + functionObjects.h // supporting classes + QhullError.cpp + QhullError.h + QhullQh.cpp + QhullQh.h + QhullStat.cpp + QhullStat.h + RoadError.cpp // Supporting base classes + RoadError.h + RoadLogEvent.cpp + RoadLogEvent.h + usermem_r-cpp.cpp // Optional override for qh_exit() to throw an error + +src/libqhullstatic/ + libqhullstatic.pro // Qt project for non-reentrant, static library + +src/libqhullstatic_r/ + libqhullstatic_r.pro // Qt project for reentrant, static library + +src/qhulltest/ + qhulltest.pro // Qt project for test of C++ interface + Coordinates_test.cpp // Test of each class + PointCoordinates_test.cpp + Qhull_test.cpp + QhullFacet_test.cpp + QhullFacetList_test.cpp + QhullFacetSet_test.cpp + QhullHyperplane_test.cpp + QhullLinkedList_test.cpp + QhullPoint_test.cpp + QhullPoints_test.cpp + QhullPointSet_test.cpp + QhullRidge_test.cpp + QhullSet_test.cpp + QhullVertex_test.cpp + QhullVertexSet_test.cpp + RboxPoints_test.cpp + RoadTest.cpp // Run multiple test files with QTestLib + RoadTest.h + +----------------- +Authors: + + C. Bradford Barber Hannu Huhdanpaa (Version 1.0) + bradb@shore.net hannu@qhull.org + + Qhull 1.0 and 2.0 were developed under NSF grants NSF/DMS-8920161 + and NSF-CCR-91-15793 750-7504 at the Geometry Center and Harvard + University. If you find Qhull useful, please let us know. diff --git a/xs/src/qhull/REGISTER.txt b/xs/src/qhull/REGISTER.txt new file mode 100644 index 0000000000..16ccb1a58d --- /dev/null +++ b/xs/src/qhull/REGISTER.txt @@ -0,0 +1,32 @@ +Dear Qhull User + +We would like to find out how you are using our software. Think of +Qhull as a new kind of shareware: you share your science and successes +with us, and we share our software and support with you. + +If you use Qhull, please send us a note telling +us what you are doing with it. + +We need to know: + + (1) What you are working on - an abstract of your work would be + fine. + + (2) How Qhull has helped you, for example, by increasing your + productivity or allowing you to do things you could not do + before. If Qhull had a direct bearing on your work, please + tell us about this. + +We encourage you to cite Qhull in your publications. + +To cite Qhull, please use + + Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., "The Quickhull + algorithm for convex hulls," ACM Trans. on Mathematical Software, + 22(4):469-483, Dec 1996, http://www.qhull.org. + +Please send e-mail to + + bradb@shore.net + +Thank you! diff --git a/xs/src/qhull/html/index.htm b/xs/src/qhull/html/index.htm new file mode 100644 index 0000000000..ca4789b47f --- /dev/null +++ b/xs/src/qhull/html/index.htm @@ -0,0 +1,935 @@ + + + + + + +Qhull manual + + + + + +

Up: Home page for Qhull
+Up:News about Qhull
+Up: FAQ about Qhull
+To: Qhull manual: Table of Contents +(please wait while loading)
+To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
+ +


+ +

[random-fixed] Qhull manual

+ +

Qhull is a general dimension code for computing convex hulls, +Delaunay triangulations, halfspace intersections about a point, Voronoi +diagrams, furthest-site Delaunay triangulations, and +furthest-site Voronoi diagrams. These structures have +applications in science, engineering, statistics, and +mathematics. See Fukuda's +introduction to convex hulls, Delaunay triangulations, +Voronoi diagrams, and linear programming. For a detailed +introduction, see O'Rourke ['94], Computational +Geometry in C. +

+ +

There are six programs. Except for rbox, they use +the same code. Each program includes instructions and examples. +

+
    +
  • qconvex -- convex hulls +
  • qdelaunay -- Delaunay triangulations and + furthest-site Delaunay triangulations +
  • qhalf -- halfspace intersections about a point +
  • qhull -- all structures with additional options +
  • qvoronoi -- Voronoi diagrams and + furthest-site Voronoi diagrams +
  • rbox -- generate point distributions for qhull +
+
+ +

Qhull implements the Quickhull algorithm for computing the +convex hull. Qhull includes options +for hull volume, facet area, multiple output formats, and +graphical output. It can approximate a convex hull.

+ +

Qhull handles roundoff errors from floating point +arithmetic. It generates a convex hull with "thick" facets. +A facet's outer plane is clearly above all of the points; +its inner plane is clearly below the facet's vertices. Any +exact convex hull must lie between the inner and outer plane. + +

Qhull uses merged facets, triangulated output, or joggled +input. Triangulated output triangulates non-simplicial, merged +facets. Joggled input also +guarantees simplicial output, but it +is less accurate than merged facets. For merged facets, Qhull +reports the maximum outer and inner plane. + +

Brad Barber, Arlington, MA

+ +

Copyright © 1995-2015 C.B. Barber

+ +
+ +

»Qhull manual: Table of +Contents

+ + +

»When to use Qhull

+
+ +

Qhull constructs convex hulls, Delaunay triangulations, +halfspace intersections about a point, Voronoi diagrams, furthest-site Delaunay +triangulations, and furthest-site Voronoi diagrams.

+ +

For convex hulls and halfspace intersections, Qhull may be used +for 2-d upto 8-d. For Voronoi diagrams and Delaunay triangulations, Qhull may be +used for 2-d upto 7-d. In higher dimensions, the size of the output +grows rapidly and Qhull does not work well with virtual memory. +If n is the size of +the input and d is the dimension (d>=3), the size of the output +and execution time +grows by n^(floor(d/2) +[see Performance]. For example, do +not try to build a 16-d convex hull of 1000 points. It will +have on the order of 1,000,000,000,000,000,000,000,000 facets. + +

On a 600 MHz Pentium 3, Qhull computes the 2-d convex hull of +300,000 cocircular points in 11 seconds. It computes the +2-d Delaunay triangulation and 3-d convex hull of 120,000 points +in 12 seconds. It computes the +3-d Delaunay triangulation and 4-d convex hull of 40,000 points +in 18 seconds. It computes the +4-d Delaunay triangulation and 5-d convex hull of 6,000 points +in 12 seconds. It computes the +5-d Delaunay triangulation and 6-d convex hull of 1,000 points +in 12 seconds. It computes the +6-d Delaunay triangulation and 7-d convex hull of 300 points +in 15 seconds. It computes the +7-d Delaunay triangulation and 8-d convex hull of 120 points +in 15 seconds. It computes the +8-d Delaunay triangulation and 9-d convex hull of 70 points +in 15 seconds. It computes the +9-d Delaunay triangulation and 10-d convex hull of 50 points +in 17 seconds. The 10-d convex hull of 50 points has about 90,000 facets. + + +

Qhull does not support constrained Delaunay +triangulations, triangulation of non-convex surfaces, mesh +generation of non-convex objects, or medium-sized inputs in 9-D +and higher.

+ +

This is a big package with many options. It is one of the +fastest available. It is the only 3-d code that handles precision +problems due to floating point arithmetic. For example, it +implements the identity function for extreme points (see Imprecision in Qhull).

+ +

[2016] A newly discovered, bad case for Qhull is multiple, nearly incident points within a 10^-13 ball of 3-d and higher +Delaunay triangulations (input sites in the unit cube). Nearly incident points within substantially +smaller or larger balls are OK. Error QH6271 is reported if a problem occurs. A future release of Qhull +will handle this case. For more information, see "Nearly coincident points on an edge" in Limitations of merged facets + +

If you need a short code for convex hull, Delaunay +triangulation, or Voronoi volumes consider Clarkson's hull +program. If you need 2-d Delaunay triangulations consider +Shewchuk's triangle +program. It is much faster than Qhull and it allows +constraints. Both programs use exact arithmetic. They are in http://www.netlib.org/voronoi/. + +

If your input is in general position (i.e., no coplanar or colinear points), +

  • Tomilov's quickhull.hpp () +or Qhull version +1.0 may meet your needs. Both programs detect precision problems, +but do not handle them.

    + +

    CGAL is a library of efficient and reliable +geometric algorithms. It uses C++ templates and the Boost library to produce dimension-specific +code. This allows more efficient use of memory than Qhull's general-dimension +code. CGAL simulates arbitrary precision while Qhull handles round-off error +with thick facets. Compare the two approaches with Robustness Issues in CGAL, +and Imprecision in Qhull. + + +

    Leda is a +library for writing computational +geometry programs and other combinatorial algorithms. It +includes routines for computing 3-d convex +hulls, 2-d Delaunay triangulations, and 3-d Delaunay triangulations. +It provides rational arithmetic and graphical output. It runs on most +platforms. + +

    If your problem is in high dimensions with a few, +non-simplicial facets, try Fukuda's cdd. +It is much faster than Qhull for these distributions.

    + +

    Custom software for 2-d and 3-d convex hulls may be faster +than Qhull. Custom software should use less memory. Qhull uses +general-dimension data structures and code. The data structures +support non-simplicial facets.

    + +

    Qhull is not suitable for mesh generation or triangulation of +arbitrary surfaces. You may use Qhull if the surface is convex or +completely visible from an interior point (e.g., a star-shaped +polyhedron). First, project each site to a sphere that is +centered at the interior point. Then, compute the convex hull of +the projected sites. The facets of the convex hull correspond to +a triangulation of the surface. For mesh generation of arbitrary +surfaces, see Schneiders' +Finite Element Mesh Generation.

    + +

    Qhull is not suitable for constrained Delaunay triangulations. +With a lot of work, you can write a program that uses Qhull to +add constraints by adding additional points to the triangulation.

    + +

    Qhull is not suitable for the subdivision of arbitrary +objects. Use qdelaunay to subdivide a convex object.

    + +
  • +

    »Description of +Qhull

    +
    + +

    »definition

    +
    + +

    The convex hull of a point set P is the smallest +convex set that contains P. If P is finite, the +convex hull defines a matrix A and a vector b such +that for all x in P, Ax+b <= [0,...].

    + +

    Qhull computes the convex hull in 2-d, 3-d, 4-d, and higher +dimensions. Qhull represents a convex hull as a list of facets. +Each facet has a set of vertices, a set of neighboring facets, +and a halfspace. A halfspace is defined by a unit normal and an +offset (i.e., a row of A and an element of b).

    + +

    Qhull accounts for round-off error. It returns +"thick" facets defined by two parallel hyperplanes. The +outer planes contain all input points. The inner planes exclude +all output vertices. See Imprecise +convex hulls.

    + +

    Qhull may be used for the Delaunay triangulation or the +Voronoi diagram of a set of points. It may be used for the +intersection of halfspaces.

    + +
    +

    »input format

    +
    + +

    The input data on stdin consists of:

    + +
      +
    • first line contains the dimension
    • +
    • second line contains the number of input points
    • +
    • remaining lines contain point coordinates
    • +
    + +

    For example:

    + +
    +    3  #sample 3-d input
    +    5
    +    0.4 -0.5 1.0
    +    1000 -1e-5 -100
    +    0.3 0.2 0.1
    +    1.0 1.0 1.0
    +    0 0 0
    +
    + +

    Input may be entered by hand. End the input with a control-D +(^D) character.

    + +

    To input data from a file, use I/O redirection or 'TI file'. The filename may not +include spaces or quotes.

    + +

    A comment starts with a non-numeric character and continues to +the end of line. The first comment is reported in summaries and +statistics. With multiple qhull commands, use option 'FQ' to place a comment in the output.

    + +

    The dimension and number of points can be reversed. Comments +and line breaks are ignored. Error reporting is better if there +is one point per line.

    + +
    +

    »option format

    +
    + +

    Use options to specify the output formats and control +Qhull. The qhull program takes all options. The +other programs use a subset of the options. They disallow +experimental and inappropriate options. + +

    +
      +
    • +qconvex == qhull +
    • +qdelaunay == qhull d Qbb +
    • +qhalf == qhull H +
    • +qvoronoi == qhull v Qbb +
    +
    + +

    Single letters are used for output formats and precision +constants. The other options are grouped into menus for formats +('F'), Geomview ('G '), printing ('P'), Qhull control ('Q '), and tracing ('T'). The menu options may be listed +together (e.g., 'GrD3' for 'Gr' and 'GD3'). Options may be in any +order. Capitalized options take a numeric argument (except for 'PG' and 'F' +options). Use option 'FO' to print +the selected options.

    + +

    Qhull uses zero-relative indexing. If there are n +points, the index of the first point is 0 and the index of +the last point is n-1.

    + +

    The default options are:

    + +
      +
    • summary output ('s')
    • +
    • merged facets ('C-0' in 2-d, + 3-d, 4-d; 'Qx' in 5-d and + up)
    • +
    + +

    Except for bounding box +('Qbk:n', etc.), drop facets +('Pdk:n', etc.), and +Qhull command ('FQ'), only the last +occurence of an option counts. +Bounding box and drop facets may be repeated for each dimension. +Option 'FQ' may be repeated any number of times. + +

    The Unix tcsh and ksh shells make it easy to +try out different options. In Windows 95, use a command window with doskey +and a window scroller (e.g., peruse).

    + +
    +

    »output format

    +
    + +

    To write the results to a file, use I/O redirection or 'TO file'. Windows 95 users should use +'TO file' or the console. If a filename is surrounded by single quotes, +it may include spaces. +

    + +

    The default output option is a short summary ('s') to stdout. There are many +others (see output and formats). You can list vertex incidences, +vertices and facets, vertex coordinates, or facet normals. You +can view Qhull objects with Geomview, Mathematica, or Maple. You can +print the internal data structures. You can call Qhull from your +application (see Qhull library).

    + +

    For example, 'qhull o' lists the +vertices and facets of the convex hull.

    + +

    Error messages and additional summaries ('s') go to stderr. Unless +redirected, stderr is the console.

    + +
    +

    »algorithm

    +
    + +

    Qhull implements the Quickhull algorithm for convex hull +[Barber et al. '96]. This algorithm +combines the 2-d Quickhull algorithm with the n-d +beneath-beyond algorithm [c.f., Preparata & Shamos '85]. It is similar to the randomized +algorithms of Clarkson and others [Clarkson & Shor '89; Clarkson et al. '93; +Mulmuley '94]. For a demonstration, see How Qhull adds a point. The main +advantages of Quickhull are output sensitive performance (in +terms of the number of extreme points), reduced space +requirements, and floating-point error handling.

    + +
    +

    »data structures

    +
    + +

    Qhull produces the following data structures for dimension d: +

    + +
      +
    • A coordinate is a real number in floating point + format.
    • +
    • A point is an array of d coordinates. + With option 'QJ', the + coordinates are joggled by a small amount.
    • +
    • A vertex is an input point.
    • +
    • A hyperplane is d normal coefficients and + an offset. The length of the normal is one. The + hyperplane defines a halfspace. If V is a normal, b + is an offset, and x is a point inside the convex + hull, then Vx+b <0.
    • +
    • An outer plane is a positive + offset from a hyperplane. When Qhull is done, all points + will be below all outer planes.
    • +
    • An inner plane is a negative + offset from a hyperplane. When Qhull is done, all + vertices will be above the corresponding inner planes.
    • +
    • An orientation is either 'top' or 'bottom'. It is the + topological equivalent of a hyperplane's geometric + orientation.
    • +
    • A simplicial facet is a set of + d neighboring facets, a set of d vertices, a + hyperplane equation, an inner plane, an outer plane, and + an orientation. For example in 3-d, a simplicial facet is + a triangle.
    • +
    • A centrum is a point on a facet's hyperplane. A + centrum is the average of a facet's vertices. Neighboring + facets are convex if each centrum is below the + neighbor facet's hyperplane.
    • +
    • A ridge is a set of d-1 vertices, two + neighboring facets, and an orientation. For example in + 3-d, a ridge is a line segment.
    • +
    • A non-simplicial facet is a set of ridges, a + hyperplane equation, a centrum, an outer plane, and an + inner plane. The ridges determine a set of neighboring + facets, a set of vertices, and an orientation. Qhull + produces a non-simplicial facet when it merges two facets + together. For example, a cube has six non-simplicial + facets.
    • +
    + +

    For examples, use option 'f'. See polyhedron operations for further +design documentation.

    + +
    +

    »Imprecision in Qhull

    +
    + +

    See Imprecision in Qhull and Merged facets or joggled input

    + +
    +

    »Examples of Qhull

    +
    + +

    See Examples of Qhull. Most of these examples require Geomview. +Some of the examples have pictures +.

    + +
    +
    +

    »Options for using Qhull

    +
    + +

    See Options.

    + +
    +

    »Qhull internals

    +
    + +

    See Internals.

    + +
    +

    »Geomview, Qhull's +graphical viewer

    +
    + +

    Geomview +is an interactive geometry viewing program. +Geomview provides a good visualization of Qhull's 2-d and 3-d results. + +

    Qhull includes Examples of Qhull that may be viewed with Geomview. + +

    Geomview can help visulalize a 3-d Delaunay triangulation or the surface of a 4-d convex hull, +Use option 'QVn' to select the 3-D facets adjacent to a vertex. + +

    You may use Geomview to create movies that animate your objects (c.f., How can I create a video animation?). +Geomview helped create the mathematical videos "Not Knot", "Outside In", and "The Shape of Space" from the Geometry Center. + + +

    »Installing Geomview

    +
    + +

    Geomview is an open source project +under SourceForge. + +

    +For build instructions see +Downloading Geomview. +Geomview builds under Linux, Unix, Macintosh OS X, and Windows. + +

    Geomview has installable packages for Debian and Ubuntu. +The OS X build needs Xcode, an X11 SDK, and Lesstif or Motif. +The Windows build uses Cygwin (see Building Geomview below for instructions). + +

    If using Xforms (e.g., for Geomview's External Modules), install the 'libXpm-devel' package from cygwin and move the xforms directory into your geomview directory, e.g.,
    mv xforms-1.2.4 geomview-1.9.5/xforms + +

    Geomview's ndview provides multiple views into 4-d and higher objects. +This module is out-of-date (geomview-users: 4dview). +Download NDview-sgi.tar.Z at newpieces and 4dview at Geomview/modules. + +

    +

    »Using Geomview

    +
    + +

    Use Geomview to view Examples of Qhull. You can spin the convex hull, fly a camera through its facets, +and see how Qhull produces thick facets in response to round-off error. + +

    Follow these instructions to view 'eg,01.cube' from Examples of Qhull +

      +
    1. Launch an XTerm command shell +
        +
      • If needed, start the X terminal server, Use 'xinit' or 'startx' in /usr/X11R6/bin
        xinit -- -multiwindow -clipboard
        startx +
      • Start an XTerm command shell. In Windows, click the Cygwin/bash icon on your desktop. +
      • Set the DISPLAY variable, e.g.,
        export DISPLAY=:0
        export DISPLAY=:0 >>~/.bashenv +
      +
    2. Use Qhull's Geomview options to create a geomview object +
        +
      • rbox c D3 | qconvex G >eg.01.cube +
      • On windows, convert the output to Unix text format with 'd2u'
        rbox c D3 | qconvex G | d2u >eg.01.cube
        d2u eg.* +
      +
    3. Run Geomview +
        +
      • Start Geomview with your example
        ./geomview eg.01.cube +
      • Follow the instructions in Gemoview Tutorial +
      • Geomview creates the Geomview control panel with Targets and External Module, the Geomview toolbar with buttons for controlling Geomview, and the Geomview camera window showing a cube. +
      • Clear the camera window by selecting your object in the Targets list and 'Edit > Delete' or 'dd' +
      • Load the Geomview files panel. Select 'Open' in the 'File' menu. +
      • Set 'Filter' in the files panel to your example directory followed by '/*' (e.g., '/usr/local/qhull-2015.2/eg/*') +
      • Click 'Filter' in the files panel to view your examples in the 'Files' list. +
      • Load another example into the camera window by selecting it and clicking 'OK'. +
      • Review the instructions for Interacting with Geomview +
      • When viewing multiple objects at once, you may want to turn off normalization. In the 'Inspect > Apperance' control panel, set 'Normalize' to 'None'. +
      +
    + +

    Geomview defines GCL (a textual API for controlling Geomview) and OOGL (a textual file format for defining objects). +

      +
    • To control Geomview, you may use any program that reads and writes from stdin and stdout. For example, it could report Qhull's information about a vertex identified by a double-click 'pick' event. +
    • GCL command language for controlling Geomview +
    • OOGL file format for defining objects (tutorial). +
    • External Modules for interacting with Geomview via GCL +
    • Interact with your objects via pick commands in response to right-mouse double clicks. Enable pick events with the interest command. +
    + +
    +

    »Building Geomview for Windows

    +
    + +

    Compile Geomview under Cygwin. For detailed instructions, see +Building Savi and Geomview under Windows. These instructions are somewhat out-of-date. Updated +instructions follow. + +

    How to compile Geomview under 32-bit Cygwin (October 2015)

    +
      +
    1. Note: L. Wood has run into multiple issues with Geomview on Cygwin. He recommends Virtualbox/Ubuntu +and a one-click install of geomview via the Ubuntu package. See his Savi/Geomview link above. +
    2. Install 32-bit Cygwin as follows. +For additional guidance, see Cygwin's Installing and Updating Cygwin Packages +and Setup cygwin. +
        +
      • Launch the cygwin installer. +
      • Select a mirror from Cygwin mirrors (e.g., http://mirrors.kernel.org/sourceware/cygwin/ in California). +
      • Select the packages to install. Besides the cygwin packages listed in the Savi/Windows instructions consider adding +
          +
        • Default -- libXm-devel (required for /usr/include/Xm/Xm.h) +
        • Devel -- bashdb, gcc-core (in place of gcc), gdb +
        • Lib -- libGL-devel, libGLU1 (required, obsolete), libGLU-devel (required, obsolete), libjpeg-devel(XForms), libXext-devel (required), libXpm-devel (Xforms) +libGL and lib +
        • Math -- bc +
        • Net -- autossh, inetutils, openssh +
        • System -- chere +
        • Utils -- dos2unix (required for qhull), keychain +
        • If installing perl, ActiveState Perl may be a better choice than cygwin's perl. Perl is not used by Geomview or Qhull. +
        • Cygwin Package Search -- Search for cygwin programs and packages +
        +
      • Click 'Next' to download and install the packages. +
      • If the download is incomplete, try again. +
      • If you try again after a successful install, cygwin will uninstall and reinstall all modules.. +
      • Click on the 'Cywin Terminal' icon on the Desktop. It sets up a user directory in /home from /etc/skel/... +
      • Mount your disk drives
        mount c: /c # Ignore the warning /c does not exist +
      +
    3. Consider installing the Road Bash scripts (/etc/road-*) from Road. +They define aliases and functions for Unix command shells (Unix, Linux, Mac OS X, Windows), +
        +
      • Download Road Bash and unzip the downloaded file +
      • Copy .../bash/etc/road-* to the Cywin /etc directory (by default, C:\cygwin\etc). +
      • Using the cygwin terminal, convert the road scripts to Unix format
        d2u /etc/road-* +
      • Try it
        source /etc/road-home.bashrc +
      • Install it
        cp /etc/road-home.bashrc ~/.bashrc +
      +
    4. Launch the X terminal server from 'Start > All programs > Cygwin-X > Xwin Server'. Alternatively, run 'startx' +
    5. Launch an XTerm shell +
        +
      • Right click the Cywin icon on the system tray in the Windows taskbar. +
      • Select 'System Tools > XTerm' +
      +
    6. Download and extract Geomview -- Downloading Geomview +
    7. Compile Geomview +
        +
      • ./configure +
      • make +
      +
    8. If './configure' fails, check 'config.log' at the failing step. Look carefully for missing libraries, etc. The Geomview FAQ contains suggestions (e.g., "configure claims it can't find OpenGl"). +
    9. If 'make' fails, read the output carefully for error messages. Usually it is a missing include file or package. Locate and install the missing cygwin packages +(Cygwin Package Search). +
    + +
    +
    +

    »What to do if something +goes wrong

    +
    + +

    Please report bugs to qhull_bug@qhull.org +. Please report if Qhull crashes. Please report if Qhull +generates an "internal error". Please report if Qhull +produces a poor approximate hull in 2-d, 3-d or 4-d. Please +report documentation errors. Please report missing or incorrect +links.

    + +

    If you do not understand something, try a small example. The rbox program is an easy way to generate +test cases. The Geomview program helps to +visualize the output from Qhull.

    + +

    If Qhull does not compile, it is due to an incompatibility +between your system and ours. The first thing to check is that +your compiler is ANSI standard. Qhull produces a compiler error +if __STDC__ is not defined. You may need to set a flag (e.g., +'-A' or '-ansi').

    + +

    If Qhull compiles but crashes on the test case (rbox D4), +there's still incompatibility between your system and ours. +Sometimes it is due to memory management. This can be turned off +with qh_NOmem in mem.h. Please let us know if you figure out how +to fix these problems.

    + +

    If you doubt the output from Qhull, add option 'Tv'. It checks that every point is +inside the outer planes of the convex hull. It checks that every +facet is convex with its neighbors. It checks the topology of the +convex hull.

    + +

    Qhull should work on all inputs. It may report precision +errors if you turn off merged facets with option 'Q0'. This can get as bad as facets with +flipped orientation or two facets with the same vertices. You'll +get a long help message if you run into such a case. They are +easy to generate with rbox.

    + +

    If you do find a problem, try to simplify it before reporting +the error. Try different size inputs to locate the smallest one +that causes an error. You're welcome to hunt through the code +using the execution trace ('T4') as +a guide. This is especially true if you're incorporating Qhull +into your own program.

    + +

    When you report an error, please attach a data set to the end +of your message. Include the options that you used with Qhull, +the results of option 'FO', and any +messages generated by Qhull. This allows me to see the error for +myself. Qhull is maintained part-time.

    + +
    +

    »Email

    +
    + +

    Please send correspondence to Brad Barber at qhull@qhull.org +and report bugs to qhull_bug@qhull.org +. Let me know how you use Qhull. If you mention it in a +paper, please send a reference and abstract.

    + +

    If you would like to get Qhull announcements (e.g., a new +version) and news (any bugs that get fixed, etc.), let us know +and we will add you to our mailing list. For Internet news about geometric algorithms +and convex hulls, look at comp.graphics.algorithms and +sci.math.num-analysis. For Qhull news look at qhull-news.html.

    + +
    +

    »Authors

    +
    + +
    +   C. Bradford Barber                    Hannu Huhdanpaa
    +   bradb@shore.net                       hannu@qhull.org
    +
    + +
    +

    »Acknowledgments

    +
    + +

    A special thanks to David Dobkin for his guidance. A special +thanks to Albert Marden, Victor Milenkovic, the Geometry Center, +and Harvard University for supporting this work.

    + +

    A special thanks to Mark Phillips, Robert Miner, and Stuart Levy for running the Geometry + Center web site long after the Geometry Center closed. + Stuart moved the web site to the University of Illinois at Champaign-Urbana. +Mark and Robert are founders of Geometry Technologies. +Mark, Stuart, and Tamara Munzner are the original authors of Geomview. + +

    A special thanks to Endocardial +Solutions, Inc. of St. Paul, Minnesota for their support of the +internal documentation (src/libqhull/index.htm). They use Qhull to build 3-d models of +heart chambers.

    + +

    Qhull 1.0 and 2.0 were developed under National Science Foundation +grants NSF/DMS-8920161 and NSF-CCR-91-15793 750-7504. If you find +it useful, please let us know.

    + +

    The Geometry Center was supported by grant DMS-8920161 from the +National Science Foundation, by grant DOE/DE-FG02-92ER25137 from +the Department of Energy, by the University of Minnesota, and by +Minnesota Technology, Inc.

    + +
    +

    »References

    +
    + +

    Aurenhammer, F., "Voronoi diagrams +-- A survey of a fundamental geometric data structure," ACM +Computing Surveys, 1991, 23:345-405.

    + +

    Barber, C. B., D.P. Dobkin, and H.T. +Huhdanpaa, "The Quickhull Algorithm for Convex Hulls," ACM +Transactions on Mathematical Software, 22(4):469-483, Dec 1996, www.qhull.org +[http://portal.acm.org; +http://citeseerx.ist.psu.edu]. +

    + +

    Clarkson, K.L. and P.W. Shor, +"Applications of random sampling in computational geometry, +II", Discrete Computational Geometry, 4:387-421, 1989

    + +

    Clarkson, K.L., K. Mehlhorn, and R. +Seidel, "Four results on randomized incremental +construction," Computational Geometry: Theory and +Applications, vol. 3, p. 185-211, 1993.

    + +

    Devillers, et. al., +"Walking in a triangulation," ACM Symposium on +Computational Geometry, June 3-5,2001, Medford MA. + +

    Dobkin, D.P. and D.G. Kirkpatrick, +"Determining the separation of preprocessed polyhedra--a +unified approach," in Proc. 17th Inter. Colloq. Automata +Lang. Program., in Lecture Notes in Computer Science, +Springer-Verlag, 443:400-413, 1990.

    + +

    Edelsbrunner, H, Geometry and Topology for Mesh Generation, +Cambridge University Press, 2001. + +

    Gartner, B., "Fast and robust smallest enclosing balls", Algorithms - ESA '99, LNCS 1643. + +

    Golub, G.H. and van Loan, C.F., Matric Computations, Baltimore, Maryland, USA: John Hopkins Press, 1983 + +

    Fortune, S., "Computational +geometry," in R. Martin, editor, Directions in Geometric +Computation, Information Geometers, 47 Stockers Avenue, +Winchester, SO22 5LB, UK, ISBN 1-874728-02-X, 1993.

    + +

    Milenkovic, V., "Robust polygon +modeling," Computer-Aided Design, vol. 25, p. 546-566, +September 1993.

    + +

    Mucke, E.P., I. Saias, B. Zhu, Fast +randomized point location without preprocessing in Two- and +Three-dimensional Delaunay Triangulations, ACM Symposium on +Computational Geometry, p. 274-283, 1996 [GeomDir]. +

    + +

    Mulmuley, K., Computational Geometry, +An Introduction Through Randomized Algorithms, Prentice-Hall, +NJ, 1994.

    + +

    O'Rourke, J., Computational Geometry +in C, Cambridge University Press, 1994.

    + +

    Preparata, F. and M. Shamos, Computational +Geometry, Springer-Verlag, New York, 1985.

    + +
    + +
    + +

    Up: Home page for Qhull
    +Up:News about Qhull
    +Up: FAQ about Qhull
    +To: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Dn: Imprecision in Qhull
    +Dn: Description of Qhull examples
    +Dn: Qhull internals
    +Dn: Qhull functions, macros, and data +structures + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/normal_voronoi_knauss_oesterle.jpg b/xs/src/qhull/html/normal_voronoi_knauss_oesterle.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f46d421274edd97de13d25e306985c43e872165b GIT binary patch literal 23924 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 RpbH2+s%0FGD58Kr|Jfm)sf+*s literal 0 HcmV?d00001 diff --git a/xs/src/qhull/html/qconvex.htm b/xs/src/qhull/html/qconvex.htm new file mode 100644 index 0000000000..38a363b082 --- /dev/null +++ b/xs/src/qhull/html/qconvex.htm @@ -0,0 +1,630 @@ + + + + +qconvex -- convex hull + + + + +Up: +Home page for Qhull
    +Up: Qhull manual -- Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options +
    + +

    [cone]qconvex -- convex hull

    + +

    The convex hull of a set of points is the smallest convex set +containing the points. See the detailed introduction by O'Rourke +['94]. See Description of Qhull and How Qhull adds a point.

    + +
    +
    +
    Example: rbox 10 D3 | qconvex s o TO result
    +
    Compute the 3-d convex hull of 10 random points. Write a + summary to the console and the points and facets to + 'result'.
    + +
     
    +
    Example: rbox c | qconvex n
    +
    Print the normals for each facet of a cube.
    +
     
    +
    Example: rbox c | qconvex i Qt
    +
    Print the triangulated facets of a cube.
    +
     
    +
    Example: rbox y 500 W0 | qconvex
    +
    Compute the convex hull of a simplex with 500 + points on its surface.
    +
     
    +
    Example: rbox x W1e-12 1000 | qconvex + QR0
    +
    Compute the convex hull of 1000 points near the + surface of a randomly rotated simplex. Report + the maximum thickness of a facet.
    +
     
    +
    Example: rbox 1000 s | qconvex s FA
    +
    Compute the convex hull of 1000 cospherical + points. Verify the results and print a summary + with the total area and volume.
    +
     
    +
    Example: rbox d D12 | qconvex QR0 FA
    +
    Compute the convex hull of a 12-d diamond. + Randomly rotate the input. Note the large number + of facets and the small volume.
    +
     
    +
    Example: rbox c D7 | qconvex FA TF1000
    +
    Compute the convex hull of the 7-d hypercube. + Report on progress every 1000 facets. Computing + the convex hull of the 9-d hypercube takes too + much time and space.
    +
     
    +
    Example: rbox c d D2 | qconvex Qc s f Fx | more
    +
    Dump all fields of all facets for a square and a + diamond. Also print a summary and a list of + vertices. Note the coplanar points.
    +
     
    +
    +
    + +

    Except for rbox, all of the qhull programs compute a convex hull. + +

    By default, Qhull merges coplanar facets. For example, the convex +hull of a cube's vertices has six facets. + +

    If you use 'Qt' (triangulated output), +all facets will be simplicial (e.g., triangles in 2-d). For the cube +example, it will have 12 facets. Some facets may be +degenerate and have zero area. + +

    If you use 'QJ' (joggled input), +all facets will be simplicial. The corresponding vertices will be +slightly perturbed and identical points will be joggled apart. +Joggled input is less accurate that triangulated +output.See Merged facets or joggled input.

    + +

    The output for 4-d convex hulls may be confusing if the convex +hull contains non-simplicial facets (e.g., a hypercube). See +Why +are there extra points in a 4-d or higher convex hull?
    +

    +

    + +

    The 'qconvex' program is equivalent to +'qhull' in 2-d to 4-d, and +'qhull Qx' +in 5-d and higher. It disables the following Qhull +options: d v H Qbb Qf Qg Qm +Qr Qu Qv Qx Qz TR E V Fp Gt Q0,etc. + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »qconvex synopsis

    +
    +qconvex- compute the convex hull.
    +    input (stdin): dimension, number of points, point coordinates
    +    comments start with a non-numeric character
    +
    +options (qconvex.htm):
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Tv   - verify result: structure, convexity, and point inclusion
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +output options (subset):
    +    s    - summary of results (default)
    +    i    - vertices incident to each facet
    +    n    - normals with offsets
    +    p    - vertex coordinates (includes coplanar points if 'Qc')
    +    Fx   - extreme points (convex hull vertices)
    +    FA   - compute total area and volume
    +    o    - OFF format (dim, n, points, facets)
    +    G    - Geomview output (2-d, 3-d, and 4-d)
    +    m    - Mathematica output (2-d and 3-d)
    +    QVn  - print facets that include point n, -n if not
    +    TO file- output results to file, may be enclosed in single quotes
    +
    +examples:
    +    rbox c D2 | qconvex s n                    rbox c D2 | qconvex i
    +    rbox c D2 | qconvex o                      rbox 1000 s | qconvex s Tv FA
    +    rbox c d D2 | qconvex s Qc Fx              rbox y 1000 W0 | qconvex s n
    +    rbox y 1000 W0 | qconvex s QJ              rbox d G1 D12 | qconvex QR0 FA Pp
    +    rbox c D7 | qconvex FA TF1000
    +
    + +

    »qconvex +input

    +
    + +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qconvex < data.txt), a pipe (e.g., rbox 10 | qconvex), +or the 'TI' option (e.g., qconvex TI data.txt). + +

    Comments start with a non-numeric character. Error reporting is +simpler if there is one point per line. Dimension +and number of points may be reversed. + +

    Here is the input for computing the convex +hull of the unit cube. The output is the normals, one +per facet.

    + +
    +

    rbox c > data

    +
    +3 RBOX c
    +8
    +  -0.5   -0.5   -0.5
    +  -0.5   -0.5    0.5
    +  -0.5    0.5   -0.5
    +  -0.5    0.5    0.5
    +   0.5   -0.5   -0.5
    +   0.5   -0.5    0.5
    +   0.5    0.5   -0.5
    +   0.5    0.5    0.5
    +
    +

    qconvex s n < data

    +
    +
    +Convex hull of 8 points in 3-d:
    +
    +  Number of vertices: 8
    +  Number of facets: 6
    +  Number of non-simplicial facets: 6
    +
    +Statistics for: RBOX c | QCONVEX s n
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 11
    +  Number of distance tests for qhull: 35
    +  Number of merged facets: 6
    +  Number of distance tests for merging: 84
    +  CPU seconds to compute hull (after input): 0.081
    +
    +4
    +6
    +     0      0     -1   -0.5
    +     0     -1      0   -0.5
    +     1      0      0   -0.5
    +    -1      0      0   -0.5
    +     0      1      0   -0.5
    +     0      0      1   -0.5
    +
    +
    + +
    +

    »qconvex outputs

    +
    + +

    These options control the output of qconvex. They may be used +individually or together.

    +
    +
    +
     
    +
    Vertices
    +
    Fx
    +
    list extreme points (i.e., vertices). The first line is the number of + extreme points. Each point is listed, one per line. The cube example + has eight vertices.
    +
    Fv
    +
    list vertices for each facet. The first line is the number of facets. + Each remaining line starts with the number of vertices. For the cube example, + each facet has four vertices.
    +
    i
    +
    list vertices for each facet. The first line is the number of facets. The + remaining lines list the vertices for each facet. In 4-d and + higher, triangulate non-simplicial facets by adding an extra point.
    +
     
    +
     
    +
    Coordinates
    +
    o
    +
    print vertices and facets of the convex hull in OFF format. The + first line is the dimension. The second line is the number of + vertices, facets, and ridges. The vertex + coordinates are next, followed by the facets. Each facet starts with + the number of vertices. The cube example has four vertices per facet.
    +
    Ft
    +
    print a triangulation of the convex hull in OFF format. The first line + is the dimension. The second line is the number of vertices and added points, + followed by the number of facets and the number of ridges. + The vertex coordinates are next, followed by the centrum coordinates. There is + one centrum for each non-simplicial facet. + The cube example has six centrums, one per square. + Each facet starts with the number of vertices or centrums. + In the cube example, each facet uses two vertices and one centrum.
    +
    p
    +
    print vertex coordinates. The first line is the dimension and the second + line is the number of vertices. The following lines are the coordinates of each + vertex. The cube example has eight vertices.
    +
    Qc p
    +
    print coordinates of vertices and coplanar points. The first line is the dimension. + The second line is the number of vertices and coplanar points. The coordinates + are next, one line per point. Use 'Qc Qi p' + to print the coordinates of all points.
    +
     
    +
     
    +
    Facets
    +
    Fn
    +
    list neighboring facets for each facet. The first line is the + number of facets. Each remaining line starts with the number of + neighboring facets. The cube example has four neighbors per facet.
    +
    FN
    +
    list neighboring facets for each point. The first line is the + total number of points. Each remaining line starts with the number of + neighboring facets. Each vertex of the cube example has three neighboring + facets. Use 'Qc Qi FN' + to include coplanar and interior points.
    +
    Fa
    +
    print area for each facet. The first line is the number of facets. + Facet area follows, one line per facet. For the cube example, each facet has area one.
    +
    FI
    +
    list facet IDs. The first line is the number of + facets. The IDs follow, one per line.
    + +
     
    +
     
    +
    Coplanar and interior points
    +
    Fc
    +
    list coplanar points for each facet. The first line is the number + of facets. The remaining lines start with the number of coplanar points. + A coplanar point is assigned to one facet.
    +
    Qi Fc
    +
    list interior points for each facet. The first line is the number + of facets. The remaining lines start with the number of interior points. + A coplanar point is assigned to one facet.
    +
    FP
    +
    print distance to nearest vertex for coplanar points. The first line is the + number of coplanar points. Each remaining line starts with the point ID of + a vertex, followed by the point ID of a coplanar point, its facet, and distance. + Use 'Qc Qi + FP' for coplanar and interior points.
    + +
     
    +
     
    +
    Hyperplanes
    +
    n
    +
    print hyperplane for each facet. The first line is the dimension. The + second line is the number of facets. Each remaining line is the hyperplane's + coefficients followed by its offset.
    +
    Fo
    +
    print outer plane for each facet. The output plane is above all points. + The first line is the dimension. The + second line is the number of facets. Each remaining line is the outer plane's + coefficients followed by its offset.
    +
    Fi
    +
    print inner plane for each facet. The inner plane of a facet is + below its vertices. + The first line is the dimension. The + second line is the number of facets. Each remaining line is the inner plane's + coefficients followed by its offset.
    + +
     
    +
     
    +
    General
    +
    s
    +
    print summary for the convex hull. Use 'Fs' and 'FS' if you need numeric data.
    +
    FA
    +
    compute total area and volume for 's' and 'FS'
    +
    m
    +
    Mathematica output for the convex hull in 2-d or 3-d.
    +
    FM
    +
    Maple output for the convex hull in 2-d or 3-d.
    +
    G
    +
    Geomview output for the convex hull in 2-d, 3-d, or 4-d.
    + +
     
    +
     
    +
    Scaling and rotation
    +
    Qbk:n
    +
    scale k'th coordinate to lower bound.
    +
    QBk:n
    +
    scale k'th coordinate to upper bound.
    +
    QbB
    +
    scale input to unit cube centered at the origin.
    +
    QRn
    +
    randomly rotate the input with a random seed of n. If n=0, the + seed is the time. If n=-1, use time for the random seed, but do + not rotate the input.
    +
    Qbk:0Bk:0
    +
    remove k'th coordinate from input. This computes the + convex hull in one lower dimension.
    +
    +
    + +
    +

    »qconvex controls

    +
    + +

    These options provide additional control:

    + +
    +
    +
    Qt
    +
    triangulated output. Qhull triangulates non-simplicial facets. It may produce + degenerate facets of zero area.
    +
    QJ
    +
    joggle the input instead of merging facets. This guarantees simplicial facets + (e.g., triangles in 3-d). It is less accurate than triangulated output ('Qt').
    +
    Qc
    +
    keep coplanar points
    +
    Qi
    +
    keep interior points
    +
    f
    +
    facet dump. Print the data structure for each facet.
    +
    QVn
    +
    select facets containing point n as a vertex,
    +
    QGn
    +
    select facets that are visible from point n + (marked 'good'). Use -n for the remainder.
    +
    PDk:0
    +
    select facets with a negative coordinate for dimension k
    +
    TFn
    +
    report progress after constructing n facets
    +
    Tv
    +
    verify result
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    Qs
    +
    search all points for the initial simplex. If Qhull can + not construct an initial simplex, it reports a +descriptive message. Usually, the point set is degenerate and one +or more dimensions should be removed ('Qbk:0Bk:0'). +If not, use option 'Qs'. It performs an exhaustive search for the +best initial simplex. This is expensive is high dimensions.
    +
    +
    + +
    +

    »qconvex graphics

    +
    + +

    Display 2-d, 3-d, and 4-d convex hulls with Geomview ('G').

    + +

    Display 2-d and 3-d convex hulls with Mathematica ('m').

    + +

    To view 4-d convex hulls in 3-d, use 'Pd0d1d2d3' to select the positive +octant and 'GrD2' to drop dimension +2.

    + +
    +

    »qconvex notes

    +
    + +

    Qhull always computes a convex hull. The +convex hull may be used for other geometric structures. The +general technique is to transform the structure into an +equivalent convex hull problem. For example, the Delaunay +triangulation is equivalent to the convex hull of the input sites +after lifting the points to a paraboloid.

    + +
    +

    »qconvex +conventions

    +
    + +

    The following terminology is used for convex hulls in Qhull. +See Qhull's data structures.

    + +
      +
    • point - d coordinates
    • +
    • vertex - extreme point of the input set
    • +
    • ridge - d-1 vertices between two + neighboring facets
    • +
    • hyperplane - halfspace defined by a unit normal + and offset
    • +
    • coplanar point - a nearly incident point to a + hyperplane
    • +
    • centrum - a point on the hyperplane for testing + convexity
    • +
    • facet - a facet with vertices, ridges, coplanar + points, neighboring facets, and hyperplane
    • +
    • simplicial facet - a facet with d + vertices, d ridges, and d neighbors
    • +
    • non-simplicial facet - a facet with more than d + vertices
    • +
    • good facet - a facet selected by 'QVn', etc.
    • +
    +
    +

    »qconvex options

    + +
    +qconvex- compute the convex hull
    +    http://www.qhull.org
    +
    +input (stdin):
    +    first lines: dimension and number of points (or vice-versa).
    +    other lines: point coordinates, best if one point per line
    +    comments:    start with a non-numeric character
    +
    +options:
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Qc   - keep coplanar points with nearest facet
    +    Qi   - keep interior points with nearest facet
    +
    +Qhull control options:
    +    Qbk:n   - scale coord k so that low bound is n
    +      QBk:n - scale coord k so that upper bound is n (QBk is 0.5)
    +    QbB  - scale input to unit cube centered at the origin
    +    Qbk:0Bk:0 - remove k-th coordinate from input
    +    QJn  - randomly joggle input in range [-n,n]
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)
    +    Qs   - search all points for the initial simplex
    +    QGn  - good facet if visible from point n, -n for not visible
    +    QVn  - good facet if it includes point n, -n if not
    +
    +Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - print statistics
    +    Tv   - verify result: structure, convexity, and point inclusion
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when point n added to hull
    +     TMn - turn on tracing at merge n
    +     TWn - trace merge facets when width > n
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)
    +     TCn - stop qhull after building cone for point n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Un   - max distance below plane for a new, coplanar point
    +    Wn   - min facet width for outside point (before roundoff)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    f    - facet dump
    +    G    - Geomview output (see below)
    +    i    - vertices incident to each facet
    +    m    - Mathematica output (2-d and 3-d)
    +    n    - normals with offsets
    +    o    - OFF file format (dim, points and facets; Voronoi regions)
    +    p    - point coordinates
    +    s    - summary (stderr)
    +
    +More formats:
    +    Fa   - area for each facet
    +    FA   - compute total area and volume for option 's'
    +    Fc   - count plus coplanar points for each facet
    +           use 'Qc' (default) for coplanar and 'Qi' for interior
    +    FC   - centrum for each facet
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FD   - use cdd format for numeric output (offset first)
    +    FF   - facet dump without ridges
    +    Fi   - inner plane for each facet
    +    FI   - ID for each facet
    +    Fm   - merge count for each facet (511 max)
    +    FM   - Maple output (2-d and 3-d)
    +    Fn   - count plus neighboring facets for each facet
    +    FN   - count plus neighboring facets for each point
    +    Fo   - outer plane (or max_outside) for each facet
    +    FO   - options and precision constants
    +    FP   - nearest vertex for each coplanar point
    +    FQ   - command used for qconvex
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,
    +                      for output: #vertices, #facets,
    +                                  #coplanar points, #non-simplicial facets
    +                    #real (2), max outer plane, min vertex
    +    FS   - sizes:   #int (0)
    +                    #real(2) tot area, tot volume
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)
    +    Fv   - count plus vertices for each facet
    +    FV   - average of vertices (a feasible point for 'H')
    +    Fx   - extreme points (in order for 2-d)
    +
    +Geomview output (2-d, 3-d, and 4-d)
    +    Ga   - all points as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices as spheres
    +    Gi   - inner planes only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc   - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +
    +Print options:
    +    PAn  - keep n largest facets by area
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)
    +    PDk:n - drop facet if normal[k] >= n
    +    Pg   - print good facets (needs 'QGn' or 'QVn')
    +    PFn  - keep facets whose area is at least n
    +    PG   - print neighbors of good facets
    +    PMn  - keep n facets with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +•Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qdelau_f.htm b/xs/src/qhull/html/qdelau_f.htm new file mode 100644 index 0000000000..d8981e16bc --- /dev/null +++ b/xs/src/qhull/html/qdelau_f.htm @@ -0,0 +1,416 @@ + + + + +qdelaunay Qu -- furthest-site Delaunay triangulation + + + + +Up: +Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +
    + +

    [delaunay]qdelaunay Qu -- furthest-site Delaunay triangulation

    + +

    The furthest-site Delaunay triangulation corresponds to the upper facets of the Delaunay construction. +Its vertices are the +extreme points of the input sites. +It is the dual of the furthest-site Voronoi diagram. + +

    +
    +
    Example: rbox 10 D2 | qdelaunay Qu Qt s + i TO + result
    +
    Compute the 2-d, furthest-site Delaunay triangulation of 10 random + points. Triangulate the output. + Write a summary to the console and the regions to + 'result'.
    +
     
    +
    Example: rbox 10 D2 | qdelaunay Qu QJ s + i TO + result
    +
    Compute the 2-d, furthest-site Delaunay triangulation of 10 random + points. Joggle the input to guarantee triangular output. + Write a summary to the console and the regions to + 'result'.
    +
     
    +
    Example: rbox r y c G1 D2 | qdelaunay Qu s + Fv TO + result
    +
    Compute the 2-d, furthest-site Delaunay triangulation of a triangle inside + a square. + Write a summary to the console and unoriented regions to 'result'. + Merge regions for cocircular input sites (e.g., the square). + The square is the only furthest-site + Delaunay region.
    +
    +
    + +

    As with the Delaunay triangulation, Qhull computes the +furthest-site Delaunay triangulation by lifting the input sites to a +paraboloid. The lower facets correspond to the Delaunay +triangulation while the upper facets correspond to the +furthest-site triangulation. Neither triangulation includes +"vertical" facets (i.e., facets whose last hyperplane +coefficient is nearly zero). Vertical facets correspond to input +sites that are coplanar to the convex hull of the input. An +example is points on the boundary of a lattice.

    + +

    By default, qdelaunay merges cocircular and cospherical regions. +For example, the furthest-site Delaunay triangulation of a square inside a diamond +('rbox D2 c d G4 | qdelaunay Qu') consists of one region (the diamond). + +

    If you use 'Qt' (triangulated output), +all furthest-site Delaunay regions will be simplicial (e.g., triangles in 2-d). +Some regions may be +degenerate and have zero area. + +

    If you use 'QJ' (joggled input), all furthest-site +Delaunay regions +will be simplicial (e.g., triangles in 2-d). Joggled input +is less accurate than triangulated output ('Qt'). See Merged facets or joggled input.

    + +

    The output for 3-d, furthest-site Delaunay triangulations may be confusing if the +input contains cospherical data. See the FAQ item +Why +are there extra points in a 4-d or higher convex hull? +Avoid these problems with triangulated output ('Qt') or +joggled input ('QJ'). +

    + +

    The 'qdelaunay' program is equivalent to +'qhull d Qbb' in 2-d to 3-d, and +'qhull d Qbb Qx' +in 4-d and higher. It disables the following Qhull +options: d n v H U Qb QB Qc Qf Qg Qi +Qm Qr QR Qv Qx TR E V FC Fi Fo Fp FV Q0,etc. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »furthest-site qdelaunay synopsis

    +
    + +See qdelaunay synopsis. The same +program is used for both constructions. Use option 'Qu' +for furthest-site Delaunay triangulations. + +
    +

    »furthest-site qdelaunay +input

    + +
    +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qdelaunay Qu < data.txt), a pipe (e.g., rbox 10 | qdelaunay Qu), +or the 'TI' option (e.g., qdelaunay Qu TI data.txt). + +

    For example, this is a square containing four random points. +Its furthest-site Delaunay +triangulation contains one square. +

    +

    +rbox c 4 D2 > data +
    +2 RBOX c 4 D2
    +8
    +-0.4999921736307369 -0.3684622117955817
    +0.2556053225468894 -0.0413498678629751
    +0.0327672376602583 -0.2810408135699488
    +-0.452955383763607 0.17886471718444
    +  -0.5   -0.5
    +  -0.5    0.5
    +   0.5   -0.5
    +   0.5    0.5
    +
    + +

    qdelaunay Qu i < data +

    +
    +Furthest-site Delaunay triangulation by the convex hull of 8 points in 3-d:
    +
    +  Number of input sites: 8
    +  Number of Delaunay regions: 1
    +  Number of non-simplicial Delaunay regions: 1
    +
    +Statistics for: RBOX c 4 D2 | QDELAUNAY s Qu i
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 20
    +  Number of facets in hull: 11
    +  Number of distance tests for qhull: 34
    +  Number of merged facets: 1
    +  Number of distance tests for merging: 107
    +  CPU seconds to compute hull (after input): 0.02
    +
    +1
    +7 6 4 5
    +
    +
    + +
    +

    »furthest-site qdelaunay +outputs

    +
    + +

    These options control the output of furthest-site Delaunay triangulations:

    +
    + +
    +
    furthest-site Delaunay regions
    +
    i
    +
    list input sites for each furthest-site Delaunay region. The first line is the number of regions. The + remaining lines list the input sites for each region. The regions are + oriented. In 3-d and + higher, report cospherical sites by adding extra points. For the points-in-square example, + the square is the only furthest-site Delaunay region.
    +
    Fv
    +
    list input sites for each furthest-site Delaunay region. The first line is the number of regions. + Each remaining line starts with the number of input sites. The regions + are unoriented. For the points-in-square example, + the square is the only furthest-site Delaunay region.
    +
    Ft
    +
    print a triangulation of the furthest-site Delaunay regions in OFF format. The first line + is the dimension. The second line is the number of input sites and added points, + followed by the number of simplices and the number of ridges. + The input coordinates are next, followed by the centrum coordinates. There is + one centrum for each non-simplicial furthest-site Delaunay region. Each remaining line starts + with dimension+1. The + simplices are oriented. + For the points-in-square example, the square has a centrum at the + origin. It splits the square into four triangular regions.
    +
    Fn
    +
    list neighboring regions for each furthest-site Delaunay region. The first line is the + number of regions. Each remaining line starts with the number of + neighboring regions. Negative indices (e.g., -1) indicate regions + outside of the furthest-site Delaunay triangulation. + For the points-in-square example, the four neighboring regions + are outside of the triangulation. They belong to the regular + Delaunay triangulation.
    +
    FN
    +
    list the furthest-site Delaunay regions for each input site. The first line is the + total number of input sites. Each remaining line starts with the number of + furthest-site Delaunay regions. Negative indices (e.g., -1) indicate regions + outside of the furthest-site Delaunay triangulation. + For the points-in-square example, the four random points belong to no region + while the square's vertices belong to region 0 and three + regions outside of the furthest-site Delaunay triangulation.
    +
    Fa
    +
    print area for each furthest-site Delaunay region. The first line is the number of regions. + The areas follow, one line per region. For the points-in-square example, the + square has unit area.
    + +
     
    +
     
    +
    Input sites
    +
    Fx
    +
    list extreme points of the input sites. These points are vertices of the furthest-point + Delaunay triangulation. They are on the + boundary of the convex hull. The first line is the number of + extreme points. Each point is listed, one per line. The points-in-square example + has four extreme points.
    +
     
    +
     
    +
    General
    +
    FA
    +
    compute total area for 's' + and 'FS'. This is the + same as the area of the convex hull.
    +
    o
    +
    print upper facets of the corresponding convex hull (a + paraboloid)
    +
    m
    +
    Mathematica output for the upper facets of the paraboloid (2-d triangulations).
    +
    FM
    +
    Maple output for the upper facets of the paraboloid (2-d triangulations).
    +
    G
    +
    Geomview output for the paraboloid (2-d or 3-d triangulations).
    +
    s
    +
    print summary for the furthest-site Delaunay triangulation. Use 'Fs' and 'FS' for numeric data.
    +
    +
    + +
    +

    »furthest-site qdelaunay +controls

    +
    + +

    These options provide additional control:

    +
    + +
    +
    Qu
    +
    must be used for furthest-site Delaunay triangulation.
    +
    Qt
    +
    triangulated output. Qhull triangulates non-simplicial facets. It may produce + degenerate facets of zero area.
    +
    QJ
    +
    joggle the input to avoid cospherical and coincident + sites. It is less accurate than triangulated output ('Qt').
    +
    QVn
    +
    select facets adjacent to input site n (marked + 'good').
    +
    Tv
    +
    verify result.
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    TFn
    +
    report progress after constructing n facets
    +
    PDk:1
    +
    include upper and lower facets in the output. Set k + to the last dimension (e.g., 'PD2:1' for 2-d inputs).
    +
    f
    +
    facet dump. Print the data structure for each facet (i.e., furthest-site Delaunay region).
    +
    +
    + +
    +

    »furthest-site qdelaunay +graphics

    +
    + +See Delaunay graphics. +They are the same except for Mathematica and Maple output. + +
    +

    »furthest-site +qdelaunay notes

    +
    + +

    The furthest-site Delaunay triangulation does not +record coincident input sites. Use qdelaunay instead. + +

    qdelaunay Qu does not work for purely cocircular +or cospherical points (e.g., rbox c | qdelaunay Qu). Instead, +use qdelaunay Qz -- when all points are vertices of the convex +hull of the input sites, the Delaunay triangulation is the same +as the furthest-site Delaunay triangulation. + +

    A non-simplicial, furthest-site Delaunay region indicates nearly cocircular or +cospherical input sites. To avoid non-simplicial regions triangulate +the output ('Qt') or joggle +the input ('QJ'). Joggled input +is less accurate than triangulated output. +You may also triangulate +non-simplicial regions with option 'Ft'. It adds +the centrum to non-simplicial regions. Alternatively, use an exact arithmetic code.

    + +

    Furthest-site Delaunay triangulations do not include facets that are +coplanar with the convex hull of the input sites. A facet is +coplanar if the last coefficient of its normal is +nearly zero (see qh_ZEROdelaunay). + +

    +

    »furthest-site qdelaunay conventions

    +
    + +

    The following terminology is used for furthest-site Delaunay +triangulations in Qhull. The underlying structure is the upper +facets of a convex hull in one higher dimension. See convex hull conventions, Delaunay conventions, +and Qhull's data structures

    +
    +
      +
    • input site - a point in the input (one dimension + lower than a point on the convex hull)
    • +
    • point - d+1 coordinates. The last + coordinate is the sum of the squares of the input site's + coordinates
    • +
    • vertex - a point on the paraboloid. It + corresponds to a unique input site.
    • +
    • furthest-site Delaunay facet - an upper facet of the + paraboloid. The last coefficient of its normal is + clearly positive.
    • +
    • furthest-site Delaunay region - a furthest-site Delaunay + facet projected to the input sites
    • +
    • non-simplicial facet - more than d + points are cocircular or cospherical
    • +
    • good facet - a furthest-site Delaunay facet with optional + restrictions by 'QVn', etc.
    • +
    +
    +
    +

    »furthest-site qdelaunay options

    +
    + +See qdelaunay options. The same +program is used for both constructions. Use option 'Qu' +for furthest-site Delaunay triangulations. + +
    + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qdelaun.htm b/xs/src/qhull/html/qdelaun.htm new file mode 100644 index 0000000000..a42223c663 --- /dev/null +++ b/xs/src/qhull/html/qdelaun.htm @@ -0,0 +1,628 @@ + + + + +qdelaunay -- Delaunay triangulation + + + + +Up: +Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +
    + +

    [delaunay]qdelaunay -- Delaunay triangulation

    + +

    The Delaunay triangulation is the triangulation with empty +circumspheres. It has many useful properties and applications. +See the survey article by Aurenhammer ['91] and the detailed introduction +by O'Rourke ['94].

    + +
    +
    +
    Example: rbox r y c G0.1 D2 | qdelaunay s + Fv TO + result
    +
    Compute the 2-d Delaunay triangulation of a triangle and + a small square. + Write a summary to the console and unoriented regions to 'result'. + Merge regions for cocircular input sites (i.e., the + square).
    +
     
    +
    Example: rbox r y c G0.1 D2 | qdelaunay s + Fv Qt
    +
    Compute the 2-d Delaunay triangulation of a triangle and + a small square. Write a summary and unoriented + regions to the console. Produce triangulated output.
    +
     
    +
    Example: rbox 10 D2 | qdelaunay QJ s + i TO + result
    +
    Compute the 2-d Delaunay triangulation of 10 random + points. Joggle the input to guarantee triangular output. + Write a summary to the console and the regions to + 'result'.
    +
    +
    + +

    Qhull computes the Delaunay triangulation by computing a +convex hull. It lifts the input sites to a paraboloid by adding +the sum of the squares of the coordinates. It scales the height +of the paraboloid to improve numeric precision ('Qbb'). +It computes the convex +hull of the lifted sites, and projects the lower convex hull to +the input. + +

    Each region of the Delaunay triangulation +corresponds to a facet of the lower half of the convex hull. +Facets of the upper half of the convex hull correspond to the furthest-site Delaunay triangulation. +See the examples, Delaunay and +Voronoi diagrams.

    + +

    See Qhull FAQ - Delaunay and +Voronoi diagram questions.

    + +

    By default, qdelaunay merges cocircular and cospherical regions. +For example, the Delaunay triangulation of a square inside a diamond +('rbox D2 c d G4 | qdelaunay') contains one region for the square. + +

    Use option 'Qz' if the input is circular, cospherical, or +nearly so. It improves precision by adding a point "at infinity," above the corresponding paraboloid. + +

    If you use 'Qt' (triangulated output), +all Delaunay regions will be simplicial (e.g., triangles in 2-d). +Some regions may be +degenerate and have zero area. Triangulated output identifies coincident +points. + +

    If you use 'QJ' (joggled input), all Delaunay regions +will be simplicial (e.g., triangles in 2-d). Coincident points will +create small regions since the points are joggled apart. Joggled input +is less accurate than triangulated output ('Qt'). See Merged facets or joggled input.

    + +

    The output for 3-d Delaunay triangulations may be confusing if the +input contains cospherical data. See the FAQ item +Why +are there extra points in a 4-d or higher convex hull? +Avoid these problems with triangulated output ('Qt') or +joggled input ('QJ'). +

    + +

    The 'qdelaunay' program is equivalent to +'qhull d Qbb' in 2-d to 3-d, and +'qhull d Qbb Qx' +in 4-d and higher. It disables the following Qhull +options: d n v H U Qb QB Qc Qf Qg Qi +Qm Qr QR Qv Qx TR E V FC Fi Fo Fp Ft FV Q0,etc. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »qdelaunay synopsis

    + +
    +qdelaunay- compute the Delaunay triangulation.
    +    input (stdin): dimension, number of points, point coordinates
    +    comments start with a non-numeric character
    +
    +options (qdelaun.htm):
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Qu   - furthest-site Delaunay triangulation
    +    Tv   - verify result: structure, convexity, and in-circle test
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +output options (subset):
    +    s    - summary of results (default)
    +    i    - vertices incident to each Delaunay region
    +    Fx   - extreme points (vertices of the convex hull)
    +    o    - OFF format (shows the points lifted to a paraboloid)
    +    G    - Geomview output (2-d and 3-d points lifted to a paraboloid)
    +    m    - Mathematica output (2-d inputs lifted to a paraboloid)
    +    QVn  - print Delaunay regions that include point n, -n if not
    +    TO file- output results to file, may be enclosed in single quotes
    +
    +examples:
    +    rbox c P0 D2 | qdelaunay s o          rbox c P0 D2 | qdelaunay i
    +    rbox c P0 D3 | qdelaunay Fv Qt        rbox c P0 D2 | qdelaunay s Qu Fv
    +    rbox c G1 d D2 | qdelaunay s i        rbox c G1 d D2 | qdelaunay s i Qt
    +    rbox M3,4 z 100 D2 | qdelaunay s      rbox M3,4 z 100 D2 | qdelaunay s Qt
    +
    + + +

    »qdelaunay +input

    + +
    +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qdelaunay < data.txt), a pipe (e.g., rbox 10 | qdelaunay), +or the 'TI' option (e.g., qdelaunay TI data.txt). + +

    For example, this is four cocircular points inside a square. Its Delaunay +triangulation contains 8 triangles and one four-sided +figure. +

    +

    +rbox s 4 W0 c G1 D2 > data +
    +2 RBOX s 4 W0 c D2
    +8
    +-0.4941988586954018 -0.07594397977563715
    +-0.06448037284989526 0.4958248496365813
    +0.4911154367094632 0.09383830681375946
    +-0.348353580869097 -0.3586778257652367
    +    -1     -1
    +    -1      1
    +     1     -1
    +     1      1
    +
    + +

    qdelaunay s i < data +

    +
    +Delaunay triangulation by the convex hull of 8 points in 3-d
    +
    +  Number of input sites: 8
    +  Number of Delaunay regions: 9
    +  Number of non-simplicial Delaunay regions: 1
    +
    +Statistics for: RBOX s 4 W0 c D2 | QDELAUNAY s i
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 18
    +  Number of facets in hull: 10
    +  Number of distance tests for qhull: 33
    +  Number of merged facets: 2
    +  Number of distance tests for merging: 102
    +  CPU seconds to compute hull (after input): 0.028
    +
    +9
    +1 7 5
    +6 3 4
    +2 3 6
    +7 2 6
    +2 7 1
    +0 5 4
    +3 0 4
    +0 1 5
    +1 0 3 2
    +
    +
    + +
    +

    »qdelaunay +outputs

    +
    + +

    These options control the output of Delaunay triangulations:

    +
    + +
    +
    Delaunay regions
    +
    i
    +
    list input sites for each Delaunay region. The first line is the number of regions. The + remaining lines list the input sites for each region. The regions are + oriented. In 3-d and + higher, report cospherical sites by adding extra points. Use triangulated + output ('Qt') to avoid non-simpicial regions. For the circle-in-square example, + eight Delaunay regions are triangular and the ninth has four input sites.
    +
    Fv
    +
    list input sites for each Delaunay region. The first line is the number of regions. + Each remaining line starts with the number of input sites. The regions + are unoriented. For the circle-in-square example, + eight Delaunay regions are triangular and the ninth has four input sites.
    +
    Fn
    +
    list neighboring regions for each Delaunay region. The first line is the + number of regions. Each remaining line starts with the number of + neighboring regions. Negative indices (e.g., -1) indicate regions + outside of the Delaunay triangulation. + For the circle-in-square example, the four regions on the square are neighbors to + the region-at-infinity.
    +
    FN
    +
    list the Delaunay regions for each input site. The first line is the + total number of input sites. Each remaining line starts with the number of + Delaunay regions. Negative indices (e.g., -1) indicate regions + outside of the Delaunay triangulation. + For the circle-in-square example, each point on the circle belongs to four + Delaunay regions. Use 'Qc FN' + to include coincident input sites and deleted vertices.
    +
    Fa
    +
    print area for each Delaunay region. The first line is the number of regions. + The areas follow, one line per region. For the circle-in-square example, the + cocircular region has area 0.4.
    +
     
    +
     
    +
    Input sites
    +
    Fc
    +
    list coincident input sites for each Delaunay region. + The first line is the number of regions. The remaining lines start with + the number of coincident sites and deleted vertices. Deleted vertices + indicate highly degenerate input (see'Fs'). + A coincident site is assigned to one Delaunay + region. Do not use 'QJ' with 'Fc'; the joggle will separate + coincident sites.
    +
    FP
    +
    print coincident input sites with distance to + nearest site (i.e., vertex). The first line is the + number of coincident sites. Each remaining line starts with the point ID of + an input site, followed by the point ID of a coincident point, its region, and distance. + Includes deleted vertices which + indicate highly degenerate input (see'Fs'). + Do not use 'QJ' with 'FP'; the joggle will separate + coincident sites.
    +
    Fx
    +
    list extreme points of the input sites. These points are on the + boundary of the convex hull. The first line is the number of + extreme points. Each point is listed, one per line. The circle-in-square example + has four extreme points.
    +
     
    +
     
    +
    General
    +
    FA
    +
    compute total area for 's' + and 'FS'
    +
    o
    +
    print lower facets of the corresponding convex hull (a + paraboloid)
    +
    m
    +
    Mathematica output for the lower facets of the paraboloid (2-d triangulations).
    +
    FM
    +
    Maple output for the lower facets of the paraboloid (2-d triangulations).
    +
    G
    +
    Geomview output for the paraboloid (2-d or 3-d triangulations).
    +
    s
    +
    print summary for the Delaunay triangulation. Use 'Fs' and 'FS' for numeric data.
    +
    +
    + +
    +

    »qdelaunay +controls

    +
    + +

    These options provide additional control:

    +
    + +
    +
    Qt
    +
    triangulated output. Qhull triangulates non-simplicial facets. It may produce +degenerate facets of zero area.
    +
    QJ
    +
    joggle the input to avoid cospherical and coincident + sites. It is less accurate than triangulated output ('Qt').
    +
    Qu
    +
    compute the furthest-site Delaunay triangulation.
    +
    Qz
    +
    add a point above the paraboloid to reduce precision + errors. Use it for nearly cocircular/cospherical input + (e.g., 'rbox c | qdelaunay Qz'). The point is printed for + options 'Ft' and 'o'.
    +
    QVn
    +
    select facets adjacent to input site n (marked + 'good').
    +
    Tv
    +
    verify result.
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    TFn
    +
    report progress after constructing n facets
    +
    PDk:1
    +
    include upper and lower facets in the output. Set k + to the last dimension (e.g., 'PD2:1' for 2-d inputs).
    +
    f
    +
    facet dump. Print the data structure for each facet (i.e., Delaunay region).
    +
    +
    + +
    +

    »qdelaunay +graphics

    +
    + +

    For 2-d and 3-d Delaunay triangulations, Geomview ('qdelaunay G') displays the corresponding convex +hull (a paraboloid).

    + +

    To view a 2-d Delaunay triangulation, use 'qdelaunay GrD2' to drop the last dimension. This +is the same as viewing the hull without perspective (see +Geomview's 'cameras' menu).

    + +

    To view a 3-d Delaunay triangulation, use 'qdelaunay GrD3' to drop the last dimension. You +may see extra edges. These are interior edges that Geomview moves +towards the viewer (see 'lines closer' in Geomview's camera +options). Use option 'Gt' to make +the outer ridges transparent in 3-d. See Delaunay and Voronoi examples.

    + +

    For 2-d Delaunay triangulations, Mathematica ('m') and Maple ('FM') output displays the lower facets of the corresponding convex +hull (a paraboloid).

    + +

    For 2-d, furthest-site Delaunay triangulations, Maple and Mathematica output ('Qu m') displays the upper facets of the corresponding convex +hull (a paraboloid).

    + +
    +

    »qdelaunay +notes

    +
    + +

    You can simplify the Delaunay triangulation by enclosing the input +sites in a large square or cube. This is particularly recommended +for cocircular or cospherical input data. + +

    A non-simplicial Delaunay region indicates nearly cocircular or +cospherical input sites. To avoid non-simplicial regions either triangulate +the output ('Qt') or joggle +the input ('QJ'). Triangulated output +is more accurate than joggled input. Alternatively, use an exact arithmetic code.

    + +

    Delaunay triangulations do not include facets that are +coplanar with the convex hull of the input sites. A facet is +coplanar if the last coefficient of its normal is +nearly zero (see qh_ZEROdelaunay). + +

    See Imprecision issues :: Delaunay triangulations +for a discussion of precision issues. Deleted vertices indicate +highly degenerate input. They are listed in the summary output and +option 'Fs'.

    + +

    To compute the Delaunay triangulation of points on a sphere, +compute their convex hull. If the sphere is the unit sphere at +the origin, the facet normals are the Voronoi vertices of the +input. The points may be restricted to a hemisphere. [S. Fortune] +

    + +

    The 3-d Delaunay triangulation of regular points on a half +spiral (e.g., 'rbox 100 l | qdelaunay') has quadratic size, while the Delaunay triangulation +of random 3-d points is +approximately linear for reasonably sized point sets. + +

    With the Qhull library, you +can use qh_findbestfacet in poly2.c to locate the facet +that contains a point. You should first lift the point to the +paraboloid (i.e., the last coordinate is the sum of the squares +of the point's coordinates -- qh_setdelaunay). Do not use options +'Qbb', 'QbB', +'Qbk:n', or 'QBk:n' since these scale the last +coordinate.

    + +

    If a point is interior to the convex hull of the input set, it +is interior to the adjacent vertices of the Delaunay +triangulation. This is demonstrated by the following pipe for +point 0: + +

    +    qdelaunay <data s FQ QV0 p | qconvex s Qb3:0B3:0 p
    +
    + +

    The first call to qdelaunay returns the neighboring points of +point 0 in the Delaunay triangulation. The second call to qconvex +returns the vertices of the convex hull of these points (after +dropping the lifted coordinate). If point 0 is interior to the +original point set, it is interior to the reduced point set.

    + +
    +

    »qdelaunay conventions

    +
    + +

    The following terminology is used for Delaunay triangulations +in Qhull for dimension d. The underlying structure is the +lower facets of a convex hull in dimension d+1. For +further information, see data +structures and convex hull +conventions.

    +
    +
      +
    • input site - a point in the input (one dimension + lower than a point on the convex hull)
    • +
    • point - a point has d+1 coordinates. The + last coordinate is the sum of the squares of the input + site's coordinates
    • +
    • coplanar point - a coincident + input site or a deleted vertex. Deleted vertices + indicate highly degenerate input.
    • +
    • vertex - a point on the paraboloid. It + corresponds to a unique input site.
    • +
    • point-at-infinity - a point added above the + paraboloid by option 'Qz'
    • +
    • lower facet - a facet corresponding to a + Delaunay region. The last coefficient of its normal is + clearly negative.
    • +
    • upper facet - a facet corresponding to a + furthest-site Delaunay region. The last coefficient of + its normal is clearly positive.
    • +
    • Delaunay region - a + lower facet projected to the input sites
    • +
    • upper Delaunay region - an upper facet projected + to the input sites
    • +
    • non-simplicial facet - more than d + input sites are cocircular or cospherical
    • +
    • good facet - a Delaunay region with optional + restrictions by 'QVn', etc.
    • +
    +
    +
    +

    »qdelaunay options

    + +
    +qdelaunay- compute the Delaunay triangulation
    +    http://www.qhull.org
    +
    +input (stdin):
    +    first lines: dimension and number of points (or vice-versa).
    +    other lines: point coordinates, best if one point per line
    +    comments:    start with a non-numeric character
    +
    +options:
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Qu   - compute furthest-site Delaunay triangulation
    +
    +Qhull control options:
    +    QJn  - randomly joggle input in range [-n,n]
    +    Qs   - search all points for the initial simplex
    +    Qz   - add point-at-infinity to Delaunay triangulation
    +    QGn  - print Delaunay region if visible from point n, -n if not
    +    QVn  - print Delaunay regions that include point n, -n if not
    +
    +Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - print statistics
    +    Tv   - verify result: structure, convexity, and in-circle test
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when point n added to hull
    +     TMn - turn on tracing at merge n
    +     TWn - trace merge facets when width > n
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)
    +     TCn - stop qhull after building cone for point n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Wn   - min facet width for outside point (before roundoff)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    f    - facet dump
    +    G    - Geomview output (see below)
    +    i    - vertices incident to each Delaunay region
    +    m    - Mathematica output (2-d only, lifted to a paraboloid)
    +    o    - OFF format (dim, points, and facets as a paraboloid)
    +    p    - point coordinates (lifted to a paraboloid)
    +    s    - summary (stderr)
    +
    +More formats:
    +    Fa   - area for each Delaunay region
    +    FA   - compute total area for option 's'
    +    Fc   - count plus coincident points for each Delaunay region
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FD   - use cdd format for numeric output (offset first)
    +    FF   - facet dump without ridges
    +    FI   - ID of each Delaunay region
    +    Fm   - merge count for each Delaunay region (511 max)
    +    FM   - Maple output (2-d only, lifted to a paraboloid)
    +    Fn   - count plus neighboring region for each Delaunay region
    +    FN   - count plus neighboring region for each point
    +    FO   - options and precision constants
    +    FP   - nearest point and distance for each coincident point
    +    FQ   - command used for qdelaunay
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,
    +                    for output: #vertices, #Delaunay regions,
    +                                #coincident points, #non-simplicial regions
    +                    #real (2), max outer plane, min vertex
    +    FS   - sizes:   #int (0)
    +                    #real (2), tot area, 0
    +    Fv   - count plus vertices for each Delaunay region
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)
    +
    +Geomview options (2-d and 3-d)
    +    Ga   - all points as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices as spheres
    +    Gi   - inner planes only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc     - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +    Gt   - transparent outer ridges to view 3-d Delaunay
    +
    +Print options:
    +    PAn  - keep n largest Delaunay regions by area
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)
    +    PDk:n - drop facet if normal[k] >= n
    +    Pg   - print good Delaunay regions (needs 'QGn' or 'QVn')
    +    PFn  - keep Delaunay regions whose area is at least n
    +    PG   - print neighbors of good regions (needs 'QGn' or 'QVn')
    +    PMn  - keep n Delaunay regions with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh--4d.gif b/xs/src/qhull/html/qh--4d.gif new file mode 100644 index 0000000000000000000000000000000000000000..08be18c8a5a1c89f07da6abb451283cbf176d5b4 GIT binary patch literal 4372 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 aB{fPSgP!@PkGDE|e!fd8+eHNc9R366^3h-b literal 0 HcmV?d00001 diff --git a/xs/src/qhull/html/qh--dt.gif b/xs/src/qhull/html/qh--dt.gif new file mode 100644 index 0000000000000000000000000000000000000000..b6d4e26727ce648be6ed6d4c8e5a49a367064f20 GIT binary patch literal 3772 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 b)E_=_r0*X`jy*hb;`bvK_r5-N2#Eb(@t%X< literal 0 HcmV?d00001 diff --git a/xs/src/qhull/html/qh--geom.gif b/xs/src/qhull/html/qh--geom.gif new file mode 100644 index 0000000000000000000000000000000000000000..9c70b54991a4574015671ec0e0b29102c5346737 GIT binary patch literal 318 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 tio%^!)G! zn&62?Ji-!#4mCI6p^dgm_q}kY@|4fOha03I1zQ+GNh(q4UGDkT*d%~OJss#Xiy9tq zU~79Jtm+!4`o^2s#6H%f#BW#{(;&vjrd#ojNqG9aO>S~Hw6Inb*g_F;FgBxCoen-^ z!5U*Y2`YG?U(o&*5Cka3JMLi$ULP32%v}YyV+`)#GkT=93PKZYY8sF$W zf+b)}-H+Q`;OX!|x>HgK9zgWuHFZT39=`H>yra{qRqg{GU=MrDVGeFEhkN0BbB_z0 z571Hx5vUM~P=2?jD`>*Ji+&GZJi;7=@Pyhs?dcsH!VrdtaKNojcL}4R11z~hD3}3s zp$mZQD~G}(2w(;?zysKDpoA}S5Q9XRp$&8Ixa5Hmj1lZy>s)8XezSVdu>=0-!Wf0< zM=yG9@WTqLu!RMxK6yR>p#&|VbttHiXKV%+1ox;w?jf#w-48+uqv(U^h2IBZXrmDP zCc+if9O`@QANS z34>q@W)KDBfCG!*4SRqF(x?V7fEgQr45HYJ=s*B7aEdDz4%k2mUl0RuKnTpJ0@2_N z-jI%qKo0CEjUF(4ZGj4QfCbYa2?ubE^>6@o5C+0nbnn0b3@`wLpbg}(2nI=z=|~RI zD2+!zJcJ<)m4F21dS%DU_So37znc@G%hA00S?H4#)tLl5h?iumzJq z03j)oZuyo6Sp-`d3zQ&oUnvmVAeLAVlgNM!=ztDlAPJ5T4R8sU(SQbi;0T=%3vNIM zcZm>r`I3E!40>P+{m>7R5CnUm288LBhpCuB&m + + + +Qhull code + + + + + +

    Up: Home page for Qhull +
    +Up: Qhull manual: Table of +Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull code: Table of Contents +(please wait while loading)
    +Dn: Qhull functions, macros, and data +structures +

    + +
    + +

    [4-d cube] Qhull code

    + +

    This section discusses the code for Qhull.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »Qhull code: Table of +Contents

    + + + +
    + +

    »Reentrant Qhull

    + +

    Qhull-2015 introduces reentrant Qhull (libqhull_r). Reentrant Qhull uses a qhT* argument instead of global data structures. +The qhT* pointer is the first argument to most Qhull routines. It allows multiple instances of Qhull to run at the same time. +It simplifies the C++ interface to Qhull. + +

    New code should be written with libqhull_r. Existing users of libqhull should consider converting to libqhull_r. +Although libqhull will be supported indefinitely, improvements may not be implemented. +Reentrant qhull is 1-2% slower than non-reentrant qhull. + +

    Note: Reentrant Qhull is not thread safe. Do not invoke Qhull routines with the same qhT* pointer from multiple threads. + +

    »How to convert code to reentrant Qhull

    + +

    C++ users need to convert to libqhull_r. +The new C++ interface does a better, but not perfect, job of hiding Qhull's C data structures. +The previous C++ interface was unusual due to Qhull's global data structures. + +

    All other users should consider converting to libqhull_r. The conversion is straight forward. +The original conversion of libqhull to libqhull_r required thousands of changes, mostly global +search and replace. The first run of Qhull (unix_r.c) produced the same +output, and nearly the same log files, as the original (unix.c). + +

    Suggestions to help with conversion. +

      +
    • Compare qconvex_r.c with qconvex.c. Define a qhT object and a pointer it. The qhT* pointer is the first argument to most Qhull functions. +Clear qh_qh-<NOerrext before calling qh_initflags(). Invoke QHULL_LIB_CHECK to check for a compatible Qhull library. +
    • Compare user_eg2_r.c with user_eg2.c +
    • Compare user_eg_r.c with user_eg.c. If you use qhT before invoking qh_init_A, call qh_zero() to clear the qhT object. +user_eg_r.c includes multiple Qhull runs. +
    • Review user_eg3_r.cpp. As with the other programs, invoke QHULL_LIB_CHECK. +Simple C++ programs should compile as is. +
    • Compare QhullFacet.cpp with the same file in Qhull-2012.1. UsingLibQhull was replaced with the macro QH_TRY_() and 'qh_qh-<NOerrext= true'. +
    • For detailed notes on libqhull_r, see "libqhull_r (reentrant Qhull)" and "Source code changes for libqhull_r" in Changes.txt. +
    • For detailed notes on libqhullcpp, see "C++ interface" and following sections in Changes.txt. +
    • For regexps and conversion notes, see README_r.txt (unedited). +
    + +

    »Qhull on 64-bit computers

    + +

    Qhull compiles for 64-bit hosts. Since the size of a pointer on a 64-bit host is double the size on a 32-bit host, +memory consumption increases about 50% for simplicial facets and up-to 100% for non-simplicial facets. + +

    You can check memory consumption with option Ts. It includes the size of +each data structure: +

      +
    • 32-bit -- merge 24 ridge 20 vertex 28 facet 88 normal 24 ridge vertices 16 facet vertices or neighbors 20 +
    • 64-bit -- merge 32 ridge 32 vertex 48 facet 120 normal 32 ridge vertices 40 facet vertices or neighbors 48 +
    + +

    For Qhull 2015, the maximum identifier for ridges, vertices, and facets was increased +from 24-bits to 32-bits. This allows for larger convex hulls, but may increase the size of +the corresponding data structures. The sizes for Qhull 2012.1 were +

      +
    • 32-bit -- merge 24 ridge 16 vertex 24 facet 88 +
    • 64-bit -- merge 32 ridge 32 vertex 40 facet 120 +
    + +

    »Calling Qhull from +C++ programs

    + +

    Qhull 2015 uses reentrant Qhull for its C++ interface. If you used +the C++ interface from qhull 2012.1, you may need to adjust how you initialize and use +the Qhull classes. See How to convert code to reentrant Qhull. + +

    +Qhull's C++ interface allows you to explore the results of running Qhull. +It provides access to Qhull's data structures. +Most of the classes derive from the corresponding qhull data structure. +For example, QhullFacet is an instance of Qhull's facetT. +

    + +

    You can retain most of the data in Qhull and use the C++ interface to explore its results. +Each object contains a reference the Qhull's data structure (via QhullQh), making the C++ representation less memory efficient. +

    + +

    Besides using the C++ interface, you can also use libqhull_r directly. For example, +the FOREACHfacet_(...) macro will visit each facet in turn. +

    + +

    The C++ interface to Qhull is incomplete. You may need to extend the interface. +If so, you will need to understand Qhull's data structures and read the code. + +Example (c.f., user_eg3 eg-100). It prints the facets generated by Qhull. + +

    +    RboxPoints rbox;
    +    rbox.appendRandomPoints("100");
    +    Qhull qhull;
    +    qhull.runQhull("", rbox);
    +    QhullFacetList facets(qhull);
    +    cout<< facets;
    +
    + +

    +The C++ iterface for RboxPoints redefines the fprintf() calls +in rboxlib.c. Instead of writing its output to stdout, RboxPoints appends +the output to a std::vector. +The same technique may be used for calling Qhull from C++. +

    +
    • +Run Qhull with option 'Ta' to annotate the +output with qh_fprintf() identifiers. +
    • +Redefine qh_fprintf() for these identifiers. +
    • +See RboxPoints.cpp for an example. +
    +

    +Since the C++ interface uses reentrant Qhull, multiple threads may run Qhull at the same time. Each thread is +one run of Qhull. +

    + +

    +Do not have two threads accessing the same Qhull instance. Qhull is not thread-safe. +

    + +

    »CoordinateIterator

    +

    +A CoordinateIterator or ConstCoordinateIterator [RboxPoints.cpp] is a std::vector<realT>::iterator for Rbox and Qhull coordinates. +It is the result type of RboxPoints.coordinates(). +

    + +

    Qhull does not use CoordinateIterator for its data structures. A point in Qhull is an array of reals instead of a std::vector. +See QhullPoint. +

    + +

    »Qhull

    +

    +Qhull is the top-level class for running Qhull. +It initializes Qhull, runs the computation, and records errors. +It provides access to the global data structure QhullQh, +Qhull's facets, and vertices. +

    + +

    »QhullError

    +

    +QhullError is derived from std::exception. It reports errors from Qhull and captures the output to stderr. +

    + +

    +If error handling is not set up, Qhull exits with a code from 1 to 5. The codes are defined by +qh_ERR* in libqhull_r.h. The exit is via qh_exit() in usermem_r.c. +The C++ interface does not report the +captured output in QhullError. Call Qhull::setErrorStream to send output to cerr instead. +

    + +

    »QhullFacet

    +

    +A QhullFacet is a facet of the convex hull, a region of the Delaunay triangulation, a vertex of a Voronoi diagram, +or an intersection of the halfspace intersection about a point. +A QhullFacet has a set of QhullVertex, a set of QhullRidge, and +a set of neighboring QhullFacets. +

    + +

    »QhullFacetList

    +

    +A QhullFacetList is a linked list of QhullFacet. The result of Qhull.runQhull is a QhullFacetList stored +in QhullQh. +

    + +

    »QhullFacetSet

    +

    +A QhullFacetSet is a QhullSet of QhullFacet. QhullFacetSet may be ordered or unordered. The neighboring facets of a QhullFacet is a QhullFacetSet. +The neighbors of a QhullFacet is a QhullFacetSet. +The neighbors are ordered for simplicial facets, matching the opposite vertex of the facet. +

    + +

    »QhullIterator

    +

    +QhullIterator contains macros for defining Java-style iterator templates from a STL-style iterator template. +

    + +

    »QhullLinkedList

    +

    +A QhullLinkedLIst is a template for linked lists with next and previous pointers. +QhullFacetList and QhullVertexList are QhullLinkedLists. +

    + +

    »QhullPoint

    +

    +A QhullPoint is an array of point coordinates, typically doubles. The length of the array is QhullQh.hull_dim. +The identifier of a QhullPoint is its 0-based index from QhullQh.first_point followed by QhullQh.other_points. +

    + +

    »QhullPointSet

    +

    +A QhullPointSet is a QhullSet of QhullPoint. The QhullPointSet of a QhullFacet is its coplanar points. +

    + +

    »QhullQh

    +

    +QhullQh is the root of Qhull's data structure. +It contains initialized constants, sets, buffers, and variables. +It contains an array and a set of QhullPoint, +a list of QhullFacet, and a list of QhullVertex. +The points are the input to Qhull. The facets and vertices are the result of running Qhull. +

    + +

    +Qhull's functions access QhullQh through the global variable, qh_qh. +The global data structures, qh_stat and qh_mem, record statistics and manage memory respectively. +

    + +

    »QhullRidge

    + +

    +A QhullRidge represents the edge between two QhullFacet's. +It is always simplicial with qh.hull_dim-1 QhullVertex)'s. +

    + +

    »QhullRidgeSet

    + +

    +A QhullRidgeSet is a QhullSet of QhullRidge. Each QhullFacet contains a QhullRidgeSet. +

    + +

    »QhullSet

    + +

    +A QhullSet is a set of pointers to objects. QhullSets may be ordered or unordered. They are the core data structure for Qhull. +

    + +

    »QhullVertex

    + +

    +A QhullVertex is a vertex of the convex hull. A simplicial QhullFacet has qh.hull_dim-1 vertices. A QhullVertex contains a QhullPoint. +It may list its neighboring QhullFacet's. +

    + +

    »QhullVertexList

    + +

    +A QhullVertexList is a QhullLinkedList of QhullVertex. +The global data structure, QhullQh contains a QhullVertexList of all +the vertices. +

    + +

    »QhullVertexSet

    + +

    +A QhullVertexSet is a QhullSet of QhullVertex. +The QhullVertexSet of a QhullFacet is the vertices of the facet. It is +ordered for simplicial facets and unordered for non-simplicial facets. +

    + +

    »RboxPoints

    + +

    +RboxPoints is a std::vector of point coordinates (QhullPoint). +It's iterator is CoordinateIterator. +

    +

    +RboxPoints.appendRandomPoints() appends points from a variety of distributions such as uniformly distributed within a cube and random points on a sphere. +It can also append a cube's vertices or specific points. +

    + +

    »Cpp questions for Qhull

    + +Developing C++ code requires many conventions, idioms, and technical details. +The following questions have either +mystified the author or do not have a clear answer. See also +C++ and Perl Guidelines. +and FIXUP notes in the code. +Please add notes to Qhull Wiki. + +
      +
    • FIXUP QH11028 Should return reference, but get reference to temporary +
      iterator Coordinates::operator++() { return iterator(++i); }
      +
    • size() as size_t, size_type, or int +
    • Should all containers have a reserve()? +
    • Qhull.feasiblePoint interface +
    • How to avoid copy constructor while logging, maybeThrowQhullMessage() +
    • How to configure Qhull output. Trace and results should go to stdout/stderr +
    • Qhull and RboxPoints messaging. e.g., ~Qhull, hasQhullMessage(). Rename them as QhullErrorMessage? +
    • How to add additional output to an error message, e.g., qh_setprint +
    • Is idx the best name for an index? It's rather cryptic, but BSD strings.h defines index(). +
    • Qhull::feasiblePoint Qhull::useOutputStream as field or getter? +
    • Define virtual functions for user customization of Qhull (e.g., qh_fprintf, qh_memfree,etc.) +
    • Figure out RoadError::global_log. clearQhullMessage currently clearGlobalLog +
    • Should the false QhullFacet be NULL or empty? e.g., QhullFacet::tricoplanarOwner() and QhullFacetSet::end() +
    • Should output format for floats be predefined (qh_REAL_1, 2.2g, 10.7g) or as currently set for stream +
    • Should cout << !point.defined() be blank or 'undefined' +
    • Infinite point as !defined() +
    • qlist and qlinkedlist define pointer, reference, size_type, difference_type, const_pointer, const_reference for the class but not for iterator and const_iterator + vector.h --
      reference operator[](difference_type _Off) const
      +
    • When forwarding an implementation is base() an approriate name (e.g., Coordinates::iterator::base() as std::vector::iterator). +
    • When forwarding an implementation, does not work "returning address of temporary" +
    • Also --, +=, and -= +
      iterator       &operator++() { return iterator(i++); }
      +
    • if vector inheritance is bad, is QhullVertexSet OK? +
    • Should QhullPointSet define pointer and reference data types? +
    + +

    »Calling Qhull from +C programs

    + +

    Warning: Qhull was not designed for calling from C +programs. You may find the C++ interface easier to use. +You will need to understand the data structures and read the code. +Most users will find it easier to call Qhull as an external +command. + +

    For examples of calling Qhull, see GNU Octave's +computational geometry code, +and Qhull's +user_eg_r.c, +user_eg2_r.c, and +user_r.c. To see how Qhull calls its library, read +unix_r.c, +qconvex.c, +qdelaun.c, +qhalf.c, and +qvoronoi.c. The '*_r.c' files are reentrant, otherwise they are non-reentrant. +Either version may be used. New code should use reentrant Qhull. + +

    See Reentrant Qhull functions, macros, and data +structures for internal documentation of Qhull. The +documentation provides an overview and index. To use the library +you will need to read and understand the code. For most users, it +is better to write data to a file, call the qhull program, and +read the results from the output file.

    + +

    If you use non-reentrant Qhull, be aware of the macros "qh" +and "qhstat", e.g., "qh hull_dim". They are +defined in libqhull.h. They allow the global data +structures to be pre-allocated (faster access) or dynamically +allocated (allows multiple copies).

    + +

    Qhull's Makefile produces a library, libqhull_r.a, +for inclusion in your programs. First review libqhull_r.h. +This defines the data structures used by Qhull and provides +prototypes for the top-level functions. +Most users will only need libqhull_r.h in their programs. For +example, the Qhull program is defined with libqhull_r.h and unix_r.c. +To access all functions, use qhull_ra.h. Include the file +with "#include <libqhull_r/qhull_ra.h>". This +avoids potential name conflicts.

    + +

    If you use the Qhull library, you are on your own as far as +bugs go. Start with small examples for which you know the output. +If you get a bug, try to duplicate it with the Qhull program. The +'Tc' option will catch many problems +as they occur. When an error occurs, use 'T4 TPn' +to trace from the last point added to the hull. Compare your +trace with the trace output from the Qhull program.

    + +

    Errors in the Qhull library are more likely than errors in the +Qhull program. These are usually due to feature interactions that +do not occur in the Qhull program. Please report all errors that +you find in the Qhull library. Please include suggestions for +improvement.

    + +

    »How to avoid exit(), fprintf(), stderr, and stdout

    + +

    Qhull sends output to qh.fout and errors, log messages, and summaries to qh.ferr. qh.fout is normally +stdout and qh.ferr is stderr. qh.fout may be redefined by option 'TO' or the caller. qh.ferr may be redirected to qh.fout by option 'Tz'.

    + +

    Qhull does not use stderr, stdout, fprintf(), or exit() directly.

    + +

    Qhull reports errors via qh_errexit() by writting a message to qh.ferr and invoking longjmp(). +This returns the caller to the corresponding setjmp() (c.f., QH_TRY_ in QhullQh.h). If +qh_errexit() is not available, Qhull functions call qh_exit(). qh_exit() normally calls exit(), +but may be redefined by the user. An example is +libqhullcpp/usermem_r-cpp.cpp. It redefines qh_exit() as a 'throw'.

    + +

    If qh_meminit() or qh_new_qhull() is called with ferr==NULL, then they set ferr to stderr. +Otherwise the Qhull libraries use qh->ferr and qh->qhmem.ferr for error output.

    + +

    If an error occurs before qh->ferr is initialized, Qhull invokes qh_fprintf_stderr(). The user +may redefine this function along with qh_exit(), qh_malloc(), and qh_free(). + +

    The Qhull libraries write output via qh_fprintf() [userprintf_r.c]. Otherwise, the Qhull +libraries do not use stdout, fprintf(), or printf(). Like qh_exit(), the user may redefine +qh_fprintf().

    + +

    »sets and quick memory +allocation

    + +

    You can use mem_r.c and qset_r.c individually. Mem_r.c +implements quick-fit memory allocation. It is faster than +malloc/free in applications that allocate and deallocate lots of +memory.

    + +

    Qset_r.c implements sets and related collections. It's +the inner loop of Qhull, so speed is more important than +abstraction. Set iteration is particularly fast. qset_r.c +just includes the functions needed for Qhull.

    + +

    »Delaunay triangulations +and point indices

    + +

    Here some unchecked code to print the point indices of each +Delaunay triangle. Use option 'QJ' if you want to avoid +non-simplicial facets. Note that upper Delaunay regions are +skipped. These facets correspond to the furthest-site Delaunay +triangulation.

    + +
    +
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  FORALLfacets {
    +    if (!facet->upperdelaunay) {
    +      printf ("%d", qh_setsize (facet->vertices);
    +      FOREACHvertex_(facet->vertices)
    +        printf (" %d", qh_pointid (vertex->point));
    +      printf ("\n");
    +    }
    +  }
    +
    +
    +
    + +

    »locate a facet with +qh_findbestfacet()

    + +

    The routine qh_findbestfacet in poly2_r.c is +particularly useful. It uses a directed search to locate the +facet that is furthest below a point. For Delaunay +triangulations, this facet is the Delaunay triangle that contains +the lifted point. For convex hulls, the distance of a point to +the convex hull is either the distance to this facet or the +distance to a subface of the facet.

    + +
    +

    Warning: If triangulated output ('Qt') and +the best facet is triangulated, qh_findbestfacet() returns one of +the corresponding 'tricoplanar' facets. The actual best facet may be a different +tricoplanar facet. +

    +See qh_nearvertex() in poly2.c for sample code to visit each +tricoplanar facet. To identify the correct tricoplanar facet, +see Devillers, et. al., ['01] +and Mucke, et al ['96]. If you +implement this test in general dimension, please notify +qhull@qhull.org. +

    + +

    qh_findbestfacet performs an exhaustive search if its directed +search returns a facet that is above the point. This occurs when +the point is inside the hull or if the curvature of the convex +hull is less than the curvature of a sphere centered at the point +(e.g., a point near a lens-shaped convex hull). When the later +occurs, the distance function is bimodal and a directed search +may return a facet on the far side of the convex hull.

    + +

    Algorithms that retain the previously constructed hulls +usually avoid an exhaustive search for the best facet. You may +use a hierarchical decomposition of the convex hull [Dobkin and +Kirkpatrick '90].

    + +

    To use qh_findbestfacet with Delaunay triangulations, lift the +point to a paraboloid by summing the squares of its coordinates +(see qh_setdelaunay in geom2_r.c). Do not scale the input with +options 'Qbk', 'QBk', 'QbB' or 'Qbb'. See Mucke, et al ['96] for a good point location +algorithm.

    + +

    The intersection of a ray with the convex hull may be found by +locating the facet closest to a distant point on the ray. +Intersecting the ray with the facet's hyperplane gives a new +point to test.

    + +

    »on-line construction with +qh_addpoint()

    + +

    The Qhull library may be used for the on-line construction of +convex hulls, Delaunay triangulations, and halfspace +intersections about a point. It may be slower than implementations that retain +intermediate convex hulls (e.g., Clarkson's hull +program). These implementations always use a directed search. +For the on-line construction of convex hulls and halfspace +intersections, Qhull may use an exhaustive search +(qh_findbestfacet).

    + +

    You may use qh_findbestfacet and qh_addpoint (libqhull.c) to add a point to +a convex hull. Do not modify the point's coordinates since +qh_addpoint does not make a copy of the coordinates. For Delaunay +triangulations, you need to lift the point to a paraboloid by +summing the squares of the coordinates (see qh_setdelaunay in +geom2.c). Do not scale the input with options 'Qbk', 'QBk', 'QbB' +or 'Qbb'. Do not deallocate the point's coordinates. You need to +provide a facet that is below the point (qh_findbestfacet). +

    + +

    You can not delete points. Another limitation is that Qhull +uses the initial set of points to determine the maximum roundoff +error (via the upper and lower bounds for each coordinate).

    + +

    For many applications, it is better to rebuild the hull from +scratch for each new point. This is especially true if the point +set is small or if many points are added at a time.

    + +

    Calling qh_addpoint from your program may be slower than +recomputing the convex hull with qh_qhull. This is especially +true if the added points are not appended to the qh first_point +array. In this case, Qhull must search a set to determine a +point's ID. [R. Weber]

    + +

    See user_eg.c for examples of the on-line construction of +convex hulls, Delaunay triangulations, and halfspace +intersections. The outline is:

    + +
    +
    +initialize qhull with an initial set of points
    +qh_qhull();
    +
    +for each additional point p
    +   append p to the end of the point array or allocate p separately
    +   lift p to the paraboloid by calling qh_setdelaunay
    +   facet= qh_findbestfacet (p, !qh_ALL, &bestdist, &isoutside);
    +   if (isoutside)
    +      if (!qh_addpoint (point, facet, False))
    +         break;  /* user requested an early exit with 'TVn' or 'TCn' */
    +
    +call qh_check_maxout() to compute outer planes
    +terminate qhull
    +
    + +

    »Constrained +Delaunay triangulation

    + +

    With a fair amount of work, Qhull is suitable for constrained +Delaunay triangulation. See Shewchuk, ACM Symposium on +Computational Geometry, Minneapolis 1998.

    + +

    Here's a quick way to add a constraint to a Delaunay +triangulation: subdivide the constraint into pieces shorter than +the minimum feature separation. You will need an independent +check of the constraint in the output since the minimum feature +separation may be incorrect. [H. Geron]

    + +

    »Tricoplanar facets and option 'Qt'

    + +

    Option 'Qt' triangulates non-simplicial +facets (e.g., a square facet in 3-d or a cubical facet in 4-d). +All facets share the same apex (i.e., the first vertex in facet->vertices). +For each triangulated facet, Qhull +sets facet->tricoplanar true and copies facet->center, facet->normal, facet->offset, and facet->maxoutside. One of +the facets owns facet->normal; its facet->keepcentrum is true. +If facet->isarea is false, facet->triowner points to the owning +facet. + +

    Qhull sets facet->degenerate if the facet's vertices belong +to the same ridge of the non-simplicial facet. + +

    To visit each tricoplanar facet of a non-simplicial facet, +either visit all neighbors of the apex or recursively visit +all neighbors of a tricoplanar facet. The tricoplanar facets +will have the same facet->center.

    + +

    See qh_detvridge for an example of ignoring tricoplanar facets.

    + +

    »Voronoi vertices of a +region

    + +

    The following code iterates over all Voronoi vertices for each +Voronoi region. Qhull computes Voronoi vertices from the convex +hull that corresponds to a Delaunay triangulation. An input site +corresponds to a vertex of the convex hull and a Voronoi vertex +corresponds to an adjacent facet. A facet is +"upperdelaunay" if it corresponds to a Voronoi vertex +"at-infinity". Qhull uses qh_printvoronoi in io.c +for 'qvoronoi o'

    + +
    +
    +/* please review this code for correctness */
    +qh_setvoronoi_all();
    +FORALLvertices {
    +   site_id = qh_pointid (vertex->point);
    +   if (qh hull_dim == 3)
    +      qh_order_vertexneighbors(vertex);
    +   infinity_seen = 0;
    +   FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay) {
    +        if (!infinity_seen) {
    +          infinity_seen = 1;
    +          ... process a Voronoi vertex "at infinity" ...
    +        }
    +      }else {
    +        voronoi_vertex = neighbor->center;
    +        ... your code goes here ...
    +      }
    +   }
    +}
    +
    +
    + +

    »Voronoi vertices of a +ridge

    + +

    Qhull uses qh_printvdiagram() in io.c to print the ridges of a +Voronoi diagram for option 'Fv'. +The helper function qh_eachvoronoi() does the real work. It calls +the callback 'printvridge' for each ridge of the Voronoi diagram. +

    + +

    You may call qh_printvdiagram2(), qh_eachvoronoi(), or +qh_eachvoronoi_all() with your own function. If you do not need +the total number of ridges, you can skip the first call to +qh_printvdiagram2(). See qh_printvridge() and qh_printvnorm() in +io.c for examples.

    + +

    »vertex neighbors of +a vertex

    + +

    To visit all of the vertices that share an edge with a vertex: +

    + +
      +
    • Generate neighbors for each vertex with + qh_vertexneighbors in poly2.c.
    • +
    • For simplicial facets, visit the vertices of each + neighbor
    • +
    • For non-simplicial facets,
        +
      • Generate ridges for neighbors with qh_makeridges + in merge.c.
      • +
      • Generate ridges for a vertex with qh_vertexridges + in merge.c.
      • +
      • Visit the vertices of these ridges.
      • +
      +
    • +
    + +

    For non-simplicial facets, the ridges form a simplicial +decomposition of the (d-2)-faces between each pair of facets -- +if you need 1-faces, you probably need to generate the full face +graph of the convex hull.

    + +

    »Performance of +Qhull

    + +

    Empirically, Qhull's performance is balanced in the sense that +the average case happens on average. This may always be true if +the precision of the input is limited to at most O(log n) +bits. Empirically, the maximum number of vertices occurs at the +end of constructing the hull.

    + +

    Let n be the number of input points, v be the +number of output vertices, and f_v be the maximum number +of facets for a convex hull of v vertices. If both +conditions hold, Qhull runs in O(n log v) in 2-d and 3-d +and O(n f_v/v) otherwise. The function f_v +increases rapidly with dimension. It is O(v^floor(d/2) / +floor(d/2)!).

    + +

    The time complexity for merging is unknown. Options 'C-0' and 'Qx' +(defaults) handle precision problems due to floating-point +arithmetic. They are optimized for simplicial outputs.

    + +

    When running large data sets, you should monitor Qhull's +performance with the 'TFn' option. +The time per facet is approximately constant. In high-d with many +merged facets, the size of the ridge sets grows rapidly. For +example the product of 8-d simplices contains 18 facets and +500,000 ridges. This will increase the time needed per facet.

    + +

    As dimension increases, the number of facets and ridges in a +convex hull grows rapidly for the same number of vertices. For +example, the convex hull of 300 cospherical points in 6-d has +30,000 facets.

    + +

    If Qhull appears to stop processing facets, check the memory +usage of Qhull. If more than 5-10% of Qhull is in virtual memory, +its performance will degrade rapidly.

    + +

    When building hulls in 20-d and higher, you can follow the +progress of Qhull with option 'T1'. +It reports each major event in processing a point.

    + +

    To reduce memory requirements, recompile Qhull for +single-precision reals (REALfloat in user.h). +Single-precision does not work with joggle ('QJ'). Check qh_MEMalign in user.h +and the match between free list sizes and data structure sizes +(see the end of the statistics report from 'Ts'). If free list sizes do not match, +you may be able to use a smaller qh_MEMalign. Setting +qh_COMPUTEfurthest saves a small amount of memory, as does +clearing qh_MAXoutside (both in user.h).

    + +

    Shewchuk is working on a 3-d version of his triangle +program. It is optimized for 3-d simplicial Delaunay triangulation +and uses less memory than Qhull.

    + +

    To reduce the size of the Qhull executable, consider +qh_NOtrace and qh_KEEPstatistics 0 in user.h. By +changing user.c you can also remove the input/output +code in io.c. If you don't need facet merging, then +version 1.01 of Qhull is much smaller. It contains some bugs that +prevent Qhull from initializing in simple test cases. It is +slower in high dimensions.

    + +

    The precision options, 'Vn', 'Wn', 'Un'. +'A-n', 'C-n', +'An', 'Cn', +and 'Qx', may have large effects on +Qhull performance. You will need to experiment to find the best +combination for your application.

    + +

    The verify option ('Tv') checks +every point after the hull is complete. If facet merging is used, +it checks that every point is inside every facet. This can take a +very long time if there are many points and many facets. You can +interrupt the verify without losing your output. If facet merging +is not used and there are many points and facets, Qhull uses a +directed search instead of an exhaustive search. This should be +fast enough for most point sets. Directed search is not used for +facet merging because directed search was already used for +updating the facets' outer planes.

    + +

    The check-frequently option ('Tc') +becomes expensive as the dimension increases. The verify option +('Tv') performs many of the same +checks before outputting the results.

    + +

    Options 'Q0' (no pre-merging), 'Q3' (no checks for redundant vertices), +'Q5' (no updates for outer planes), +and 'Q8' (no near-interior points) +increase Qhull's speed. The corresponding operations may not be +needed in your application.

    + +

    In 2-d and 3-d, a partial hull may be faster to produce. +Option 'QgGn' only builds facets +visible to point n. Option 'QgVn' +only builds facets that contain point n. In higher-dimensions, +this does not reduce the number of facets.

    + +

    User.h includes a number of performance-related +constants. Changes may improve Qhull performance on your data +sets. To understand their effect on performance, you will need to +read the corresponding code.

    + +

    GNU gprof reports that the dominate cost for 3-d +convex hull of cosperical points is qh_distplane(), mainly called +from qh_findbestnew(). The dominate cost for 3-d Delaunay triangulation +is creating new facets in qh_addpoint(), while qh_distplane() remains +the most expensive function. + +

    +

    »Enhancements to Qhull

    + +

    There are many ways in which Qhull can be improved.

    + +
    +[Jan 2016] Suggestions
    +------------
    +To do for a future verson of Qhull
    + - Add a post-merge pass for Delaunay slivers.  Merge into a neighbor with a circumsphere that includes the opposite point. [M. Treacy]
    + - Add a merge pass before cone creation to remove duplicate subridges between horizon facets
    + - Option to add a bounding box for Delaunay triangulations, e,g., nearly coincident points
    + - Report error when rbox Cn,r,m does not produce points (e.g., 'r' distributions)
    + - Rescale output to match 'QbB' on input [J. Metz, 1/30/2014 12:21p]
    + - Run through valgrind
    + - Notes to compgeom on conformant triangulation and Voronoi volume
    + - Git: Create signed tags for Qhull versions
    + - Implement weighted Delaunay triangulation and weighted Voronoi diagram [A. Liebscher]
    +   e.g., Sugihara, "Three-dimensional convex hull as a fruitful source of diagrams," Theoretical Computer Science, 2000, 235:325-337
    + - testqset: test qh_setdelnth and move-to-front
    + - Makefile: Re-review gcc/g++ warnings.  OK in 2011.
    + - Break up -Wextra into its components or figure out how to override -Wunused-but-set-variable
    +   unused-but-set-variable is reporting incorrectly.  All instances are annotated.
    + - CMakelists.txt:  Why are files duplicated for cmake build
    + - CMakeLists.txt: configure the 'ctest' target
    + - The size of maxpoints in qh_maxsimplex should be d+3 unique points to help avoid QH6154
    +
    + - Can countT be defined as 'int', 'unsigned int', or 64-bit int?
    +   countT is currently defined as 'int' in qset_r.h
    +   Vertex ID and ridge ID perhaps should be countT, They are currently 'unsigned'
    +   Check use of 'int' vs. countT in all cpp code
    +   Check use of 'int' vs. countT in all c code
    +   qset_r.h defines countT -- duplicates code in user_r.h -- need to add to qset.h/user.h
    +   countT -1 used as a flag in Coordinates.mid(), QhullFacet->id()
    +   Also QhullPoints indexOf and lastIndexOf
    +   Also QhullPointSet indexOf and lastIndexOf
    +   Coordinates.indexOf assumes countT is signed (from end)
    +   Coordinates.lastIndexOf assumes countT is signed (from end)
    +   All error messages with countT are wrong, convert to int?
    +   RboxPoints.qh_fprintf_rbox, etc. message 9393 assumes countT but may be int, va_arg(args, countT);  Need to split
    +
    +------------
    +To do for a furture version of the C++ interface
    + - Fix C++ memory leak in user_eg3 [M. Sandim]
    + - Document C++ using Doxygen conventions (//! and //!<)
    + - Should Qhull manage the output formats for doubles?  QH11010 user_r.h defines qh_REAL_1 as %6.8g
    + - Allocate memory for QhullSet using Qhull.qhmem.  Create default constructors for QhullVertexSet etc.  Also mid() etc.
    + - Add interior point for automatic translation?
    + - Add hasNext() to all next() iterators (e.g., QhullVertex)
    + - Add defineAs() to each object
    + - Write a program with concurrent Qhull
    + - Write QhullStat and QhullStat_test
    + - Add QList and vector instance of facetT*, etc.
    + - Generalize QhullPointSetIterator
    + - qh-code.htm: Document changes to C++ interface.
    +      Organize C++ documentation into collection classes, etc.
    + - Review all C++ classes and C++ tests
    + - QhullVertexSet uses QhullSetBase::referenceSetT() to free it's memory.   Probably needed elsewhere
    + - The Boost Graph Library provides C++ classes for graph data structures. It may help
    +   enhance Qhull's C++ interface [Dr. Dobb's 9/00 p. 29-38; OOPSLA '99 p. 399-414].
    +
    +[Jan 2010] Suggestions
    + - Generate vcproj from qtpro files
    +   cd qtpro && qmake -spec win32-msvc2005 -tp vc -recursive
    +   sed -i 's/C\:\/bash\/local\/qhull\/qtpro\///' qhull-all.sln
    +   Change qhullcpp to libqhull.dll
    +   Allow both builds on same host (keep /tmp separate)
    + - Make distribution -- remove tmp, news, .git, leftovers from project, change CRLF
    +     search for 2010.1, Dates
    +     qhulltest --all added to output
    +     Add md5sum
    +     Add test of user_eg3, etc.
    + - C++ class for access to statistics, accumulate vs. add
    + - Add dialog box to RoadError-- a virtual function?
    + - Option 'Gt' does not make visible all facets of the mesh example, rbox 32 M1,0,1 | qhull d Gt
    + - Option to select bounded Voronoi regions [A. Uzunovic]
    + - Merge small volume boundary cells into unbounded regions [Dominik Szczerba]
    + - Postmerge with merge options
    + - Add const to C code
    + - Add modify operators and MutablePointCoordinateIterator to PointCoordinates
    + - Add Qtest::toString() functions for QhullPoint and others.  QByteArray and qstrdup()
    + - Fix option Qt for conformant triangulations of merged facets
    + - Investigate flipped facet -- rbox 100 s D3 t1263080158 | qhull R1e-3 Tcv Qc
    + - Add doc comments to c++ code
    + - Measure performance of Qhull, seconds per point by dimension
    + - Report potential wraparound of 64-bit ints -- e.g., a large set or points
    +
    +Documentation
    +- Qhull::addPoint().  Problems with qh_findbestfacet and otherpoints see
    +   qh-code.htm#inc on-line construction with qh_addpoint()
    +- How to handle 64-bit possible loss of data.  WARN64, ptr_intT, size_t/int
    +- Show custom of qh_fprintf
    +- grep 'qh_mem ' x | sort | awk '{ print $2; }' | uniq -c | grep -vE ' (2|4|6|8|10|12|14|16|20|64|162)[^0-9]'
    +- qtpro/qhulltest contains .pro and Makefile.  Remove Makefiles by setting shadow directory to ../../tmp/projectname
    +- Rules for use of qh_qh and multi processes
    +    UsingQhull
    +    errorIfAnotherUser
    +    ~QhullPoints() needs ownership of qh_qh
    +    Does !qh_pointer work?
    +    When is qh_qh required?  Minimize the time.
    +   qhmem, qhstat.ferr
    +   qhull_inuse==1 when qhull globals active [not useful?]
    +   rbox_inuse==1 when rbox globals active
    +   - Multithreaded -- call largest dimension for infinityPoint() and origin()
    + - Better documentation for qhmem totshort, freesize, etc.
    + - how to change .h, .c, and .cpp to text/html.  OK in Opera
    + - QhullVertex.dimension() is not quite correct, epensive
    + - Check globalAngleEpsilon
    + - Deprecate save_qhull()
    +
    +[Dec 2003] Here is a partial list:
    + - fix finddelaunay() in user_eg.c for tricoplanar facets
    + - write a BGL, C++ interface to Qhull
    +     http://www.boost.org/libs/graph/doc/table_of_contents.html
    + - change qh_save_qhull to swap the qhT structure instead of using pointers
    + - change error handling and tracing to be independent of 'qh ferr'
    + - determine the maximum width for a given set of parameters
    + - prove that directed search locates all coplanar facets
    + - in high-d merging, can a loop of facets become disconnected?
    + - find a way to improve inner hulls in 5-d and higher
    + - determine the best policy for facet visibility ('Vn')
    + - determine the limitations of 'Qg'
    +
    +Precision improvements:
    + - For 'Qt', resolve cross-linked, butterfly ridges.
    +     May allow retriangulation in qh_addpoint().
    + - for Delaunay triangulations ('d' or 'v') under joggled input ('QJ'),
    +     remove vertical facets whose lowest vertex may be coplanar with convex hull
    + - review use of 'Qbb' with 'd QJ'.  Is MAXabs_coord better than MAXwidth?
    + - check Sugihara and Iri's better in-sphere test [Canadian
    +     Conf. on Comp. Geo., 1989; Univ. of Tokyo RMI 89-05]
    + - replace centrum with center of mass and facet area
    + - handle numeric overflow in qh_normalize and elsewhere
    + - merge flipped facets into non-flipped neighbors.
    +     currently they merge into best neighbor (appears ok)
    + - determine min norm for Cramer's rule (qh_sethyperplane_det).  It looks high.
    + - improve facet width for very narrow distributions
    +
    +New features:
    + - implement Matlab's tsearch() using Qhull
    + - compute volume of Voronoi regions.  You need to determine the dual face
    +   graph in all dimensions [see Clarkson's hull program]
    + - compute alpha shapes [see Clarkson's hull program]
    + - implement deletion of Delaunay vertices
    +      see Devillers, ACM Symposium on Computational Geometry, Minneapolis 1999.
    + - compute largest empty circle [see O'Rourke, chapter 5.5.3] [Hase]
    + - list redundant (i.e., coincident) vertices [Spitz]
    + - implement Mucke, et al, ['96] for point location in Delaunay triangulations
    + - implement convex hull of moving points
    + - implement constrained Delaunay diagrams
    +      see Shewchuk, ACM Symposium on Computational Geometry, Minneapolis 1998.
    + - estimate outer volume of hull
    + - automatically determine lower dimensional hulls
    + - allow "color" data for input points
    +      need to insert a coordinate for Delaunay triangulations
    +
    +Input/output improvements:
    + - Support the VTK Visualization Toolkit, http://www.kitware.com/vtk.html
    + - generate output data array for Qhull library [Gautier]
    + - need improved DOS window with screen fonts, scrollbar, cut/paste
    + - generate Geomview output for Voronoi ridges and unbounded rays
    + - generate Geomview output for halfspace intersection
    + - generate Geomview display of furthest-site Voronoi diagram
    + - use 'GDn' to view 5-d facets in 4-d
    + - convert Geomview output for other 3-d viewers
    + - add interactive output option to avoid recomputing a hull
    + - orient vertex neighbors for 'Fv' in 3-d and 2-d
    + - track total number of ridges for summary and logging
    +
    +Performance improvements:
    + - optimize Qhull for 2-d Delaunay triangulations
    + -   use O'Rourke's '94 vertex->duplicate_edge
    + -   add bucketing
    + -   better to specialize all of the code (ca. 2-3x faster w/o merging)
    + - use updated LU decomposition to speed up hyperplane construction
    + -        [Gill et al. 1974, Math. Comp. 28:505-35]
    + - construct hyperplanes from the corresponding horizon/visible facets
    + - for merging in high d, do not use vertex->neighbors
    +
    +
    + +

    Please let us know about your applications and improvements.

    + +
    + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of +Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull code: Table of Contents
    +Dn: Qhull functions, macros, and data +structures + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see changes.txt

    + + diff --git a/xs/src/qhull/html/qh-eg.htm b/xs/src/qhull/html/qh-eg.htm new file mode 100644 index 0000000000..a08f0d13f4 --- /dev/null +++ b/xs/src/qhull/html/qh-eg.htm @@ -0,0 +1,693 @@ + + + + +Examples of Qhull + + + + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull examples: Table of Contents (please wait +while loading)
    + +


    + +

    [halfspace] Examples of Qhull

    + +

    This section of the Qhull manual will introduce you to Qhull +and its options. Each example is a file for viewing with Geomview. You will need to +use a Unix computer with a copy of Geomview. +

    +If you are not running Unix, you can view pictures +for some of the examples. To understand Qhull without Geomview, try the +examples in Programs and +Programs/input. You can also try small +examples that you compute by hand. Use rbox +to generate examples. +

    +To generate the Geomview examples, execute the shell script eg/q_eg. +It uses rbox. The shell script eg/q_egtest generates +test examples, and eg/q_test exercises the code. If you +find yourself viewing the inside of a 3-d example, use Geomview's +normalization option on the 'obscure' menu.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »Qhull examples: Table of +Contents

    + + + +
    + + +
    + +

    »2-d and 3-d examples

    + +

    »rbox c D3 | qconvex G +>eg.01.cube

    + +

    The first example is a cube in 3-d. The color of each facet +indicates its normal. For example, normal [0,0,1] along the Z +axis is (r=0.5, g=0.5, b=1.0). With the 'Dn' option in rbox, +you can generate hypercubes in any dimension. Above 7-d the +number of intermediate facets grows rapidly. Use 'TFn' to track qconvex's progress. Note +that each facet is a square that qconvex merged from coplanar +triangles.

    + +

    »rbox c d G3.0 | qconvex G +>eg.02.diamond.cube

    + +

    The second example is a cube plus a diamond ('d') scaled by rbox's +'G' option. In higher dimensions, diamonds are much simpler than +hypercubes.

    + +

    »rbox s 100 D3 | qconvex G +>eg.03.sphere

    + +

    The rbox s option generates random points and +projects them to the d-sphere. All points should be on the convex +hull. Notice that random points look more clustered than you +might expect. You can get a smoother distribution by merging +facets and printing the vertices, e.g., rbox 1000 s | qconvex +A-0.95 p | qconvex G >eg.99.

    + +

    »rbox s 100 D2 | qconvex G +>eg.04.circle

    + +

    In 2-d, there are many ways to generate a convex hull. One of +the earliest algorithms, and one of the fastest, is the 2-d +Quickhull algorithm [c.f., Preparata & Shamos '85]. It was the model for +Qhull.

    + +

    »rbox 10 l | qconvex G +>eg.05.spiral

    + +

    One rotation of a spiral.

    + +

    »rbox 1000 D2 | qconvex C-0.03 +Qc Gapcv >eg.06.merge.square

    + +

    This demonstrates how Qhull handles precision errors. Option 'C-0.03' requires a clearly convex angle +between adjacent facets. Otherwise, Qhull merges the facets.

    + +

    This is the convex hull of random points in a square. The +facets have thickness because they must be outside all points and +must include their vertices. The colored lines represent the +original points and the spheres represent the vertices. Floating +in the middle of each facet is the centrum. Each centrum is at +least 0.03 below the planes of its neighbors. This guarantees +that the facets are convex.

    + +

    »rbox 1000 D3 | qconvex G +>eg.07.box

    + +

    Here's the same distribution but in 3-d with Qhull handling +machine roundoff errors. Note the large number of facets.

    + +

    »rbox c G0.4 s 500 | qconvex G +>eg.08a.cube.sphere

    + +

    The sphere is just barely poking out of the cube. Try the same +distribution with randomization turned on ('Qr'). This turns Qhull into a +randomized incremental algorithm. To compare Qhull and +randomization, look at the number of hyperplanes created and the +number of points partitioned. Don't compare CPU times since Qhull's +implementation of randomization is inefficient. The number of +hyperplanes and partitionings indicate the dominant costs for +Qhull. With randomization, you'll notice that the number of +facets created is larger than before. This is especially true as +you increase the number of points. It is because the randomized +algorithm builds most of the sphere before it adds the cube's +vertices.

    + +

    »rbox d G0.6 s 500 | qconvex G +>eg.08b.diamond.sphere

    + +

    This is a combination of the diamond distribution and the +sphere.

    + +

    »rbox 100 L3 G0.5 s | qconvex +G >eg.09.lens

    + +

    Each half of the lens distribution lies on a sphere of radius +three. A directed search for the furthest facet below a point +(e.g., qh_findbest in geom.c) may fail if started from +an arbitrary facet. For example, if the first facet is on the +opposite side of the lens, a directed search will report that the +point is inside the convex hull even though it is outside. This +problem occurs whenever the curvature of the convex hull is less +than a sphere centered at the test point.

    + +

    To prevent this problem, Qhull does not use directed search +all the time. When Qhull processes a point on the edge of the +lens, it partitions the remaining points with an exhaustive +search instead of a directed search (see qh_findbestnew in geom2.c). +

    + +

    »How Qhull adds a point

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qconvex Ga QG0 >eg.10a.sphere.visible

    + +

    The next 4 examples show how Qhull adds a point. The point +[0.5,0.5,0.5] is at one corner of the bounding box. Qhull adds a +point using the beneath-beyond algorithm. First Qhull finds all +of the facets that are visible from the point. Qhull will replace +these facets with new facets.

    + +

    »rbox 100 s +P0.5,0.5,0.5|qconvex Ga QG-0 >eg.10b.sphere.beyond

    + +

    These are the facets that are not visible from the point. +Qhull will keep these facets.

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qconvex PG Ga QG0 >eg.10c.sphere.horizon

    + +

    These facets are the horizon facets; they border the visible +facets. The inside edges are the horizon ridges. Each horizon +ridge will form the base for a new facet.

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qconvex Ga QV0 PgG >eg.10d.sphere.cone

    + +

    This is the cone of points from the new point to the horizon +facets. Try combining this image with eg.10c.sphere.horizon +and eg.10a.sphere.visible. +

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qconvex Ga >eg.10e.sphere.new

    + +

    This is the convex hull after [0.5,0.5,0.5] has been added. +Note that in actual practice, the above sequence would never +happen. Unlike the randomized algorithms, Qhull always processes +a point that is furthest in an outside set. A point like +[0.5,0.5,0.5] would be one of the first points processed.

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qhull Ga QV0g Q0 >eg.14.sphere.corner

    + +

    The 'QVn', 'QGn ' and 'Pdk' +options define good facets for Qhull. In this case 'QV0' defines the 0'th point +[0.5,0.5,0.5] as the good vertex, and 'Qg' +tells Qhull to only build facets that might be part of a good +facet. This technique reduces output size in low dimensions. It +does not work with facet merging.

    + +

    »Triangulated output or joggled input

    + +

    »rbox 500 W0 | qconvex QR0 Qc Gvp >eg.15a.surface

    + +

    This is the convex hull of 500 points on the surface of +a cube. Note the large, non-simplicial facet for each face. +Qhull merges non-convex facets. + +

    If the facets were not merged, Qhull +would report precision problems. For example, turn off facet merging +with option 'Q0'. Qhull may report concave +facets, flipped facets, or other precision errors: +

    +rbox 500 W0 | qhull QR0 Q0 +
    + +

    +

    »rbox 500 W0 | qconvex QR0 Qt Qc Gvp >eg.15b.triangle

    + +

    Like the previous examples, this is the convex hull of 500 points on the +surface of a cube. Option 'Qt' triangulates the +non-simplicial facets. Triangulated output is +particularly helpful for Delaunay triangulations. + +

    +

    »rbox 500 W0 | qconvex QR0 QJ5e-2 Qc Gvp >eg.15c.joggle

    + +

    This is the convex hull of 500 joggled points on the surface of +a cube. The option 'QJ5e-2' +sets a very large joggle to make the effect visible. Notice +that all of the facets are triangles. If you rotate the cube, +you'll see red-yellow lines for coplanar points. +

    +With option 'QJ', Qhull joggles the +input to avoid precision problems. It adds a small random number +to each input coordinate. If a precision +error occurs, it increases the joggle and tries again. It repeats +this process until no precision problems occur. +

    +Joggled input is a simple solution to precision problems in +computational geometry. Qhull can also merge facets to handle +precision problems. See Merged facets or joggled input. + +

    »Delaunay and Voronoi diagrams

    + +

    »qdelaunay Qt +<eg.data.17 GnraD2 >eg.17a.delaunay.2

    + +

    +The input file, eg.data.17, consists of a square, 15 random +points within the outside half of the square, and 6 co-circular +points centered on the square. + +

    The Delaunay triangulation is the triangulation with empty +circumcircles. The input for this example is unusual because it +includes six co-circular points. Every triangular subset of these +points has the same circumcircle. Option 'Qt' +triangulates the co-circular facet.

    + +

    »qdelaunay <eg.data.17 +GnraD2 >eg.17b.delaunay.2i

    + +

    This is the same example without triangulated output ('Qt'). qdelaunay +merges the non-unique Delaunay triangles into a hexagon.

    + +

    »qdelaunay <eg.data.17 +Ga >eg.17c.delaunay.2-3

    + +

    This is how Qhull generated both diagrams. Use Geomview's +'obscure' menu to turn off normalization, and Geomview's +'cameras' menu to turn off perspective. Then load this object +with one of the previous diagrams.

    + +

    The points are lifted to a paraboloid by summing the squares +of each coordinate. These are the light blue points. Then the +convex hull is taken. That's what you see here. If you look up +the Z-axis, you'll see that points and edges coincide.

    + +

    »qvoronoi QJ +<eg.data.17 Gna >eg.17d.voronoi.2

    + +

    The Voronoi diagram is the dual of the Delaunay triangulation. +Here you see the original sites and the Voronoi vertices. +Notice the each +vertex is equidistant from three sites. The edges indicate the +Voronoi region for a site. Qhull does not draw the unbounded +edges. Instead, it draws extra edges to close the unbounded +Voronoi regions. You may find it helpful to enclose the input +points in a square. You can compute the unbounded +rays from option 'Fo'. +

    + +

    Instead +of triangulated output ('Qt'), this +example uses joggled input ('QJ'). +Normally, you should use neither 'QJ' nor 'Qt' for Voronoi diagrams. + +

    »qvoronoi <eg.data.17 +Gna >eg.17e.voronoi.2i

    + +

    This looks the same as the previous diagrams, but take a look +at the data. Run 'qvoronoi p <eg/eg.data.17'. This prints +the Voronoi vertices. + +

    With 'QJ', there are four nearly identical Voronoi vertices +within 10^-11 of the origin. Option 'QJ' joggled the input. After the joggle, +the cocircular +input sites are no longer cocircular. The corresponding Voronoi vertices are +similar but not identical. + +

    This example does not use options 'Qt' or 'QJ'. The cocircular +input sites define one Voronoi vertex near the origin.

    + +

    Option 'Qt' would triangulate the corresponding Delaunay region into +four triangles. Each triangle is assigned the same Voronoi vertex.

    + +

    » rbox c G0.1 d | +qdelaunay Gt Qz <eg.17f.delaunay.3

    + +

    This is the 3-d Delaunay triangulation of a small cube inside +a prism. Since the outside ridges are transparent, it shows the +interior of the outermost facets. If you slice open the +triangulation with Geomview's ginsu, you will see that the innermost +facet is a cube. Note the use of 'Qz' +to add a point "at infinity". This avoids a degenerate +input due to cospherical points.

    + +

    »rbox 10 D2 d | qdelaunay +Qu G >eg.18a.furthest.2-3

    + +

    The furthest-site Voronoi diagram contains Voronoi regions for +points that are furthest from an input site. It is the +dual of the furthest-site Delaunay triangulation. You can +determine the furthest-site Delaunay triangulation from the +convex hull of the lifted points (eg.17c.delaunay.2-3). +The upper convex hull (blue) generates the furthest-site Delaunay +triangulation.

    + +

    »rbox 10 D2 d | qdelaunay +Qu Pd2 G >eg.18b.furthest-up.2-3

    + +

    This is the upper convex hull of the preceding example. The +furthest-site Delaunay triangulation is the projection of the +upper convex hull back to the input points. The furthest-site +Voronoi vertices are the circumcenters of the furthest-site +Delaunay triangles.

    + +

    »rbox 10 D2 d | qvoronoi +Qu Gv >eg.18c.furthest.2

    + +

    This shows an incomplete furthest-site Voronoi diagram. It +only shows regions with more than two vertices. The regions are +artificially truncated. The actual regions are unbounded. You can +print the regions' vertices with 'qvoronoi Qu o'.

    + +

    Use Geomview's 'obscure' menu to turn off normalization, and +Geomview's 'cameras' menu to turn off perspective. Then load this +with the upper convex hull.

    + +

    »rbox 10 D3 | qvoronoi QV5 +p | qconvex G >eg.19.voronoi.region.3

    + +

    This shows the Voronoi region for input site 5 of a 3-d +Voronoi diagram.

    + +

    »Facet merging for imprecision

    + +

    »rbox r s 20 Z1 G0.2 | +qconvex G >eg.20.cone

    + +

    There are two things unusual about this cone. +One is the large flat disk at one end and the other is the +rectangles about the middle. That's how the points were +generated, and if those points were exact, this is the correct +hull. But rbox used floating point arithmetic to +generate the data. So the precise convex hull should have been +triangles instead of rectangles. By requiring convexity, Qhull +has recovered the original design.

    + +

    »rbox 200 s | qhull Q0 +R0.01 Gav Po >eg.21a.roundoff.errors

    + +

    This is the convex hull of 200 cospherical points with +precision errors ignored ('Q0'). To +demonstrate the effect of roundoff error, we've added a random +perturbation ('R0.01') to every +distance and hyperplane calculation. Qhull, like all other convex +hull algorithms with floating point arithmetic, makes +inconsistent decisions and generates wildly wrong results. In +this case, one or more facets are flipped over. These facets have +the wrong color. You can also turn on 'normals' in Geomview's +appearances menu and turn off 'facing normals'. There should be +some white lines pointing in the wrong direction. These +correspond to flipped facets.

    + +

    Different machines may not produce this picture. If your +machine generated a long error message, decrease the number of +points or the random perturbation ('R0.01'). +If it did not report flipped facets, increase the number of +points or perturbation.

    + +

    »rbox 200 s | qconvex Qc +R0.01 Gpav >eg.21b.roundoff.fixed

    + +

    Qhull handles the random perturbations and returns an +imprecise sphere. +In this case, the output is a weak approximation to the points. +This is because a random perturbation of 'R0.01 ' is equivalent to losing all but +1.8 digits of precision. The outer planes float above the points +because Qhull needs to allow for the maximum roundoff error.

    +

    +If you start with a smaller random perturbation, you +can use joggle ('QJn') to avoid +precision problems. You need to set n significantly +larger than the random perturbation. For example, try +'rbox 200 s | qconvex Qc R1e-4 QJ1e-1'. + +

    »rbox 1000 s| qconvex C0.01 +Qc Gcrp >eg.22a.merge.sphere.01

    + +

    »rbox 1000 s| qconvex +C-0.01 Qc Gcrp >eg.22b.merge.sphere.-01

    + +

    »rbox 1000 s| qconvex C0.05 +Qc Gcrpv >eg.22c.merge.sphere.05

    + +

    »rbox 1000 s| qconvex +C-0.05 Qc Gcrpv >eg.22d.merge.sphere.-05

    + +

    The next four examples compare post-merging and pre-merging ('Cn' vs. 'C-n'). +Qhull uses '-' as a flag to indicate pre-merging.

    + +

    Post-merging happens after the convex hull is built. During +post-merging, Qhull repeatedly merges an independent set of +non-convex facets. For a given set of parameters, the result is +about as good as one can hope for.

    + +

    Pre-merging does the same thing as post-merging, except that +it happens after adding each point to the convex hull. With +pre-merging, Qhull guarantees a convex hull, but the facets are +wider than those from post-merging. If a pre-merge option is not +specified, Qhull handles machine round-off errors.

    + +

    You may see coplanar points appearing slightly outside +the facets of the last example. This is becomes Geomview moves +line segments forward toward the viewer. You can avoid the +effect by setting 'lines closer' to '0' in Geomview's camera menu. + +

    »rbox 1000 | qconvex s +Gcprvah C0.1 Qc >eg.23.merge.cube

    + +

    Here's the 3-d imprecise cube with all of the Geomview +options. There's spheres for the vertices, radii for the coplanar +points, dots for the interior points, hyperplane intersections, +centrums, and inner and outer planes. The radii are shorter than +the spheres because this uses post-merging ('C0.1') +instead of pre-merging. + +

    »4-d objects

    + +

    With Qhull and Geomview you can develop an intuitive sense of +4-d surfaces. When you get into trouble, think of viewing the +surface of a 3-d sphere in a 2-d plane.

    + +

    »rbox 5000 D4 | qconvex s GD0v +Pd0:0.5 C-0.02 C0.1 >eg.24.merge.cube.4d-in-3d

    + +

    Here's one facet of the imprecise cube in 4-d. It's projected +into 3-d (the 'GDn' option drops +dimension n). Each ridge consists of two triangles between this +facet and the neighboring facet. In this case, Geomview displays +the topological ridges, i.e., as triangles between three +vertices. That is why the cube looks lopsided.

    + +

    »rbox 5000 D4 | qconvex s +C-0.02 C0.1 Gh >eg.30.4d.merge.cube

    + +

    Here +is the equivalent in 4-d of the imprecise square +and imprecise cube. It's the imprecise convex +hull of 5000 random points in a hypercube. It's a full 4-d object +so Geomview's ginsu does not work. If you view it in +Geomview, you'll be inside the hypercube. To view 4-d objects +directly, either load the 4dview module or the ndview +module. For 4dview, you must have started Geomview +in the same directory as the object. For ndview, +initialize a set of windows with the prefab menu, and load the +object through Geomview. The 4dview module includes an +option for slicing along any hyperplane. If you do this in the x, +y, or z plane, you'll see the inside of a hypercube.

    + +

    The 'Gh' option prints the +geometric intersections between adjacent facets. Note the strong +convexity constraint for post-merging ('C0.1'). +It deletes the small facets.

    + +

    »rbox 20 D3 | qdelaunay G +>eg.31.4d.delaunay

    + +

    The Delaunay triangulation of 3-d sites corresponds to a 4-d +convex hull. You can't see 4-d directly but each facet is a 3-d +object that you can project to 3-d. This is exactly the same as +projecting a 2-d facet of a soccer ball onto a plane.

    + +

    Here we see all of the facets together. You can use Geomview's +ndview to look at the object from several directions. +Try turning on edges in the appearance menu. You'll notice that +some edges seem to disappear. That's because the object is +actually two sets of overlapping facets.

    + +

    You can slice the object apart using Geomview's 4dview. +With 4dview, try slicing along the w axis to get a +single set of facets and then slice along the x axis to look +inside. Another interesting picture is to slice away the equator +in the w dimension.

    + +

    »rbox 30 s D4 | qconvex s G +Pd0d1d2D3

    + +

    This is the positive octant of the convex hull of 30 4-d +points. When looking at 4-d, it's easier to look at just a few +facets at once. If you picked a facet that was directly above +you, then that facet looks exactly the same in 3-d as it looks in +4-d. If you pick several facets, then you need to imagine that +the space of a facet is rotated relative to its neighbors. Try +Geomview's ndview on this example.

    + +

    »Halfspace intersections

    + +

    »rbox 10 r s Z1 G0.3 | +qconvex G >eg.33a.cone

    + +

    »rbox 10 r s Z1 G0.3 | +qconvex FV n | qhalf G >eg.33b.cone.dual

    + +

    »rbox 10 r s Z1 G0.3 | +qconvex FV n | qhalf Fp | qconvex G >eg.33c.cone.halfspace

    + +

    These examples illustrate halfspace intersection. The first +picture is the convex hull of two 20-gons plus an apex. The +second picture is the dual of the first. Try loading both +at once. Each vertex of the second picture corresponds to a facet +or halfspace of the first. The vertices with four edges +correspond to a facet with four neighbors. Similarly the facets +correspond to vertices. A facet's normal coefficients divided by +its negative offset is the vertice's coordinates. The coordinates +are the intersection of the original halfspaces.

    + +

    The third picture shows how Qhull can go back and forth +between equivalent representations. It starts with a cone, +generates the halfspaces that define each facet of the cone, and +then intersects these halfspaces. Except for roundoff error, the +third picture is a duplicate of the first.

    + +
    + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull examples: Table of Contents (please wait +while loading)
    + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-faq.htm b/xs/src/qhull/html/qh-faq.htm new file mode 100644 index 0000000000..feda544a75 --- /dev/null +++ b/xs/src/qhull/html/qh-faq.htm @@ -0,0 +1,1547 @@ + + + + + + +Qhull FAQ + + + + + +

    Up: Home page for Qhull +(http://www.qhull.org)
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: FAQ: Table of Contents (please +wait while loading)
    + +


    + +

    [4-d cube] Frequently Asked Questions about Qhull

    +

    If your question does not appear here, see:

    + + + +

    Qhull is a general dimension code for computing convex hulls, +Delaunay triangulations, halfspace intersections about a point, +Voronoi diagrams, furthest-site Delaunay triangulations, and +furthest-site Voronoi diagrams. These structures have +applications in science, engineering, statistics, and +mathematics. For a detailed introduction, see O'Rourke ['94], Computational Geometry in C. +

    + +

    There are separate programs for each application of +Qhull. These programs disable experimental and inappropriate +options. If you prefer, you may use Qhull directly. All programs +run the same code. + +

    Version 3.1 added triangulated output ('Qt'). +It should be used for Delaunay triangulations instead of +using joggled input ('QJ'). + +

    Brad Barber, Arlington MA, +2010/01/09

    + +

    Copyright © 1998-2015 C.B. Barber

    + +
    + +

    »FAQ: Table of Contents

    + +

    Within each category, the most recently asked questions are +first. +

      +
    • Startup questions
        +
      • How do I run Qhull from Windows? +
      • How do I enter points for Qhull? +
      • How do I learn to use Qhull?
      • +
      +
    • Convex hull questions
        +
      • How do I report just the area and volume of a + convex hull? +
      • Why are there extra points in a 4-d or higher + convex hull? +
      • How do I report duplicate + vertices?
      • +
      +
    • Delaunay triangulation questions
        +
      • How do I get rid of nearly flat Delaunay + triangles? +
      • How do I find the Delaunay triangle or Voronoi + region that is closest to a point? + +
      • How do I compute the Delaunay triangulation of a + non-convex object? + +
      • How do I mesh a volume from a set of triangulated + surface points? + +
      • Can Qhull produce a triangular mesh for an + object? + +
      • For 3-d Delaunay triangulations, how do I + report the triangles of each tetrahedron? + +
      • How do I construct a 3-d Delaunay triangulation? +
      • How do I get the triangles for a 2-d Delaunay + triangulation and the vertices of its Voronoi diagram? +
      • Can Qhull triangulate a + hundred 16-d points?
      • +
      + +
    • Voronoi diagram questions
        +
      • See also "Delaunay diagram questions". Qhull computes the Voronoi diagram from the Delaunay triagulation. +
      • How do I compute the volume of a Voronoi region? +
      • How do I get the radii of the empty + spheres for each Voronoi vertex? + +
      • What is the Voronoi diagram of a square? + +
      • How do I construct the Voronoi diagram of + cospherical points? +
      • Can Qhull compute the unbounded rays of the + Voronoi diagram? +
      +
    • Approximation questions
        +
      • How do I approximate data + with a simplex?
      • +
      +
    • Halfspace questions
        +
      • How do I compute the + intersection of halfspaces with Qhull?
      • +
      +
    • Qhull library questions
        +
      • Is Qhull available for Mathematica, Matlab, or + Maple? + +
      • Why are there too few ridges? +
      • Can Qhull use coordinates without placing them in + a data file? +
      • How large are Qhull's data structures? +
      • Can Qhull construct convex hulls and Delaunay + triangulations one point at a time? +
      • How do I visit the ridges of a Delaunay + triangulation? +
      • How do I visit the Delaunay facets? +
      • When is a point outside or inside a facet? +
      • How do I find the facet that is closest to a + point? +
      • How do I find the Delaunay triangle or Voronoi + region that is closest to a point? +
      • How do I list the vertices? +
      • How do I test code that uses the Qhull library? +
      • When I compute a plane + equation from a facet, I sometimes get an + outward-pointing normal and sometimes an + inward-pointing normal
      • +
      +
    • +
    + +
    + +

    »Startup questions

    + +

    »How do I run Qhull +from Windows?

    + +

    Qhull is a console program. You will first need a command window +(i.e., a "command prompt"). You can double click on +'eg\Qhull-go.bat'.

    + +
      +
    • Type 'qconvex', 'qdelaunay', 'qhalf', 'qvoronoi, + 'qhull', and 'rbox' for a synopsis of each program. + +
    • Type 'rbox c D2 | qconvex s i' to compute the + convex hull of a square. + +
    • Type 'rbox c D2 | qconvex s i TO results.txt' to + write the results to the file 'results.txt'. A summary is still printed on + the the console. + +
    • Type 'rbox c D2' to see the input format for + qconvex. + +
    • Type 'qconvex < data.txt s i TO results.txt' to + read input data from 'data.txt'. + +
    • If you want to enter data by hand, type 'qconvex s i TO + results.txt' to read input data from the console. Type in + the numbers and end with a ctrl-D.
    • +
    + +

    »How do I enter +points for Qhull?

    + +

    Qhull takes its data from standard input. For example, create +a file named 'data.txt' with the following contents:

    + +
    +
    +2  #sample 2-d input
    +5  #number of points
    +1 2  #coordinates of points
    +-1.1 3
    +3 2.2
    +4 5
    +-10 -10
    +
    +
    + +

    Then call qconvex with 'qconvex < data.txt'. It will print a +summary of the convex hull. Use 'qconvex < data.txt o' to print +the vertices and edges. See also input +format.

    + +

    You can generate sample data with rbox, e.g., 'rbox 10' +generates 10 random points in 3-d. Use a pipe ('|') to run rbox +and qhull together, e.g.,

    + +
    +

    rbox c | qconvex o

    +
    + +

    computes the convex hull of a cube.

    + +

    »How do I learn to +use Qhull?

    + +

    First read:

    + + + +

    Look at Qhull's on-line documentation:

    + +
      +
    • 'qconvex' gives a synopsis of qconvex and its options + +
    • 'rbox' lists all of the options for generating point + sets +
    • 'qconvex - | more' lists the options for qconvex +
    • 'qconvex .' gives a concise list of options +
    • 'qdelaunay', 'qhalf', 'qvoronoi', and 'qhull' also have a synopsis and option list
    • +
    + +

    Then try out the Qhull programs on small examples.

    + +
      +
    • 'rbox c' lists the vertices of a cube +
    • 'rbox c | qconvex' is the convex hull of a cube +
    • 'rbox c | qconvex o' lists the vertices and facets of + a cube +
    • 'rbox c | qconvex Qt o' triangulates the cube +
    • 'rbox c | qconvex QJ o' joggles the input and + triangulates the cube +
    • 'rbox c D2 | qconvex' generates the convex hull of a + square +
    • 'rbox c D4 | qconvex' generates the convex hull of a + hypercube +
    • 'rbox 6 s D2 | qconvex p Fx' lists 6 random points in + a circle and lists the vertices of their convex hull in order +
    • 'rbox c D2 c G2 | qdelaunay' computes the Delaunay + triangulation of two embedded squares. It merges the cospherical facets. +
    • 'rbox c D2 c G2 | qdelaunay Qt' computes the Delaunay + triangulation of two embedded squares. It triangulates the cospherical facets. +
    • 'rbox c D2 c G2 | qvoronoi o' computes the + corresponding Voronoi vertices and regions. +
    • 'rbox c D2 c G2 | qvoronio Fv' shows the Voronoi diagram + for the previous example. Each line is one edge of the + diagram. The first number is 4, the next two numbers list + a pair of input sites, and the last two numbers list the + corresponding pair of Voronoi vertices.
    • +
    + +

    Install Geomview +if you are running SGI Irix, Solaris, SunOS, Linux, HP, IBM +RS/6000, DEC Alpha, or Next. You can then visualize the output of +Qhull. Qhull comes with Geomview examples. +

    + +

    Then try Qhull with a small example of your application. Work +out the results by hand. Then experiment with Qhull's options to +find the ones that you need.

    + +

    You will need to decide how Qhull should handle precision +problems. It can triangulate the output ('Qt'), joggle the input ('QJ'), or merge facets (the default).

    + +
      +
    • With joggle, Qhull produces simplicial (i.e., + triangular) output by joggling the input. After joggle, + no points are cocircular or cospherical. +
    • With facet merging, Qhull produces a better + approximation and does not modify the input. +
    • With triangulated output, Qhull merges facets and triangulates + the result.
    • +
    • See Merged facets or joggled input.
    • +
    + +
    +

    »Convex hull questions

    + +

    »How do I report just the area + and volume of a convex hull?

    + +Use option 'FS'. For example, + +
    +C:\qhull>rbox 10 | qconvex FS
    +0
    +2 2.192915621644613 0.2027867899638665
    +
    +C:\qhull>rbox 10 | qconvex FA
    +
    +Convex hull of 10 points in 3-d:
    +
    +  Number of vertices: 10
    +  Number of facets: 16
    +
    +Statistics for: RBOX 10 | QCONVEX FA
    +
    +  Number of points processed: 10
    +  Number of hyperplanes created: 28
    +  Number of distance tests for qhull: 44
    +  CPU seconds to compute hull (after input):  0
    +  Total facet area:   2.1929156
    +  Total volume:       0.20278679
    +
    + +

    »Why are there extra +points in a 4-d or higher convex hull?

    + +

    You may see extra points if you use options 'i' or 'Ft' + without using triangulated output ('Qt'). +The extra points occur when a facet is non-simplicial (i.e., a +facet with more than d vertices). For example, Qhull +reports the following for one facet of the convex hull of a hypercube. +Option 'Pd0:0.5' returns the facet along the positive-x axis:

    + +
    +
    +rbox c D4 | qconvex i Pd0:0.5
    +12
    +17 13 14 15
    +17 13 12 14
    +17 11 13 15
    +17 14 11 15
    +17 10 11 14
    +17 14 12 8
    +17 12 13 8
    +17 10 14 8
    +17 11 10 8
    +17 13 9 8
    +17 9 11 8
    +17 11 9 13
    +
    +
    + +

    The 4-d hypercube has 16 vertices; so point "17" was +added by qconvex. Qhull adds the point in order to report a +simplicial decomposition of the facet. The point corresponds to +the "centrum" which Qhull computes to test for +convexity.

    + +

    Triangulate the output ('Qt') to avoid the extra points. +Since the hypercube is 4-d, each simplicial facet is a tetrahedron. +

    +
    +C:\qhull3.1>rbox c D4 | qconvex i Pd0:0.5 Qt
    +9
    +9 13 14 15
    +12 9 13 14
    +9 11 13 15
    +11 9 14 15
    +9 10 11 14
    +12 9 14 8
    +9 12 13 8
    +9 10 14 8
    +10 9 11 8
    +
    +
    + +

    Use the 'Fv' option to print the +vertices of simplicial and non-simplicial facets. For example, +here is the same hypercube facet with option 'Fv' instead of 'i': +

    + +
    +
    +C:\qhull>rbox c D4 | qconvex Pd0:0.5 Fv
    +1
    +8 9 10 12 11 13 14 15 8
    +
    +
    + +

    The coordinates of the extra point are printed with the 'Ft' option.

    + +
    +
    +rbox c D4 | qconvex Pd0:0.5 Ft
    +4
    +17 12 3
    +  -0.5   -0.5   -0.5   -0.5
    +  -0.5   -0.5   -0.5    0.5
    +  -0.5   -0.5    0.5   -0.5
    +  -0.5   -0.5    0.5    0.5
    +  -0.5    0.5   -0.5   -0.5
    +  -0.5    0.5   -0.5    0.5
    +  -0.5    0.5    0.5   -0.5
    +  -0.5    0.5    0.5    0.5
    +   0.5   -0.5   -0.5   -0.5
    +   0.5   -0.5   -0.5    0.5
    +   0.5   -0.5    0.5   -0.5
    +   0.5   -0.5    0.5    0.5
    +   0.5    0.5   -0.5   -0.5
    +   0.5    0.5   -0.5    0.5
    +   0.5    0.5    0.5   -0.5
    +   0.5    0.5    0.5    0.5
    +   0.5      0      0      0
    +4 16 13 14 15
    +4 16 13 12 14
    +4 16 11 13 15
    +4 16 14 11 15
    +4 16 10 11 14
    +4 16 14 12 8
    +4 16 12 13 8
    +4 16 10 14 8
    +4 16 11 10 8
    +4 16 13 9 8
    +4 16 9 11 8
    +4 16 11 9 13
    +
    +
    + +

    »How do I report +duplicate vertices?

    + +

    There's no direct way. You can use option +'FP' to +report the distance to the nearest vertex for coplanar input +points. Select the minimum distance for a duplicated vertex, and +locate all input sites less than this distance.

    + +

    For Delaunay triangulations, all coplanar points are nearly +incident to a vertex. If you want a report of coincident input +sites, do not use option 'QJ'. By +adding a small random quantity to each input coordinate, it +prevents coincident input sites.

    + +
    +

    »Delaunay triangulation questions

    + +

    »How do I get rid of +nearly flat Delaunay triangles?

    + +

    Nearly flat triangles occur when boundary points are nearly +collinear or coplanar. They also occur for nearly coincident +points. Both events can easily occur when using joggle. For example +(rbox 10 W0 D2 | qdelaunay QJ Fa) lists the areas of the Delaunay +triangles of 10 points on the boundary of a square. Some of +these triangles are nearly flat. This occurs when one point +is joggled inside of two other points. In this case, nearly flat +triangles do not occur with triangulated output (rbox 10 W0 D2 | qdelaunay Qt Fa). + + +

    Another example, (rbox c P0 P0 D2 | qdelaunay QJ Fa), computes the +areas of the Delaunay triangles for the unit square and two +instances of the origin. Four of the triangles have an area +of 0.25 while two have an area of 2.0e-11. The later are due to +the duplicated origin. With triangulated output (rbox c P0 P0 D2 | qdelaunay Qt Fa) +there are four triangles of equal area. + +

    Nearly flat triangles also occur without using joggle. For +example, (rbox c P0 P0,0.4999999999 | qdelaunay Fa), computes +the areas of the Delaunay triangles for the unit square, +a nearly collinear point, and the origin. One triangle has an +area of 3.3e-11. + +

    Unfortunately, none of Qhull's merging options remove nearly +flat Delaunay triangles due to nearly collinear or coplanar boundary +points. +The merging options concern the empty circumsphere +property of Delaunay triangles. This is independent of the area of +the Delaunay triangles. Qhull does handle nearly coincident points. + +

    If you are calling Qhull from a program, you can merge slivers into an adjacent facet. +In d dimensions with simplicial facets (e.g., from 'Qt'), each facet has +d+1 neighbors. Each neighbor shares d vertices of the facet's d+1 vertices. Let the +other vertex be the opposite vertex. For each neighboring facet, if its circumsphere +includes the opposite.vertex, the two facets can be merged. [M. Treacy] + +

    You can handle collinear or coplanar boundary points by +enclosing the points in a box. For example, +(rbox c P0 P0,0.4999999999 c G1 | qdelaunay Fa), surrounds the +previous points with [(1,1), (1,-1), (-1,-1), (-1, 1)]. +Its Delaunay triangulation does not include a +nearly flat triangle. The box also simplifies the graphical +output from Qhull. + +

    Without joggle, Qhull lists coincident points as "coplanar" +points. For example, (rbox c P0 P0 D2 | qdelaunay Fa), ignores +the duplicated origin and lists four triangles of size 0.25. +Use 'Fc' to list the coincident points (e.g., +rbox c P0 P0 D2 | qdelaunay Fc). + +

    There is no easy way to determine coincident points with joggle. +Joggle removes all coincident, cocircular, and cospherical points +before running Qhull. Instead use facet merging (the default) +or triangulated output ('Qt'). + +

    »How do I compute +the Delaunay triangulation of a non-convex object?

    + +

    A similar question is +"How do I mesh a volume from a set of triangulated surface points?" + +

    This is an instance of the constrained Delaunay Triangulation +problem. Qhull does not handle constraints. The boundary of the +Delaunay triangulation is always convex. But if the input set +contains enough points, the triangulation will include the +boundary. The number of points needed depends on the input. + +

    Shewchuk has developed a theory of constrained Delaunay triangulations. +See his +paper at the +1998 Computational Geometry Conference. Using these ideas, constraints +could be added to Qhull. They would have many applications. + +

    There is a large literature on mesh generation and many commercial +offerings. For pointers see +Owen's International Meshing Roundtable +and Schneiders' +Finite Element Mesh Generation page.

    + +

    »Can Qhull +produce a triangular mesh for an object?

    + +

    Yes for convex objects, no for non-convex objects. For +non-convex objects, it triangulates the concavities. Unless the +object has many points on its surface, triangles may cross the +surface.

    + +

    »For 3-d Delaunay +triangulations, how do I report the triangles of each +tetrahedron?

    + +

    For points in general position, a 3-d Delaunay triangulation +generates tetrahedron. Each face of a tetrahedron is a triangle. +For example, the 3-d Delaunay triangulation of random points on +the surface of a cube, is a cellular structure of tetrahedron.

    + +

    Use triangulated output ('qdelaunay Qt i') or joggled input ('qdelaunay QJ i') +to generate the Delaunay triangulation. +Option 'i' reports each tetrahedron. The triangles are +every combination of 3 vertices. Each triangle is a +"ridge" of the Delaunay triangulation.

    + +

    For example,

    + +
    +        rbox 10 | qdelaunay Qt i
    +        14
    +        9 5 8 7
    +        0 9 8 7
    +        5 3 8 7
    +        3 0 8 7
    +        5 4 8 1
    +        4 6 8 1
    +        2 9 5 8
    +        4 2 5 8
    +        4 2 9 5
    +        6 2 4 8
    +        9 2 0 8
    +        2 6 0 8
    +        2 4 9 1
    +        2 6 4 1
    +
    + +

    is the Delaunay triangulation of 10 random points. Ridge 9-5-8 +occurs twice. Once for tetrahedron 9 5 8 7 and the other for +tetrahedron 2 9 5 8.

    + +

    You can also use the Qhull library to generate the triangles. +See "How do I visit the ridges of a +Delaunay triangulation?"

    + +

    »How do I construct a +3-d Delaunay triangulation?

    + +

    For 3-d Delaunay triangulations with cospherical input sites, +use triangulated output ('Qt') or +joggled input ('QJ'). Otherwise +option 'i' will +triangulate non-simplicial facets by adding a point to the facet. + +

    If you want non-simplicial output for cospherical sites, use +option +'Fv' or 'o'. +For option 'o', ignore the last coordinate. It is the lifted +coordinate for the corresponding convex hull in 4-d. + +

    The following example is a cube +inside a tetrahedron. The 8-vertex facet is the cube. Ignore the +last coordinates.

    + +
    +
    +C:\qhull>rbox r y c G0.1 | qdelaunay Fv
    +4
    +12 20 44
    +   0.5      0      0 0.3055555555555555
    +   0    0.5      0 0.3055555555555555
    +   0      0    0.5 0.3055555555555555
    +  -0.5   -0.5   -0.5 0.9999999999999999
    +  -0.1   -0.1   -0.1 -6.938893903907228e-018
    +  -0.1   -0.1    0.1 -6.938893903907228e-018
    +  -0.1    0.1   -0.1 -6.938893903907228e-018
    +  -0.1    0.1    0.1 -6.938893903907228e-018
    +   0.1   -0.1   -0.1 -6.938893903907228e-018
    +   0.1   -0.1    0.1 -6.938893903907228e-018
    +   0.1    0.1   -0.1 -6.938893903907228e-018
    +   0.1    0.1    0.1 -6.938893903907228e-018
    +4 2 11 1 0
    +4 10 1 0 3
    +4 11 10 1 0
    +4 2 9 0 3
    +4 9 11 2 0
    +4 7 2 1 3
    +4 11 7 2 1
    +4 8 10 0 3
    +4 9 8 0 3
    +5 8 9 10 11 0
    +4 10 6 1 3
    +4 6 7 1 3
    +5 6 8 10 4 3
    +5 6 7 10 11 1
    +4 5 9 2 3
    +4 7 5 2 3
    +5 5 8 9 4 3
    +5 5 6 7 4 3
    +8 5 6 8 7 9 10 11 4
    +5 5 7 9 11 2
    +
    +
    + +

    If you want simplicial output use options +'Qt i' or +'QJ i', e.g., +

    + +
    +
    +rbox r y c G0.1 | qdelaunay Qt i
    +31
    +2 11 1 0
    +11 10 1 0
    +9 11 2 0
    +11 7 2 1
    +8 10 0 3
    +9 8 0 3
    +10 6 1 3
    +6 7 1 3
    +5 9 2 3
    +7 5 2 3
    +9 8 10 11
    +8 10 11 0
    +9 8 11 0
    +6 8 10 4
    +8 6 10 3
    +6 8 4 3
    +6 7 10 11
    +10 6 11 1
    +6 7 11 1
    +8 5 4 3
    +5 8 9 3
    +5 6 4 3
    +6 5 7 3
    +5 9 10 11
    +8 5 9 10
    +7 5 10 11
    +5 6 7 10
    +8 5 10 4
    +5 6 10 4
    +5 9 11 2
    +7 5 11 2
    +
    +
    + +

    »How do I get the +triangles for a 2-d Delaunay triangulation and the vertices of +its Voronoi diagram?

    + +

    To compute the Delaunay triangles indexed by the indices of +the input sites, use

    + +
    +

    rbox 10 D2 | qdelaunay Qt i

    +
    + +

    To compute the Voronoi vertices and the Voronoi region for +each input site, use

    + +
    +

    rbox 10 D2 | qvoronoi o

    +
    + +

    To compute each edge ("ridge") of the Voronoi +diagram for each pair of adjacent input sites, use

    + +
    +

    rbox 10 D2 | qvoronoi Fv

    +
    + +

    To compute the area and volume of the Voronoi region for input site 5 (site 0 is the first one), +use

    + +
    +

    rbox 10 D2 | qvoronoi QV5 p | qconvex s FS

    +
    + +

    To compute the lines ("hyperplanes") that define the +Voronoi region for input site 5, use

    + +
    +

    rbox 10 D2 | qvoronoi QV5 p | qconvex n

    +
    +or +
    +

    rbox 10 D2 | qvoronoi QV5 Fi Fo

    +
    + +

    To list the extreme points of the input sites use

    + +
    +

    rbox 10 D2 | qdelaunay Fx

    +
    + +

    You will get the same point ids with

    + +
    +

    rbox 10 D2 | qconvex Fx

    +
    + +

    »Can Qhull triangulate +a hundred 16-d points?

    + +

    No. This is an immense structure. A triangulation of 19, 16-d +points has 43 simplices. If you add one point at a time, the +triangulation increased as follows: 43, 189, 523, 1289, 2830, +6071, 11410, 20487. The last triangulation for 26 points used 13 +megabytes of memory. When Qhull uses virtual memory, it becomes +too slow to use.

    + +
    +

    »Voronoi +diagram questions

    + +

    »How do I compute the volume of a Voronoi region?

    + +

    For each Voronoi region, compute the convex hull of the region's Voronoi vertices. The volume of each convex hull is the volume +of the corresponding Vornoi region.

    + +

    For example, to compute the volume of the bounded Voronoi region about [0,0,0]: output the origin's Voronoi vertices and +compute the volume of their convex hull. The last number from option 'FS' is the volume.

    +
    +rbox P0 10 | qvoronoi QV0 p | qhull FS
    +0
    +2 1.448134756744281 0.1067973560800857
    +
    + +

    For another example, see How do I get the triangles for a 2-d Delaunay + triangulation and the vertices of its Voronoi diagram?

    + +

    This approach is slow if you are using the command line. A faster approcach is to call Qhull from +a program. The fastest method is Clarkson's hull program. +It computes the volume for all Voronoi regions.

    + +

    An unbounded Voronoi region does not have a volume.

    + +

    »How do I get the radii of the empty + spheres for each Voronoi vertex?

    + +Use option 'Fi' to list each bisector (i.e. Delaunay ridge). Then compute the +minimum distance for each Voronoi vertex. + +

    There's other ways to get the same information. Let me know if you +find a better method. + +

    »What is the Voronoi diagram + of a square?

    + +

    +Consider a square, +

    +C:\qhull>rbox c D2
    +2 RBOX c D2
    +4
    +  -0.5   -0.5
    +  -0.5    0.5
    +   0.5   -0.5
    +   0.5    0.5
    +
    + +

    There's two ways to compute the Voronoi diagram: with facet merging +or with joggle. With facet merging, the +result is: + +

    +C:\qhull>rbox c D2 | qvoronoi Qz
    +
    +Voronoi diagram by the convex hull of 5 points in 3-d:
    +
    +  Number of Voronoi regions and at-infinity: 5
    +  Number of Voronoi vertices: 1
    +  Number of facets in hull: 5
    +
    +Statistics for: RBOX c D2 | QVORONOI Qz
    +
    +  Number of points processed: 5
    +  Number of hyperplanes created: 7
    +  Number of distance tests for qhull: 8
    +  Number of merged facets: 1
    +  Number of distance tests for merging: 29
    +  CPU seconds to compute hull (after input):  0
    +
    +C:\qhull>rbox c D2 | qvoronoi Qz o
    +2
    +2 5 1
    +-10.101 -10.101
    +     0      0
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +0
    +
    +C:\qhull>rbox c D2 | qvoronoi Qz Fv
    +4
    +4 0 1 0 1
    +4 0 2 0 1
    +4 1 3 0 1
    +4 2 3 0 1
    +
    + +

    There is one Voronoi vertex at the origin and rays from the origin +along each of the coordinate axes. +The last line '4 2 3 0 1' means that there is +a ray that bisects input points #2 and #3 from infinity (vertex 0) to +the origin (vertex 1). +Option 'Qz' adds an artificial point since the input is cocircular. +Coordinates -10.101 indicate the +vertex at infinity. + +

    With triangulated output, the Voronoi vertex is +duplicated: + +

    +C:\qhull3.1>rbox c D2 | qvoronoi Qt Qz
    +
    +Voronoi diagram by the convex hull of 5 points in 3-d:
    +
    +  Number of Voronoi regions and at-infinity: 5
    +  Number of Voronoi vertices: 2
    +  Number of triangulated facets: 1
    +
    +Statistics for: RBOX c D2 | QVORONOI Qt Qz
    +
    +  Number of points processed: 5
    +  Number of hyperplanes created: 7
    +  Number of facets in hull: 6
    +  Number of distance tests for qhull: 8
    +  Number of distance tests for merging: 33
    +  Number of distance tests for checking: 30
    +  Number of merged facets: 1
    +  CPU seconds to compute hull (after input): 0.05
    +
    +C:\qhull3.1>rbox c D2 | qvoronoi Qt Qz o
    +2
    +3 5 1
    +-10.101 -10.101
    +     0      0
    +     0      0
    +3 2 0 1
    +2 1 0
    +2 2 0
    +3 2 0 1
    +0
    +
    +C:\qhull3.1>rbox c D2 | qvoronoi Qt Qz Fv
    +4
    +4 0 2 0 2
    +4 0 1 0 1
    +4 1 3 0 1
    +4 2 3 0 2
    +
    + + +

    With joggle, the input is no longer cocircular and the Voronoi vertex is +split into two: + +

    +C:\qhull>rbox c D2 | qvoronoi Qt Qz
    +
    +C:\qhull>rbox c D2 | qvoronoi QJ o
    +2
    +3 4 1
    +-10.101 -10.101
    +-4.71511718558304e-012 -1.775812830118184e-011
    +9.020340030474472e-012 -4.02267108512433e-012
    +2 0 1
    +3 2 1 0
    +3 2 0 1
    +2 2 0
    +
    +C:\qhull>rbox c D2 | qvoronoi QJ Fv
    +5
    +4 0 2 0 1
    +4 0 1 0 1
    +4 1 2 1 2
    +4 1 3 0 2
    +4 2 3 0 2
    +
    + +

    Note that the Voronoi diagram includes the same rays as + before plus a short edge between the two vertices.

    + + +

    »How do I construct +the Voronoi diagram of cospherical points?

    + +

    Three-d terrain data can be approximated with cospherical +points. The Delaunay triangulation of cospherical points is the +same as their convex hull. If the points lie on the unit sphere, +the facet normals are the Voronoi vertices [via S. Fortune].

    + +

    For example, consider the points {[1,0,0], [-1,0,0], [0,1,0], +...}. Their convex hull is:

    + +
    +rbox d G1 | qconvex o
    +3
    +6 8 12
    +     0      0     -1
    +     0      0      1
    +     0     -1      0
    +     0      1      0
    +    -1      0      0
    +     1      0      0
    +3 3 1 4
    +3 1 3 5
    +3 0 3 4
    +3 3 0 5
    +3 2 1 5
    +3 1 2 4
    +3 2 0 4
    +3 0 2 5
    +
    + +

    The facet normals are:

    + +
    +rbox d G1 | qconvex n
    +4
    +8
    +-0.5773502691896258  0.5773502691896258  0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258  0.5773502691896258  0.5773502691896258 -0.5773502691896258
    +-0.5773502691896258  0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258  0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258 -0.5773502691896258  0.5773502691896258 -0.5773502691896258
    +-0.5773502691896258 -0.5773502691896258  0.5773502691896258 -0.5773502691896258
    +-0.5773502691896258 -0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258 -0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    +
    + +

    If you drop the offset from each line (the last number), each +line is the Voronoi vertex for the corresponding facet. The +neighboring facets for each point define the Voronoi region for +each point. For example:

    + +
    +rbox d G1 | qconvex FN
    +6
    +4 7 3 2 6
    +4 5 0 1 4
    +4 7 4 5 6
    +4 3 1 0 2
    +4 6 2 0 5
    +4 7 3 1 4
    +
    + +

    The Voronoi vertices {7, 3, 2, 6} define the Voronoi region +for point 0. Point 0 is [0,0,-1]. Its Voronoi vertices are

    + +
    +-0.5773502691896258  0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258  0.5773502691896258 -0.5773502691896258
    +-0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    +
    + +

    In this case, the Voronoi vertices are oriented, but in +general they are unordered.

    + +

    By taking the dual of the Delaunay triangulation, you can +construct the Voronoi diagram. For cospherical points, the convex +hull vertices for each facet, define the input sites for each +Voronoi vertex. In 3-d, the input sites are oriented. For +example:

    + +
    +rbox d G1 | qconvex i
    +8
    +3 1 4
    +1 3 5
    +0 3 4
    +3 0 5
    +2 1 5
    +1 2 4
    +2 0 4
    +0 2 5
    +
    + +

    The convex hull vertices for facet 0 are {3, 1, 4}. So Voronoi +vertex 0 (i.e., [-0.577, 0.577, 0.577]) is the Voronoi vertex for +input sites {3, 1, 4} (i.e., {[0,1,0], [0,0,1], [-1,0,0]}).

    + +

    »Can Qhull compute the +unbounded rays of the Voronoi diagram?

    + +

    Use 'Fo' to compute the separating +hyperplanes for unbounded Voronoi regions. The corresponding ray +goes to infinity from the Voronoi vertices. If you enclose the +input sites in a large enough box, the outermost bounded regions +will represent the unbounded regions of the original points.

    + +

    If you do not box the input sites, you can identify the +unbounded regions. They list '0' as a vertex. Vertex 0 represents +"infinity". Each unbounded ray includes vertex 0 in +option 'Fv. See Voronoi graphics and Voronoi notes.

    + +
    +

    »Approximation questions

    + +

    »How do I +approximate data with a simplex

    + +

    Qhull may be used to help select a simplex that approximates a +data set. It will take experimentation. Geomview will help to +visualize the results. This task may be difficult to do in 5-d +and higher. Use rbox options 'x' and 'y' to produce random +distributions within a simplex. Your methods work if you can +recover the simplex.

    + +

    Use Qhull's precision options to get a first approximation to +the hull, say with 10 to 50 facets. For example, try 'C0.05' to +remove small facets after constructing the hull. Use 'W0.05' to +ignore points within 0.05 of a facet. Use 'PA5' to print the five +largest facets by area.

    + +

    Then use other methods to fit a simplex to this data. Remove +outlying vertices with few nearby points. Look for large facets +in different quadrants. You can use option 'Pd0d1d2' to print all +the facets in a quadrant.

    + +

    In 4-d and higher, use the outer planes (option 'Fo' or +'facet->maxoutside') since the hyperplane of an approximate +facet may be below many of the input points.

    + +

    For example, consider fitting a cube to 1000 uniformly random +points in the unit cube. In this case, the first try was good:

    + +
    +
    +rbox 1000 | qconvex W0.05 C0.05 PA6 Fo
    +4
    +6
    +0.35715408374381 0.08706467018177928 -0.9299788727015564 -0.5985514741284483
    +0.995841591359023 -0.02512604712761577 0.08756829720435189 -0.5258834069202866
    +0.02448099521570909 -0.02685210459017302 0.9993396046151313 -0.5158104982631999
    +-0.9990223929415094 -0.01261133513150079 0.04236994958247349 -0.509218270408407
    +-0.0128069014364698 -0.9998380680115362 0.01264203427283151 -0.5002512653670584
    +0.01120895057872914 0.01803671994177704 -0.9997744926535512 -0.5056824072956361
    +
    +
    + +
    +

    »Halfspace questions

    + +

    »How do I compute the + intersection of halfspaces with Qhull?

    + +

    Qhull computes the halfspace intersection about a point. The +point must be inside all of the halfspaces. Given a point, a +duality turns a halfspace intersection problem into a convex +hull problem. + +

    Use linear programming if you +do not know a point in the interior of the halfspaces. +See the notes for qhalf. You will need + a linear programming code. This may require a fair amount of work to + implement.

    + + + +
    +

    »Qhull library +questions

    + +

    »Is Qhull available for Mathematica, Matlab, or Maple?

    + +

    MATLAB + +

    Z. You of MathWorks added qhull to MATLAB 6. +See functions convhulln, + delaunayn, + griddata3, + griddatan, + tsearch, + tsearchn, and + voronoin. V. Brumberg update MATLAB R14 for Qhull 2003.1 and triangulated output. + +

    Engwirda wrote mesh2d for unstructured mesh generation in MATLAB. +It is based on the iterative method of Persson and generally results in better quality meshes than delaunay refinement. + + +

    Mathematica and Maple + +

    See qh-math +for a Delaunay interface to Mathematica. It includes projects for CodeWarrior +on the Macintosh and Visual C++ on Win32 PCs. + +

    See Mathematica ('m') and Maple ('FM') output options. + +

    +

    »Why are there too few ridges?

    + +The following sample code may produce fewer ridges than expected: + +
    +  facetT *facetp;
    +  ridgeT *ridge, **ridgep;
    +
    +  FORALLfacets {
    +    printf("facet f%d\n", facet->id);
    +    FOREACHridge_(facet->ridges) {
    +      printf("   ridge r%d between f%d and f%d\n", ridge->id, ridge->top->id, ridge->bottom->id);
    +    }
    +  }
    +
    + +

    Qhull does not create ridges for simplicial facets. +Instead it computes ridges from facet->neighbors. To make ridges for a +simplicial facet, use qh_makeridges() in merge.c. Usefacet->visit_id to visit +each ridge once (instead of twice). For example, + +

    +  facetT *facet, *neighbor;
    +  ridgeT *ridge, **ridgep;
    +
    +  qh visit_id++;
    +  FORALLfacets {
    +    printf("facet f%d\n", facet->id);
    +    qh_makeridges(facet);
    +    facet->visitId= qh visit_id;
    +    FOREACHridge_(facet->ridges) {
    +        neighbor= otherfacet_(ridge, visible);
    +        if (neighbor->visitid != qh visit_id)
    +            printf("   ridge r%d between f%d and f%d\n", ridge->id, ridge->top->id, ridge->bottom->id);
    +    }
    +  }
    +
    + +

    »Can Qhull use coordinates without placing + them in a data file?

    + +

    You may call Qhull from a program. Please use the reentrant Qhull library (libqhullstatic_r.a, libqhull_r.so, or qhull_r.dll). + +See user_eg.c and "Qhull-template" in user_r.c for examples.. + +See Qhull internals for an introduction to Qhull's reentrant library and its C++ interface. + +

    Hint: Start with a small example for which you know the + answer.

    + +

    »How large are Qhull's data structures?

    + +

    Qhull uses a general-dimension data structure. +The size depends on the dimension. Use option 'Ts' to print +out the memory statistics [e.g., 'rbox D2 10 | qconvex Ts']. + + +

    »Can Qhull construct +convex hulls and Delaunay triangulations one point at a time?

    + +

    The Qhull library may be used to construct convex hulls and +Delaunay triangulations one point at a time. It may not be used +for deleting points or moving points.

    + +

    Qhull is designed for batch processing. Neither Clarkson's +randomized incremental algorithm nor Qhull are designed for +on-line operation. For many applications, it is better to +reconstruct the convex hull or Delaunay triangulation from +scratch for each new point.

    + +

    With random point sets and on-line processing, Clarkson's +algorithm should run faster than Qhull. Clarkson uses the +intermediate facets to reject new, interior points, while Qhull, +when used on-line, visits every facet to reject such points. If +used on-line for n points, Clarkson may take O(n) times as much +memory as the average off-line case, while Qhull's space +requirement does not change.

    + +

    If you triangulate the output before adding all the points +(option 'Qt' and procedure qh_triangulate), you must set +option 'Q11'. It duplicates the +normals of triangulated facets and recomputes the centrums. +This should be avoided for regular use since triangulated facets +are not clearly convex with their neighbors. It appears to +work most of the time, but fails for cases that Qhull normally +handles well [see the test call to qh_triangulate in qh_addpoint]. + +

    »How do I visit the +ridges of a Delaunay triangulation?

    + +

    To visit the ridges of a Delaunay triangulation, visit each +facet. Each ridge will appear twice since it belongs to two +facets. In pseudo-code:

    + +
    +    for each facet of the triangulation
    +        if the facet is Delaunay (i.e., part of the lower convex hull)
    +            for each ridge of the facet
    +                if the ridge's neighboring facet has not been visited
    +                    ... process a ridge of the Delaunay triangulation ...
    +
    + +

    In undebugged, C code:

    + +
    +    qh visit_id++;
    +    FORALLfacets_(facetlist)
    +        if (!facet->upperdelaunay) {
    +            facet->visitid= qh visit_id;
    +            qh_makeridges(facet);
    +            FOREACHridge_(facet->ridges) {
    +                neighbor= otherfacet_(ridge, facet);
    +                if (neighbor->visitid != qh visit_id) {
    +                    /* Print ridge here with facet-id and neighbor-id */
    +                    /*fprintf(fp, "f%d\tf%d\t",facet->id,neighbor->ID);*/
    +                    FOREACHvertex_(ridge->vertices)
    +                        fprintf(fp,"%d ",qh_pointid (vertex->point) );
    +                    qh_printfacetNvertex_simplicial (fp, facet, format);
    +                    fprintf(fp," ");
    +                    if(neighbor->upperdelaunay)
    +                        fprintf(fp," -1 -1 -1 -1 ");
    +                    else
    +                        qh_printfacetNvertex_simplicial (fp, neighbor, format);
    +                    fprintf(fp,"\n");
    +                }
    +            }
    +        }
    +    }
    +
    + +

    Qhull should be redesigned as a class library, or at least as +an API. It currently provides everything needed, but the +programmer has to do a lot of work. Hopefully someone will write +C++ wrapper classes or a Python module for Qhull.

    + +

    »How do I visit the +Delaunay regions?

    + +

    Qhull constructs a Delaunay triangulation by lifting the +input sites to a paraboloid. The Delaunay triangulation +corresponds to the lower convex hull of the lifted points. To +visit each facet of the lower convex hull, use:

    + +
    +    facetT *facet;
    +
    +    ...
    +    FORALLfacets {
    +        if (!facet->upperdelaunay) {
    +            ... only facets for Delaunay regions ...
    +        }
    +    }
    +
    + +

    »When is a point +outside or inside a facet?

    + +

    A point is outside of a facet if it is clearly outside the +facet's outer plane. The outer plane is defined by an offset +(facet->maxoutside) from the facet's hyperplane.

    + +
    +    facetT *facet;
    +    pointT *point;
    +    realT dist;
    +
    +    ...
    +    qh_distplane(point, facet, &dist);
    +    if (dist > facet->maxoutside + 2 * qh DISTround) {
    +        /* point is clearly outside of facet */
    +    }
    +
    + +

    A point is inside of a facet if it is clearly inside the +facet's inner plane. The inner plane is computed as the maximum +distance of a vertex to the facet. It may be computed for an +individual facet, or you may use the maximum over all facets. For +example:

    + +
    +    facetT *facet;
    +    pointT *point;
    +    realT dist;
    +
    +    ...
    +    qh_distplane(point, facet, &dist);
    +    if (dist < qh min_vertex - 2 * qh DISTround) {
    +        /* point is clearly inside of facet */
    +    }
    +
    + +

    Both tests include two qh.DISTrounds because the computation +of the furthest point from a facet may be off by qh.DISTround and +the computation of the current distance to the facet may be off +by qh.DISTround.

    + +

    »How do I find the +facet that is closest to a point?

    + +

    Use qh_findbestfacet(). For example,

    + +
    +    coordT point[ DIM ];
    +    boolT isoutside;
    +    realT bestdist;
    +    facetT *facet;
    +
    +    ... set coordinates for point ...
    +
    +    facet= qh_findbestfacet (point, qh_ALL, &bestdist, &isoutside);
    +
    +    /* 'facet' is the closest facet to 'point' */
    +
    + +

    qh_findbestfacet() performs a directed search for the facet +furthest below the point. If the point lies inside this facet, +qh_findbestfacet() performs an exhaustive search of all facets. +An exhaustive search may be needed because a facet on the far +side of a lens-shaped distribution may be closer to a point than +all of the facet's neighbors. The exhaustive search may be +skipped for spherical distributions.

    + +

    Also see, "How do I find the +Delaunay triangle that is closest to a +point?"

    + +

    »How do I find the +Delaunay triangle or Voronoi region that is closest to a point?

    + +

    A Delaunay triangulation subdivides the plane, or in general +dimension, subdivides space. Given a point, how do you determine +the subdivision containing the point? Or, given a set of points, +how do you determine the subdivision containing each point of the set? +Efficiency is important -- an exhaustive search of the subdivision +is too slow. + +

    First compute the Delaunay triangle with qh_new_qhull() in user_r.c or Qhull::runQhull(). +Lift the point to the paraboloid by summing the squares of the +coordinates. Use qh_findbestfacet() [poly2.c] to find the closest Delaunay +triangle. Determine the closest vertex to find the corresponding +Voronoi region. Do not use options +'Qbb', 'QbB', +'Qbk:n', or 'QBk:n' since these scale the last +coordinate. Optimizations of qh_findbestfacet() should +be possible for Delaunay triangulations.

    + +

    You first need to lift the point to the paraboloid (i.e., the +last coordinate is the sum of the squares of the point's coordinates). +The +routine, qh_setdelaunay() [geom2.c], lifts an array of points to the +paraboloid. The following excerpt is from findclosest() in +user_eg.c.

    + +
    +    coordT point[ DIM + 1];  /* one extra coordinate for lifting the point */
    +    boolT isoutside;
    +    realT bestdist;
    +    facetT *facet;
    +
    +    ... set coordinates for point[] ...
    +
    +    qh_setdelaunay (DIM+1, 1, point);
    +    facet= qh_findbestfacet (point, qh_ALL, &bestdist, &isoutside);
    +    /* 'facet' is the closest Delaunay triangle to 'point' */
    +
    + +

    The returned facet either contains the point or it is the +closest Delaunay triangle along the convex hull of the input set. + +

    Point location is an active research area in Computational +Geometry. For a practical approach, see Mucke, et al, "Fast randomized +point location without preprocessing in two- and +three-dimensional Delaunay triangulations," Computational +Geometry '96, p. 274-283, May 1996. +For an introduction to planar point location see [O'Rourke '93]. +Also see, "How do I find the facet that is closest to a +point?"

    + +

    To locate the closest Voronoi region, determine the closest +vertex of the closest Delaunay triangle.

    + +
    +    realT dist, bestdist= REALmax;
    +        vertexT *bestvertex= NULL, *vertex, **vertexp;
    +
    +    /* 'facet' is the closest Delaunay triangle to 'point' */
    +
    +    FOREACHvertex_( facet->vertices ) {
    +        dist= qh_pointdist( point, vertex->point, DIM );
    +        if (dist < bestdist) {
    +            bestdist= dist;
    +            bestvertex= vertex;
    +        }
    +    }
    +    /* 'bestvertex' represents the Voronoi region closest to 'point'.  The corresponding
    +       input site is 'bestvertex->point' */
    +
    + +

    »How do I list the +vertices?

    + +

    To list the vertices (i.e., extreme points) of the convex hull +use

    + +
    +
    +    vertexT *vertex;
    +
    +    FORALLvertices {
    +      ...
    +      // vertex->point is the coordinates of the vertex
    +      // qh_pointid(vertex->point) is the point ID of the vertex
    +      ...
    +    }
    +    
    +
    + +

    »How do I test code +that uses the Qhull library?

    + +

    Compare the output from your program with the output from the +Qhull program. Use option 'T1' or 'T4' to trace what Qhull is +doing. Prepare a small example for which you know the +output. Run the example through the Qhull program and your code. +Compare the trace outputs. If you do everything right, the two +trace outputs should be almost the same. The trace output will +also guide you to the functions that you need to review.

    + +

    »When I compute a +plane equation from a facet, I sometimes get an outward-pointing +normal and sometimes an inward-pointing normal

    + +

    Qhull orients simplicial facets, and prints oriented output +for 'i', 'Ft', and other options. The orientation depends on both +the vertex order and the flag facet->toporient.

    + +

    Qhull does not orient + non-simplicial facets. Instead it orients the facet's ridges. These are + printed with the 'Qt' and 'Ft' option. The facet's hyperplane is oriented.

    + +
    +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: FAQ: Table of Contents
    + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: +Sept. 25, 1995 --- Last modified: see top +

    + + diff --git a/xs/src/qhull/html/qh-get.htm b/xs/src/qhull/html/qh-get.htm new file mode 100644 index 0000000000..c39ed22564 --- /dev/null +++ b/xs/src/qhull/html/qh-get.htm @@ -0,0 +1,106 @@ + + + + +Qhull Downloads + + + + +

    Up: Qhull Home Page
    +

    + +
    + +

    [CONE] Qhull Downloads

    + + + +
    + +

    Up: Qhull Home Page
    +

    + +
    + +

    [HOME] The Geometry Center Home Page

    + +

    Comments to: qhull@qhull.org
    + + diff --git a/xs/src/qhull/html/qh-impre.htm b/xs/src/qhull/html/qh-impre.htm new file mode 100644 index 0000000000..cfbe0acb82 --- /dev/null +++ b/xs/src/qhull/html/qh-impre.htm @@ -0,0 +1,826 @@ + + + + +Imprecision in Qhull + + + + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of +Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull imprecision: Table of Contents +(please wait while loading) + +


    + +

    [4-d cube] Imprecision in Qhull

    + +

    This section of the Qhull manual discusses the problems caused +by coplanar points and why Qhull uses options 'C-0' or 'Qx' +by default. If you ignore precision issues with option 'Q0', the output from Qhull can be +arbitrarily bad. Qhull avoids precision problems if you merge facets (the default) or joggle +the input ('QJ').

    + +

    Use option 'Tv' to verify the +output from Qhull. It verifies that adjacent facets are clearly +convex. It verifies that all points are on or below all facets.

    + +

    Qhull automatically tests for convexity if it detects +precision errors while constructing the hull.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »Qhull +imprecision: Table of Contents

    + + + +
    + +

    »Precision problems

    + +

    Since Qhull uses floating point arithmetic, roundoff error +occurs with each calculation. This causes problems for +geometric algorithms. Other floating point codes for convex +hulls, Delaunay triangulations, and Voronoi diagrams also suffer +from these problems. Qhull handles most of them.

    + +

    There are several kinds of precision errors:

    + +
      +
    • Representation error occurs when there are not enough + digits to represent a number, e.g., 1/3.
    • +
    • Measurement error occurs when the input coordinates are + from measurements.
    • +
    • Roundoff error occurs when a calculation is rounded to a + fixed number of digits, e.g., a floating point + calculation.
    • +
    • Approximation error occurs when the user wants an + approximate result because the exact result contains too + much detail.
    • +
    + +

    Under imprecision, calculations may return erroneous results. +For example, roundoff error can turn a small, positive number +into a small, negative number. See Milenkovic ['93] for a discussion of strict +robust geometry. Qhull does not meet Milenkovic's criterion +for accuracy. Qhull's error bound is empirical instead of +theoretical.

    + +

    Qhull 1.0 checked for precision errors but did not handle +them. The output could contain concave facets, facets with +inverted orientation ("flipped" facets), more than two +facets adjacent to a ridge, and two facets with exactly the same +set of vertices.

    + +

    Qhull 2.4 and later automatically handles errors due to +machine round-off. Option 'C-0' or 'Qx' is set by default. In 5-d and +higher, the output is clearly convex but an input point could be +outside of the hull. This may be corrected by using option 'C-0', but then the output may contain +wide facets.

    + +

    Qhull 2.5 and later provides option 'QJ' +to joggled input. Each input coordinate is modified by a +small, random quantity. If a precision error occurs, a larger +modification is tried. When no precision errors occur, Qhull is +done.

    + +

    Qhull 3.1 and later provides option 'Qt' +for triangulated output. This removes the need for +joggled input ('QJ'). +Non-simplicial facets are triangulated. +The facets may have zero area. +Triangulated output is particularly useful for Delaunay triangulations.

    + +

    By handling round-off errors, Qhull can provide a variety of +output formats. For example, it can return the halfspace that +defines each facet ('n'). The +halfspaces include roundoff error. If the halfspaces were exact, +their intersection would return the original extreme points. With +imprecise halfspaces and exact arithmetic, nearly incident points +may be returned for an original extreme point. By handling +roundoff error, Qhull returns one intersection point for each of +the original extreme points. Qhull may split or merge an extreme +point, but this appears to be unlikely.

    + +

    The following pipe implements the identity function for +extreme points (with roundoff): +

    + qconvex FV n | qhalf Fp +
    + +

    Bernd Gartner published his +Miniball +algorithm ["Fast and robust smallest enclosing balls", Algorithms - ESA '99, LNCS 1643]. +It uses floating point arithmetic and a carefully designed primitive operation. +It is practical to 20-D or higher, and identifies at least two points on the +convex hull of the input set. Like Qhull, it is an incremental algorithm that +processes points furthest from the intermediate result and ignores +points that are close to the intermediate result. + +

    »Merged facets or joggled input

    + +

    This section discusses the choice between merged facets and joggled input. +By default, Qhull uses merged facets to handle +precision problems. With option 'QJ', +the input is joggled. See examples +of joggled input and triangulated output. +

      +
    • Use merged facets (the default) +when you want non-simplicial output (e.g., the faces of a cube). +
    • Use merged facets and triangulated output ('Qt') when +you want simplicial output and coplanar facets (e.g., triangles for a Delaunay triangulation). +
    • Use joggled input ('QJ') when you need clearly-convex, +simplicial output. +
    + +

    The choice between merged facets and joggled input depends on +the application. Both run about the same speed. Joggled input may +be faster if the initial joggle is sufficiently large to avoid +precision errors. + +

    Most applications should used merged facets +with triangulated output.

    + +

    Use merged facets (the +default, 'C-0') +or triangulated output ('Qt') if

    + +
      +
    • Your application supports non-simplicial facets, or + it allows degenerate, simplicial facets (option 'Qt').
    • +
    • You do not want the input modified.
    • +
    • You want to set additional options for approximating the + hull.
    • +
    • You use single precision arithmetic (realT). +
    • +
    + +

    Use joggled input ('QJ') if

    + +
      +
    • Your application needs clearly convex, simplicial output
    • +
    • Your application supports perturbed input points and narrow triangles.
    • +
    • Seven significant digits is sufficient accuracy.
    • +
    + +

    You may use both techniques or combine joggle with +post-merging ('Cn').

    + +

    Other researchers have used techniques similar to joggled +input. Sullivan and Beichel [ref?] randomly perturb the input +before computing the Delaunay triangulation. Corkum and Wyllie +[news://comp.graphics, 1990] randomly rotate a polytope before +testing point inclusion. Edelsbrunner and Mucke [Symp. Comp. +Geo., 1988] and Yap [J. Comp. Sys. Sci., 1990] symbolically +perturb the input to remove singularities.

    + +

    Merged facets ('C-0') handles +precision problems directly. If a precision problem occurs, Qhull +merges one of the offending facets into one of its neighbors. +Since all precision problems in Qhull are associated with one or +more facets, Qhull will either fix the problem or attempt to merge the +last remaining facets.

    + +

    »Delaunay +triangulations

    + +

    Programs that use Delaunay triangulations traditionally assume +a triangulated input. By default, qdelaunay +merges regions with cocircular or cospherical input sites. +If you want a simplicial triangulation +use triangulated output ('Qt') or joggled +input ('QJ'). + +

    For Delaunay triangulations, triangulated +output should produce good results. All points are within roundoff error of +a paraboloid. If two points are nearly incident, one will be a +coplanar point. So all points are clearly separated and convex. +If qhull reports deleted vertices, the triangulation +may contain serious precision faults. Deleted vertices are reported +in the summary ('s', 'Fs'

    + +

    You should use option 'Qbb' with Delaunay +triangulations. It scales the last coordinate and may reduce +roundoff error. It is automatically set for qdelaunay, +qvoronoi, and option 'QJ'.

    + +

    Edelsbrunner, H, Geometry and Topology for Mesh Generation, Cambridge University Press, 2001. +Good mathematical treatise on Delaunay triangulation and mesh generation for 2-d +and 3-d surfaces. The chapter on surface simplification is +particularly interesting. It is similar to facet merging in Qhull. + +

    Veron and Leon published an algorithm for shape preserving polyhedral +simplification with bounded error [Computers and Graphics, 22.5:565-585, 1998]. +It remove nodes using front propagation and multiple remeshing. + +

    »Halfspace intersection

    + +

    +The identity pipe for Qhull reveals some precision questions for +halfspace intersections. The identity pipe creates the convex hull of +a set of points and intersects the facets' hyperplanes. It should return the input +points, but narrow distributions may drop points while offset distributions may add +points. It may be better to normalize the input set about the origin. +For example, compare the first results with the later two results: [T. Abraham] +

    + rbox 100 s t | tee r | qconvex FV n | qhalf Fp | cat - r | /bin/sort -n | tail +
    + rbox 100 L1e5 t | tee r | qconvex FV n | qhalf Fp | cat - r | /bin/sort -n | tail +
    + rbox 100 s O10 t | tee r | qconvex FV n | qhalf Fp | cat - r | /bin/sort -n | tail +
    + + +

    »Merged facets

    + +

    Qhull detects precision +problems when computing distances. A precision problem occurs if +the distance computation is less than the maximum roundoff error. +Qhull treats the result of a hyperplane computation as if it +were exact.

    + +

    Qhull handles precision problems by merging non-convex facets. +The result of merging two facets is a thick facet defined by an inner +plane and an outer plane. The inner and outer planes +are offsets from the facet's hyperplane. The inner plane is +clearly below the facet's vertices. At the end of Qhull, the +outer planes are clearly above all input points. Any exact convex +hull must lie between the inner and outer planes.

    + +

    Qhull tests for convexity by computing a point for each facet. +This point is called the facet's centrum. It is the +arithmetic center of the facet's vertices projected to the +facet's hyperplane. For simplicial facets with d +vertices, the centrum is equivalent to the centroid or center of +gravity.

    + +

    Two neighboring facets are convex if each centrum is clearly +below the other hyperplane. The 'Cn' +or 'C-n' options sets the centrum's +radius to n . A centrum is clearly below a hyperplane if +the computed distance from the centrum to the hyperplane is +greater than the centrum's radius plus two maximum roundoff +errors. Two are required because the centrum can be the maximum +roundoff error above its hyperplane and the distance computation +can be high by the maximum roundoff error.

    + +

    With the 'C-n' or 'A-n ' options, Qhull merges non-convex +facets while constructing the hull. The remaining facets are +clearly convex. With the 'Qx ' +option, Qhull merges coplanar facets after constructing the hull. +While constructing the hull, it merges coplanar horizon facets, +flipped facets, concave facets and duplicated ridges. With 'Qx', coplanar points may be missed, but +it appears to be unlikely.

    + +

    If the user sets the 'An' or 'A-n' option, then all neighboring +facets are clearly convex and the maximum (exact) cosine of an +angle is n.

    + +

    If 'C-0' or 'Qx' is used without other precision +options (default), Qhull tests vertices instead of centrums for +adjacent simplices. In 3-d, if simplex abc is adjacent to +simplex bcd, Qhull tests that vertex a is clearly +below simplex bcd , and vertex d is clearly below +simplex abc. When building the hull, Qhull tests vertices +if the horizon is simplicial and no merges occur.

    + +

    »How Qhull merges facets

    + +

    If two facets are not clearly convex, then Qhull removes one +or the other facet by merging the facet into a neighbor. It +selects the merge which minimizes the distance from the +neighboring hyperplane to the facet's vertices. Qhull also +performs merges when a facet has fewer than d neighbors (called a +degenerate facet), when a facet's vertices are included in a +neighboring facet's vertices (called a redundant facet), when a +facet's orientation is flipped, or when a ridge occurs between +more than two facets.

    + +

    Qhull performs merges in a series of passes sorted by merge +angle. Each pass merges those facets which haven't already been +merged in that pass. After a pass, Qhull checks for redundant +vertices. For example, if a vertex has only two neighbors in 3-d, +the vertex is redundant and Qhull merges it into an adjacent +vertex.

    + +

    Merging two simplicial facets creates a non-simplicial facet +of d+1 vertices. Additional merges create larger facets. +When merging facet A into facet B, Qhull retains facet B's +hyperplane. It merges the vertices, neighbors, and ridges of both +facets. It recomputes the centrum if a wide merge has not +occurred (qh_WIDEcoplanar) and the number of extra vertices is +smaller than a constant (qh_MAXnewcentrum).

    + + +

    »Limitations of merged +facets

    + +
      +
    • Uneven dimensions -- +If one coordinate has a larger absolute value than other +coordinates, it may dominate the effect of roundoff errors on +distance computations. You may use option 'QbB' to scale points to the unit cube. +For Delaunay triangulations and Voronoi diagrams, qdelaunay +and qvoronoi always set +option 'Qbb'. It scales the last +coordinate to [0,m] where m is the maximum width of the +other coordinates. Option 'Qbb' is +needed for Delaunay triangulations of integer coordinates +and nearly cocircular points. + +

      For example, compare +

      +        rbox 1000 W0 t | qconvex Qb2:-1e-14B2:1e-14
      +
      +with +
      +        rbox 1000 W0 t | qconvex
      +
      +The distributions are the same but the first is compressed to a 2e-14 slab. +

      +

    • Post-merging of coplanar facets -- In 5-d and higher, option 'Qx' +(default) delays merging of coplanar facets until post-merging. +This may allow "dents" to occur in the intermediate +convex hulls. A point may be poorly partitioned and force a poor +approximation. See option 'Qx' for +further discussion.

      + +

      This is difficult to produce in 5-d and higher. Option 'Q6' turns off merging of concave +facets. This is similar to 'Qx'. It may lead to serious precision errors, +for example, +

      +        rbox 10000 W1e-13  | qhull Q6  Tv
      +
      + +

      +

    • Maximum facet width -- +Qhull reports the maximum outer plane and inner planes (if +more than roundoff error apart). There is no upper bound +for either figure. This is an area for further research. Qhull +does a good job of post-merging in all dimensions. Qhull does a +good job of pre-merging in 2-d, 3-d, and 4-d. With the 'Qx' option, it does a good job in +higher dimensions. In 5-d and higher, Qhull does poorly at +detecting redundant vertices.

      + +

      In the summary ('s'), look at the +ratio between the maximum facet width and the maximum width of a +single merge, e.g., "(3.4x)". Qhull usually reports a +ratio of four or lower in 3-d and six or lower in 4-d. If it +reports a ratio greater than 10, this may indicate an +implementation error. Narrow distributions (see following) may +produce wide facets. + +

      For example, if special processing for narrow distributions is +turned off ('Q10'), qhull may produce +a wide facet:

      +
      +         rbox 1000 L100000 s G1e-16 t1002074964 | qhull Tv Q10
      +
      + +

      +

    • Narrow distribution -- In 3-d, a narrow distribution may result in a poor +approximation. For example, if you do not use qdelaunay nor option +'Qbb', the furthest-site +Delaunay triangulation of nearly cocircular points may produce a poor +approximation: +
      +         rbox s 5000 W1e-13 D2 t1002151341 | qhull d Qt
      +         rbox 1000 s W1e-13 t1002231672 | qhull d Tv
      +
      + +

      During +construction of the hull, a point may be above two +facets with opposite orientations that span the input +set. Even though the point may be nearly coplanar with both +facets, and can be distant from the precise convex +hull of the input sites. Additional facets leave the point distant from +a facet. To fix this problem, add option 'Qbb' +(it scales the last coordinate). Option 'Qbb' +is automatically set for qdelaunay and qvoronoi. + +

      Qhull generates a warning if the initial simplex is narrow. +For narrow distributions, Qhull changes how it processes coplanar +points -- it does not make a point coplanar until the hull is +finished. +Use option 'Q10' to try Qhull without +special processing for narrow distributions. +For example, special processing is needed for: +

      +         rbox 1000 L100000 s G1e-16 t1002074964 | qhull Tv Q10
      +
      + +

      You may turn off the warning message by reducing +qh_WARNnarrow in user.h or by setting option +'Pp'.

      + +

      Similar problems occur for distributions with a large flat facet surrounded +with many small facet at a sharp angle to the large facet. +Qhull 3.1 fixes most of these problems, but a poor approximation can occur. +A point may be left outside of the convex hull ('Tv'). +Examples include +the furthest-site Delaunay triangulation of nearly cocircular points plus the origin, and the convex hull of a cone of nearly cocircular points. The width of the band is 10^-13. +

      +        rbox s 1000 W1e-13 P0 D2 t996799242 | qhull d Tv
      +        rbox 1000 s Z1 G1e-13 t1002152123 | qhull Tv
      +        rbox 1000 s Z1 G1e-13 t1002231668 | qhull Tv
      +
      + +

      +

    • Quadratic running time -- If the output contains large, non-simplicial +facets, the running time for Qhull may be quadratic in the size of the triangulated +output. For example, rbox 1000 s W1e-13 c G2 | qhull d is 4 times +faster for 500 points. The convex hull contains two large nearly spherical facets and +many nearly coplanar facets. Each new point retriangulates the spherical facet and repartitions the remaining points into all of the nearly coplanar facets. +In this case, quadratic running time is avoided if you use qdelaunay, +add option 'Qbb', +or add the origin ('P0') to the input. +

      +

    • Nearly coincident points within 1e-13 -- +Multiple, nearly coincident points within a 1e-13 ball of points in the unit cube +may lead to wide facets or quadratic running time. +For example, the convex hull a 1000 coincident, cospherical points in 4-D, +or the 3-D Delaunay triangulation of nearly coincident points, may lead to very +wide facets (e.g., 2267021951.3x). + +

      For Delaunay triangulations, the problem typically occurs for extreme points of the input +set (i.e., on the edge between the upper and lower convex hull). After multiple facet merges, four +facets may share the same, duplicate ridge and must be merged. +Some of these facets may be long and narrow, leading to a very wide merged facet. +If so, error QH6271 is reported. It may be overriden with option 'Q12'. + +

      Duplicate ridges occur when the horizon facets for a new point is "pinched". +In a duplicate ridge, a subridge (e.g., a line segment in 3-d) is shared by two horizon facets. +At least two of its vertices are nearly coincident. It is easy to generate coincident points with +option 'Cn,r,m' of rbox. It generates n points within an r ball for each of m input sites. For example, +every point of the following distributions has a nearly coincident point within a 1e-13 ball. +Substantially smaller or larger balls do not lead to pinched horizons. +

      +        rbox 1000 C1,1e-13 D4 s t | qhull
      +        rbox 75 C1,1e-13 t | qhull d
      +
      +For Delaunay triangulations, a bounding box may alleviate this error (e.g., rbox 500 C1,1E-13 t c G1 | qhull d). +A later release of qhull will avoid pinched horizons by merging duplicate subridges. A subridge is +merged by merging adjacent vertices. +

      +

    • Facet with zero-area -- +It is possible for a zero-area facet to be convex with its +neighbors. This can occur if the hyperplanes of neighboring +facets are above the facet's centrum, and the facet's hyperplane +is above the neighboring centrums. Qhull computes the facet's +hyperplane so that it passes through the facet's vertices. The +vertices can be collinear.

      + +

      +

    • No more facets -- Qhull reports an error if there are d+1 facets left +and two of the facets are not clearly convex. This typically +occurs when the convexity constraints are too strong or the input +points are degenerate. The former is more likely in 5-d and +higher -- especially with option 'C-n'.

      + +

      +

    • Deleted cone -- Lots of merging can end up deleting all +of the new facets for a point. This is a rare event that has +only been seen while debugging the code. + +

      +

    • Triangulated output leads to precision problems -- With sufficient +merging, the ridges of a non-simplicial facet may have serious topological +and geometric problems. A ridge may be between more than two +neighboring facets. If so, their triangulation ('Qt') +will fail since two facets have the same vertex set. Furthermore, +a triangulated facet may have flipped orientation compared to its +neighbors.
    • + +

      The triangulation process detects degenerate facets with +only two neighbors. These are marked degenerate. They have +zero area. + +

      +

    • Coplanar points -- +Option 'Qc' is determined by +qh_check_maxout() after constructing the hull. Qhull needs to +retain all possible coplanar points in the facets' coplanar sets. +This depends on qh_RATIOnearInside in user.h. +Furthermore, the cutoff for a coplanar point is arbitrarily set +at the minimum vertex. If coplanar points are important to your +application, remove the interior points by hand (set 'Qc Qi') or +make qh_RATIOnearInside sufficiently large.

      + +

      +

    • Maximum roundoff error -- Qhull computes the maximum roundoff error from the maximum +coordinates of the point set. Usually the maximum roundoff error +is a reasonable choice for all distance computations. The maximum +roundoff error could be computed separately for each point or for +each distance computation. This is expensive and it conflicts +with option 'C-n'. + +

      +

    • All flipped or upper Delaunay -- When a lot of merging occurs for +Delaunay triangulations, a new point may lead to no good facets. For example, +try a strong convexity constraint: +
      +        rbox 1000 s t993602376 | qdelaunay C-1e-3
      +
      + +
    + +

    »Joggled input

    + +

    Joggled input is a simple work-around for precision problems +in computational geometry ["joggle: to shake or jar +slightly," Amer. Heritage Dictionary]. Other names are +jostled input or random perturbation. +Qhull joggles the +input by modifying each coordinate by a small random quantity. If +a precision problem occurs, Qhull joggles the input with a larger +quantity and the algorithm is restarted. This process continues +until no precision problems occur. Unless all inputs incur +precision problems, Qhull will terminate. Qhull adjusts the inner +and outer planes to account for the joggled input.

    + +

    Neither joggle nor merged facets has an upper bound for the width of the output +facets, but both methods work well in practice. Joggled input is +easier to justify. Precision errors occur when the points are +nearly singular. For example, four points may be coplanar or +three points may be collinear. Consider a line and an incident +point. A precision error occurs if the point is within some +epsilon of the line. Now joggle the point away from the line by a +small, uniformly distributed, random quantity. If the point is +changed by more than epsilon, the precision error is avoided. The +probability of this event depends on the maximum joggle. Once the +maximum joggle is larger than epsilon, doubling the maximum +joggle will halve the probability of a precision error.

    + +

    With actual data, an analysis would need to account for each +point changing independently and other computations. It is easier +to determine the probabilities empirically ('TRn') . For example, consider +computing the convex hull of the unit cube centered on the +origin. The arithmetic has 16 significant decimal digits.

    + +
    +

    Convex hull of unit cube

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    joggleerror prob.
    1.0e-150.983
    2.0e-150.830
    4.0e-150.561
    8.0e-150.325
    1.6e-140.185
    3.2e-140.099
    6.4e-140.051
    1.3e-130.025
    2.6e-130.010
    5.1e-130.004
    1.0e-120.002
    2.0e-120.001
    +
    + +

    A larger joggle is needed for multiple points. Since the +number of potential singularities increases, the probability of +one or more precision errors increases. Here is an example.

    + +
    +

    Convex hull of 1000 points on unit cube

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    joggleerror prob.
    1.0e-120.870
    2.0e-120.700
    4.0e-120.450
    8.0e-120.250
    1.6e-110.110
    3.2e-110.065
    6.4e-110.030
    1.3e-100.010
    2.6e-100.008
    5.1e-090.003
    +
    + +

    Other distributions behave similarly. No distribution should +behave significantly worse. In Euclidean space, the probability +measure of all singularities is zero. With floating point +numbers, the probability of a singularity is non-zero. With +sufficient digits, the probability of a singularity is extremely +small for random data. For a sufficiently large joggle, all data +is nearly random data.

    + +

    Qhull uses an initial joggle of 30,000 times the maximum +roundoff error for a distance computation. This avoids most +potential singularities. If a failure occurs, Qhull retries at +the initial joggle (in case bad luck occurred). If it occurs +again, Qhull increases the joggle by ten-fold and tries again. +This process repeats until the joggle is a hundredth of the width +of the input points. Qhull reports an error after 100 attempts. +This should never happen with double-precision arithmetic. Once +the probability of success is non-zero, the probability of +success increases about ten-fold at each iteration. The +probability of repeated failures becomes extremely small.

    + +

    Merged facets produces a significantly better approximation. +Empirically, the maximum separation between inner and outer +facets is about 30 times the maximum roundoff error for a +distance computation. This is about 2,000 times better than +joggled input. Most applications though will not notice the +difference.

    + +

    »Exact arithmetic

    + +

    Exact arithmetic may be used instead of floating point. +Singularities such as coplanar points can either be handled +directly or the input can be symbolically perturbed. Using exact +arithmetic is slower than using floating point arithmetic and the +output may take more space. Chaining a sequence of operations +increases the time and space required. Some operations are +difficult to do.

    + +

    Clarkson's hull +program and Shewchuk's triangle +program are practical implementations of exact arithmetic.

    + +

    Clarkson limits the input precision to about fifteen digits. +This reduces the number of nearly singular computations. When a +determinant is nearly singular, he uses exact arithmetic to +compute a precise result.

    + +

    »Approximating a +convex hull

    + +

    Qhull may be used for approximating a convex hull. This is +particularly valuable in 5-d and higher where hulls can be +immense. You can use 'Qx C-n' to merge facets as the hull is +being constructed. Then use 'Cn' +and/or 'An' to merge small facets +during post-processing. You can print the n largest facets +with option 'PAn'. You can print +facets whose area is at least n with option 'PFn'. You can output the outer planes +and an interior point with 'FV Fo' and then compute their intersection +with 'qhalf'.

    + +

    To approximate a convex hull in 6-d and higher, use +post-merging with 'Wn' (e.g., qhull +W1e-1 C1e-2 TF2000). Pre-merging with a convexity constraint +(e.g., qhull Qx C-1e-2) often produces a poor approximation or +terminates with a simplex. Option 'QbB' +may help to spread out the data.

    + +

    You will need to experiment to determine a satisfactory set of +options. Use rbox to generate test sets +quickly and Geomview to view +the results. You will probably want to write your own driver for +Qhull using the Qhull library. For example, you could select the +largest facet in each quadrant.

    + + +
    + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of +Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull imprecision: Table of Contents + + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optc.htm b/xs/src/qhull/html/qh-optc.htm new file mode 100644 index 0000000000..87308180d7 --- /dev/null +++ b/xs/src/qhull/html/qh-optc.htm @@ -0,0 +1,292 @@ + + + + +Qhull precision options + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull precision options

    + +This section lists the precision options for Qhull. These options are +indicated by an upper-case letter followed by a number. + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Precision options

    + +

    Most users will not need to set these options. They are best +used for approximating a +convex hull. They may also be used for testing Qhull's handling +of precision errors.

    + +

    By default, Qhull uses options 'C-0' for +2-d, 3-d and 4-d, and 'Qx' for 5-d +and higher. These options use facet merging to handle precision +errors. You may also use joggled input 'QJ' +to avoid precision problems. +For more information see Imprecision in Qhull.

    + +
    +
     
    +
    General
    +
    Cn
    +
    centrum radius for post-merging
    +
    C-n
    +
    centrum radius for pre-merging
    +
    An
    +
    cosine of maximum angle for post-merging
    +
    A-n
    +
    cosine of maximum angle for pre-merging
    +
    Qx
    +
    exact pre-merges (allows coplanar facets)
    +
    C-0
    +
    handle all precision errors
    +
    Wn
    +
    min distance above plane for outside points
    +
    + +
    +
     
    +
    Experimental
    +
    Un
    +
    max distance below plane for a new, coplanar point
    +
    En
    +
    max roundoff error for distance computation
    +
    Vn
    +
    min distance above plane for a visible facet
    +
    Rn
    +
    randomly perturb computations by a factor of [1-n,1+n]
    +
    + +
    +
    + +
    + +

    »A-n - cosine of maximum +angle for pre-merging.

    + +

    Pre-merging occurs while Qhull constructs the hull. It is +indicated by 'C-n', 'A-n', or 'Qx'.

    + +

    If the angle between a pair of facet normals is greater than n, +Qhull merges one of the facets into a neighbor. It selects the +facet that is closest to a neighboring facet.

    + +

    For example, option 'A-0.99' merges facets during the +construction of the hull. If the cosine of the angle between +facets is greater than 0.99, one or the other facet is merged. +Qhull accounts for the maximum roundoff error.

    + +

    If 'A-n' is set without 'C-n', then 'C-0' is automatically set.

    + +

    In 5-d and higher, you should set 'Qx' +along with 'A-n'. It skips merges of coplanar facets until after +the hull is constructed and before 'An' and 'Cn' are checked.

    + +

    »An - cosine of maximum angle for +post-merging.

    + +

    Post merging occurs after the hull is constructed. For +example, option 'A0.99' merges a facet if the cosine of the angle +between facets is greater than 0.99. Qhull accounts for the +maximum roundoff error.

    + +

    If 'An' is set without 'Cn', then 'C0' is automatically set.

    + +

    »C-0 - handle all precision +errors

    + +

    Qhull handles precision errors by merging facets. The 'C-0' +option handles all precision errors in 2-d, 3-d, and 4-d. It is +set by default. It may be used in higher dimensions, but +sometimes the facet width grows rapidly. It is usually better to +use 'Qx' in 5-d and higher. +Use 'QJ' to joggle the input +instead of merging facets. +Use 'Q0' to turn both options off.

    + +

    Qhull optimizes 'C-0' ("_zero-centrum") by testing +vertices instead of centrums for adjacent simplices. This may be +slower in higher dimensions if merges decrease the number of +processed points. The optimization may be turned off by setting a +small value such as 'C-1e-30'. See How +Qhull handles imprecision.

    + +

    »C-n - centrum radius for +pre-merging

    + +

    Pre-merging occurs while Qhull constructs the hull. It is +indicated by 'C-n', 'A-n', or 'Qx'.

    + +

    The centrum of a facet is a point on the facet for +testing facet convexity. It is the average of the vertices +projected to the facet's hyperplane. Two adjacent facets are +convex if each centrum is clearly below the other facet.

    + +

    If adjacent facets are non-convex, one of the facets is merged +into a neighboring facet. Qhull merges the facet that is closest +to a neighboring facet.

    + +

    For option 'C-n', n is the centrum radius. For example, +'C-0.001' merges facets whenever the centrum is less than 0.001 +from a neighboring hyperplane. Qhull accounts for roundoff error +when testing the centrum.

    + +

    In 5-d and higher, you should set 'Qx' +along with 'C-n'. It skips merges of coplanar facets until after +the hull is constructed and before 'An' and 'Cn' are checked.

    + +

    »Cn - centrum radius for +post-merging

    + +

    Post-merging occurs after Qhull constructs the hull. It is +indicated by 'Cn' or 'An'.

    + +

    For option 'Cn', n is the centrum +radius. For example, 'C0.001' merges facets when the centrum is +less than 0.001 from a neighboring hyperplane. Qhull accounts for +roundoff error when testing the centrum.

    + +

    Both pre-merging and post-merging may be defined. If only +post-merging is used ('Q0' with +'Cn'), Qhull may fail to produce a hull due to precision errors +during the hull's construction.

    + +

    »En - max roundoff error +for distance computations

    + +

    This allows the user to change the maximum roundoff error +computed by Qhull. The value computed by Qhull may be overly +pessimistic. If 'En' is set too small, then the output may not be +convex. The statistic "max. distance of a new vertex to a +facet" (from option 'Ts') is a +reasonable upper bound for the actual roundoff error.

    + +

    »Rn - randomly perturb +computations

    + +

    This option perturbs every distance, hyperplane, and angle +computation by up to (+/- n * max_coord). It simulates the +effect of roundoff errors. Unless 'En' is +explicitly set, it is adjusted for 'Rn'. The command 'qhull Rn' +will generate a convex hull despite the perturbations. See the Examples section for an example.

    + +

    Options 'Rn C-n' have the effect of 'W2n' +and 'C-2n'. To use time as the random number +seed, use option 'QR-1'.

    + +

    »Un - max distance for a +new, coplanar point

    + +

    This allows the user to set coplanarity. When pre-merging ('C-n ', 'A-n' or 'Qx'), Qhull merges a new point into any +coplanar facets. The default value for 'Un' is 'Vn'.

    + +

    »Vn - min distance for a +visible facet

    + +

    This allows the user to set facet visibility. When adding a +point to the convex hull, Qhull determines all facets that are +visible from the point. A facet is visible if the distance from +the point to the facet is greater than 'Vn'.

    + +

    Without merging, the default value for 'Vn' is the roundoff +error ('En'). With merging, the default value +is the pre-merge centrum ('C-n') in 2-d or 3-d, +or three times that in other dimensions. If the outside width is +specified with option 'Wn ', the maximum, +default value for 'Vn' is 'Wn'.

    + +

    Qhull warns if 'Vn' is greater than 'Wn' and +furthest outside ('Qf') is not +selected; this combination usually results in flipped facets +(i.e., reversed normals).

    + +

    »Wn - min distance above +plane for outside points

    + +

    Points are added to the convex hull only if they are clearly +outside of a facet. A point is outside of a facet if its distance +to the facet is greater than 'Wn'. Without pre-merging, the +default value for 'Wn' is 'En '. If the user +specifies pre-merging and does not set 'Wn', than 'Wn' is set to +the maximum of 'C-n' and maxcoord*(1 - A-n).

    + +

    This option is good for approximating +a convex hull.

    + +

    Options 'Qc' and 'Qi' use the minimum vertex to +distinguish coplanar points from interior points.

    + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optf.htm b/xs/src/qhull/html/qh-optf.htm new file mode 100644 index 0000000000..3c7a2b1db2 --- /dev/null +++ b/xs/src/qhull/html/qh-optf.htm @@ -0,0 +1,736 @@ + + + + +Qhull format options (F) + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    +
    + +

    [delaunay] Qhull format options (F)

    + +

    This section lists the format options for Qhull. These options +are indicated by 'F' followed by a letter. See Output, Print, +and Geomview for other output +options.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Additional input & output formats

    + +

    These options allow for automatic processing of Qhull output. +Options 'i', 'o', +'n', and 'p' +may also be used.

    + +
    +
    +
    Summary and control +
    FA +
    compute total area and volume for option 's' + +
    FV +
    print average vertex (interior point for 'qhalf') +
    FQ +
    print command for qhull and input +
    FO +
    print options to stderr or stdout +
    FS +
    print sizes: total area and volume +
    Fs +
    print summary: dim, #points, total vertices and + facets, #vertices, #facets, max outer and inner plane +
    Fd +
    use format for input (offset first) +
    FD +
    use cdd format for normals (offset first) +
    FM +
    print Maple output (2-d and 3-d) +
    +
    +
    Facets, points, and vertices +
    Fa +
    print area for each facet +
    FC +
    print centrum for each facet +
    Fc +
    print coplanar points for each facet +
    Fx +
    print extreme points (i.e., vertices) of convex hull. + +
    FF +
    print facets w/o ridges +
    FI +
    print ID for each facet +
    Fi +
    print inner planes for each facet +
    Fm +
    print merge count for each facet (511 max) +
    FP +
    print nearest vertex for coplanar points +
    Fn +
    print neighboring facets for each facet +
    FN +
    print neighboring facets for each point +
    Fo +
    print outer planes for each facet +
    Ft +
    print triangulation with added points +
    Fv +
    print vertices for each facet +
    +
    +
    Delaunay, Voronoi, and halfspace +
    Fx +
    print extreme input sites of Delaunay triangulation + or Voronoi diagram. +
    Fp +
    print points at halfspace intersections +
    Fi +
    print separating hyperplanes for inner, bounded + Voronoi regions +
    Fo +
    print separating hyperplanes for outer, unbounded + Voronoi regions +
    Fv +
    print Voronoi diagram as ridges for each input pair +
    FC +
    print Voronoi vertex ("center") for each facet
    +
    + +
    + +

    »Fa - print area for each +facet

    + +

    The first line is the number of facets. The remaining lines +are the area for each facet, one facet per line. See 'FA' and 'FS' for computing the total area and volume.

    + +

    Use 'PAn' for printing the n +largest facets. Use option 'PFn' +for printing facets larger than n.

    + +

    For Delaunay triangulations, the area is the area of each +Delaunay triangle. For Voronoi vertices, the area is the area of +the dual facet to each vertex.

    + +

    Qhull uses the centrum and ridges to triangulate +non-simplicial facets. The area for non-simplicial facets is the +sum of the areas for each triangle. It is an approximation of the +actual area. The ridge's vertices are projected to the facet's +hyperplane. If a vertex is far below a facet (qh_WIDEcoplanar in user.h), +the corresponding triangles are ignored.

    + +

    For non-simplicial facets, vertices are often below the +facet's hyperplane. If so, the approximation is less than the +actual value and it may be significantly less.

    + +

    »FA - compute total area +and volume for option 's'

    + +

    With option 'FA', Qhull includes the total area and volume in +the summary ('s'). Option 'FS' also includes the total area and volume. +If facets are +merged, the area and volume are approximations. Option 'FA' is +automatically set for options 'Fa', 'PAn', and 'PFn'. +

    + +

    With 'qdelaunay s FA', Qhull computes the total area of +the Delaunay triangulation. This equals the volume of the convex +hull of the data points. With options 'qdelaunay Qu +s FA', Qhull computes the +total area of the furthest-site Delaunay triangulation. This +equals of the total area of the Delaunay triangulation.

    + +

    See 'Fa' for further details. Option 'FS' also computes the total area and volume.

    + +

    »Fc - print coplanar +points for each facet

    + +

    The output starts with the number of facets. Then each facet +is printed one per line. Each line is the number of coplanar +points followed by the point ids.

    + +

    By default, option 'Fc' reports coplanar points +('Qc'). You may also use +option 'Qi'. Options 'Qi Fc' prints +interior points while 'Qci Fc' prints both coplanar and interior +points. + +

    Each coplanar point or interior point is assigned to the +facet it is furthest above (resp., least below).

    + +

    Use 'Qc p' to print vertex and +coplanar point coordinates. Use 'Fv' +to print vertices.

    + +

    »FC - print centrum or +Voronoi vertex for each facet

    + +

    The output starts with the dimension followed by the number of +facets. Then each facet centrum is printed, one per line. For +qvoronoi, Voronoi vertices are +printed instead.

    + +

    »Fd - use cdd format for +input

    + +

    The input starts with comments. The first comment is reported +in the summary. Data starts after a "begin" line. The +next line is the number of points followed by the dimension plus +one and "real" or "integer". Then the points +are listed with a leading "1" or "1.0". The +data ends with an "end" line.

    + +

    For halfspaces ('qhalf Fd'), +the input format is the same. Each halfspace starts with its +offset. The signs of the offset and coefficients are the +opposite of Qhull's +convention. The first two lines of the input may be an interior +point in 'FV' format.

    + +

    »FD - use cdd format for +normals

    + +

    Option 'FD' prints normals ('n', 'Fo', 'Fi') or points ('p') in cdd format. The first line is the +command line that invoked Qhull. Data starts with a +"begin" line. The next line is the number of normals or +points followed by the dimension plus one and "real". +Then the normals or points are listed with the offset before the +coefficients. The offset for points is 1.0. For normals, +the offset and coefficients use the opposite sign from Qhull. +The data ends with an "end" line.

    + +

    »FF - print facets w/o +ridges

    + +

    Option 'FF' prints all fields of all facets (as in 'f') without printing the ridges. This is +useful in higher dimensions where a facet may have many ridges. +For simplicial facets, options 'FF' and 'f +' are equivalent.

    + +

    »Fi - print inner planes +for each facet

    + +

    The first line is the dimension plus one. The second line is +the number of facets. The remainder is one inner plane per line. +The format is the same as option 'n'.

    + +

    The inner plane is a plane that is below the facet's vertices. +It is an offset from the facet's hyperplane. It includes a +roundoff error for computing the vertex distance.

    + +

    Note that the inner planes for Geomview output ('Gi') include an additional offset for +vertex visualization and roundoff error.

    + +

    »Fi - print separating +hyperplanes for inner, bounded Voronoi regions

    + +

    With qvoronoi, 'Fi' prints the +separating hyperplanes for inner, bounded regions of the Voronoi +diagram. The first line is the number of ridges. Then each +hyperplane is printed, one per line. A line starts with the +number of indices and floats. The first pair of indices indicates +an adjacent pair of input sites. The next d floats are the +normalized coefficients for the hyperplane, and the last float is +the offset. The hyperplane is oriented toward 'QVn' (if defined), or the first input +site of the pair.

    + +

    Use 'Fo' for unbounded regions, +and 'Fv' for the corresponding +Voronoi vertices.

    + +

    Use 'Tv' to verify that the +hyperplanes are perpendicular bisectors. It will list relevant +statistics to stderr. The hyperplane is a perpendicular bisector +if the midpoint of the input sites lies on the plane, all Voronoi +vertices in the ridge lie on the plane, and the angle between the +input sites and the plane is ninety degrees. This is true if all +statistics are zero. Roundoff and computation errors make these +non-zero. The deviations appear to be largest when the +corresponding Delaunay triangles are large and thin; for example, +the Voronoi diagram of nearly cospherical points.

    + +

    »FI - print ID for each +facet

    + +

    Print facet identifiers. These are used internally and listed +with options 'f' and 'FF'. +Options 'Fn ' and 'FN' use +facet identifiers for negative indices.

    + +

    »Fm - print merge count +for each facet

    + +

    The first line is the number of facets. The remainder is the +number of merges for each facet, one per line. At most 511 merges +are reported for a facet. See 'PMn' +for printing the facets with the most merges.

    + +

    »FM - print Maple +output

    + +

    Qhull writes a Maple file for 2-d and 3-d convex hulls, +2-d and 3-d halfspace intersections, +and 2-d Delaunay triangulations. Qhull produces a 2-d +or 3-d plot. + +

    Warning: This option has not been tested in Maple. + +

    [From T. K. Abraham with help from M. R. Feinberg and N. Platinova.] +The following steps apply while working within the +Maple worksheet environment : +

      +
    1. Generate the data and store it as an array . For example, in 3-d, data generated +in Maple is of the form : x[i],y[i],z[i] +

      +

    2. Create a single variable and assign the entire array of data points to this variable. +Use the "seq" command within square brackets as shown in the following example. +(The square brackets are essential for the rest of the steps to work.) +

      +>data:=[seq([x[i],y[i],z[i]],i=1..n)]:# here n is the number of data points + +

    3. Next we need to write the data to a file to be read by qhull. Before +writing the data to a file, make sure that the qhull executable files and +the data file lie in the same subdirectory. If the executable files are +stored in the "C:\qhull3.1\" subdirectory, then save the file in the same +subdirectory, say "C:\qhull3.1\datafile.txt". For the sake of integrity of +the data file , it is best to first ensure that the data file does not +exist before writing into the data file. This can be done by running a +delete command first . To write the data to the file, use the "writedata" +and the "writedata[APPEND]" commands as illustrated in the following example : +

      +>system("del c:\\qhull3.1\\datafile.txt");#To erase any previous versions of the file +
      >writedata("c:\\qhull3.1\\datafile.txt ",[3, nops(data)]);#writing in qhull format +
      >writedata[APPEND]("c:\\ qhull3.1\\datafile.txt ", data);#writing the data points +

    4. +Use the 'FM' option to produce Maple output. Store the output as a ".mpl" file. +For example, using the file we created above, we type the following (in DOS environment) +

      +qconvex s FM <datafile.txt >dataplot.mpl + +

    5. +To read 3-d output in Maple, we use the 'read' command followed by +a 'display3d' command. For example (in Maple environment): +

      +>with (plots): +
      >read `c:\\qhull3.1\\dataplot.mpl`:#IMPORTANT - Note that the punctuation mark used is ' and NOT '. The correct punctuation mark is the one next to the key for "1" (not the punctuation mark near the enter key) +
      > qhullplot:=%: +
      > display3d(qhullplot); +

    + +

    For Delaunay triangulation orthogonal projection is better. + +

    For halfspace intersections, Qhull produces the dual +convex hull. + +

    See Is Qhull available for Maple? +for other URLs. + +

    »Fn - print neighboring +facets for each facet

    + +

    The output starts with the number of facets. Then each facet +is printed one per line. Each line is the number of neighbors +followed by an index for each neighbor. The indices match the +other facet output formats.

    + +

    For simplicial facets, each neighbor is opposite +the corresponding vertex (option 'Fv'). +Do not compare to option 'i'. Option 'i' +orients facets by reversing the order of two vertices. For non-simplicial facets, +the neighbors are unordered. + +

    A negative index indicates an unprinted facet due to printing +only good facets ('Pg', qdelaunay, +qvoronoi). It +is the negation of the facet's ID (option 'FI'). +For example, negative indices are used for facets "at +infinity" in the Delaunay triangulation.

    + +

    »FN - print neighboring +facets for each point

    + +

    The first line is the number of points. Then each point is +printed, one per line. For unassigned points (either interior or +coplanar), the line is "0". For assigned coplanar +points ('Qc'), the line is +"1" followed by the index of the facet that is furthest +below the point. For assigned interior points ('Qi'), the line is "1" +followed by the index of the facet that is least above the point. +For vertices that do not belong to good facet, the line is +"0"

    + +

    For vertices of good facets, the line is the number of +neighboring facets followed by the facet indices. The indices +correspond to the other 'F' formats. In 4-d +and higher, the facets are sorted by index. In 3-d, the facets +are in adjacency order (not oriented).

    + +

    A negative index indicates an unprinted facet due to printing +only good facets (qdelaunay, +qvoronoi, 'Pdk', +'Pg'). It is the negation of the +facet's ID (' FI'). For example, negative +indices are used for facets "at infinity" in the +Delaunay triangulation.

    + +

    For Voronoi vertices, option 'FN' lists the vertices of the +Voronoi region for each input site. Option 'FN' lists the regions +in site ID order. Option 'FN' corresponds to the second half of +option 'o'. To convert from 'FN' to 'o', replace negative indices with zero +and increment non-negative indices by one.

    + +

    If you are using the Qhull +library or C++ interface, option 'FN' has the side effect of reordering the +neighbors for a vertex

    + +

    »Fo - print outer planes +for each facet

    + +

    The first line is the dimension plus one. The second line is +the number of facets. The remainder is one outer plane per line. +The format is the same as option 'n'.

    + +

    The outer plane is a plane that is above all points. It is an +offset from the facet's hyperplane. It includes a roundoff error +for computing the point distance. When testing the outer plane +(e.g., 'Tv'), another roundoff error +should be added for the tested point.

    + +

    If outer planes are not checked ('Q5') +or not computed (!qh_MAXoutside), the maximum, computed outside +distance is used instead. This can be much larger than the actual +outer planes.

    + +

    Note that the outer planes for Geomview output ('G') include an additional offset for +vertex/point visualization, 'lines closer,' and roundoff error.

    + +

    »Fo - print separating +hyperplanes for outer, unbounded Voronoi regions

    + +

    With qvoronoi, 'Fo' prints the +separating hyperplanes for outer, unbounded regions of the +Voronoi diagram. The first line is the number of ridges. Then +each hyperplane is printed, one per line. A line starts with the +number of indices and floats. The first pair of indices indicates +an adjacent pair of input sites. The next d floats are the +normalized coefficients for the hyperplane, and the last float is +the offset. The hyperplane is oriented toward 'QVn' (if defined), or the first input +site of the pair.

    + +

    Option 'Fo' gives the hyperplanes for the unbounded rays of +the unbounded regions of the Voronoi diagram. Each hyperplane +goes through the midpoint of the corresponding input sites. The +rays are directed away from the input sites.

    + +

    Use 'Fi' for bounded regions, +and 'Fv' for the corresponding +Voronoi vertices. Use 'Tv' to verify +that the corresponding Voronoi vertices lie on the hyperplane.

    + +

    »FO - print list of +selected options

    + +

    Lists selected options and default values to stderr. +Additional 'FO's are printed to stdout.

    + +

    »Fp - print points at +halfspace intersections

    + +

    The first line is the number of intersection points. The +remainder is one intersection point per line. A intersection +point is the intersection of d or more halfspaces from +'qhalf'. It corresponds to a +facet of the dual polytope. The "infinity" point +[-10.101,-10.101,...] indicates an unbounded intersection.

    + +

    If [x,y,z] are the dual facet's normal coefficients and b<0 +is its offset, the halfspace intersection occurs at +[x/-b,y/-b,z/-b] plus the interior point. If b>=0, the +halfspace intersection is unbounded.

    + +

    »FP - print nearest +vertex for coplanar points

    + +

    The output starts with the number of coplanar points. Then +each coplanar point is printed one per line. Each line is the +point ID of the closest vertex, the point ID of the coplanar +point, the corresponding facet ID, and the distance. Sort the +lines to list the coplanar points nearest to each vertex.

    + +

    Use options 'Qc' and/or 'Qi' with 'FP'. Options 'Qc FP' prints +coplanar points while 'Qci FP' prints coplanar and interior +points. Option 'Qc' is automatically selected if 'Qi' is not +selected. + +

    For Delaunay triangulations (qdelaunay +or qvoronoi), a coplanar point is nearly +incident to a vertex. The distance is the distance in the +original point set.

    + +

    If imprecision problems are severe, Qhull will delete input +sites when constructing the Delaunay triangulation. Option 'FP' will +list these points along with coincident points.

    + +

    If there are many coplanar or coincident points and non-simplicial +facets are triangulated ('Qt'), option +'FP' may be inefficient. It redetermines the original vertex set +for each coplanar point.

    + +

    »FQ - print command for +qhull and input

    + +

    Prints qhull and input command, e.g., "rbox 10 s | qhull +FQ". Option 'FQ' may be repeated multiple times.

    + +

    »Fs - print summary

    + +

    The first line consists of number of integers ("10") +followed by the: +

      +
    • dimension +
    • number of points +
    • number of vertices +
    • number of facets +
    • number of vertices selected for output +
    • number of facets selected for output +
    • number of coplanar points for selected facets +
    • number of nonsimplicial or merged facets selected for + output +
    • number of deleted vertices
    • +
    • number of triangulated facets ('Qt')
    • +
    + +

    The second line consists of the number of reals +("2") followed by the: +

      +
    • maximum offset to an outer plane +
    • minimum offset to an inner plane.
    • +
    +Roundoff and joggle are included. +

    + +

    For Delaunay triangulations and Voronoi diagrams, the +number of deleted vertices should be zero. If greater than zero, then the +input is highly degenerate and coplanar points are not necessarily coincident +points. For example, 'RBOX 1000 s W1e-13 t995138628 | QHULL d Qbb' reports +deleted vertices; the input is nearly cospherical.

    + +

    Later versions of Qhull may produce additional integers or reals.

    + +

    »FS - print sizes

    + +

    The first line consists of the number of integers +("0"). The second line consists of the number of reals +("2"), followed by the total facet area, and the total +volume. Later versions of Qhull may produce additional integers +or reals.

    + +

    The total volume measures the volume of the intersection of +the halfspaces defined by each facet. It is computed from the +facet area. Both area and volume are approximations for +non-simplicial facets. See option 'Fa ' for +further notes. Option 'FA ' also computes the total area and volume.

    + +

    »Ft - print triangulation

    + +

    Prints a triangulation with added points for non-simplicial +facets. The output is

    + +
      +
    • The first line is the dimension +
    • The second line is the number of points, the number + of facets, and the number of ridges. +
    • All of the input points follow, one per line. +
    • The centrums follow, one per non-simplicial facet +
    • Then the facets follow as a list of point indices + preceded by the number of points. The simplices are + oriented.
    • +
    + +

    For convex hulls with simplicial facets, the output is the +same as option 'o'.

    + +

    The added points are the centrums of the non-simplicial +facets. Except for large facets, the centrum is the average +vertex coordinate projected to the facet's hyperplane. Large +facets may use an old centrum to avoid recomputing the centrum +after each merge. In either case, the centrum is clearly below +neighboring facets. See Precision issues. +

    + +

    The new simplices will not be clearly convex with their +neighbors and they will not satisfy the Delaunay property. They +may even have a flipped orientation. Use triangulated input ('Qt') for Delaunay triangulations. + +

    For Delaunay triangulations with simplicial facets, the output is the +same as option 'o' without the lifted +coordinate. Since 'Ft' is invalid for merged Delaunay facets, option +'Ft' is not available for qdelaunay or qvoronoi. It may be used with +joggled input ('QJ') or triangulated output ('Qt'), for example, rbox 10 c G 0.01 | qhull d QJ Ft

    + +

    If you add a point-at-infinity with 'Qz', +it is printed after the input sites and before any centrums. It +will not be used in a Delaunay facet.

    + +

    »Fv - print vertices for +each facet

    + +

    The first line is the number of facets. Then each facet is +printed, one per line. Each line is the number of vertices +followed by the corresponding point ids. Vertices are listed in +the order they were added to the hull (the last one added is the +first listed). +

    +

    Option 'i' also lists the vertices, +but it orients facets by reversing the order of two +vertices. Option 'i' triangulates non-simplicial, 4-d and higher facets by +adding vertices for the centrums. +

    + +

    »Fv - print Voronoi +diagram

    + +

    With qvoronoi, 'Fv' prints the +Voronoi diagram or furthest-site Voronoi diagram. The first line +is the number of ridges. Then each ridge is printed, one per +line. The first number is the count of indices. The second pair +of indices indicates a pair of input sites. The remaining indices +list the corresponding ridge of Voronoi vertices. Vertex 0 is the +vertex-at-infinity. It indicates an unbounded ray.

    + +

    All vertices of a ridge are coplanar. If the ridge is +unbounded, add the midpoint of the pair of input sites. The +unbounded ray is directed from the Voronoi vertices to infinity.

    + +

    Use 'Fo' for separating +hyperplanes of outer, unbounded regions. Use 'Fi' for separating hyperplanes of +inner, bounded regions.

    + +

    Option 'Fv' does not list ridges that require more than one +midpoint. For example, the Voronoi diagram of cospherical points +lists zero ridges (e.g., 'rbox 10 s | qvoronoi Fv Qz'). +Other examples are the Voronoi diagrams of a rectangular mesh +(e.g., 'rbox 27 M1,0 | qvoronoi Fv') or a point set with +a rectangular corner (e.g., +'rbox P4,4,4 P4,2,4 P2,4,4 P4,4,2 10 | qvoronoi Fv'). +Both cases miss unbounded rays at the corners. +To determine these ridges, surround the points with a +large cube (e.g., 'rbox 10 s c G2.0 | qvoronoi Fv Qz'). +The cube needs to be large enough to bound all Voronoi regions of the original point set. +Please report any other cases that are missed. If you +can formally describe these cases or +write code to handle them, please send email to qhull@qhull.org.

    + +

    »FV - print average +vertex

    + +

    The average vertex is the average of all vertex coordinates. +It is an interior point for halfspace intersection. The first +line is the dimension and "1"; the second line is the +coordinates. For example,

    + +
    +

    qconvex FV n | qhalf Fp

    +
    + +

    prints the extreme points of the original point set (roundoff +included).

    + +

    »Fx - print extreme +points (vertices) of convex hulls and Delaunay triangulations

    + +

    The first line is the number of points. The following lines +give the index of the corresponding points. The first point is +'0'.

    + +

    In 2-d, the extreme points (vertices) are listed in +counterclockwise order (by qh_ORIENTclock in user.h).

    + +

    In 3-d and higher convex hulls, the extreme points (vertices) +are sorted by index. This is the same order as option 'p' when it doesn't include coplanar or +interior points.

    + +

    For Delaunay triangulations, 'Fx' lists the extreme +points of the input sites (i.e., the vertices of their convex hull). The points +are unordered.

    + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: +Sept. 25, 1995 --- Last modified: see top +

    + + diff --git a/xs/src/qhull/html/qh-optg.htm b/xs/src/qhull/html/qh-optg.htm new file mode 100644 index 0000000000..a56e29df10 --- /dev/null +++ b/xs/src/qhull/html/qh-optg.htm @@ -0,0 +1,274 @@ + + + +Qhull Geomview options (G) + + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + + +

    [delaunay] Qhull Geomview options (G)

    + +This section lists the Geomview options for Qhull. These options are +indicated by 'G' followed by a letter. See +Output, Print, +and Format for other output options. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Geomview output options

    + +

    Geomview is the graphical +viewer for visualizing Qhull output in 2-d, 3-d and 4-d.

    + +

    Geomview displays each facet of the convex hull. The color of +a facet is determined by the coefficients of the facet's normal +equation. For imprecise hulls, Geomview displays the inner and +outer hull. Geomview can also display points, ridges, vertices, +coplanar points, and facet intersections.

    + +

    For 2-d Delaunay triangulations, Geomview displays the +corresponding paraboloid. Geomview displays the 2-d Voronoi +diagram. For halfspace intersections, it displays the +dual convex hull.

    + +
    +
     
    +
    General
    +
    G
    +
    display Geomview output
    +
    Gt
    +
    display transparent 3-d Delaunay triangulation
    +
    GDn
    +
    drop dimension n in 3-d and 4-d output
    + +
     
    +
     
    +
    Specific
    +
    Ga
    +
    display all points as dots
    +
    Gc
    +
    display centrums (2-d, 3-d)
    +
    Gp
    +
    display coplanar points and vertices as radii
    +
    Gh
    +
    display hyperplane intersections
    +
    Gi
    +
    display inner planes only (2-d, 3-d)
    +
    Go
    +
    display outer planes only (2-d, 3-d)
    +
    Gr
    +
    display ridges (3-d)
    +
    Gv
    +
    display vertices as spheres
    +
    Gn
    +
    do not display planes
    + +
    + +
    + +

    »G - produce output for +viewing with Geomview

    + +

    By default, option 'G' displays edges in 2-d, outer planes in +3-d, and ridges in 4-d.

    + +

    A ridge can be explicit or implicit. An explicit ridge is a (d-1)-dimensional +simplex between two facets. In 4-d, the explicit ridges are +triangles. An implicit ridge is the topological intersection of +two neighboring facets. It is the union of explicit ridges.

    + +

    For non-simplicial 4-d facets, the explicit ridges can be +quite complex. When displaying a ridge in 4-d, Qhull projects the +ridge's vertices to one of its facets' hyperplanes. Use 'Gh' to project ridges to the intersection of both +hyperplanes. This usually results in a cleaner display.

    + +

    For 2-d Delaunay triangulations, Geomview displays the +corresponding paraboloid. Geomview displays the 2-d Voronoi +diagram. For halfspace intersections, it displays the +dual convex hull. + +

    »Ga - display all +points as dots

    + +

    Each input point is displayed as a green dot.

    + +

    »Gc - display centrums +(3-d)

    + +

    The centrum is defined by a green radius sitting on a blue +plane. The plane corresponds to the facet's hyperplane. If you +sight along a facet's hyperplane, you will see that all +neighboring centrums are below the facet. The radius is defined +by 'C-n' or 'Cn'.

    + +

    »GDn - drop dimension +n in 3-d and 4-d output

    + +

    The result is a 2-d or 3-d object. In 4-d, this corresponds to +viewing the 4-d object from the nth axis without perspective. +It's best to view 4-d objects in pieces. Use the 'Pdk' 'Pg' +'PG' 'QGn' +and 'QVn' options to select a few +facets. If one of the facets is perpendicular to an axis, then +projecting along that axis will show the facet exactly as it is +in 4-d. If you generate many facets, use Geomview's ginsu +module to view the interior

    + +

    To view multiple 4-d dimensions at once, output the object +without 'GDn' and read it with Geomview's ndview. As you +rotate the object in one set of dimensions, you can see how it +changes in other sets of dimensions.

    + +

    For additional control over 4-d objects, output the object +without 'GDn' and read it with Geomview's 4dview. You +can slice the object along any 4-d plane. You can also flip the +halfspace that's deleted when slicing. By combining these +features, you can get some interesting cross sections.

    + +

    »Gh - display +hyperplane intersections (3-d, 4-d)

    + +

    In 3-d, the intersection is a black line. It lies on two +neighboring hyperplanes, c.f., the blue squares associated with +centrums ('Gc '). In 4-d, the ridges are +projected to the intersection of both hyperplanes. If you turn on +edges (Geomview's 'appearances' menu), each triangle corresponds +to one ridge. The ridges may overlap each other.

    + +

    »Gi - display inner +planes only (2-d, 3-d)

    + +

    The inner plane of a facet is below all of its vertices. It is +parallel to the facet's hyperplane. The inner plane's color is +the opposite of the outer plane's color, i.e., [1-r,1-g,1-b] . +Its edges are determined by the vertices.

    + +

    »Gn - do not display +planes

    + +

    By default, Geomview displays the precise plane (no merging) +or both inner and output planes (if merging). If merging, +Geomview does not display the inner plane if the the difference +between inner and outer is too small.

    + +

    »Go - display outer +planes only (2-d, 3-d)

    + +

    The outer plane of a facet is above all input points. It is +parallel to the facet's hyperplane. Its color is determined by +the facet's normal, and its edges are determined by the vertices.

    + +

    »Gp - display coplanar +points and vertices as radii

    + +

    Coplanar points ('Qc'), interior +points ('Qi'), outside points ('TCn' or 'TVn'), +and vertices are displayed as red and yellow radii. The radii are +perpendicular to the corresponding facet. Vertices are aligned +with an interior point. The radii define a ball which corresponds +to the imprecision of the point. The imprecision is the maximum +of the roundoff error, the centrum radius, and maxcoord * (1 - +A-n). It is at +least 1/20'th of the maximum coordinate, and ignores post merging +if pre-merging is done.

    + +

    If 'Gv' (print vertices as +spheres) is also selected, option 'Gp' displays coplanar +points as radii. Select options Qc' +and/or 'Qi'. Options 'Qc Gpv' displays +coplanar points while 'Qci Gpv' displays coplanar and interior +points. Option 'Qc' is automatically selected if 'Qi' is not +selected with options 'Gpv'. + +

    »Gr - display ridges +(3-d)

    + +

    A ridge connects the two vertices that are shared by +neighboring facets. It is displayed in green. A ridge is the +topological edge between two facets while the hyperplane +intersection is the geometric edge between two facets. Ridges are +always displayed in 4-d.

    + +

    »Gt - transparent 3-d +Delaunay

    + +

    A 3-d Delaunay triangulation looks like a convex hull with +interior facets. Option 'Gt' removes the outside ridges to reveal +the outermost facets. It automatically sets options 'Gr' and 'GDn'. See example eg.17f.delaunay.3.

    + +

    »Gv - display vertices +as spheres (2-d, 3-d)

    + +

    The radius of the sphere corresponds to the imprecision of the +data. See 'Gp' for determining the radius.

    + + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-opto.htm b/xs/src/qhull/html/qh-opto.htm new file mode 100644 index 0000000000..e7b21745c1 --- /dev/null +++ b/xs/src/qhull/html/qh-opto.htm @@ -0,0 +1,353 @@ + + + + +Qhull output options + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull output options

    + +

    This section lists the output options for Qhull. These options +are indicated by lower case characters. See Formats, Print, and Geomview for other output +options.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Output options

    + +

    Qhull prints its output to standard out. All output is printed +text. The default output is a summary (option 's'). +Other outputs may be specified as follows.

    + +
    +
    f
    +
    print all fields of all facets
    +
    n
    +
    print hyperplane normals with offsets
    +
    m
    +
    print Mathematica output (2-d and 3-d)
    +
    o
    +
    print OFF file format (dim, points and facets)
    +
    s
    +
    print summary to stderr
    +
    p
    +
    print vertex and point coordinates
    +
    i
    +
    print vertices incident to each facet
    +
     
    +
     
    +
    Related options
    +
    F
    +
    additional input/output formats
    +
    G
    +
    Geomview output
    +
    P
    +
    Print options
    +
    Ft
    +
    print triangulation with added points
    +
     
    +
    + +
    + +

    »f - print all fields of +all facets

    + +

    Print all fields of all facets. +The facet is the primary data structure for +Qhull. + +

    Option 'f' is for +debugging. Most of the fields are available via the 'F' options. If you need specialized +information from Qhull, you can use the Qhull library or C++ interface.

    + +

    Use the 'FF' option to print the +facets but not the ridges.

    + +

    »i - print vertices +incident to each facet

    + +

    The first line is the number of facets. The remaining lines +list the vertices for each facet, one facet per line. The indices +are 0-relative indices of the corresponding input points. The +facets are oriented. Option 'Fv' +displays an unoriented list of vertices with a vertex count per +line. Options 'o' and 'Ft' displays coordinates for each +vertex prior to the vertices for each facet.

    + +

    Simplicial facets (e.g., triangles in 3-d) consist of d +vertices. Non-simplicial facets in 3-d consist of 4 or more +vertices. For example, a facet of a cube consists of 4 vertices. +Use option 'Qt' to triangulate non-simplicial facets.

    + +

    For 4-d and higher convex hulls and 3-d and higher Delaunay +triangulations, d vertices are listed for all facets. A +non-simplicial facet is triangulated with its centrum and each +ridge. The index of the centrum is higher than any input point. +Use option 'Fv' to list the vertices +of non-simplicial facets as is. Use option 'Ft' to print the coordinates of the +centrums as well as those of the input points.

    + +

    »m - print Mathematica +output

    + +

    Qhull writes a Mathematica file for 2-d and 3-d convex hulls, +2-d and 3-d halfspace intersections, +and 2-d Delaunay triangulations. Qhull produces a list of +objects that you can assign to a variable in Mathematica, for +example: "list= << <outputfilename> ". +If the object is 2-d, it can be visualized by "Show[Graphics[list]] +". For 3-d objects the command is "Show[Graphics3D[list]] +". Now the object can be manipulated by commands of the +form "Show[%, <parametername> -> +<newvalue>]".

    + +

    For Delaunay triangulation orthogonal projection is better. +This can be specified, for example, by "BoxRatios: +Show[%, BoxRatios -> {1, 1, 1e-8}]". To see the +meaningful side of the 3-d object used to visualize 2-d Delaunay, +you need to change the viewpoint: "Show[%, ViewPoint +-> {0, 0, -1}]". By specifying different viewpoints +you can slowly rotate objects.

    + +

    For halfspace intersections, Qhull produces the dual +convex hull. + +

    See Is Qhull available for Mathematica? +for URLs. + +

    »n - print hyperplane +normals with offsets

    + +

    The first line is the dimension plus one. The second line is +the number of facets. The remaining lines are the normals for +each facet, one normal per line. The facet's offset follows its +normal coefficients.

    + +

    The normals point outward, i.e., the convex hull satisfies Ax +<= -b where A is the matrix of coefficients and b +is the vector of offsets.

    + +

    A point is inside or below a hyperplane if its distance +to the hyperplane is negative. A point is outside or above a hyperplane +if its distance to the hyperplane is positive. Otherwise a point is on or +coplanar to the hyperplane. + +

    If cdd output is specified ('FD'), +Qhull prints the command line, the keyword "begin", the +number of facets, the dimension (plus one), the keyword +"real", and the normals for each facet. The facet's +negative offset precedes its normal coefficients (i.e., if the +origin is an interior point, the offset is positive). Qhull ends +the output with the keyword "end".

    + +

    »o - print OFF file format +

    + +

    The output is:

    + +
      +
    • The first line is the dimension
    • +
    • The second line is the number of points, the number of + facets, and the number of ridges.
    • +
    • All of the input points follow, one per line.
    • +
    • Then Qhull prints the vertices for each facet. Each facet + is on a separate line. The first number is the number of + vertices. The remainder is the indices of the + corresponding points. The vertices are oriented in 2-d, + 3-d, and in simplicial facets.
    • +
    + +

    Option 'Ft' prints the same +information with added points for non-simplicial facets.

    + +

    Option 'i' displays vertices +without the point coordinates. Option 'p' +displays the point coordinates without vertex and facet information.

    + +

    In 3-d, Geomview can load the file directly if you delete the +first line (e.g., by piping through 'tail +2').

    + +

    For Voronoi diagrams (qvoronoi), option +'o' prints Voronoi vertices and Voronoi regions instead of input +points and facets. The first vertex is the infinity vertex +[-10.101, -10.101, ...]. Then, option 'o' lists the vertices in +the Voronoi region for each input site. The regions appear in +site ID order. In 2-d, the vertices of a Voronoi region are +sorted by adjacency (non-oriented). In 3-d and higher, the +Voronoi vertices are sorted by index. See the 'FN' option for listing Voronoi regions +without listing Voronoi vertices.

    + +

    If you are using the Qhull library, options 'v o' have the +side effect of reordering the neighbors for a vertex.

    + +

    »p - print vertex and +point coordinates

    + +

    The first line is the dimension. The second line is the number +of vertices. The remaining lines are the vertices, one vertex per +line. A vertex consists of its point coordinates

    + +

    With the 'Gc' and 'Gi' options, option 'p' also prints +coplanar and interior points respectively.

    + +

    For qvoronoi, it prints the +coordinates of each Voronoi vertex.

    + +

    For qdelaunay, it prints the +input sites as lifted to a paraboloid. For qhalf +it prints the dual points. For both, option 'p' is the same as the first +section of option 'o'.

    + +

    Use 'Fx' to list the point ids of +the extreme points (i.e., vertices).

    + +

    If a subset of the facets is selected ('Pdk', 'PDk', +'Pg' options), option 'p' only +prints vertices and points associated with those facets.

    + +

    If cdd-output format is selected ('FD'), +the first line is "begin". The second line is the +number of vertices, the dimension plus one, and "real". +The vertices follow with a leading "1". Output ends +with "end".

    + +

    »s - print summary to +stderr

    + +

    The default output of Qhull is a summary to stderr. Options 'FS' and 'Fs' +produce the same information for programs. Note: Windows 95 and 98 +treats stderr the same as stdout. Use option 'TO file' to separate +stderr and stdout.

    + +

    The summary lists the number of input points, the dimension, +the number of vertices in the convex hull, and the number of +facets in the convex hull. It lists the number of selected +("good") facets for options 'Pg', +'Pdk', qdelaunay, +or qvoronoi (Delaunay triangulations only +use the lower half of a convex hull). It lists the number of +coplanar points. For Delaunay triangulations without 'Qc', it lists the total number of +coplanar points. It lists the number of simplicial facets in +the output.

    + +

    The terminology depends on the output structure.

    + +

    The summary lists these statistics:

    + +
      +
    • number of points processed by Qhull
    • +
    • number of hyperplanes created
    • +
    • number of distance tests (not counting statistics, + summary, and checking)
    • +
    • number of merged facets (if any)
    • +
    • number of distance tests for merging (if any)
    • +
    • CPU seconds to compute the hull
    • +
    • the maximum joggle for 'QJ'
      + or, the probability of precision errors for 'QJ TRn' +
    • +
    • total area and volume (if computed, see 'FS' 'FA' + 'Fa' 'PAn')
    • +
    • max. distance of a point above a facet (if non-zero)
    • +
    • max. distance of a vertex below a facet (if non-zero)
    • +
    + +

    The statistics include intermediate hulls. For example 'rbox d +D4 | qhull' reports merged facets even though the final hull is +simplicial.

    + +

    Qhull starts counting CPU seconds after it has read and +projected the input points. It stops counting before producing +output. In the code, CPU seconds measures the execution time of +function qhull() in libqhull.c. If the number of CPU +seconds is clearly wrong, check qh_SECticks in user.h.

    + +

    The last two figures measure the maximum distance from a point +or vertex to a facet. They are not printed if less than roundoff +or if not merging. They account for roundoff error in computing +the distance (c.f., option 'Rn'). +Use 'Fs' to report the maximum outer +and inner plane.

    + +

    A number may appear in parentheses after the maximum distance +(e.g., 2.1x). It is the ratio between the maximum distance and +the worst-case distance due to merging two simplicial facets. It +should be small for 2-d, 3-d, and 4-d, and for higher dimensions +with 'Qx'. It is not printed if less +than 0.05.

    + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optp.htm b/xs/src/qhull/html/qh-optp.htm new file mode 100644 index 0000000000..9c6df90f5a --- /dev/null +++ b/xs/src/qhull/html/qh-optp.htm @@ -0,0 +1,253 @@ + + + + +Qhull print options (P) + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull print options (P)

    + +This section lists the print options for Qhull. These options are +indicated by 'P' followed by a letter. See +Output, Geomview, +and Format for other output options. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Print options

    +
    +
    +
     
    +
    General
    +
    Pp
    +
    do not report precision problems
    +
    Po
    +
    force output despite precision problems
    +
    Po
    +
    if error, output neighborhood of facet
    +
     
    +
     
    +
    Select
    +
    Pdk:n
    +
    print facets with normal[k] >= n (default 0.0)
    +
    PDk:n
    +
    print facets with normal[k] <= n
    +
    PFn
    +
    print facets whose area is at least n
    +
    Pg
    +
    print good facets only (needs 'QGn' + or 'QVn ')
    +
    PMn
    +
    print n facets with most merges
    +
    PAn
    +
    print n largest facets by area
    +
    PG
    +
    print neighbors of good facets
    +
    +
    +
    + +

    »PAn - keep n largest +facets by area

    + +

    The n largest facets are marked good for printing. This +may be useful for approximating +a hull. Unless 'PG' is set, 'Pg' +is automatically set.

    + +

    »Pdk:n - print facet if +normal[k] >= n

    + +

    For a given output, print only those facets with normal[k] >= n +and drop the others. For example, 'Pd0:0.5' prints facets with normal[0] +>= 0.5 . The default value of n is zero. For +example in 3-d, 'Pd0d1d2' prints facets in the positive octant. +

    +If no facets match, the closest facet is returned.

    +

    +On Windows 95, do not combine multiple options. A 'd' is considered +part of a number. For example, use 'Pd0:0.5 Pd1:0.5' instead of +'Pd0:0.5d1:0.5'. + +

    »PDk:n - print facet if +normal[k] <= n

    + +

    For a given output, print only those facets with normal[k] <= n +and drop the others. +For example, 'PD0:0.5' prints facets with normal[0] +<= 0.5 . The default value of n is zero. For +example in 3-d, 'PD0D1D2' displays facets in the negative octant. +

    +If no facets match, the closest facet is returned.

    + +

    In 2-d, 'd G PD2' displays the Delaunay triangulation instead +of the corresponding paraboloid.

    + +

    Be careful of placing 'Dk' or 'dk' immediately after a real +number. Some compilers treat the 'D' as a double precision +exponent.

    + +

    »PFn - keep facets whose +area is at least n

    + +

    The facets with area at least n are marked good for +printing. This may be useful for approximating a hull. Unless +'PG' is set, 'Pg' is +automatically set.

    + +

    »Pg - print good facets

    + +

    Qhull can mark facets as "good". This is used to

    + +
      +
    • mark the lower convex hull for Delaunay triangulations + and Voronoi diagrams
    • +
    • mark the facets that are visible from a point (the 'QGn ' option)
    • +
    • mark the facets that contain a point (the 'QVn' option).
    • +
    • indicate facets with a large enough area (options 'PAn' and 'PFn')
    • +
    + +

    Option 'Pg' only prints good facets that +also meet 'Pdk' and 'PDk' +options. It is automatically set for options 'PAn', +'PFn ', 'QGn', +and 'QVn'.

    + +

    »PG - print neighbors of +good facets

    + +

    Option 'PG' can be used with or without option 'Pg' +to print the neighbors of good facets. For example, options 'QGn' and 'QVn' +print the horizon facets for point n.

    + +

    »PMn - keep n facets with +most merges

    + +

    The n facets with the most merges are marked good for +printing. This may be useful for approximating a hull. Unless +'PG' is set, 'Pg' is +automatically set.

    + +

    Use option 'Fm' to print merges +per facet. + +

    »Po - force output despite +precision problems

    + +

    Use options 'Po' and 'Q0' if you +can not merge facets, triangulate the output ('Qt'), +or joggle the input (QJ). + +

    Option 'Po' can not force output when +duplicate ridges or duplicate facets occur. It may produce +erroneous results. For these reasons, merged facets, joggled input, or exact arithmetic are better.

    + +

    If you need a simplicial Delaunay triangulation, use +joggled input 'QJ' or triangulated +output 'Ft'. + +

    Option 'Po' may be used without 'Q0' +to remove some steps from Qhull or to output the neighborhood of +an error.

    + +

    Option 'Po' may be used with option 'Q5') +to skip qh_check_maxout (i.e., do not determine the maximum outside distance). +This can save a significant amount of time. + +

    If option 'Po' is used,

    + +
      +
    • most precision errors allow Qhull to continue.
    • +
    • verify ('Tv') does not check + coplanar points.
    • +
    • points are not partitioned into flipped facets and a + flipped facet is always visible to a point. This may + delete flipped facets from the output.
    • +
    + +

    »Po - if error, output +neighborhood of facet

    + +

    If an error occurs before the completion of Qhull and tracing +is not active, 'Po' outputs a neighborhood of the erroneous +facets (if any). It uses the current output options.

    + +

    See 'Po' - force output despite +precision problems. + +

    »Pp - do not report +precision problems

    + +

    With option 'Pp', Qhull does not print statistics about +precision problems, and it removes some of the warnings. It +removes the narrow hull warning.

    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optq.htm b/xs/src/qhull/html/qh-optq.htm new file mode 100644 index 0000000000..2edbb1fd43 --- /dev/null +++ b/xs/src/qhull/html/qh-optq.htm @@ -0,0 +1,731 @@ + + + + +Qhull control options (Q) + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull control options (Q)

    + +

    This section lists the control options for Qhull. These +options are indicated by 'Q' followed by a letter.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Qhull control options

    + +
    +
     
    +
    General
    +
    Qu
    +
    compute upper hull for furthest-site Delaunay + triangulation
    +
    Qc
    +
    keep coplanar points with nearest facet
    +
    Qi
    +
    keep interior points with nearest facet
    +
    QJ
    +
    joggled input to avoid precision problems
    +
    Qt
    +
    triangulated output
    +
     
    +
     
    +
    Precision handling
    +
    Qz
    +
    add a point-at-infinity for Delaunay triangulations
    +
    Qx
    +
    exact pre-merges (allows coplanar facets)
    +
    Qs
    +
    search all points for the initial simplex
    +
    Qbb
    +
    scale last coordinate to [0,m] for Delaunay
    +
    Qv
    +
    test vertex neighbors for convexity
    +
     
    +
     
    +
    Transform input
    +
    Qbk:0Bk:0
    +
    drop dimension k from input
    +
    QRn
    +
    random rotation (n=seed, n=0 time, n=-1 time/no rotate)
    +
    Qbk:n
    +
    scale coord[k] to low bound of n (default -0.5)
    +
    QBk:n
    +
    scale coord[k] to upper bound of n (default 0.5)
    +
    QbB
    +
    scale input to fit the unit cube
    +
     
    +
     
    +
    Select facets
    +
    QVn
    +
    good facet if it includes point n, -n if not
    +
    QGn
    +
    good facet if visible from point n, -n for not visible
    +
    Qg
    +
    only build good facets (needs 'QGn', 'QVn ', or 'Pdk')
    +
     
    +
     
    +
    Experimental
    +
    Q4
    +
    avoid merging old facets into new facets
    +
    Q5
    +
    do not correct outer planes at end of qhull
    +
    Q3
    +
    do not merge redundant vertices
    +
    Q6
    +
    do not pre-merge concave or coplanar facets
    +
    Q0
    +
    do not pre-merge facets with 'C-0' or 'Qx'
    +
    Q8
    +
    ignore near-interior points
    +
    Q2
    +
    merge all non-convex at once instead of independent sets
    +
    Qf
    +
    partition point to furthest outside facet
    +
    Q7
    +
    process facets depth-first instead of breadth-first
    +
    Q9
    +
    process furthest of furthest points
    +
    Q10
    +
    no special processing for narrow distributions
    +
    Q11
    +
    copy normals and recompute centrums for tricoplanar facets
    +
    Q12
    +
    do not error on wide merge due to duplicate ridge and nearly coincident points
    +
    Qm
    +
    process points only if they would increase the max. outer + plane
    +
    Qr
    +
    process random outside points instead of furthest one
    +
    Q1
    +
    sort merges by type instead of angle
    +
    + +
    + +

    »Qbb - scale the last +coordinate to [0,m] for Delaunay

    + +

    After scaling with option 'Qbb', the lower bound of the last +coordinate will be 0 and the upper bound will be the maximum +width of the other coordinates. Scaling happens after projecting +the points to a paraboloid and scaling other coordinates.

    + +

    Option 'Qbb' is automatically set for qdelaunay +and qvoronoi. Option 'Qbb' is automatically set for joggled input 'QJ'.

    + +

    Option 'Qbb' should be used for Delaunay triangulations with +integer coordinates. Since the last coordinate is the sum of +squares, it may be much larger than the other coordinates. For +example, rbox 10000 D2 B1e8 | qhull d has precision +problems while rbox 10000 D2 B1e8 | qhull d Qbb is OK.

    + +

    »QbB - scale the input to +fit the unit cube

    + +

    After scaling with option 'QbB', the lower bound will be -0.5 +and the upper bound +0.5 in all dimensions. For different bounds +change qh_DEFAULTbox in user.h (0.5 is best for Geomview).

    + +

    For Delaunay and Voronoi diagrams, scaling happens after +projection to the paraboloid. Under precise arithmetic, scaling +does not change the topology of the convex hull. Scaling may +reduce precision errors if coordinate values vary widely.

    + +

    »Qbk:n - scale coord[k] +to low bound

    + +

    After scaling, the lower bound for dimension k of the input +points will be n. 'Qbk' scales coord[k] to -0.5.

    + +

    »QBk:n - scale coord[k] +to upper bound

    + +

    After scaling, the upper bound for dimension k of the input +points will be n. 'QBk' scales coord[k] to 0.5.

    + +

    »Qbk:0Bk:0 - drop +dimension k from the input points

    + +

    Drop dimension k from the input points. For example, +'Qb1:0B1:0' deletes the y-coordinate from all input points. This +allows the user to take convex hulls of sub-dimensional objects. +It happens before the Delaunay and Voronoi transformation. +It happens after the halfspace transformation for both the data +and the feasible point.

    + +

    »Qc - keep coplanar points +with nearest facet

    + +

    During construction of the hull, a point is coplanar if it is +between 'Wn' above and 'Un' below a facet's hyperplane. A +different definition is used for output from Qhull.

    + +

    For output, a coplanar point is above the minimum vertex +(i.e., above the inner plane). With joggle ('QJ'), a coplanar point includes points +within one joggle of the inner plane.

    + +

    With option 'Qc', output formats 'p ', +'f', 'Gp', +'Fc', 'FN', +and 'FP' will print the coplanar +points. With options 'Qc Qi' these outputs +include the interior points.

    + +

    For Delaunay triangulations (qdelaunay +or qvoronoi), a coplanar point is a point +that is nearly incident to a vertex. All input points are either +vertices of the triangulation or coplanar.

    + +

    Qhull stores coplanar points with a facet. While constructing +the hull, it retains all points within qh_RATIOnearInside +(user.h) of a facet. In qh_check_maxout(), it uses these points +to determine the outer plane for each facet. With option 'Qc', +qh_check_maxout() retains points above the minimum vertex for the +hull. Other points are removed. If qh_RATIOnearInside is wrong or +if options 'Q5 Q8' are set, a +coplanar point may be missed in the output (see Qhull limitations).

    + +

    »Qf - partition point to +furthest outside facet

    + +

    After adding a new point to the convex hull, Qhull partitions +the outside points and coplanar points of the old, visible +facets. Without the 'f ' option and +merging, it assigns a point to the first facet that it is outside +('Wn'). When merging, it assigns a +point to the first facet that is more than several times outside +(see qh_DISToutside in user.h).

    + +

    If option 'Qf' is selected, Qhull performs a directed search +(no merging) or an exhaustive search (merging) of new facets. +Option 'Qf' may reduce precision errors if pre-merging does not +occur.

    + +

    Option 'Q9' processes the furthest of all +furthest points.

    + +

    »Qg - only build good +facets (needs 'QGn' 'QVn' or 'Pdk')

    + +

    Qhull has several options for defining and printing good +facets. With the 'Qg' option, Qhull will only +build those facets that it needs to determine the good facets in +the output. This may speed up Qhull in 2-d and 3-d. It is +useful for furthest-site Delaunay +triangulations (qdelaunay Qu, +invoke with 'qhull d Qbb Qu Qg'). +It is not effective in higher +dimensions because many facets see a given point and contain a +given vertex. It is not guaranteed to work for all combinations.

    + +

    See 'QGn', 'QVn', and 'Pdk' for defining good facets, and 'Pg' and 'PG' +for printing good facets and their neighbors. If pre-merging ('C-n') is not used and there are +coplanar facets, then 'Qg Pg' may produce a different result than +'Pg'.

    + +

    »QGn - good facet if +visible from point n, -n for not visible

    + +

    With option 'QGn', a facet is good (see 'Qg' +and 'Pg') if it is visible from +point n. If n < 0, a facet is good if it is not visible +from point n. Point n is not added to the hull (unless 'TCn' or 'TPn').

    + +

    With rbox, use the 'Pn,m,r' option +to define your point; it will be point 0 ('QG0').

    + +

    »Qi - keep interior points +with nearest facet

    + +

    Normally Qhull ignores points that are clearly interior to the +convex hull. With option 'Qi', Qhull treats interior points the +same as coplanar points. Option 'Qi' does not retain coplanar +points. You will probably want 'Qc ' as well.

    + +

    Option 'Qi' is automatically set for 'qdelaunay +Qc' and 'qvoronoi +Qc'. If you use +'qdelaunay Qi' or 'qvoronoi +Qi', option 's' reports all nearly +incident points while option 'Fs' +reports the number of interior points (should always be zero).

    + +

    With option 'Qi', output formats 'p', +'f','Gp', +'Fc', 'FN', +and 'FP' include interior points.

    + +

    »QJ or QJn - joggled +input to avoid precision errors

    + +

    Option 'QJ' or 'QJn' joggles each input coordinate by adding a +random number in the range [-n,n]. If a precision error occurs, +It tries again. If precision errors still occur, Qhull increases n +ten-fold and tries again. The maximum value for increasing n +is 0.01 times the maximum width of the input. Option 'QJ' selects +a default value for n. User.h +defines these parameters and a maximum number of retries. See Merged facets or joggled input.

    + +

    Users of joggled input should consider converting to +triangulated output ('Qt'). Triangulated output is +approximately 1000 times more accurate than joggled input. + +

    Option 'QJ' also sets 'Qbb' for +Delaunay triangulations and Voronoi diagrams. It does not set +'Qbb' if 'Qbk:n' or 'QBk:n' are set.

    + +

    If 'QJn' is set, Qhull does not merge facets unless requested +to. All facets are simplicial (triangular in 2-d). This may be +important for your application. You may also use triangulated output +('Qt') or Option 'Ft'. + +

    Qhull adjusts the outer and inner planes for 'QJn' ('Fs'). They are increased by sqrt(d)*n +to account for the maximum distance between a joggled point and +the corresponding input point. Coplanar points ('Qc') require an additional sqrt(d)*n +since vertices and coplanar points may be joggled in opposite +directions.

    + +

    For Delaunay triangulations (qdelaunay), joggle +happens before lifting the input sites to a paraboloid. Instead of +'QJ', you may use triangulated output ('Qt')

    + +

    This option is deprecated for Voronoi diagrams (qvoronoi). +It triangulates cospherical points, leading to duplicated Voronoi vertices.

    + +

    By default, 'QJn' uses a fixed random number seed. To use time +as the random number seed, select 'QR-1'. +The summary ('s') will show the +selected seed as 'QR-n'. + +

    With 'QJn', Qhull does not error on degenerate hyperplane +computations. Except for Delaunay and Voronoi computations, Qhull +does not error on coplanar points.

    + +

    Use option 'FO' to display the +selected options. Option 'FO' displays the joggle and the joggle +seed. If Qhull restarts, it will redisplay the options.

    + +

    Use option 'TRn' to estimate the +probability that Qhull will fail for a given 'QJn'. + +

    »Qm - only process points +that increase the maximum outer plane

    + +

    Qhull reports the maximum outer plane in its summary ('s'). With option 'Qm', Qhull does not +process points that are below the current, maximum outer plane. +This is equivalent to always adjusting 'Wn +' to the maximum distance of a coplanar point to a facet. It +is ignored for points above the upper convex hull of a Delaunay +triangulation. Option 'Qm' is no longer important for merging.

    + +

    »Qr - process random +outside points instead of furthest ones

    + +

    Normally, Qhull processes the furthest point of a facet's +outside points. Option 'Qr' instead selects a random outside +point for processing. This makes Qhull equivalent to the +randomized incremental algorithms.

    + +

    The original randomization algorithm by Clarkson and Shor ['89] used a conflict list which +is equivalent to Qhull's outside set. Later randomized algorithms +retained the previously constructed facets.

    + +

    To compare Qhull to the randomized algorithms with option +'Qr', compare "hyperplanes constructed" and +"distance tests". Qhull does not report CPU time +because the randomization is inefficient.

    + +

    »QRn - random rotation

    + +

    Option 'QRn' randomly rotates the input. For Delaunay +triangulations (qdelaunay or qvoronoi), +it rotates the lifted points about +the last axis.

    + +

    If n=0, use time as the random number seed. If n>0, +use n as the random number seed. If n=-1, don't rotate +but use time as the random number seed. If n<-1, +don't rotate but use n as the random number seed.

    + +

    »Qs - search all points +for the initial simplex

    + +

    Qhull constructs an initial simplex from d+1 points. It +selects points with the maximum and minimum coordinates and +non-zero determinants. If this fails, it searches all other +points. In 8-d and higher, Qhull selects points with the minimum +x or maximum coordinate (see qh_initialvertices in poly2.c ). +It rejects points with nearly zero determinants. This should work +for almost all input sets.

    + +

    If Qhull can not construct an initial simplex, it reports a +descriptive message. Usually, the point set is degenerate and one +or more dimensions should be removed ('Qbk:0Bk:0'). +If not, use option 'Qs'. It performs an exhaustive search for the +best initial simplex. This is expensive is high dimensions.

    + +

    »Qt - triangulated output

    + +

    By default, qhull merges facets to handle precision errors. This +produces non-simplicial facets (e.g., the convex hull of a cube has 6 square +facets). Each facet is non-simplicial because it has four vertices. + +

    Use option 'Qt' to triangulate all non-simplicial facets before generating +results. Alternatively, use joggled input ('QJ') to +prevent non-simplical facets. Unless 'Pp' is set, +qhull produces a warning if 'QJ' and 'Qt' are used together. + +

    For Delaunay triangulations (qdelaunay), triangulation +occurs after lifting the input sites to a paraboloid and computing the convex hull. +

    + +

    Option 'Qt' is deprecated for Voronoi diagrams (qvoronoi). +It triangulates cospherical points, leading to duplicated Voronoi vertices.

    + +

    Option 'Qt' may produce degenerate facets with zero area.

    + +

    Facet area and hull volumes may differ with and without +'Qt'. The triangulations are different and different triangles +may be ignored due to precision errors. + +

    With sufficient merging, the ridges of a non-simplicial facet may share more than two neighboring facets. If so, their triangulation ('Qt') will fail since two facets have the same vertex set.

    + +

    »Qu - compute upper hull +for furthest-site Delaunay triangulation

    + +

    When computing a Delaunay triangulation (qdelaunay +or qvoronoi), +Qhull computes both the the convex hull of points on a +paraboloid. It normally prints facets of the lower hull. These +correspond to the Delaunay triangulation. With option 'Qu', Qhull +prints facets of the upper hull. These correspond to the furthest-site Delaunay triangulation +and the furthest-site Voronoi diagram.

    + +

    Option 'qhull d Qbb Qu Qg' may improve the speed of option +'Qu'. If you use the Qhull library, a faster method is 1) use +Qhull to compute the convex hull of the input sites; 2) take the +extreme points (vertices) of the convex hull; 3) add one interior +point (e.g., +'FV', the average of d extreme points); 4) run +'qhull d Qbb Qu' or 'qhull v Qbb Qu' on these points.

    + +

    »Qv - test vertex +neighbors for convexity

    + +

    Normally, Qhull tests all facet neighbors for convexity. +Non-neighboring facets which share a vertex may not satisfy the +convexity constraint. This occurs when a facet undercuts the +centrum of another facet. They should still be convex. Option +'Qv' extends Qhull's convexity testing to all neighboring facets +of each vertex. The extra testing occurs after the hull is +constructed..

    + +

    »QVn - good facet if it +includes point n, -n if not

    + +

    With option 'QVn', a facet is good ('Qg', +'Pg') if one of its vertices is +point n. If n<0, a good facet does not include point n. + +

    If options 'PG' +and 'Qg' are not set, option 'Pg' +(print only good) +is automatically set. +

    + +

    Option 'QVn' behaves oddly with options 'Fx' +and 'qvoronoi Fv'. + +

    If used with option 'Qg' (only process good facets), point n is +either in the initial simplex or it is the first +point added to the hull. Options 'QVn Qg' require either 'QJ' or +'Q0' (no merging).

    + +

    »Qx - exact pre-merges +(allows coplanar facets)

    + +

    Option 'Qx' performs exact merges while building the hull. +Option 'Qx' is set by default in 5-d and higher. Use option 'Q0' to not use 'Qx' by default. Unless otherwise +specified, option 'Qx' sets option 'C-0'. +

    + +

    The "exact" merges are merging a point into a +coplanar facet (defined by 'Vn ', 'Un', and 'C-n'), +merging concave facets, merging duplicate ridges, and merging +flipped facets. Coplanar merges and angle coplanar merges ('A-n') are not performed. Concavity +testing is delayed until a merge occurs.

    + +

    After the hull is built, all coplanar merges are performed +(defined by 'C-n' and 'A-n'), then post-merges are performed +(defined by 'Cn' and 'An'). If facet progress is logged ('TFn'), Qhull reports each phase and +prints intermediate summaries and statistics ('Ts').

    + +

    Without 'Qx' in 5-d and higher, options 'C-n' and 'A-n' +may merge too many facets. Since redundant vertices are not +removed effectively, facets become increasingly wide.

    + +

    Option 'Qx' may report a wide facet. With 'Qx', coplanar +facets are not merged. This can produce a "dent" in an +intermediate hull. If a point is partitioned into a dent and it +is below the surrounding facets but above other facets, one or +more wide facets will occur. In practice, this is unlikely. To +observe this effect, run Qhull with option 'Q6' +which doesn't pre-merge concave facets. A concave facet makes a +large dent in the intermediate hull.

    + +

    Option 'Qx' may set an outer plane below one of the input +points. A coplanar point may be assigned to the wrong facet +because of a "dent" in an intermediate hull. After +constructing the hull, Qhull double checks all outer planes with +qh_check_maxout in poly2.c . If a coplanar point is +assigned to the wrong facet, qh_check_maxout may reach a local +maximum instead of locating all coplanar facets. This appears to +be unlikely.

    + +

    »Qz - add a +point-at-infinity for Delaunay triangulations

    + +

    Option 'Qz' adds a point above the paraboloid of lifted sites +for a Delaunay triangulation. It allows the Delaunay +triangulation of cospherical sites. It reduces precision errors +for nearly cospherical sites.

    + +

    »Q0 - no merging with C-0 +and Qx

    + +

    Turn off default merge options 'C-0' +and 'Qx'.

    + +

    With 'Q0' and without other pre-merge options, Qhull ignores +precision issues while constructing the convex hull. This may +lead to precision errors. If so, a descriptive warning is +generated. See Precision issues.

    + +

    »Q1 - sort merges by type +instead of angle

    + +

    Qhull sorts the coplanar facets before picking a subset of the +facets to merge. It merges concave and flipped facets first. Then +it merges facets that meet at a steep angle. With 'Q1', Qhull +sorts merges by type (coplanar, angle coplanar, concave) instead +of by angle. This may make the facets wider.

    + +

    »Q2 - merge all non-convex +at once instead of independent sets

    + +

    With 'Q2', Qhull merges all facets at once instead of +performing merges in independent sets. This may make the facets +wider.

    + +

    »Q3 - do not merge +redundant vertices

    + +

    With 'Q3', Qhull does not remove redundant vertices. In 6-d +and higher, Qhull never removes redundant vertices (since +vertices are highly interconnected). Option 'Q3' may be faster, +but it may result in wider facets. Its effect is easiest to see +in 3-d and 4-d.

    + +

    »Q4 - avoid merging old +facets into new facets

    + +

    With 'Q4', Qhull avoids merges of an old facet into a new +facet. This sometimes improves facet width and sometimes makes it +worse.

    + +

    »Q5 - do not correct outer +planes at end of qhull

    + +

    When merging facets or approximating a hull, Qhull tests +coplanar points and outer planes after constructing the hull. It +does this by performing a directed search (qh_findbest in geom.c). +It includes points that are just inside the hull.

    + +

    With options 'Q5' or 'Po', Qhull +does not test outer planes. The maximum outer plane is used +instead. Coplanar points ('Qc') are defined by +'Un'. An input point may be outside +of the maximum outer plane (this appears to be unlikely). An +interior point may be above 'Un' +from a hyperplane.

    + +

    Option 'Q5' may be used if outer planes are not needed. Outer +planes are needed for options 's', 'G', 'Go ', +'Fs', 'Fo', +'FF', and 'f'.

    + +

    »Q6 - do not pre-merge +concave or coplanar facets

    + +

    With 'Q6', Qhull does not pre-merge concave or coplanar +facets. This demonstrates the effect of "dents" when +using 'Qx'.

    + +

    »Q7 - depth-first +processing instead of breadth-first

    + +

    With 'Q7', Qhull processes facets in depth-first order instead +of breadth-first order. This may increase the locality of +reference in low dimensions. If so, Qhull may be able to use +virtual memory effectively.

    + +

    In 5-d and higher, many facets are visible from each +unprocessed point. So each iteration may access a large +proportion of allocated memory. This makes virtual memory +ineffectual. Once real memory is used up, Qhull will spend most +of its time waiting for I/O.

    + +

    Under 'Q7', Qhull runs slower and the facets may be wider.

    + +

    »Q8 - ignore near-interior +points

    + +

    With 'Q8' and merging, Qhull does not process interior points +that are near to a facet (as defined by qh_RATIOnearInside in +user.h). This avoids partitioning steps. It may miss a coplanar +point when adjusting outer hulls in qh_check_maxout(). The best +value for qh_RATIOnearInside is not known. Options 'Q8 Qc' may be sufficient.

    + +

    »Q9 - process furthest of +furthest points

    + +

    With 'Q9', Qhull processes the furthest point of all outside +sets. This may reduce precision problems. The furthest point of +all outside sets is not necessarily the furthest point from the +convex hull.

    + +

    »Q10 - no special processing +for narrow distributions

    + +

    With 'Q10', Qhull does not special-case narrow distributions. +See Limitations of merged facets for +more information. + +

    »Q11 - copy normals and recompute +centrums for +tricoplanar facets

    + +Option 'Qt' triangulates non-simplicial facets +into "tricoplanar" facets. +Normally tricoplanar facets share the same normal, centrum, and +Voronoi vertex. They can not be merged or replaced. With +option 'Q11', Qhull duplicates the normal and Voronoi vertex. +It recomputes the centrum. + +

    Use 'Q11' if you use the Qhull library to add points +incrementally and call qh_triangulate() after each point. +Otherwise, Qhull will report an error when it tries to +merge and replace a tricoplanar facet. + +

    With sufficient merging and new points, option 'Q11' may +lead to precision problems such +as duplicate ridges and concave facets. For example, if qh_triangulate() +is added to qh_addpoint(), RBOX 1000 s W1e-12 t1001813667 P0 | QHULL d Q11 Tv, +reports an error due to a duplicate ridge. + +

    »Q12 - do not error +on wide merge due to duplicate ridge and nearly coincident points

    + +

    In 3-d and higher Delaunay Triangulations or 4-d and higher convex hulls, multiple, +nearly coincident points may lead to very wide facets. An error is reported if a +merge across a duplicate ridge would increase the facet width by 100x or more. + +

    Use option 'Q12' to log a warning instead of throwing an error. + +

    For Delaunay triangulations, a bounding box may alleviate this error (e.g., rbox 500 C1,1E-13 t c G1 | qhull d). +This avoids the ill-defined edge between upper and lower convex hulls. +The problem will be fixed in a future release of Qhull. + +

    To demonstrate the problem, use rbox option 'Cn,r,m' to generate nearly coincident points. +For more information, see "Nearly coincident points on an edge" +in Nearly coincident points on an edge. + + +


    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optt.htm b/xs/src/qhull/html/qh-optt.htm new file mode 100644 index 0000000000..0709f58c66 --- /dev/null +++ b/xs/src/qhull/html/qh-optt.htm @@ -0,0 +1,278 @@ + + + + +Qhull trace options (T) + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull trace options (T)

    + +This section lists the trace options for Qhull. These options are +indicated by 'T' followed by a letter. + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Trace options

    + +
    +
     
    +
    General
    +
    Tz
    +
    output error information to stdout instead of stderr
    +
    TI file
    +
    input data from a file
    +
    TO file
    +
    output results to a file
    +
    Ts
    +
    print statistics
    +
    TFn
    +
    report progress whenever n or more facets created
    +
    TRn
    +
    rerun qhull n times
    +
    Tv
    +
    verify result: structure, convexity, and point inclusion
    + +
     
    +
     
    +
    Debugging
    +
    Tc
    +
    check frequently during execution
    +
    TVn
    +
    stop qhull after adding point n
    +
    TCn
    +
    stop qhull after building cone for point n
    +
    TV-n
    +
    stop qhull before adding point n
    +
    T4
    +
    trace at level n, 4=all, 5=mem/gauss, -1= events
    +
    TWn
    +
    trace merge facets when width > n
    +
    TMn
    +
    turn on tracing at merge n
    +
    TPn
    +
    turn on tracing when point n added to hull
    +
    + +
    + +

    »Tc - check frequently +during execution

    + +

    Qhull includes frequent checks of its data structures. Option +'Tc' will catch most inconsistency errors. It is slow and should +not be used for production runs. Option 'Tv' +performs the same checks after the hull is constructed.

    + +

    »TCn - stop qhull after +building cone for point n

    + +

    Qhull builds a cone from the point to its horizon facets. +Option 'TCn' stops Qhull just after building the cone. The output +for 'f' includes the cone and the old +hull.'.

    + +

    »TFn - report summary +whenever n or more facets created

    + +

    Option 'TFn' reports progress whenever more than n facets are +created. The test occurs just before adding a new point to the +hull. During post-merging, 'TFn' reports progress after more than +n/2 merges.

    + +

    »TI file - input data from file

    + +

    Input data from 'file' instead of stdin. The filename may not +contain spaces or use single quotes. +You may use I/O redirection +instead (e.g., 'rbox 10 | qdelaunay >results').

    + +

    »TMn - turn on tracing at +merge n

    + +

    Turn on tracing at n'th merge.

    + +

    »Tn - trace at level n

    + +

    Qhull includes full execution tracing. 'T-1' traces events. +'T1' traces the overall execution of the program. 'T2' and 'T3' +trace overall execution and geometric and topological events. +'T4' traces the algorithm. 'T5' includes information about memory +allocation and Gaussian elimination. 'T1' is useful for logging +progress of Qhull in high dimensions.

    + +

    Option 'Tn' can produce large amounts of output. Use options 'TPn', 'TWn', and 'TMn' to selectively +turn on tracing. Since all errors report the last processed +point, option 'TPn' is particularly useful.

    + +

    Different executions of the same program may produce different +traces and different results. The reason is that Qhull uses hashing +to match ridges of non-simplicial facets. For performance reasons, +the hash computation uses +memory addresses which may change across executions. + +

    »TO file - output results to file

    + +

    Redirect stdout to 'file'. The filename may be enclosed in +single quotes. Unix and Windows NT users may use I/O redirection +instead (e.g., 'rbox 10 | qdelaunay >results').

    +

    +Windows95 users should always use 'TO file'. If they use I/O redirection, +error output is not sent to the console. Qhull uses single quotes instead +of double quotes because a missing double quote can +freeze Windows95 (e.g., do not run, rbox 10 | qhull TO "x)

    +

    + +

    »TPn - turn on tracing +when point n added to hull

    + +

    Option 'TPn' turns on tracing when point n is added to +the hull. It also traces partitions of point n. This option +reduces the output size when tracing. It is the normal +method to determine the cause of a Qhull error. All Qhull errors +report the last point added. + +

    Use options 'TPn TVn' to +trace the addition of point n to the convex hull and stop when done.

    + +

    If used with option 'TWn', +'TPn' turns off tracing after adding point n to the hull. +Use options 'TPn TWn' to +trace the addition of point n to the convex hull, partitions +of point n, and wide merges.

    + +

    »TRn - rerun qhull n times

    + +

    Option 'TRn' reruns Qhull n times. It is usually used +with 'QJn' to determine the probability +that a given joggle will fail. The summary +('s') lists the failure +rate and the precision errors that occurred. +Option 'Ts' will report statistics for +all of the runs. Trace and output options only apply to the last +run. An event trace, 'T-1' reports events for all runs. + +

    Tracing applies to the last run of Qhull. If an error +is reported, the options list the run number as "_run". +To trace this run, set 'TRn' to the same value.

    + +

    »Ts - print statistics

    + +

    Option 'Ts' collects statistics and prints them to stderr. For +Delaunay triangulations, the angle statistics are restricted to +the lower or upper envelope.

    + +

    »Tv - verify result: +structure, convexity, and point inclusion

    + +

    Option 'Tv' checks the topological structure, convexity, and +point inclusion. If precision problems occurred, facet convexity +is tested whether or not 'Tv' is selected. Option 'Tv' does not +check point inclusion if forcing output with 'Po', or if 'Q5' +is set.

    + +

    The convex hull of a set of points is the smallest polytope +that includes the points. Option 'Tv' tests point inclusion. +Qhull verifies that all points are below all outer planes +(facet->maxoutside). Point inclusion is exhaustive if merging +or if the facet-point product is small enough; otherwise Qhull +verifies each point with a directed search (qh_findbest). To +force an exhaustive test when using option 'C-0' (default), use 'C-1e-30' instead.

    + +

    Point inclusion testing occurs after producing output. It +prints a message to stderr unless option 'Pp' is used. This allows the user to +interrupt Qhull without changing the output.

    + +

    With 'qvoronoi Fi' +and 'qvoronoi Fo', +option 'Tv' collects statistics that verify all Voronoi vertices lie +on the separating hyperplane, and for bounded regions, all +separating hyperplanes are perpendicular bisectors. + +

    »TV-n - stop qhull before +adding point n

    + +

    Qhull adds one point at a time to the convex hull. See how Qhull adds a point. Option 'TV-n' +stops Qhull just before adding a new point. Output shows the hull +at this time.

    + +

    »TVn - stop qhull after +adding point n

    + +

    Option 'TVn' stops Qhull after it has added point n. Output +shows the hull at this time.

    + +

    »TWn - trace merge facets +when width > n

    + +

    Along with TMn, this option allows the user to determine the +cause of a wide merge.

    +

    »Tz - send all output to +stdout

    + +

    Redirect stderr to stdout.

    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-quick.htm b/xs/src/qhull/html/qh-quick.htm new file mode 100644 index 0000000000..9d52e7d750 --- /dev/null +++ b/xs/src/qhull/html/qh-quick.htm @@ -0,0 +1,495 @@ + + + + +Qhull quick reference + + + + +

    Up: Home +page for Qhull
    +Up: Qhull manual
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull internals
    +To: Qhull functions, macros, and data structures
    +To: Qhull files
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + +

    [cone] Qhull quick reference

    + +This section lists all programs and options in Qhull. + +

    Copyright © 1995-2015 C.B. Barber

    + +

    +  +


    +Qhull programs +

    » Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    +
    qconvex -- convex hull
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    qdelaunay -- Delaunay triangulation
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    qdelaunay Qu -- furthest-site Delaunay triangulation
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    qhalf -- halfspace intersection about a point
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    qvoronoi -- Voronoi diagram
    +
    synopsis • input • outputs • + controls • graphics • notes • conventions • options
    +
     
    +
    qvoronoi Qu -- furthest-site Voronoi diagram
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    rbox -- generate point distributions for qhull
    +
    synopsis • outputs • examples • notes • options
    +
     
    +
    qhull -- convex hull and related structures
    +
    synopsis • input • outputs • controls • options
    +
    +  +
    +Qhull options + +

    » Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    'Fa' +Farea +'FA' +FArea-total +'Fc' +Fcoplanars +'FC' +FCentrums + +
    'Fd' +Fd-cdd-in +'FD' +FD-cdd-out +'FF' +FF-dump-xridge +'Fi' +Finner + +
    'Fi' +Finner_bounded +'FI' +FIDs +'Fm' +Fmerges +'FM' +FMaple + +
    'Fn' +Fneighbors +'FN' +FNeigh-vertex +'Fo' +Fouter +'Fo' +Fouter_unbounded + +
    'FO' +FOptions +'Fp' +Fpoint-intersect +'FP' +FPoint_near +'FQ' +FQhull + +
    'Fs' +Fsummary +'FS' +FSize +'Ft' +Ftriangles +'Fv' +Fvertices + +
    'Fv' +Fvoronoi +'FV' +FVertex-ave +'Fx' +Fxtremes + +Merged facets or joggled input + +
     
    'PAn' +PArea-keep +'Pdk:n' +Pdrop_low +'PDk:n' +Pdrop_high +'Pg' +Pgood + +
    'PFn' +PFacet_area_keep +'PG' +PGood_neighbors +'PMn' +PMerge-keep +'Po' +Poutput_forced + +
    'Po' +Poutput_error +'Pp' +Pprecision_not + +
     
    'd' +delaunay +'v' +voronoi +'G' +Geomview +'H' +Halfspace + +
    'f' +facet_dump +'i' +incidences +'m' +mathematica +'n' +normals + +
    'o' +OFF_format +'p' +points +'s' +summary + +
     
    'Gv' +Gvertices +'Gp' +Gpoints +'Ga' +Gall_points +'Gn' +Gno_planes + +
    'Gi' +Ginner +'Gc' +Gcentrums +'Gh' +Ghyperplanes +'Gr' +Gridges + +
    'Go' +Gouter +'GDn' +GDrop_dim +'Gt' +Gtransparent + +
     
    'T4' +T4_trace +'Tc' +Tcheck_often +'Ts' +Tstatistics +'Tv' +Tverify + +
    'Tz' +Tz_stdout +'TFn' +TFacet_log +'TI file' +TInput_file +'TPn' +TPoint_trace + +
    'TMn' +TMerge_trace +'TO file' +TOutput_file +'TRn' +TRerun +'TWn' +TWide_trace + +
    'TV-n' +TVertex_stop_before +
    'TVn' +TVertex_stop_after +'TCn' +TCone_stop_after + +
     
    'A-n' +Angle_max_pre +'An' +Angle_max_post +'C-0' +Centrum_roundoff +'C-n' +Centrum_size_pre + +
    'Cn' +Centrum_size_post +'En' +Error_round +'Rn' +Random_dist +'Vn' +Visible_min + +
    'Un' +Ucoplanar_max +'Wn' +Wide_outside + +
     
    'Qbk:n' +Qbound_low +'QBk:n' +QBound_high +'Qbk:0Bk:0' +Qbound_drop +'QbB' +QbB-scale-box + +
    'Qbb' +Qbb-scale-last +'Qc' +Qcoplanar +'Qf' +Qfurthest +'Qg' +Qgood_only + +
    'QGn' +QGood_point +'Qi' +Qinterior +'Qm' +Qmax_out +'QJn' +QJoggle + +
    'Qr' +Qrandom +'QRn' +QRotate +'Qs' +Qsearch_1st +'Qt' +Qtriangulate + +
    'Qu' +QupperDelaunay +'QVn' +QVertex_good +'Qv' +Qvneighbors +'Qx' +Qxact_merge + +
    'Qz' +Qzinfinite + +
     
    'Q0' +Q0_no_premerge +'Q1' +Q1_no_angle +'Q2' +Q2_no_independ +'Q3' +Q3_no_redundant + +
    'Q4' +Q4_no_old +'Q5' +Q5_no_check_out +'Q6' +Q6_no_concave +'Q7' +Q7_depth_first + +
    'Q8' +Q8_no_near_in +'Q9' +Q9_pick_furthest +'Q10' +Q10_no_narrow +'Q11' +Q11_trinormals +
    + + +


    + +

    Up: Home +page for Qhull
    +Up: Qhull manual
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull internals
    +To: Qhull functions, macros, and data structures
    +To: Qhull files
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser
    + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qhalf.htm b/xs/src/qhull/html/qhalf.htm new file mode 100644 index 0000000000..c87fe719ed --- /dev/null +++ b/xs/src/qhull/html/qhalf.htm @@ -0,0 +1,626 @@ + + + + +qhalf -- halfspace intersection about a point + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    [halfspace]qhalf -- halfspace intersection about a point

    + +

    The intersection of a set of halfspaces is a polytope. The +polytope may be unbounded. See Preparata & Shamos ['85] for a discussion. In low +dimensions, halfspace intersection may be used for linear +programming. + +

    +
    +

    Example: rbox c | qconvex FQ FV + n | qhalf Fp

    +
    Print the intersection of the facets of a cube. rbox c + generates the vertices of a cube. qconvex FV n returns of average + of the cube's vertices (in this case, the origin) and the halfspaces + that define the cube. qhalf Fp computes the intersection of + the halfspaces about the origin. The intersection is the vertices + of the original cube.
    + +

    Example: rbox c d G0.55 | qconvex FQ FV + n | qhalf Fp

    +
    Print the intersection of the facets of a cube and a diamond. There + are 24 facets and 14 intersection points. Four facets define each diamond + vertex. Six facets define each cube vertex. +
    + +

    Example: rbox c d G0.55 | qconvex FQ FV + n | qhalf Fp + Qt

    +
    Same as above except triangulate before computing + the intersection points. Three facets define each intersection + point. There are two duplicates of the diamond and four duplicates of the cube. +
    + +

    Example: rbox 10 s t10 | qconvex FQ FV + n | qhalf Fp Fn

    +
    Print the intersection of the facets of the convex hull of 10 cospherical points. + Include the intersection points and the neighboring intersections. + As in the previous examples, the intersection points are nearly the same as the + original input points. +
    +
    +
    + +

    In Qhull, a halfspace is defined by the points on or below a hyperplane. +The distance of each point to the hyperplane is less than or equal to zero. + +

    Qhull computes a halfspace intersection by the geometric +duality between points and halfspaces. +See halfspace examples, +qhalf notes, and +option 'p' of qhalf outputs.

    + +

    Qhalf's outputs are the intersection +points (Fp) and +the neighboring intersection points (Fn). +For random inputs, halfspace +intersections are usually defined by more than d halfspaces. See the sphere example. + +

    You can try triangulated output ('Qt') and joggled input ('QJ'). +It demonstrates that triangulated output is more accurate than joggled input. + +

    If you use 'Qt' (triangulated output), all +halfspace intersections are simplicial (e.g., three halfspaces per +intersection in 3-d). In 3-d, if more than three halfspaces intersect +at the same point, triangulated output will produce +duplicate intersections, one for each additional halfspace. See the third example, or +add 'Qt' to the sphere example.

    + +

    If you use 'QJ' (joggled input), all halfspace +intersections are simplicial. This may lead to nearly identical +intersections. For example, either replace 'Qt' with 'QJ' above, or add +'QJ' to the sphere example. +See Merged facets or joggled input.

    + +

    The 'qhalf' program is equivalent to +'qhull H' in 2-d to 4-d, and +'qhull H Qx' +in 5-d and higher. It disables the following Qhull +options: d n v Qbb QbB Qf Qg Qm +Qr QR Qv Qx Qz TR E V Fa FA FC FD FS Ft FV Gt Q0,etc. + + +

    Copyright © 1995-2015 C.B. Barber

    +
    + +

    »qhalf synopsis

    +
    +qhalf- halfspace intersection about a point.
    +    input (stdin): [dim, 1, interior point]
    +                   dim+1, n
    +                   halfspace coefficients + offset
    +    comments start with a non-numeric character
    +
    +options (qhalf.htm):
    +    Hn,n - specify coordinates of interior point
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Tv   - verify result: structure, convexity, and redundancy
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +output options (subset):
    +    s    - summary of results (default)
    +    Fp   - intersection coordinates
    +    Fv   - non-redundant halfspaces incident to each intersection
    +    Fx   - non-redundant halfspaces
    +    o    - OFF file format (dual convex hull)
    +    G    - Geomview output (dual convex hull)
    +    m    - Mathematica output (dual convex hull)
    +    QVn  - print intersections for halfspace n, -n if not
    +    TO file - output results to file, may be enclosed in single quotes
    +
    +examples:
    +    rbox d | qconvex n | qhalf s H0,0,0 Fp
    +    rbox c | qconvex FV n | qhalf s i
    +    rbox c | qconvex FV n | qhalf s o
    +
    + +

    »qhalf input

    + +
    +

    The input data on stdin consists of:

    +
      +
    • [optional] interior point +
        +
      • dimension +
      • 1 +
      • coordinates of interior point +
      +
    • dimension + 1 +
    • number of halfspaces
    • +
    • halfspace coefficients followed by offset
    • +
    + +

    Use I/O redirection (e.g., qhalf < data.txt), a pipe (e.g., rbox c | qconvex FV n | qhalf), +or the 'TI' option (e.g., qhalf TI data.txt). + +

    Qhull needs an interior point to compute the halfspace +intersection. An interior point is clearly inside all of the halfspaces. +A point is inside a halfspace if its distance to the corresponding hyperplane is negative. + +

    The interior point may be listed at the beginning of the input (as shown above). +If not, option +'Hn,n' defines the interior point as +[n,n,0,...] where 0 is the default coordinate (e.g., +'H0' is the origin). Use linear programming if you do not know +the interior point (see halfspace notes),

    + +

    The input to qhalf is a set of halfspaces that are defined by their hyperplanes. +Each halfspace is defined by +d coefficients followed by a signed offset. This defines +a linear inequality. The coefficients define a vector that is +normal to the halfspace. +The vector may have any length. If it +has length one, the offset is the distance from the origin to the +halfspace's boundary. Points in the halfspace have a negative distance to the hyperplane. +The distance from the interior point to each +halfspace is likewise negative.

    + +

    The halfspace format is the same as Qhull's output options 'n', 'Fo', +and 'Fi'. Use option 'Fd' to use cdd format for the +halfspaces.

    + +

    For example, here is the input for computing the intersection +of halfplanes that form a cube.

    + +
    +

    rbox c | qconvex FQ FV n TO data

    +
    +RBOX c | QCONVEX FQ FV n
    +3 1
    +     0      0      0
    +4
    +6
    +     0      0     -1   -0.5
    +     0     -1      0   -0.5
    +     1      0      0   -0.5
    +    -1      0      0   -0.5
    +     0      1      0   -0.5
    +     0      0      1   -0.5
    +
    +

    qhalf s Fp < data

    +
    +
    +Halfspace intersection by the convex hull of 6 points in 3-d:
    +
    +  Number of halfspaces: 6
    +  Number of non-redundant halfspaces: 6
    +  Number of intersection points: 8
    +
    +Statistics for: RBOX c | QCONVEX FQ FV n | QHALF s Fp
    +
    +  Number of points processed: 6
    +  Number of hyperplanes created: 11
    +  Number of distance tests for qhull: 11
    +  Number of merged facets: 1
    +  Number of distance tests for merging: 45
    +  CPU seconds to compute hull (after input):  0
    +
    +3
    +3
    +8
    +  -0.5    0.5    0.5
    +   0.5    0.5    0.5
    +  -0.5    0.5   -0.5
    +   0.5    0.5   -0.5
    +   0.5   -0.5    0.5
    +  -0.5   -0.5    0.5
    +  -0.5   -0.5   -0.5
    +   0.5   -0.5   -0.5
    +
    +
    + +
    +

    »qhalf outputs

    +
    + +

    The following options control the output for halfspace +intersection.

    +
    +
    +
     
    +
    Intersections
    +
    FN
    +
    list intersection points for each non-redundant + halfspace. The first line + is the number of non-redundant halfspaces. Each remaining + lines starts with the number of intersection points. For the cube + example, each halfspace has four intersection points.
    +
    Fn
    +
    list neighboring intersections for each intersection point. The first line + is the number of intersection points. Each remaining line + starts with the number of neighboring intersections. For the cube + example, each intersection point has three neighboring intersections. +

    + In 3-d, a non-simplicial intersection has more than three neighboring + intersections. For random data (e.g., the sphere example), non-simplicial intersections are the norm. + Option 'Qt' produces three + neighboring intersections per intersection by duplicating the intersection + points. Option QJ' produces three + neighboring intersections per intersection by joggling the hyperplanes and + hence their intersections. +

    +
    Fp
    +
    print intersection coordinates. The first line is the dimension and the + second line is the number of intersection points. The following lines are the + coordinates of each intersection.
    +
    FI
    +
    list intersection IDs. The first line is the number of + intersections. The IDs follow, one per line.
    +
     
    +
     
    +
    Halfspaces
    +
    Fx
    +
    list non-redundant halfspaces. The first line is the number of + non-redundant halfspaces. The other lines list one halfspace per line. + A halfspace is non-redundant if it + defines a facet of the intersection. Redundant halfspaces are ignored. For + the cube example, all of the halfspaces are non-redundant. +
    +
    Fv
    +
    list non-redundant halfspaces incident to each intersection point. + The first line is the number of + non-redundant halfspaces. Each remaining line starts with the number + of non-redundant halfspaces. For the + cube example, each intersection is incident to three halfspaces.
    +
    i
    +
    list non-redundant halfspaces incident to each intersection point. The first + line is the number of intersection points. Each remaining line + lists the incident, non-redundant halfspaces. For the + cube example, each intersection is incident to three halfspaces. +
    +
    Fc
    +
    list coplanar halfspaces for each intersection point. The first line is + the number of intersection points. Each remaining line starts with + the number of coplanar halfspaces. A coplanar halfspace is listed for + one intersection point even though it is coplanar to multiple intersection + points.
    +
    Qi Fc
    +
    list redundant halfspaces for each intersection point. The first line is + the number of intersection points. Each remaining line starts with + the number of redundant halfspaces. Use options 'Qc Qi Fc' to list + coplanar and redundant halfspaces.
    + +
     
    +
     
    +
    General
    +
    s
    +
    print summary for the halfspace intersection. Use 'Fs' if you need numeric data.
    +
    o
    +
    print vertices and facets of the dual convex hull. The + first line is the dimension. The second line is the number of + vertices, facets, and ridges. The vertex + coordinates are next, followed by the facets, one per line.
    +
    p
    +
    print vertex coordinates of the dual convex hull. Each vertex corresponds + to a non-redundant halfspace. Its coordinates are the negative of the hyperplane's coefficients + divided by the offset plus the inner product of the coefficients and + the interior point (-c/(b+a.p). + Options 'p Qc' includes coplanar halfspaces. + Options 'p Qi' includes redundant halfspaces.
    +
    m
    +
    Mathematica output for the dual convex hull in 2-d or 3-d.
    +
    FM
    +
    Maple output for the dual convex hull in 2-d or 3-d.
    +
    G
    +
    Geomview output for the dual convex hull in 2-d, 3-d, or 4-d.
    +
    +
    + +
    +

    »qhalf controls

    +
    + +

    These options provide additional control:

    + +
    +
    +
    Qt
    +
    triangulated output. If a 3-d intersection is defined by more than + three hyperplanes, Qhull produces duplicate intersections -- one for + each extra hyperplane.
    +
    QJ
    +
    joggle the input instead of merging facets. In 3-d, this guarantees that + each intersection is defined by three hyperplanes.
    +
    f
    +
    facet dump. Print the data structure for each intersection (i.e., + facet)
    +
    TFn
    +
    report summary after constructing n + intersections
    +
    QVn
    +
    select intersection points for halfspace n + (marked 'good')
    +
    QGn
    +
    select intersection points that are visible to halfspace n + (marked 'good'). Use -n for the remainder.
    +
    Qbk:0Bk:0
    +
    remove the k-th coordinate from the input. This computes the + halfspace intersection in one lower dimension.
    +
    Tv
    +
    verify result
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    Qs
    +
    search all points for the initial simplex. If Qhull can + not construct an initial simplex, it reports a +descriptive message. Usually, the point set is degenerate and one +or more dimensions should be removed ('Qbk:0Bk:0'). +If not, use option 'Qs'. It performs an exhaustive search for the +best initial simplex. This is expensive is high dimensions.
    +
    +
    + + +
    +

    »qhalf graphics

    +
    + +

    To view the results with Geomview, compute the convex hull of +the intersection points ('qhull FQ H0 Fp | qhull G'). See Halfspace examples.

    + +
    +

    »qhalf notes

    +
    + +

    See halfspace intersection for precision issues related to qhalf.

    + +

    If you do not know an interior point for the halfspaces, use +linear programming to find one. Assume, n halfspaces +defined by: aj*x1+bj*x2+cj*x3+dj<=0, j=1..n. Perform +the following linear program:

    + +
    +

    max(x5) aj*x1+bj*x2+cj*x3+dj*x4+x5<=0, j=1..n

    +
    + +

    Then, if [x1,x2,x3,x4,x5] is an optimal solution with +x4>0 and x5>0 we get:

    + +
    +

    aj*(x1/x4)+bj*(x2/x4)+cj*(x3/x4)+dj<=(-x5/x4) j=1..n and (-x5/x4)<0, +

    +
    + +

    and conclude that the point [x1/x4,x2/x4,x3/x4] is in +the interior of all the halfspaces. Since x5 is +optimal, this point is "way in" the interior (good +for precision errors).

    + +

    After finding an interior point, the rest of the intersection +algorithm is from Preparata & Shamos ['85, p. 316, "A simple case +..."]. Translate the halfspaces so that the interior point +is the origin. Calculate the dual polytope. The dual polytope is +the convex hull of the vertices dual to the original faces in +regard to the unit sphere (i.e., halfspaces at distance d +from the origin are dual to vertices at distance 1/d). +Then calculate the resulting polytope, which is the dual of the +dual polytope, and translate the origin back to the interior +point [S. Spitz, S. Teller, D. Strawn].

    + + +
    +

    »qhalf +conventions

    +
    + +

    The following terminology is used for halfspace intersection +in Qhull. This is the hardest structure to understand. The +underlying structure is a convex hull with one vertex per +non-redundant halfspace. See convex hull +conventions and Qhull's data structures.

    + +
      +
    • interior point - a point in the intersection of + the halfspaces. Qhull needs an interior point to compute + the intersection. See halfspace input.
    • +
    • halfspace - d coordinates for the + normal and a signed offset. The distance to an interior + point is negative.
    • +
    • non-redundant halfspace - a halfspace that + defines an output facet
    • +
    • vertex - a dual vertex in the convex hull + corresponding to a non-redundant halfspace
    • +
    • coplanar point - the dual point corresponding to + a similar halfspace
    • +
    • interior point - the dual point corresponding to + a redundant halfspace
    • +
    • intersection point- the intersection of d + or more non-redundant halfspaces
    • +
    • facet - a dual facet in the convex hull + corresponding to an intersection point
    • +
    • non-simplicial facet - more than d + halfspaces intersect at a point
    • +
    • good facet - an intersection point that + satisfies restriction 'QVn', + etc.
    • +
    + +
    +

    »qhalf options

    + +
    +qhalf- compute the intersection of halfspaces about a point
    +    http://www.qhull.org
    +
    +input (stdin):
    +    optional interior point: dimension, 1, coordinates
    +    first lines: dimension+1 and number of halfspaces
    +    other lines: halfspace coefficients followed by offset
    +    comments:    start with a non-numeric character
    +
    +options:
    +    Hn,n - specify coordinates of interior point
    +    Qt   - triangulated ouput
    +    QJ   - joggle input instead of merging facets
    +    Qc   - keep coplanar halfspaces
    +    Qi   - keep other redundant halfspaces
    +
    +Qhull control options:
    +    QJn  - randomly joggle input in range [-n,n]
    +    Qbk:0Bk:0 - remove k-th coordinate from input
    +    Qs   - search all halfspaces for the initial simplex
    +    QGn  - print intersection if redundant to halfspace n, -n for not
    +    QVn  - print intersections for halfspace n, -n if not
    +
    +Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - print statistics
    +    Tv   - verify result: structure, convexity, and redundancy
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when halfspace n added to intersection
    +    TMn  - turn on tracing at merge n
    +    TWn  - trace merge facets when width > n
    +    TVn  - stop qhull after adding halfspace n, -n for before (see TCn)
    +    TCn  - stop qhull after building cone for halfspace n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Un   - max distance below plane for a new, coplanar halfspace
    +    Wn   - min facet width for outside halfspace (before roundoff)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    f    - facet dump
    +    G    - Geomview output (dual convex hull)
    +    i    - non-redundant halfspaces incident to each intersection
    +    m    - Mathematica output (dual convex hull)
    +    o    - OFF format (dual convex hull: dimension, points, and facets)
    +    p    - vertex coordinates of dual convex hull (coplanars if 'Qc' or 'Qi')
    +    s    - summary (stderr)
    +
    +More formats:
    +    Fc   - count plus redundant halfspaces for each intersection
    +         -   Qc (default) for coplanar and Qi for other redundant
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FF   - facet dump without ridges
    +    FI   - ID of each intersection
    +    Fm   - merge count for each intersection (511 max)
    +    FM   - Maple output (dual convex hull)
    +    Fn   - count plus neighboring intersections for each intersection
    +    FN   - count plus intersections for each non-redundant halfspace
    +    FO   - options and precision constants
    +    Fp   - dim, count, and intersection coordinates
    +    FP   - nearest halfspace and distance for each redundant halfspace
    +    FQ   - command used for qhalf
    +    Fs   - summary: #int (8), dim, #halfspaces, #non-redundant, #intersections
    +                      for output: #non-redundant, #intersections, #coplanar
    +                                  halfspaces, #non-simplicial intersections
    +                    #real (2), max outer plane, min vertex
    +    Fv   - count plus non-redundant halfspaces for each intersection
    +    Fx   - non-redundant halfspaces
    +
    +Geomview output (2-d, 3-d and 4-d; dual convex hull)
    +    Ga   - all points (i.e., transformed halfspaces) as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices (i.e., non-redundant halfspaces) as spheres
    +    Gi   - inner planes (i.e., halfspace intersections) only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc   - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +
    +Print options:
    +    PAn  - keep n largest facets (i.e., intersections) by area
    +    Pdk:n- drop facet if normal[k] <= n (default 0.0)
    +    PDk:n- drop facet if normal[k] >= n
    +    Pg   - print good facets (needs 'QGn' or 'QVn')
    +    PFn  - keep facets whose area is at least n
    +    PG   - print neighbors of good facets
    +    PMn  - keep n facets with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + + diff --git a/xs/src/qhull/html/qhull-cpp.xml b/xs/src/qhull/html/qhull-cpp.xml new file mode 100644 index 0000000000..ae755e8266 --- /dev/null +++ b/xs/src/qhull/html/qhull-cpp.xml @@ -0,0 +1,214 @@ + + + + +

    Qhull C++ -- C++ interface to Qhull

    + + Copyright (c) 2009-2015, C.B. Barber + + +
    +

    This draft + document records some of the design decisions for Qhull C++. Convert it to HTML by road-faq.xsl from road-faq. + + Please send comments and suggestions to bradb@shore.net +

    +
    +
    +
    + Help +
    • +
    • +
    • +
    +
    +
    +
    • +
    • +
    +
    +
    +
    + . +
    + +
    + + + + Qhull's collection APIs are modeled on Qt's collection API (QList, QVector, QHash) w/o QT_STRICT_ITERATORS. They support STL and Qt programming. + +

    Some of Qhull's collection classes derive from STL classes. If so, + please avoid additional STL functions and operators added by inheritance. + These collection classes may be rewritten to derive from Qt classes instead. + See Road's . +

    + + + Qhull's collection API (where applicable). For documentation, see Qt's QList, QMap, QListIterator, QMapIterator, QMutableListIterator, and QMutableMapIterator +
    • + STL types [list, qlinkedlist, qlist, qvector, vector] -- const_iterator, iterator +
    • + STL types describing iterators [list, qlinkedlist, qlist, qvector, vector] -- const_pointer, const_reference, difference_type, + pointer, reference, size_type, value_type. + Pointer and reference types not defined if unavailable (not needed for <algorithm>) +
    • + const_iterator, iterator types -- difference_type, iterator_category, pointer, reference, value_type +
    • + Qt types [qlinkedlist, qlist, qvector] -- ConstIterator, Iterator, QhullclassIterator, MutableQhullclassIterator. + Qt's foreach requires const_iterator. +
    • + Types for sets/maps [hash_map, QHash] -- key_compare, key_type, mapped_type +
    • + Constructor -- default constructor, copy constructor, assignment operator, destructor +
    • + Conversion -- to/from/as corresponding C, STL, and Qt constructs. Include toQList and toStdVector (may be filtered, e.g., QhullFacetSet). + Do not define fromStdList and fromQList if container is not reference counted (i.e., acts like a value) +
    • + Get/set -- configuration options for class +
    • + STL-style iterator - begin, constBegin, constEnd, end, key, value, =, *, [], ->, ++, --, +, -, ==, !=, <, + <=, >, >=, const_iterator(iterator), iterator COMPARE const_iterator. + An iterator is an abstraction of a pointer. It is not aware of its container. +
    • + Java-style iterator [qiterator.h] - countRemaining, findNext, findPrevious, hasNext, hasPrevious, next, peekNext, peekPrevious, previous, toBack, toFront, = Coordinates +
    • + Mutable Java-style iterator adds - insert, remove, setValue, value +
    • + Element access -- back, first, front, last +
    • + Element access w/ index -- [], at (const& only), constData, data, mid, value +
    • + Read-only - (int)count, empty, isEmpty, (size_t)size. Count() and size() may be filtered. If so, they may be zero when !empty(). +
    • + Read-only for sets/maps - capacity, key, keys, reserve, resize, values +
    • + Operator - ==, !=, +, +=, << +
    • + Read-write -- append, clear, erase, insert, move, prepend, pop_back, pop_front, push_back, push_front, removeAll, removeAt, removeFirst, removeLast, replace, + swap, takeAt, takeFirst, takeLast +
    • + Read-write for sets/maps -- insertMulti, squeeze, take, unite +
    • + Search -- contains(const T &), count(const T &), indexOf, lastIndexOf +
    • + Search for sets/maps -- constFind, lowerBound, upperBound +
    • + Stream I/O -- stream << +
    + + STL list and vector -- For unfiltered access to each element. +
    • + Apache: Creating your own containers -- requirements for STL containers. Iterators should define the types from 'iterator_traits'. +
    • + STL types -- allocator_type, const_iterator, const_pointer, const_reference, const_reverse_iterator, difference_type, iterator, iterator_category, pointer, reference, reverse_iterator, size_type, value_type +
    • + STL constructors -- MyType(), MyType(count), MyType(count, value), MyType(first, last), + MyType(MyType&), +
    • + STL getter/setters -- at (random_access only), back, begin, capacity, end, front, rbegin, rend, size, max_size +
    • + STL predicates -- empty +
    • + STL iterator types -- const_pointer, const_reference, difference_type, iterator_category, pointer, reference, value_type +
    • + STL iterator operators -- *, -<, ++, --, +=, -=, +, -, [], ==, !=, <, >, >=, <= +
    • + STL operators -- =, [] (random_access only), ==, !=, <, >, <=, >= +
    • + STL modifiers -- assign, clear, erase, insert, pop_back, push_back, reserve, resize, swap +
    • +
    + + Qt Qlist -- For unfiltered access to each element +
    • +
    • + Additional Qt types -- ConstIterator, Iterator, QListIterator, QMutableListIterator +
    • + Additional Qt get/set -- constBegin, constEnd, count, first, last, value (random_access only) +
    • + Additional Qt predicates -- isEmpty +
    • + Additional Qt -- mid (random_access only) +
    • + Additional Qt search -- contains, count(T&), indexOf (random_access only), lastIndeOf (random_access only) +
    • + Additional Qt modifiers -- append, insert(index,value) (random_access only), move (random_access only), pop_front, prepend, push_front, removeAll, removeAt (random_access only), removeFirst, removeLast, replace, swap by index, takeAt, takeFirst, takeLast +
    • + Additional Qt operators -- +, <<, +=, + stream << and >> +
    • + Unsupported types by Qt -- allocator_type, const_reverse_iterator, reverse_iterator +
    • + Unsupported accessors by Qt -- max_size, rbegin, rend +
    • + Unsupported constructors by Qt -- multi-value constructors +
    • + unsupported modifiers by Qt -- assign, muli-value inserts, STL's swaps +
    • +
    + + STL map and Qt QMap. These use nearly the same API as list and vector classes. They add the following. +
    • + STL types -- key_compare, key_type, mapped_type +
    • + STL search -- equal_range, find, lower_bound, upper_bound +
    • + Qt removes -- equal_range, key_compare +
    • + Qt renames -- lowerBound, upperBound +
    • + Qt adds -- constFind, insertMulti, key, keys, take, uniqueKeys, unite, values +
    • + Not applicable to map and QMap -- at, back, pop_back, pop_front, push_back, push_front, swap +
    • + Not applicable to QMap -- append, first, last, lastIndexOf, mid, move, prepend, removeAll, removeAt, removeFirst, removeLast, replace, squeeze, takeAt, takeFirst, takeLast +
    • + Not applicable to map -- assign +
    + + Qt QHash. STL extensions provide similar classes, e.g., Microsoft's stdext::hash_set. THey are nearly the same as QMap +
    • +
    • +
    • + Not applicable to Qhash -- lowerBound, unite, upperBound, +
    • + Qt adds -- squeeze +
    +
    + +
    • + check... -- Throw error on failure +
    • + try... -- Return false on failure. Do not throw errors. +
    • + ...Temporarily -- lifetime depends on source. e.g., toByteArrayTemporarily +
    • + ...p -- indicates pointer-to. +
    • + end... -- points to one beyond the last available +
    • + private functions -- No syntactic indication. They may become public later on. +
    • + Error messages -- Preceed error messages with the name of the class throwing the error (e.g. "ClassName: ..."). If this is an internal error, use "ClassName inconsistent: ..." +
    • + parameter order -- qhRunId, dimension, coordinates, count. +
    • + toClass -- Convert into a Class object (makes a deep copy) +
    • + qRunId -- Requires Qh installed. Some routines allow 0 for limited info (e.g., operator<<) +
    • + Disable methods in derived classes -- If the default constructor, copy constructor, or copy assignment is disabled, it should be also disabled in derived classes (better error messages). +
    • + Constructor order -- default constructor, other constructors, copy constructor, copy assignment, destructor +
    +
    +
    +
    diff --git a/xs/src/qhull/html/qhull.htm b/xs/src/qhull/html/qhull.htm new file mode 100644 index 0000000000..0a2aa75e06 --- /dev/null +++ b/xs/src/qhull/html/qhull.htm @@ -0,0 +1,473 @@ + + + + +qhull -- convex hull and related structures + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis • input +• outputs • controls +• options +


    + +

    [cone]qhull -- convex hull and related structures

    + +

    The convex hull of a set of points is the smallest convex set +containing the points. The Delaunay triangulation and furthest-site +Delaunay triangulation are equivalent to a convex hull in one +higher dimension. Halfspace intersection about a point is +equivalent to a convex hull by polar duality. + +

    The qhull program provides options to build these +structures and to experiment with the process. Use the +qconvex, +qdelaunay, qhalf, +and qvoronoi programs +to build specific structures. You may use qhull instead. +It takes the same options and uses the same code. +

    +
    +
    Example: rbox 1000 D3 | qhull + C-1e-4 + FO + Ts +
    +
    Compute the 3-d convex hull of 1000 random + points. + Centrums must be 10^-4 below neighboring + hyperplanes. Print the options and precision constants. + When done, print statistics. These options may be + used with any of the Qhull programs.
    +
     
    +
    Example: rbox 1000 D3 | qhull d + Qbb + R1e-4 + Q0
    +
    Compute the 3-d Delaunay triangulation of 1000 random + points. Randomly perturb all calculations by + [0.9999,1.0001]. Do not correct precision problems. + This leads to serious precision errors.
    +
    +
    +

    Use the following equivalences when calling qhull in 2-d to 4-d (a 3-d +Delaunay triangulation is a 4-d convex hull): +

    + +
    + +

    Use the following equivalences when calling qhull in 5-d and higher (a 4-d +Delaunay triangulation is a 5-d convex hull): +

    + +
    + + +

    By default, Qhull merges coplanar facets. For example, the convex +hull of a cube's vertices has six facets. + +

    If you use 'Qt' (triangulated output), +all facets will be simplicial (e.g., triangles in 2-d). For the cube +example, it will have 12 facets. Some facets may be +degenerate and have zero area. + +

    If you use 'QJ' (joggled input), +all facets will be simplicial. The corresponding vertices will be +slightly perturbed. Joggled input is less accurate that triangulated +output.See Merged facets or joggled input.

    + +

    The output for 4-d convex hulls may be confusing if the convex +hull contains non-simplicial facets (e.g., a hypercube). See +Why +are there extra points in a 4-d or higher convex hull?
    +

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »qhull synopsis

    +
    +qhull- compute convex hulls and related structures.
    +    input (stdin): dimension, n, point coordinates
    +    comments start with a non-numeric character
    +    halfspace: use dim+1 and put offsets after coefficients
    +
    +options (qh-quick.htm):
    +    d    - Delaunay triangulation by lifting points to a paraboloid
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)
    +    v    - Voronoi diagram as the dual of the Delaunay triangulation
    +    v Qu - furthest-site Voronoi diagram
    +    H1,1 - Halfspace intersection about [1,1,0,...] via polar duality
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Tv   - verify result: structure, convexity, and point inclusion
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +Output options (subset):
    +    s    - summary of results (default)
    +    i    - vertices incident to each facet
    +    n    - normals with offsets
    +    p    - vertex coordinates (if 'Qc', includes coplanar points)
    +           if 'v', Voronoi vertices
    +    Fp   - halfspace intersections
    +    Fx   - extreme points (convex hull vertices)
    +    FA   - compute total area and volume
    +    o    - OFF format (if 'v', outputs Voronoi regions)
    +    G    - Geomview output (2-d, 3-d and 4-d)
    +    m    - Mathematica output (2-d and 3-d)
    +    QVn  - print facets that include point n, -n if not
    +    TO file- output results to file, may be enclosed in single quotes
    +
    +examples:
    +    rbox c d D2 | qhull Qc s f Fx | more      rbox 1000 s | qhull Tv s FA
    +    rbox 10 D2 | qhull d QJ s i TO result     rbox 10 D2 | qhull v Qbb Qt p
    +    rbox 10 D2 | qhull d Qu QJ m              rbox 10 D2 | qhull v Qu QJ o
    +    rbox c | qhull n                          rbox c | qhull FV n | qhull H Fp
    +    rbox d D12 | qhull QR0 FA                 rbox c D7 | qhull FA TF1000
    +    rbox y 1000 W0 | qhull                    rbox 10 | qhull v QJ o Fv
    +
    + +

    »qhull input

    +
    + +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qhull < data.txt), a pipe (e.g., rbox 10 | qhull), +or the 'TI' option (e.g., qhull TI data.txt). + +

    Comments start with a non-numeric character. Error reporting is +simpler if there is one point per line. Dimension +and number of points may be reversed. For halfspace intersection, +an interior point may be prepended (see qhalf input). + +

    Here is the input for computing the convex +hull of the unit cube. The output is the normals, one +per facet.

    + +
    +

    rbox c > data

    +
    +3 RBOX c
    +8
    +  -0.5   -0.5   -0.5
    +  -0.5   -0.5    0.5
    +  -0.5    0.5   -0.5
    +  -0.5    0.5    0.5
    +   0.5   -0.5   -0.5
    +   0.5   -0.5    0.5
    +   0.5    0.5   -0.5
    +   0.5    0.5    0.5
    +
    +

    qhull s n < data

    +
    +
    +Convex hull of 8 points in 3-d:
    +
    +  Number of vertices: 8
    +  Number of facets: 6
    +  Number of non-simplicial facets: 6
    +
    +Statistics for: RBOX c | QHULL s n
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 11
    +  Number of distance tests for qhull: 35
    +  Number of merged facets: 6
    +  Number of distance tests for merging: 84
    +  CPU seconds to compute hull (after input): 0.081
    +
    +4
    +6
    +     0      0     -1   -0.5
    +     0     -1      0   -0.5
    +     1      0      0   -0.5
    +    -1      0      0   -0.5
    +     0      1      0   -0.5
    +     0      0      1   -0.5
    +
    +
    + +
    +

    »qhull outputs

    +
    + +

    These options control the output of qhull. They may be used +individually or together.

    +
    +
    +
     
    +
    General
    +
    qhull
    +
    compute the convex hull of the input points. + See qconvex.
    +
    qhull d Qbb
    +
    compute the Delaunay triangulation by lifting the points + to a paraboloid. Use option 'Qbb' + to scale the paraboloid and improve numeric precision. + See qdelaunay.
    +
    qhull v Qbb
    +
    compute the Voronoi diagram by computing the Delaunay + triangulation. Use option 'Qbb' + to scale the paraboloid and improve numeric precision. + See qvoronoi.
    +
    qhull H
    +
    compute the halfspace intersection about a point via polar + duality. The point is below the hyperplane that defines the halfspace. + See qhalf.
    +
    +
    + +

    For a full list of output options see +

    + +
    + +
    +

    »qhull controls

    +
    + +

    For a full list of control options see +

    + +
    + +
    +

    »qhull options

    + +
    +qhull- compute convex hulls and related structures.
    +    http://www.qhull.org
    +
    +input (stdin):
    +    first lines: dimension and number of points (or vice-versa).
    +    other lines: point coordinates, best if one point per line
    +    comments:    start with a non-numeric character
    +    halfspaces:  use dim plus one and put offset after coefficients.
    +                 May be preceded by a single interior point ('H').
    +
    +options:
    +    d    - Delaunay triangulation by lifting points to a paraboloid
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)
    +    v    - Voronoi diagram (dual of the Delaunay triangulation)
    +    v Qu - furthest-site Voronoi diagram
    +    Hn,n,... - halfspace intersection about point [n,n,0,...]
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Qc   - keep coplanar points with nearest facet
    +    Qi   - keep interior points with nearest facet
    +
    +Qhull control options:
    +    Qbk:n   - scale coord k so that low bound is n
    +      QBk:n - scale coord k so that upper bound is n (QBk is 0.5)
    +    QbB  - scale input to unit cube centered at the origin
    +    Qbb  - scale last coordinate to [0,m] for Delaunay triangulations
    +    Qbk:0Bk:0 - remove k-th coordinate from input
    +    QJn  - randomly joggle input in range [-n,n]
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)
    +    Qf   - partition point to furthest outside facet
    +    Qg   - only build good facets (needs 'QGn', 'QVn', or 'PdD')
    +    Qm   - only process points that would increase max_outside
    +    Qr   - process random outside points instead of furthest ones
    +    Qs   - search all points for the initial simplex
    +    Qu   - for 'd' or 'v', compute upper hull without point at-infinity
    +              returns furthest-site Delaunay triangulation
    +    Qv   - test vertex neighbors for convexity
    +    Qx   - exact pre-merges (skips coplanar and anglomaniacs facets)
    +    Qz   - add point-at-infinity to Delaunay triangulation
    +    QGn  - good facet if visible from point n, -n for not visible
    +    QVn  - good facet if it includes point n, -n if not
    +    Q0   - turn off default p remerge with 'C-0'/'Qx'
    +    Q1     - sort merges by type instead of angle
    +    Q2   - merge all non-convex at once instead of independent sets
    +    Q3   - do not merge redundant vertices
    +    Q4   - avoid old>new merges
    +    Q5   - do not correct outer planes at end of qhull
    +    Q6   - do not pre-merge concave or coplanar facets
    +    Q7   - depth-first processing instead of breadth-first
    +    Q8   - do not process near-inside points
    +    Q9   - process furthest of furthest points
    +    Q10  - no special processing for narrow distributions
    +    Q11  - copy normals and recompute centrums for tricoplanar facets
    +    Q12  - do not error on wide merge due to duplicate ridge and nearly coincident points
    +
    +Towpaths Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - print statistics
    +    Tv   - verify result: structure, convexity, and point inclusion
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when point n added to hull
    +     TMn - turn on tracing at merge n
    +     TWn - trace merge facets when width > n
    +    TRn  - rerun qhull n times.  Use with 'QJn'
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)
    +     TCn - stop qhull after building cone for point n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    En   - max roundoff error for distance computation
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Vn   - min distance above plane for a visible facet (default 3C-n or En)
    +    Un   - max distance below plane for a new, coplanar point (default Vn)
    +    Wn   - min facet width for outside point (before roundoff, default 2Vn)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    f    - facet dump
    +    G    - Geomview output (see below)
    +    i    - vertices incident to each facet
    +    m    - Mathematica output (2-d and 3-d)
    +    o    - OFF format (dim, points and facets; Voronoi regions)
    +    n    - normals with offsets
    +    p    - vertex coordinates or Voronoi vertices (coplanar points if 'Qc')
    +    s    - summary (stderr)
    +
    +More formats:
    +    Fa   - area for each facet
    +    FA   - compute total area and volume for option 's'
    +    Fc   - count plus coplanar points for each facet
    +           use 'Qc' (default) for coplanar and 'Qi' for interior
    +    FC   - centrum or Voronoi center for each facet
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FD   - use cdd format for numeric output (offset first)
    +    FF   - facet dump without ridges
    +    Fi   - inner plane for each facet
    +           for 'v', separating hyperplanes for bounded Voronoi regions
    +    FI   - ID of each facet
    +    Fm   - merge count for each facet (511 max)
    +    FM   - Maple output (2-d and 3-d)
    +    Fn   - count plus neighboring facets for each facet
    +    FN   - count plus neighboring facets for each point
    +    Fo   - outer plane (or max_outside) for each facet
    +           for 'v', separating hyperplanes for unbounded Voronoi regions
    +    FO   - options and precision constants
    +    Fp   - dim, count, and intersection coordinates (halfspace only)
    +    FP   - nearest vertex and distance for each coplanar point
    +    FQ   - command used for qhull
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,
    +                      output: #vertices, #facets, #coplanars, #nonsimplicial
    +                    #real (2), max outer plane, min vertex
    +    FS   - sizes:   #int (0)
    +                    #real(2) tot area, tot volume
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)
    +    Fv   - count plus vertices for each facet
    +           for 'v', Voronoi diagram as Voronoi vertices for pairs of sites
    +    FV   - average of vertices (a feasible point for 'H')
    +    Fx   - extreme points (in order for 2-d)
    +
    +Geomview options (2-d, 3-d, and 4-d; 2-d Voronoi)
    +    Ga   - all points as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices as spheres
    +    Gi   - inner planes only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc   - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +    Gt   - for 3-d 'd', transparent outer ridges
    +
    +Print options:
    +    PAn  - keep n largest facets by area
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)
    +    PDk:n - drop facet if normal[k] >= n
    +    Pg   - print good facets (needs 'QGn' or 'QVn')
    +    PFn  - keep facets whose area is at least n
    +    PG   - print neighbors of good facets
    +    PMn  - keep n facets with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis • input +• outputs • controls +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qhull.man b/xs/src/qhull/html/qhull.man new file mode 100644 index 0000000000..8d1dc08ace --- /dev/null +++ b/xs/src/qhull/html/qhull.man @@ -0,0 +1,1008 @@ +.\" This is the Unix manual page for qhull, written in nroff, the standard +.\" manual formatter for Unix systems. To format it, type +.\" +.\" nroff -man qhull.man +.\" +.\" This will print a formatted copy to standard output. If you want +.\" to ensure that the output is plain ASCII, free of any control +.\" characters that nroff uses for underlining etc, pipe the output +.\" through "col -b": +.\" +.\" nroff -man qhull.man | col -b +.\" +.\" Warning: a leading quote "'" or dot "." will not format correctly +.\" +.TH qhull 1 "2003/12/30" "Geometry Center" +.SH NAME +qhull \- convex hull, Delaunay triangulation, Voronoi diagram, +halfspace intersection about a point, hull volume, facet area +.SH SYNOPSIS +.nf +qhull- compute convex hulls and related structures + input (stdin): dimension, #points, point coordinates + first comment (non-numeric) is listed in the summary + halfspace: use dim plus one with offsets after coefficients + +options (qh-quick.htm): + d - Delaunay triangulation by lifting points to a paraboloid + v - Voronoi diagram via the Delaunay triangulation + H1,1 - Halfspace intersection about [1,1,0,...] + d Qu - Furthest-site Delaunay triangulation (upper convex hull) + v Qu - Furthest-site Voronoi diagram + Qt - triangulated output + QJ - Joggle the input to avoid precision problems + . - concise list of all options + - - one-line description of all options + +Output options (subset): + FA - compute total area and volume + Fx - extreme points (convex hull vertices) + G - Geomview output (2-d, 3-d and 4-d) + Fp - halfspace intersection coordinates + m - Mathematica output (2-d and 3-d) + n - normals with offsets + o - OFF file format (if Voronoi, outputs regions) + TO file- output results to file, may be enclosed in single quotes + f - print all fields of all facets + s - summary of results (default) + Tv - verify result: structure, convexity, and point inclusion + p - vertex coordinates (centers for Voronoi) + i - vertices incident to each facet + +example: + rbox 1000 s | qhull Tv s FA +.fi + + - html manual: index.htm + - installation: README.txt + - see also: COPYING.txt, REGISTER.txt, Changes.txt + - WWW: + - GIT: + - mirror: + - news: + - Geomview: + - news group: + - FAQ: + - email: qhull@qhull.org + - bug reports: qhull_bug@qhull.org + +The sections are: + - INTRODUCTION + - DESCRIPTION, a description of Qhull + - IMPRECISION, how Qhull handles imprecision + - OPTIONS + - Input and output options + - Additional input/output formats + - Precision options + - Geomview options + - Print options + - Qhull options + - Trace options + - BUGS + - E-MAIL + - SEE ALSO + - AUTHORS + - ACKNOWLEGEMENTS + +This man page briefly describes all Qhull options. Please report +any mismatches with Qhull's html manual (index.htm). + +.PP +.SH INTRODUCTION +Qhull is a general dimension code for computing convex hulls, Delaunay +triangulations, Voronoi diagram, furthest\[hy]site Voronoi diagram, +furthest\[hy]site Delaunay triangulations, and +halfspace intersections about a point. It implements the Quickhull algorithm for +computing the convex hull. Qhull handles round\[hy]off errors from floating +point arithmetic. It can approximate a convex hull. + +The program includes options for hull volume, facet area, partial hulls, +input transformations, randomization, tracing, multiple output formats, and +execution statistics. The program can be called from within your application. +You can view the results in 2\[hy]d, 3\[hy]d and 4\[hy]d with Geomview. +.PP +.SH DESCRIPTION +.PP +The format of input is the following: first line contains the dimension, +second line contains the number of input points, and point coordinates follow. +The dimension and number of points can be reversed. +Comments and line breaks are ignored. A comment starts with a +non\[hy]numeric character and continues to the end of line. The first comment +is reported in summaries and statistics. +Error reporting is +better if there is one point per line. +.PP +The default printout option is a short summary. There are many +other output formats. +.PP +Qhull implements the Quickhull algorithm for convex hull. This algorithm combines +the 2\[hy]d Quickhull algorithm with the n\[hy]d beneath\[hy]beyond algorithm +[c.f., Preparata & Shamos '85]. +It is similar to the randomized algorithms of Clarkson and +others [Clarkson et al. '93]. The main +advantages of Quickhull are output sensitive performance, reduced +space requirements, and automatic handling of precision problems. +.PP +The data structure produced by Qhull consists of vertices, ridges, and facets. +A vertex is a point of the input set. A ridge is a set of d vertices +and two neighboring facets. For example in 3\[hy]d, a ridge is an edge of the +polyhedron. A facet is a set of ridges, a set of neighboring facets, a set +of incident vertices, and a hyperplane equation. For simplicial facets, the +ridges are defined by the vertices and neighboring facets. When Qhull +merges two facets, it produces a non\[hy]simplicial +facet. A non\[hy]simplicial facet has more than d neighbors and may share more than +one ridge with a neighbor. +.PP +.SH IMPRECISION +.PP +Since Qhull uses floating point arithmetic, roundoff error may occur for each +calculation. This causes problems +for most geometric algorithms. +.PP +Qhull automatically sets option 'C\-0' in 2\[hy]d, 3\[hy]d, and 4\[hy]d, or +option 'Qx' in 5\[hy]d and higher. These options handle precision problems +by merging facets. Alternatively, use option 'QJ' to joggle the +input. +.PP +With 'C\-0', Qhull merges non\[hy]convex +facets while constructing the hull. The remaining facets are +clearly convex. With 'Qx', Qhull merges +coplanar horizon facets, flipped facets, concave facets and +duplicated ridges. It merges coplanar facets after constructing +the hull. +With 'Qx', coplanar points may be missed, but it +appears to be unlikely. +.PP +To guarantee triangular output, joggle the input with option 'QJ'. Facet +merging will not occur. +.SH OPTIONS +.PP +To get a list of the most important options, execute 'qhull' by itself. +To get a complete list of options, +execute 'qhull \-'. +To get a complete, concise list of options, execute 'qhull .'. + +Options can be in any order. +Capitalized options take an argument (except 'PG' and 'F' options). +Single letters are used for output formats and precision constants. The +other options are grouped into menus for other output formats ('F'), +Geomview output ('G'), +printing ('P'), Qhull control ('Q'), and tracing ('T'). +.TP +Main options: +.TP +default +Compute the convex hull of the input points. Report a summary of +the result. +.TP +d +Compute the Delaunay triangulation by lifting the input points to a +paraboloid. The 'o' option prints the input points and facets. +The 'QJ' option guarantees triangular output. The 'Ft' +option prints a triangulation. It adds points (the centrums) to non\[hy]simplicial +facets. +.TP +v +Compute the Voronoi diagram from the Delaunay triangulation. +The 'p' option prints the Voronoi vertices. +The 'o' option prints the Voronoi vertices and the +vertices in each Voronoi region. It lists regions in +site ID order. +The 'Fv' option prints each ridge of the Voronoi diagram. +The first or zero'th vertex +indicates the infinity vertex. Its coordinates are +qh_INFINITE (\-10.101). It indicates unbounded Voronoi +regions or degenerate Delaunay triangles. +.TP +Hn,n,... +Compute halfspace intersection about [n,n,0,...]. +The input is a set of halfspaces +defined in the same format as 'n', 'Fo', and 'Fi'. +Use 'Fp' to print the intersection points. Use 'Fv' +to list the intersection points for each halfspace. The +other output formats display the dual convex hull. + +The point [n,n,n,...] is a feasible point for the halfspaces, i.e., +a point that is inside all +of the halfspaces (Hx+b <= 0). The default coordinate value is 0. + +The input may start with a feasible point. If so, use 'H' by itself. +The input starts with a feasible point when the first number is the dimension, +the second number is "1", and the coordinates complete a line. The 'FV' +option produces a feasible point for a convex hull. +.TP +d Qu +Compute the furthest\[hy]site Delaunay triangulation from the upper +convex hull. The 'o' option prints the input points and facets. +The 'QJ' option guarantees triangular otuput. You can also use 'Ft' +to triangulate via the centrums of non\[hy]simplicial +facets. +.TP +v Qu +Compute the furthest\[hy]site Voronoi diagram. +The 'p' option prints the Voronoi vertices. +The 'o' option prints the Voronoi vertices and the +vertices in each Voronoi region. +The 'Fv' option prints each ridge of the Voronoi diagram. +The first or zero'th vertex +indicates the infinity vertex at infinity. Its coordinates are +qh_INFINITE (\-10.101). It indicates unbounded Voronoi regions +and degenerate Delaunay triangles. +.PP +.TP +Input/Output options: +.TP +f +Print out all facets and all fields of each facet. +.TP +G +Output the hull in Geomview format. For imprecise hulls, +Geomview displays the inner and outer hull. Geomview can also +display points, ridges, vertices, coplanar points, and +facet intersections. See below for a list of options. + +For Delaunay triangulations, 'G' displays the +corresponding paraboloid. For halfspace intersection, 'G' displays the +dual polytope. +.TP +i +Output the incident vertices for each facet. +Qhull prints the number of facets followed by the +vertices of each facet. One facet is printed per line. The numbers +are the 0\[hy]relative indices of the corresponding input points. +The facets +are oriented. + +In 4d and higher, +Qhull triangulates non\[hy]simplicial facets. Each apex (the first vertex) is +a created point that corresponds to the facet's centrum. Its index is greater +than the indices of the input points. Each base +corresponds to a simplicial ridge between two facets. +To print the vertices without triangulation, use option 'Fv'. +.TP +m +Output the hull in Mathematica format. Qhull writes a Mathematica file for 2\[hy]d and 3\[hy]d +convex hulls and for 2\[hy]d Delaunay triangulations. Qhull produces a list of objects +that you can assign to a variable in Mathematica, for example: +"list= << ". If the object is 2\[hy]d, it can be +visualized by "Show[Graphics[list]] ". For 3\[hy]d objects the command is +"Show[Graphics3D[list]]". +.TP +n +Output the normal equation for each facet. +Qhull prints the dimension (plus one), the number of facets, +and the normals for each facet. The facet's offset follows its +normal coefficients. +.TP +o +Output the facets in OFF file format. +Qhull prints the dimension, number of points, number +of facets, and number of ridges. Then it prints the coordinates of +the input points and the vertices for each facet. Each facet is on +a separate line. The first number is the number of vertices. The +remainder are the indices of the corresponding points. The vertices are +oriented in 2\[hy]d, 3\[hy]d, and in simplicial facets. + +For 2\[hy]d Voronoi diagrams, +the vertices are sorted by adjacency, but not oriented. In 3\[hy]d and higher, +the Voronoi vertices are sorted by index. +See the 'v' option for more information. +.TP +p +Output the coordinates of each vertex point. +Qhull prints the dimension, the number of points, +and the coordinates for each vertex. +With the 'Gc' and 'Gi' options, it also prints coplanar +and interior points. For Voronoi diagrams, it prints the coordinates +of each Voronoi vertex. +.TP +s +Print a summary to stderr. If no output options +are specified at all, a summary goes to stdout. The summary lists +the number of input points, the dimension, the number of vertices +in the convex hull, the number of facets in the convex hull, the +number of good facets (if 'Pg'), and statistics. + +The last two statistics (if needed) measure the maximum distance +from a point or vertex to a +facet. The number in parenthesis (e.g., 2.1x) is the ratio between the +maximum distance and the worst\[hy]case distance due to merging +two simplicial facets. +.PP +.TP +Precision options +.TP +An +Maximum angle given as a cosine. If the angle between a pair of facet +normals +is greater than n, Qhull merges one of the facets into a neighbor. +If 'n' is negative, Qhull tests angles after adding +each point to the hull (pre\[hy]merging). +If 'n' is positive, Qhull tests angles after +constructing the hull (post\[hy]merging). +Both pre\[hy] and post\[hy]merging can be defined. + +Option 'C0' or 'C\-0' is set if the corresponding 'Cn' or 'C\-n' +is not set. If 'Qx' +is set, then 'A\-n' and 'C\-n' are checked after the hull is constructed +and before 'An' and 'Cn' are checked. +.TP +Cn +Centrum radius. +If a centrum is less than n below a neighboring facet, Qhull merges one +of the facets. +If 'n' is negative or '\-0', Qhull tests and merges facets after adding +each point to the hull. This is called "pre\[hy]merging". If 'n' is positive, +Qhull tests for convexity after constructing the hull ("post\[hy]merging"). +Both pre\[hy] and post\[hy]merging can be defined. + +For 5\[hy]d and higher, 'Qx' should be used +instead of 'C\-n'. Otherwise, most or all facets may be merged +together. +.TP +En +Maximum roundoff error for distance computations. +.TP +Rn +Randomly perturb distance computations up to +/\- n * max_coord. +This option perturbs every distance, hyperplane, and angle computation. +To use time as the random number seed, use option 'QR\-1'. +.TP +Vn +Minimum distance for a facet to be visible. +A facet is visible if the distance from the point to the +facet is greater than 'Vn'. + +Without merging, the default value for 'Vn' is the round\[hy]off error ('En'). +With merging, the default value is the pre\[hy]merge centrum ('C\-n') in 2\[hy]d or +3\[hy]d, or three times that in other dimensions. If the outside width +is specified ('Wn'), the maximum, default value for 'Vn' is 'Wn'. +.TP +Un +Maximum distance below a facet for a point to be coplanar to the facet. The +default value is 'Vn'. +.TP +Wn +Minimum outside width of the hull. Points are added to the convex hull +only if they are clearly outside of a facet. A point is outside of a +facet if its distance to the facet is greater than 'Wn'. The normal +value for 'Wn' is 'En'. If the user specifies pre\[hy]merging and +does not set 'Wn', than 'Wn' is set +to the premerge 'Cn' and maxcoord*(1\-An). +.PP +.TP +Additional input/output formats +.TP +Fa +Print area for each facet. +For Delaunay triangulations, the area is the area of the triangle. +For Voronoi diagrams, the area is the area of the dual facet. +Use 'PAn' for printing the n largest facets, and option 'PFn' for +printing facets larger than 'n'. + +The area for non\[hy]simplicial facets is the sum of the +areas for each ridge to the centrum. Vertices far below +the facet's hyperplane are ignored. +The reported area may be significantly less than the actual area. +.TP +FA +Compute the total area and volume for option 's'. It is an approximation +for non\[hy]simplicial facets (see 'Fa'). +.TP +Fc +Print coplanar points for each facet. The output starts with the +number of facets. Then each facet is printed one per line. Each line +is the number of coplanar points followed by the point ids. +Option 'Qi' includes the interior points. Each coplanar point (interior point) is +assigned to the facet it is furthest above (resp., least below). +.TP +FC +Print centrums for each facet. The output starts with the +dimension followed by the number of facets. +Then each facet centrum is printed, one per line. +.TP +Fd +Read input in cdd format with homogeneous points. +The input starts with comments. The first comment is reported in +the summary. +Data starts after a "begin" line. The next line is the number of points +followed by the dimension+1 and "real" or "integer". Then the points +are listed with a leading "1" or "1.0". The data ends with an "end" line. + +For halfspaces ('Fd Hn,n,...'), the input format is the same. Each halfspace +starts with its offset. The sign of the offset is the opposite of Qhull's +convention. +.TP +FD +Print normals ('n', 'Fo', 'Fi') or points ('p') in cdd format. +The first line is the command line that invoked Qhull. +Data starts with a "begin" line. The next line is the number of normals or points +followed by the dimension+1 and "real". Then the normals or points +are listed with the offset before the coefficients. The offset for points is +1.0. The offset for normals has the opposite sign. +The data ends with an "end" line. +.TP +FF +Print facets (as in 'f') without printing the ridges. +.TP +Fi +Print inner planes for each facet. The inner plane is below all vertices. +.TP +Fi +Print separating hyperplanes for bounded, inner regions of the Voronoi +diagram. The first line is the number +of ridges. Then each hyperplane is printed, one per line. A line starts +with the number of indices and floats. The first pair lists +adjacent input +sites, the next d floats are the normalized coefficients for the hyperplane, +and the last float is the offset. The hyperplane is oriented toward 'QVn' +(if defined), or the first input site of the pair. Use 'Tv' to +verify that the hyperplanes are perpendicular bisectors. Use 'Fo' for +unbounded regions, and 'Fv' for the corresponding Voronoi vertices. +.TP +FI +Print facet identifiers. +.TP +Fm +Print number of merges for each facet. At most 511 merges are reported for +a facet. See 'PMn' for printing the facets with the most merges. +.TP +FM +Output the hull in Maple format. Qhull writes a Maple +file for 2\[hy]d and 3\[hy]d +convex hulls and for 2\[hy]d Delaunay triangulations. Qhull produces a '.mpl' +file for displaying with display3d(). +.TP +Fn +Print neighbors for each facet. The output starts with the number of facets. +Then each facet is printed one per line. Each line +is the number of neighbors followed by an index for each neighbor. The indices +match the other facet output formats. + +A negative index indicates an unprinted +facet due to printing only good facets ('Pg'). It is the negation of the facet's +ID (option 'FI'). +For example, negative indices are used for facets +"at infinity" in the Delaunay triangulation. +.TP +FN +Print vertex neighbors or coplanar facet for each point. +The first line is the number +of points. Then each point is printed, one per line. If the +point is coplanar, the line is "1" followed by the facet's ID. +If the point is +not a selected vertex, the line is "0". +Otherwise, each line is the number of +neighbors followed by the corresponding facet indices (see 'Fn'). +.TP +Fo +Print outer planes for each facet in the same format as 'n'. +The outer plane is above all points. +.TP +Fo +Print separating hyperplanes for unbounded, outer regions of the Voronoi +diagram. The first line is the number +of ridges. Then each hyperplane is printed, one per line. A line starts +with the number of indices and floats. The first pair lists +adjacent input +sites, the next d floats are the normalized coefficients for the hyperplane, +and the last float is the offset. The hyperplane is oriented toward 'QVn' +(if defined), or the first input site of the pair. Use 'Tv' to +verify that the hyperplanes are perpendicular bisectors. Use 'Fi' for +bounded regions, and 'Fv' for the corresponding Voronoi vertices. +.TP +FO +List all options to stderr, including the default values. Additional 'FO's +are printed to stdout. +.TP +Fp +Print points for halfspace intersections (option 'Hn,n,...'). Each +intersection corresponds to a facet of the dual polytope. +The "infinity" point [\-10.101,\-10.101,...] +indicates an unbounded intersection. +.TP +FP +For each coplanar point ('Qc') print the point ID of the nearest vertex, +the point ID, the facet ID, and the distance. +.TP +FQ +Print command used for qhull and input. +.TP +Fs +Print a summary. The first line consists of the number of integers ("8"), +followed by the dimension, the number of points, the number of vertices, +the number of facets, the number of vertices selected for output, the +number of facets selected for output, the number of coplanar points selected +for output, number of simplicial, unmerged facets in output + +The second line consists of the number of reals ("2"), +followed by the maxmimum offset to an outer plane and and minimum offset to +an inner plane. Roundoff is included. Later +versions of Qhull may produce additional integers or reals. +.TP +FS +Print the size of the hull. The first line consists of the number of integers ("0"). +The second line consists of the number of reals ("2"), +followed by the total facet area, and the total volume. +Later +versions of Qhull may produce additional integers or reals. + +The total volume measures the volume +of the intersection of the halfspaces defined by each facet. +Both area and volume are +approximations for non\[hy]simplicial facets. See option 'Fa'. +.TP +Ft +Print a triangulation with added points for non\[hy]simplicial +facets. The first line is the dimension and the second line is the +number of points and the number of facets. The points follow, one +per line, then the facets follow as a list of point indices. With option 'Qz', the +points include the point\[hy]at\[hy]infinity. +.TP +Fv +Print vertices for each facet. The first line is the number +of facets. Then each facet is printed, one per line. Each line is +the number of vertices followed by the corresponding point ids. Vertices +are listed in the order they were added to the hull (the last one is first). +.TP +Fv +Print all ridges of a Voronoi diagram. The first line is the number +of ridges. Then each ridge is printed, one per line. A line starts +with the number of indices. The first pair lists adjacent input +sites, the remaining indices list Voronoi vertices. Vertex '0' indicates +the vertex\[hy]at\[hy]infinity (i.e., an unbounded ray). In 3\[hy]d, the vertices +are listed in order. See 'Fi' and 'Fo' for separating hyperplanes. +.TP +FV +Print average vertex. The average vertex is a feasible point +for halfspace intersection. +.TP +Fx +List extreme points (vertices) of the convex hull. The first line +is the number of points. The other lines give the indices of the +corresponding points. The first point is '0'. In 2\[hy]d, the points +occur in counter\[hy]clockwise order; otherwise they occur in input order. +For Delaunay triangulations, 'Fx' lists the extreme points of the +input sites. The points are unordered. +.PP +.TP +Geomview options +.TP +G +Produce a file for viewing with Geomview. Without other options, +Qhull displays edges in 2\[hy]d, outer planes in 3\[hy]d, and ridges in 4\[hy]d. +A ridge can be +explicit or implicit. An explicit ridge is a dim\-1 dimensional simplex +between two facets. +In 4\[hy]d, the explicit ridges are triangles. +When displaying a ridge in 4\[hy]d, Qhull projects the ridge's vertices to +one of its facets' hyperplanes. +Use 'Gh' to +project ridges to the intersection of both hyperplanes. +.TP +Ga +Display all input points as dots. +.TP +Gc +Display the centrum for each facet in 3\[hy]d. The centrum is defined by a +green radius sitting on a blue plane. The plane corresponds to the +facet's hyperplane. +The radius is defined by 'C\-n' or 'Cn'. +.TP +GDn +Drop dimension n in 3\[hy]d or 4\[hy]d. The result is a 2\[hy]d or 3\[hy]d object. +.TP +Gh +Display hyperplane intersections in 3\[hy]d and 4\[hy]d. In 3\[hy]d, the +intersection is a black line. It lies on two neighboring hyperplanes +(c.f., the blue squares associated with centrums ('Gc')). In 4\[hy]d, +the ridges are projected to the intersection of both hyperplanes. +.TP +Gi +Display inner planes in 2\[hy]d and 3\[hy]d. The inner plane of a facet +is below all of its vertices. It is parallel to the facet's hyperplane. +The inner plane's color is the opposite (1\-r,1\-g,1\-b) of the outer +plane. Its edges are determined by the vertices. +.TP +Gn +Do not display inner or outer planes. By default, +Geomview displays the precise plane (no merging) or both +inner and output planes (merging). Under merging, Geomview does +not display the inner plane if the +the difference between inner and outer is too small. +.TP +Go +Display outer planes in 2\[hy]d and 3\[hy]d. The outer plane of a facet +is above all input points. It is parallel to the facet's hyperplane. +Its color is determined by the facet's normal, and its +edges are determined by the vertices. +.TP +Gp +Display coplanar points and vertices as radii. A radius defines a ball +which corresponds to the imprecision of the point. The imprecision is +the maximum of the roundoff error, the centrum radius, and maxcoord * +(1\-An). It is at least 1/20'th of the maximum coordinate, +and ignores post\[hy]merging if pre\[hy]merging is done. +.TP +Gr +Display ridges in 3\[hy]d. A ridge connects the two vertices that are shared +by neighboring facets. Ridges are always displayed in 4\[hy]d. +.TP +Gt +A 3\[hy]d Delaunay triangulation looks like a convex hull with interior +facets. Option 'Gt' removes the outside ridges to reveal the outermost +facets. It automatically sets options 'Gr' and 'GDn'. +.TP +Gv +Display vertices as spheres. The radius of the sphere corresponds to +the imprecision of the data. See 'Gp' for determining the radius. +.PP +.TP +Print options +.TP +PAn +Only the n largest facets are marked good for printing. +Unless 'PG' is set, 'Pg' is automatically set. +.TP +Pdk:n +Drop facet from output if normal[k] <= n. The option 'Pdk' uses the +default value of 0 for n. +.TP +PDk:n +Drop facet from output if normal[k] >= n. The option 'PDk' uses the +default value of 0 for n. +.TP +PFn +Only facets with area at least 'n' are marked good for printing. +Unless 'PG' is set, 'Pg' is automatically set. +.TP +Pg +Print only good facets. A good facet is either visible from a point +(the 'QGn' option) or includes a point (the 'QVn' option). It also meets the +requirements of 'Pdk' and 'PDk' options. Option 'Pg' is automatically +set for options 'PAn' and 'PFn'. +.TP +PG +Print neighbors of good facets. +.TP +PMn +Only the n facets with the most merges are marked good for printing. +Unless 'PG' is set, 'Pg' is automatically set. +.TP +Po +Force output despite precision problems. Verify ('Tv') does not check +coplanar points. +Flipped facets are reported and concave facets are counted. +If 'Po' is used, points are not +partitioned into flipped facets and a flipped facet is always visible +to a point. +Also, if an error occurs before the completion of Qhull and tracing is +not active, 'Po' outputs a neighborhood of the erroneous facets +(if any). +.TP +Pp +Do not report precision problems. +.PP +.TP +Qhull control options +.TP +Qbk:0Bk:0 +Drop dimension k from the input points. This allows the user to +take convex hulls of sub\[hy]dimensional objects. It happens before +the Delaunay and Voronoi transformation. +.TP +QbB +Scale the input points to fit the unit cube. After scaling, the lower +bound will be \-0.5 and the upper bound +0.5 in all dimensions. +For Delaunay and +Voronoi diagrams, scaling happens after projection to the paraboloid. +Under precise +arithmetic, scaling does not change the topology of the convex hull. +.TP +Qbb +Scale the last coordinate to [0, m] where m is the maximum absolute +value of the other coordinates. For Delaunay and +Voronoi diagrams, scaling happens after projection to the paraboloid. +It reduces roundoff error for inputs with integer coordinates. +Under precise +arithmetic, scaling does not change the topology of the convex hull. +.TP +Qbk:n +Scale the k'th coordinate of the input points. After scaling, the lower +bound of the input points will be n. 'Qbk' scales to \-0.5. +.TP +QBk:n +Scale the k'th coordinate of the input points. After scaling, the upper +bound will be n. 'QBk' scales to +0.5. +.TP +Qc +Keep coplanar points with the nearest facet. Output +formats 'p', 'f', 'Gp', 'Fc', 'FN', and 'FP' will print the points. +.TP +Qf +Partition points to the furthest outside facet. +.TP +Qg +Only build good facets. With the 'Qg' option, Qhull will only build +those facets that it needs to determine the good facets in the output. +See 'QGn', 'QVn', and 'PdD' for defining good facets, and 'Pg' and 'PG' +for printing good facets and their neighbors. +.TP +QGn +A facet is good (see 'Qg' and 'Pg') if it is visible from point n. If n < 0, a facet is +good if it is not visible from point n. Point n is not added to the +hull (unless 'TCn' or 'TPn'). +With rbox, use the 'Pn,m,r' option to define your point; it +will be point 0 (QG0). +.TP +Qi +Keep interior points with the nearest facet. +Output formats 'p', 'f', 'Gp', 'FN', 'FP', and 'Fc' will print the points. +.TP +QJn +Joggle each input coordinate by adding a random number in [\-n,n]. If a +precision error occurs, then qhull increases n and tries again. It does +not increase n beyond a certain value, and it stops after a certain number +of attempts [see user.h]. Option 'QJ' +selects a default value for n. The output will be simplicial. For +Delaunay triangulations, 'QJn' sets 'Qbb' to scale the last coordinate +(not if 'Qbk:n' or 'QBk:n' is set). +\'QJn' is deprecated for Voronoi diagrams. See also 'Qt'. +.TP +Qm +Only process points that would otherwise increase max_outside. Other +points are treated as coplanar or interior points. +.TP +Qr +Process random outside points instead of furthest ones. This makes +Qhull equivalent to the randomized incremental algorithms. CPU time +is not reported since the randomization is inefficient. +.TP +QRn +Randomly rotate the input points. If n=0, use time as the random number seed. +If n>0, use n as the random number seed. If n=\-1, don't rotate but use +time as the random number seed. For Delaunay triangulations ('d' and 'v'), +rotate about the last axis. +.TP +Qs +Search all points for the initial simplex. +.TP +Qt +Triangulated output. Triangulate all non\[hy]simplicial facets. +\'Qt' is deprecated for Voronoi diagrams. See also 'Qt'. +.TP +Qv +Test vertex neighbors for convexity after post\[hy]merging. +To use the 'Qv' option, you also need to set a merge option +(e.g., 'Qx' or 'C\-0'). +.TP +QVn +A good facet (see 'Qg' and 'Pg') includes point n. If n<0, then a good facet does not +include point n. The point is either in the initial simplex or it +is the first point added to the hull. Option 'QVn' may not be used with merging. +.TP +Qx +Perform exact merges while building the hull. The "exact" merges +are merging a point into a coplanar facet (defined by 'Vn', 'Un', +and 'C\-n'), merging concave facets, merging duplicate ridges, and +merging flipped facets. Coplanar merges and angle coplanar merges ('A\-n') +are not performed. Concavity testing is delayed until a merge occurs. + +After +the hull is built, all coplanar merges are performed (defined by 'C\-n' +and 'A\-n'), then post\[hy]merges are performed +(defined by 'Cn' and 'An'). +.TP +Qz +Add a point "at infinity" that is above the paraboloid for Delaunay triangulations +and Voronoi diagrams. This reduces precision problems and allows the triangulation +of cospherical points. +.PP +.TP +Qhull experiments and speedups +.TP +Q0 +Turn off pre\[hy]merging as a default option. +With 'Q0'/'Qx' and without explicit pre\[hy]merge options, Qhull +ignores precision issues while constructing the convex hull. This +may lead to precision errors. If so, a descriptive warning is +generated. +.TP +Q1 +With 'Q1', Qhull sorts merges by type (coplanar, angle coplanar, concave) +instead of by angle. +.TP +Q2 +With 'Q2', Qhull merges all facets at once instead of using +independent sets of merges and then retesting. +.TP +Q3 +With 'Q3', Qhull does not remove redundant vertices. +.TP +Q4 +With 'Q4', Qhull avoids merges of an old facet into a new facet. +.TP +Q5 +With 'Q5', Qhull does not correct outer planes at the end. The +maximum outer plane is used instead. +.TP +Q6 +With 'Q6', Qhull does not pre\[hy]merge concave or coplanar facets. +.TP +Q7 +With 'Q7', Qhull processes facets in depth\[hy]first order instead of +breadth\[hy]first order. +.TP +Q8 +With 'Q8' and merging, Qhull does not retain near\[hy]interior points for adjusting +outer planes. 'Qc' will probably retain +all points that adjust outer planes. +.TP +Q9 +With 'Q9', Qhull processes the furthest of all outside sets at each iteration. +.TP +Q10 +With 'Q10', Qhull does not use special processing for narrow distributions. +.TP +Q11 +With 'Q11', Qhull copies normals and recompute centrums for tricoplanar facets. +.TP +Q12 +With 'Q12', Qhull does not report a very wide merge due to a duplicated ridge with nearly coincident vertices +.PP +.TP +Trace options +.TP +Tn +Trace at level n. Qhull includes full execution tracing. 'T\-1' +traces events. 'T1' traces +the overall execution of the program. 'T2' and 'T3' trace overall +execution and geometric and topological events. 'T4' traces the +algorithm. 'T5' includes information about memory allocation and +Gaussian elimination. +.TP +Ta +Annotate output with codes that identify the +corresponding qh_fprintf() statement. +.TP +Tc +Check frequently during execution. This will catch most inconsistency +errors. +.TP +TCn +Stop Qhull after building the cone of new facets for point n. The +output for 'f' includes the cone and the old hull. +See also 'TVn'. +.TP +TFn +Report progress whenever more than n facets are created +During post\[hy]merging, 'TFn' +reports progress after more than n/2 merges. +.TP +TI file +Input data from 'file'. The filename may not include spaces or +quotes. +.TP +TO file +Output results to 'file'. The name may be enclosed in single +quotes. +.TP +TPn +Turn on tracing when point n is added to the hull. Trace +partitions of point n. If used with TWn, turn off +tracing after adding point n to the hull. +.TP +TRn +Rerun qhull n times. Usually used with 'QJn' to determine the +probability that a given joggle will fail. +.TP +Ts +Collect statistics and print to stderr at the end of execution. +.TP +Tv +Verify the convex hull. This checks the topological structure, facet +convexity, and point inclusion. +If precision problems occurred, facet convexity is tested whether or +not 'Tv' is selected. +Option 'Tv' does not check point inclusion if forcing output with 'Po', +or if 'Q5' is set. + +For point inclusion testing, Qhull verifies that all points are below +all outer planes (facet\->maxoutside). Point inclusion is exhaustive +if merging or if the facet\[hy]point product is small enough; +otherwise Qhull verifies each point with a directed +search (qh_findbest). + +Point inclusion testing occurs after producing output. It prints +a message to stderr unless option 'Pp' is used. This +allows the user to interrupt Qhull without changing the output. +.TP +TVn +Stop Qhull after adding point n. If n < 0, stop Qhull before adding +point n. Output shows the hull at this time. See also 'TCn' +.TP +TMn +Turn on tracing at n'th merge. +.TP +TWn +Trace merge facets when the width is greater than n. +.TP +Tz +Redirect stderr to stdout. +.PP +.SH BUGS +Please report bugs to Brad Barber at qhull_bug@qhull.org. + +If Qhull does not compile, it is due to an incompatibility between your +system and ours. The first thing to check is that your compiler is +ANSI standard. If it is, check the man page for the best options, or +find someone to help you. If you locate the cause of your problem, +please send email since it might help others. + +If Qhull compiles but crashes on the test case (rbox D4), there's +still incompatibility between your system and ours. Typically it's +been due to mem.c and memory alignment. You can use qh_NOmem in mem.h +to turn off memory management. Please let us know if you figure out +how to fix these problems. + +If you do find a problem, try to simplify it before reporting the +error. Try different size inputs to locate the smallest one that +causes an error. You're welcome to hunt through the code using the +execution trace as a guide. This is especially true if you're +incorporating Qhull into your own program. + +When you do report an error, please attach a data set to the +end of your message. This allows us to see the error for ourselves. +Qhull is maintained part\[hy]time. +.PP +.SH E\[hy]MAIL +Please send correspondence to qhull@qhull.org and report bugs to +qhull_bug@qhull.org. Let us know how you use Qhull. If you +mention it in a paper, please send the reference and an abstract. + +If you would like to get Qhull announcements (e.g., a new version) +and news (any bugs that get fixed, etc.), let us know and we will add you to +our mailing list. If you would like to communicate with other +Qhull users, we will add you to the qhull_users alias. +For Internet news about geometric algorithms and convex hulls, look at +comp.graphics.algorithms and sci.math.num\-analysis + +.SH SEE ALSO +rbox(1) + +Barber, C. B., D.P. Dobkin, and H.T. Huhdanpaa, +"The Quickhull Algorithm for Convex Hulls," ACM +Trans. on Mathematical Software, 22(4):469\[en]483, Dec. 1996. +http://portal.acm.org/citation.cfm?doid=235815.235821 +http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.117.405 + +Clarkson, K.L., K. Mehlhorn, and R. Seidel, "Four results on randomized +incremental construction," Computational Geometry: Theory and Applications, +vol. 3, p. 185\[en]211, 1993. + +Preparata, F. and M. Shamos, Computational +Geometry, Springer\[hy]Verlag, New York, 1985. + +.PP +.SH AUTHORS +.nf + C. Bradford Barber Hannu Huhdanpaa + bradb@shore.net hannu@qhull.org + + .fi + +.SH ACKNOWLEDGEMENTS + +A special thanks to Albert Marden, Victor Milenkovic, the Geometry Center, +Harvard University, and Endocardial Solutions, Inc. for supporting this work. + +Qhull 1.0 and 2.0 were developed under National Science Foundation +grants NSF/DMS\[hy]8920161 and NSF\[hy]CCR\[hy]91\[hy]15793 750\[hy]7504. David Dobkin +guided the original work at Princeton University. +If you find it useful, please let us know. + +The Geometry Center is supported by grant DMS\[hy]8920161 from the National +Science Foundation, by grant DOE/DE\[hy]FG02\[hy]92ER25137 from the Department +of Energy, by the University of Minnesota, and by Minnesota Technology, Inc. + +Qhull is available from http://www.qhull.org diff --git a/xs/src/qhull/html/qhull.txt b/xs/src/qhull/html/qhull.txt new file mode 100644 index 0000000000..03753547e9 --- /dev/null +++ b/xs/src/qhull/html/qhull.txt @@ -0,0 +1,1263 @@ + + + +qhull(1) qhull(1) + + +NAME + qhull - convex hull, Delaunay triangulation, Voronoi dia- + gram, halfspace intersection about a point, hull volume, facet area + +SYNOPSIS + qhull- compute convex hulls and related structures + input (stdin): dimension, #points, point coordinates + first comment (non-numeric) is listed in the summary + halfspace: use dim plus one with offsets after coefficients + + options (qh-quick.htm): + d - Delaunay triangulation by lifting points to a paraboloid + v - Voronoi diagram via the Delaunay triangulation + H1,1 - Halfspace intersection about [1,1,0,...] + d Qu - Furthest-site Delaunay triangulation (upper convex hull) + v Qu - Furthest-site Voronoi diagram + QJ - Joggle the input to avoid precision problems + . - concise list of all options + - - one-line description of all options + + Output options (subset): + FA - compute total area and volume + Fx - extreme points (convex hull vertices) + G - Geomview output (2-d, 3-d and 4-d) + Fp - halfspace intersection coordinates + m - Mathematica output (2-d and 3-d) + n - normals with offsets + o - OFF file format (if Voronoi, outputs regions) + TO file- output results to file, may be enclosed in single quotes + f - print all fields of all facets + s - summary of results (default) + Tv - verify result: structure, convexity, and point inclusion + p - vertex coordinates + i - vertices incident to each facet + + example: + rbox 1000 s | qhull Tv s FA + + - html manual: index.htm + - installation: README.txt + - see also: COPYING.txt, REGISTER.txt, Changes.txt + - WWW: + - GIT: + - mirror: + - news: + - Geomview: + - news group: + - FAQ: + - email: qhull@qhull.org + - bug reports: qhull_bug@qhull.org + + + + +Geometry Center 2003/12/30 1 + + + + + +qhull(1) qhull(1) + + + The sections are: + - INTRODUCTION + - DESCRIPTION, a description of Qhull + - IMPRECISION, how Qhull handles imprecision + - OPTIONS + - Input and output options + - Additional input/output formats + - Precision options + - Geomview options + - Print options + - Qhull options + - Trace options + - BUGS + - E-MAIL + - SEE ALSO + - AUTHORS + - ACKNOWLEGEMENTS + + This man page briefly describes all Qhull options. Please + report any mismatches with Qhull's html manual (qh- + man.htm). + + + +INTRODUCTION + Qhull is a general dimension code for computing convex + hulls, Delaunay triangulations, Voronoi diagram, furthest- + site Voronoi diagram, furthest-site Delaunay triangula- + tions, and halfspace intersections about a point. It + implements the Quickhull algorithm for computing the con- + vex hull. Qhull handles round-off errors from floating + point arithmetic. It can approximate a convex hull. + + The program includes options for hull volume, facet area, + partial hulls, input transformations, randomization, trac- + ing, multiple output formats, and execution statistics. + The program can be called from within your application. + You can view the results in 2-d, 3-d and 4-d with + Geomview. + + +DESCRIPTION + The format of input is the following: first line contains + the dimension, second line contains the number of input + points, and point coordinates follow. The dimension and + number of points can be reversed. Comments and line + breaks are ignored. A comment starts with a non-numeric + character and continues to the end of line. The first + comment is reported in summaries and statistics. Error + reporting is better if there is one point per line. + + The default printout option is a short summary. There are + many other output formats. + + + + +Geometry Center 2003/12/30 2 + + + + + +qhull(1) qhull(1) + + + Qhull implements the Quickhull algorithm for convex hull. + This algorithm combines the 2-d Quickhull algorithm with + the n-d beneath-beyond algorithm [c.f., Preparata & Shamos + '85]. It is similar to the randomized algorithms of + Clarkson and others [Clarkson et al. '93]. The main + advantages of Quickhull are output sensitive performance, + reduced space requirements, and automatic handling of pre- + cision problems. + + The data structure produced by Qhull consists of vertices, + ridges, and facets. A vertex is a point of the input set. + A ridge is a set of d vertices and two neighboring facets. + For example in 3-d, a ridge is an edge of the polyhedron. + A facet is a set of ridges, a set of neighboring facets, a + set of incident vertices, and a hyperplane equation. For + simplicial facets, the ridges are defined by the vertices + and neighboring facets. When Qhull merges two facets, it + produces a non-simplicial facet. A non-simplicial facet + has more than d neighbors and may share more than one + ridge with a neighbor. + + +IMPRECISION + Since Qhull uses floating point arithmetic, roundoff error + may occur for each calculation. This causes problems for + most geometric algorithms. + + Qhull automatically sets option 'C-0' in 2-d, 3-d, and + 4-d, or option 'Qx' in 5-d and higher. These options han- + dle precision problems by merging facets. Alternatively, + use option 'QJ' to joggle the input. + + With 'C-0', Qhull merges non-convex facets while con- + structing the hull. The remaining facets are clearly con- + vex. With 'Qx', Qhull merges coplanar horizon facets, + flipped facets, concave facets and duplicated ridges. It + merges coplanar facets after constructing the hull. With + 'Qx', coplanar points may be missed, but it appears to be + unlikely. + + To guarantee triangular output, joggle the input with + option 'QJ'. Facet merging will not occur. + +OPTIONS + To get a list of the most important options, execute + 'qhull' by itself. To get a complete list of options, + execute 'qhull -'. To get a complete, concise list of + options, execute 'qhull .'. + + Options can be in any order. Capitalized options take an + argument (except 'PG' and 'F' options). Single letters + are used for output formats and precision constants. The + other options are grouped into menus for other output for- + mats ('F'), Geomview output ('G'), printing ('P'), Qhull + + + +Geometry Center 2003/12/30 3 + + + + + +qhull(1) qhull(1) + + + control ('Q'), and tracing ('T'). + + Main options: + + default + Compute the convex hull of the input points. + Report a summary of the result. + + d Compute the Delaunay triangulation by lifting the + input points to a paraboloid. The 'o' option + prints the input points and facets. The 'QJ' + option guarantees triangular output. The 'Ft' + option prints a triangulation. It adds points (the + centrums) to non-simplicial facets. + + v Compute the Voronoi diagram from the Delaunay tri- + angulation. The 'p' option prints the Voronoi ver- + tices. The 'o' option prints the Voronoi vertices + and the vertices in each Voronoi region. It lists + regions in site id order. The 'Fv' option prints + each ridge of the Voronoi diagram. The first or + zero'th vertex indicates the infinity vertex. Its + coordinates are qh_INFINITE (-10.101). It indi- + cates unbounded Voronoi regions or degenerate + Delaunay triangles. + + Hn,n,... + Compute halfspace intersection about [n,n,0,...]. + The input is a set of halfspaces defined in the + same format as 'n', 'Fo', and 'Fi'. Use 'Fp' to + print the intersection points. Use 'Fv' to list + the intersection points for each halfspace. The + other output formats display the dual convex hull. + + The point [n,n,n,...] is a feasible point for the + halfspaces, i.e., a point that is inside all of the + halfspaces (Hx+b <= 0). The default coordinate + value is 0. + + The input may start with a feasible point. If so, + use 'H' by itself. The input starts with a feasi- + ble point when the first number is the dimension, + the second number is "1", and the coordinates com- + plete a line. The 'FV' option produces a feasible + point for a convex hull. + + d Qu Compute the furthest-site Delaunay triangulation + from the upper convex hull. The 'o' option prints + the input points and facets. The 'QJ' option guar- + antees triangular otuput. You can also use facets. + + v Qu Compute the furthest-site Voronoi diagram. The 'p' + option prints the Voronoi vertices. The 'o' option + prints the Voronoi vertices and the vertices in + + + +Geometry Center 2003/12/30 4 + + + + + +qhull(1) qhull(1) + + + each Voronoi region. The 'Fv' option prints each + ridge of the Voronoi diagram. The first or zero'th + vertex indicates the infinity vertex at infinity. + Its coordinates are qh_INFINITE (-10.101). It + indicates unbounded Voronoi regions and degenerate + Delaunay triangles. + + Qt Triangulated output. + + + Input/Output options: + + f Print out all facets and all fields of each facet. + + G Output the hull in Geomview format. For imprecise + hulls, Geomview displays the inner and outer hull. + Geomview can also display points, ridges, vertices, + coplanar points, and facet intersections. See + below for a list of options. + + For Delaunay triangulations, 'G' displays the cor- + responding paraboloid. For halfspace intersection, + 'G' displays the dual polytope. + + i Output the incident vertices for each facet. Qhull + prints the number of facets followed by the ver- + tices of each facet. One facet is printed per + line. The numbers are the 0-relative indices of + the corresponding input points. The facets are + oriented. + + In 4-d and higher, Qhull triangulates non-simpli- + cial facets. Each apex (the first vertex) is a + created point that corresponds to the facet's cen- + trum. Its index is greater than the indices of the + input points. Each base corresponds to a simpli- + cial ridge between two facets. To print the ver- + tices without triangulation, use option 'Fv'. + + m Output the hull in Mathematica format. Qhull + writes a Mathematica file for 2-d and 3-d convex + hulls and for 2-d Delaunay triangulations. Qhull + produces a list of objects that you can assign to a + variable in Mathematica, for example: "list= << + ". If the object is 2-d, it can be + visualized by "Show[Graphics[list]] ". For 3-d + objects the command is "Show[Graphics3D[list]]". + + n Output the normal equation for each facet. Qhull + prints the dimension (plus one), the number of + facets, and the normals for each facet. The + facet's offset follows its normal coefficients. + + o Output the facets in OFF file format. Qhull prints + the dimension, number of points, number of facets, + and number of ridges. Then it prints the + + + +Geometry Center 2003/12/30 5 + + + + + +qhull(1) qhull(1) + + + coordinates of the input points and the vertices + for each facet. Each facet is on a separate line. + The first number is the number of vertices. The + remainder are the indices of the corresponding + points. The vertices are oriented in 2-d, 3-d, and + in simplicial facets. + + For 2-d Voronoi diagrams, the vertices are sorted + by adjacency, but not oriented. In 3-d and higher, + the Voronoi vertices are sorted by index. See the + 'v' option for more information. + + p Output the coordinates of each vertex point. Qhull + prints the dimension, the number of points, and the + coordinates for each vertex. With the 'Gc' and + 'Gi' options, it also prints coplanar and interior + points. For Voronoi diagrams, it prints the coor- + dinates of each Voronoi vertex. + + s Print a summary to stderr. If no output options + are specified at all, a summary goes to stdout. + The summary lists the number of input points, the + dimension, the number of vertices in the convex + hull, the number of facets in the convex hull, the + number of good facets (if 'Pg'), and statistics. + + The last two statistics (if needed) measure the + maximum distance from a point or vertex to a facet. + The number in parenthesis (e.g., 2.1x) is the ratio + between the maximum distance and the worst-case + distance due to merging two simplicial facets. + + + Precision options + + An Maximum angle given as a cosine. If the angle + between a pair of facet normals is greater than n, Qhull + merges one of the facets into a neighbor. If 'n' + is negative, Qhull tests angles after adding each + point to the hull (pre-merging). If 'n' is posi- + tive, Qhull tests angles after constructing the + hull (post-merging). Both pre- and post-merging + can be defined. + + Option 'C0' or 'C-0' is set if the corresponding + 'Cn' or 'C-n' is not set. If 'Qx' is set, then 'A- + n' and 'C-n' are checked after the hull is con- + structed and before 'An' and 'Cn' are checked. + + Cn Centrum radius. If a centrum is less than n below + a neighboring facet, Qhull merges one of the + facets. If 'n' is negative or '-0', Qhull tests + and merges facets after adding each point to the + hull. This is called "pre-merging". If 'n' is + + + +Geometry Center 2003/12/30 6 + + + + + +qhull(1) qhull(1) + + + positive, Qhull tests for convexity after con- + structing the hull ("post-merging"). Both pre- and + post-merging can be defined. + + For 5-d and higher, 'Qx' should be used instead of + 'C-n'. Otherwise, most or all facets may be merged + together. + + En Maximum roundoff error for distance computations. + + Rn Randomly perturb distance computations up to +/- n + * max_coord. This option perturbs every distance, + hyperplane, and angle computation. To use time as + the random number seed, use option 'QR-1'. + + Vn Minimum distance for a facet to be visible. A + facet is visible if the distance from the point to + the facet is greater than 'Vn'. + + Without merging, the default value for 'Vn' is the + round-off error ('En'). With merging, the default + value is the pre-merge centrum ('C-n') in 2-d or + 3--d, or three times that in other dimensions. If + the outside width is specified ('Wn'), the maximum, + default value for 'Vn' is 'Wn'. + + Un Maximum distance below a facet for a point to be + coplanar to the facet. The default value is 'Vn'. + + Wn Minimum outside width of the hull. Points are + added to the convex hull only if they are clearly + outside of a facet. A point is outside of a facet + if its distance to the facet is greater than 'Wn'. + The normal value for 'Wn' is 'En'. If the user + specifies pre-merging and does not set 'Wn', than + 'Wn' is set to the premerge 'Cn' and maxco- + ord*(1-An). + + + Additional input/output formats + + Fa Print area for each facet. For Delaunay triangula- + tions, the area is the area of the triangle. For + Voronoi diagrams, the area is the area of the dual + facet. Use 'PAn' for printing the n largest + facets, and option 'PFn' for printing facets larger + than 'n'. + + The area for non-simplicial facets is the sum of + the areas for each ridge to the centrum. Vertices + far below the facet's hyperplane are ignored. The + reported area may be significantly less than the + actual area. + + + + +Geometry Center 2003/12/30 7 + + + + + +qhull(1) qhull(1) + + + FA Compute the total area and volume for option 's'. + It is an approximation for non-simplicial facets + (see 'Fa'). + + Fc Print coplanar points for each facet. The output + starts with the number of facets. Then each facet + is printed one per line. Each line is the number + of coplanar points followed by the point ids. + Option 'Qi' includes the interior points. Each + coplanar point (interior point) is assigned to the + facet it is furthest above (resp., least below). + + FC Print centrums for each facet. The output starts + with the dimension followed by the number of + facets. Then each facet centrum is printed, one + per line. + + Fd Read input in cdd format with homogeneous points. + The input starts with comments. The first comment + is reported in the summary. Data starts after a + "begin" line. The next line is the number of + points followed by the dimension+1 and "real" or + "integer". Then the points are listed with a + leading "1" or "1.0". The data ends with an "end" + line. + + For halfspaces ('Fd Hn,n,...'), the input format is + the same. Each halfspace starts with its offset. + The sign of the offset is the opposite of Qhull's + convention. + + FD Print normals ('n', 'Fo', 'Fi') or points ('p') in + cdd format. The first line is the command line + that invoked Qhull. Data starts with a "begin" + line. The next line is the number of normals or + points followed by the dimension+1 and "real". + Then the normals or points are listed with the + offset before the coefficients. The offset for + points is 1.0. The offset for normals has the + opposite sign. The data ends with an "end" line. + + FF Print facets (as in 'f') without printing the + ridges. + + Fi Print inner planes for each facet. The inner plane + is below all vertices. + + Fi Print separating hyperplanes for bounded, inner + regions of the Voronoi diagram. The first line is + the number of ridges. Then each hyperplane is + printed, one per line. A line starts with the num- + ber of indices and floats. The first pair lists + adjacent input sites, the next d floats are the + normalized coefficients for the hyperplane, and the + + + +Geometry Center 2003/12/30 8 + + + + + +qhull(1) qhull(1) + + + last float is the offset. The hyperplane is ori- + ented toward verify that the hyperplanes are per- + pendicular bisectors. Use 'Fo' for unbounded + regions, and 'Fv' for the corresponding Voronoi + vertices. + + FI Print facet identifiers. + + Fm Print number of merges for each facet. At most 511 + merges are reported for a facet. See 'PMn' for + printing the facets with the most merges. + + FM Output the hull in Maple format. See 'm' + + Fn Print neighbors for each facet. The output starts + with the number of facets. Then each facet is + printed one per line. Each line is the number of + neighbors followed by an index for each neighbor. + The indices match the other facet output formats. + + A negative index indicates an unprinted facet due + to printing only good facets ('Pg'). It is the + negation of the facet's id (option 'FI'). For + example, negative indices are used for facets "at + infinity" in the Delaunay triangulation. + + FN Print vertex neighbors or coplanar facet for each + point. The first line is the number of points. + Then each point is printed, one per line. If the + point is coplanar, the line is "1" followed by the + facet's id. If the point is not a selected vertex, + the line is "0". Otherwise, each line is the num- + ber of neighbors followed by the corresponding + facet indices (see 'Fn'). + + Fo Print outer planes for each facet in the same for- + mat as 'n'. The outer plane is above all points. + + Fo Print separating hyperplanes for unbounded, outer + regions of the Voronoi diagram. The first line is + the number of ridges. Then each hyperplane is + printed, one per line. A line starts with the num- + ber of indices and floats. The first pair lists + adjacent input sites, the next d floats are the + normalized coefficients for the hyperplane, and the + last float is the offset. The hyperplane is ori- + ented toward verify that the hyperplanes are per- + pendicular bisectors. Use 'Fi' for bounded + regions, and 'Fv' for the corresponding Voronoi + vertices. + + FO List all options to stderr, including the default + values. Additional 'FO's are printed to stdout. + + Fp Print points for halfspace intersections (option + 'Hn,n,...'). Each intersection corresponds to a + + + +Geometry Center 2003/12/30 9 + + + +qhull(1) qhull(1) + + + facet of the dual polytope. The "infinity" point + [-10.101,-10.101,...] indicates an unbounded + intersection. + + FP For each coplanar point ('Qc') print the point id + of the nearest vertex, the point id, the facet id, + and the distance. + + FQ Print command used for qhull and input. + + Fs Print a summary. The first line consists of the + number of integers ("7"), followed by the dimen- + sion, the number of points, the number of vertices, + the number of facets, the number of vertices + selected for output, the number of facets selected + for output, the number of coplanar points selected + for output. + + The second line consists of the number of reals + ("2"), followed by the maxmimum offset to an outer + plane and and minimum offset to an inner plane. + Roundoff is included. Later versions of Qhull may + produce additional integers or reals. + + FS Print the size of the hull. The first line con- + sists of the number of integers ("0"). The second + line consists of the number of reals ("2"), fol- + lowed by the total facet area, and the total vol- + ume. Later versions of Qhull may produce addi- + tional integers or reals. + + The total volume measures the volume of the inter- + section of the halfspaces defined by each facet. + Both area and volume are approximations for non- + simplicial facets. See option 'Fa'. + + Ft Print a triangulation with added points for non- + simplicial facets. The first line is the dimension + and the second line is the number of points and the + number of facets. The points follow, one per line, + then the facets follow as a list of point indices. + With option points include the point-at-infinity. + + Fv Print vertices for each facet. The first line is + the number of facets. Then each facet is printed, + one per line. Each line is the number of vertices + followed by the corresponding point ids. Vertices + are listed in the order they were added to the hull + (the last one is first). + + Fv Print all ridges of a Voronoi diagram. The first + line is the number of ridges. Then each ridge is + printed, one per line. A line starts with the num- + ber of indices. The first pair lists adjacent + + + +Geometry Center 2003/12/30 10 + + + + + +qhull(1) qhull(1) + + + input sites, the remaining indices list Voronoi + vertices. Vertex '0' indicates the vertex-at- + infinity (i.e., an unbounded ray). In 3-d, the + vertices are listed in order. See 'Fi' and 'Fo' + for separating hyperplanes. + + FV Print average vertex. The average vertex is a fea- + sible point for halfspace intersection. + + Fx List extreme points (vertices) of the convex hull. + The first line is the number of points. The other + lines give the indices of the corresponding points. + The first point is '0'. In 2-d, the points occur + in counter-clockwise order; otherwise they occur in + input order. For Delaunay triangulations, 'Fx' + lists the extreme points of the input sites. The + points are unordered. + + + Geomview options + + G Produce a file for viewing with Geomview. Without + other options, Qhull displays edges in 2-d, outer + planes in 3-d, and ridges in 4-d. A ridge can be + explicit or implicit. An explicit ridge is a dim-1 + dimensional simplex between two facets. In 4-d, + the explicit ridges are triangles. When displaying + a ridge in 4-d, Qhull projects the ridge's vertices + to one of its facets' hyperplanes. Use 'Gh' to + project ridges to the intersection of both hyper- + planes. + + Ga Display all input points as dots. + + Gc Display the centrum for each facet in 3-d. The + centrum is defined by a green radius sitting on a + blue plane. The plane corresponds to the facet's + hyperplane. The radius is defined by 'C-n' or + 'Cn'. + + GDn Drop dimension n in 3-d or 4-d. The result is a + 2-d or 3-d object. + + Gh Display hyperplane intersections in 3-d and 4-d. + In 3-d, the intersection is a black line. It lies + on two neighboring hyperplanes (c.f., the blue + squares associated with centrums ('Gc')). In 4-d, + the ridges are projected to the intersection of + both hyperplanes. + + Gi Display inner planes in 2-d and 3-d. The inner + plane of a facet is below all of its vertices. It + is parallel to the facet's hyperplane. The inner + plane's color is the opposite (1-r,1-g,1-b) of the + + + +Geometry Center 2003/12/30 11 + + + + + +qhull(1) qhull(1) + + + outer plane. Its edges are determined by the ver- + tices. + + Gn Do not display inner or outer planes. By default, + Geomview displays the precise plane (no merging) or + both inner and output planes (merging). Under + merging, Geomview does not display the inner plane + if the the difference between inner and outer is + too small. + + Go Display outer planes in 2-d and 3-d. The outer + plane of a facet is above all input points. It is + parallel to the facet's hyperplane. Its color is + determined by the facet's normal, and its edges are + determined by the vertices. + + Gp Display coplanar points and vertices as radii. A + radius defines a ball which corresponds to the + imprecision of the point. The imprecision is the + maximum of the roundoff error, the centrum radius, + and maxcoord * (1-An). It is at least 1/20'th of + the maximum coordinate, and ignores post-merging if + pre-merging is done. + + Gr Display ridges in 3-d. A ridge connects the two + vertices that are shared by neighboring facets. + Ridges are always displayed in 4-d. + + Gt A 3-d Delaunay triangulation looks like a convex + hull with interior facets. Option 'Gt' removes the + outside ridges to reveal the outermost facets. It + automatically sets options 'Gr' and 'GDn'. + + Gv Display vertices as spheres. The radius of the + sphere corresponds to the imprecision of the data. + See 'Gp' for determining the radius. + + + Print options + + PAn Only the n largest facets are marked good for + printing. Unless 'PG' is set, 'Pg' is automati- + cally set. + + Pdk:n Drop facet from output if normal[k] <= n. The + option 'Pdk' uses the default value of 0 for n. + + PDk:n Drop facet from output if normal[k] >= n. The + option 'PDk' uses the default value of 0 for n. + + PFn Only facets with area at least 'n' are marked good + for printing. Unless 'PG' is set, 'Pg' is automat- + ically set. + + + + +Geometry Center 2003/12/30 12 + + + + + +qhull(1) qhull(1) + + + Pg Print only good facets. A good facet is either + visible from a point (the 'QGn' option) or includes + a point (the 'QVn' option). It also meets the + requirements of 'Pdk' and 'PDk' options. Option + 'Pg' is automatically set for options 'PAn' and + 'PFn'. + + PG Print neighbors of good facets. + + PMn Only the n facets with the most merges are marked + good for printing. Unless 'PG' is set, 'Pg' is + automatically set. + + Po Force output despite precision problems. Verify ('Tv') does not check + coplanar points. Flipped facets are reported and + concave facets are counted. If 'Po' is used, + points are not partitioned into flipped facets and + a flipped facet is always visible to a point. + Also, if an error occurs before the completion of + Qhull and tracing is not active, 'Po' outputs a + neighborhood of the erroneous facets (if any). + + Pp Do not report precision problems. + + + Qhull control options + + Qbk:0Bk:0 + Drop dimension k from the input points. This + allows the user to take convex hulls of sub-dimen- + sional objects. It happens before the Delaunay and + Voronoi transformation. + + QbB Scale the input points to fit the unit cube. After + scaling, the lower bound will be -0.5 and the upper + bound +0.5 in all dimensions. For Delaunay and + Voronoi diagrams, scaling happens after projection + to the paraboloid. Under precise arithmetic, scal- + ing does not change the topology of the convex + hull. + + Qbb Scale the last coordinate to [0, m] where m is the + maximum absolute value of the other coordinates. + For Delaunay and Voronoi diagrams, scaling happens + after projection to the paraboloid. It reduces + roundoff error for inputs with integer coordinates. + Under precise arithmetic, scaling does not change + the topology of the convex hull. + + Qbk:n Scale the k'th coordinate of the input points. + After scaling, the lower bound of the input points + will be n. 'Qbk' scales to -0.5. + + + +Geometry Center 2003/12/30 13 + + + + + +qhull(1) qhull(1) + + + QBk:n Scale the k'th coordinate of the input points. + After scaling, the upper bound will be n. 'QBk' + scales to +0.5. + + Qc Keep coplanar points with the nearest facet. Out- + put formats 'p', 'f', 'Gp', 'Fc', 'FN', and 'FP' + will print the points. + + Qf Partition points to the furthest outside facet. + + Qg Only build good facets. With the 'Qg' option, + Qhull will only build those facets that it needs to + determine the good facets in the output. See + 'QGn', 'QVn', and 'PdD' for defining good facets, + and 'Pg' and 'PG' for printing good facets and + their neighbors. + + QGn A facet is good (see 'Qg' and 'Pg') if it is visi- + ble from point n. If n < 0, a facet is good if it + is not visible from point n. Point n is not added + to the hull (unless 'TCn' or 'TPn'). With rbox, + use the 'Pn,m,r' option to define your point; it + will be point 0 (QG0). + + Qi Keep interior points with the nearest facet. Out- + put formats 'p', 'f', 'Gp', 'FN', 'FP', and 'Fc' + will print the points. + + QJn Joggle each input coordinate by adding a random + number in [-n,n]. If a precision error occurs, + then qhull increases n and tries again. It does + not increase n beyond a certain value, and it stops + after a certain number of attempts [see user.h]. + Option 'QJ' selects a default value for n. The + output will be simplicial. For Delaunay triangula- + tions, 'QJn' sets 'Qbb' to scale the last coordi- + nate (not if 'Qbk:n' or 'QBk:n' is set). 'QJn' is + deprecated for Voronoi diagrams. See also 'Qt'. + + Qm Only process points that would otherwise increase + max_outside. Other points are treated as coplanar + or interior points. + + Qr Process random outside points instead of furthest + ones. This makes Qhull equivalent to the random- + ized incremental algorithms. CPU time is not + reported since the randomization is inefficient. + + QRn Randomly rotate the input points. If n=0, use time + as the random number seed. If n>0, use n as the + random number seed. If n=-1, don't rotate but use + time as the random number seed. For Delaunay tri- + angulations ('d' and 'v'), rotate about the last + axis. + + + + +Geometry Center 2003/12/30 14 + + + + + +qhull(1) qhull(1) + + + Qs Search all points for the initial simplex. + + Qt Triangulated output. Triangulate non-simplicial + facets. 'Qt' is deprecated for Voronoi diagrams. + See also 'QJn' + + Qv Test vertex neighbors for convexity after post- + merging. To use the 'Qv' option, you also need to + set a merge option (e.g., 'Qx' or 'C-0'). + + QVn A good facet (see 'Qg' and 'Pg') includes point n. + If n<0, then a good facet does not include point n. + The point is either in the initial simplex or it is + the first point added to the hull. Option 'QVn' + may not be used with merging. + + Qx Perform exact merges while building the hull. The + "exact" merges are merging a point into a coplanar + facet (defined by 'Vn', 'Un', and 'C-n'), merging + concave facets, merging duplicate ridges, and merg- + ing flipped facets. Coplanar merges and angle + coplanar merges ('A-n') are not performed. Concav- + ity testing is delayed until a merge occurs. + + After the hull is built, all coplanar merges are + performed (defined by 'C-n' and 'A-n'), then post- + merges are performed (defined by 'Cn' and 'An'). + + Qz Add a point "at infinity" that is above the + paraboloid for Delaunay triangulations and Voronoi + diagrams. This reduces precision problems and + allows the triangulation of cospherical points. + + + Qhull experiments and speedups + + Q0 Turn off pre-merging as a default option. With + 'Q0'/'Qx' and without explicit pre-merge options, + Qhull ignores precision issues while constructing + the convex hull. This may lead to precision + errors. If so, a descriptive warning is generated. + + Q1 With 'Q1', Qhull sorts merges by type (coplanar, + angle coplanar, concave) instead of by angle. + + Q2 With 'Q2', Qhull merges all facets at once instead + of using independent sets of merges and then + retesting. + + Q3 With 'Q3', Qhull does not remove redundant ver- + tices. + + Q4 With 'Q4', Qhull avoids merges of an old facet into + a new facet. + + Q5 With 'Q5', Qhull does not correct outer planes at + the end. The maximum outer plane is used instead. + + + + +Geometry Center 2003/12/30 15 + + + + + +qhull(1) qhull(1) + + + Q6 With 'Q6', Qhull does not pre-merge concave or + coplanar facets. + + Q7 With 'Q7', Qhull processes facets in depth-first + order instead of breadth-first order. + + Q8 With 'Q8' and merging, Qhull does not retain near- + interior points for adjusting outer planes. 'Qc' + will probably retain all points that adjust outer + planes. + + Q9 With 'Q9', Qhull processes the furthest of all out- + side sets at each iteration. + + Q10 With 'Q10', Qhull does not use special processing + for narrow distributions. + + Q11 With 'Q11', Qhull copies normals and recomputes + centrums for tricoplanar facets. + + Q12 With 'Q12', Qhull does not report a very wide merge due + to a duplicated ridge with nearly coincident vertices + + Trace options + + Tn Trace at level n. Qhull includes full execution + tracing. 'T-1' traces events. 'T1' traces the + overall execution of the program. 'T2' and 'T3' + trace overall execution and geometric and topologi- + cal events. 'T4' traces the algorithm. 'T5' + includes information about memory allocation and + Gaussian elimination. + + Ta Annotate output with codes that identify the + corresponding qh_fprintf() statement. + + Tc Check frequently during execution. This will catch + most inconsistency errors. + + TCn Stop Qhull after building the cone of new facets + for point n. The output for 'f' includes the cone + and the old hull. See also 'TVn'. + + TFn Report progress whenever more than n facets are + created During post-merging, 'TFn' reports progress + after more than n/2 merges. + + TI file + Input data from 'file'. The filename may not include + spaces or quotes. + + TO file + Output results to 'file'. The name may be enclosed + in single quotes. + + TPn Turn on tracing when point n is added to the hull. + Trace partitions of point n. If used with TWn, turn off + tracing after adding point n to the hull. + + TRn Rerun qhull n times. Usually used with 'QJn' to + determine the probability that a given joggle will + fail. + + Ts Collect statistics and print to stderr at the end + of execution. + + Tv Verify the convex hull. This checks the topologi- + cal structure, facet convexity, and point inclu- + sion. If precision problems occurred, facet con- + vexity is tested whether or not 'Tv' is selected. + Option 'Tv' does not check point inclusion if + + + +Geometry Center 2003/12/30 16 + + + + + +qhull(1) qhull(1) + + + forcing output with 'Po', or if 'Q5' is set. + + For point inclusion testing, Qhull verifies that + all points are below all outer planes (facet->max- + outside). Point inclusion is exhaustive if merging + or if the facet-point product is small enough; oth- + erwise Qhull verifies each point with a directed + search (qh_findbest). + + Point inclusion testing occurs after producing out- + put. It prints a message to stderr unless option + 'Pp' is used. This allows the user to interrupt + Qhull without changing the output. + + TVn Stop Qhull after adding point n. If n < 0, stop + Qhull before adding point n. Output shows the hull + at this time. See also 'TCn' + + TMn Turn on tracing at n'th merge. + + TWn Trace merge facets when the width is greater than + n. + + Tz Redirect stderr to stdout. + + +BUGS + Please report bugs to Brad Barber at + qhull_bug@qhull.org. + + If Qhull does not compile, it is due to an incompatibility + between your system and ours. The first thing to check is + that your compiler is ANSI standard. If it is, check the + man page for the best options, or find someone to help + you. If you locate the cause of your problem, please send + email since it might help others. + + If Qhull compiles but crashes on the test case (rbox D4), + there's still incompatibility between your system and + ours. Typically it's been due to mem.c and memory align- + ment. You can use qh_NOmem in mem.h to turn off memory + management. Please let us know if you figure out how to + fix these problems. + + If you do find a problem, try to simplify it before + reporting the error. Try different size inputs to locate + the smallest one that causes an error. You're welcome to + hunt through the code using the execution trace as a + guide. This is especially true if you're incorporating + Qhull into your own program. + + When you do report an error, please attach a data set to + the end of your message. This allows us to see the error + for ourselves. Qhull is maintained part-time. + + + +Geometry Center 2003/12/30 17 + + + + + +qhull(1) qhull(1) + + +E-MAIL + Please send correspondence to qhull@qhull.org and + report bugs to qhull_bug@qhull.org. Let us know how + you use Qhull. If you mention it in a paper, please send + the reference and an abstract. + + If you would like to get Qhull announcements (e.g., a new + version) and news (any bugs that get fixed, etc.), let us + know and we will add you to our mailing list. If you + would like to communicate with other Qhull users, we will + add you to the qhull_users alias. For Internet news about + geometric algorithms and convex hulls, look at comp.graph- + ics.algorithms and sci.math.num-analysis + + +SEE ALSO + rbox(1) + + Barber, C. B., D.P. Dobkin, and H.T. Huhdanpaa, "The + Quickhull Algorithm for Convex Hulls," ACM Trans. on Math- + ematical Software, 22(4):469-483, Dec. 1996. + http://portal.acm.org/citation.cfm?doid=235815.235821 + http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.117.405 + + + Clarkson, K.L., K. Mehlhorn, and R. Seidel, "Four results + on randomized incremental construction," Computational + Geometry: Theory and Applications, vol. 3, p. 185-211, + 1993. + + Preparata, F. and M. Shamos, Computational Geometry, + Springer-Verlag, New York, 1985. + + + +AUTHORS + C. Bradford Barber Hannu Huhdanpaa + bradb@shore.net hannu@qhull.org + + + +ACKNOWLEDGEMENTS + A special thanks to Albert Marden, Victor Milenkovic, the + Geometry Center, Harvard University, and Endocardial Solu- + tions, Inc. for supporting this work. + + Qhull 1.0 and 2.0 were developed under National Science Foundation + grants NSF/DMS-8920161 and NSF-CCR-91-15793 750-7504. David Dobkin + + + +Geometry Center 2003/12/30 18 + + + + + +qhull(1) qhull(1) + + + guided the original work at Princeton University. If you find it + useful, please let us know. + + The Geometry Center was supported by grant DMS-8920161 from the National + Science Foundation, by grant DOE/DE-FG02-92ER25137 from the Department + of Energy, by the University of Minnesota, and by Minnesota Technology, Inc. + + Qhull is available from http://www.qhull.org + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Geometry Center 2003/12/30 19 + + diff --git a/xs/src/qhull/html/qvoron_f.htm b/xs/src/qhull/html/qvoron_f.htm new file mode 100644 index 0000000000..db538b5ab5 --- /dev/null +++ b/xs/src/qhull/html/qvoron_f.htm @@ -0,0 +1,396 @@ + + + + +qvoronoi Qu -- furthest-site Voronoi diagram + + + + +Up: +Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +
    + +

    [delaunay]qvoronoi Qu -- furthest-site Voronoi diagram

    + +

    The furthest-site Voronoi diagram is the furthest-neighbor map for a set of +points. Each region contains those points that are further +from one input site than any other input site. See the +survey article by Aurenhammer ['91] +and the brief introduction by O'Rourke ['94]. The furthest-site Voronoi diagram is the dual of the furthest-site Delaunay triangulation. +

    + +
    +
    +
    Example: rbox 10 D2 | qvoronoi Qu s + o TO + result
    +
    Compute the 2-d, furthest-site Voronoi diagram of 10 + random points. Write a summary to the console and the Voronoi + regions and vertices to 'result'. The first vertex of the + result indicates unbounded regions. Almost all regions + are unbounded.
    +
    + +
    +
    Example: rbox r y c G1 D2 | qvoronoi Qu + s + Fn TO + result
    +
    Compute the 2-d furthest-site Voronoi diagram of a square + and a small triangle. Write a summary to the console and the Voronoi + vertices for each input site to 'result'. + The origin is the only furthest-site Voronoi vertex. The + negative indices indicate vertices-at-infinity.
    +
    +
    + +

    +Qhull computes the furthest-site Voronoi diagram via the +furthest-site Delaunay triangulation. +Each furthest-site Voronoi vertex is the circumcenter of an upper +facet of the Delaunay triangulation. Each furthest-site Voronoi +region corresponds to a vertex of the Delaunay triangulation +(i.e., an input site).

    + +

    See Qhull FAQ - Delaunay and +Voronoi diagram questions.

    + +

    The 'qvonoroi' program is equivalent to +'qhull v Qbb' in 2-d to 3-d, and +'qhull v Qbb Qx' +in 4-d and higher. It disables the following Qhull +options: d n m v H U Qb +QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V Fa FA FC Fp FS Ft FV Gt Q0,etc. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    +

    »furthest-site qvoronoi synopsis

    +
    + +See qvoronoi synopsis. The same +program is used for both constructions. Use option 'Qu' +for furthest-site Voronoi diagrams. + + +
    +

    »furthest-site qvoronoi +input

    +
    +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qvoronoi Qu < data.txt), a pipe (e.g., rbox 10 | qvoronoi Qu), +or the 'TI' option (e.g., qvoronoi TI data.txt Qu). + +

    For example, this is a square containing four random points. +Its furthest-site Voronoi diagram has on vertex and four unbounded, +separating hyperplanes (i.e., the coordinate axes) +

    +

    +rbox c 4 D2 > data +
    +2 RBOX c 4 D2
    +8
    +-0.4999921736307369 -0.3684622117955817
    +0.2556053225468894 -0.0413498678629751
    +0.0327672376602583 -0.2810408135699488
    +-0.452955383763607 0.17886471718444
    +  -0.5   -0.5
    +  -0.5    0.5
    +   0.5   -0.5
    +   0.5    0.5
    +
    + +

    qvoronoi Qu s Fo < data +

    +
    +Furthest-site Voronoi vertices by the convex hull of 8 points in 3-d:
    +
    +  Number of Voronoi regions: 8
    +  Number of Voronoi vertices: 1
    +  Number of non-simplicial Voronoi vertices: 1
    +
    +Statistics for: RBOX c 4 D2 | QVORONOI Qu s Fo
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 20
    +  Number of facets in hull: 11
    +  Number of distance tests for qhull: 34
    +  Number of merged facets: 1
    +  Number of distance tests for merging: 107
    +  CPU seconds to compute hull (after input):  0
    +
    +4
    +5 4 5      0      1      0
    +5 4 6      1      0      0
    +5 5 7      1      0      0
    +5 6 7      0      1      0
    +
    +
    + +
    +

    » furthest-site qvoronoi +outputs

    +
    + +

    These options control the output of furthest-site Voronoi diagrams.

    +
    + +
    +
     
    +
    furthest-site Voronoi vertices
    +
    p
    +
    print the coordinates of the furthest-site Voronoi vertices. The first line + is the dimension. The second line is the number of vertices. Each + remaining line is a furthest-site Voronoi vertex. The points-in-square example + has one furthest-site Voronoi vertex at the origin.
    +
    Fn
    +
    list the neighboring furthest-site Voronoi vertices for each furthest-site Voronoi + vertex. The first line is the number of Voronoi vertices. Each + remaining line starts with the number of neighboring vertices. Negative indices (e.g., -1) indicate vertices + outside of the Voronoi diagram. In the points-in-square example, the + Voronoi vertex at the origin has four neighbors-at-infinity.
    +
    FN
    +
    list the furthest-site Voronoi vertices for each furthest-site Voronoi region. The first line is + the number of Voronoi regions. Each remaining line starts with the + number of Voronoi vertices. Negative indices (e.g., -1) indicate vertices + outside of the Voronoi diagram. + In the points-in-square example, all regions share the Voronoi vertex + at the origin.
    + +
     
    +
     
    +
    furthest-site Voronoi regions
    +
    o
    +
    print the furthest-site Voronoi regions in OFF format. The first line is the + dimension. The second line is the number of vertices, the number + of input sites, and "1". The third line represents the vertex-at-infinity. + Its coordinates are "-10.101". The next lines are the coordinates + of the furthest-site Voronoi vertices. Each remaining line starts with the number + of Voronoi vertices in a Voronoi region. In 2-d, the vertices are +listed in adjacency order (unoriented). In 3-d and higher, the +vertices are listed in numeric order. In the points-in-square + example, each unbounded region includes the Voronoi vertex at + the origin. Lines consisting of 0 indicate + interior input sites.
    +
    Fi
    +
    print separating hyperplanes for inner, bounded furthest-site Voronoi + regions. The first number is the number of separating + hyperplanes. Each remaining line starts with 3+dim. The + next two numbers are adjacent input sites. The next dim + numbers are the coefficients of the separating hyperplane. The + last number is its offset. The are no bounded, separating hyperplanes + for the points-in-square example.
    +
    Fo
    +
    print separating hyperplanes for outer, unbounded furthest-site Voronoi + regions. The first number is the number of separating + hyperplanes. Each remaining line starts with 3+dim. The + next two numbers are adjacent input sites on the convex hull. The + next dim + numbers are the coefficients of the separating hyperplane. The + last number is its offset. The points-in-square example has four + unbounded, separating hyperplanes.
    +
     
    +
     
    +
    Input sites
    +
    Fv
    +
    list ridges of furthest-site Voronoi vertices for pairs of input sites. The + first line is the number of ridges. Each remaining line starts with + two plus the number of Voronoi vertices in the ridge. The next + two numbers are two adjacent input sites. The remaining numbers list + the Voronoi vertices. As with option 'o', a 0 indicates + the vertex-at-infinity + and an unbounded, separating hyperplane. + The perpendicular bisector (separating hyperplane) + of the input sites is a flat through these vertices. + In the points-in-square example, the ridge for each edge of the square + is unbounded.
    +
     
    +
     
    +
    General
    +
    s
    +
    print summary of the furthest-site Voronoi diagram. Use 'Fs' for numeric data.
    +
    i
    +
    list input sites for each furthest-site Delaunay region. Use option 'Pp' + to avoid the warning. The first line is the number of regions. The + remaining lines list the input sites for each region. The regions are + oriented. In the points-in-square example, the square region has four + input sites. In 3-d and higher, report cospherical sites by adding extra points. +
    +
    G
    +
    Geomview output for 2-d furthest-site Voronoi diagrams.
    +
    +
    + +
    +

    » furthest-site qvoronoi +controls

    +
    + +

    These options provide additional control:

    +
    + +
    +
    Qu
    +
    must be used.
    +
    QVn
    +
    select furthest-site Voronoi vertices for input site n
    +
    Tv
    +
    verify result
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    TFn
    +
    report progress after constructing n facets
    +
    PDk:1
    +
    include upper and lower facets in the output. Set k + to the last dimension (e.g., 'PD2:1' for 2-d inputs).
    +
    f
    +
    facet dump. Print the data structure for each facet (i.e., + furthest-site Voronoi vertex).
    +
    + +
    +
    +

    » furthest-site qvoronoi +graphics

    +
    +

    In 2-d, Geomview output ('G') +displays a furthest-site Voronoi diagram with extra edges to +close the unbounded furthest-site Voronoi regions. All regions +will be unbounded. Since the points-in-box example has only +one furthest-site Voronoi vertex, the Geomview output is one +point.

    + +

    See the Delaunay and Voronoi +examples for a 2-d example. Turn off normalization (on +Geomview's 'obscure' menu) when comparing the furthest-site +Voronoi diagram with the corresponding Voronoi diagram.

    + +
    +

    »furthest-site qvoronoi +notes

    +
    + +

    See Voronoi notes.

    + +
    +

    »furthest-site qvoronoi conventions

    +
    + +

    The following terminology is used for furthest-site Voronoi +diagrams in Qhull. The underlying structure is a furthest-site +Delaunay triangulation from a convex hull in one higher +dimension. Upper facets of the Delaunay triangulation correspond +to vertices of the furthest-site Voronoi diagram. Vertices of the +furthest-site Delaunay triangulation correspond to input sites. +They also define regions of the furthest-site Voronoi diagram. +All vertices are extreme points of the input sites. See qconvex conventions, furthest-site delaunay +conventions, and Qhull's data structures.

    + +
      +
    • input site - a point in the input (one dimension + lower than a point on the convex hull)
    • +
    • point - a point has d+1 coordinates. The + last coordinate is the sum of the squares of the input + site's coordinates
    • +
    • vertex - a point on the upper facets of the + paraboloid. It corresponds to a unique input site.
    • +
    • furthest-site Delaunay facet - an upper facet of the + paraboloid. The last coefficient of its normal is + clearly positive.
    • +
    • furthest-site Voronoi vertex - the circumcenter + of a furthest-site Delaunay facet
    • +
    • furthest-site Voronoi region - the region of + Euclidean space further from an input site than any other + input site. Qhull lists the furthest-site Voronoi + vertices that define each furthest-site Voronoi region.
    • +
    • furthest-site Voronoi diagram - the graph of the + furthest-site Voronoi regions with the ridges (edges) + between the regions.
    • +
    • infinity vertex - the Voronoi vertex for + unbounded furthest-site Voronoi regions in 'o' output format. Its + coordinates are -10.101.
    • +
    • good facet - an furthest-site Voronoi vertex with + optional restrictions by 'QVn', + etc.
    • +
    + +
    +

    »furthest-site qvoronoi options

    +
    + +See qvoronoi options. The same +program is used for both constructions. Use option 'Qu' +for furthest-site Voronoi diagrams. +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qvoronoi.htm b/xs/src/qhull/html/qvoronoi.htm new file mode 100644 index 0000000000..6d81d48c15 --- /dev/null +++ b/xs/src/qhull/html/qvoronoi.htm @@ -0,0 +1,667 @@ + + + + +qvoronoi -- Voronoi diagram + + + + +Up: +Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +
    + +

    [voronoi]qvoronoi -- Voronoi diagram

    + +

    The Voronoi diagram is the nearest-neighbor map for a set of +points. Each region contains those points that are nearer +one input site than any other input site. It has many useful properties and applications. See the +survey article by Aurenhammer ['91] +and the detailed introduction by O'Rourke ['94]. The Voronoi diagram is the +dual of the Delaunay triangulation.

    + +
    +
    +
    Example: rbox 10 D3 | qvoronoi s + o TO + result
    +
    Compute the 3-d Voronoi diagram of 10 random points. Write a + summary to the console and the Voronoi vertices and + regions to 'result'. The first vertex of the result + indicates unbounded regions.
    + +
     
    +
    Example: rbox r y c G0.1 D2 | qvoronoi + s + o TO + result
    +
    Compute the 2-d Voronoi diagram of a triangle and a small + square. Write a + summary to the console and Voronoi vertices and regions + to 'result'. Report a single Voronoi vertex for + cocircular input sites. The first vertex of the result + indicates unbounded regions. The origin is the Voronoi + vertex for the square.
    + +
     
    +
    Example: rbox r y c G0.1 D2 | qvoronoi Fv + TO result
    +
    Compute the 2-d Voronoi diagram of a triangle and a small + square. Write a + summary to the console and the Voronoi ridges to + 'result'. Each ridge is the perpendicular bisector of a + pair of input sites. Vertex "0" indicates + unbounded ridges. Vertex "8" is the Voronoi + vertex for the square.
    + +
     
    +
    Example: rbox r y c G0.1 D2 | qvoronoi Fi
    +
    Print the bounded, separating hyperplanes for the 2-d Voronoi diagram of a + triangle and a small + square. Note the four hyperplanes (i.e., lines) for Voronoi vertex + "8". It is at the origin. +
    +
    +
    + +

    Qhull computes the Voronoi diagram via the Delaunay +triangulation. Each Voronoi +vertex is the circumcenter of a facet of the Delaunay +triangulation. Each Voronoi region corresponds to a vertex (i.e., input site) of the +Delaunay triangulation.

    + +

    Qhull outputs the Voronoi vertices for each Voronoi region. With +option 'Fv', +it lists all ridges of the Voronoi diagram with the corresponding +pairs of input sites. With +options 'Fi' and 'Fo', +it lists the bounded and unbounded separating hyperplanes. +You can also output a single Voronoi region +for further processing [see graphics].

    + +

    Use option 'Qz' if the input is circular, cospherical, or +nearly so. It improves precision by adding a point "at infinity," above the corresponding paraboloid. + +

    See Qhull FAQ - Delaunay and +Voronoi diagram questions.

    + +

    The 'qvonoroi' program is equivalent to +'qhull v Qbb' in 2-d to 3-d, and +'qhull v Qbb Qx' +in 4-d and higher. It disables the following Qhull +options: d n v Qbb QbB Qf Qg Qm +Qr QR Qv Qx Qz TR E V Fa FA FC FD FS Ft FV Gt Q0,etc. + +

    Copyright © 1995-2015 C.B. Barber

    + +

    Voronoi image by KOOK Architecture, Silvan Oesterle and Michael Knauss. + +


    +

    »qvoronoi synopsis

    + +
    +qvoronoi- compute the Voronoi diagram.
    +    input (stdin): dimension, number of points, point coordinates
    +    comments start with a non-numeric character
    +
    +options (qh-voron.htm):
    +    Qu   - compute furthest-site Voronoi diagram
    +    Tv   - verify result: structure, convexity, and in-circle test
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +output options (subset):
    +    s    - summary of results (default)
    +    p    - Voronoi vertices
    +    o    - OFF file format (dim, Voronoi vertices, and Voronoi regions)
    +    FN   - count and Voronoi vertices for each Voronoi region
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites
    +    Fi   - separating hyperplanes for bounded regions, 'Fo' for unbounded
    +    G    - Geomview output (2-d only)
    +    QVn  - Voronoi vertices for input point n, -n if not
    +    TO file- output results to file, may be enclosed in single quotes
    +
    +examples:
    +rbox c P0 D2 | qvoronoi s o         rbox c P0 D2 | qvoronoi Fi
    +rbox c P0 D2 | qvoronoi Fo          rbox c P0 D2 | qvoronoi Fv
    +rbox c P0 D2 | qvoronoi s Qu Fv     rbox c P0 D2 | qvoronoi Qu Fo
    +rbox c G1 d D2 | qvoronoi s p       rbox c P0 D2 | qvoronoi s Fv QV0
    +
    + +

    »qvoronoi input

    +
    +The input data on stdin consists of: +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qvoronoi < data.txt), a pipe (e.g., rbox 10 | qvoronoi), +or the 'TI' option (e.g., qvoronoi TI data.txt). + +

    For example, this is four cocircular points inside a square. Their Voronoi +diagram has nine vertices and eight regions. Notice the Voronoi vertex +at the origin, and the Voronoi vertices (on each axis) for the four +sides of the square. +

    +

    +rbox s 4 W0 c G1 D2 > data +
    +2 RBOX s 4 W0 c D2
    +8
    +-0.4941988586954018 -0.07594397977563715
    +-0.06448037284989526 0.4958248496365813
    +0.4911154367094632 0.09383830681375946
    +-0.348353580869097 -0.3586778257652367
    +    -1     -1
    +    -1      1
    +     1     -1
    +     1      1
    +
    + +

    qvoronoi s p < data +

    +
    +Voronoi diagram by the convex hull of 8 points in 3-d:
    +
    +  Number of Voronoi regions: 8
    +  Number of Voronoi vertices: 9
    +  Number of non-simplicial Voronoi vertices: 1
    +
    +Statistics for: RBOX s 4 W0 c D2 | QVORONOI s p
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 18
    +  Number of facets in hull: 10
    +  Number of distance tests for qhull: 33
    +  Number of merged facets: 2
    +  Number of distance tests for merging: 102
    +  CPU seconds to compute hull (after input): 0.094
    +
    +2
    +9
    +4.217546450968612e-17 1.735507986399734
    +-8.402566836762659e-17 -1.364368854147395
    +0.3447488772716865 -0.6395484723719818
    +1.719446929853986 2.136555906154247e-17
    +0.4967882915039657 0.68662371396699
    +-1.729928876283549 1.343733067524222e-17
    +-0.8906163241424728 -0.4594150543829102
    +-0.6656840313875723 0.5003013793414868
    +-7.318364664277155e-19 -1.188217818408333e-16
    +
    +
    + +
    +

    » qvoronoi +outputs

    +
    + +

    These options control the output of Voronoi diagrams.

    +
    + +
    +
     
    +
    Voronoi vertices
    +
    p
    +
    print the coordinates of the Voronoi vertices. The first line + is the dimension. The second line is the number of vertices. Each + remaining line is a Voronoi vertex.
    +
    Fn
    +
    list the neighboring Voronoi vertices for each Voronoi + vertex. The first line is the number of Voronoi vertices. Each + remaining line starts with the number of neighboring vertices. + Negative vertices (e.g., -1) indicate vertices + outside of the Voronoi diagram. + In the circle-in-box example, the + Voronoi vertex at the origin has four neighbors.
    +
    FN
    +
    list the Voronoi vertices for each Voronoi region. The first line is + the number of Voronoi regions. Each remaining line starts with the + number of Voronoi vertices. Negative indices (e.g., -1) indicate vertices + outside of the Voronoi diagram. + In the circle-in-box example, the four bounded regions are defined by four + Voronoi vertices.
    + +
     
    +
     
    +
    Voronoi regions
    +
    o
    +
    print the Voronoi regions in OFF format. The first line is the + dimension. The second line is the number of vertices, the number + of input sites, and "1". The third line represents the vertex-at-infinity. + Its coordinates are "-10.101". The next lines are the coordinates + of the Voronoi vertices. Each remaining line starts with the number + of Voronoi vertices in a Voronoi region. In 2-d, the vertices are +listed in adjacency order (unoriented). In 3-d and higher, the +vertices are listed in numeric order. In the circle-in-square + example, each bounded region includes the Voronoi vertex at + the origin. Lines consisting of 0 indicate + coplanar input sites or 'Qz'.
    +
    Fi
    +
    print separating hyperplanes for inner, bounded Voronoi + regions. The first number is the number of separating + hyperplanes. Each remaining line starts with 3+dim. The + next two numbers are adjacent input sites. The next dim + numbers are the coefficients of the separating hyperplane. The + last number is its offset. Use 'Tv' to verify that the +hyperplanes are perpendicular bisectors. It will list relevant +statistics to stderr.
    +
    Fo
    +
    print separating hyperplanes for outer, unbounded Voronoi + regions. The first number is the number of separating + hyperplanes. Each remaining line starts with 3+dim. The + next two numbers are adjacent input sites on the convex hull. The + next dim + numbers are the coefficients of the separating hyperplane. The + last number is its offset. Use 'Tv' to verify that the +hyperplanes are perpendicular bisectors. It will list relevant +statistics to stderr,
    +
     
    +
     
    +
    Input sites
    +
    Fv
    +
    list ridges of Voronoi vertices for pairs of input sites. The + first line is the number of ridges. Each remaining line starts with + two plus the number of Voronoi vertices in the ridge. The next + two numbers are two adjacent input sites. The remaining numbers list + the Voronoi vertices. As with option 'o', a 0 indicates + the vertex-at-infinity + and an unbounded, separating hyperplane. + The perpendicular bisector (separating hyperplane) + of the input sites is a flat through these vertices. + In the circle-in-square example, the ridge for each edge of the square + is unbounded.
    +
    Fc
    +
    list coincident input sites for each Voronoi vertex. + The first line is the number of vertices. The remaining lines start with + the number of coincident sites and deleted vertices. Deleted vertices + indicate highly degenerate input (see'Fs'). + A coincident site is assigned to one Voronoi + vertex. Do not use 'QJ' with 'Fc'; the joggle will separate + coincident sites.
    +
    FP
    +
    print coincident input sites with distance to + nearest site (i.e., vertex). The first line is the + number of coincident sites. Each remaining line starts with the point ID of + an input site, followed by the point ID of a coincident point, its vertex, and distance. + Includes deleted vertices which + indicate highly degenerate input (see'Fs'). + Do not use 'QJ' with 'FP'; the joggle will separate + coincident sites.
    +
     
    +
     
    +
    General
    +
    s
    +
    print summary of the Voronoi diagram. Use 'Fs' for numeric data.
    +
    i
    +
    list input sites for each Delaunay region. Use option 'Pp' + to avoid the warning. The first line is the number of regions. The + remaining lines list the input sites for each region. The regions are + oriented. In the circle-in-square example, the cocircular region has four + edges. In 3-d and higher, report cospherical sites by adding extra points. +
    +
    G
    +
    Geomview output for 2-d Voronoi diagrams.
    +
    +
    +
    +

    » qvoronoi +controls

    +
    + +

    These options provide additional control:

    +
    + +
    +
    Qu
    +
    compute the furthest-site Voronoi diagram.
    +
    QVn
    +
    select Voronoi vertices for input site n
    +
    Qz
    +
    add a point above the paraboloid to reduce precision + errors. Use it for nearly cocircular/cospherical input + (e.g., 'rbox c | qvoronoi Qz').
    +
    Tv
    +
    verify result
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    TFn
    +
    report progress after constructing n facets
    +
    PDk:1
    +
    include upper and lower facets in the output. Set k + to the last dimension (e.g., 'PD2:1' for 2-d inputs).
    +
    f
    +
    facet dump. Print the data structure for each facet (i.e., + Voronoi vertex).
    +
    + +
    +
    +

    » qvoronoi +graphics

    +
    + +

    In 2-d, Geomview output ('G') +displays a Voronoi diagram with extra edges to close the +unbounded Voronoi regions. To view the unbounded rays, enclose +the input points in a square.

    + +

    You can also view individual Voronoi regions in 3-d. To +view the Voronoi region for site 3 in Geomview, execute

    + +
    +

    qvoronoi <data QV3 p | qconvex s G >output

    +
    + +

    The qvoronoi command returns the Voronoi vertices +for input site 3. The qconvex command computes their convex hull. +This is the Voronoi region for input site 3. Its +hyperplane normals (qconvex 'n') are the same as the separating hyperplanes +from options 'Fi' +and 'Fo' (up to roundoff error). + +

    See the Delaunay and Voronoi +examples for 2-d and 3-d examples. Turn off normalization (on +Geomview's 'obscure' menu) when comparing the Voronoi diagram +with the corresponding Delaunay triangulation.

    + +
    +

    »qvoronoi +notes

    +
    + +

    You can simplify the Voronoi diagram by enclosing the input +sites in a large square or cube. This is particularly recommended +for cocircular or cospherical input data.

    + +

    See Voronoi graphics for computing +the convex hull of a Voronoi region.

    + +

    Voronoi diagrams do not include facets that are +coplanar with the convex hull of the input sites. A facet is +coplanar if the last coefficient of its normal is +nearly zero (see qh_ZEROdelaunay). + +

    Unbounded regions can be confusing. For example, 'rbox c | +qvoronoi Qz o' produces the Voronoi regions for the vertices +of a cube centered at the origin. All regions are unbounded. The +output is

    + +
    +
    3
    +2 9 1
    +-10.101 -10.101 -10.101
    +     0      0      0
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +0
    +
    +
    + +

    The first line is the dimension. The second line is the number +of vertices and the number of regions. There is one region per +input point plus a region for the point-at-infinity added by +option 'Qz'. The next two lines +lists the Voronoi vertices. The first vertex is the infinity +vertex. It is indicate by the coordinates -10.101. The +second vertex is the origin. The next nine lines list the +regions. Each region lists two vertices -- the infinity vertex +and the origin. The last line is "0" because no region +is associated with the point-at-infinity. A "0" would +also be listed for nearly incident input sites.

    + +

    To use option 'Fv', add an +interior point. For example,

    + +
    +
    +rbox c P0 | qvoronoi Fv
    +20
    +5 0 7 1 3 5
    +5 0 3 1 4 5
    +5 0 5 1 2 3
    +5 0 1 1 2 4
    +5 0 6 2 3 6
    +5 0 2 2 4 6
    +5 0 4 4 5 6
    +5 0 8 5 3 6
    +5 1 2 0 2 4
    +5 1 3 0 1 4
    +5 1 5 0 1 2
    +5 2 4 0 4 6
    +5 2 6 0 2 6
    +5 3 4 0 4 5
    +5 3 7 0 1 5
    +5 4 8 0 6 5
    +5 5 6 0 2 3
    +5 5 7 0 1 3
    +5 6 8 0 6 3
    +5 7 8 0 3 5
    +
    +
    + +

    The output consists of 20 ridges and each ridge lists a pair +of input sites and a triplet of Voronoi vertices. The first eight +ridges connect the origin ('P0'). The remainder list the edges of +the cube. Each edge generates an unbounded ray through the +midpoint. The corresponding separating planes ('Fo') follow each +pair of coordinate axes.

    + +

    Options 'Qt' (triangulated output) +and 'QJ' (joggled input) are deprecated. They may produce +unexpected results. If you use these options, cocircular and cospherical input sites will +produce duplicate or nearly duplicate Voronoi vertices. See also Merged facets or joggled input.

    + +
    +

    »qvoronoi conventions

    +
    + +

    The following terminology is used for Voronoi diagrams in +Qhull. The underlying structure is a Delaunay triangulation from +a convex hull in one higher dimension. Facets of the Delaunay +triangulation correspond to vertices of the Voronoi diagram. +Vertices of the Delaunay triangulation correspond to input sites. +They also correspond to regions of the Voronoi diagram. See convex hull conventions, Delaunay conventions, and +Qhull's data structures.

    +
    + +
      +
    • input site - a point in the input (one dimension + lower than a point on the convex hull)
    • +
    • point - a point has d+1 coordinates. The + last coordinate is the sum of the squares of the input + site's coordinates
    • +
    • coplanar point - a nearly incident + input site
    • +
    • vertex - a point on the paraboloid. It + corresponds to a unique input site.
    • +
    • point-at-infinity - a point added above the + paraboloid by option 'Qz'
    • +
    • Delaunay facet - a lower facet of the + paraboloid. The last coefficient of its normal is + clearly negative.
    • +
    • Voronoi vertex - the circumcenter of a Delaunay + facet
    • +
    • Voronoi region - the Voronoi vertices for an + input site. The region of Euclidean space nearest to an + input site.
    • +
    • Voronoi diagram - the graph of the Voronoi + regions. It includes the ridges (i.e., edges) between the + regions.
    • +
    • vertex-at-infinity - the Voronoi vertex that + indicates unbounded Voronoi regions in 'o' output format. Its + coordinates are -10.101.
    • +
    • good facet - a Voronoi vertex with optional + restrictions by 'QVn', etc.
    • +
    + +
    +
    +

    »qvoronoi options

    + +
    +qvoronoi- compute the Voronoi diagram
    +    http://www.qhull.org
    +
    +input (stdin):
    +    first lines: dimension and number of points (or vice-versa).
    +    other lines: point coordinates, best if one point per line
    +    comments:    start with a non-numeric character
    +
    +options:
    +    Qu   - compute furthest-site Voronoi diagram
    +
    +Qhull control options:
    +    QJn  - randomly joggle input in range [-n,n]
    +    Qs   - search all points for the initial simplex
    +    Qz   - add point-at-infinity to Voronoi diagram
    +    QGn  - Voronoi vertices if visible from point n, -n if not
    +    QVn  - Voronoi vertices for input point n, -n if not
    +
    +Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - statistics
    +    Tv   - verify result: structure, convexity, and in-circle test
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when point n added to hull
    +     TMn - turn on tracing at merge n
    +     TWn - trace merge facets when width > n
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)
    +     TCn - stop qhull after building cone for point n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Wn   - min facet width for non-coincident point (before roundoff)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    s    - summary to stderr
    +    p    - Voronoi vertices
    +    o    - OFF format (dim, Voronoi vertices, and Voronoi regions)
    +    i    - Delaunay regions (use 'Pp' to avoid warning)
    +    f    - facet dump
    +
    +More formats:
    +    Fc   - count plus coincident points (by Voronoi vertex)
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FD   - use cdd format for output (offset first)
    +    FF   - facet dump without ridges
    +    Fi   - separating hyperplanes for bounded Voronoi regions
    +    FI   - ID for each Voronoi vertex
    +    Fm   - merge count for each Voronoi vertex (511 max)
    +    Fn   - count plus neighboring Voronoi vertices for each Voronoi vertex
    +    FN   - count and Voronoi vertices for each Voronoi region
    +    Fo   - separating hyperplanes for unbounded Voronoi regions
    +    FO   - options and precision constants
    +    FP   - nearest point and distance for each coincident point
    +    FQ   - command used for qvoronoi
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,
    +                    for output: #Voronoi regions, #Voronoi vertices,
    +                                #coincident points, #non-simplicial regions
    +                    #real (2), max outer plane and min vertex
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)
    +
    +Geomview options (2-d only)
    +    Ga   - all points as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices as spheres
    +    Gi   - inner planes only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc   - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +
    +Print options:
    +    PAn  - keep n largest Voronoi vertices by 'area'
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)
    +    PDk:n - drop facet if normal[k] >= n
    +    Pg   - print good Voronoi vertices (needs 'QGn' or 'QVn')
    +    PFn  - keep Voronoi vertices whose 'area' is at least n
    +    PG   - print neighbors of good Voronoi vertices
    +    PMn  - keep n Voronoi vertices with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/rbox.htm b/xs/src/qhull/html/rbox.htm new file mode 100644 index 0000000000..9c28face56 --- /dev/null +++ b/xs/src/qhull/html/rbox.htm @@ -0,0 +1,277 @@ + + + + +rbox -- generate point distributions + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis • outputs +• examples • notes +• options +


    + +

    [CONE]rbox -- generate point distributions

    + +
    + rbox generates random or regular points according to the + options given, and outputs the points to stdout. The + points are generated in a cube, unless 's', 'x', or 'y' + are given. + +
    +

    »rbox synopsis

    +
    +rbox- generate various point distributions.  Default is random in cube.
    +
    +args (any order, space separated):
    +  3000    number of random points in cube, lens, spiral, sphere or grid
    +  D3      dimension 3-d
    +  c       add a unit cube to the output ('c G2.0' sets size)
    +  d       add a unit diamond to the output ('d G2.0' sets size)
    +  l       generate a regular 3-d spiral
    +  r       generate a regular polygon, ('r s Z1 G0.1' makes a cone)
    +  s       generate cospherical points
    +  x       generate random points in simplex, may use 'r' or 'Wn'
    +  y       same as 'x', plus simplex
    +  Cn,r,m  add n nearly coincident points within radius r of m points
    +  Pn,m,r  add point [n,m,r] first, pads with 0
    +
    +  Ln      lens distribution of radius n.  Also 's', 'r', 'G', 'W'.
    +  Mn,m,r  lattice (Mesh) rotated by [n,-m,0], [m,n,0], [0,0,r], ...
    +          '27 M1,0,1' is {0,1,2} x {0,1,2} x {0,1,2}.  Try 'M3,4 z'.
    +  W0.1    random distribution within 0.1 of the cube's or sphere's surface
    +  Z0.5 s  random points in a 0.5 disk projected to a sphere
    +  Z0.5 s G0.6 same as Z0.5 within a 0.6 gap
    +
    +  Bn      bounding box coordinates, default 0.5
    +  h       output as homogeneous coordinates for cdd
    +  n       remove command line from the first line of output
    +  On      offset coordinates by n
    +  t       use time as the random number seed (default is command line)
    +  tn      use n as the random number seed
    +  z       print integer coordinates, default 'Bn' is 1e+06
    +
    + +

    »rbox outputs

    +
    + +The format of the output is the following: first line contains + the dimension and a comment, second line contains the + number of points, and the following lines contain the points, + one point per line. Points are represented by their coordinate values. + +

    For example, rbox c 10 D2 generates +

    +
    +2 RBOX c 10 D2
    +14
    +-0.4999921736307369 -0.3684622117955817
    +0.2556053225468894 -0.0413498678629751
    +0.0327672376602583 -0.2810408135699488
    +-0.452955383763607 0.17886471718444
    +0.1792964061529342 0.4346928963760779
    +-0.1164979223315585 0.01941637230982666
    +0.3309653464993139 -0.4654278894564396
    +-0.4465383649305798 0.02970019358182344
    +0.1711493843897706 -0.4923018137852678
    +-0.1165843490665633 -0.433157762450313
    +  -0.5   -0.5
    +  -0.5    0.5
    +   0.5   -0.5
    +   0.5    0.5
    +
    + +
    + +
    +

    »rbox examples

    + +
    +       rbox 10
    +              10 random points in the unit cube centered  at  the
    +              origin.
    +
    +       rbox 10 s D2
    +              10 random points on a 2-d circle.
    +
    +       rbox 100 W0
    +              100 random points on the surface of a cube.
    +
    +       rbox 1000 s D4
    +              1000 random points on a 4-d sphere.
    +
    +       rbox c D5 O0.5
    +              a 5-d hypercube with one corner at the origin.
    +
    +       rbox d D10
    +              a 10-d diamond.
    +
    +       rbox x 1000 r W0
    +              100 random points on the surface of a fixed simplex
    +
    +       rbox y D12
    +              a 12-d simplex.
    +
    +       rbox l 10
    +              10 random points along a spiral
    +
    +       rbox l 10 r
    +              10 regular points  along  a  spiral  plus  two  end
    +              points
    +
    +       rbox 1000 L10000 D4 s
    +              1000 random points on the surface of a narrow lens.
    +
    +           rbox 1000 L100000 s G1e-6
    +                  1000 random points near the edge of a narrow lens
    +
    +       rbox c G2 d G3
    +              a cube with coordinates +2/-2 and  a  diamond  with
    +              coordinates +3/-3.
    +
    +       rbox 64 M3,4 z
    +              a  rotated,  {0,1,2,3} x {0,1,2,3} x {0,1,2,3} lat-
    +              tice (Mesh) of integer points.
    +
    +       rbox P0 P0 P0 P0 P0
    +              5 copies of the origin in 3-d.  Try 'rbox P0 P0  P0
    +              P0 P0 | qhull QJ'.
    +
    +       r 100 s Z1 G0.1
    +              two  cospherical  100-gons plus another cospherical
    +              point.
    +
    +       100 s Z1
    +              a cone of points.
    +
    +       100 s Z1e-7
    +              a narrow cone of points with many precision errors.
    +
    + +

    »rbox notes

    +
    +Some combinations of arguments generate odd results. + +
    +

    »rbox options

    + +
    +       n      number of points
    +
    +       Dn     dimension n-d (default 3-d)
    +
    +       Bn     bounding box coordinates (default 0.5)
    +
    +       l      spiral distribution, available only in 3-d
    +
    +       Ln     lens  distribution  of  radius n.  May be used with
    +              's', 'r', 'G', and 'W'.
    +
    +       Mn,m,r lattice  (Mesh)  rotated  by  {[n,-m,0],   [m,n,0],
    +              [0,0,r],  ...}.   Use  'Mm,n'  for a rigid rotation
    +              with r = sqrt(n^2+m^2).  'M1,0'  is  an  orthogonal
    +              lattice.   For  example,  '27  M1,0'  is  {0,1,2} x
    +              {0,1,2} x {0,1,2}.
    +
    +       s      cospherical points randomly generated in a cube and
    +              projected to the unit sphere
    +
    +       x      simplicial  distribution.   It  is fixed for option
    +              'r'.  May be used with 'W'.
    +
    +       y      simplicial distribution plus a simplex.   Both  'x'
    +              and 'y' generate the same points.
    +
    +       Wn     restrict  points  to distance n of the surface of a
    +              sphere or a cube
    +
    +       c      add a unit cube to the output
    +
    +       c Gm   add a cube with all combinations of +m  and  -m  to
    +              the output
    +
    +       d      add a unit diamond to the output.
    +
    +       d Gm   add a diamond made of 0, +m and -m to the output
    +
    +       Cn,r,m add n nearly coincident points within radius r of m points
    +
    +       Pn,m,r add point [n,m,r] to the output first.  Pad coordi-
    +              nates with 0.0.
    +
    +       n      Remove the command line from the first line of out-
    +              put.
    +
    +       On     offset the data by adding n to each coordinate.
    +
    +       t      use  time  in  seconds  as  the  random number seed
    +              (default is command line).
    +
    +       tn     set the random number seed to n.
    +
    +       z      generate integer coordinates.  Use 'Bn'  to  change
    +              the  range.   The  default  is 'B1e6' for six-digit
    +              coordinates.  In R^4, seven-digit coordinates  will
    +              overflow hyperplane normalization.
    +
    +       Zn s   restrict points to a disk about the z+ axis and the
    +              sphere (default Z1.0).  Includes the opposite pole.
    +              'Z1e-6'  generates  degenerate  points under single
    +              precision.
    +
    +       Zn Gm s
    +              same as Zn with an empty center (default G0.5).
    +
    +       r s D2 generate a regular polygon
    +
    +       r s Z1 G0.1
    +              generate a regular cone
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis • outputs +• examples • notes +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: August 12, 1998

    + + diff --git a/xs/src/qhull/html/rbox.man b/xs/src/qhull/html/rbox.man new file mode 100644 index 0000000000..3ea6395e69 --- /dev/null +++ b/xs/src/qhull/html/rbox.man @@ -0,0 +1,176 @@ +.\" This is the Unix manual page for rbox, written in nroff, the standard +.\" manual formatter for Unix systems. To format it, type +.\" +.\" nroff -man rbox.man +.\" +.\" This will print a formatted copy to standard output. If you want +.\" to ensure that the output is plain ascii, free of any control +.\" characters that nroff uses for underlining etc, pipe the output +.\" through "col -b": +.\" +.\" nroff -man rbox.man | col -b +.\" +.TH rbox 1 "August 10, 1998" "Geometry Center" +.SH NAME +rbox \- generate point distributions for qhull +.SH SYNOPSIS +Command "rbox" (w/o arguments) lists the options. +.SH DESCRIPTION +.PP +rbox generates random or regular points according to the options given, and +outputs +the points to stdout. The points are generated in a cube, unless 's' or 'k' +option is +given. The format of the output is the following: first line +contains the dimension and a comment, +second line contains the number of points, and the +following lines contain the points, one point per line. Points are represented +by their coordinate values. +.SH EXAMPLES +.TP +rbox 10 +10 random points in the unit cube centered at the origin. +.TP +rbox 10 s D2 +10 random points on a 2\[hy]d circle. +.TP +rbox 100 W0 +100 random points on the surface of a cube. +.TP +rbox 1000 s D4 +1000 random points on a 4\[hy]d sphere. +.TP +rbox c D5 O0.5 +a 5\[hy]d hypercube with one corner at the origin. +.TP +rbox d D10 +a 10\[hy]d diamond. +.TP +rbox x 1000 r W0 +100 random points on the surface of a fixed simplex +.TP +rbox y D12 +a 12\[hy]d simplex. +.TP +rbox l 10 +10 random points along a spiral +.TP +rbox l 10 r +10 regular points along a spiral plus two end points +.TP +rbox 1000 L10000 D4 s +1000 random points on the surface of a narrow lens. +.TP +rbox c G2 d G3 +a cube with coordinates +2/\-2 and a diamond with coordinates +3/\-3. +.TP +rbox 64 M3,4 z +a rotated, {0,1,2,3} x {0,1,2,3} x {0,1,2,3} lattice (Mesh) of integer +points. 'rbox 64 M1,0' is orthogonal. +.TP +rbox P0 P0 P0 P0 P0 +5 copies of the origin in 3\-d. Try 'rbox P0 P0 P0 P0 P0 | qhull QJ'. +.TP +r 100 s Z1 G0.1 +two cospherical 100\-gons plus another cospherical point. +.TP +100 s Z1 +a cone of points. +.TP +100 s Z1e\-7 +a narrow cone of points with many precision errors. +.SH OPTIONS +.TP +n +number of points +.TP +Dn +dimension n\[hy]d (default 3\[hy]d) +.TP +Bn +bounding box coordinates (default 0.5) +.TP +l +spiral distribution, available only in 3\[hy]d +.TP +Ln +lens distribution of radius n. May be used with 's', 'r', 'G', and 'W'. +.TP +Mn,m,r +lattice (Mesh) rotated by {[n,\-m,0], [m,n,0], [0,0,r], ...}. +Use 'Mm,n' for a rigid rotation with r = sqrt(n^2+m^2). 'M1,0' is an +orthogonal lattice. For example, '27 M1,0' is {0,1,2} x {0,1,2} x +{0,1,2}. '27 M3,4 z' is a rotated integer lattice. +.TP +s +cospherical points randomly generated in a cube and projected to the unit sphere +.TP +x +simplicial distribution. It is fixed for option 'r'. May be used with 'W'. +.TP +y +simplicial distribution plus a simplex. Both 'x' and 'y' generate the same points. +.TP +Wn +restrict points to distance n of the surface of a sphere or a cube +.TP +c +add a unit cube to the output +.TP +c Gm +add a cube with all combinations of +m and \-m to the output +.TP +d +add a unit diamond to the output. +.TP +d Gm +add a diamond made of 0, +m and \-m to the output +.TP +Cn,r,m +add n nearly coincident points within radius r of m points +.TP +Pn,m,r +add point [n,m,r] to the output first. Pad coordinates with 0.0. +.TP +n +Remove the command line from the first line of output. +.TP +On +offset the data by adding n to each coordinate. +.TP +t +use time in seconds as the random number seed (default is command line). +.TP +tn +set the random number seed to n. +.TP +z +generate integer coordinates. Use 'Bn' to change the range. +The default is 'B1e6' for six\[hy]digit coordinates. In R^4, seven\[hy]digit +coordinates will overflow hyperplane normalization. +.TP +Zn s +restrict points to a disk about the z+ axis and the sphere (default Z1.0). +Includes the opposite pole. 'Z1e\-6' generates degenerate points under +single precision. +.TP +Zn Gm s +same as Zn with an empty center (default G0.5). +.TP +r s D2 +generate a regular polygon +.TP +r s Z1 G0.1 +generate a regular cone +.SH BUGS +Some combinations of arguments generate odd results. + +Report bugs to qhull_bug@qhull.org, other correspondence to qhull@qhull.org +.SH SEE ALSO +qhull(1) +.SH AUTHOR +.nf +C. Bradford Barber +bradb@shore.net +.fi + diff --git a/xs/src/qhull/html/rbox.txt b/xs/src/qhull/html/rbox.txt new file mode 100644 index 0000000000..e3cf721892 --- /dev/null +++ b/xs/src/qhull/html/rbox.txt @@ -0,0 +1,195 @@ + + + +rbox(1) rbox(1) + + +NAME + rbox - generate point distributions for qhull + +SYNOPSIS + Command "rbox" (w/o arguments) lists the options. + +DESCRIPTION + rbox generates random or regular points according to the + options given, and outputs the points to stdout. The + points are generated in a cube, unless 's' or given. The + format of the output is the following: first line contains + the dimension and a comment, second line contains the num- + ber of points, and the following lines contain the points, + one point per line. Points are represented by their coor- + dinate values. + +EXAMPLES + rbox 10 + 10 random points in the unit cube centered at the + origin. + + rbox 10 s D2 + 10 random points on a 2-d circle. + + rbox 100 W0 + 100 random points on the surface of a cube. + + rbox 1000 s D4 + 1000 random points on a 4-d sphere. + + rbox c D5 O0.5 + a 5-d hypercube with one corner at the origin. + + rbox d D10 + a 10-d diamond. + + rbox x 1000 r W0 + 100 random points on the surface of a fixed simplex + + rbox y D12 + a 12-d simplex. + + rbox l 10 + 10 random points along a spiral + + rbox l 10 r + 10 regular points along a spiral plus two end + points + + rbox 1000 L10000 D4 s + 1000 random points on the surface of a narrow lens. + + rbox c G2 d G3 + a cube with coordinates +2/-2 and a diamond with + + + +Geometry Center August 10, 1998 1 + + + + + +rbox(1) rbox(1) + + + coordinates +3/-3. + + rbox 64 M3,4 z + a rotated, {0,1,2,3} x {0,1,2,3} x {0,1,2,3} lat- + tice (Mesh) of integer points. + + rbox P0 P0 P0 P0 P0 + 5 copies of the origin in 3-d. Try 'rbox P0 P0 P0 + P0 P0 | qhull QJ'. + + r 100 s Z1 G0.1 + two cospherical 100-gons plus another cospherical + point. + + 100 s Z1 + a cone of points. + + 100 s Z1e-7 + a narrow cone of points with many precision errors. + +OPTIONS + n number of points + + Dn dimension n-d (default 3-d) + + Bn bounding box coordinates (default 0.5) + + l spiral distribution, available only in 3-d + + Ln lens distribution of radius n. May be used with + 's', 'r', 'G', and 'W'. + + Mn,m,r lattice (Mesh) rotated by {[n,-m,0], [m,n,0], + [0,0,r], ...}. Use 'Mm,n' for a rigid rotation + with r = sqrt(n^2+m^2). 'M1,0' is an orthogonal + lattice. For example, '27 M1,0' is {0,1,2} x + {0,1,2} x {0,1,2}. + + s cospherical points randomly generated in a cube and + projected to the unit sphere + + x simplicial distribution. It is fixed for option + 'r'. May be used with 'W'. + + y simplicial distribution plus a simplex. Both 'x' + and 'y' generate the same points. + + Wn restrict points to distance n of the surface of a + sphere or a cube + + c add a unit cube to the output + + c Gm add a cube with all combinations of +m and -m to + the output + + + +Geometry Center August 10, 1998 2 + + + + + +rbox(1) rbox(1) + + + d add a unit diamond to the output. + + d Gm add a diamond made of 0, +m and -m to the output + + Cn,r,m add n nearly coincident points within radius r of m points + + Pn,m,r add point [n,m,r] to the output first. Pad coordi- + nates with 0.0. + + n Remove the command line from the first line of out- + put. + + On offset the data by adding n to each coordinate. + + t use time in seconds as the random number seed + (default is command line). + + tn set the random number seed to n. + + z generate integer coordinates. Use 'Bn' to change + the range. The default is 'B1e6' for six-digit + coordinates. In R^4, seven-digit coordinates will + overflow hyperplane normalization. + + Zn s restrict points to a disk about the z+ axis and the + sphere (default Z1.0). Includes the opposite pole. + 'Z1e-6' generates degenerate points under single + precision. + + Zn Gm s + same as Zn with an empty center (default G0.5). + + r s D2 generate a regular polygon + + r s Z1 G0.1 + generate a regular cone + +BUGS + Some combinations of arguments generate odd results. + + Report bugs to qhull_bug@qhull.org, other correspon- + dence to qhull@qhull.org + +SEE ALSO + qhull(1) + +AUTHOR + C. Bradford Barber + bradb@shore.net + + + + + +Geometry Center August 10, 1998 3 + + diff --git a/xs/src/qhull/index.htm b/xs/src/qhull/index.htm new file mode 100644 index 0000000000..4ea7806c93 --- /dev/null +++ b/xs/src/qhull/index.htm @@ -0,0 +1,284 @@ + + + + +Qhull code for Convex Hull, Delaunay Triangulation, Voronoi Diagram, and Halfspace Intersection about a Point + + + + +URL: http://www.qhull.org +
    To: +News +• Download +• CiteSeer +• Images +• Manual +• FAQ +• Programs +• Options +

    + +
    + + +
    +

    Qhull

    + [CONE] +
    +Qhull computes the convex hull, Delaunay triangulation, Voronoi diagram, +halfspace intersection about a point, furthest-site Delaunay +triangulation, and furthest-site Voronoi diagram. The source code runs in +2-d, 3-d, 4-d, and higher dimensions. Qhull implements the Quickhull +algorithm for computing the convex hull. It handles roundoff +errors from floating point arithmetic. It computes volumes, +surface areas, and approximations to the convex hull.

    + + +

    Qhull does not support triangulation of non-convex surfaces, mesh +generation of non-convex objects, medium-sized inputs in 9-D +and higher, alpha shapes, weighted Voronoi diagrams, Voronoi volumes, or +constrained Delaunay triangulations,

    + +

    Qhull 2015.2 introduces reentrant Qhull. It allows concurrent Qhull runs and simplifies the C++ interface to Qhull. +If you call Qhull from your program, you should use reentrant Qhull (libqhull_r) instead of qh_QHpointer (libqhull). +If you use Qhull 2003.1. please upgrade or apply poly.c-qh_gethash.patch. +

    +
    + +
    +
    + + + +
    + +

    Introduction +

      +
    • Fukuda's introduction to convex hulls, Delaunay + triangulations, Voronoi diagrams, and linear programming
    • +
    • Lambert's Java visualization of convex hull algorithms
    • +
    • LEDA Guide to geometry algorithms +
    • MathWorld's Computational Geometry from Wolfram Research +
    • Skiena's Computational Geometry from his Algorithm Design Manual. +
    • Stony Brook Algorithm Repository, computational geometry
    • +
    + +

    Qhull Documentation and Support +

    + +

    Related URLs +

    + +

    FAQs and Newsgroups +

    + +
    + +

    The program includes options for input transformations, +randomization, tracing, multiple output formats, and execution +statistics. The program can be called from within your +application.

    + +

    You can view the results in 2-d, 3-d and 4-d with Geomview. An alternative +is VTK.

    + +

    For an article about Qhull, download from + ACM or CiteSeer: +

    + +
    +

    Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., "The + Quickhull algorithm for convex hulls," ACM Trans. on + Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org

    +
    + +

    Abstract:

    + +
    +

    The convex hull of a set of points is the smallest convex + set that contains the points. This article presents a + practical convex hull algorithm that combines the + two-dimensional Quickhull Algorithm with the general + dimension Beneath-Beyond Algorithm. It is similar to the + randomized, incremental algorithms for convex hull and + Delaunay triangulation. We provide empirical evidence that + the algorithm runs faster when the input contains non-extreme + points, and that it uses less memory.

    +

    Computational geometry algorithms have traditionally + assumed that input sets are well behaved. When an algorithm + is implemented with floating point arithmetic, this + assumption can lead to serious errors. We briefly describe a + solution to this problem when computing the convex hull in + two, three, or four dimensions. The output is a set of + "thick" facets that contain all possible exact convex hulls + of the input. A variation is effective in five or more + dimensions.

    +
    + +
    + +

    Up: Past Software +Projects of the Geometry Center
    +URL: http://www.qhull.org +
    To: +News +• Download +• CiteSeer +• Images +• Manual +• FAQ +• Programs +• Options +

    + +
    + +

    [HOME] The Geometry Center Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: May 17 1995 --- + + diff --git a/xs/src/qhull/origCMakeLists.txt b/xs/src/qhull/origCMakeLists.txt new file mode 100644 index 0000000000..1034d1dea9 --- /dev/null +++ b/xs/src/qhull/origCMakeLists.txt @@ -0,0 +1,426 @@ +# CMakeLists.txt -- CMake configuration file for qhull, qhull6, and related programs +# +# To install CMake +# Download from http://www.cmake.org/download/ +# +# To find the available targets for CMake -G "..." +# cmake --help +# +# To build with MSYS/mingw +# cd build && cmake -G "MSYS Makefiles" .. && cmake .. +# make +# make install +# +# To uninstall on unix or MSYS/mingw +# xargs rm [B. Boeckel] + - Moved include file for each C++ source file to the top of the includes + - Prepend cpp includes with "libqhullcpp/" + - RoadLogEvent includes RoadLogEvent.h + - QhullIterator.h: Only QHULL_DECLARE_SEQUENTIAL_ITERATOR is used. + + - Compared src/libqhull/* to src/libqhull_r/* and resolved differences + - qh_printpoint in io.c skips qh_IDnone like io_r.c + - qhull_p-exports.def: Added three missing exports + - set_r.h: Removed countT. Too many issues + + - libqhull_r/Makefile: Add help prompts to 'make qtest' + - libqhull.pro: Add '../libqhull/' to sources and headers + - libqhull/Makefile: Fixed -I,./,,/src + + - qhull-zip.sh: Add CMakeModules to tarball [C. Rosenvik] + - CMakeLists.txt: Add targets qhullp and user_egp for qh_QHpointer and libqhull_p + - Reorganized 'make help' + - Makefile cleanall: Delete testqset and qhulltest from bin/ + - Fix filetype of Unix-only files + - Fix Unix line endings for Makefile and check in qhull-zip.sh + - Fix Windows line-endings and check in qhull-zip.sh + - qhull-zip.sh: Check for Unix text files + + ------------ +Qhull 2015.1 2016/01/03 (7.1.0) + - Add Rbox option 'Cn,r,m' to add nearly coincident points. Trigger for duplicate ridges + - Add Qhull option 'Q12' to ignore error on wide merge due to duplicate ridge + + - qh_findbestlower: Call qh_findfacet_all to fix rare "flipped or upper Delaunay" error QH6228. + QH6228 input provided by J. Metz. Reported (date order): L. Fiaschi, N. Bowler, A. Liebscher, V. Vieira, N. Rhinehart, N. Vance, P. Shafer + - qh_check_dupridge: Check if wide merge due to duplicate ridge from nearly coincident points + - qh_initialhull: Fix error messages for initial simplex is flat + - qh_determinant: increased 2-d and 3-d nearzero by 10x due to a counter-example + - rbox: Add qh_outcoord() to output coordinates w/ or w/o iscdd + - qh_meminit (mem.c): Add call to qh_memcheck + - Compare libqhull/... to libqhull_r/... and resolve differences + - Update builds for DevStudio (qhull.sln for msdev 2005..2009, qhull-32.sln and qhull-64.sln for recent releases) + + - qh-impre.htm: Add a section about precision errors for 'Nearly coincident points on an edge' + - html/index.htm#geomview: Document how to install, build, and use Geomview. + - html/index.htm: Emphasize program links and move related urls to end + - qhull/index.htm: Emphasize manual, geomview, and imprecision + - Fix documentation links in libqhull_r/index.htm + - Add 'Functions' link to documentation headers + - Change '...' to '...' + - libqhull_r/index.htm -- Add instructions for configuring web browsers for source links. + - libqhull_r/ -- Fix source links for ..._r.htm files + +------------ +Qhull 2015.0.7 2015/11/09 (7.0.7) + - Fix return type of operator-> in QhullLinkedList and other collection classes [F. Jares] + - Fix return types for QhullLinkedList + - Fix return types for QhullPoints + - Simplify return type for Coordinates::operator[] (same as QList) + - Add const to operators for QhullSet::iterator and add documentation + - Coordinates.h: Fix return types for operations of iterator and const_iterator + - Drop use of Perforce changelist number in qhull_VERSION of CMakeLists.txt + - Rename the md5sum files as *.tgz.md5sum instead of *-tgz.md5sum + - Fix build dependency for testqset_r [asekez] + - rbox.c depends on Qhull due to qh_lib_check which uses qh_version2 for error messages + - QhullFacet_test.cpp: Annotate Qhull invocations. Allows their repetition. + - QhullFacet_test.cpp: Adjust epsilon on distance tests + - Do not create libqhullcpp as a shared library. Qhull C++ classes may change layout and size. + - qhull-cpp.xml: Make a relative path to road-faq.xsl + +------------ +Qhull 2015.0.6 2015/10/20 (7.0.6.2013) + - In the libraries, exit() is only called from qh_exit(). qh_exit may be redefined. + - Add qh_fprintf_stderr to usermem.c. May be overridden to avoid use of stderr [D. Sterratt] + Add usermem to testqset builds + Used by qh_fprintf_rbox + - Remove most instances of stderr/stdout from libqhull, libqhull_r, and libqhullcpp [D. Sterratt] + qh_fprintf_stderr may be redefined. qh_meminit and qh_new_qhull use stderr as the default ferr + - qh_initflags: Use qh.fout instead of stdout for 'TO file'. A library caller may define a different qh.fout. + - qh_settemppush: Call qh_fprintf() instead of fprintf() on error. + - Rename qh_call_qhull as "Qhull-template" from user.c. Updated its references. + + - qh-code.htm: "How to avoid exit(), fprintf(), stderr, and stdout" + - html/index.htm: Fix table of contents for qh-code + - libqhull_r/index.htm: Rewrite introduction to Reentrant Qhull + - qh-faq.htm: Rewrite "Can Qhull use coordinates without placing them in a data file?" + - qh-get.html: Link to github + - Remove qhull_interface.cpp from the documentation + +------------ +Qhull 2015.0.5 2015/10/12 (7.0.5.1995) +- qh_new_qhull: default 'errfile' is 'stderr'. outfile and errfile are optional [B. Pearlmutter] +- qh_new_qhull: returns qh_ERRinput instead of exit() if qhull_cmd is not "qhull ..." [B. Pearlmutter] +- qhalf_r.c,etc: Add clear of qh.NOerrexit +- global.c: gcc 4.4.0 mingw32 segfault cleared by adding comment +- usermem_r-cpp.cpp: Optional file to redefine qh_exit() as throw "QH10003.." [B. Pearlmutter] + qh_exit() is called by qhull_r when qh_errexit() is not available. + +- html/index.htm: Add bibliographic reference to Golub & van Loan and other references [R. Gaul] +- qhalf.htm: A halfspace is the points on or below a hyperplane [D. Strawn] +- qh-opto.htm#n: Defined inside, outside, on, above, and below a hyperplane [D. Strawn] +- qhalf.htm#notes: Recast the linear program using negative halfspaces (as used by Qhull) [D. Strawn] +- qhull_a.h: Fix comment '#include "libqhull/qhull_a.h" [fe rew] + +- build/qhull*.pc.in: Templates for pkg-config (derived from Fedorra) [P. McMunn] + https://bitbucket.org/mgorny/pkg-config-spec/src/c1bf12afe0df6d95f2fe3f5e1ffb4c50f018825d/pkg-config-spec.txt?at=master&fileviewer=file-view-default +- Makefile: Remove user_eg3.o from LIBQHULLCPP_OBJS +- Makefile: Add .h dependencies for unix_r.o, etc. +- libqhull/Makefile: Fix build of rbox +- libqhull_r/Makefile: Fix build -I +- qhull.sln/user_eg3: Add dependency on libcpp +- Removed bin/libqhull_r.dll (should be qhull_r.dll) +- Removed build/qhulltest.vcproj (see build/qhulltest/qhulltest.vcproj) + +------------ +Qhull 2015.0.4 2015/9/30 (7.0.4.1984) + - qh-get.htm: Unix tarball includes version number (e.g., qhull-2015-src-7.1.0.1940.tgz) [Hauptman] + - qglobal.c: Add qh_version2 with Unix version for "-V" option [Hauptman] + - build/qhull-32.sln, *-32.vcxproj: Add Visual Studio 32-bit build for 2010+ + - build/qhull-64.sln, *-64.vcxproj: Add Visual Studio 64-bit build for 2010+ [G. Lodron] + - make-vcproj.sh: Restore to eg/... It is required for Visual Studio builds + - README.txt: updated builds and reentrant Qhull + - Add documentation for QHULL_LIB_CHECK + - qh_lib_check: Check for unknown QHULL_LIB_TYPE + - qh-code.htm: Add memory requirements for 32- and 64-bit + +------------ +Qhull 2015.0.3 2015/9/22 + - qh_mem, qh_merge: Log before 'delete' instead of afterwards [Coverity, K. Schwehr] + - qh_merge: Test for NULL horizon in qh_checkzero [Coverity, K. Schwehr] + - qh_matchneighbor: Check for matchfacet not a neighbor of facet [Coverity, K. Schwehr] + - qh_triangulate: Explicit check for visible==NULL [Coverity, K. Schwehr] + - qh_findbestfacet (unused by qhull): Fix test of isoutside [Coverity, K. Schwehr] + - qh_check_maxout: Check bestfacet!=0 for logging its id [Coverity, K. Schwehr] + - qh_nearvertex: Check for bestvertex not found [Coverity, K. Schwehr] + - qh_checkfacet: Check for missing neighbors of simplicial facets [Coverity, K. Schwehr] + - qh_setdelnth: Check 'nth' before using it [Coverity, K. Schwehr] + - Annotate code for Coverity warnings (most of these protected by qh_errexit) [K. Schwehr] + + - qh_printfacet3math: explicit format string (duplicates change to io.c) [B. Pearlmutter] + - libqhull_r.h: fix spelling error (duplicates change to libqhull.h) [B. Pearlmutter] + - unix_r.c: fix spelling error (duplicates change to unix.c) [B. Pearlmutter] + - qhull_a.h: define qhullUnused() only if defined(__cplusplus) [R. Stogner] + - qh_version: Use const char str[]= "string" instead of const char * str= "string" [U. Drepper, p. 27] + - qh_newvertex: Use UINT_MAX instead of 0xFFFFFFFF + - qh_newridge: Use UINT_MAX instead of 0xFFFFFFFF + - Reviewed FIXUP notes + + - QhullRidge_test: t_foreach use 'foreach(const QhullVertex &v, vertices) + - Made '#include "RoadTest.h" consistent across all C++ tests + + - qh-code.htm: May also use libqhull_r (e.g., FOREACHfacet_(...)) + - qh-get.htm: Add list of download build repositories + + - Add CMakeModules/CheckLFS.cmake: Enables Large File Support [B. Pearlmutter] + - Makefile: Use -fpic at all times instead of -fPIC, [U. Drepper p. 15] + +------------ +Qhull 2015.0.2 2015/9/1 + - global_r.c: Fixed spelling of /* duplicated in...qh_clear_outputflags */ [K. Schwehr] + - Replaced Gitorious with GitHub + - Moved 'to do' comments into Changes.txt + +------------ +Qhull 2015.0.1 2015/8/31 + + Source code changes + - Increased size of vertexT.id and ridgeT.id to 2^32 [H. Strandenes, C. Carson, K. Nguyen] + Reworded the warning message for ridgeT.id overflow. It does not affect Qhull output + - Add qh_lib_check to check for a compatible Qhull library. + Programs should call QHULL_LIB_CHECK before calling Qhull. + - Include headers prefixed with libqhull/, libqhull_r/, or libqhullcpp/ + - Renamed debugging routines dfacet/dvertex to qh_dfacet/qh_dvertex + - Rewrote user_eg, user_eg2, and user_eg3 as reentrant code + - Renamed 'qh_rand_seed' to 'qh_last_random'. Declare it as DATA + - qh_initqhull_start2 sets qh->NOerrexit on initialization + User must clear NOerrexit after setjmp() + + Other source code changes + - Define ptr_intT as 'long long' for __MINGW64__ [A. Voskov] + - poly_r.c: initialize horizon_skip [K. Schwehr] + - Removed vertexT.dim and MAX_vdim. It is not used by reentrant Qhull. + - Removed qhull_inuse. Not used by C++ + - Removed old __MWERKS__/__POWERPC__ code that speed up SIOUX I/O + - Moved #include libqhull/... before system includes (e.g., + - Comment-out _isatty declaration. Avoids "C4273 ... inconsistent dll linkage" + - Add random.h/random_r.h as an include file to random.c/random_r.c + - Rename rbox routines to qh_roundi/qh_out1/qh_out2n/qh_out3n + - Rename dfacet and dvertex to qh_dfacet and qh_dvertex + - Replace 'qhmem .zzz' with 'qhmem.zzz' + - Removed spaces between function name and parentheses + - Rename 'enum statistics' to 'enum qh_statistics' + - Declare rbox as DATA in qhull-exports.def and qhull_p-exports.def + - In comments, use 'qh.zzz' to reference qhT fields + - In qh_fprintf, use qhmem.ferr to report errors + - qh_fprintf may be called for errors in qh_initstatistics and qh_meminit + - qh_pointid returns qh_IDnone, qh_IDinterior, qh_IDunknown in place of -3, -2, -1 resp. + - getid_() returns qh_IDunknown in place of -1 + - After qh_meminit, qhmem.ferr is non-zero (stderr is the default) + - Update qh_MEMalign in testqset.c to user.h (with realT and void*) + - Split rboxT into a header file + - Add rboxlib.h to libqhull_a.h + - Rename PI to qh_PI and extend to 30 digits + - Rename MAXdim to qh_MAXdim + - Change spacing for type annotations '*' and '&' in C++ header files + - Test for !rbox_output/cpp_object in qh_fprintf_rbox + - Remove 'inline' annotation from explicit inline declarations + - Column 25 formatting for iterators, etc. + - Use '#//!\name' for section headers + - QhullFacet.cpp: zinc_(Zdistio); + - Clear qhT.ALLOWrestart in qh_errexit + - Replace longjmp with qh_errexit_rbox in qh_rboxpoints + - Add jmpExtra after rbox_errexit to protect against compiler errors + - Add qh.ISqhullQh to indicate initialization by QhullQh() + - Add library warnings to 'rbox D4', user_eg, user_eg2, user_eg3 + - Add headers to q_eg, q_egtest, and q_test + - Check that qh.NOerrexit is cleared before call to qh_initflags + +Qhull documentation + - README.txt: Added references to qh-code.htm + - README.txt: Added section 'Calling Qhull from C programs' + - qh-code.htm: Moved Performance after C++ and C interface + - qh-code.htm: Moved Cpp Questions to end of the C++ section + - qh-code.htm: Fixed documentation for 'include' path. It should be include/libqhull + - qconvex.htm: Fixed documentation for 'i'. It triangulates in 4-d and higher [ref] + - Clarified qhalf space documentation for the interior point [J. Santos] + - rbox.c: Version is same date as qh_version in global.c + - gobal_r.c: Version includes a '.r' suffix to indicate 'reentrant' + +Qhull builds + - Development moved to http://github.com/qhull/qhull + git clone git@github.com:qhull/qhull.git + - Exchanged make targets for testing. + 'make test' is a quick test of qhull programs. + 'make testall' is a thorough test + - Added 'make help' and 'make test' to libqhull and libqhull_r Makefiles + - CMakeLists.txt: Remove libqhull, libqhull_r, and libqhullcpp from include_directories + - CMakeLists.txt: Add qhull_SHAREDR for qhull_r + - CMakeLists.txt: Retain qhull_SHARED and qhull_SHAREDP (qh_QHpointer) + - CMakeLists.txt: Move qhull_SHARED and qhull_SHAREDP (qh_QHpointer) to qhull_TARGETS_OLD + Drop qhull_STATICP (use qhull_SHAREDP or qhull_STATIC) + Set SOVERSION and VERSION for shared libraries + - Move qhull_p-exports.def back to libqhull + - Switched to mingw-w64-install for gcc + - Improved prompts for 'make' + - qhull-all.pro: Remove user_eg3.cpp from OTHER_FILES + - libqhull.pro: Ordered object files by frequency of execution, as done before + - Add the folder name to C++ includes and remove libqhullcpp from INCLUDEPATH + - Changed CONFIG+=qtestlib to QT+=testlib + - Changed Makefile to gcc -O3 (was -O2) + - Changed libqhull/libqhull_r Makefiles to both produce rbox, qhull, ..., user_eg, and user_eg2 + - Removed Debian 'config/...'. It was needed for Qhull 2012. + +libqhull_r (reentrant Qhull) + - Replaced qh_qh with a parameter to each procedure [P. Klosterman] + No more globally defined data structures in Qhull + Simplified multithreading and C++ user interface + All functions are reentrant (Qt: "A reentrant function can ... be called simultaneously from multiple threads, but only if each invocation uses its own data.") + No more qh_QHpointer. + See user_eg3 and qhulltest + New libraries + libqhull_r -- Shared library with reentrant sources (e.g., poly_r.h and poly_r.c which replace libqhull's poly.h and poly.c) + libqhullstatic_r -- Static library with the same sources as libqhull_r + libqhullcpp -- The C++ interface using libqhullstatic_r (further notes below) + New executables + testqset_r -- Test qset_r.c (the reentrant version of qset.c + + Source code changes for libqhull_r + - Add qh_zero() to initialize and zero memory for qh_new_qhull + - Remove qh_save_qhull(), qh_restore_qhull(), and qh.old_qhstat from global_r.c + - Remove qh_freeqhull2() (global_r.c) + - Remove qh_freestatistics() (stat_r.c) + - Remove qh_compare_vertexpoint (qhT is not available, unused code) + - Remove conditional code for __POWERPC__ from unix_r.c and rbox_r.c + - Move qh_last_random into qh->last_random (random_r.c) + - Rename sources files with a '_r' suffix. qhull_a.h becomes qhull_ra.h + - Replace 'qh' macro with 'qh->' + - Replace global qhT with parameter-0 + - Add qhmemT to beginning of qhT. It may not be used standalone. + - Add qhstatT to end of qhT + - Remove qhull_inuse + - Change qhmem.zzz to qh->qhmem.zzz + - Replace qh_qhstat with qh->qhstat + - Remove qh_freestatistics + - Replace qh_last_random with qh->last_random + - Replace rboxT with qh->rbox_errexit, rbox_isinteger, rbox_out_offset + - Replace rbox.ferr/fout with qh->ferr/fout + - No qh for qh_exit, qh_free, qh_malloc, qh_strtod, qh_strtol, qh_stddev + - New qmake include files qhull-app-c_r.pri, qhull-app-shared_r.pri, qhull-libqhull-src_r.pri + - Replace 'int' with 'countT' and 'COUNTmax' for large counts and identifiers + - qhset converted to countT + - Removed vertexT.dim -- No longer needed by cpp + Removed MAX_vdim + - Guarantee that qh->run_id!=0. Old code assumed that qh_RANDOMint was 31 bits + +Changes to libqhullcpp + - Added QhullVertexSet.h to libqhullcpp.pro and libqhullpcpp.pro + - QhullVertexSet: error if qhsettemp_defined at copy constructor/assignment (otherwise double free) + - Enable QhullSet.operator=. Copy constructor and assignment only copies pointers + - Changed QhullPoint.operator==() to sqrt(distanceEpsilon) + - Added assignment of base class QhullPoints to PointCoordinates.operator= + - Enable QhullPoints.operator= + - Rename PointCoordinates.point_comment to describe_points + - Add 'typename T' to definition of QhullSet::value() + +C++ interface + - Reimplemented C++ interface on reentrant libqhull_r instead of libqhull + - Prepend include files with libqhullcpp/ + - Replaced UsingLibQhull with QhullQh and macro QH_TRY + Removed UsingLibQhull.currentAngleEpsilon and related routines + Removed UsingLibQhull_test.cpp + Replaced globalDistanceEpsilon with QhullQh.distanceEpsilon + Replaced globalAngleEpsilon with QhullQh.angleEpsilon + Moved UsingQhullLib.checkQhullMemoryEmpty to QhullQh.checkAndFreeQhullMemory + Replaced FACTORepsilon=10 with QhullQh.factor_epsilon=1.0 + - To avoid -Wshadow for QhullQh*, use 'qqh' for parameters and 'qh()' for methods + - Moved messaging from Qhull to QhullQh + - Add check of RboxPoints* in qh_fprintf_rbox + - Renamed Qhull.initializeQhull to Qhull.allocateQhullQh + Added qh_freeqhull(!qh_ALL) as done by unix.c and other programs + - Moved QhullPoints.extraCoordinatesCount into QhullPoints.cpp + - Replaced section tags with '#//!\name ...' + - Removed qhRunId from print() to ostream. + - Removed print() to ostream. Use '<< qhullPoint' or '<< qhullPoint.print("message")' + +C++ interface for most classes + - Remove qhRunId + - Add QhullQh *qh_qh to all types + Pointer comparisons of facetT,etc. do not test corresponding qh_qh + Added to end of type for debugging information, unless wasteful alignment + - Add QhullQh * to all constructors + - All constructors may use Qhull & instead of QhullQh * + - For inherited QhullQh types, change to 'protected' + - Renamed 'o' to 'other' except where used extensively in iterators + - Except for conditional code, merged the Conversion section into GetSet + - Removed empty(). Use isEmpty() instead + - Add operator= instead of keeping it private + - print_message=0 not allowed. Use "" instead. + - Rename isDefined() to isValid() to match Qt conventions + +C++ interface by class + - Coordinates + Removed empty(). Use isEmpty() instead + Added append(dim, coordT*) + Reformated the iterators + Convert to countT + - PointCoordinates + Added constructors for Qhull or QhullQh* (provides access to QhullPoint.operator==) + Removed PointCoordinates(int pointDimension) since PointCoordinates should have a comment. Also, it is ambiguous with PointCoordinates(QhullQh*) + Renamed point_comment to describe_points + Convert to countT + - Qhull + Remove qhull_run_i + Remove qh_active + Replace property feasiblePoint with field feasible_point and methods setFeasiblePoint/feasiblePoint + Returns qh.feasible_point if defined + Moved useOutputStream to QhullQh use_output_stream + Renamed useOutputStream() to hasOutputStream() + Replaced qhull_dimension with qh->input_dim //! Dimension of result (qh.hull_dim or one less for Delaunay/Voronoi) + Removed global s_qhull_output= 0; + Move qhull_status, qhull_message, error_stream, output_stream to QhullQh + Renamed qhullQh() to qh() + Added check of base address to allocateQhullQh(), Was not needed for qhullpcpp + - QhullFacet + Constructor requires Qhull or QhullQh* pointer + Convert to countT + Dropped implicit conversion from facetT + Dropped runId + Add print("message") to replace print() + - QhullFacetList + Constructor requires Qhull or QhullQh* pointer + Convert to countT + Dropped runId + - QhullFacetSet + Removed empty(). Use isEmpty() instead + Constructor requires Qhull or QhullQh* pointer + Convert to countT + Dropped runId + Add operator= + Implement print("message") + - QhullHyperplane + Add hyperplaneAngle() method + Rewrite operator== to use hyperplaneAngle() + Reorganize fields to keep pointers aligned + Except for default constructor requires Qhull or QhullQh* pointer + Enable copy assignment + Reorganized header + - QhullLinkedList + Add operator= + Removed empty(). Use isEmpty() instead + Convert to countT + iterator(T) made iterator(const T &) + const_iterator(T) made const_iterator(const T &) + const_iterator(iterator) made const_iterator(const iterator &) + - QhullPoint + Add constructors for Qhull or QhullQh* pointer (for id() and operator==) + Add defineAs(coordT*) + Add getBaseT() and base_type for QhullSet + Added checks for point_coordinates==0 + Removed static QhullPoint::id(), use QhullPoint.id() instead + distance() throws an error if dimension doesn't agree or if a point is undefined + Convert to countT + If !qh_qh, operator==() requires equal coordinates + Use cout<

    [R. Richter, S. Pasko] + - Remove deprecated libqhull/qhull.h + Use libqhull/libqhull.h instead. Avoids confusion with libqhullcpp/Qhull.h + - Makefile: Add LIBDIR, INCDIR, and DESTDIR to install [L.H. de Mello] + Separate MAN install from DOC install + Create install directories + Installs headers to include/libqhull, include/libqhullcpp, include/road + - CMakeLists.txt: Add MAN_INSTALL_DIR for qhull.1 and rbox.1 man pages + Add RoadTest.h to include/road for Qt users (road_HEADERS) + - Renamed md5sum files to avoid two extensions + - qh-get.htm: Add Readme links and 2009.1 note. + - qh-optf.htm: Fix link + - index.htm: Updated Google Scholar link + - qhull-zip.sh: Improved error message. + +------------ +Qhull 2011.1 2011/04/17 6.2.0.1373 + +Changes to deliverables + - qvoronoi: Deprecated 'Qt' and 'QJn'. Removed from documentation and prompts. + These options produced duplicate Voronoi vertices for cospherical data. + - Removed doskey from Qhull-go.bat. It is incompatible with Windows 7 + - Added 'facets' argument to user_eg3.cpp + - user_eg links with shared library + - qhulltest.cpp: Add closing prompt. + +Changes to build system + - Reorganized source directories + - Moved executables to bin directory + - Add CMake build for all targets (CMakeFiles.txt) [M. Moll assisted] + - Add gcc build for all targets (Makefile) + - Fixed location of qhull.man and rbox.man [M. Moll] + - Add DevStudio builds for all targets (build/*.vcproj) + - Added shared library (lib/qhull6.dll) + Added qh_QHpointer_dllimport to work around problems with MSVC + - Added static libraries with and without qh_QHpointer (lib/qhullstatic.lib) + - Added eg/make-vcproj.sh to create vcproj/sln files from cmake and qmake + - Document location of qh_QHpointer + - Use shadow build directory + - Made -fno-strict-aliasing conditional on gcc version + - Added src/qhull-app-cpp.pri, src/qhull-app-c.pri, etc. for common settings + - Add .gitignore with ignored files and directories. + - Use .git/info/exclude for locally excluded files. + - Fixed MBorland for new directory structure + - cleanall (Makefile): Delete 'linked' programs due to libqhull_r and libqhull/Makefile + +Changes to documentation + - qvoronoi.htm: Remove quotes from qvoronoi example + - qhull-cpp.xml: Add naming conventions + - index.htm: Add Google Scholar references + - qh-optf.htm: Add note about order of 'Fn' matching 'Fv' order [Q. Pan] + - Add patch for old builds in qh-get.htm + - Added C++ compiling instructions to README.txt + - Add instructions for fixing the DOS window + - Changed DOS window to command window + - Fixed html links + - qh-get.htm: Dropped the Spanish mirror site. It was disabled. + +Changes to C code + - mem.h: Define ptr_intT as 'long long' for Microsoft Windows _win64 builds. + On Linux and Mac, 'long' is 64-bits on a 64-bit host + - Added qh_QHpointer_dllimport to work around MSVC problem + - qconvex.c,etc.: Define prototype for _isatty + - Define MSG_QHULL_ERROR in user.h + - Move MSG_FIXUP to 11000 and updated FIXUP QH11... + +Changes to test code + - Add note to q_test than R1e-3 may error (qh-code.htm, Enhancements) + - Add test for executables to q_eg, etc. + - Fixed Qhull-go.bat. QHULL-GO invokes it with command.com, + +Changes to C++ interface + - QhullFacet: Added isSimplicial, isTopOrient, isTriCoplanar, isUpperDelaunay + - Added Qhull::defineVertexFacetNeighbors() for facetNeighbors of vertices. + Automatically called for facet merging and Voronoi diagrams + Do not print QhullVertex::facetNeighbors is !facetNeighborsDefined() + - Assigned FIXUP identifiers + - QhullError: Add copy constructor, assignment operator, and destructor + - Add throw() specifiers to RoadError and QhullError + - Renamed RoadError::defined() to RoadError::isDefined() + - Add #error to Qhull.h if qh_QHpointer is not defined + +Changes to C++ code + - Fixed bug reported by renangms. Vertex output throws error QH10034 + and defineVertexNeighbors() does not exist. + - Define QHULL_USES_QT for qt-qhull.cpp [renangms] + - Reviewed all copy constructors and copy assignments. Updated comments. + Defined Qhull copy constructor and copy assignment [G. Rivet-Sabourin] + Disabled UsingQhullLib default constructor, copy construct, and copy assign + - Merged changes from J. Obermayr in gitorious/jobermayrs-qhull:next + - Fix strncat limit in rboxlib.c and global.c + - Changes to CMakeLists.txt for openSUSE + - Fixed additional uses of strncat + - Fixed QhullFacet::PrintRidges to check hasNextRidge3d() + - Removed gcc warnings for shadowing from code (src/qhull-warn.pri) + - Removed semicolon after extern "C" {...} + - Removed experimental QhullEvent/QhullLog + - Use fabs() instead of abs() to avoid accidental conversions to int + - Fixed type of vertex->neighbors in qh_printvoronoi [no effect on results] + - Removed unnecessary if statement in qh_printvoronoi + +------------ +qhull 2010.1 2010/01/14 +- Fixed quote for #include in qhull.h [U.Hergenhahn, K.Roland] +- Add qt-qhull.cpp with Qt conditional code +- Add libqhullp.proj +- Add libqhull5 to Readme, Announce, download +- Reviewed #pragma +- Reviewed FIXUP and assigned QH tags +- All projects compile with warnings enabled +- Replaced 'up' glyphs with » +- Moved cpp questions to qh-code.htm#questions-cpp +- Moved suggestions to qh-code.htm#enhance +- Moved documentation requests to qh-code.htm#enhance +- Add md5sum file to distributions +- Switched to DevStudio builds to avoid dependent libraries, 10% slower + Removed user_eg3.exe and qhullcpp.dll from Windows build + Fix qhull.sln and project files for qh_QHpointer +- Add eg/qhull-zip.sh to build qhull distribution files + +------------ +qhull 2010.1 2010/01/10 +- Test for NULL fp in qh_eachvoronoi [D. Szczerba] + +qhull 2010.1 2010/01/09 + +Changes to build and distribution +- Use qh_QHpointer=0 for libqhull.a, qhull, rbox, etc. + Use -Dqh_QHpointer for libqhullp.a, qhullcpp.dll, etc. + qh_QHpointer [2010, gcc] 4% time 4% space, [2003, msvc] 8% time 2% space +- Add config/ and project/debian/ for Autoconf build [R. Laboissiere] + from debian branch in git and http://savannah.nongnu.org/cvs/?group=qhull +- Add CMakeLists.txt [kwilliams] +- Fix tabs in Makefile.txt [mschamschula] +- Add -fno-strict-aliasing to Makefile for gcc 4.1, 4.2, and 4.3 qset segfault +- Remove user_eg.exe and user_eg2.exe from Windows distribution +- Order object files by frequency of execution for better locality. + +Changes to source +- Remove ptr_intT from qh_matchvertices. It was int since the beginning. +- user.h requires for CLOCKS_PER_SEC +- Move ostream<

      ---------------------------------
    +
    +   geom.c
    +   geometric routines of qhull
    +
    +   see qh-geom.htm and geom.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/geom.c#2 $$Change: 1995 $
    +   $DateTime: 2015/10/13 21:59:42 $$Author: bbarber $
    +
    +   infrequent code goes into geom2.c
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*---------------------------------
    +
    +  qh_distplane( point, facet, dist )
    +    return distance from point to facet
    +
    +  returns:
    +    dist
    +    if qh.RANDOMdist, joggles result
    +
    +  notes:
    +    dist > 0 if point is above facet (i.e., outside)
    +    does not error (for qh_sortfacets, qh_outerinner)
    +
    +  see:
    +    qh_distnorm in geom2.c
    +    qh_distplane [geom.c], QhullFacet::distance, and QhullHyperplane::distance are copies
    +*/
    +void qh_distplane(pointT *point, facetT *facet, realT *dist) {
    +  coordT *normal= facet->normal, *coordp, randr;
    +  int k;
    +
    +  switch (qh hull_dim){
    +  case 2:
    +    *dist= facet->offset + point[0] * normal[0] + point[1] * normal[1];
    +    break;
    +  case 3:
    +    *dist= facet->offset + point[0] * normal[0] + point[1] * normal[1] + point[2] * normal[2];
    +    break;
    +  case 4:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3];
    +    break;
    +  case 5:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4];
    +    break;
    +  case 6:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5];
    +    break;
    +  case 7:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6];
    +    break;
    +  case 8:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6]+point[7]*normal[7];
    +    break;
    +  default:
    +    *dist= facet->offset;
    +    coordp= point;
    +    for (k=qh hull_dim; k--; )
    +      *dist += *coordp++ * *normal++;
    +    break;
    +  }
    +  zinc_(Zdistplane);
    +  if (!qh RANDOMdist && qh IStracing < 4)
    +    return;
    +  if (qh RANDOMdist) {
    +    randr= qh_RANDOMint;
    +    *dist += (2.0 * randr / qh_RANDOMmax - 1.0) *
    +      qh RANDOMfactor * qh MAXabs_coord;
    +  }
    +  if (qh IStracing >= 4) {
    +    qh_fprintf(qh ferr, 8001, "qh_distplane: ");
    +    qh_fprintf(qh ferr, 8002, qh_REAL_1, *dist);
    +    qh_fprintf(qh ferr, 8003, "from p%d to f%d\n", qh_pointid(point), facet->id);
    +  }
    +  return;
    +} /* distplane */
    +
    +
    +/*---------------------------------
    +
    +  qh_findbest( point, startfacet, bestoutside, qh_ISnewfacets, qh_NOupper, dist, isoutside, numpart )
    +    find facet that is furthest below a point
    +    for upperDelaunay facets
    +      returns facet only if !qh_NOupper and clearly above
    +
    +  input:
    +    starts search at 'startfacet' (can not be flipped)
    +    if !bestoutside(qh_ALL), stops at qh.MINoutside
    +
    +  returns:
    +    best facet (reports error if NULL)
    +    early out if isoutside defined and bestdist > qh.MINoutside
    +    dist is distance to facet
    +    isoutside is true if point is outside of facet
    +    numpart counts the number of distance tests
    +
    +  see also:
    +    qh_findbestnew()
    +
    +  notes:
    +    If merging (testhorizon), searches horizon facets of coplanar best facets because
    +    after qh_distplane, this and qh_partitionpoint are the most expensive in 3-d
    +      avoid calls to distplane, function calls, and real number operations.
    +    caller traces result
    +    Optimized for outside points.   Tried recording a search set for qh_findhorizon.
    +    Made code more complicated.
    +
    +  when called by qh_partitionvisible():
    +    indicated by qh_ISnewfacets
    +    qh.newfacet_list is list of simplicial, new facets
    +    qh_findbestnew set if qh_sharpnewfacets returns True (to use qh_findbestnew)
    +    qh.bestfacet_notsharp set if qh_sharpnewfacets returns False
    +
    +  when called by qh_findfacet(), qh_partitionpoint(), qh_partitioncoplanar(),
    +                 qh_check_bestdist(), qh_addpoint()
    +    indicated by !qh_ISnewfacets
    +    returns best facet in neighborhood of given facet
    +      this is best facet overall if dist > -   qh.MAXcoplanar
    +        or hull has at least a "spherical" curvature
    +
    +  design:
    +    initialize and test for early exit
    +    repeat while there are better facets
    +      for each neighbor of facet
    +        exit if outside facet found
    +        test for better facet
    +    if point is inside and partitioning
    +      test for new facets with a "sharp" intersection
    +      if so, future calls go to qh_findbestnew()
    +    test horizon facets
    +*/
    +facetT *qh_findbest(pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT isnewfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart) {
    +  realT bestdist= -REALmax/2 /* avoid underflow */;
    +  facetT *facet, *neighbor, **neighborp;
    +  facetT *bestfacet= NULL, *lastfacet= NULL;
    +  int oldtrace= qh IStracing;
    +  unsigned int visitid= ++qh visit_id;
    +  int numpartnew=0;
    +  boolT testhorizon = True; /* needed if precise, e.g., rbox c D6 | qhull Q0 Tv */
    +
    +  zinc_(Zfindbest);
    +  if (qh IStracing >= 3 || (qh TRACElevel && qh TRACEpoint >= 0 && qh TRACEpoint == qh_pointid(point))) {
    +    if (qh TRACElevel > qh IStracing)
    +      qh IStracing= qh TRACElevel;
    +    qh_fprintf(qh ferr, 8004, "qh_findbest: point p%d starting at f%d isnewfacets? %d, unless %d exit if > %2.2g\n",
    +             qh_pointid(point), startfacet->id, isnewfacets, bestoutside, qh MINoutside);
    +    qh_fprintf(qh ferr, 8005, "  testhorizon? %d noupper? %d", testhorizon, noupper);
    +    qh_fprintf(qh ferr, 8006, "  Last point added was p%d.", qh furthest_id);
    +    qh_fprintf(qh ferr, 8007, "  Last merge was #%d.  max_outside %2.2g\n", zzval_(Ztotmerge), qh max_outside);
    +  }
    +  if (isoutside)
    +    *isoutside= True;
    +  if (!startfacet->flipped) {  /* test startfacet */
    +    *numpart= 1;
    +    qh_distplane(point, startfacet, dist);  /* this code is duplicated below */
    +    if (!bestoutside && *dist >= qh MINoutside
    +    && (!startfacet->upperdelaunay || !noupper)) {
    +      bestfacet= startfacet;
    +      goto LABELreturn_best;
    +    }
    +    bestdist= *dist;
    +    if (!startfacet->upperdelaunay) {
    +      bestfacet= startfacet;
    +    }
    +  }else
    +    *numpart= 0;
    +  startfacet->visitid= visitid;
    +  facet= startfacet;
    +  while (facet) {
    +    trace4((qh ferr, 4001, "qh_findbest: neighbors of f%d, bestdist %2.2g f%d\n",
    +                facet->id, bestdist, getid_(bestfacet)));
    +    lastfacet= facet;
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->newfacet && isnewfacets)
    +        continue;
    +      if (neighbor->visitid == visitid)
    +        continue;
    +      neighbor->visitid= visitid;
    +      if (!neighbor->flipped) {  /* code duplicated above */
    +        (*numpart)++;
    +        qh_distplane(point, neighbor, dist);
    +        if (*dist > bestdist) {
    +          if (!bestoutside && *dist >= qh MINoutside
    +          && (!neighbor->upperdelaunay || !noupper)) {
    +            bestfacet= neighbor;
    +            goto LABELreturn_best;
    +          }
    +          if (!neighbor->upperdelaunay) {
    +            bestfacet= neighbor;
    +            bestdist= *dist;
    +            break; /* switch to neighbor */
    +          }else if (!bestfacet) {
    +            bestdist= *dist;
    +            break; /* switch to neighbor */
    +          }
    +        } /* end of *dist>bestdist */
    +      } /* end of !flipped */
    +    } /* end of FOREACHneighbor */
    +    facet= neighbor;  /* non-NULL only if *dist>bestdist */
    +  } /* end of while facet (directed search) */
    +  if (isnewfacets) {
    +    if (!bestfacet) {
    +      bestdist= -REALmax/2;
    +      bestfacet= qh_findbestnew(point, startfacet->next, &bestdist, bestoutside, isoutside, &numpartnew);
    +      testhorizon= False; /* qh_findbestnew calls qh_findbesthorizon */
    +    }else if (!qh findbest_notsharp && bestdist < - qh DISTround) {
    +      if (qh_sharpnewfacets()) {
    +        /* seldom used, qh_findbestnew will retest all facets */
    +        zinc_(Zfindnewsharp);
    +        bestfacet= qh_findbestnew(point, bestfacet, &bestdist, bestoutside, isoutside, &numpartnew);
    +        testhorizon= False; /* qh_findbestnew calls qh_findbesthorizon */
    +        qh findbestnew= True;
    +      }else
    +        qh findbest_notsharp= True;
    +    }
    +  }
    +  if (!bestfacet)
    +    bestfacet= qh_findbestlower(lastfacet, point, &bestdist, numpart);
    +  if (testhorizon)
    +    bestfacet= qh_findbesthorizon(!qh_IScheckmax, point, bestfacet, noupper, &bestdist, &numpartnew);
    +  *dist= bestdist;
    +  if (isoutside && bestdist < qh MINoutside)
    +    *isoutside= False;
    +LABELreturn_best:
    +  zadd_(Zfindbesttot, *numpart);
    +  zmax_(Zfindbestmax, *numpart);
    +  (*numpart) += numpartnew;
    +  qh IStracing= oldtrace;
    +  return bestfacet;
    +}  /* findbest */
    +
    +
    +/*---------------------------------
    +
    +  qh_findbesthorizon( qh_IScheckmax, point, startfacet, qh_NOupper, &bestdist, &numpart )
    +    search coplanar and better horizon facets from startfacet/bestdist
    +    ischeckmax turns off statistics and minsearch update
    +    all arguments must be initialized
    +  returns(ischeckmax):
    +    best facet
    +  returns(!ischeckmax):
    +    best facet that is not upperdelaunay
    +    allows upperdelaunay that is clearly outside
    +  returns:
    +    bestdist is distance to bestfacet
    +    numpart -- updates number of distance tests
    +
    +  notes:
    +    no early out -- use qh_findbest() or qh_findbestnew()
    +    Searches coplanar or better horizon facets
    +
    +  when called by qh_check_maxout() (qh_IScheckmax)
    +    startfacet must be closest to the point
    +      Otherwise, if point is beyond and below startfacet, startfacet may be a local minimum
    +      even though other facets are below the point.
    +    updates facet->maxoutside for good, visited facets
    +    may return NULL
    +
    +    searchdist is qh.max_outside + 2 * DISTround
    +      + max( MINvisible('Vn'), MAXcoplanar('Un'));
    +    This setting is a guess.  It must be at least max_outside + 2*DISTround
    +    because a facet may have a geometric neighbor across a vertex
    +
    +  design:
    +    for each horizon facet of coplanar best facets
    +      continue if clearly inside
    +      unless upperdelaunay or clearly outside
    +         update best facet
    +*/
    +facetT *qh_findbesthorizon(boolT ischeckmax, pointT* point, facetT *startfacet, boolT noupper, realT *bestdist, int *numpart) {
    +  facetT *bestfacet= startfacet;
    +  realT dist;
    +  facetT *neighbor, **neighborp, *facet;
    +  facetT *nextfacet= NULL; /* optimize last facet of coplanarfacetset */
    +  int numpartinit= *numpart, coplanarfacetset_size;
    +  unsigned int visitid= ++qh visit_id;
    +  boolT newbest= False; /* for tracing */
    +  realT minsearch, searchdist;  /* skip facets that are too far from point */
    +
    +  if (!ischeckmax) {
    +    zinc_(Zfindhorizon);
    +  }else {
    +#if qh_MAXoutside
    +    if ((!qh ONLYgood || startfacet->good) && *bestdist > startfacet->maxoutside)
    +      startfacet->maxoutside= *bestdist;
    +#endif
    +  }
    +  searchdist= qh_SEARCHdist; /* multiple of qh.max_outside and precision constants */
    +  minsearch= *bestdist - searchdist;
    +  if (ischeckmax) {
    +    /* Always check coplanar facets.  Needed for RBOX 1000 s Z1 G1e-13 t996564279 | QHULL Tv */
    +    minimize_(minsearch, -searchdist);
    +  }
    +  coplanarfacetset_size= 0;
    +  facet= startfacet;
    +  while (True) {
    +    trace4((qh ferr, 4002, "qh_findbesthorizon: neighbors of f%d bestdist %2.2g f%d ischeckmax? %d noupper? %d minsearch %2.2g searchdist %2.2g\n",
    +                facet->id, *bestdist, getid_(bestfacet), ischeckmax, noupper,
    +                minsearch, searchdist));
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == visitid)
    +        continue;
    +      neighbor->visitid= visitid;
    +      if (!neighbor->flipped) {
    +        qh_distplane(point, neighbor, &dist);
    +        (*numpart)++;
    +        if (dist > *bestdist) {
    +          if (!neighbor->upperdelaunay || ischeckmax || (!noupper && dist >= qh MINoutside)) {
    +            bestfacet= neighbor;
    +            *bestdist= dist;
    +            newbest= True;
    +            if (!ischeckmax) {
    +              minsearch= dist - searchdist;
    +              if (dist > *bestdist + searchdist) {
    +                zinc_(Zfindjump);  /* everything in qh.coplanarfacetset at least searchdist below */
    +                coplanarfacetset_size= 0;
    +              }
    +            }
    +          }
    +        }else if (dist < minsearch)
    +          continue;  /* if ischeckmax, dist can't be positive */
    +#if qh_MAXoutside
    +        if (ischeckmax && dist > neighbor->maxoutside)
    +          neighbor->maxoutside= dist;
    +#endif
    +      } /* end of !flipped */
    +      if (nextfacet) {
    +        if (!coplanarfacetset_size++) {
    +          SETfirst_(qh coplanarfacetset)= nextfacet;
    +          SETtruncate_(qh coplanarfacetset, 1);
    +        }else
    +          qh_setappend(&qh coplanarfacetset, nextfacet); /* Was needed for RBOX 1000 s W1e-13 P0 t996547055 | QHULL d Qbb Qc Tv
    +                                                 and RBOX 1000 s Z1 G1e-13 t996564279 | qhull Tv  */
    +      }
    +      nextfacet= neighbor;
    +    } /* end of EACHneighbor */
    +    facet= nextfacet;
    +    if (facet)
    +      nextfacet= NULL;
    +    else if (!coplanarfacetset_size)
    +      break;
    +    else if (!--coplanarfacetset_size) {
    +      facet= SETfirstt_(qh coplanarfacetset, facetT);
    +      SETtruncate_(qh coplanarfacetset, 0);
    +    }else
    +      facet= (facetT*)qh_setdellast(qh coplanarfacetset);
    +  } /* while True, for each facet in qh.coplanarfacetset */
    +  if (!ischeckmax) {
    +    zadd_(Zfindhorizontot, *numpart - numpartinit);
    +    zmax_(Zfindhorizonmax, *numpart - numpartinit);
    +    if (newbest)
    +      zinc_(Zparthorizon);
    +  }
    +  trace4((qh ferr, 4003, "qh_findbesthorizon: newbest? %d bestfacet f%d bestdist %2.2g\n", newbest, getid_(bestfacet), *bestdist));
    +  return bestfacet;
    +}  /* findbesthorizon */
    +
    +/*---------------------------------
    +
    +  qh_findbestnew( point, startfacet, dist, isoutside, numpart )
    +    find best newfacet for point
    +    searches all of qh.newfacet_list starting at startfacet
    +    searches horizon facets of coplanar best newfacets
    +    searches all facets if startfacet == qh.facet_list
    +  returns:
    +    best new or horizon facet that is not upperdelaunay
    +    early out if isoutside and not 'Qf'
    +    dist is distance to facet
    +    isoutside is true if point is outside of facet
    +    numpart is number of distance tests
    +
    +  notes:
    +    Always used for merged new facets (see qh_USEfindbestnew)
    +    Avoids upperdelaunay facet unless (isoutside and outside)
    +
    +    Uses qh.visit_id, qh.coplanarfacetset.
    +    If share visit_id with qh_findbest, coplanarfacetset is incorrect.
    +
    +    If merging (testhorizon), searches horizon facets of coplanar best facets because
    +    a point maybe coplanar to the bestfacet, below its horizon facet,
    +    and above a horizon facet of a coplanar newfacet.  For example,
    +      rbox 1000 s Z1 G1e-13 | qhull
    +      rbox 1000 s W1e-13 P0 t992110337 | QHULL d Qbb Qc
    +
    +    qh_findbestnew() used if
    +       qh_sharpnewfacets -- newfacets contains a sharp angle
    +       if many merges, qh_premerge found a merge, or 'Qf' (qh.findbestnew)
    +
    +  see also:
    +    qh_partitionall() and qh_findbest()
    +
    +  design:
    +    for each new facet starting from startfacet
    +      test distance from point to facet
    +      return facet if clearly outside
    +      unless upperdelaunay and a lowerdelaunay exists
    +         update best facet
    +    test horizon facets
    +*/
    +facetT *qh_findbestnew(pointT *point, facetT *startfacet,
    +           realT *dist, boolT bestoutside, boolT *isoutside, int *numpart) {
    +  realT bestdist= -REALmax/2;
    +  facetT *bestfacet= NULL, *facet;
    +  int oldtrace= qh IStracing, i;
    +  unsigned int visitid= ++qh visit_id;
    +  realT distoutside= 0.0;
    +  boolT isdistoutside; /* True if distoutside is defined */
    +  boolT testhorizon = True; /* needed if precise, e.g., rbox c D6 | qhull Q0 Tv */
    +
    +  if (!startfacet) {
    +    if (qh MERGING)
    +      qh_fprintf(qh ferr, 6001, "qhull precision error (qh_findbestnew): merging has formed and deleted a cone of new facets.  Can not continue.\n");
    +    else
    +      qh_fprintf(qh ferr, 6002, "qhull internal error (qh_findbestnew): no new facets for point p%d\n",
    +              qh furthest_id);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  zinc_(Zfindnew);
    +  if (qh BESToutside || bestoutside)
    +    isdistoutside= False;
    +  else {
    +    isdistoutside= True;
    +    distoutside= qh_DISToutside; /* multiple of qh.MINoutside & qh.max_outside, see user.h */
    +  }
    +  if (isoutside)
    +    *isoutside= True;
    +  *numpart= 0;
    +  if (qh IStracing >= 3 || (qh TRACElevel && qh TRACEpoint >= 0 && qh TRACEpoint == qh_pointid(point))) {
    +    if (qh TRACElevel > qh IStracing)
    +      qh IStracing= qh TRACElevel;
    +    qh_fprintf(qh ferr, 8008, "qh_findbestnew: point p%d facet f%d. Stop? %d if dist > %2.2g\n",
    +             qh_pointid(point), startfacet->id, isdistoutside, distoutside);
    +    qh_fprintf(qh ferr, 8009, "  Last point added p%d visitid %d.",  qh furthest_id, visitid);
    +    qh_fprintf(qh ferr, 8010, "  Last merge was #%d.\n", zzval_(Ztotmerge));
    +  }
    +  /* visit all new facets starting with startfacet, maybe qh facet_list */
    +  for (i=0, facet=startfacet; i < 2; i++, facet= qh newfacet_list) {
    +    FORALLfacet_(facet) {
    +      if (facet == startfacet && i)
    +        break;
    +      facet->visitid= visitid;
    +      if (!facet->flipped) {
    +        qh_distplane(point, facet, dist);
    +        (*numpart)++;
    +        if (*dist > bestdist) {
    +          if (!facet->upperdelaunay || *dist >= qh MINoutside) {
    +            bestfacet= facet;
    +            if (isdistoutside && *dist >= distoutside)
    +              goto LABELreturn_bestnew;
    +            bestdist= *dist;
    +          }
    +        }
    +      } /* end of !flipped */
    +    } /* FORALLfacet from startfacet or qh newfacet_list */
    +  }
    +  if (testhorizon || !bestfacet) /* testhorizon is always True.  Keep the same code as qh_findbest */
    +    bestfacet= qh_findbesthorizon(!qh_IScheckmax, point, bestfacet ? bestfacet : startfacet,
    +                                        !qh_NOupper, &bestdist, numpart);
    +  *dist= bestdist;
    +  if (isoutside && *dist < qh MINoutside)
    +    *isoutside= False;
    +LABELreturn_bestnew:
    +  zadd_(Zfindnewtot, *numpart);
    +  zmax_(Zfindnewmax, *numpart);
    +  trace4((qh ferr, 4004, "qh_findbestnew: bestfacet f%d bestdist %2.2g\n", getid_(bestfacet), *dist));
    +  qh IStracing= oldtrace;
    +  return bestfacet;
    +}  /* findbestnew */
    +
    +/* ============ hyperplane functions -- keep code together [?] ============ */
    +
    +/*---------------------------------
    +
    +  qh_backnormal( rows, numrow, numcol, sign, normal, nearzero )
    +    given an upper-triangular rows array and a sign,
    +    solve for normal equation x using back substitution over rows U
    +
    +  returns:
    +     normal= x
    +
    +     if will not be able to divzero() when normalized(qh.MINdenom_2 and qh.MINdenom_1_2),
    +       if fails on last row
    +         this means that the hyperplane intersects [0,..,1]
    +         sets last coordinate of normal to sign
    +       otherwise
    +         sets tail of normal to [...,sign,0,...], i.e., solves for b= [0...0]
    +         sets nearzero
    +
    +  notes:
    +     assumes numrow == numcol-1
    +
    +     see Golub & van Loan, 1983, Eq. 4.4-9 for "Gaussian elimination with complete pivoting"
    +
    +     solves Ux=b where Ax=b and PA=LU
    +     b= [0,...,0,sign or 0]  (sign is either -1 or +1)
    +     last row of A= [0,...,0,1]
    +
    +     1) Ly=Pb == y=b since P only permutes the 0's of   b
    +
    +  design:
    +    for each row from end
    +      perform back substitution
    +      if near zero
    +        use qh_divzero for division
    +        if zero divide and not last row
    +          set tail of normal to 0
    +*/
    +void qh_backnormal(realT **rows, int numrow, int numcol, boolT sign,
    +        coordT *normal, boolT *nearzero) {
    +  int i, j;
    +  coordT *normalp, *normal_tail, *ai, *ak;
    +  realT diagonal;
    +  boolT waszero;
    +  int zerocol= -1;
    +
    +  normalp= normal + numcol - 1;
    +  *normalp--= (sign ? -1.0 : 1.0);
    +  for (i=numrow; i--; ) {
    +    *normalp= 0.0;
    +    ai= rows[i] + i + 1;
    +    ak= normalp+1;
    +    for (j=i+1; j < numcol; j++)
    +      *normalp -= *ai++ * *ak++;
    +    diagonal= (rows[i])[i];
    +    if (fabs_(diagonal) > qh MINdenom_2)
    +      *(normalp--) /= diagonal;
    +    else {
    +      waszero= False;
    +      *normalp= qh_divzero(*normalp, diagonal, qh MINdenom_1_2, &waszero);
    +      if (waszero) {
    +        zerocol= i;
    +        *(normalp--)= (sign ? -1.0 : 1.0);
    +        for (normal_tail= normalp+2; normal_tail < normal + numcol; normal_tail++)
    +          *normal_tail= 0.0;
    +      }else
    +        normalp--;
    +    }
    +  }
    +  if (zerocol != -1) {
    +    zzinc_(Zback0);
    +    *nearzero= True;
    +    trace4((qh ferr, 4005, "qh_backnormal: zero diagonal at column %d.\n", i));
    +    qh_precision("zero diagonal on back substitution");
    +  }
    +} /* backnormal */
    +
    +/*---------------------------------
    +
    +  qh_gausselim( rows, numrow, numcol, sign )
    +    Gaussian elimination with partial pivoting
    +
    +  returns:
    +    rows is upper triangular (includes row exchanges)
    +    flips sign for each row exchange
    +    sets nearzero if pivot[k] < qh.NEARzero[k], else clears it
    +
    +  notes:
    +    if nearzero, the determinant's sign may be incorrect.
    +    assumes numrow <= numcol
    +
    +  design:
    +    for each row
    +      determine pivot and exchange rows if necessary
    +      test for near zero
    +      perform gaussian elimination step
    +*/
    +void qh_gausselim(realT **rows, int numrow, int numcol, boolT *sign, boolT *nearzero) {
    +  realT *ai, *ak, *rowp, *pivotrow;
    +  realT n, pivot, pivot_abs= 0.0, temp;
    +  int i, j, k, pivoti, flip=0;
    +
    +  *nearzero= False;
    +  for (k=0; k < numrow; k++) {
    +    pivot_abs= fabs_((rows[k])[k]);
    +    pivoti= k;
    +    for (i=k+1; i < numrow; i++) {
    +      if ((temp= fabs_((rows[i])[k])) > pivot_abs) {
    +        pivot_abs= temp;
    +        pivoti= i;
    +      }
    +    }
    +    if (pivoti != k) {
    +      rowp= rows[pivoti];
    +      rows[pivoti]= rows[k];
    +      rows[k]= rowp;
    +      *sign ^= 1;
    +      flip ^= 1;
    +    }
    +    if (pivot_abs <= qh NEARzero[k]) {
    +      *nearzero= True;
    +      if (pivot_abs == 0.0) {   /* remainder of column == 0 */
    +        if (qh IStracing >= 4) {
    +          qh_fprintf(qh ferr, 8011, "qh_gausselim: 0 pivot at column %d. (%2.2g < %2.2g)\n", k, pivot_abs, qh DISTround);
    +          qh_printmatrix(qh ferr, "Matrix:", rows, numrow, numcol);
    +        }
    +        zzinc_(Zgauss0);
    +        qh_precision("zero pivot for Gaussian elimination");
    +        goto LABELnextcol;
    +      }
    +    }
    +    pivotrow= rows[k] + k;
    +    pivot= *pivotrow++;  /* signed value of pivot, and remainder of row */
    +    for (i=k+1; i < numrow; i++) {
    +      ai= rows[i] + k;
    +      ak= pivotrow;
    +      n= (*ai++)/pivot;   /* divzero() not needed since |pivot| >= |*ai| */
    +      for (j= numcol - (k+1); j--; )
    +        *ai++ -= n * *ak++;
    +    }
    +  LABELnextcol:
    +    ;
    +  }
    +  wmin_(Wmindenom, pivot_abs);  /* last pivot element */
    +  if (qh IStracing >= 5)
    +    qh_printmatrix(qh ferr, "qh_gausselem: result", rows, numrow, numcol);
    +} /* gausselim */
    +
    +
    +/*---------------------------------
    +
    +  qh_getangle( vect1, vect2 )
    +    returns the dot product of two vectors
    +    if qh.RANDOMdist, joggles result
    +
    +  notes:
    +    the angle may be > 1.0 or < -1.0 because of roundoff errors
    +
    +*/
    +realT qh_getangle(pointT *vect1, pointT *vect2) {
    +  realT angle= 0, randr;
    +  int k;
    +
    +  for (k=qh hull_dim; k--; )
    +    angle += *vect1++ * *vect2++;
    +  if (qh RANDOMdist) {
    +    randr= qh_RANDOMint;
    +    angle += (2.0 * randr / qh_RANDOMmax - 1.0) *
    +      qh RANDOMfactor;
    +  }
    +  trace4((qh ferr, 4006, "qh_getangle: %2.2g\n", angle));
    +  return(angle);
    +} /* getangle */
    +
    +
    +/*---------------------------------
    +
    +  qh_getcenter( vertices )
    +    returns arithmetic center of a set of vertices as a new point
    +
    +  notes:
    +    allocates point array for center
    +*/
    +pointT *qh_getcenter(setT *vertices) {
    +  int k;
    +  pointT *center, *coord;
    +  vertexT *vertex, **vertexp;
    +  int count= qh_setsize(vertices);
    +
    +  if (count < 2) {
    +    qh_fprintf(qh ferr, 6003, "qhull internal error (qh_getcenter): not defined for %d points\n", count);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  center= (pointT *)qh_memalloc(qh normal_size);
    +  for (k=0; k < qh hull_dim; k++) {
    +    coord= center+k;
    +    *coord= 0.0;
    +    FOREACHvertex_(vertices)
    +      *coord += vertex->point[k];
    +    *coord /= count;  /* count>=2 by QH6003 */
    +  }
    +  return(center);
    +} /* getcenter */
    +
    +
    +/*---------------------------------
    +
    +  qh_getcentrum( facet )
    +    returns the centrum for a facet as a new point
    +
    +  notes:
    +    allocates the centrum
    +*/
    +pointT *qh_getcentrum(facetT *facet) {
    +  realT dist;
    +  pointT *centrum, *point;
    +
    +  point= qh_getcenter(facet->vertices);
    +  zzinc_(Zcentrumtests);
    +  qh_distplane(point, facet, &dist);
    +  centrum= qh_projectpoint(point, facet, dist);
    +  qh_memfree(point, qh normal_size);
    +  trace4((qh ferr, 4007, "qh_getcentrum: for f%d, %d vertices dist= %2.2g\n",
    +          facet->id, qh_setsize(facet->vertices), dist));
    +  return centrum;
    +} /* getcentrum */
    +
    +
    +/*---------------------------------
    +
    +  qh_getdistance( facet, neighbor, mindist, maxdist )
    +    returns the maxdist and mindist distance of any vertex from neighbor
    +
    +  returns:
    +    the max absolute value
    +
    +  design:
    +    for each vertex of facet that is not in neighbor
    +      test the distance from vertex to neighbor
    +*/
    +realT qh_getdistance(facetT *facet, facetT *neighbor, realT *mindist, realT *maxdist) {
    +  vertexT *vertex, **vertexp;
    +  realT dist, maxd, mind;
    +
    +  FOREACHvertex_(facet->vertices)
    +    vertex->seen= False;
    +  FOREACHvertex_(neighbor->vertices)
    +    vertex->seen= True;
    +  mind= 0.0;
    +  maxd= 0.0;
    +  FOREACHvertex_(facet->vertices) {
    +    if (!vertex->seen) {
    +      zzinc_(Zbestdist);
    +      qh_distplane(vertex->point, neighbor, &dist);
    +      if (dist < mind)
    +        mind= dist;
    +      else if (dist > maxd)
    +        maxd= dist;
    +    }
    +  }
    +  *mindist= mind;
    +  *maxdist= maxd;
    +  mind= -mind;
    +  if (maxd > mind)
    +    return maxd;
    +  else
    +    return mind;
    +} /* getdistance */
    +
    +
    +/*---------------------------------
    +
    +  qh_normalize( normal, dim, toporient )
    +    normalize a vector and report if too small
    +    does not use min norm
    +
    +  see:
    +    qh_normalize2
    +*/
    +void qh_normalize(coordT *normal, int dim, boolT toporient) {
    +  qh_normalize2( normal, dim, toporient, NULL, NULL);
    +} /* normalize */
    +
    +/*---------------------------------
    +
    +  qh_normalize2( normal, dim, toporient, minnorm, ismin )
    +    normalize a vector and report if too small
    +    qh.MINdenom/MINdenom1 are the upper limits for divide overflow
    +
    +  returns:
    +    normalized vector
    +    flips sign if !toporient
    +    if minnorm non-NULL,
    +      sets ismin if normal < minnorm
    +
    +  notes:
    +    if zero norm
    +       sets all elements to sqrt(1.0/dim)
    +    if divide by zero (divzero())
    +       sets largest element to   +/-1
    +       bumps Znearlysingular
    +
    +  design:
    +    computes norm
    +    test for minnorm
    +    if not near zero
    +      normalizes normal
    +    else if zero norm
    +      sets normal to standard value
    +    else
    +      uses qh_divzero to normalize
    +      if nearzero
    +        sets norm to direction of maximum value
    +*/
    +void qh_normalize2(coordT *normal, int dim, boolT toporient,
    +            realT *minnorm, boolT *ismin) {
    +  int k;
    +  realT *colp, *maxp, norm= 0, temp, *norm1, *norm2, *norm3;
    +  boolT zerodiv;
    +
    +  norm1= normal+1;
    +  norm2= normal+2;
    +  norm3= normal+3;
    +  if (dim == 2)
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1));
    +  else if (dim == 3)
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2));
    +  else if (dim == 4) {
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2)
    +               + (*norm3)*(*norm3));
    +  }else if (dim > 4) {
    +    norm= (*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2)
    +               + (*norm3)*(*norm3);
    +    for (k=dim-4, colp=normal+4; k--; colp++)
    +      norm += (*colp) * (*colp);
    +    norm= sqrt(norm);
    +  }
    +  if (minnorm) {
    +    if (norm < *minnorm)
    +      *ismin= True;
    +    else
    +      *ismin= False;
    +  }
    +  wmin_(Wmindenom, norm);
    +  if (norm > qh MINdenom) {
    +    if (!toporient)
    +      norm= -norm;
    +    *normal /= norm;
    +    *norm1 /= norm;
    +    if (dim == 2)
    +      ; /* all done */
    +    else if (dim == 3)
    +      *norm2 /= norm;
    +    else if (dim == 4) {
    +      *norm2 /= norm;
    +      *norm3 /= norm;
    +    }else if (dim >4) {
    +      *norm2 /= norm;
    +      *norm3 /= norm;
    +      for (k=dim-4, colp=normal+4; k--; )
    +        *colp++ /= norm;
    +    }
    +  }else if (norm == 0.0) {
    +    temp= sqrt(1.0/dim);
    +    for (k=dim, colp=normal; k--; )
    +      *colp++ = temp;
    +  }else {
    +    if (!toporient)
    +      norm= -norm;
    +    for (k=dim, colp=normal; k--; colp++) { /* k used below */
    +      temp= qh_divzero(*colp, norm, qh MINdenom_1, &zerodiv);
    +      if (!zerodiv)
    +        *colp= temp;
    +      else {
    +        maxp= qh_maxabsval(normal, dim);
    +        temp= ((*maxp * norm >= 0.0) ? 1.0 : -1.0);
    +        for (k=dim, colp=normal; k--; colp++)
    +          *colp= 0.0;
    +        *maxp= temp;
    +        zzinc_(Znearlysingular);
    +        trace0((qh ferr, 1, "qh_normalize: norm=%2.2g too small during p%d\n",
    +               norm, qh furthest_id));
    +        return;
    +      }
    +    }
    +  }
    +} /* normalize */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectpoint( point, facet, dist )
    +    project point onto a facet by dist
    +
    +  returns:
    +    returns a new point
    +
    +  notes:
    +    if dist= distplane(point,facet)
    +      this projects point to hyperplane
    +    assumes qh_memfree_() is valid for normal_size
    +*/
    +pointT *qh_projectpoint(pointT *point, facetT *facet, realT dist) {
    +  pointT *newpoint, *np, *normal;
    +  int normsize= qh normal_size;
    +  int k;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_(normsize, freelistp, newpoint, pointT);
    +  np= newpoint;
    +  normal= facet->normal;
    +  for (k=qh hull_dim; k--; )
    +    *(np++)= *point++ - dist * *normal++;
    +  return(newpoint);
    +} /* projectpoint */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfacetplane( facet )
    +    sets the hyperplane for a facet
    +    if qh.RANDOMdist, joggles hyperplane
    +
    +  notes:
    +    uses global buffers qh.gm_matrix and qh.gm_row
    +    overwrites facet->normal if already defined
    +    updates Wnewvertex if PRINTstatistics
    +    sets facet->upperdelaunay if upper envelope of Delaunay triangulation
    +
    +  design:
    +    copy vertex coordinates to qh.gm_matrix/gm_row
    +    compute determinate
    +    if nearzero
    +      recompute determinate with gaussian elimination
    +      if nearzero
    +        force outside orientation by testing interior point
    +*/
    +void qh_setfacetplane(facetT *facet) {
    +  pointT *point;
    +  vertexT *vertex, **vertexp;
    +  int normsize= qh normal_size;
    +  int k,i, oldtrace= 0;
    +  realT dist;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +  coordT *coord, *gmcoord;
    +  pointT *point0= SETfirstt_(facet->vertices, vertexT)->point;
    +  boolT nearzero= False;
    +
    +  zzinc_(Zsetplane);
    +  if (!facet->normal)
    +    qh_memalloc_(normsize, freelistp, facet->normal, coordT);
    +  if (facet == qh tracefacet) {
    +    oldtrace= qh IStracing;
    +    qh IStracing= 5;
    +    qh_fprintf(qh ferr, 8012, "qh_setfacetplane: facet f%d created.\n", facet->id);
    +    qh_fprintf(qh ferr, 8013, "  Last point added to hull was p%d.", qh furthest_id);
    +    if (zzval_(Ztotmerge))
    +      qh_fprintf(qh ferr, 8014, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    qh_fprintf(qh ferr, 8015, "\n\nCurrent summary is:\n");
    +      qh_printsummary(qh ferr);
    +  }
    +  if (qh hull_dim <= 4) {
    +    i= 0;
    +    if (qh RANDOMdist) {
    +      gmcoord= qh gm_matrix;
    +      FOREACHvertex_(facet->vertices) {
    +        qh gm_row[i++]= gmcoord;
    +        coord= vertex->point;
    +        for (k=qh hull_dim; k--; )
    +          *(gmcoord++)= *coord++ * qh_randomfactor(qh RANDOMa, qh RANDOMb);
    +      }
    +    }else {
    +      FOREACHvertex_(facet->vertices)
    +       qh gm_row[i++]= vertex->point;
    +    }
    +    qh_sethyperplane_det(qh hull_dim, qh gm_row, point0, facet->toporient,
    +                facet->normal, &facet->offset, &nearzero);
    +  }
    +  if (qh hull_dim > 4 || nearzero) {
    +    i= 0;
    +    gmcoord= qh gm_matrix;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->point != point0) {
    +        qh gm_row[i++]= gmcoord;
    +        coord= vertex->point;
    +        point= point0;
    +        for (k=qh hull_dim; k--; )
    +          *(gmcoord++)= *coord++ - *point++;
    +      }
    +    }
    +    qh gm_row[i]= gmcoord;  /* for areasimplex */
    +    if (qh RANDOMdist) {
    +      gmcoord= qh gm_matrix;
    +      for (i=qh hull_dim-1; i--; ) {
    +        for (k=qh hull_dim; k--; )
    +          *(gmcoord++) *= qh_randomfactor(qh RANDOMa, qh RANDOMb);
    +      }
    +    }
    +    qh_sethyperplane_gauss(qh hull_dim, qh gm_row, point0, facet->toporient,
    +                facet->normal, &facet->offset, &nearzero);
    +    if (nearzero) {
    +      if (qh_orientoutside(facet)) {
    +        trace0((qh ferr, 2, "qh_setfacetplane: flipped orientation after testing interior_point during p%d\n", qh furthest_id));
    +      /* this is part of using Gaussian Elimination.  For example in 5-d
    +           1 1 1 1 0
    +           1 1 1 1 1
    +           0 0 0 1 0
    +           0 1 0 0 0
    +           1 0 0 0 0
    +           norm= 0.38 0.38 -0.76 0.38 0
    +         has a determinate of 1, but g.e. after subtracting pt. 0 has
    +         0's in the diagonal, even with full pivoting.  It does work
    +         if you subtract pt. 4 instead. */
    +      }
    +    }
    +  }
    +  facet->upperdelaunay= False;
    +  if (qh DELAUNAY) {
    +    if (qh UPPERdelaunay) {     /* matches qh_triangulate_facet and qh.lower_threshold in qh_initbuild */
    +      if (facet->normal[qh hull_dim -1] >= qh ANGLEround * qh_ZEROdelaunay)
    +        facet->upperdelaunay= True;
    +    }else {
    +      if (facet->normal[qh hull_dim -1] > -qh ANGLEround * qh_ZEROdelaunay)
    +        facet->upperdelaunay= True;
    +    }
    +  }
    +  if (qh PRINTstatistics || qh IStracing || qh TRACElevel || qh JOGGLEmax < REALmax) {
    +    qh old_randomdist= qh RANDOMdist;
    +    qh RANDOMdist= False;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->point != point0) {
    +        boolT istrace= False;
    +        zinc_(Zdiststat);
    +        qh_distplane(vertex->point, facet, &dist);
    +        dist= fabs_(dist);
    +        zinc_(Znewvertex);
    +        wadd_(Wnewvertex, dist);
    +        if (dist > wwval_(Wnewvertexmax)) {
    +          wwval_(Wnewvertexmax)= dist;
    +          if (dist > qh max_outside) {
    +            qh max_outside= dist;  /* used by qh_maxouter() */
    +            if (dist > qh TRACEdist)
    +              istrace= True;
    +          }
    +        }else if (-dist > qh TRACEdist)
    +          istrace= True;
    +        if (istrace) {
    +          qh_fprintf(qh ferr, 8016, "qh_setfacetplane: ====== vertex p%d(v%d) increases max_outside to %2.2g for new facet f%d last p%d\n",
    +                qh_pointid(vertex->point), vertex->id, dist, facet->id, qh furthest_id);
    +          qh_errprint("DISTANT", facet, NULL, NULL, NULL);
    +        }
    +      }
    +    }
    +    qh RANDOMdist= qh old_randomdist;
    +  }
    +  if (qh IStracing >= 3) {
    +    qh_fprintf(qh ferr, 8017, "qh_setfacetplane: f%d offset %2.2g normal: ",
    +             facet->id, facet->offset);
    +    for (k=0; k < qh hull_dim; k++)
    +      qh_fprintf(qh ferr, 8018, "%2.2g ", facet->normal[k]);
    +    qh_fprintf(qh ferr, 8019, "\n");
    +  }
    +  if (facet == qh tracefacet)
    +    qh IStracing= oldtrace;
    +} /* setfacetplane */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethyperplane_det( dim, rows, point0, toporient, normal, offset, nearzero )
    +    given dim X dim array indexed by rows[], one row per point,
    +        toporient(flips all signs),
    +        and point0 (any row)
    +    set normalized hyperplane equation from oriented simplex
    +
    +  returns:
    +    normal (normalized)
    +    offset (places point0 on the hyperplane)
    +    sets nearzero if hyperplane not through points
    +
    +  notes:
    +    only defined for dim == 2..4
    +    rows[] is not modified
    +    solves det(P-V_0, V_n-V_0, ..., V_1-V_0)=0, i.e. every point is on hyperplane
    +    see Bower & Woodworth, A programmer's geometry, Butterworths 1983.
    +
    +  derivation of 3-d minnorm
    +    Goal: all vertices V_i within qh.one_merge of hyperplane
    +    Plan: exactly translate the facet so that V_0 is the origin
    +          exactly rotate the facet so that V_1 is on the x-axis and y_2=0.
    +          exactly rotate the effective perturbation to only effect n_0
    +             this introduces a factor of sqrt(3)
    +    n_0 = ((y_2-y_0)*(z_1-z_0) - (z_2-z_0)*(y_1-y_0)) / norm
    +    Let M_d be the max coordinate difference
    +    Let M_a be the greater of M_d and the max abs. coordinate
    +    Let u be machine roundoff and distround be max error for distance computation
    +    The max error for n_0 is sqrt(3) u M_a M_d / norm.  n_1 is approx. 1 and n_2 is approx. 0
    +    The max error for distance of V_1 is sqrt(3) u M_a M_d M_d / norm.  Offset=0 at origin
    +    Then minnorm = 1.8 u M_a M_d M_d / qh.ONEmerge
    +    Note that qh.one_merge is approx. 45.5 u M_a and norm is usually about M_d M_d
    +
    +  derivation of 4-d minnorm
    +    same as above except rotate the facet so that V_1 on x-axis and w_2, y_3, w_3=0
    +     [if two vertices fixed on x-axis, can rotate the other two in yzw.]
    +    n_0 = det3_(...) = y_2 det2_(z_1, w_1, z_3, w_3) = - y_2 w_1 z_3
    +     [all other terms contain at least two factors nearly zero.]
    +    The max error for n_0 is sqrt(4) u M_a M_d M_d / norm
    +    Then minnorm = 2 u M_a M_d M_d M_d / qh.ONEmerge
    +    Note that qh.one_merge is approx. 82 u M_a and norm is usually about M_d M_d M_d
    +*/
    +void qh_sethyperplane_det(int dim, coordT **rows, coordT *point0,
    +          boolT toporient, coordT *normal, realT *offset, boolT *nearzero) {
    +  realT maxround, dist;
    +  int i;
    +  pointT *point;
    +
    +
    +  if (dim == 2) {
    +    normal[0]= dY(1,0);
    +    normal[1]= dX(0,1);
    +    qh_normalize2(normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0]+point0[1]*normal[1]);
    +    *nearzero= False;  /* since nearzero norm => incident points */
    +  }else if (dim == 3) {
    +    normal[0]= det2_(dY(2,0), dZ(2,0),
    +                     dY(1,0), dZ(1,0));
    +    normal[1]= det2_(dX(1,0), dZ(1,0),
    +                     dX(2,0), dZ(2,0));
    +    normal[2]= det2_(dX(2,0), dY(2,0),
    +                     dX(1,0), dY(1,0));
    +    qh_normalize2(normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0] + point0[1]*normal[1]
    +               + point0[2]*normal[2]);
    +    maxround= qh DISTround;
    +    for (i=dim; i--; ) {
    +      point= rows[i];
    +      if (point != point0) {
    +        dist= *offset + (point[0]*normal[0] + point[1]*normal[1]
    +               + point[2]*normal[2]);
    +        if (dist > maxround || dist < -maxround) {
    +          *nearzero= True;
    +          break;
    +        }
    +      }
    +    }
    +  }else if (dim == 4) {
    +    normal[0]= - det3_(dY(2,0), dZ(2,0), dW(2,0),
    +                        dY(1,0), dZ(1,0), dW(1,0),
    +                        dY(3,0), dZ(3,0), dW(3,0));
    +    normal[1]=   det3_(dX(2,0), dZ(2,0), dW(2,0),
    +                        dX(1,0), dZ(1,0), dW(1,0),
    +                        dX(3,0), dZ(3,0), dW(3,0));
    +    normal[2]= - det3_(dX(2,0), dY(2,0), dW(2,0),
    +                        dX(1,0), dY(1,0), dW(1,0),
    +                        dX(3,0), dY(3,0), dW(3,0));
    +    normal[3]=   det3_(dX(2,0), dY(2,0), dZ(2,0),
    +                        dX(1,0), dY(1,0), dZ(1,0),
    +                        dX(3,0), dY(3,0), dZ(3,0));
    +    qh_normalize2(normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0] + point0[1]*normal[1]
    +               + point0[2]*normal[2] + point0[3]*normal[3]);
    +    maxround= qh DISTround;
    +    for (i=dim; i--; ) {
    +      point= rows[i];
    +      if (point != point0) {
    +        dist= *offset + (point[0]*normal[0] + point[1]*normal[1]
    +               + point[2]*normal[2] + point[3]*normal[3]);
    +        if (dist > maxround || dist < -maxround) {
    +          *nearzero= True;
    +          break;
    +        }
    +      }
    +    }
    +  }
    +  if (*nearzero) {
    +    zzinc_(Zminnorm);
    +    trace0((qh ferr, 3, "qh_sethyperplane_det: degenerate norm during p%d.\n", qh furthest_id));
    +    zzinc_(Znearlysingular);
    +  }
    +} /* sethyperplane_det */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethyperplane_gauss( dim, rows, point0, toporient, normal, offset, nearzero )
    +    given(dim-1) X dim array of rows[i]= V_{i+1} - V_0 (point0)
    +    set normalized hyperplane equation from oriented simplex
    +
    +  returns:
    +    normal (normalized)
    +    offset (places point0 on the hyperplane)
    +
    +  notes:
    +    if nearzero
    +      orientation may be incorrect because of incorrect sign flips in gausselim
    +    solves [V_n-V_0,...,V_1-V_0, 0 .. 0 1] * N == [0 .. 0 1]
    +        or [V_n-V_0,...,V_1-V_0, 0 .. 0 1] * N == [0]
    +    i.e., N is normal to the hyperplane, and the unnormalized
    +        distance to [0 .. 1] is either 1 or   0
    +
    +  design:
    +    perform gaussian elimination
    +    flip sign for negative values
    +    perform back substitution
    +    normalize result
    +    compute offset
    +*/
    +void qh_sethyperplane_gauss(int dim, coordT **rows, pointT *point0,
    +                boolT toporient, coordT *normal, coordT *offset, boolT *nearzero) {
    +  coordT *pointcoord, *normalcoef;
    +  int k;
    +  boolT sign= toporient, nearzero2= False;
    +
    +  qh_gausselim(rows, dim-1, dim, &sign, nearzero);
    +  for (k=dim-1; k--; ) {
    +    if ((rows[k])[k] < 0)
    +      sign ^= 1;
    +  }
    +  if (*nearzero) {
    +    zzinc_(Znearlysingular);
    +    trace0((qh ferr, 4, "qh_sethyperplane_gauss: nearly singular or axis parallel hyperplane during p%d.\n", qh furthest_id));
    +    qh_backnormal(rows, dim-1, dim, sign, normal, &nearzero2);
    +  }else {
    +    qh_backnormal(rows, dim-1, dim, sign, normal, &nearzero2);
    +    if (nearzero2) {
    +      zzinc_(Znearlysingular);
    +      trace0((qh ferr, 5, "qh_sethyperplane_gauss: singular or axis parallel hyperplane at normalization during p%d.\n", qh furthest_id));
    +    }
    +  }
    +  if (nearzero2)
    +    *nearzero= True;
    +  qh_normalize2(normal, dim, True, NULL, NULL);
    +  pointcoord= point0;
    +  normalcoef= normal;
    +  *offset= -(*pointcoord++ * *normalcoef++);
    +  for (k=dim-1; k--; )
    +    *offset -= *pointcoord++ * *normalcoef++;
    +} /* sethyperplane_gauss */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/geom.h b/xs/src/qhull/src/libqhull/geom.h
    new file mode 100644
    index 0000000000..16ef48d2d7
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/geom.h
    @@ -0,0 +1,176 @@
    +/*
      ---------------------------------
    +
    +  geom.h
    +    header file for geometric routines
    +
    +   see qh-geom.htm and geom.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/geom.h#1 $$Change: 1981 $
    +   $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFgeom
    +#define qhDEFgeom 1
    +
    +#include "libqhull.h"
    +
    +/* ============ -macros- ======================== */
    +
    +/*----------------------------------
    +
    +  fabs_(a)
    +    returns the absolute value of a
    +*/
    +#define fabs_( a ) ((( a ) < 0 ) ? -( a ):( a ))
    +
    +/*----------------------------------
    +
    +  fmax_(a,b)
    +    returns the maximum value of a and b
    +*/
    +#define fmax_( a,b )  ( ( a ) < ( b ) ? ( b ) : ( a ) )
    +
    +/*----------------------------------
    +
    +  fmin_(a,b)
    +    returns the minimum value of a and b
    +*/
    +#define fmin_( a,b )  ( ( a ) > ( b ) ? ( b ) : ( a ) )
    +
    +/*----------------------------------
    +
    +  maximize_(maxval, val)
    +    set maxval to val if val is greater than maxval
    +*/
    +#define maximize_( maxval, val ) { if (( maxval ) < ( val )) ( maxval )= ( val ); }
    +
    +/*----------------------------------
    +
    +  minimize_(minval, val)
    +    set minval to val if val is less than minval
    +*/
    +#define minimize_( minval, val ) { if (( minval ) > ( val )) ( minval )= ( val ); }
    +
    +/*----------------------------------
    +
    +  det2_(a1, a2,
    +        b1, b2)
    +
    +    compute a 2-d determinate
    +*/
    +#define det2_( a1,a2,b1,b2 ) (( a1 )*( b2 ) - ( a2 )*( b1 ))
    +
    +/*----------------------------------
    +
    +  det3_(a1, a2, a3,
    +       b1, b2, b3,
    +       c1, c2, c3)
    +
    +    compute a 3-d determinate
    +*/
    +#define det3_( a1,a2,a3,b1,b2,b3,c1,c2,c3 ) ( ( a1 )*det2_( b2,b3,c2,c3 ) \
    +                - ( b1 )*det2_( a2,a3,c2,c3 ) + ( c1 )*det2_( a2,a3,b2,b3 ) )
    +
    +/*----------------------------------
    +
    +  dX( p1, p2 )
    +  dY( p1, p2 )
    +  dZ( p1, p2 )
    +
    +    given two indices into rows[],
    +
    +    compute the difference between X, Y, or Z coordinates
    +*/
    +#define dX( p1,p2 )  ( *( rows[p1] ) - *( rows[p2] ))
    +#define dY( p1,p2 )  ( *( rows[p1]+1 ) - *( rows[p2]+1 ))
    +#define dZ( p1,p2 )  ( *( rows[p1]+2 ) - *( rows[p2]+2 ))
    +#define dW( p1,p2 )  ( *( rows[p1]+3 ) - *( rows[p2]+3 ))
    +
    +/*============= prototypes in alphabetical order, infrequent at end ======= */
    +
    +void    qh_backnormal(realT **rows, int numrow, int numcol, boolT sign, coordT *normal, boolT *nearzero);
    +void    qh_distplane(pointT *point, facetT *facet, realT *dist);
    +facetT *qh_findbest(pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT isnewfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart);
    +facetT *qh_findbesthorizon(boolT ischeckmax, pointT *point,
    +                     facetT *startfacet, boolT noupper, realT *bestdist, int *numpart);
    +facetT *qh_findbestnew(pointT *point, facetT *startfacet, realT *dist,
    +                     boolT bestoutside, boolT *isoutside, int *numpart);
    +void    qh_gausselim(realT **rows, int numrow, int numcol, boolT *sign, boolT *nearzero);
    +realT   qh_getangle(pointT *vect1, pointT *vect2);
    +pointT *qh_getcenter(setT *vertices);
    +pointT *qh_getcentrum(facetT *facet);
    +realT   qh_getdistance(facetT *facet, facetT *neighbor, realT *mindist, realT *maxdist);
    +void    qh_normalize(coordT *normal, int dim, boolT toporient);
    +void    qh_normalize2(coordT *normal, int dim, boolT toporient,
    +            realT *minnorm, boolT *ismin);
    +pointT *qh_projectpoint(pointT *point, facetT *facet, realT dist);
    +
    +void    qh_setfacetplane(facetT *newfacets);
    +void    qh_sethyperplane_det(int dim, coordT **rows, coordT *point0,
    +              boolT toporient, coordT *normal, realT *offset, boolT *nearzero);
    +void    qh_sethyperplane_gauss(int dim, coordT **rows, pointT *point0,
    +             boolT toporient, coordT *normal, coordT *offset, boolT *nearzero);
    +boolT   qh_sharpnewfacets(void);
    +
    +/*========= infrequently used code in geom2.c =============*/
    +
    +coordT *qh_copypoints(coordT *points, int numpoints, int dimension);
    +void    qh_crossproduct(int dim, realT vecA[3], realT vecB[3], realT vecC[3]);
    +realT   qh_determinant(realT **rows, int dim, boolT *nearzero);
    +realT   qh_detjoggle(pointT *points, int numpoints, int dimension);
    +void    qh_detroundoff(void);
    +realT   qh_detsimplex(pointT *apex, setT *points, int dim, boolT *nearzero);
    +realT   qh_distnorm(int dim, pointT *point, pointT *normal, realT *offsetp);
    +realT   qh_distround(int dimension, realT maxabs, realT maxsumabs);
    +realT   qh_divzero(realT numer, realT denom, realT mindenom1, boolT *zerodiv);
    +realT   qh_facetarea(facetT *facet);
    +realT   qh_facetarea_simplex(int dim, coordT *apex, setT *vertices,
    +          vertexT *notvertex,  boolT toporient, coordT *normal, realT *offset);
    +pointT *qh_facetcenter(setT *vertices);
    +facetT *qh_findgooddist(pointT *point, facetT *facetA, realT *distp, facetT **facetlist);
    +void    qh_getarea(facetT *facetlist);
    +boolT   qh_gram_schmidt(int dim, realT **rows);
    +boolT   qh_inthresholds(coordT *normal, realT *angle);
    +void    qh_joggleinput(void);
    +realT  *qh_maxabsval(realT *normal, int dim);
    +setT   *qh_maxmin(pointT *points, int numpoints, int dimension);
    +realT   qh_maxouter(void);
    +void    qh_maxsimplex(int dim, setT *maxpoints, pointT *points, int numpoints, setT **simplex);
    +realT   qh_minabsval(realT *normal, int dim);
    +int     qh_mindiff(realT *vecA, realT *vecB, int dim);
    +boolT   qh_orientoutside(facetT *facet);
    +void    qh_outerinner(facetT *facet, realT *outerplane, realT *innerplane);
    +coordT  qh_pointdist(pointT *point1, pointT *point2, int dim);
    +void    qh_printmatrix(FILE *fp, const char *string, realT **rows, int numrow, int numcol);
    +void    qh_printpoints(FILE *fp, const char *string, setT *points);
    +void    qh_projectinput(void);
    +void    qh_projectpoints(signed char *project, int n, realT *points,
    +             int numpoints, int dim, realT *newpoints, int newdim);
    +void    qh_rotateinput(realT **rows);
    +void    qh_rotatepoints(realT *points, int numpoints, int dim, realT **rows);
    +void    qh_scaleinput(void);
    +void    qh_scalelast(coordT *points, int numpoints, int dim, coordT low,
    +                   coordT high, coordT newhigh);
    +void    qh_scalepoints(pointT *points, int numpoints, int dim,
    +                realT *newlows, realT *newhighs);
    +boolT   qh_sethalfspace(int dim, coordT *coords, coordT **nextp,
    +              coordT *normal, coordT *offset, coordT *feasible);
    +coordT *qh_sethalfspace_all(int dim, int count, coordT *halfspaces, pointT *feasible);
    +pointT *qh_voronoi_center(int dim, setT *points);
    +
    +#endif /* qhDEFgeom */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/geom2.c b/xs/src/qhull/src/libqhull/geom2.c
    new file mode 100644
    index 0000000000..82ec4936ef
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/geom2.c
    @@ -0,0 +1,2094 @@
    +/*
      ---------------------------------
    +
    +
    +   geom2.c
    +   infrequently used geometric routines of qhull
    +
    +   see qh-geom.htm and geom.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/geom2.c#6 $$Change: 2065 $
    +   $DateTime: 2016/01/18 13:51:04 $$Author: bbarber $
    +
    +   frequently used code goes into geom.c
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*================== functions in alphabetic order ============*/
    +
    +/*---------------------------------
    +
    +  qh_copypoints( points, numpoints, dimension)
    +    return qh_malloc'd copy of points
    +  notes:
    +    qh_free the returned points to avoid a memory leak
    +*/
    +coordT *qh_copypoints(coordT *points, int numpoints, int dimension) {
    +  int size;
    +  coordT *newpoints;
    +
    +  size= numpoints * dimension * (int)sizeof(coordT);
    +  if (!(newpoints=(coordT*)qh_malloc((size_t)size))) {
    +    qh_fprintf(qh ferr, 6004, "qhull error: insufficient memory to copy %d points\n",
    +        numpoints);
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  memcpy((char *)newpoints, (char *)points, (size_t)size); /* newpoints!=0 by QH6004 */
    +  return newpoints;
    +} /* copypoints */
    +
    +/*---------------------------------
    +
    +  qh_crossproduct( dim, vecA, vecB, vecC )
    +    crossproduct of 2 dim vectors
    +    C= A x B
    +
    +  notes:
    +    from Glasner, Graphics Gems I, p. 639
    +    only defined for dim==3
    +*/
    +void qh_crossproduct(int dim, realT vecA[3], realT vecB[3], realT vecC[3]){
    +
    +  if (dim == 3) {
    +    vecC[0]=   det2_(vecA[1], vecA[2],
    +                     vecB[1], vecB[2]);
    +    vecC[1]= - det2_(vecA[0], vecA[2],
    +                     vecB[0], vecB[2]);
    +    vecC[2]=   det2_(vecA[0], vecA[1],
    +                     vecB[0], vecB[1]);
    +  }
    +} /* vcross */
    +
    +/*---------------------------------
    +
    +  qh_determinant( rows, dim, nearzero )
    +    compute signed determinant of a square matrix
    +    uses qh.NEARzero to test for degenerate matrices
    +
    +  returns:
    +    determinant
    +    overwrites rows and the matrix
    +    if dim == 2 or 3
    +      nearzero iff determinant < qh NEARzero[dim-1]
    +      (!quite correct, not critical)
    +    if dim >= 4
    +      nearzero iff diagonal[k] < qh NEARzero[k]
    +*/
    +realT qh_determinant(realT **rows, int dim, boolT *nearzero) {
    +  realT det=0;
    +  int i;
    +  boolT sign= False;
    +
    +  *nearzero= False;
    +  if (dim < 2) {
    +    qh_fprintf(qh ferr, 6005, "qhull internal error (qh_determinate): only implemented for dimension >= 2\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }else if (dim == 2) {
    +    det= det2_(rows[0][0], rows[0][1],
    +                 rows[1][0], rows[1][1]);
    +    if (fabs_(det) < 10*qh NEARzero[1])  /* not really correct, what should this be? */
    +      *nearzero= True;
    +  }else if (dim == 3) {
    +    det= det3_(rows[0][0], rows[0][1], rows[0][2],
    +                 rows[1][0], rows[1][1], rows[1][2],
    +                 rows[2][0], rows[2][1], rows[2][2]);
    +    if (fabs_(det) < 10*qh NEARzero[2])  /* what should this be?  det 5.5e-12 was flat for qh_maxsimplex of qdelaunay 0,0 27,27 -36,36 -9,63  */
    +      *nearzero= True;
    +  }else {
    +    qh_gausselim(rows, dim, dim, &sign, nearzero);  /* if nearzero, diagonal still ok*/
    +    det= 1.0;
    +    for (i=dim; i--; )
    +      det *= (rows[i])[i];
    +    if (sign)
    +      det= -det;
    +  }
    +  return det;
    +} /* determinant */
    +
    +/*---------------------------------
    +
    +  qh_detjoggle( points, numpoints, dimension )
    +    determine default max joggle for point array
    +      as qh_distround * qh_JOGGLEdefault
    +
    +  returns:
    +    initial value for JOGGLEmax from points and REALepsilon
    +
    +  notes:
    +    computes DISTround since qh_maxmin not called yet
    +    if qh SCALElast, last dimension will be scaled later to MAXwidth
    +
    +    loop duplicated from qh_maxmin
    +*/
    +realT qh_detjoggle(pointT *points, int numpoints, int dimension) {
    +  realT abscoord, distround, joggle, maxcoord, mincoord;
    +  pointT *point, *pointtemp;
    +  realT maxabs= -REALmax;
    +  realT sumabs= 0;
    +  realT maxwidth= 0;
    +  int k;
    +
    +  for (k=0; k < dimension; k++) {
    +    if (qh SCALElast && k == dimension-1)
    +      abscoord= maxwidth;
    +    else if (qh DELAUNAY && k == dimension-1) /* will qh_setdelaunay() */
    +      abscoord= 2 * maxabs * maxabs;  /* may be low by qh hull_dim/2 */
    +    else {
    +      maxcoord= -REALmax;
    +      mincoord= REALmax;
    +      FORALLpoint_(points, numpoints) {
    +        maximize_(maxcoord, point[k]);
    +        minimize_(mincoord, point[k]);
    +      }
    +      maximize_(maxwidth, maxcoord-mincoord);
    +      abscoord= fmax_(maxcoord, -mincoord);
    +    }
    +    sumabs += abscoord;
    +    maximize_(maxabs, abscoord);
    +  } /* for k */
    +  distround= qh_distround(qh hull_dim, maxabs, sumabs);
    +  joggle= distround * qh_JOGGLEdefault;
    +  maximize_(joggle, REALepsilon * qh_JOGGLEdefault);
    +  trace2((qh ferr, 2001, "qh_detjoggle: joggle=%2.2g maxwidth=%2.2g\n", joggle, maxwidth));
    +  return joggle;
    +} /* detjoggle */
    +
    +/*---------------------------------
    +
    +  qh_detroundoff()
    +    determine maximum roundoff errors from
    +      REALepsilon, REALmax, REALmin, qh.hull_dim, qh.MAXabs_coord,
    +      qh.MAXsumcoord, qh.MAXwidth, qh.MINdenom_1
    +
    +    accounts for qh.SETroundoff, qh.RANDOMdist, qh MERGEexact
    +      qh.premerge_cos, qh.postmerge_cos, qh.premerge_centrum,
    +      qh.postmerge_centrum, qh.MINoutside,
    +      qh_RATIOnearinside, qh_COPLANARratio, qh_WIDEcoplanar
    +
    +  returns:
    +    sets qh.DISTround, etc. (see below)
    +    appends precision constants to qh.qhull_options
    +
    +  see:
    +    qh_maxmin() for qh.NEARzero
    +
    +  design:
    +    determine qh.DISTround for distance computations
    +    determine minimum denominators for qh_divzero
    +    determine qh.ANGLEround for angle computations
    +    adjust qh.premerge_cos,... for roundoff error
    +    determine qh.ONEmerge for maximum error due to a single merge
    +    determine qh.NEARinside, qh.MAXcoplanar, qh.MINvisible,
    +      qh.MINoutside, qh.WIDEfacet
    +    initialize qh.max_vertex and qh.minvertex
    +*/
    +void qh_detroundoff(void) {
    +
    +  qh_option("_max-width", NULL, &qh MAXwidth);
    +  if (!qh SETroundoff) {
    +    qh DISTround= qh_distround(qh hull_dim, qh MAXabs_coord, qh MAXsumcoord);
    +    if (qh RANDOMdist)
    +      qh DISTround += qh RANDOMfactor * qh MAXabs_coord;
    +    qh_option("Error-roundoff", NULL, &qh DISTround);
    +  }
    +  qh MINdenom= qh MINdenom_1 * qh MAXabs_coord;
    +  qh MINdenom_1_2= sqrt(qh MINdenom_1 * qh hull_dim) ;  /* if will be normalized */
    +  qh MINdenom_2= qh MINdenom_1_2 * qh MAXabs_coord;
    +                                              /* for inner product */
    +  qh ANGLEround= 1.01 * qh hull_dim * REALepsilon;
    +  if (qh RANDOMdist)
    +    qh ANGLEround += qh RANDOMfactor;
    +  if (qh premerge_cos < REALmax/2) {
    +    qh premerge_cos -= qh ANGLEround;
    +    if (qh RANDOMdist)
    +      qh_option("Angle-premerge-with-random", NULL, &qh premerge_cos);
    +  }
    +  if (qh postmerge_cos < REALmax/2) {
    +    qh postmerge_cos -= qh ANGLEround;
    +    if (qh RANDOMdist)
    +      qh_option("Angle-postmerge-with-random", NULL, &qh postmerge_cos);
    +  }
    +  qh premerge_centrum += 2 * qh DISTround;    /*2 for centrum and distplane()*/
    +  qh postmerge_centrum += 2 * qh DISTround;
    +  if (qh RANDOMdist && (qh MERGEexact || qh PREmerge))
    +    qh_option("Centrum-premerge-with-random", NULL, &qh premerge_centrum);
    +  if (qh RANDOMdist && qh POSTmerge)
    +    qh_option("Centrum-postmerge-with-random", NULL, &qh postmerge_centrum);
    +  { /* compute ONEmerge, max vertex offset for merging simplicial facets */
    +    realT maxangle= 1.0, maxrho;
    +
    +    minimize_(maxangle, qh premerge_cos);
    +    minimize_(maxangle, qh postmerge_cos);
    +    /* max diameter * sin theta + DISTround for vertex to its hyperplane */
    +    qh ONEmerge= sqrt((realT)qh hull_dim) * qh MAXwidth *
    +      sqrt(1.0 - maxangle * maxangle) + qh DISTround;
    +    maxrho= qh hull_dim * qh premerge_centrum + qh DISTround;
    +    maximize_(qh ONEmerge, maxrho);
    +    maxrho= qh hull_dim * qh postmerge_centrum + qh DISTround;
    +    maximize_(qh ONEmerge, maxrho);
    +    if (qh MERGING)
    +      qh_option("_one-merge", NULL, &qh ONEmerge);
    +  }
    +  qh NEARinside= qh ONEmerge * qh_RATIOnearinside; /* only used if qh KEEPnearinside */
    +  if (qh JOGGLEmax < REALmax/2 && (qh KEEPcoplanar || qh KEEPinside)) {
    +    realT maxdist;             /* adjust qh.NEARinside for joggle */
    +    qh KEEPnearinside= True;
    +    maxdist= sqrt((realT)qh hull_dim) * qh JOGGLEmax + qh DISTround;
    +    maxdist= 2*maxdist;        /* vertex and coplanar point can joggle in opposite directions */
    +    maximize_(qh NEARinside, maxdist);  /* must agree with qh_nearcoplanar() */
    +  }
    +  if (qh KEEPnearinside)
    +    qh_option("_near-inside", NULL, &qh NEARinside);
    +  if (qh JOGGLEmax < qh DISTround) {
    +    qh_fprintf(qh ferr, 6006, "qhull error: the joggle for 'QJn', %.2g, is below roundoff for distance computations, %.2g\n",
    +         qh JOGGLEmax, qh DISTround);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh MINvisible > REALmax/2) {
    +    if (!qh MERGING)
    +      qh MINvisible= qh DISTround;
    +    else if (qh hull_dim <= 3)
    +      qh MINvisible= qh premerge_centrum;
    +    else
    +      qh MINvisible= qh_COPLANARratio * qh premerge_centrum;
    +    if (qh APPROXhull && qh MINvisible > qh MINoutside)
    +      qh MINvisible= qh MINoutside;
    +    qh_option("Visible-distance", NULL, &qh MINvisible);
    +  }
    +  if (qh MAXcoplanar > REALmax/2) {
    +    qh MAXcoplanar= qh MINvisible;
    +    qh_option("U-coplanar-distance", NULL, &qh MAXcoplanar);
    +  }
    +  if (!qh APPROXhull) {             /* user may specify qh MINoutside */
    +    qh MINoutside= 2 * qh MINvisible;
    +    if (qh premerge_cos < REALmax/2)
    +      maximize_(qh MINoutside, (1- qh premerge_cos) * qh MAXabs_coord);
    +    qh_option("Width-outside", NULL, &qh MINoutside);
    +  }
    +  qh WIDEfacet= qh MINoutside;
    +  maximize_(qh WIDEfacet, qh_WIDEcoplanar * qh MAXcoplanar);
    +  maximize_(qh WIDEfacet, qh_WIDEcoplanar * qh MINvisible);
    +  qh_option("_wide-facet", NULL, &qh WIDEfacet);
    +  if (qh MINvisible > qh MINoutside + 3 * REALepsilon
    +  && !qh BESToutside && !qh FORCEoutput)
    +    qh_fprintf(qh ferr, 7001, "qhull input warning: minimum visibility V%.2g is greater than \nminimum outside W%.2g.  Flipped facets are likely.\n",
    +             qh MINvisible, qh MINoutside);
    +  qh max_vertex= qh DISTround;
    +  qh min_vertex= -qh DISTround;
    +  /* numeric constants reported in printsummary */
    +} /* detroundoff */
    +
    +/*---------------------------------
    +
    +  qh_detsimplex( apex, points, dim, nearzero )
    +    compute determinant of a simplex with point apex and base points
    +
    +  returns:
    +     signed determinant and nearzero from qh_determinant
    +
    +  notes:
    +     uses qh.gm_matrix/qh.gm_row (assumes they're big enough)
    +
    +  design:
    +    construct qm_matrix by subtracting apex from points
    +    compute determinate
    +*/
    +realT qh_detsimplex(pointT *apex, setT *points, int dim, boolT *nearzero) {
    +  pointT *coorda, *coordp, *gmcoord, *point, **pointp;
    +  coordT **rows;
    +  int k,  i=0;
    +  realT det;
    +
    +  zinc_(Zdetsimplex);
    +  gmcoord= qh gm_matrix;
    +  rows= qh gm_row;
    +  FOREACHpoint_(points) {
    +    if (i == dim)
    +      break;
    +    rows[i++]= gmcoord;
    +    coordp= point;
    +    coorda= apex;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= *coordp++ - *coorda++;
    +  }
    +  if (i < dim) {
    +    qh_fprintf(qh ferr, 6007, "qhull internal error (qh_detsimplex): #points %d < dimension %d\n",
    +               i, dim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  det= qh_determinant(rows, dim, nearzero);
    +  trace2((qh ferr, 2002, "qh_detsimplex: det=%2.2g for point p%d, dim %d, nearzero? %d\n",
    +          det, qh_pointid(apex), dim, *nearzero));
    +  return det;
    +} /* detsimplex */
    +
    +/*---------------------------------
    +
    +  qh_distnorm( dim, point, normal, offset )
    +    return distance from point to hyperplane at normal/offset
    +
    +  returns:
    +    dist
    +
    +  notes:
    +    dist > 0 if point is outside of hyperplane
    +
    +  see:
    +    qh_distplane in geom.c
    +*/
    +realT qh_distnorm(int dim, pointT *point, pointT *normal, realT *offsetp) {
    +  coordT *normalp= normal, *coordp= point;
    +  realT dist;
    +  int k;
    +
    +  dist= *offsetp;
    +  for (k=dim; k--; )
    +    dist += *(coordp++) * *(normalp++);
    +  return dist;
    +} /* distnorm */
    +
    +/*---------------------------------
    +
    +  qh_distround(dimension, maxabs, maxsumabs )
    +    compute maximum round-off error for a distance computation
    +      to a normalized hyperplane
    +    maxabs is the maximum absolute value of a coordinate
    +    maxsumabs is the maximum possible sum of absolute coordinate values
    +
    +  returns:
    +    max dist round for REALepsilon
    +
    +  notes:
    +    calculate roundoff error according to Golub & van Loan, 1983, Lemma 3.2-1, "Rounding Errors"
    +    use sqrt(dim) since one vector is normalized
    +      or use maxsumabs since one vector is < 1
    +*/
    +realT qh_distround(int dimension, realT maxabs, realT maxsumabs) {
    +  realT maxdistsum, maxround;
    +
    +  maxdistsum= sqrt((realT)dimension) * maxabs;
    +  minimize_( maxdistsum, maxsumabs);
    +  maxround= REALepsilon * (dimension * maxdistsum * 1.01 + maxabs);
    +              /* adds maxabs for offset */
    +  trace4((qh ferr, 4008, "qh_distround: %2.2g maxabs %2.2g maxsumabs %2.2g maxdistsum %2.2g\n",
    +                 maxround, maxabs, maxsumabs, maxdistsum));
    +  return maxround;
    +} /* distround */
    +
    +/*---------------------------------
    +
    +  qh_divzero( numer, denom, mindenom1, zerodiv )
    +    divide by a number that's nearly zero
    +    mindenom1= minimum denominator for dividing into 1.0
    +
    +  returns:
    +    quotient
    +    sets zerodiv and returns 0.0 if it would overflow
    +
    +  design:
    +    if numer is nearly zero and abs(numer) < abs(denom)
    +      return numer/denom
    +    else if numer is nearly zero
    +      return 0 and zerodiv
    +    else if denom/numer non-zero
    +      return numer/denom
    +    else
    +      return 0 and zerodiv
    +*/
    +realT qh_divzero(realT numer, realT denom, realT mindenom1, boolT *zerodiv) {
    +  realT temp, numerx, denomx;
    +
    +
    +  if (numer < mindenom1 && numer > -mindenom1) {
    +    numerx= fabs_(numer);
    +    denomx= fabs_(denom);
    +    if (numerx < denomx) {
    +      *zerodiv= False;
    +      return numer/denom;
    +    }else {
    +      *zerodiv= True;
    +      return 0.0;
    +    }
    +  }
    +  temp= denom/numer;
    +  if (temp > mindenom1 || temp < -mindenom1) {
    +    *zerodiv= False;
    +    return numer/denom;
    +  }else {
    +    *zerodiv= True;
    +    return 0.0;
    +  }
    +} /* divzero */
    +
    +
    +/*---------------------------------
    +
    +  qh_facetarea( facet )
    +    return area for a facet
    +
    +  notes:
    +    if non-simplicial,
    +      uses centrum to triangulate facet and sums the projected areas.
    +    if (qh DELAUNAY),
    +      computes projected area instead for last coordinate
    +    assumes facet->normal exists
    +    projecting tricoplanar facets to the hyperplane does not appear to make a difference
    +
    +  design:
    +    if simplicial
    +      compute area
    +    else
    +      for each ridge
    +        compute area from centrum to ridge
    +    negate area if upper Delaunay facet
    +*/
    +realT qh_facetarea(facetT *facet) {
    +  vertexT *apex;
    +  pointT *centrum;
    +  realT area= 0.0;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (facet->simplicial) {
    +    apex= SETfirstt_(facet->vertices, vertexT);
    +    area= qh_facetarea_simplex(qh hull_dim, apex->point, facet->vertices,
    +                    apex, facet->toporient, facet->normal, &facet->offset);
    +  }else {
    +    if (qh CENTERtype == qh_AScentrum)
    +      centrum= facet->center;
    +    else
    +      centrum= qh_getcentrum(facet);
    +    FOREACHridge_(facet->ridges)
    +      area += qh_facetarea_simplex(qh hull_dim, centrum, ridge->vertices,
    +                 NULL, (boolT)(ridge->top == facet),  facet->normal, &facet->offset);
    +    if (qh CENTERtype != qh_AScentrum)
    +      qh_memfree(centrum, qh normal_size);
    +  }
    +  if (facet->upperdelaunay && qh DELAUNAY)
    +    area= -area;  /* the normal should be [0,...,1] */
    +  trace4((qh ferr, 4009, "qh_facetarea: f%d area %2.2g\n", facet->id, area));
    +  return area;
    +} /* facetarea */
    +
    +/*---------------------------------
    +
    +  qh_facetarea_simplex( dim, apex, vertices, notvertex, toporient, normal, offset )
    +    return area for a simplex defined by
    +      an apex, a base of vertices, an orientation, and a unit normal
    +    if simplicial or tricoplanar facet,
    +      notvertex is defined and it is skipped in vertices
    +
    +  returns:
    +    computes area of simplex projected to plane [normal,offset]
    +    returns 0 if vertex too far below plane (qh WIDEfacet)
    +      vertex can't be apex of tricoplanar facet
    +
    +  notes:
    +    if (qh DELAUNAY),
    +      computes projected area instead for last coordinate
    +    uses qh gm_matrix/gm_row and qh hull_dim
    +    helper function for qh_facetarea
    +
    +  design:
    +    if Notvertex
    +      translate simplex to apex
    +    else
    +      project simplex to normal/offset
    +      translate simplex to apex
    +    if Delaunay
    +      set last row/column to 0 with -1 on diagonal
    +    else
    +      set last row to Normal
    +    compute determinate
    +    scale and flip sign for area
    +*/
    +realT qh_facetarea_simplex(int dim, coordT *apex, setT *vertices,
    +        vertexT *notvertex,  boolT toporient, coordT *normal, realT *offset) {
    +  pointT *coorda, *coordp, *gmcoord;
    +  coordT **rows, *normalp;
    +  int k,  i=0;
    +  realT area, dist;
    +  vertexT *vertex, **vertexp;
    +  boolT nearzero;
    +
    +  gmcoord= qh gm_matrix;
    +  rows= qh gm_row;
    +  FOREACHvertex_(vertices) {
    +    if (vertex == notvertex)
    +      continue;
    +    rows[i++]= gmcoord;
    +    coorda= apex;
    +    coordp= vertex->point;
    +    normalp= normal;
    +    if (notvertex) {
    +      for (k=dim; k--; )
    +        *(gmcoord++)= *coordp++ - *coorda++;
    +    }else {
    +      dist= *offset;
    +      for (k=dim; k--; )
    +        dist += *coordp++ * *normalp++;
    +      if (dist < -qh WIDEfacet) {
    +        zinc_(Znoarea);
    +        return 0.0;
    +      }
    +      coordp= vertex->point;
    +      normalp= normal;
    +      for (k=dim; k--; )
    +        *(gmcoord++)= (*coordp++ - dist * *normalp++) - *coorda++;
    +    }
    +  }
    +  if (i != dim-1) {
    +    qh_fprintf(qh ferr, 6008, "qhull internal error (qh_facetarea_simplex): #points %d != dim %d -1\n",
    +               i, dim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  rows[i]= gmcoord;
    +  if (qh DELAUNAY) {
    +    for (i=0; i < dim-1; i++)
    +      rows[i][dim-1]= 0.0;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= 0.0;
    +    rows[dim-1][dim-1]= -1.0;
    +  }else {
    +    normalp= normal;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= *normalp++;
    +  }
    +  zinc_(Zdetsimplex);
    +  area= qh_determinant(rows, dim, &nearzero);
    +  if (toporient)
    +    area= -area;
    +  area *= qh AREAfactor;
    +  trace4((qh ferr, 4010, "qh_facetarea_simplex: area=%2.2g for point p%d, toporient %d, nearzero? %d\n",
    +          area, qh_pointid(apex), toporient, nearzero));
    +  return area;
    +} /* facetarea_simplex */
    +
    +/*---------------------------------
    +
    +  qh_facetcenter( vertices )
    +    return Voronoi center (Voronoi vertex) for a facet's vertices
    +
    +  returns:
    +    return temporary point equal to the center
    +
    +  see:
    +    qh_voronoi_center()
    +*/
    +pointT *qh_facetcenter(setT *vertices) {
    +  setT *points= qh_settemp(qh_setsize(vertices));
    +  vertexT *vertex, **vertexp;
    +  pointT *center;
    +
    +  FOREACHvertex_(vertices)
    +    qh_setappend(&points, vertex->point);
    +  center= qh_voronoi_center(qh hull_dim-1, points);
    +  qh_settempfree(&points);
    +  return center;
    +} /* facetcenter */
    +
    +/*---------------------------------
    +
    +  qh_findgooddist( point, facetA, dist, facetlist )
    +    find best good facet visible for point from facetA
    +    assumes facetA is visible from point
    +
    +  returns:
    +    best facet, i.e., good facet that is furthest from point
    +      distance to best facet
    +      NULL if none
    +
    +    moves good, visible facets (and some other visible facets)
    +      to end of qh facet_list
    +
    +  notes:
    +    uses qh visit_id
    +
    +  design:
    +    initialize bestfacet if facetA is good
    +    move facetA to end of facetlist
    +    for each facet on facetlist
    +      for each unvisited neighbor of facet
    +        move visible neighbors to end of facetlist
    +        update best good neighbor
    +        if no good neighbors, update best facet
    +*/
    +facetT *qh_findgooddist(pointT *point, facetT *facetA, realT *distp,
    +               facetT **facetlist) {
    +  realT bestdist= -REALmax, dist;
    +  facetT *neighbor, **neighborp, *bestfacet=NULL, *facet;
    +  boolT goodseen= False;
    +
    +  if (facetA->good) {
    +    zzinc_(Zcheckpart);  /* calls from check_bestdist occur after print stats */
    +    qh_distplane(point, facetA, &bestdist);
    +    bestfacet= facetA;
    +    goodseen= True;
    +  }
    +  qh_removefacet(facetA);
    +  qh_appendfacet(facetA);
    +  *facetlist= facetA;
    +  facetA->visitid= ++qh visit_id;
    +  FORALLfacet_(*facetlist) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == qh visit_id)
    +        continue;
    +      neighbor->visitid= qh visit_id;
    +      if (goodseen && !neighbor->good)
    +        continue;
    +      zzinc_(Zcheckpart);
    +      qh_distplane(point, neighbor, &dist);
    +      if (dist > 0) {
    +        qh_removefacet(neighbor);
    +        qh_appendfacet(neighbor);
    +        if (neighbor->good) {
    +          goodseen= True;
    +          if (dist > bestdist) {
    +            bestdist= dist;
    +            bestfacet= neighbor;
    +          }
    +        }
    +      }
    +    }
    +  }
    +  if (bestfacet) {
    +    *distp= bestdist;
    +    trace2((qh ferr, 2003, "qh_findgooddist: p%d is %2.2g above good facet f%d\n",
    +      qh_pointid(point), bestdist, bestfacet->id));
    +    return bestfacet;
    +  }
    +  trace4((qh ferr, 4011, "qh_findgooddist: no good facet for p%d above f%d\n",
    +      qh_pointid(point), facetA->id));
    +  return NULL;
    +}  /* findgooddist */
    +
    +/*---------------------------------
    +
    +  qh_getarea( facetlist )
    +    set area of all facets in facetlist
    +    collect statistics
    +    nop if hasAreaVolume
    +
    +  returns:
    +    sets qh totarea/totvol to total area and volume of convex hull
    +    for Delaunay triangulation, computes projected area of the lower or upper hull
    +      ignores upper hull if qh ATinfinity
    +
    +  notes:
    +    could compute outer volume by expanding facet area by rays from interior
    +    the following attempt at perpendicular projection underestimated badly:
    +      qh.totoutvol += (-dist + facet->maxoutside + qh DISTround)
    +                            * area/ qh hull_dim;
    +  design:
    +    for each facet on facetlist
    +      compute facet->area
    +      update qh.totarea and qh.totvol
    +*/
    +void qh_getarea(facetT *facetlist) {
    +  realT area;
    +  realT dist;
    +  facetT *facet;
    +
    +  if (qh hasAreaVolume)
    +    return;
    +  if (qh REPORTfreq)
    +    qh_fprintf(qh ferr, 8020, "computing area of each facet and volume of the convex hull\n");
    +  else
    +    trace1((qh ferr, 1001, "qh_getarea: computing volume and area for each facet\n"));
    +  qh totarea= qh totvol= 0.0;
    +  FORALLfacet_(facetlist) {
    +    if (!facet->normal)
    +      continue;
    +    if (facet->upperdelaunay && qh ATinfinity)
    +      continue;
    +    if (!facet->isarea) {
    +      facet->f.area= qh_facetarea(facet);
    +      facet->isarea= True;
    +    }
    +    area= facet->f.area;
    +    if (qh DELAUNAY) {
    +      if (facet->upperdelaunay == qh UPPERdelaunay)
    +        qh totarea += area;
    +    }else {
    +      qh totarea += area;
    +      qh_distplane(qh interior_point, facet, &dist);
    +      qh totvol += -dist * area/ qh hull_dim;
    +    }
    +    if (qh PRINTstatistics) {
    +      wadd_(Wareatot, area);
    +      wmax_(Wareamax, area);
    +      wmin_(Wareamin, area);
    +    }
    +  }
    +  qh hasAreaVolume= True;
    +} /* getarea */
    +
    +/*---------------------------------
    +
    +  qh_gram_schmidt( dim, row )
    +    implements Gram-Schmidt orthogonalization by rows
    +
    +  returns:
    +    false if zero norm
    +    overwrites rows[dim][dim]
    +
    +  notes:
    +    see Golub & van Loan, 1983, Algorithm 6.2-2, "Modified Gram-Schmidt"
    +    overflow due to small divisors not handled
    +
    +  design:
    +    for each row
    +      compute norm for row
    +      if non-zero, normalize row
    +      for each remaining rowA
    +        compute inner product of row and rowA
    +        reduce rowA by row * inner product
    +*/
    +boolT qh_gram_schmidt(int dim, realT **row) {
    +  realT *rowi, *rowj, norm;
    +  int i, j, k;
    +
    +  for (i=0; i < dim; i++) {
    +    rowi= row[i];
    +    for (norm= 0.0, k= dim; k--; rowi++)
    +      norm += *rowi * *rowi;
    +    norm= sqrt(norm);
    +    wmin_(Wmindenom, norm);
    +    if (norm == 0.0)  /* either 0 or overflow due to sqrt */
    +      return False;
    +    for (k=dim; k--; )
    +      *(--rowi) /= norm;
    +    for (j=i+1; j < dim; j++) {
    +      rowj= row[j];
    +      for (norm= 0.0, k=dim; k--; )
    +        norm += *rowi++ * *rowj++;
    +      for (k=dim; k--; )
    +        *(--rowj) -= *(--rowi) * norm;
    +    }
    +  }
    +  return True;
    +} /* gram_schmidt */
    +
    +
    +/*---------------------------------
    +
    +  qh_inthresholds( normal, angle )
    +    return True if normal within qh.lower_/upper_threshold
    +
    +  returns:
    +    estimate of angle by summing of threshold diffs
    +      angle may be NULL
    +      smaller "angle" is better
    +
    +  notes:
    +    invalid if qh.SPLITthresholds
    +
    +  see:
    +    qh.lower_threshold in qh_initbuild()
    +    qh_initthresholds()
    +
    +  design:
    +    for each dimension
    +      test threshold
    +*/
    +boolT qh_inthresholds(coordT *normal, realT *angle) {
    +  boolT within= True;
    +  int k;
    +  realT threshold;
    +
    +  if (angle)
    +    *angle= 0.0;
    +  for (k=0; k < qh hull_dim; k++) {
    +    threshold= qh lower_threshold[k];
    +    if (threshold > -REALmax/2) {
    +      if (normal[k] < threshold)
    +        within= False;
    +      if (angle) {
    +        threshold -= normal[k];
    +        *angle += fabs_(threshold);
    +      }
    +    }
    +    if (qh upper_threshold[k] < REALmax/2) {
    +      threshold= qh upper_threshold[k];
    +      if (normal[k] > threshold)
    +        within= False;
    +      if (angle) {
    +        threshold -= normal[k];
    +        *angle += fabs_(threshold);
    +      }
    +    }
    +  }
    +  return within;
    +} /* inthresholds */
    +
    +
    +/*---------------------------------
    +
    +  qh_joggleinput()
    +    randomly joggle input to Qhull by qh.JOGGLEmax
    +    initial input is qh.first_point/qh.num_points of qh.hull_dim
    +      repeated calls use qh.input_points/qh.num_points
    +
    +  returns:
    +    joggles points at qh.first_point/qh.num_points
    +    copies data to qh.input_points/qh.input_malloc if first time
    +    determines qh.JOGGLEmax if it was zero
    +    if qh.DELAUNAY
    +      computes the Delaunay projection of the joggled points
    +
    +  notes:
    +    if qh.DELAUNAY, unnecessarily joggles the last coordinate
    +    the initial 'QJn' may be set larger than qh_JOGGLEmaxincrease
    +
    +  design:
    +    if qh.DELAUNAY
    +      set qh.SCALElast for reduced precision errors
    +    if first call
    +      initialize qh.input_points to the original input points
    +      if qh.JOGGLEmax == 0
    +        determine default qh.JOGGLEmax
    +    else
    +      increase qh.JOGGLEmax according to qh.build_cnt
    +    joggle the input by adding a random number in [-qh.JOGGLEmax,qh.JOGGLEmax]
    +    if qh.DELAUNAY
    +      sets the Delaunay projection
    +*/
    +void qh_joggleinput(void) {
    +  int i, seed, size;
    +  coordT *coordp, *inputp;
    +  realT randr, randa, randb;
    +
    +  if (!qh input_points) { /* first call */
    +    qh input_points= qh first_point;
    +    qh input_malloc= qh POINTSmalloc;
    +    size= qh num_points * qh hull_dim * sizeof(coordT);
    +    if (!(qh first_point=(coordT*)qh_malloc((size_t)size))) {
    +      qh_fprintf(qh ferr, 6009, "qhull error: insufficient memory to joggle %d points\n",
    +          qh num_points);
    +      qh_errexit(qh_ERRmem, NULL, NULL);
    +    }
    +    qh POINTSmalloc= True;
    +    if (qh JOGGLEmax == 0.0) {
    +      qh JOGGLEmax= qh_detjoggle(qh input_points, qh num_points, qh hull_dim);
    +      qh_option("QJoggle", NULL, &qh JOGGLEmax);
    +    }
    +  }else {                 /* repeated call */
    +    if (!qh RERUN && qh build_cnt > qh_JOGGLEretry) {
    +      if (((qh build_cnt-qh_JOGGLEretry-1) % qh_JOGGLEagain) == 0) {
    +        realT maxjoggle= qh MAXwidth * qh_JOGGLEmaxincrease;
    +        if (qh JOGGLEmax < maxjoggle) {
    +          qh JOGGLEmax *= qh_JOGGLEincrease;
    +          minimize_(qh JOGGLEmax, maxjoggle);
    +        }
    +      }
    +    }
    +    qh_option("QJoggle", NULL, &qh JOGGLEmax);
    +  }
    +  if (qh build_cnt > 1 && qh JOGGLEmax > fmax_(qh MAXwidth/4, 0.1)) {
    +      qh_fprintf(qh ferr, 6010, "qhull error: the current joggle for 'QJn', %.2g, is too large for the width\nof the input.  If possible, recompile Qhull with higher-precision reals.\n",
    +                qh JOGGLEmax);
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  /* for some reason, using qh ROTATErandom and qh_RANDOMseed does not repeat the run. Use 'TRn' instead */
    +  seed= qh_RANDOMint;
    +  qh_option("_joggle-seed", &seed, NULL);
    +  trace0((qh ferr, 6, "qh_joggleinput: joggle input by %2.2g with seed %d\n",
    +    qh JOGGLEmax, seed));
    +  inputp= qh input_points;
    +  coordp= qh first_point;
    +  randa= 2.0 * qh JOGGLEmax/qh_RANDOMmax;
    +  randb= -qh JOGGLEmax;
    +  size= qh num_points * qh hull_dim;
    +  for (i=size; i--; ) {
    +    randr= qh_RANDOMint;
    +    *(coordp++)= *(inputp++) + (randr * randa + randb);
    +  }
    +  if (qh DELAUNAY) {
    +    qh last_low= qh last_high= qh last_newhigh= REALmax;
    +    qh_setdelaunay(qh hull_dim, qh num_points, qh first_point);
    +  }
    +} /* joggleinput */
    +
    +/*---------------------------------
    +
    +  qh_maxabsval( normal, dim )
    +    return pointer to maximum absolute value of a dim vector
    +    returns NULL if dim=0
    +*/
    +realT *qh_maxabsval(realT *normal, int dim) {
    +  realT maxval= -REALmax;
    +  realT *maxp= NULL, *colp, absval;
    +  int k;
    +
    +  for (k=dim, colp= normal; k--; colp++) {
    +    absval= fabs_(*colp);
    +    if (absval > maxval) {
    +      maxval= absval;
    +      maxp= colp;
    +    }
    +  }
    +  return maxp;
    +} /* maxabsval */
    +
    +
    +/*---------------------------------
    +
    +  qh_maxmin( points, numpoints, dimension )
    +    return max/min points for each dimension
    +    determine max and min coordinates
    +
    +  returns:
    +    returns a temporary set of max and min points
    +      may include duplicate points. Does not include qh.GOODpoint
    +    sets qh.NEARzero, qh.MAXabs_coord, qh.MAXsumcoord, qh.MAXwidth
    +         qh.MAXlastcoord, qh.MINlastcoord
    +    initializes qh.max_outside, qh.min_vertex, qh.WAScoplanar, qh.ZEROall_ok
    +
    +  notes:
    +    loop duplicated in qh_detjoggle()
    +
    +  design:
    +    initialize global precision variables
    +    checks definition of REAL...
    +    for each dimension
    +      for each point
    +        collect maximum and minimum point
    +      collect maximum of maximums and minimum of minimums
    +      determine qh.NEARzero for Gaussian Elimination
    +*/
    +setT *qh_maxmin(pointT *points, int numpoints, int dimension) {
    +  int k;
    +  realT maxcoord, temp;
    +  pointT *minimum, *maximum, *point, *pointtemp;
    +  setT *set;
    +
    +  qh max_outside= 0.0;
    +  qh MAXabs_coord= 0.0;
    +  qh MAXwidth= -REALmax;
    +  qh MAXsumcoord= 0.0;
    +  qh min_vertex= 0.0;
    +  qh WAScoplanar= False;
    +  if (qh ZEROcentrum)
    +    qh ZEROall_ok= True;
    +  if (REALmin < REALepsilon && REALmin < REALmax && REALmin > -REALmax
    +  && REALmax > 0.0 && -REALmax < 0.0)
    +    ; /* all ok */
    +  else {
    +    qh_fprintf(qh ferr, 6011, "qhull error: floating point constants in user.h are wrong\n\
    +REALepsilon %g REALmin %g REALmax %g -REALmax %g\n",
    +             REALepsilon, REALmin, REALmax, -REALmax);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  set= qh_settemp(2*dimension);
    +  for (k=0; k < dimension; k++) {
    +    if (points == qh GOODpointp)
    +      minimum= maximum= points + dimension;
    +    else
    +      minimum= maximum= points;
    +    FORALLpoint_(points, numpoints) {
    +      if (point == qh GOODpointp)
    +        continue;
    +      if (maximum[k] < point[k])
    +        maximum= point;
    +      else if (minimum[k] > point[k])
    +        minimum= point;
    +    }
    +    if (k == dimension-1) {
    +      qh MINlastcoord= minimum[k];
    +      qh MAXlastcoord= maximum[k];
    +    }
    +    if (qh SCALElast && k == dimension-1)
    +      maxcoord= qh MAXwidth;
    +    else {
    +      maxcoord= fmax_(maximum[k], -minimum[k]);
    +      if (qh GOODpointp) {
    +        temp= fmax_(qh GOODpointp[k], -qh GOODpointp[k]);
    +        maximize_(maxcoord, temp);
    +      }
    +      temp= maximum[k] - minimum[k];
    +      maximize_(qh MAXwidth, temp);
    +    }
    +    maximize_(qh MAXabs_coord, maxcoord);
    +    qh MAXsumcoord += maxcoord;
    +    qh_setappend(&set, maximum);
    +    qh_setappend(&set, minimum);
    +    /* calculation of qh NEARzero is based on Golub & van Loan, 1983,
    +       Eq. 4.4-13 for "Gaussian elimination with complete pivoting".
    +       Golub & van Loan say that n^3 can be ignored and 10 be used in
    +       place of rho */
    +    qh NEARzero[k]= 80 * qh MAXsumcoord * REALepsilon;
    +  }
    +  if (qh IStracing >=1)
    +    qh_printpoints(qh ferr, "qh_maxmin: found the max and min points(by dim):", set);
    +  return(set);
    +} /* maxmin */
    +
    +/*---------------------------------
    +
    +  qh_maxouter()
    +    return maximum distance from facet to outer plane
    +    normally this is qh.max_outside+qh.DISTround
    +    does not include qh.JOGGLEmax
    +
    +  see:
    +    qh_outerinner()
    +
    +  notes:
    +    need to add another qh.DISTround if testing actual point with computation
    +
    +  for joggle:
    +    qh_setfacetplane() updated qh.max_outer for Wnewvertexmax (max distance to vertex)
    +    need to use Wnewvertexmax since could have a coplanar point for a high
    +      facet that is replaced by a low facet
    +    need to add qh.JOGGLEmax if testing input points
    +*/
    +realT qh_maxouter(void) {
    +  realT dist;
    +
    +  dist= fmax_(qh max_outside, qh DISTround);
    +  dist += qh DISTround;
    +  trace4((qh ferr, 4012, "qh_maxouter: max distance from facet to outer plane is %2.2g max_outside is %2.2g\n", dist, qh max_outside));
    +  return dist;
    +} /* maxouter */
    +
    +/*---------------------------------
    +
    +  qh_maxsimplex( dim, maxpoints, points, numpoints, simplex )
    +    determines maximum simplex for a set of points
    +    starts from points already in simplex
    +    skips qh.GOODpointp (assumes that it isn't in maxpoints)
    +
    +  returns:
    +    simplex with dim+1 points
    +
    +  notes:
    +    assumes at least pointsneeded points in points
    +    maximizes determinate for x,y,z,w, etc.
    +    uses maxpoints as long as determinate is clearly non-zero
    +
    +  design:
    +    initialize simplex with at least two points
    +      (find points with max or min x coordinate)
    +    for each remaining dimension
    +      add point that maximizes the determinate
    +        (use points from maxpoints first)
    +*/
    +void qh_maxsimplex(int dim, setT *maxpoints, pointT *points, int numpoints, setT **simplex) {
    +  pointT *point, **pointp, *pointtemp, *maxpoint, *minx=NULL, *maxx=NULL;
    +  boolT nearzero, maxnearzero= False;
    +  int k, sizinit;
    +  realT maxdet= -REALmax, det, mincoord= REALmax, maxcoord= -REALmax;
    +
    +  sizinit= qh_setsize(*simplex);
    +  if (sizinit < 2) {
    +    if (qh_setsize(maxpoints) >= 2) {
    +      FOREACHpoint_(maxpoints) {
    +        if (maxcoord < point[0]) {
    +          maxcoord= point[0];
    +          maxx= point;
    +        }
    +        if (mincoord > point[0]) {
    +          mincoord= point[0];
    +          minx= point;
    +        }
    +      }
    +    }else {
    +      FORALLpoint_(points, numpoints) {
    +        if (point == qh GOODpointp)
    +          continue;
    +        if (maxcoord < point[0]) {
    +          maxcoord= point[0];
    +          maxx= point;
    +        }
    +        if (mincoord > point[0]) {
    +          mincoord= point[0];
    +          minx= point;
    +        }
    +      }
    +    }
    +    qh_setunique(simplex, minx);
    +    if (qh_setsize(*simplex) < 2)
    +      qh_setunique(simplex, maxx);
    +    sizinit= qh_setsize(*simplex);
    +    if (sizinit < 2) {
    +      qh_precision("input has same x coordinate");
    +      if (zzval_(Zsetplane) > qh hull_dim+1) {
    +        qh_fprintf(qh ferr, 6012, "qhull precision error (qh_maxsimplex for voronoi_center):\n%d points with the same x coordinate.\n",
    +                 qh_setsize(maxpoints)+numpoints);
    +        qh_errexit(qh_ERRprec, NULL, NULL);
    +      }else {
    +        qh_fprintf(qh ferr, 6013, "qhull input error: input is less than %d-dimensional since it has the same x coordinate\n", qh hull_dim);
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +    }
    +  }
    +  for (k=sizinit; k < dim+1; k++) {
    +    maxpoint= NULL;
    +    maxdet= -REALmax;
    +    FOREACHpoint_(maxpoints) {
    +      if (!qh_setin(*simplex, point)) {
    +        det= qh_detsimplex(point, *simplex, k, &nearzero);
    +        if ((det= fabs_(det)) > maxdet) {
    +          maxdet= det;
    +          maxpoint= point;
    +          maxnearzero= nearzero;
    +        }
    +      }
    +    }
    +    if (!maxpoint || maxnearzero) {
    +      zinc_(Zsearchpoints);
    +      if (!maxpoint) {
    +        trace0((qh ferr, 7, "qh_maxsimplex: searching all points for %d-th initial vertex.\n", k+1));
    +      }else {
    +        trace0((qh ferr, 8, "qh_maxsimplex: searching all points for %d-th initial vertex, better than p%d det %2.2g\n",
    +                k+1, qh_pointid(maxpoint), maxdet));
    +      }
    +      FORALLpoint_(points, numpoints) {
    +        if (point == qh GOODpointp)
    +          continue;
    +        if (!qh_setin(*simplex, point)) {
    +          det= qh_detsimplex(point, *simplex, k, &nearzero);
    +          if ((det= fabs_(det)) > maxdet) {
    +            maxdet= det;
    +            maxpoint= point;
    +            maxnearzero= nearzero;
    +          }
    +        }
    +      }
    +    } /* !maxpoint */
    +    if (!maxpoint) {
    +      qh_fprintf(qh ferr, 6014, "qhull internal error (qh_maxsimplex): not enough points available\n");
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +    qh_setappend(simplex, maxpoint);
    +    trace1((qh ferr, 1002, "qh_maxsimplex: selected point p%d for %d`th initial vertex, det=%2.2g\n",
    +            qh_pointid(maxpoint), k+1, maxdet));
    +  } /* k */
    +} /* maxsimplex */
    +
    +/*---------------------------------
    +
    +  qh_minabsval( normal, dim )
    +    return minimum absolute value of a dim vector
    +*/
    +realT qh_minabsval(realT *normal, int dim) {
    +  realT minval= 0;
    +  realT maxval= 0;
    +  realT *colp;
    +  int k;
    +
    +  for (k=dim, colp=normal; k--; colp++) {
    +    maximize_(maxval, *colp);
    +    minimize_(minval, *colp);
    +  }
    +  return fmax_(maxval, -minval);
    +} /* minabsval */
    +
    +
    +/*---------------------------------
    +
    +  qh_mindif( vecA, vecB, dim )
    +    return index of min abs. difference of two vectors
    +*/
    +int qh_mindiff(realT *vecA, realT *vecB, int dim) {
    +  realT mindiff= REALmax, diff;
    +  realT *vecAp= vecA, *vecBp= vecB;
    +  int k, mink= 0;
    +
    +  for (k=0; k < dim; k++) {
    +    diff= *vecAp++ - *vecBp++;
    +    diff= fabs_(diff);
    +    if (diff < mindiff) {
    +      mindiff= diff;
    +      mink= k;
    +    }
    +  }
    +  return mink;
    +} /* mindiff */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_orientoutside( facet  )
    +    make facet outside oriented via qh.interior_point
    +
    +  returns:
    +    True if facet reversed orientation.
    +*/
    +boolT qh_orientoutside(facetT *facet) {
    +  int k;
    +  realT dist;
    +
    +  qh_distplane(qh interior_point, facet, &dist);
    +  if (dist > 0) {
    +    for (k=qh hull_dim; k--; )
    +      facet->normal[k]= -facet->normal[k];
    +    facet->offset= -facet->offset;
    +    return True;
    +  }
    +  return False;
    +} /* orientoutside */
    +
    +/*---------------------------------
    +
    +  qh_outerinner( facet, outerplane, innerplane  )
    +    if facet and qh.maxoutdone (i.e., qh_check_maxout)
    +      returns outer and inner plane for facet
    +    else
    +      returns maximum outer and inner plane
    +    accounts for qh.JOGGLEmax
    +
    +  see:
    +    qh_maxouter(), qh_check_bestdist(), qh_check_points()
    +
    +  notes:
    +    outerplaner or innerplane may be NULL
    +    facet is const
    +    Does not error (QhullFacet)
    +
    +    includes qh.DISTround for actual points
    +    adds another qh.DISTround if testing with floating point arithmetic
    +*/
    +void qh_outerinner(facetT *facet, realT *outerplane, realT *innerplane) {
    +  realT dist, mindist;
    +  vertexT *vertex, **vertexp;
    +
    +  if (outerplane) {
    +    if (!qh_MAXoutside || !facet || !qh maxoutdone) {
    +      *outerplane= qh_maxouter();       /* includes qh.DISTround */
    +    }else { /* qh_MAXoutside ... */
    +#if qh_MAXoutside
    +      *outerplane= facet->maxoutside + qh DISTround;
    +#endif
    +
    +    }
    +    if (qh JOGGLEmax < REALmax/2)
    +      *outerplane += qh JOGGLEmax * sqrt((realT)qh hull_dim);
    +  }
    +  if (innerplane) {
    +    if (facet) {
    +      mindist= REALmax;
    +      FOREACHvertex_(facet->vertices) {
    +        zinc_(Zdistio);
    +        qh_distplane(vertex->point, facet, &dist);
    +        minimize_(mindist, dist);
    +      }
    +      *innerplane= mindist - qh DISTround;
    +    }else
    +      *innerplane= qh min_vertex - qh DISTround;
    +    if (qh JOGGLEmax < REALmax/2)
    +      *innerplane -= qh JOGGLEmax * sqrt((realT)qh hull_dim);
    +  }
    +} /* outerinner */
    +
    +/*---------------------------------
    +
    +  qh_pointdist( point1, point2, dim )
    +    return distance between two points
    +
    +  notes:
    +    returns distance squared if 'dim' is negative
    +*/
    +coordT qh_pointdist(pointT *point1, pointT *point2, int dim) {
    +  coordT dist, diff;
    +  int k;
    +
    +  dist= 0.0;
    +  for (k= (dim > 0 ? dim : -dim); k--; ) {
    +    diff= *point1++ - *point2++;
    +    dist += diff * diff;
    +  }
    +  if (dim > 0)
    +    return(sqrt(dist));
    +  return dist;
    +} /* pointdist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printmatrix( fp, string, rows, numrow, numcol )
    +    print matrix to fp given by row vectors
    +    print string as header
    +
    +  notes:
    +    print a vector by qh_printmatrix(fp, "", &vect, 1, len)
    +*/
    +void qh_printmatrix(FILE *fp, const char *string, realT **rows, int numrow, int numcol) {
    +  realT *rowp;
    +  realT r; /*bug fix*/
    +  int i,k;
    +
    +  qh_fprintf(fp, 9001, "%s\n", string);
    +  for (i=0; i < numrow; i++) {
    +    rowp= rows[i];
    +    for (k=0; k < numcol; k++) {
    +      r= *rowp++;
    +      qh_fprintf(fp, 9002, "%6.3g ", r);
    +    }
    +    qh_fprintf(fp, 9003, "\n");
    +  }
    +} /* printmatrix */
    +
    +
    +/*---------------------------------
    +
    +  qh_printpoints( fp, string, points )
    +    print pointids to fp for a set of points
    +    if string, prints string and 'p' point ids
    +*/
    +void qh_printpoints(FILE *fp, const char *string, setT *points) {
    +  pointT *point, **pointp;
    +
    +  if (string) {
    +    qh_fprintf(fp, 9004, "%s", string);
    +    FOREACHpoint_(points)
    +      qh_fprintf(fp, 9005, " p%d", qh_pointid(point));
    +    qh_fprintf(fp, 9006, "\n");
    +  }else {
    +    FOREACHpoint_(points)
    +      qh_fprintf(fp, 9007, " %d", qh_pointid(point));
    +    qh_fprintf(fp, 9008, "\n");
    +  }
    +} /* printpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectinput()
    +    project input points using qh.lower_bound/upper_bound and qh DELAUNAY
    +    if qh.lower_bound[k]=qh.upper_bound[k]= 0,
    +      removes dimension k
    +    if halfspace intersection
    +      removes dimension k from qh.feasible_point
    +    input points in qh first_point, num_points, input_dim
    +
    +  returns:
    +    new point array in qh first_point of qh hull_dim coordinates
    +    sets qh POINTSmalloc
    +    if qh DELAUNAY
    +      projects points to paraboloid
    +      lowbound/highbound is also projected
    +    if qh ATinfinity
    +      adds point "at-infinity"
    +    if qh POINTSmalloc
    +      frees old point array
    +
    +  notes:
    +    checks that qh.hull_dim agrees with qh.input_dim, PROJECTinput, and DELAUNAY
    +
    +
    +  design:
    +    sets project[k] to -1 (delete), 0 (keep), 1 (add for Delaunay)
    +    determines newdim and newnum for qh hull_dim and qh num_points
    +    projects points to newpoints
    +    projects qh.lower_bound to itself
    +    projects qh.upper_bound to itself
    +    if qh DELAUNAY
    +      if qh ATINFINITY
    +        projects points to paraboloid
    +        computes "infinity" point as vertex average and 10% above all points
    +      else
    +        uses qh_setdelaunay to project points to paraboloid
    +*/
    +void qh_projectinput(void) {
    +  int k,i;
    +  int newdim= qh input_dim, newnum= qh num_points;
    +  signed char *project;
    +  int projectsize= (qh input_dim+1)*sizeof(*project);
    +  pointT *newpoints, *coord, *infinity;
    +  realT paraboloid, maxboloid= 0;
    +
    +  project= (signed char*)qh_memalloc(projectsize);
    +  memset((char*)project, 0, (size_t)projectsize);
    +  for (k=0; k < qh input_dim; k++) {   /* skip Delaunay bound */
    +    if (qh lower_bound[k] == 0 && qh upper_bound[k] == 0) {
    +      project[k]= -1;
    +      newdim--;
    +    }
    +  }
    +  if (qh DELAUNAY) {
    +    project[k]= 1;
    +    newdim++;
    +    if (qh ATinfinity)
    +      newnum++;
    +  }
    +  if (newdim != qh hull_dim) {
    +    qh_memfree(project, projectsize);
    +    qh_fprintf(qh ferr, 6015, "qhull internal error (qh_projectinput): dimension after projection %d != hull_dim %d\n", newdim, qh hull_dim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (!(newpoints= qh temp_malloc= (coordT*)qh_malloc(newnum*newdim*sizeof(coordT)))){
    +    qh_memfree(project, projectsize);
    +    qh_fprintf(qh ferr, 6016, "qhull error: insufficient memory to project %d points\n",
    +           qh num_points);
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  /* qh_projectpoints throws error if mismatched dimensions */
    +  qh_projectpoints(project, qh input_dim+1, qh first_point,
    +                    qh num_points, qh input_dim, newpoints, newdim);
    +  trace1((qh ferr, 1003, "qh_projectinput: updating lower and upper_bound\n"));
    +  qh_projectpoints(project, qh input_dim+1, qh lower_bound,
    +                    1, qh input_dim+1, qh lower_bound, newdim+1);
    +  qh_projectpoints(project, qh input_dim+1, qh upper_bound,
    +                    1, qh input_dim+1, qh upper_bound, newdim+1);
    +  if (qh HALFspace) {
    +    if (!qh feasible_point) {
    +      qh_memfree(project, projectsize);
    +      qh_fprintf(qh ferr, 6017, "qhull internal error (qh_projectinput): HALFspace defined without qh.feasible_point\n");
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +    qh_projectpoints(project, qh input_dim, qh feasible_point,
    +                      1, qh input_dim, qh feasible_point, newdim);
    +  }
    +  qh_memfree(project, projectsize);
    +  if (qh POINTSmalloc)
    +    qh_free(qh first_point);
    +  qh first_point= newpoints;
    +  qh POINTSmalloc= True;
    +  qh temp_malloc= NULL;
    +  if (qh DELAUNAY && qh ATinfinity) {
    +    coord= qh first_point;
    +    infinity= qh first_point + qh hull_dim * qh num_points;
    +    for (k=qh hull_dim-1; k--; )
    +      infinity[k]= 0.0;
    +    for (i=qh num_points; i--; ) {
    +      paraboloid= 0.0;
    +      for (k=0; k < qh hull_dim-1; k++) {
    +        paraboloid += *coord * *coord;
    +        infinity[k] += *coord;
    +        coord++;
    +      }
    +      *(coord++)= paraboloid;
    +      maximize_(maxboloid, paraboloid);
    +    }
    +    /* coord == infinity */
    +    for (k=qh hull_dim-1; k--; )
    +      *(coord++) /= qh num_points;
    +    *(coord++)= maxboloid * 1.1;
    +    qh num_points++;
    +    trace0((qh ferr, 9, "qh_projectinput: projected points to paraboloid for Delaunay\n"));
    +  }else if (qh DELAUNAY)  /* !qh ATinfinity */
    +    qh_setdelaunay( qh hull_dim, qh num_points, qh first_point);
    +} /* projectinput */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectpoints( project, n, points, numpoints, dim, newpoints, newdim )
    +    project points/numpoints/dim to newpoints/newdim
    +    if project[k] == -1
    +      delete dimension k
    +    if project[k] == 1
    +      add dimension k by duplicating previous column
    +    n is size of project
    +
    +  notes:
    +    newpoints may be points if only adding dimension at end
    +
    +  design:
    +    check that 'project' and 'newdim' agree
    +    for each dimension
    +      if project == -1
    +        skip dimension
    +      else
    +        determine start of column in newpoints
    +        determine start of column in points
    +          if project == +1, duplicate previous column
    +        copy dimension (column) from points to newpoints
    +*/
    +void qh_projectpoints(signed char *project, int n, realT *points,
    +        int numpoints, int dim, realT *newpoints, int newdim) {
    +  int testdim= dim, oldk=0, newk=0, i,j=0,k;
    +  realT *newp, *oldp;
    +
    +  for (k=0; k < n; k++)
    +    testdim += project[k];
    +  if (testdim != newdim) {
    +    qh_fprintf(qh ferr, 6018, "qhull internal error (qh_projectpoints): newdim %d should be %d after projection\n",
    +      newdim, testdim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  for (j=0; j= dim)
    +          continue;
    +        oldp= points+oldk;
    +      }else
    +        oldp= points+oldk++;
    +      for (i=numpoints; i--; ) {
    +        *newp= *oldp;
    +        newp += newdim;
    +        oldp += dim;
    +      }
    +    }
    +    if (oldk >= dim)
    +      break;
    +  }
    +  trace1((qh ferr, 1004, "qh_projectpoints: projected %d points from dim %d to dim %d\n",
    +    numpoints, dim, newdim));
    +} /* projectpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_rotateinput( rows )
    +    rotate input using row matrix
    +    input points given by qh first_point, num_points, hull_dim
    +    assumes rows[dim] is a scratch buffer
    +    if qh POINTSmalloc, overwrites input points, else mallocs a new array
    +
    +  returns:
    +    rotated input
    +    sets qh POINTSmalloc
    +
    +  design:
    +    see qh_rotatepoints
    +*/
    +void qh_rotateinput(realT **rows) {
    +
    +  if (!qh POINTSmalloc) {
    +    qh first_point= qh_copypoints(qh first_point, qh num_points, qh hull_dim);
    +    qh POINTSmalloc= True;
    +  }
    +  qh_rotatepoints(qh first_point, qh num_points, qh hull_dim, rows);
    +}  /* rotateinput */
    +
    +/*---------------------------------
    +
    +  qh_rotatepoints( points, numpoints, dim, row )
    +    rotate numpoints points by a d-dim row matrix
    +    assumes rows[dim] is a scratch buffer
    +
    +  returns:
    +    rotated points in place
    +
    +  design:
    +    for each point
    +      for each coordinate
    +        use row[dim] to compute partial inner product
    +      for each coordinate
    +        rotate by partial inner product
    +*/
    +void qh_rotatepoints(realT *points, int numpoints, int dim, realT **row) {
    +  realT *point, *rowi, *coord= NULL, sum, *newval;
    +  int i,j,k;
    +
    +  if (qh IStracing >= 1)
    +    qh_printmatrix(qh ferr, "qh_rotatepoints: rotate points by", row, dim, dim);
    +  for (point= points, j= numpoints; j--; point += dim) {
    +    newval= row[dim];
    +    for (i=0; i < dim; i++) {
    +      rowi= row[i];
    +      coord= point;
    +      for (sum= 0.0, k= dim; k--; )
    +        sum += *rowi++ * *coord++;
    +      *(newval++)= sum;
    +    }
    +    for (k=dim; k--; )
    +      *(--coord)= *(--newval);
    +  }
    +} /* rotatepoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_scaleinput()
    +    scale input points using qh low_bound/high_bound
    +    input points given by qh first_point, num_points, hull_dim
    +    if qh POINTSmalloc, overwrites input points, else mallocs a new array
    +
    +  returns:
    +    scales coordinates of points to low_bound[k], high_bound[k]
    +    sets qh POINTSmalloc
    +
    +  design:
    +    see qh_scalepoints
    +*/
    +void qh_scaleinput(void) {
    +
    +  if (!qh POINTSmalloc) {
    +    qh first_point= qh_copypoints(qh first_point, qh num_points, qh hull_dim);
    +    qh POINTSmalloc= True;
    +  }
    +  qh_scalepoints(qh first_point, qh num_points, qh hull_dim,
    +       qh lower_bound, qh upper_bound);
    +}  /* scaleinput */
    +
    +/*---------------------------------
    +
    +  qh_scalelast( points, numpoints, dim, low, high, newhigh )
    +    scale last coordinate to [0,m] for Delaunay triangulations
    +    input points given by points, numpoints, dim
    +
    +  returns:
    +    changes scale of last coordinate from [low, high] to [0, newhigh]
    +    overwrites last coordinate of each point
    +    saves low/high/newhigh in qh.last_low, etc. for qh_setdelaunay()
    +
    +  notes:
    +    when called by qh_setdelaunay, low/high may not match actual data
    +
    +  design:
    +    compute scale and shift factors
    +    apply to last coordinate of each point
    +*/
    +void qh_scalelast(coordT *points, int numpoints, int dim, coordT low,
    +                   coordT high, coordT newhigh) {
    +  realT scale, shift;
    +  coordT *coord;
    +  int i;
    +  boolT nearzero= False;
    +
    +  trace4((qh ferr, 4013, "qh_scalelast: scale last coordinate from [%2.2g, %2.2g] to [0,%2.2g]\n",
    +    low, high, newhigh));
    +  qh last_low= low;
    +  qh last_high= high;
    +  qh last_newhigh= newhigh;
    +  scale= qh_divzero(newhigh, high - low,
    +                  qh MINdenom_1, &nearzero);
    +  if (nearzero) {
    +    if (qh DELAUNAY)
    +      qh_fprintf(qh ferr, 6019, "qhull input error: can not scale last coordinate.  Input is cocircular\n   or cospherical.   Use option 'Qz' to add a point at infinity.\n");
    +    else
    +      qh_fprintf(qh ferr, 6020, "qhull input error: can not scale last coordinate.  New bounds [0, %2.2g] are too wide for\nexisting bounds [%2.2g, %2.2g] (width %2.2g)\n",
    +                newhigh, low, high, high-low);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  shift= - low * newhigh / (high-low);
    +  coord= points + dim - 1;
    +  for (i=numpoints; i--; coord += dim)
    +    *coord= *coord * scale + shift;
    +} /* scalelast */
    +
    +/*---------------------------------
    +
    +  qh_scalepoints( points, numpoints, dim, newlows, newhighs )
    +    scale points to new lowbound and highbound
    +    retains old bound when newlow= -REALmax or newhigh= +REALmax
    +
    +  returns:
    +    scaled points
    +    overwrites old points
    +
    +  design:
    +    for each coordinate
    +      compute current low and high bound
    +      compute scale and shift factors
    +      scale all points
    +      enforce new low and high bound for all points
    +*/
    +void qh_scalepoints(pointT *points, int numpoints, int dim,
    +        realT *newlows, realT *newhighs) {
    +  int i,k;
    +  realT shift, scale, *coord, low, high, newlow, newhigh, mincoord, maxcoord;
    +  boolT nearzero= False;
    +
    +  for (k=0; k < dim; k++) {
    +    newhigh= newhighs[k];
    +    newlow= newlows[k];
    +    if (newhigh > REALmax/2 && newlow < -REALmax/2)
    +      continue;
    +    low= REALmax;
    +    high= -REALmax;
    +    for (i=numpoints, coord=points+k; i--; coord += dim) {
    +      minimize_(low, *coord);
    +      maximize_(high, *coord);
    +    }
    +    if (newhigh > REALmax/2)
    +      newhigh= high;
    +    if (newlow < -REALmax/2)
    +      newlow= low;
    +    if (qh DELAUNAY && k == dim-1 && newhigh < newlow) {
    +      qh_fprintf(qh ferr, 6021, "qhull input error: 'Qb%d' or 'QB%d' inverts paraboloid since high bound %.2g < low bound %.2g\n",
    +               k, k, newhigh, newlow);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    scale= qh_divzero(newhigh - newlow, high - low,
    +                  qh MINdenom_1, &nearzero);
    +    if (nearzero) {
    +      qh_fprintf(qh ferr, 6022, "qhull input error: %d'th dimension's new bounds [%2.2g, %2.2g] too wide for\nexisting bounds [%2.2g, %2.2g]\n",
    +              k, newlow, newhigh, low, high);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    shift= (newlow * high - low * newhigh)/(high-low);
    +    coord= points+k;
    +    for (i=numpoints; i--; coord += dim)
    +      *coord= *coord * scale + shift;
    +    coord= points+k;
    +    if (newlow < newhigh) {
    +      mincoord= newlow;
    +      maxcoord= newhigh;
    +    }else {
    +      mincoord= newhigh;
    +      maxcoord= newlow;
    +    }
    +    for (i=numpoints; i--; coord += dim) {
    +      minimize_(*coord, maxcoord);  /* because of roundoff error */
    +      maximize_(*coord, mincoord);
    +    }
    +    trace0((qh ferr, 10, "qh_scalepoints: scaled %d'th coordinate [%2.2g, %2.2g] to [%.2g, %.2g] for %d points by %2.2g and shifted %2.2g\n",
    +      k, low, high, newlow, newhigh, numpoints, scale, shift));
    +  }
    +} /* scalepoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelaunay( dim, count, points )
    +    project count points to dim-d paraboloid for Delaunay triangulation
    +
    +    dim is one more than the dimension of the input set
    +    assumes dim is at least 3 (i.e., at least a 2-d Delaunay triangulation)
    +
    +    points is a dim*count realT array.  The first dim-1 coordinates
    +    are the coordinates of the first input point.  array[dim] is
    +    the first coordinate of the second input point.  array[2*dim] is
    +    the first coordinate of the third input point.
    +
    +    if qh.last_low defined (i.e., 'Qbb' called qh_scalelast)
    +      calls qh_scalelast to scale the last coordinate the same as the other points
    +
    +  returns:
    +    for each point
    +      sets point[dim-1] to sum of squares of coordinates
    +    scale points to 'Qbb' if needed
    +
    +  notes:
    +    to project one point, use
    +      qh_setdelaunay(qh hull_dim, 1, point)
    +
    +    Do not use options 'Qbk', 'QBk', or 'QbB' since they scale
    +    the coordinates after the original projection.
    +
    +*/
    +void qh_setdelaunay(int dim, int count, pointT *points) {
    +  int i, k;
    +  coordT *coordp, coord;
    +  realT paraboloid;
    +
    +  trace0((qh ferr, 11, "qh_setdelaunay: project %d points to paraboloid for Delaunay triangulation\n", count));
    +  coordp= points;
    +  for (i=0; i < count; i++) {
    +    coord= *coordp++;
    +    paraboloid= coord*coord;
    +    for (k=dim-2; k--; ) {
    +      coord= *coordp++;
    +      paraboloid += coord*coord;
    +    }
    +    *coordp++ = paraboloid;
    +  }
    +  if (qh last_low < REALmax/2)
    +    qh_scalelast(points, count, dim, qh last_low, qh last_high, qh last_newhigh);
    +} /* setdelaunay */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethalfspace( dim, coords, nextp, normal, offset, feasible )
    +    set point to dual of halfspace relative to feasible point
    +    halfspace is normal coefficients and offset.
    +
    +  returns:
    +    false and prints error if feasible point is outside of hull
    +    overwrites coordinates for point at dim coords
    +    nextp= next point (coords)
    +    does not call qh_errexit
    +
    +  design:
    +    compute distance from feasible point to halfspace
    +    divide each normal coefficient by -dist
    +*/
    +boolT qh_sethalfspace(int dim, coordT *coords, coordT **nextp,
    +         coordT *normal, coordT *offset, coordT *feasible) {
    +  coordT *normp= normal, *feasiblep= feasible, *coordp= coords;
    +  realT dist;
    +  realT r; /*bug fix*/
    +  int k;
    +  boolT zerodiv;
    +
    +  dist= *offset;
    +  for (k=dim; k--; )
    +    dist += *(normp++) * *(feasiblep++);
    +  if (dist > 0)
    +    goto LABELerroroutside;
    +  normp= normal;
    +  if (dist < -qh MINdenom) {
    +    for (k=dim; k--; )
    +      *(coordp++)= *(normp++) / -dist;
    +  }else {
    +    for (k=dim; k--; ) {
    +      *(coordp++)= qh_divzero(*(normp++), -dist, qh MINdenom_1, &zerodiv);
    +      if (zerodiv)
    +        goto LABELerroroutside;
    +    }
    +  }
    +  *nextp= coordp;
    +  if (qh IStracing >= 4) {
    +    qh_fprintf(qh ferr, 8021, "qh_sethalfspace: halfspace at offset %6.2g to point: ", *offset);
    +    for (k=dim, coordp=coords; k--; ) {
    +      r= *coordp++;
    +      qh_fprintf(qh ferr, 8022, " %6.2g", r);
    +    }
    +    qh_fprintf(qh ferr, 8023, "\n");
    +  }
    +  return True;
    +LABELerroroutside:
    +  feasiblep= feasible;
    +  normp= normal;
    +  qh_fprintf(qh ferr, 6023, "qhull input error: feasible point is not clearly inside halfspace\nfeasible point: ");
    +  for (k=dim; k--; )
    +    qh_fprintf(qh ferr, 8024, qh_REAL_1, r=*(feasiblep++));
    +  qh_fprintf(qh ferr, 8025, "\n     halfspace: ");
    +  for (k=dim; k--; )
    +    qh_fprintf(qh ferr, 8026, qh_REAL_1, r=*(normp++));
    +  qh_fprintf(qh ferr, 8027, "\n     at offset: ");
    +  qh_fprintf(qh ferr, 8028, qh_REAL_1, *offset);
    +  qh_fprintf(qh ferr, 8029, " and distance: ");
    +  qh_fprintf(qh ferr, 8030, qh_REAL_1, dist);
    +  qh_fprintf(qh ferr, 8031, "\n");
    +  return False;
    +} /* sethalfspace */
    +
    +/*---------------------------------
    +
    +  qh_sethalfspace_all( dim, count, halfspaces, feasible )
    +    generate dual for halfspace intersection with feasible point
    +    array of count halfspaces
    +      each halfspace is normal coefficients followed by offset
    +      the origin is inside the halfspace if the offset is negative
    +    feasible is a point inside all halfspaces (http://www.qhull.org/html/qhalf.htm#notes)
    +
    +  returns:
    +    malloc'd array of count X dim-1 points
    +
    +  notes:
    +    call before qh_init_B or qh_initqhull_globals
    +    free memory when done
    +    unused/untested code: please email bradb@shore.net if this works ok for you
    +    if using option 'Fp', qh->feasible_point must be set (e.g., to 'feasible')
    +    qh->feasible_point is a malloc'd array that is freed by qh_freebuffers.
    +
    +  design:
    +    see qh_sethalfspace
    +*/
    +coordT *qh_sethalfspace_all(int dim, int count, coordT *halfspaces, pointT *feasible) {
    +  int i, newdim;
    +  pointT *newpoints;
    +  coordT *coordp, *normalp, *offsetp;
    +
    +  trace0((qh ferr, 12, "qh_sethalfspace_all: compute dual for halfspace intersection\n"));
    +  newdim= dim - 1;
    +  if (!(newpoints=(coordT*)qh_malloc(count*newdim*sizeof(coordT)))){
    +    qh_fprintf(qh ferr, 6024, "qhull error: insufficient memory to compute dual of %d halfspaces\n",
    +          count);
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  coordp= newpoints;
    +  normalp= halfspaces;
    +  for (i=0; i < count; i++) {
    +    offsetp= normalp + newdim;
    +    if (!qh_sethalfspace(newdim, coordp, &coordp, normalp, offsetp, feasible)) {
    +      qh_free(newpoints);  /* feasible is not inside halfspace as reported by qh_sethalfspace */
    +      qh_fprintf(qh ferr, 8032, "The halfspace was at index %d\n", i);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    normalp= offsetp + 1;
    +  }
    +  return newpoints;
    +} /* sethalfspace_all */
    +
    +
    +/*---------------------------------
    +
    +  qh_sharpnewfacets()
    +
    +  returns:
    +    true if could be an acute angle (facets in different quadrants)
    +
    +  notes:
    +    for qh_findbest
    +
    +  design:
    +    for all facets on qh.newfacet_list
    +      if two facets are in different quadrants
    +        set issharp
    +*/
    +boolT qh_sharpnewfacets(void) {
    +  facetT *facet;
    +  boolT issharp = False;
    +  int *quadrant, k;
    +
    +  quadrant= (int*)qh_memalloc(qh hull_dim * sizeof(int));
    +  FORALLfacet_(qh newfacet_list) {
    +    if (facet == qh newfacet_list) {
    +      for (k=qh hull_dim; k--; )
    +        quadrant[ k]= (facet->normal[ k] > 0);
    +    }else {
    +      for (k=qh hull_dim; k--; ) {
    +        if (quadrant[ k] != (facet->normal[ k] > 0)) {
    +          issharp= True;
    +          break;
    +        }
    +      }
    +    }
    +    if (issharp)
    +      break;
    +  }
    +  qh_memfree( quadrant, qh hull_dim * sizeof(int));
    +  trace3((qh ferr, 3001, "qh_sharpnewfacets: %d\n", issharp));
    +  return issharp;
    +} /* sharpnewfacets */
    +
    +/*---------------------------------
    +
    +  qh_voronoi_center( dim, points )
    +    return Voronoi center for a set of points
    +    dim is the orginal dimension of the points
    +    gh.gm_matrix/qh.gm_row are scratch buffers
    +
    +  returns:
    +    center as a temporary point (qh_memalloc)
    +    if non-simplicial,
    +      returns center for max simplex of points
    +
    +  notes:
    +    only called by qh_facetcenter
    +    from Bowyer & Woodwark, A Programmer's Geometry, 1983, p. 65
    +
    +  design:
    +    if non-simplicial
    +      determine max simplex for points
    +    translate point0 of simplex to origin
    +    compute sum of squares of diagonal
    +    compute determinate
    +    compute Voronoi center (see Bowyer & Woodwark)
    +*/
    +pointT *qh_voronoi_center(int dim, setT *points) {
    +  pointT *point, **pointp, *point0;
    +  pointT *center= (pointT*)qh_memalloc(qh center_size);
    +  setT *simplex;
    +  int i, j, k, size= qh_setsize(points);
    +  coordT *gmcoord;
    +  realT *diffp, sum2, *sum2row, *sum2p, det, factor;
    +  boolT nearzero, infinite;
    +
    +  if (size == dim+1)
    +    simplex= points;
    +  else if (size < dim+1) {
    +    qh_memfree(center, qh center_size);
    +    qh_fprintf(qh ferr, 6025, "qhull internal error (qh_voronoi_center):\n  need at least %d points to construct a Voronoi center\n",
    +             dim+1);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +    simplex= points;  /* never executed -- avoids warning */
    +  }else {
    +    simplex= qh_settemp(dim+1);
    +    qh_maxsimplex(dim, points, NULL, 0, &simplex);
    +  }
    +  point0= SETfirstt_(simplex, pointT);
    +  gmcoord= qh gm_matrix;
    +  for (k=0; k < dim; k++) {
    +    qh gm_row[k]= gmcoord;
    +    FOREACHpoint_(simplex) {
    +      if (point != point0)
    +        *(gmcoord++)= point[k] - point0[k];
    +    }
    +  }
    +  sum2row= gmcoord;
    +  for (i=0; i < dim; i++) {
    +    sum2= 0.0;
    +    for (k=0; k < dim; k++) {
    +      diffp= qh gm_row[k] + i;
    +      sum2 += *diffp * *diffp;
    +    }
    +    *(gmcoord++)= sum2;
    +  }
    +  det= qh_determinant(qh gm_row, dim, &nearzero);
    +  factor= qh_divzero(0.5, det, qh MINdenom, &infinite);
    +  if (infinite) {
    +    for (k=dim; k--; )
    +      center[k]= qh_INFINITE;
    +    if (qh IStracing)
    +      qh_printpoints(qh ferr, "qh_voronoi_center: at infinity for ", simplex);
    +  }else {
    +    for (i=0; i < dim; i++) {
    +      gmcoord= qh gm_matrix;
    +      sum2p= sum2row;
    +      for (k=0; k < dim; k++) {
    +        qh gm_row[k]= gmcoord;
    +        if (k == i) {
    +          for (j=dim; j--; )
    +            *(gmcoord++)= *sum2p++;
    +        }else {
    +          FOREACHpoint_(simplex) {
    +            if (point != point0)
    +              *(gmcoord++)= point[k] - point0[k];
    +          }
    +        }
    +      }
    +      center[i]= qh_determinant(qh gm_row, dim, &nearzero)*factor + point0[i];
    +    }
    +#ifndef qh_NOtrace
    +    if (qh IStracing >= 3) {
    +      qh_fprintf(qh ferr, 8033, "qh_voronoi_center: det %2.2g factor %2.2g ", det, factor);
    +      qh_printmatrix(qh ferr, "center:", ¢er, 1, dim);
    +      if (qh IStracing >= 5) {
    +        qh_printpoints(qh ferr, "points", simplex);
    +        FOREACHpoint_(simplex)
    +          qh_fprintf(qh ferr, 8034, "p%d dist %.2g, ", qh_pointid(point),
    +                   qh_pointdist(point, center, dim));
    +        qh_fprintf(qh ferr, 8035, "\n");
    +      }
    +    }
    +#endif
    +  }
    +  if (simplex != points)
    +    qh_settempfree(&simplex);
    +  return center;
    +} /* voronoi_center */
    +
    diff --git a/xs/src/qhull/src/libqhull/global.c b/xs/src/qhull/src/libqhull/global.c
    new file mode 100644
    index 0000000000..0328fea7b9
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/global.c
    @@ -0,0 +1,2217 @@
    +
    +/*
      ---------------------------------
    +
    +   global.c
    +   initializes all the globals of the qhull application
    +
    +   see README
    +
    +   see libqhull.h for qh.globals and function prototypes
    +
    +   see qhull_a.h for internal functions
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/global.c#17 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    + */
    +
    +#include "qhull_a.h"
    +
    +/*========= qh definition -- globals defined in libqhull.h =======================*/
    +
    +#if qh_QHpointer
    +qhT *qh_qh= NULL;       /* pointer to all global variables */
    +#else
    +qhT qh_qh;              /* all global variables.
    +                           Add "= {0}" if this causes a compiler error.
    +                           Also qh_qhstat in stat.c and qhmem in mem.c.  */
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_version
    +    version string by year and date
    +    qh_version2 for Unix users and -V
    +
    +    the revision increases on code changes only
    +
    +  notes:
    +    change date:    Changes.txt, Announce.txt, index.htm, README.txt,
    +                    qhull-news.html, Eudora signatures, CMakeLists.txt
    +    change version: README.txt, qh-get.htm, File_id.diz, Makefile.txt, CMakeLists.txt
    +    check that CmakeLists @version is the same as qh_version2
    +    change year:    Copying.txt
    +    check download size
    +    recompile user_eg.c, rbox.c, libqhull.c, qconvex.c, qdelaun.c qvoronoi.c, qhalf.c, testqset.c
    +*/
    +
    +const char qh_version[]= "2015.2 2016/01/18";
    +const char qh_version2[]= "qhull 7.2.0 (2015.2 2016/01/18)";
    +
    +/*---------------------------------
    +
    +  qh_appendprint( printFormat )
    +    append printFormat to qh.PRINTout unless already defined
    +*/
    +void qh_appendprint(qh_PRINT format) {
    +  int i;
    +
    +  for (i=0; i < qh_PRINTEND; i++) {
    +    if (qh PRINTout[i] == format && format != qh_PRINTqhull)
    +      break;
    +    if (!qh PRINTout[i]) {
    +      qh PRINTout[i]= format;
    +      break;
    +    }
    +  }
    +} /* appendprint */
    +
    +/*---------------------------------
    +
    +  qh_checkflags( commandStr, hiddenFlags )
    +    errors if commandStr contains hiddenFlags
    +    hiddenFlags starts and ends with a space and is space delimited (checked)
    +
    +  notes:
    +    ignores first word (e.g., "qconvex i")
    +    use qh_strtol/strtod since strtol/strtod may or may not skip trailing spaces
    +
    +  see:
    +    qh_initflags() initializes Qhull according to commandStr
    +*/
    +void qh_checkflags(char *command, char *hiddenflags) {
    +  char *s= command, *t, *chkerr; /* qh_skipfilename is non-const */
    +  char key, opt, prevopt;
    +  char chkkey[]= "   ";
    +  char chkopt[]=  "    ";
    +  char chkopt2[]= "     ";
    +  boolT waserr= False;
    +
    +  if (*hiddenflags != ' ' || hiddenflags[strlen(hiddenflags)-1] != ' ') {
    +    qh_fprintf(qh ferr, 6026, "qhull error (qh_checkflags): hiddenflags must start and end with a space: \"%s\"", hiddenflags);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (strpbrk(hiddenflags, ",\n\r\t")) {
    +    qh_fprintf(qh ferr, 6027, "qhull error (qh_checkflags): hiddenflags contains commas, newlines, or tabs: \"%s\"", hiddenflags);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  while (*s && !isspace(*s))  /* skip program name */
    +    s++;
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    key = *s++;
    +    chkerr = NULL;
    +    if (key == 'T' && (*s == 'I' || *s == 'O')) {  /* TI or TO 'file name' */
    +      s= qh_skipfilename(++s);
    +      continue;
    +    }
    +    chkkey[1]= key;
    +    if (strstr(hiddenflags, chkkey)) {
    +      chkerr= chkkey;
    +    }else if (isupper(key)) {
    +      opt= ' ';
    +      prevopt= ' ';
    +      chkopt[1]= key;
    +      chkopt2[1]= key;
    +      while (!chkerr && *s && !isspace(*s)) {
    +        opt= *s++;
    +        if (isalpha(opt)) {
    +          chkopt[2]= opt;
    +          if (strstr(hiddenflags, chkopt))
    +            chkerr= chkopt;
    +          if (prevopt != ' ') {
    +            chkopt2[2]= prevopt;
    +            chkopt2[3]= opt;
    +            if (strstr(hiddenflags, chkopt2))
    +              chkerr= chkopt2;
    +          }
    +        }else if (key == 'Q' && isdigit(opt) && prevopt != 'b'
    +              && (prevopt == ' ' || islower(prevopt))) {
    +            chkopt[2]= opt;
    +            if (strstr(hiddenflags, chkopt))
    +              chkerr= chkopt;
    +        }else {
    +          qh_strtod(s-1, &t);
    +          if (s < t)
    +            s= t;
    +        }
    +        prevopt= opt;
    +      }
    +    }
    +    if (chkerr) {
    +      *chkerr= '\'';
    +      chkerr[strlen(chkerr)-1]=  '\'';
    +      qh_fprintf(qh ferr, 6029, "qhull error: option %s is not used with this program.\n             It may be used with qhull.\n", chkerr);
    +      waserr= True;
    +    }
    +  }
    +  if (waserr)
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +} /* checkflags */
    +
    +/*---------------------------------
    +
    +  qh_clear_outputflags()
    +    Clear output flags for QhullPoints
    +*/
    +void qh_clear_outputflags(void) {
    +  int i,k;
    +
    +  qh ANNOTATEoutput= False;
    +  qh DOintersections= False;
    +  qh DROPdim= -1;
    +  qh FORCEoutput= False;
    +  qh GETarea= False;
    +  qh GOODpoint= 0;
    +  qh GOODpointp= NULL;
    +  qh GOODthreshold= False;
    +  qh GOODvertex= 0;
    +  qh GOODvertexp= NULL;
    +  qh IStracing= 0;
    +  qh KEEParea= False;
    +  qh KEEPmerge= False;
    +  qh KEEPminArea= REALmax;
    +  qh PRINTcentrums= False;
    +  qh PRINTcoplanar= False;
    +  qh PRINTdots= False;
    +  qh PRINTgood= False;
    +  qh PRINTinner= False;
    +  qh PRINTneighbors= False;
    +  qh PRINTnoplanes= False;
    +  qh PRINToptions1st= False;
    +  qh PRINTouter= False;
    +  qh PRINTprecision= True;
    +  qh PRINTridges= False;
    +  qh PRINTspheres= False;
    +  qh PRINTstatistics= False;
    +  qh PRINTsummary= False;
    +  qh PRINTtransparent= False;
    +  qh SPLITthresholds= False;
    +  qh TRACElevel= 0;
    +  qh TRInormals= False;
    +  qh USEstdout= False;
    +  qh VERIFYoutput= False;
    +  for (k=qh input_dim+1; k--; ) {  /* duplicated in qh_initqhull_buffers and qh_clear_outputflags */
    +    qh lower_threshold[k]= -REALmax;
    +    qh upper_threshold[k]= REALmax;
    +    qh lower_bound[k]= -REALmax;
    +    qh upper_bound[k]= REALmax;
    +  }
    +
    +  for (i=0; i < qh_PRINTEND; i++) {
    +    qh PRINTout[i]= qh_PRINTnone;
    +  }
    +
    +  if (!qh qhull_commandsiz2)
    +      qh qhull_commandsiz2= (int)strlen(qh qhull_command); /* WARN64 */
    +  else {
    +      qh qhull_command[qh qhull_commandsiz2]= '\0';
    +  }
    +  if (!qh qhull_optionsiz2)
    +    qh qhull_optionsiz2= (int)strlen(qh qhull_options);  /* WARN64 */
    +  else {
    +    qh qhull_options[qh qhull_optionsiz2]= '\0';
    +    qh qhull_optionlen= qh_OPTIONline;  /* start a new line */
    +  }
    +} /* clear_outputflags */
    +
    +/*---------------------------------
    +
    +  qh_clock()
    +    return user CPU time in 100ths (qh_SECtick)
    +    only defined for qh_CLOCKtype == 2
    +
    +  notes:
    +    use first value to determine time 0
    +    from Stevens '92 8.15
    +*/
    +unsigned long qh_clock(void) {
    +
    +#if (qh_CLOCKtype == 2)
    +  struct tms time;
    +  static long clktck;  /* initialized first call and never updated */
    +  double ratio, cpu;
    +  unsigned long ticks;
    +
    +  if (!clktck) {
    +    if ((clktck= sysconf(_SC_CLK_TCK)) < 0) {
    +      qh_fprintf(qh ferr, 6030, "qhull internal error (qh_clock): sysconf() failed.  Use qh_CLOCKtype 1 in user.h\n");
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +  }
    +  if (times(&time) == -1) {
    +    qh_fprintf(qh ferr, 6031, "qhull internal error (qh_clock): times() failed.  Use qh_CLOCKtype 1 in user.h\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  ratio= qh_SECticks / (double)clktck;
    +  ticks= time.tms_utime * ratio;
    +  return ticks;
    +#else
    +  qh_fprintf(qh ferr, 6032, "qhull internal error (qh_clock): use qh_CLOCKtype 2 in user.h\n");
    +  qh_errexit(qh_ERRqhull, NULL, NULL); /* never returns */
    +  return 0;
    +#endif
    +} /* clock */
    +
    +/*---------------------------------
    +
    +  qh_freebuffers()
    +    free up global memory buffers
    +
    +  notes:
    +    must match qh_initbuffers()
    +*/
    +void qh_freebuffers(void) {
    +
    +  trace5((qh ferr, 5001, "qh_freebuffers: freeing up global memory buffers\n"));
    +  /* allocated by qh_initqhull_buffers */
    +  qh_memfree(qh NEARzero, qh hull_dim * sizeof(realT));
    +  qh_memfree(qh lower_threshold, (qh input_dim+1) * sizeof(realT));
    +  qh_memfree(qh upper_threshold, (qh input_dim+1) * sizeof(realT));
    +  qh_memfree(qh lower_bound, (qh input_dim+1) * sizeof(realT));
    +  qh_memfree(qh upper_bound, (qh input_dim+1) * sizeof(realT));
    +  qh_memfree(qh gm_matrix, (qh hull_dim+1) * qh hull_dim * sizeof(coordT));
    +  qh_memfree(qh gm_row, (qh hull_dim+1) * sizeof(coordT *));
    +  qh NEARzero= qh lower_threshold= qh upper_threshold= NULL;
    +  qh lower_bound= qh upper_bound= NULL;
    +  qh gm_matrix= NULL;
    +  qh gm_row= NULL;
    +  qh_setfree(&qh other_points);
    +  qh_setfree(&qh del_vertices);
    +  qh_setfree(&qh coplanarfacetset);
    +  if (qh line)                /* allocated by qh_readinput, freed if no error */
    +    qh_free(qh line);
    +  if (qh half_space)
    +    qh_free(qh half_space);
    +  if (qh temp_malloc)
    +    qh_free(qh temp_malloc);
    +  if (qh feasible_point)      /* allocated by qh_readfeasible */
    +    qh_free(qh feasible_point);
    +  if (qh feasible_string)     /* allocated by qh_initflags */
    +    qh_free(qh feasible_string);
    +  qh line= qh feasible_string= NULL;
    +  qh half_space= qh feasible_point= qh temp_malloc= NULL;
    +  /* usually allocated by qh_readinput */
    +  if (qh first_point && qh POINTSmalloc) {
    +    qh_free(qh first_point);
    +    qh first_point= NULL;
    +  }
    +  if (qh input_points && qh input_malloc) { /* set by qh_joggleinput */
    +    qh_free(qh input_points);
    +    qh input_points= NULL;
    +  }
    +  trace5((qh ferr, 5002, "qh_freebuffers: finished\n"));
    +} /* freebuffers */
    +
    +
    +/*---------------------------------
    +
    +  qh_freebuild( allmem )
    +    free global memory used by qh_initbuild and qh_buildhull
    +    if !allmem,
    +      does not free short memory (e.g., facetT, freed by qh_memfreeshort)
    +
    +  design:
    +    free centrums
    +    free each vertex
    +    mark unattached ridges
    +    for each facet
    +      free ridges
    +      free outside set, coplanar set, neighbor set, ridge set, vertex set
    +      free facet
    +    free hash table
    +    free interior point
    +    free merge set
    +    free temporary sets
    +*/
    +void qh_freebuild(boolT allmem) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  ridgeT *ridge, **ridgep;
    +  mergeT *merge, **mergep;
    +
    +  trace1((qh ferr, 1005, "qh_freebuild: free memory from qh_inithull and qh_buildhull\n"));
    +  if (qh del_vertices)
    +    qh_settruncate(qh del_vertices, 0);
    +  if (allmem) {
    +    while ((vertex= qh vertex_list)) {
    +      if (vertex->next)
    +        qh_delvertex(vertex);
    +      else {
    +        qh_memfree(vertex, (int)sizeof(vertexT));
    +        qh newvertex_list= qh vertex_list= NULL;
    +      }
    +    }
    +  }else if (qh VERTEXneighbors) {
    +    FORALLvertices
    +      qh_setfreelong(&(vertex->neighbors));
    +  }
    +  qh VERTEXneighbors= False;
    +  qh GOODclosest= NULL;
    +  if (allmem) {
    +    FORALLfacets {
    +      FOREACHridge_(facet->ridges)
    +        ridge->seen= False;
    +    }
    +    FORALLfacets {
    +      if (facet->visible) {
    +        FOREACHridge_(facet->ridges) {
    +          if (!otherfacet_(ridge, facet)->visible)
    +            ridge->seen= True;  /* an unattached ridge */
    +        }
    +      }
    +    }
    +    while ((facet= qh facet_list)) {
    +      FOREACHridge_(facet->ridges) {
    +        if (ridge->seen) {
    +          qh_setfree(&(ridge->vertices));
    +          qh_memfree(ridge, (int)sizeof(ridgeT));
    +        }else
    +          ridge->seen= True;
    +      }
    +      qh_setfree(&(facet->outsideset));
    +      qh_setfree(&(facet->coplanarset));
    +      qh_setfree(&(facet->neighbors));
    +      qh_setfree(&(facet->ridges));
    +      qh_setfree(&(facet->vertices));
    +      if (facet->next)
    +        qh_delfacet(facet);
    +      else {
    +        qh_memfree(facet, (int)sizeof(facetT));
    +        qh visible_list= qh newfacet_list= qh facet_list= NULL;
    +      }
    +    }
    +  }else {
    +    FORALLfacets {
    +      qh_setfreelong(&(facet->outsideset));
    +      qh_setfreelong(&(facet->coplanarset));
    +      if (!facet->simplicial) {
    +        qh_setfreelong(&(facet->neighbors));
    +        qh_setfreelong(&(facet->ridges));
    +        qh_setfreelong(&(facet->vertices));
    +      }
    +    }
    +  }
    +  qh_setfree(&(qh hash_table));
    +  qh_memfree(qh interior_point, qh normal_size);
    +  qh interior_point= NULL;
    +  FOREACHmerge_(qh facet_mergeset)  /* usually empty */
    +    qh_memfree(merge, (int)sizeof(mergeT));
    +  qh facet_mergeset= NULL;  /* temp set */
    +  qh degen_mergeset= NULL;  /* temp set */
    +  qh_settempfree_all();
    +} /* freebuild */
    +
    +/*---------------------------------
    +
    +  qh_freeqhull( allmem )
    +    see qh_freeqhull2
    +    if qh_QHpointer, frees qh_qh
    +*/
    +void qh_freeqhull(boolT allmem) {
    +    qh_freeqhull2(allmem);
    +#if qh_QHpointer
    +    qh_free(qh_qh);
    +    qh_qh= NULL;
    +#endif
    +}
    +
    +/*---------------------------------
    +
    +qh_freeqhull2( allmem )
    +  free global memory and set qhT to 0
    +  if !allmem,
    +    does not free short memory (freed by qh_memfreeshort unless qh_NOmem)
    +
    +notes:
    +  sets qh.NOerrexit in case caller forgets to
    +  Does not throw errors
    +
    +see:
    +  see qh_initqhull_start2()
    +
    +design:
    +  free global and temporary memory from qh_initbuild and qh_buildhull
    +  free buffers
    +  free statistics
    +*/
    +void qh_freeqhull2(boolT allmem) {
    +
    +  qh NOerrexit= True;  /* no more setjmp since called at exit and ~QhullQh */
    +  trace1((qh ferr, 1006, "qh_freeqhull: free global memory\n"));
    +  qh_freebuild(allmem);
    +  qh_freebuffers();
    +  qh_freestatistics();
    +#if qh_QHpointer
    +  memset((char *)qh_qh, 0, sizeof(qhT));
    +  /* qh_qh freed by caller, qh_freeqhull() */
    +#else
    +  memset((char *)&qh_qh, 0, sizeof(qhT));
    +#endif
    +  qh NOerrexit= True;
    +} /* freeqhull2 */
    +
    +/*---------------------------------
    +
    +  qh_init_A( infile, outfile, errfile, argc, argv )
    +    initialize memory and stdio files
    +    convert input options to option string (qh.qhull_command)
    +
    +  notes:
    +    infile may be NULL if qh_readpoints() is not called
    +
    +    errfile should always be defined.  It is used for reporting
    +    errors.  outfile is used for output and format options.
    +
    +    argc/argv may be 0/NULL
    +
    +    called before error handling initialized
    +    qh_errexit() may not be used
    +*/
    +void qh_init_A(FILE *infile, FILE *outfile, FILE *errfile, int argc, char *argv[]) {
    +  qh_meminit(errfile);
    +  qh_initqhull_start(infile, outfile, errfile);
    +  qh_init_qhull_command(argc, argv);
    +} /* init_A */
    +
    +/*---------------------------------
    +
    +  qh_init_B( points, numpoints, dim, ismalloc )
    +    initialize globals for points array
    +
    +    points has numpoints dim-dimensional points
    +      points[0] is the first coordinate of the first point
    +      points[1] is the second coordinate of the first point
    +      points[dim] is the first coordinate of the second point
    +
    +    ismalloc=True
    +      Qhull will call qh_free(points) on exit or input transformation
    +    ismalloc=False
    +      Qhull will allocate a new point array if needed for input transformation
    +
    +    qh.qhull_command
    +      is the option string.
    +      It is defined by qh_init_B(), qh_qhull_command(), or qh_initflags
    +
    +  returns:
    +    if qh.PROJECTinput or (qh.DELAUNAY and qh.PROJECTdelaunay)
    +      projects the input to a new point array
    +
    +        if qh.DELAUNAY,
    +          qh.hull_dim is increased by one
    +        if qh.ATinfinity,
    +          qh_projectinput adds point-at-infinity for Delaunay tri.
    +
    +    if qh.SCALEinput
    +      changes the upper and lower bounds of the input, see qh_scaleinput()
    +
    +    if qh.ROTATEinput
    +      rotates the input by a random rotation, see qh_rotateinput()
    +      if qh.DELAUNAY
    +        rotates about the last coordinate
    +
    +  notes:
    +    called after points are defined
    +    qh_errexit() may be used
    +*/
    +void qh_init_B(coordT *points, int numpoints, int dim, boolT ismalloc) {
    +  qh_initqhull_globals(points, numpoints, dim, ismalloc);
    +  if (qhmem.LASTsize == 0)
    +    qh_initqhull_mem();
    +  /* mem.c and qset.c are initialized */
    +  qh_initqhull_buffers();
    +  qh_initthresholds(qh qhull_command);
    +  if (qh PROJECTinput || (qh DELAUNAY && qh PROJECTdelaunay))
    +    qh_projectinput();
    +  if (qh SCALEinput)
    +    qh_scaleinput();
    +  if (qh ROTATErandom >= 0) {
    +    qh_randommatrix(qh gm_matrix, qh hull_dim, qh gm_row);
    +    if (qh DELAUNAY) {
    +      int k, lastk= qh hull_dim-1;
    +      for (k=0; k < lastk; k++) {
    +        qh gm_row[k][lastk]= 0.0;
    +        qh gm_row[lastk][k]= 0.0;
    +      }
    +      qh gm_row[lastk][lastk]= 1.0;
    +    }
    +    qh_gram_schmidt(qh hull_dim, qh gm_row);
    +    qh_rotateinput(qh gm_row);
    +  }
    +} /* init_B */
    +
    +/*---------------------------------
    +
    +  qh_init_qhull_command( argc, argv )
    +    build qh.qhull_command from argc/argv
    +    Calls qh_exit if qhull_command is too short
    +
    +  returns:
    +    a space-delimited string of options (just as typed)
    +
    +  notes:
    +    makes option string easy to input and output
    +
    +    argc/argv may be 0/NULL
    +*/
    +void qh_init_qhull_command(int argc, char *argv[]) {
    +
    +  if (!qh_argv_to_command(argc, argv, qh qhull_command, (int)sizeof(qh qhull_command))){
    +    /* Assumes qh.ferr is defined. */
    +    qh_fprintf(qh ferr, 6033, "qhull input error: more than %d characters in command line\n",
    +          (int)sizeof(qh qhull_command));
    +    qh_exit(qh_ERRinput);  /* error reported, can not use qh_errexit */
    +  }
    +} /* init_qhull_command */
    +
    +/*---------------------------------
    +
    +  qh_initflags( commandStr )
    +    set flags and initialized constants from commandStr
    +    calls qh_exit() if qh->NOerrexit
    +
    +  returns:
    +    sets qh.qhull_command to command if needed
    +
    +  notes:
    +    ignores first word (e.g., "qhull d")
    +    use qh_strtol/strtod since strtol/strtod may or may not skip trailing spaces
    +
    +  see:
    +    qh_initthresholds() continues processing of 'Pdn' and 'PDn'
    +    'prompt' in unix.c for documentation
    +
    +  design:
    +    for each space-delimited option group
    +      if top-level option
    +        check syntax
    +        append appropriate option to option string
    +        set appropriate global variable or append printFormat to print options
    +      else
    +        for each sub-option
    +          check syntax
    +          append appropriate option to option string
    +          set appropriate global variable or append printFormat to print options
    +*/
    +void qh_initflags(char *command) {
    +  int k, i, lastproject;
    +  char *s= command, *t, *prev_s, *start, key;
    +  boolT isgeom= False, wasproject;
    +  realT r;
    +
    +  if(qh NOerrexit){/* without this comment, segfault in gcc 4.4.0 mingw32 */
    +    qh_fprintf(qh ferr, 6245, "qhull initflags error: qh.NOerrexit was not cleared before calling qh_initflags().  It should be cleared after setjmp().  Exit qhull.");
    +    qh_exit(6245);
    +  }
    +  if (command <= &qh qhull_command[0] || command > &qh qhull_command[0] + sizeof(qh qhull_command)) {
    +    if (command != &qh qhull_command[0]) {
    +      *qh qhull_command= '\0';
    +      strncat(qh qhull_command, command, sizeof(qh qhull_command)-strlen(qh qhull_command)-1);
    +    }
    +    while (*s && !isspace(*s))  /* skip program name */
    +      s++;
    +  }
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    prev_s= s;
    +    switch (*s++) {
    +    case 'd':
    +      qh_option("delaunay", NULL, NULL);
    +      qh DELAUNAY= True;
    +      break;
    +    case 'f':
    +      qh_option("facets", NULL, NULL);
    +      qh_appendprint(qh_PRINTfacets);
    +      break;
    +    case 'i':
    +      qh_option("incidence", NULL, NULL);
    +      qh_appendprint(qh_PRINTincidences);
    +      break;
    +    case 'm':
    +      qh_option("mathematica", NULL, NULL);
    +      qh_appendprint(qh_PRINTmathematica);
    +      break;
    +    case 'n':
    +      qh_option("normals", NULL, NULL);
    +      qh_appendprint(qh_PRINTnormals);
    +      break;
    +    case 'o':
    +      qh_option("offFile", NULL, NULL);
    +      qh_appendprint(qh_PRINToff);
    +      break;
    +    case 'p':
    +      qh_option("points", NULL, NULL);
    +      qh_appendprint(qh_PRINTpoints);
    +      break;
    +    case 's':
    +      qh_option("summary", NULL, NULL);
    +      qh PRINTsummary= True;
    +      break;
    +    case 'v':
    +      qh_option("voronoi", NULL, NULL);
    +      qh VORONOI= True;
    +      qh DELAUNAY= True;
    +      break;
    +    case 'A':
    +      if (!isdigit(*s) && *s != '.' && *s != '-')
    +        qh_fprintf(qh ferr, 7002, "qhull warning: no maximum cosine angle given for option 'An'.  Ignored.\n");
    +      else {
    +        if (*s == '-') {
    +          qh premerge_cos= -qh_strtod(s, &s);
    +          qh_option("Angle-premerge-", NULL, &qh premerge_cos);
    +          qh PREmerge= True;
    +        }else {
    +          qh postmerge_cos= qh_strtod(s, &s);
    +          qh_option("Angle-postmerge", NULL, &qh postmerge_cos);
    +          qh POSTmerge= True;
    +        }
    +        qh MERGING= True;
    +      }
    +      break;
    +    case 'C':
    +      if (!isdigit(*s) && *s != '.' && *s != '-')
    +        qh_fprintf(qh ferr, 7003, "qhull warning: no centrum radius given for option 'Cn'.  Ignored.\n");
    +      else {
    +        if (*s == '-') {
    +          qh premerge_centrum= -qh_strtod(s, &s);
    +          qh_option("Centrum-premerge-", NULL, &qh premerge_centrum);
    +          qh PREmerge= True;
    +        }else {
    +          qh postmerge_centrum= qh_strtod(s, &s);
    +          qh_option("Centrum-postmerge", NULL, &qh postmerge_centrum);
    +          qh POSTmerge= True;
    +        }
    +        qh MERGING= True;
    +      }
    +      break;
    +    case 'E':
    +      if (*s == '-')
    +        qh_fprintf(qh ferr, 7004, "qhull warning: negative maximum roundoff given for option 'An'.  Ignored.\n");
    +      else if (!isdigit(*s))
    +        qh_fprintf(qh ferr, 7005, "qhull warning: no maximum roundoff given for option 'En'.  Ignored.\n");
    +      else {
    +        qh DISTround= qh_strtod(s, &s);
    +        qh_option("Distance-roundoff", NULL, &qh DISTround);
    +        qh SETroundoff= True;
    +      }
    +      break;
    +    case 'H':
    +      start= s;
    +      qh HALFspace= True;
    +      qh_strtod(s, &t);
    +      while (t > s)  {
    +        if (*t && !isspace(*t)) {
    +          if (*t == ',')
    +            t++;
    +          else
    +            qh_fprintf(qh ferr, 7006, "qhull warning: origin for Halfspace intersection should be 'Hn,n,n,...'\n");
    +        }
    +        s= t;
    +        qh_strtod(s, &t);
    +      }
    +      if (start < t) {
    +        if (!(qh feasible_string= (char*)calloc((size_t)(t-start+1), (size_t)1))) {
    +          qh_fprintf(qh ferr, 6034, "qhull error: insufficient memory for 'Hn,n,n'\n");
    +          qh_errexit(qh_ERRmem, NULL, NULL);
    +        }
    +        strncpy(qh feasible_string, start, (size_t)(t-start));
    +        qh_option("Halfspace-about", NULL, NULL);
    +        qh_option(qh feasible_string, NULL, NULL);
    +      }else
    +        qh_option("Halfspace", NULL, NULL);
    +      break;
    +    case 'R':
    +      if (!isdigit(*s))
    +        qh_fprintf(qh ferr, 7007, "qhull warning: missing random perturbation for option 'Rn'.  Ignored\n");
    +      else {
    +        qh RANDOMfactor= qh_strtod(s, &s);
    +        qh_option("Random_perturb", NULL, &qh RANDOMfactor);
    +        qh RANDOMdist= True;
    +      }
    +      break;
    +    case 'V':
    +      if (!isdigit(*s) && *s != '-')
    +        qh_fprintf(qh ferr, 7008, "qhull warning: missing visible distance for option 'Vn'.  Ignored\n");
    +      else {
    +        qh MINvisible= qh_strtod(s, &s);
    +        qh_option("Visible", NULL, &qh MINvisible);
    +      }
    +      break;
    +    case 'U':
    +      if (!isdigit(*s) && *s != '-')
    +        qh_fprintf(qh ferr, 7009, "qhull warning: missing coplanar distance for option 'Un'.  Ignored\n");
    +      else {
    +        qh MAXcoplanar= qh_strtod(s, &s);
    +        qh_option("U-coplanar", NULL, &qh MAXcoplanar);
    +      }
    +      break;
    +    case 'W':
    +      if (*s == '-')
    +        qh_fprintf(qh ferr, 7010, "qhull warning: negative outside width for option 'Wn'.  Ignored.\n");
    +      else if (!isdigit(*s))
    +        qh_fprintf(qh ferr, 7011, "qhull warning: missing outside width for option 'Wn'.  Ignored\n");
    +      else {
    +        qh MINoutside= qh_strtod(s, &s);
    +        qh_option("W-outside", NULL, &qh MINoutside);
    +        qh APPROXhull= True;
    +      }
    +      break;
    +    /************  sub menus ***************/
    +    case 'F':
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'a':
    +          qh_option("Farea", NULL, NULL);
    +          qh_appendprint(qh_PRINTarea);
    +          qh GETarea= True;
    +          break;
    +        case 'A':
    +          qh_option("FArea-total", NULL, NULL);
    +          qh GETarea= True;
    +          break;
    +        case 'c':
    +          qh_option("Fcoplanars", NULL, NULL);
    +          qh_appendprint(qh_PRINTcoplanars);
    +          break;
    +        case 'C':
    +          qh_option("FCentrums", NULL, NULL);
    +          qh_appendprint(qh_PRINTcentrums);
    +          break;
    +        case 'd':
    +          qh_option("Fd-cdd-in", NULL, NULL);
    +          qh CDDinput= True;
    +          break;
    +        case 'D':
    +          qh_option("FD-cdd-out", NULL, NULL);
    +          qh CDDoutput= True;
    +          break;
    +        case 'F':
    +          qh_option("FFacets-xridge", NULL, NULL);
    +          qh_appendprint(qh_PRINTfacets_xridge);
    +          break;
    +        case 'i':
    +          qh_option("Finner", NULL, NULL);
    +          qh_appendprint(qh_PRINTinner);
    +          break;
    +        case 'I':
    +          qh_option("FIDs", NULL, NULL);
    +          qh_appendprint(qh_PRINTids);
    +          break;
    +        case 'm':
    +          qh_option("Fmerges", NULL, NULL);
    +          qh_appendprint(qh_PRINTmerges);
    +          break;
    +        case 'M':
    +          qh_option("FMaple", NULL, NULL);
    +          qh_appendprint(qh_PRINTmaple);
    +          break;
    +        case 'n':
    +          qh_option("Fneighbors", NULL, NULL);
    +          qh_appendprint(qh_PRINTneighbors);
    +          break;
    +        case 'N':
    +          qh_option("FNeighbors-vertex", NULL, NULL);
    +          qh_appendprint(qh_PRINTvneighbors);
    +          break;
    +        case 'o':
    +          qh_option("Fouter", NULL, NULL);
    +          qh_appendprint(qh_PRINTouter);
    +          break;
    +        case 'O':
    +          if (qh PRINToptions1st) {
    +            qh_option("FOptions", NULL, NULL);
    +            qh_appendprint(qh_PRINToptions);
    +          }else
    +            qh PRINToptions1st= True;
    +          break;
    +        case 'p':
    +          qh_option("Fpoint-intersect", NULL, NULL);
    +          qh_appendprint(qh_PRINTpointintersect);
    +          break;
    +        case 'P':
    +          qh_option("FPoint-nearest", NULL, NULL);
    +          qh_appendprint(qh_PRINTpointnearest);
    +          break;
    +        case 'Q':
    +          qh_option("FQhull", NULL, NULL);
    +          qh_appendprint(qh_PRINTqhull);
    +          break;
    +        case 's':
    +          qh_option("Fsummary", NULL, NULL);
    +          qh_appendprint(qh_PRINTsummary);
    +          break;
    +        case 'S':
    +          qh_option("FSize", NULL, NULL);
    +          qh_appendprint(qh_PRINTsize);
    +          qh GETarea= True;
    +          break;
    +        case 't':
    +          qh_option("Ftriangles", NULL, NULL);
    +          qh_appendprint(qh_PRINTtriangles);
    +          break;
    +        case 'v':
    +          /* option set in qh_initqhull_globals */
    +          qh_appendprint(qh_PRINTvertices);
    +          break;
    +        case 'V':
    +          qh_option("FVertex-average", NULL, NULL);
    +          qh_appendprint(qh_PRINTaverage);
    +          break;
    +        case 'x':
    +          qh_option("Fxtremes", NULL, NULL);
    +          qh_appendprint(qh_PRINTextremes);
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7012, "qhull warning: unknown 'F' output option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'G':
    +      isgeom= True;
    +      qh_appendprint(qh_PRINTgeom);
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'a':
    +          qh_option("Gall-points", NULL, NULL);
    +          qh PRINTdots= True;
    +          break;
    +        case 'c':
    +          qh_option("Gcentrums", NULL, NULL);
    +          qh PRINTcentrums= True;
    +          break;
    +        case 'h':
    +          qh_option("Gintersections", NULL, NULL);
    +          qh DOintersections= True;
    +          break;
    +        case 'i':
    +          qh_option("Ginner", NULL, NULL);
    +          qh PRINTinner= True;
    +          break;
    +        case 'n':
    +          qh_option("Gno-planes", NULL, NULL);
    +          qh PRINTnoplanes= True;
    +          break;
    +        case 'o':
    +          qh_option("Gouter", NULL, NULL);
    +          qh PRINTouter= True;
    +          break;
    +        case 'p':
    +          qh_option("Gpoints", NULL, NULL);
    +          qh PRINTcoplanar= True;
    +          break;
    +        case 'r':
    +          qh_option("Gridges", NULL, NULL);
    +          qh PRINTridges= True;
    +          break;
    +        case 't':
    +          qh_option("Gtransparent", NULL, NULL);
    +          qh PRINTtransparent= True;
    +          break;
    +        case 'v':
    +          qh_option("Gvertices", NULL, NULL);
    +          qh PRINTspheres= True;
    +          break;
    +        case 'D':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6035, "qhull input error: missing dimension for option 'GDn'\n");
    +          else {
    +            if (qh DROPdim >= 0)
    +              qh_fprintf(qh ferr, 7013, "qhull warning: can only drop one dimension.  Previous 'GD%d' ignored\n",
    +                   qh DROPdim);
    +            qh DROPdim= qh_strtol(s, &s);
    +            qh_option("GDrop-dim", &qh DROPdim, NULL);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7014, "qhull warning: unknown 'G' print option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'P':
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'd': case 'D':  /* see qh_initthresholds() */
    +          key= s[-1];
    +          i= qh_strtol(s, &s);
    +          r= 0;
    +          if (*s == ':') {
    +            s++;
    +            r= qh_strtod(s, &s);
    +          }
    +          if (key == 'd')
    +            qh_option("Pdrop-facets-dim-less", &i, &r);
    +          else
    +            qh_option("PDrop-facets-dim-more", &i, &r);
    +          break;
    +        case 'g':
    +          qh_option("Pgood-facets", NULL, NULL);
    +          qh PRINTgood= True;
    +          break;
    +        case 'G':
    +          qh_option("PGood-facet-neighbors", NULL, NULL);
    +          qh PRINTneighbors= True;
    +          break;
    +        case 'o':
    +          qh_option("Poutput-forced", NULL, NULL);
    +          qh FORCEoutput= True;
    +          break;
    +        case 'p':
    +          qh_option("Pprecision-ignore", NULL, NULL);
    +          qh PRINTprecision= False;
    +          break;
    +        case 'A':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6036, "qhull input error: missing facet count for keep area option 'PAn'\n");
    +          else {
    +            qh KEEParea= qh_strtol(s, &s);
    +            qh_option("PArea-keep", &qh KEEParea, NULL);
    +            qh GETarea= True;
    +          }
    +          break;
    +        case 'F':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6037, "qhull input error: missing facet area for option 'PFn'\n");
    +          else {
    +            qh KEEPminArea= qh_strtod(s, &s);
    +            qh_option("PFacet-area-keep", NULL, &qh KEEPminArea);
    +            qh GETarea= True;
    +          }
    +          break;
    +        case 'M':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6038, "qhull input error: missing merge count for option 'PMn'\n");
    +          else {
    +            qh KEEPmerge= qh_strtol(s, &s);
    +            qh_option("PMerge-keep", &qh KEEPmerge, NULL);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7015, "qhull warning: unknown 'P' print option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'Q':
    +      lastproject= -1;
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'b': case 'B':  /* handled by qh_initthresholds */
    +          key= s[-1];
    +          if (key == 'b' && *s == 'B') {
    +            s++;
    +            r= qh_DEFAULTbox;
    +            qh SCALEinput= True;
    +            qh_option("QbBound-unit-box", NULL, &r);
    +            break;
    +          }
    +          if (key == 'b' && *s == 'b') {
    +            s++;
    +            qh SCALElast= True;
    +            qh_option("Qbbound-last", NULL, NULL);
    +            break;
    +          }
    +          k= qh_strtol(s, &s);
    +          r= 0.0;
    +          wasproject= False;
    +          if (*s == ':') {
    +            s++;
    +            if ((r= qh_strtod(s, &s)) == 0.0) {
    +              t= s;            /* need true dimension for memory allocation */
    +              while (*t && !isspace(*t)) {
    +                if (toupper(*t++) == 'B'
    +                 && k == qh_strtol(t, &t)
    +                 && *t++ == ':'
    +                 && qh_strtod(t, &t) == 0.0) {
    +                  qh PROJECTinput++;
    +                  trace2((qh ferr, 2004, "qh_initflags: project dimension %d\n", k));
    +                  qh_option("Qb-project-dim", &k, NULL);
    +                  wasproject= True;
    +                  lastproject= k;
    +                  break;
    +                }
    +              }
    +            }
    +          }
    +          if (!wasproject) {
    +            if (lastproject == k && r == 0.0)
    +              lastproject= -1;  /* doesn't catch all possible sequences */
    +            else if (key == 'b') {
    +              qh SCALEinput= True;
    +              if (r == 0.0)
    +                r= -qh_DEFAULTbox;
    +              qh_option("Qbound-dim-low", &k, &r);
    +            }else {
    +              qh SCALEinput= True;
    +              if (r == 0.0)
    +                r= qh_DEFAULTbox;
    +              qh_option("QBound-dim-high", &k, &r);
    +            }
    +          }
    +          break;
    +        case 'c':
    +          qh_option("Qcoplanar-keep", NULL, NULL);
    +          qh KEEPcoplanar= True;
    +          break;
    +        case 'f':
    +          qh_option("Qfurthest-outside", NULL, NULL);
    +          qh BESToutside= True;
    +          break;
    +        case 'g':
    +          qh_option("Qgood-facets-only", NULL, NULL);
    +          qh ONLYgood= True;
    +          break;
    +        case 'i':
    +          qh_option("Qinterior-keep", NULL, NULL);
    +          qh KEEPinside= True;
    +          break;
    +        case 'm':
    +          qh_option("Qmax-outside-only", NULL, NULL);
    +          qh ONLYmax= True;
    +          break;
    +        case 'r':
    +          qh_option("Qrandom-outside", NULL, NULL);
    +          qh RANDOMoutside= True;
    +          break;
    +        case 's':
    +          qh_option("Qsearch-initial-simplex", NULL, NULL);
    +          qh ALLpoints= True;
    +          break;
    +        case 't':
    +          qh_option("Qtriangulate", NULL, NULL);
    +          qh TRIangulate= True;
    +          break;
    +        case 'T':
    +          qh_option("QTestPoints", NULL, NULL);
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6039, "qhull input error: missing number of test points for option 'QTn'\n");
    +          else {
    +            qh TESTpoints= qh_strtol(s, &s);
    +            qh_option("QTestPoints", &qh TESTpoints, NULL);
    +          }
    +          break;
    +        case 'u':
    +          qh_option("QupperDelaunay", NULL, NULL);
    +          qh UPPERdelaunay= True;
    +          break;
    +        case 'v':
    +          qh_option("Qvertex-neighbors-convex", NULL, NULL);
    +          qh TESTvneighbors= True;
    +          break;
    +        case 'x':
    +          qh_option("Qxact-merge", NULL, NULL);
    +          qh MERGEexact= True;
    +          break;
    +        case 'z':
    +          qh_option("Qz-infinity-point", NULL, NULL);
    +          qh ATinfinity= True;
    +          break;
    +        case '0':
    +          qh_option("Q0-no-premerge", NULL, NULL);
    +          qh NOpremerge= True;
    +          break;
    +        case '1':
    +          if (!isdigit(*s)) {
    +            qh_option("Q1-no-angle-sort", NULL, NULL);
    +            qh ANGLEmerge= False;
    +            break;
    +          }
    +          switch (*s++) {
    +          case '0':
    +            qh_option("Q10-no-narrow", NULL, NULL);
    +            qh NOnarrow= True;
    +            break;
    +          case '1':
    +            qh_option("Q11-trinormals Qtriangulate", NULL, NULL);
    +            qh TRInormals= True;
    +            qh TRIangulate= True;
    +            break;
    +          case '2':
    +            qh_option("Q12-no-wide-dup", NULL, NULL);
    +            qh NOwide= True;
    +            break;
    +          default:
    +            s--;
    +            qh_fprintf(qh ferr, 7016, "qhull warning: unknown 'Q' qhull option 1%c, rest ignored\n", (int)s[0]);
    +            while (*++s && !isspace(*s));
    +            break;
    +          }
    +          break;
    +        case '2':
    +          qh_option("Q2-no-merge-independent", NULL, NULL);
    +          qh MERGEindependent= False;
    +          goto LABELcheckdigit;
    +          break; /* no warnings */
    +        case '3':
    +          qh_option("Q3-no-merge-vertices", NULL, NULL);
    +          qh MERGEvertices= False;
    +        LABELcheckdigit:
    +          if (isdigit(*s))
    +            qh_fprintf(qh ferr, 7017, "qhull warning: can not follow '1', '2', or '3' with a digit.  '%c' skipped.\n",
    +                     *s++);
    +          break;
    +        case '4':
    +          qh_option("Q4-avoid-old-into-new", NULL, NULL);
    +          qh AVOIDold= True;
    +          break;
    +        case '5':
    +          qh_option("Q5-no-check-outer", NULL, NULL);
    +          qh SKIPcheckmax= True;
    +          break;
    +        case '6':
    +          qh_option("Q6-no-concave-merge", NULL, NULL);
    +          qh SKIPconvex= True;
    +          break;
    +        case '7':
    +          qh_option("Q7-no-breadth-first", NULL, NULL);
    +          qh VIRTUALmemory= True;
    +          break;
    +        case '8':
    +          qh_option("Q8-no-near-inside", NULL, NULL);
    +          qh NOnearinside= True;
    +          break;
    +        case '9':
    +          qh_option("Q9-pick-furthest", NULL, NULL);
    +          qh PICKfurthest= True;
    +          break;
    +        case 'G':
    +          i= qh_strtol(s, &t);
    +          if (qh GOODpoint)
    +            qh_fprintf(qh ferr, 7018, "qhull warning: good point already defined for option 'QGn'.  Ignored\n");
    +          else if (s == t)
    +            qh_fprintf(qh ferr, 7019, "qhull warning: missing good point id for option 'QGn'.  Ignored\n");
    +          else if (i < 0 || *s == '-') {
    +            qh GOODpoint= i-1;
    +            qh_option("QGood-if-dont-see-point", &i, NULL);
    +          }else {
    +            qh GOODpoint= i+1;
    +            qh_option("QGood-if-see-point", &i, NULL);
    +          }
    +          s= t;
    +          break;
    +        case 'J':
    +          if (!isdigit(*s) && *s != '-')
    +            qh JOGGLEmax= 0.0;
    +          else {
    +            qh JOGGLEmax= (realT) qh_strtod(s, &s);
    +            qh_option("QJoggle", NULL, &qh JOGGLEmax);
    +          }
    +          break;
    +        case 'R':
    +          if (!isdigit(*s) && *s != '-')
    +            qh_fprintf(qh ferr, 7020, "qhull warning: missing random seed for option 'QRn'.  Ignored\n");
    +          else {
    +            qh ROTATErandom= i= qh_strtol(s, &s);
    +            if (i > 0)
    +              qh_option("QRotate-id", &i, NULL );
    +            else if (i < -1)
    +              qh_option("QRandom-seed", &i, NULL );
    +          }
    +          break;
    +        case 'V':
    +          i= qh_strtol(s, &t);
    +          if (qh GOODvertex)
    +            qh_fprintf(qh ferr, 7021, "qhull warning: good vertex already defined for option 'QVn'.  Ignored\n");
    +          else if (s == t)
    +            qh_fprintf(qh ferr, 7022, "qhull warning: no good point id given for option 'QVn'.  Ignored\n");
    +          else if (i < 0) {
    +            qh GOODvertex= i - 1;
    +            qh_option("QV-good-facets-not-point", &i, NULL);
    +          }else {
    +            qh_option("QV-good-facets-point", &i, NULL);
    +            qh GOODvertex= i + 1;
    +          }
    +          s= t;
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7023, "qhull warning: unknown 'Q' qhull option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'T':
    +      while (*s && !isspace(*s)) {
    +        if (isdigit(*s) || *s == '-')
    +          qh IStracing= qh_strtol(s, &s);
    +        else switch (*s++) {
    +        case 'a':
    +          qh_option("Tannotate-output", NULL, NULL);
    +          qh ANNOTATEoutput= True;
    +          break;
    +        case 'c':
    +          qh_option("Tcheck-frequently", NULL, NULL);
    +          qh CHECKfrequently= True;
    +          break;
    +        case 's':
    +          qh_option("Tstatistics", NULL, NULL);
    +          qh PRINTstatistics= True;
    +          break;
    +        case 'v':
    +          qh_option("Tverify", NULL, NULL);
    +          qh VERIFYoutput= True;
    +          break;
    +        case 'z':
    +          if (qh ferr == qh_FILEstderr) {
    +            /* The C++ interface captures the output in qh_fprint_qhull() */
    +            qh_option("Tz-stdout", NULL, NULL);
    +            qh USEstdout= True;
    +          }else if (!qh fout)
    +            qh_fprintf(qh ferr, 7024, "qhull warning: output file undefined(stdout).  Option 'Tz' ignored.\n");
    +          else {
    +            qh_option("Tz-stdout", NULL, NULL);
    +            qh USEstdout= True;
    +            qh ferr= qh fout;
    +            qhmem.ferr= qh fout;
    +          }
    +          break;
    +        case 'C':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7025, "qhull warning: missing point id for cone for trace option 'TCn'.  Ignored\n");
    +          else {
    +            i= qh_strtol(s, &s);
    +            qh_option("TCone-stop", &i, NULL);
    +            qh STOPcone= i + 1;
    +          }
    +          break;
    +        case 'F':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7026, "qhull warning: missing frequency count for trace option 'TFn'.  Ignored\n");
    +          else {
    +            qh REPORTfreq= qh_strtol(s, &s);
    +            qh_option("TFacet-log", &qh REPORTfreq, NULL);
    +            qh REPORTfreq2= qh REPORTfreq/2;  /* for tracemerging() */
    +          }
    +          break;
    +        case 'I':
    +          if (!isspace(*s))
    +            qh_fprintf(qh ferr, 7027, "qhull warning: missing space between 'TI' and filename, %s\n", s);
    +          while (isspace(*s))
    +            s++;
    +          t= qh_skipfilename(s);
    +          {
    +            char filename[qh_FILENAMElen];
    +
    +            qh_copyfilename(filename, (int)sizeof(filename), s, (int)(t-s));   /* WARN64 */
    +            s= t;
    +            if (!freopen(filename, "r", stdin)) {
    +              qh_fprintf(qh ferr, 6041, "qhull error: could not open file \"%s\".", filename);
    +              qh_errexit(qh_ERRinput, NULL, NULL);
    +            }else {
    +              qh_option("TInput-file", NULL, NULL);
    +              qh_option(filename, NULL, NULL);
    +            }
    +          }
    +          break;
    +        case 'O':
    +            if (!isspace(*s))
    +                qh_fprintf(qh ferr, 7028, "qhull warning: missing space between 'TO' and filename, %s\n", s);
    +            while (isspace(*s))
    +                s++;
    +            t= qh_skipfilename(s);
    +            {
    +              char filename[qh_FILENAMElen];
    +
    +              qh_copyfilename(filename, (int)sizeof(filename), s, (int)(t-s));  /* WARN64 */
    +              s= t;
    +              if (!qh fout) {
    +                qh_fprintf(qh ferr, 6266, "qhull input warning: qh.fout was not set by caller.  Cannot use option 'TO' to redirect output.  Ignoring option 'TO'\n");
    +              }else if (!freopen(filename, "w", qh fout)) {
    +                qh_fprintf(qh ferr, 6044, "qhull error: could not open file \"%s\".", filename);
    +                qh_errexit(qh_ERRinput, NULL, NULL);
    +              }else {
    +                qh_option("TOutput-file", NULL, NULL);
    +              qh_option(filename, NULL, NULL);
    +            }
    +          }
    +          break;
    +        case 'P':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7029, "qhull warning: missing point id for trace option 'TPn'.  Ignored\n");
    +          else {
    +            qh TRACEpoint= qh_strtol(s, &s);
    +            qh_option("Trace-point", &qh TRACEpoint, NULL);
    +          }
    +          break;
    +        case 'M':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7030, "qhull warning: missing merge id for trace option 'TMn'.  Ignored\n");
    +          else {
    +            qh TRACEmerge= qh_strtol(s, &s);
    +            qh_option("Trace-merge", &qh TRACEmerge, NULL);
    +          }
    +          break;
    +        case 'R':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7031, "qhull warning: missing rerun count for trace option 'TRn'.  Ignored\n");
    +          else {
    +            qh RERUN= qh_strtol(s, &s);
    +            qh_option("TRerun", &qh RERUN, NULL);
    +          }
    +          break;
    +        case 'V':
    +          i= qh_strtol(s, &t);
    +          if (s == t)
    +            qh_fprintf(qh ferr, 7032, "qhull warning: missing furthest point id for trace option 'TVn'.  Ignored\n");
    +          else if (i < 0) {
    +            qh STOPpoint= i - 1;
    +            qh_option("TV-stop-before-point", &i, NULL);
    +          }else {
    +            qh STOPpoint= i + 1;
    +            qh_option("TV-stop-after-point", &i, NULL);
    +          }
    +          s= t;
    +          break;
    +        case 'W':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7033, "qhull warning: missing max width for trace option 'TWn'.  Ignored\n");
    +          else {
    +            qh TRACEdist= (realT) qh_strtod(s, &s);
    +            qh_option("TWide-trace", NULL, &qh TRACEdist);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7034, "qhull warning: unknown 'T' trace option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    default:
    +      qh_fprintf(qh ferr, 7035, "qhull warning: unknown flag %c(%x)\n", (int)s[-1],
    +               (int)s[-1]);
    +      break;
    +    }
    +    if (s-1 == prev_s && *s && !isspace(*s)) {
    +      qh_fprintf(qh ferr, 7036, "qhull warning: missing space after flag %c(%x); reserved for menu. Skipped.\n",
    +               (int)*prev_s, (int)*prev_s);
    +      while (*s && !isspace(*s))
    +        s++;
    +    }
    +  }
    +  if (qh STOPcone && qh JOGGLEmax < REALmax/2)
    +    qh_fprintf(qh ferr, 7078, "qhull warning: 'TCn' (stopCone) ignored when used with 'QJn' (joggle)\n");
    +  if (isgeom && !qh FORCEoutput && qh PRINTout[1])
    +    qh_fprintf(qh ferr, 7037, "qhull warning: additional output formats are not compatible with Geomview\n");
    +  /* set derived values in qh_initqhull_globals */
    +} /* initflags */
    +
    +
    +/*---------------------------------
    +
    +  qh_initqhull_buffers()
    +    initialize global memory buffers
    +
    +  notes:
    +    must match qh_freebuffers()
    +*/
    +void qh_initqhull_buffers(void) {
    +  int k;
    +
    +  qh TEMPsize= (qhmem.LASTsize - sizeof(setT))/SETelemsize;
    +  if (qh TEMPsize <= 0 || qh TEMPsize > qhmem.LASTsize)
    +    qh TEMPsize= 8;  /* e.g., if qh_NOmem */
    +  qh other_points= qh_setnew(qh TEMPsize);
    +  qh del_vertices= qh_setnew(qh TEMPsize);
    +  qh coplanarfacetset= qh_setnew(qh TEMPsize);
    +  qh NEARzero= (realT *)qh_memalloc(qh hull_dim * sizeof(realT));
    +  qh lower_threshold= (realT *)qh_memalloc((qh input_dim+1) * sizeof(realT));
    +  qh upper_threshold= (realT *)qh_memalloc((qh input_dim+1) * sizeof(realT));
    +  qh lower_bound= (realT *)qh_memalloc((qh input_dim+1) * sizeof(realT));
    +  qh upper_bound= (realT *)qh_memalloc((qh input_dim+1) * sizeof(realT));
    +  for (k=qh input_dim+1; k--; ) {  /* duplicated in qh_initqhull_buffers and qh_clear_outputflags */
    +    qh lower_threshold[k]= -REALmax;
    +    qh upper_threshold[k]= REALmax;
    +    qh lower_bound[k]= -REALmax;
    +    qh upper_bound[k]= REALmax;
    +  }
    +  qh gm_matrix= (coordT *)qh_memalloc((qh hull_dim+1) * qh hull_dim * sizeof(coordT));
    +  qh gm_row= (coordT **)qh_memalloc((qh hull_dim+1) * sizeof(coordT *));
    +} /* initqhull_buffers */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_globals( points, numpoints, dim, ismalloc )
    +    initialize globals
    +    if ismalloc
    +      points were malloc'd and qhull should free at end
    +
    +  returns:
    +    sets qh.first_point, num_points, input_dim, hull_dim and others
    +    seeds random number generator (seed=1 if tracing)
    +    modifies qh.hull_dim if ((qh.DELAUNAY and qh.PROJECTdelaunay) or qh.PROJECTinput)
    +    adjust user flags as needed
    +    also checks DIM3 dependencies and constants
    +
    +  notes:
    +    do not use qh_point() since an input transformation may move them elsewhere
    +
    +  see:
    +    qh_initqhull_start() sets default values for non-zero globals
    +
    +  design:
    +    initialize points array from input arguments
    +    test for qh.ZEROcentrum
    +      (i.e., use opposite vertex instead of cetrum for convexity testing)
    +    initialize qh.CENTERtype, qh.normal_size,
    +      qh.center_size, qh.TRACEpoint/level,
    +    initialize and test random numbers
    +    qh_initqhull_outputflags() -- adjust and test output flags
    +*/
    +void qh_initqhull_globals(coordT *points, int numpoints, int dim, boolT ismalloc) {
    +  int seed, pointsneeded, extra= 0, i, randi, k;
    +  realT randr;
    +  realT factorial;
    +
    +  time_t timedata;
    +
    +  trace0((qh ferr, 13, "qh_initqhull_globals: for %s | %s\n", qh rbox_command,
    +      qh qhull_command));
    +  qh POINTSmalloc= ismalloc;
    +  qh first_point= points;
    +  qh num_points= numpoints;
    +  qh hull_dim= qh input_dim= dim;
    +  if (!qh NOpremerge && !qh MERGEexact && !qh PREmerge && qh JOGGLEmax > REALmax/2) {
    +    qh MERGING= True;
    +    if (qh hull_dim <= 4) {
    +      qh PREmerge= True;
    +      qh_option("_pre-merge", NULL, NULL);
    +    }else {
    +      qh MERGEexact= True;
    +      qh_option("Qxact_merge", NULL, NULL);
    +    }
    +  }else if (qh MERGEexact)
    +    qh MERGING= True;
    +  if (!qh NOpremerge && qh JOGGLEmax > REALmax/2) {
    +#ifdef qh_NOmerge
    +    qh JOGGLEmax= 0.0;
    +#endif
    +  }
    +  if (qh TRIangulate && qh JOGGLEmax < REALmax/2 && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 7038, "qhull warning: joggle('QJ') always produces simplicial output.  Triangulated output('Qt') does nothing.\n");
    +  if (qh JOGGLEmax < REALmax/2 && qh DELAUNAY && !qh SCALEinput && !qh SCALElast) {
    +    qh SCALElast= True;
    +    qh_option("Qbbound-last-qj", NULL, NULL);
    +  }
    +  if (qh MERGING && !qh POSTmerge && qh premerge_cos > REALmax/2
    +  && qh premerge_centrum == 0) {
    +    qh ZEROcentrum= True;
    +    qh ZEROall_ok= True;
    +    qh_option("_zero-centrum", NULL, NULL);
    +  }
    +  if (qh JOGGLEmax < REALmax/2 && REALepsilon > 2e-8 && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 7039, "qhull warning: real epsilon, %2.2g, is probably too large for joggle('QJn')\nRecompile with double precision reals(see user.h).\n",
    +          REALepsilon);
    +#ifdef qh_NOmerge
    +  if (qh MERGING) {
    +    qh_fprintf(qh ferr, 6045, "qhull input error: merging not installed(qh_NOmerge + 'Qx', 'Cn' or 'An')\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +#endif
    +  if (qh DELAUNAY && qh KEEPcoplanar && !qh KEEPinside) {
    +    qh KEEPinside= True;
    +    qh_option("Qinterior-keep", NULL, NULL);
    +  }
    +  if (qh DELAUNAY && qh HALFspace) {
    +    qh_fprintf(qh ferr, 6046, "qhull input error: can not use Delaunay('d') or Voronoi('v') with halfspace intersection('H')\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (!qh DELAUNAY && (qh UPPERdelaunay || qh ATinfinity)) {
    +    qh_fprintf(qh ferr, 6047, "qhull input error: use upper-Delaunay('Qu') or infinity-point('Qz') with Delaunay('d') or Voronoi('v')\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh UPPERdelaunay && qh ATinfinity) {
    +    qh_fprintf(qh ferr, 6048, "qhull input error: can not use infinity-point('Qz') with upper-Delaunay('Qu')\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh SCALElast && !qh DELAUNAY && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 7040, "qhull input warning: option 'Qbb' (scale-last-coordinate) is normally used with 'd' or 'v'\n");
    +  qh DOcheckmax= (!qh SKIPcheckmax && qh MERGING );
    +  qh KEEPnearinside= (qh DOcheckmax && !(qh KEEPinside && qh KEEPcoplanar)
    +                          && !qh NOnearinside);
    +  if (qh MERGING)
    +    qh CENTERtype= qh_AScentrum;
    +  else if (qh VORONOI)
    +    qh CENTERtype= qh_ASvoronoi;
    +  if (qh TESTvneighbors && !qh MERGING) {
    +    qh_fprintf(qh ferr, 6049, "qhull input error: test vertex neighbors('Qv') needs a merge option\n");
    +    qh_errexit(qh_ERRinput, NULL ,NULL);
    +  }
    +  if (qh PROJECTinput || (qh DELAUNAY && qh PROJECTdelaunay)) {
    +    qh hull_dim -= qh PROJECTinput;
    +    if (qh DELAUNAY) {
    +      qh hull_dim++;
    +      if (qh ATinfinity)
    +        extra= 1;
    +    }
    +  }
    +  if (qh hull_dim <= 1) {
    +    qh_fprintf(qh ferr, 6050, "qhull error: dimension %d must be > 1\n", qh hull_dim);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  for (k=2, factorial=1.0; k < qh hull_dim; k++)
    +    factorial *= k;
    +  qh AREAfactor= 1.0 / factorial;
    +  trace2((qh ferr, 2005, "qh_initqhull_globals: initialize globals.  dim %d numpoints %d malloc? %d projected %d to hull_dim %d\n",
    +        dim, numpoints, ismalloc, qh PROJECTinput, qh hull_dim));
    +  qh normal_size= qh hull_dim * sizeof(coordT);
    +  qh center_size= qh normal_size - sizeof(coordT);
    +  pointsneeded= qh hull_dim+1;
    +  if (qh hull_dim > qh_DIMmergeVertex) {
    +    qh MERGEvertices= False;
    +    qh_option("Q3-no-merge-vertices-dim-high", NULL, NULL);
    +  }
    +  if (qh GOODpoint)
    +    pointsneeded++;
    +#ifdef qh_NOtrace
    +  if (qh IStracing) {
    +    qh_fprintf(qh ferr, 6051, "qhull input error: tracing is not installed(qh_NOtrace in user.h)");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +#endif
    +  if (qh RERUN > 1) {
    +    qh TRACElastrun= qh IStracing; /* qh_build_withrestart duplicates next conditional */
    +    if (qh IStracing != -1)
    +      qh IStracing= 0;
    +  }else if (qh TRACEpoint != qh_IDunknown || qh TRACEdist < REALmax/2 || qh TRACEmerge) {
    +    qh TRACElevel= (qh IStracing? qh IStracing : 3);
    +    qh IStracing= 0;
    +  }
    +  if (qh ROTATErandom == 0 || qh ROTATErandom == -1) {
    +    seed= (int)time(&timedata);
    +    if (qh ROTATErandom  == -1) {
    +      seed= -seed;
    +      qh_option("QRandom-seed", &seed, NULL );
    +    }else
    +      qh_option("QRotate-random", &seed, NULL);
    +    qh ROTATErandom= seed;
    +  }
    +  seed= qh ROTATErandom;
    +  if (seed == INT_MIN)    /* default value */
    +    seed= 1;
    +  else if (seed < 0)
    +    seed= -seed;
    +  qh_RANDOMseed_(seed);
    +  randr= 0.0;
    +  for (i=1000; i--; ) {
    +    randi= qh_RANDOMint;
    +    randr += randi;
    +    if (randi > qh_RANDOMmax) {
    +      qh_fprintf(qh ferr, 8036, "\
    +qhull configuration error (qh_RANDOMmax in user.h):\n\
    +   random integer %d > qh_RANDOMmax(%.8g)\n",
    +               randi, qh_RANDOMmax);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +  }
    +  qh_RANDOMseed_(seed);
    +  randr = randr/1000;
    +  if (randr < qh_RANDOMmax * 0.1
    +  || randr > qh_RANDOMmax * 0.9)
    +    qh_fprintf(qh ferr, 8037, "\
    +qhull configuration warning (qh_RANDOMmax in user.h):\n\
    +   average of 1000 random integers (%.2g) is much different than expected (%.2g).\n\
    +   Is qh_RANDOMmax (%.2g) wrong?\n",
    +             randr, qh_RANDOMmax * 0.5, qh_RANDOMmax);
    +  qh RANDOMa= 2.0 * qh RANDOMfactor/qh_RANDOMmax;
    +  qh RANDOMb= 1.0 - qh RANDOMfactor;
    +  if (qh_HASHfactor < 1.1) {
    +    qh_fprintf(qh ferr, 6052, "qhull internal error (qh_initqhull_globals): qh_HASHfactor %d must be at least 1.1.  Qhull uses linear hash probing\n",
    +      qh_HASHfactor);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (numpoints+extra < pointsneeded) {
    +    qh_fprintf(qh ferr, 6214, "qhull input error: not enough points(%d) to construct initial simplex (need %d)\n",
    +            numpoints, pointsneeded);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  qh_initqhull_outputflags();
    +} /* initqhull_globals */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_mem(  )
    +    initialize mem.c for qhull
    +    qh.hull_dim and qh.normal_size determine some of the allocation sizes
    +    if qh.MERGING,
    +      includes ridgeT
    +    calls qh_user_memsizes() to add up to 10 additional sizes for quick allocation
    +      (see numsizes below)
    +
    +  returns:
    +    mem.c already for qh_memalloc/qh_memfree (errors if called beforehand)
    +
    +  notes:
    +    qh_produceoutput() prints memsizes
    +
    +*/
    +void qh_initqhull_mem(void) {
    +  int numsizes;
    +  int i;
    +
    +  numsizes= 8+10;
    +  qh_meminitbuffers(qh IStracing, qh_MEMalign, numsizes,
    +                     qh_MEMbufsize,qh_MEMinitbuf);
    +  qh_memsize((int)sizeof(vertexT));
    +  if (qh MERGING) {
    +    qh_memsize((int)sizeof(ridgeT));
    +    qh_memsize((int)sizeof(mergeT));
    +  }
    +  qh_memsize((int)sizeof(facetT));
    +  i= sizeof(setT) + (qh hull_dim - 1) * SETelemsize;  /* ridge.vertices */
    +  qh_memsize(i);
    +  qh_memsize(qh normal_size);        /* normal */
    +  i += SETelemsize;                 /* facet.vertices, .ridges, .neighbors */
    +  qh_memsize(i);
    +  qh_user_memsizes();
    +  qh_memsetup();
    +} /* initqhull_mem */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_outputflags
    +    initialize flags concerned with output
    +
    +  returns:
    +    adjust user flags as needed
    +
    +  see:
    +    qh_clear_outputflags() resets the flags
    +
    +  design:
    +    test for qh.PRINTgood (i.e., only print 'good' facets)
    +    check for conflicting print output options
    +*/
    +void qh_initqhull_outputflags(void) {
    +  boolT printgeom= False, printmath= False, printcoplanar= False;
    +  int i;
    +
    +  trace3((qh ferr, 3024, "qh_initqhull_outputflags: %s\n", qh qhull_command));
    +  if (!(qh PRINTgood || qh PRINTneighbors)) {
    +    if (qh KEEParea || qh KEEPminArea < REALmax/2 || qh KEEPmerge || qh DELAUNAY
    +        || (!qh ONLYgood && (qh GOODvertex || qh GOODpoint))) {
    +      qh PRINTgood= True;
    +      qh_option("Pgood", NULL, NULL);
    +    }
    +  }
    +  if (qh PRINTtransparent) {
    +    if (qh hull_dim != 4 || !qh DELAUNAY || qh VORONOI || qh DROPdim >= 0) {
    +      qh_fprintf(qh ferr, 6215, "qhull input error: transparent Delaunay('Gt') needs 3-d Delaunay('d') w/o 'GDn'\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    qh DROPdim = 3;
    +    qh PRINTridges = True;
    +  }
    +  for (i=qh_PRINTEND; i--; ) {
    +    if (qh PRINTout[i] == qh_PRINTgeom)
    +      printgeom= True;
    +    else if (qh PRINTout[i] == qh_PRINTmathematica || qh PRINTout[i] == qh_PRINTmaple)
    +      printmath= True;
    +    else if (qh PRINTout[i] == qh_PRINTcoplanars)
    +      printcoplanar= True;
    +    else if (qh PRINTout[i] == qh_PRINTpointnearest)
    +      printcoplanar= True;
    +    else if (qh PRINTout[i] == qh_PRINTpointintersect && !qh HALFspace) {
    +      qh_fprintf(qh ferr, 6053, "qhull input error: option 'Fp' is only used for \nhalfspace intersection('Hn,n,n').\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }else if (qh PRINTout[i] == qh_PRINTtriangles && (qh HALFspace || qh VORONOI)) {
    +      qh_fprintf(qh ferr, 6054, "qhull input error: option 'Ft' is not available for Voronoi vertices or halfspace intersection\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }else if (qh PRINTout[i] == qh_PRINTcentrums && qh VORONOI) {
    +      qh_fprintf(qh ferr, 6055, "qhull input error: option 'FC' is not available for Voronoi vertices('v')\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }else if (qh PRINTout[i] == qh_PRINTvertices) {
    +      if (qh VORONOI)
    +        qh_option("Fvoronoi", NULL, NULL);
    +      else
    +        qh_option("Fvertices", NULL, NULL);
    +    }
    +  }
    +  if (printcoplanar && qh DELAUNAY && qh JOGGLEmax < REALmax/2) {
    +    if (qh PRINTprecision)
    +      qh_fprintf(qh ferr, 7041, "qhull input warning: 'QJ' (joggle) will usually prevent coincident input sites for options 'Fc' and 'FP'\n");
    +  }
    +  if (printmath && (qh hull_dim > 3 || qh VORONOI)) {
    +    qh_fprintf(qh ferr, 6056, "qhull input error: Mathematica and Maple output is only available for 2-d and 3-d convex hulls and 2-d Delaunay triangulations\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (printgeom) {
    +    if (qh hull_dim > 4) {
    +      qh_fprintf(qh ferr, 6057, "qhull input error: Geomview output is only available for 2-d, 3-d and 4-d\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh PRINTnoplanes && !(qh PRINTcoplanar + qh PRINTcentrums
    +     + qh PRINTdots + qh PRINTspheres + qh DOintersections + qh PRINTridges)) {
    +      qh_fprintf(qh ferr, 6058, "qhull input error: no output specified for Geomview\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh VORONOI && (qh hull_dim > 3 || qh DROPdim >= 0)) {
    +      qh_fprintf(qh ferr, 6059, "qhull input error: Geomview output for Voronoi diagrams only for 2-d\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    /* can not warn about furthest-site Geomview output: no lower_threshold */
    +    if (qh hull_dim == 4 && qh DROPdim == -1 &&
    +        (qh PRINTcoplanar || qh PRINTspheres || qh PRINTcentrums)) {
    +      qh_fprintf(qh ferr, 7042, "qhull input warning: coplanars, vertices, and centrums output not\n\
    +available for 4-d output(ignored).  Could use 'GDn' instead.\n");
    +      qh PRINTcoplanar= qh PRINTspheres= qh PRINTcentrums= False;
    +    }
    +  }
    +  if (!qh KEEPcoplanar && !qh KEEPinside && !qh ONLYgood) {
    +    if ((qh PRINTcoplanar && qh PRINTspheres) || printcoplanar) {
    +      if (qh QHULLfinished) {
    +        qh_fprintf(qh ferr, 7072, "qhull output warning: ignoring coplanar points, option 'Qc' was not set for the first run of qhull.\n");
    +      }else {
    +        qh KEEPcoplanar = True;
    +        qh_option("Qcoplanar", NULL, NULL);
    +      }
    +    }
    +  }
    +  qh PRINTdim= qh hull_dim;
    +  if (qh DROPdim >=0) {    /* after Geomview checks */
    +    if (qh DROPdim < qh hull_dim) {
    +      qh PRINTdim--;
    +      if (!printgeom || qh hull_dim < 3)
    +        qh_fprintf(qh ferr, 7043, "qhull input warning: drop dimension 'GD%d' is only available for 3-d/4-d Geomview\n", qh DROPdim);
    +    }else
    +      qh DROPdim= -1;
    +  }else if (qh VORONOI) {
    +    qh DROPdim= qh hull_dim-1;
    +    qh PRINTdim= qh hull_dim-1;
    +  }
    +} /* qh_initqhull_outputflags */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_start( infile, outfile, errfile )
    +    allocate memory if needed and call qh_initqhull_start2()
    +*/
    +void qh_initqhull_start(FILE *infile, FILE *outfile, FILE *errfile) {
    +
    +#if qh_QHpointer
    +  if (qh_qh) {
    +    qh_fprintf(errfile, 6205, "qhull error (qh_initqhull_start): qh_qh already defined.  Call qh_save_qhull() first\n");
    +    qh_exit(qh_ERRqhull);  /* no error handler */
    +  }
    +  if (!(qh_qh= (qhT *)qh_malloc(sizeof(qhT)))) {
    +    qh_fprintf(errfile, 6060, "qhull error (qh_initqhull_start): insufficient memory\n");
    +    qh_exit(qh_ERRmem);  /* no error handler */
    +  }
    +#endif
    +  qh_initstatistics();
    +  qh_initqhull_start2(infile, outfile, errfile);
    +} /* initqhull_start */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_start2( infile, outfile, errfile )
    +    start initialization of qhull
    +    initialize statistics, stdio, default values for global variables
    +    assumes qh_qh is defined
    +  notes:
    +    report errors elsewhere, error handling and g_qhull_output [Qhull.cpp, QhullQh()] not in initialized
    +  see:
    +    qh_maxmin() determines the precision constants
    +    qh_freeqhull2()
    +*/
    +void qh_initqhull_start2(FILE *infile, FILE *outfile, FILE *errfile) {
    +  time_t timedata;
    +  int seed;
    +
    +  qh_CPUclock; /* start the clock(for qh_clock).  One-shot. */
    +#if qh_QHpointer
    +  memset((char *)qh_qh, 0, sizeof(qhT));   /* every field is 0, FALSE, NULL */
    +#else
    +  memset((char *)&qh_qh, 0, sizeof(qhT));
    +#endif
    +  qh ANGLEmerge= True;
    +  qh DROPdim= -1;
    +  qh ferr= errfile;
    +  qh fin= infile;
    +  qh fout= outfile;
    +  qh furthest_id= qh_IDunknown;
    +  qh JOGGLEmax= REALmax;
    +  qh KEEPminArea = REALmax;
    +  qh last_low= REALmax;
    +  qh last_high= REALmax;
    +  qh last_newhigh= REALmax;
    +  qh max_outside= 0.0;
    +  qh max_vertex= 0.0;
    +  qh MAXabs_coord= 0.0;
    +  qh MAXsumcoord= 0.0;
    +  qh MAXwidth= -REALmax;
    +  qh MERGEindependent= True;
    +  qh MINdenom_1= fmax_(1.0/REALmax, REALmin); /* used by qh_scalepoints */
    +  qh MINoutside= 0.0;
    +  qh MINvisible= REALmax;
    +  qh MAXcoplanar= REALmax;
    +  qh outside_err= REALmax;
    +  qh premerge_centrum= 0.0;
    +  qh premerge_cos= REALmax;
    +  qh PRINTprecision= True;
    +  qh PRINTradius= 0.0;
    +  qh postmerge_cos= REALmax;
    +  qh postmerge_centrum= 0.0;
    +  qh ROTATErandom= INT_MIN;
    +  qh MERGEvertices= True;
    +  qh totarea= 0.0;
    +  qh totvol= 0.0;
    +  qh TRACEdist= REALmax;
    +  qh TRACEpoint= qh_IDunknown; /* recompile or use 'TPn' */
    +  qh tracefacet_id= UINT_MAX;  /* recompile to trace a facet */
    +  qh tracevertex_id= UINT_MAX; /* recompile to trace a vertex */
    +  seed= (int)time(&timedata);
    +  qh_RANDOMseed_(seed);
    +  qh run_id= qh_RANDOMint;
    +  if(!qh run_id)
    +      qh run_id++;  /* guarantee non-zero */
    +  qh_option("run-id", &qh run_id, NULL);
    +  strcat(qh qhull, "qhull");
    +} /* initqhull_start2 */
    +
    +/*---------------------------------
    +
    +  qh_initthresholds( commandString )
    +    set thresholds for printing and scaling from commandString
    +
    +  returns:
    +    sets qh.GOODthreshold or qh.SPLITthreshold if 'Pd0D1' used
    +
    +  see:
    +    qh_initflags(), 'Qbk' 'QBk' 'Pdk' and 'PDk'
    +    qh_inthresholds()
    +
    +  design:
    +    for each 'Pdn' or 'PDn' option
    +      check syntax
    +      set qh.lower_threshold or qh.upper_threshold
    +    set qh.GOODthreshold if an unbounded threshold is used
    +    set qh.SPLITthreshold if a bounded threshold is used
    +*/
    +void qh_initthresholds(char *command) {
    +  realT value;
    +  int idx, maxdim, k;
    +  char *s= command; /* non-const due to strtol */
    +  char key;
    +
    +  maxdim= qh input_dim;
    +  if (qh DELAUNAY && (qh PROJECTdelaunay || qh PROJECTinput))
    +    maxdim++;
    +  while (*s) {
    +    if (*s == '-')
    +      s++;
    +    if (*s == 'P') {
    +      s++;
    +      while (*s && !isspace(key= *s++)) {
    +        if (key == 'd' || key == 'D') {
    +          if (!isdigit(*s)) {
    +            qh_fprintf(qh ferr, 7044, "qhull warning: no dimension given for Print option '%c' at: %s.  Ignored\n",
    +                    key, s-1);
    +            continue;
    +          }
    +          idx= qh_strtol(s, &s);
    +          if (idx >= qh hull_dim) {
    +            qh_fprintf(qh ferr, 7045, "qhull warning: dimension %d for Print option '%c' is >= %d.  Ignored\n",
    +                idx, key, qh hull_dim);
    +            continue;
    +          }
    +          if (*s == ':') {
    +            s++;
    +            value= qh_strtod(s, &s);
    +            if (fabs((double)value) > 1.0) {
    +              qh_fprintf(qh ferr, 7046, "qhull warning: value %2.4g for Print option %c is > +1 or < -1.  Ignored\n",
    +                      value, key);
    +              continue;
    +            }
    +          }else
    +            value= 0.0;
    +          if (key == 'd')
    +            qh lower_threshold[idx]= value;
    +          else
    +            qh upper_threshold[idx]= value;
    +        }
    +      }
    +    }else if (*s == 'Q') {
    +      s++;
    +      while (*s && !isspace(key= *s++)) {
    +        if (key == 'b' && *s == 'B') {
    +          s++;
    +          for (k=maxdim; k--; ) {
    +            qh lower_bound[k]= -qh_DEFAULTbox;
    +            qh upper_bound[k]= qh_DEFAULTbox;
    +          }
    +        }else if (key == 'b' && *s == 'b')
    +          s++;
    +        else if (key == 'b' || key == 'B') {
    +          if (!isdigit(*s)) {
    +            qh_fprintf(qh ferr, 7047, "qhull warning: no dimension given for Qhull option %c.  Ignored\n",
    +                    key);
    +            continue;
    +          }
    +          idx= qh_strtol(s, &s);
    +          if (idx >= maxdim) {
    +            qh_fprintf(qh ferr, 7048, "qhull warning: dimension %d for Qhull option %c is >= %d.  Ignored\n",
    +                idx, key, maxdim);
    +            continue;
    +          }
    +          if (*s == ':') {
    +            s++;
    +            value= qh_strtod(s, &s);
    +          }else if (key == 'b')
    +            value= -qh_DEFAULTbox;
    +          else
    +            value= qh_DEFAULTbox;
    +          if (key == 'b')
    +            qh lower_bound[idx]= value;
    +          else
    +            qh upper_bound[idx]= value;
    +        }
    +      }
    +    }else {
    +      while (*s && !isspace(*s))
    +        s++;
    +    }
    +    while (isspace(*s))
    +      s++;
    +  }
    +  for (k=qh hull_dim; k--; ) {
    +    if (qh lower_threshold[k] > -REALmax/2) {
    +      qh GOODthreshold= True;
    +      if (qh upper_threshold[k] < REALmax/2) {
    +        qh SPLITthresholds= True;
    +        qh GOODthreshold= False;
    +        break;
    +      }
    +    }else if (qh upper_threshold[k] < REALmax/2)
    +      qh GOODthreshold= True;
    +  }
    +} /* initthresholds */
    +
    +/*---------------------------------
    +
    +  qh_lib_check( qhullLibraryType, qhTsize, vertexTsize, ridgeTsize, facetTsize, setTsize, qhmemTsize )
    +    Report error if library does not agree with caller
    +
    +  notes:
    +    NOerrors -- qh_lib_check can not call qh_errexit()
    +*/
    +void qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeTsize, int facetTsize, int setTsize, int qhmemTsize) {
    +    boolT iserror= False;
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)  /* user_r.h */
    +    // _CrtSetBreakAlloc(744);  /* Break at memalloc {744}, or 'watch' _crtBreakAlloc */
    +    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) );
    +    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
    +    _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR );
    +    _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR );
    +#endif
    +
    +    if (qhullLibraryType==QHULL_NON_REENTRANT) { /* 0 */
    +        if (qh_QHpointer) {
    +            qh_fprintf_stderr(6246, "qh_lib_check: Incorrect qhull library called.  Caller uses a static qhT while library uses a dynamic qhT via qh_QHpointer.  Both caller and library are non-reentrant.\n");
    +            iserror= True;
    +        }
    +    }else if (qhullLibraryType==QHULL_QH_POINTER) { /* 1 */
    +        if (!qh_QHpointer) {
    +            qh_fprintf_stderr(6247, "qh_lib_check: Incorrect qhull library called.  Caller uses a dynamic qhT via qh_QHpointer while library uses a static qhT.  Both caller and library are non-reentrant.\n");
    +            iserror= True;
    +        }
    +    }else if (qhullLibraryType==QHULL_REENTRANT) { /* 2 */
    +        qh_fprintf_stderr(6248, "qh_lib_check: Incorrect qhull library called.  Caller uses reentrant Qhull while library is non-reentrant\n");
    +        iserror= True;
    +    }else{
    +        qh_fprintf_stderr(6262, "qh_lib_check: Expecting qhullLibraryType QHULL_NON_REENTRANT(0), QHULL_QH_POINTER(1), or QHULL_REENTRANT(2).  Got %d\n", qhullLibraryType);
    +        iserror= True;
    +    }
    +    if (qhTsize != sizeof(qhT)) {
    +        qh_fprintf_stderr(6249, "qh_lib_check: Incorrect qhull library called.  Size of qhT for caller is %d, but for library is %d.\n", qhTsize, sizeof(qhT));
    +        iserror= True;
    +    }
    +    if (vertexTsize != sizeof(vertexT)) {
    +        qh_fprintf_stderr(6250, "qh_lib_check: Incorrect qhull library called.  Size of vertexT for caller is %d, but for library is %d.\n", vertexTsize, sizeof(vertexT));
    +        iserror= True;
    +    }
    +    if (ridgeTsize != sizeof(ridgeT)) {
    +        qh_fprintf_stderr(6251, "qh_lib_check: Incorrect qhull library called.  Size of ridgeT for caller is %d, but for library is %d.\n", ridgeTsize, sizeof(ridgeT));
    +        iserror= True;
    +    }
    +    if (facetTsize != sizeof(facetT)) {
    +        qh_fprintf_stderr(6252, "qh_lib_check: Incorrect qhull library called.  Size of facetT for caller is %d, but for library is %d.\n", facetTsize, sizeof(facetT));
    +        iserror= True;
    +    }
    +    if (setTsize && setTsize != sizeof(setT)) {
    +        qh_fprintf_stderr(6253, "qh_lib_check: Incorrect qhull library called.  Size of setT for caller is %d, but for library is %d.\n", setTsize, sizeof(setT));
    +        iserror= True;
    +    }
    +    if (qhmemTsize && qhmemTsize != sizeof(qhmemT)) {
    +        qh_fprintf_stderr(6254, "qh_lib_check: Incorrect qhull library called.  Size of qhmemT for caller is %d, but for library is %d.\n", qhmemTsize, sizeof(qhmemT));
    +        iserror= True;
    +    }
    +    if (iserror) {
    +        if(qh_QHpointer){
    +            qh_fprintf_stderr(6255, "qh_lib_check: Cannot continue.  Library '%s' uses a dynamic qhT via qh_QHpointer (e.g., qhull_p.so)\n", qh_version2);
    +        }else{
    +            qh_fprintf_stderr(6256, "qh_lib_check: Cannot continue.  Library '%s' uses a static qhT (e.g., libqhull.so)\n", qh_version2);
    +        }
    +        qh_exit(qh_ERRqhull);  /* can not use qh_errexit() */
    +    }
    +} /* lib_check */
    +
    +/*---------------------------------
    +
    +  qh_option( option, intVal, realVal )
    +    add an option description to qh.qhull_options
    +
    +  notes:
    +    NOerrors -- qh_option can not call qh_errexit() [qh_initqhull_start2]
    +    will be printed with statistics ('Ts') and errors
    +    strlen(option) < 40
    +*/
    +void qh_option(const char *option, int *i, realT *r) {
    +  char buf[200];
    +  int len, maxlen;
    +
    +  sprintf(buf, "  %s", option);
    +  if (i)
    +    sprintf(buf+strlen(buf), " %d", *i);
    +  if (r)
    +    sprintf(buf+strlen(buf), " %2.2g", *r);
    +  len= (int)strlen(buf);  /* WARN64 */
    +  qh qhull_optionlen += len;
    +  maxlen= sizeof(qh qhull_options) - len -1;
    +  maximize_(maxlen, 0);
    +  if (qh qhull_optionlen >= qh_OPTIONline && maxlen > 0) {
    +    qh qhull_optionlen= len;
    +    strncat(qh qhull_options, "\n", (size_t)(maxlen--));
    +  }
    +  strncat(qh qhull_options, buf, (size_t)maxlen);
    +} /* option */
    +
    +#if qh_QHpointer
    +/*---------------------------------
    +
    +  qh_restore_qhull( oldqh )
    +    restores a previously saved qhull
    +    also restores qh_qhstat and qhmem.tempstack
    +    Sets *oldqh to NULL
    +  notes:
    +    errors if current qhull hasn't been saved or freed
    +    uses qhmem for error reporting
    +
    +  NOTE 1998/5/11:
    +    Freeing memory after qh_save_qhull and qh_restore_qhull
    +    is complicated.  The procedures will be redesigned.
    +
    +  see:
    +    qh_save_qhull(), UsingLibQhull
    +*/
    +void qh_restore_qhull(qhT **oldqh) {
    +
    +  if (*oldqh && strcmp((*oldqh)->qhull, "qhull")) {
    +    qh_fprintf(qhmem.ferr, 6061, "qhull internal error (qh_restore_qhull): %p is not a qhull data structure\n",
    +                  *oldqh);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (qh_qh) {
    +    qh_fprintf(qhmem.ferr, 6062, "qhull internal error (qh_restore_qhull): did not save or free existing qhull\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (!*oldqh || !(*oldqh)->old_qhstat) {
    +    qh_fprintf(qhmem.ferr, 6063, "qhull internal error (qh_restore_qhull): did not previously save qhull %p\n",
    +                  *oldqh);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh_qh= *oldqh;
    +  *oldqh= NULL;
    +  qh_qhstat= qh old_qhstat;
    +  qhmem.tempstack= qh old_tempstack;
    +  qh old_qhstat= 0;
    +  qh old_tempstack= 0;
    +  trace1((qh ferr, 1007, "qh_restore_qhull: restored qhull from %p\n", *oldqh));
    +} /* restore_qhull */
    +
    +/*---------------------------------
    +
    +  qh_save_qhull(  )
    +    saves qhull for a later qh_restore_qhull
    +    also saves qh_qhstat and qhmem.tempstack
    +
    +  returns:
    +    qh_qh=NULL
    +
    +  notes:
    +    need to initialize qhull or call qh_restore_qhull before continuing
    +
    +  NOTE 1998/5/11:
    +    Freeing memory after qh_save_qhull and qh_restore_qhull
    +    is complicated.  The procedures will be redesigned.
    +
    +  see:
    +    qh_restore_qhull()
    +*/
    +qhT *qh_save_qhull(void) {
    +  qhT *oldqh;
    +
    +  trace1((qhmem.ferr, 1045, "qh_save_qhull: save qhull %p\n", qh_qh));
    +  if (!qh_qh) {
    +    qh_fprintf(qhmem.ferr, 6064, "qhull internal error (qh_save_qhull): qhull not initialized\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh old_qhstat= qh_qhstat;
    +  qh_qhstat= NULL;
    +  qh old_tempstack= qhmem.tempstack;
    +  qhmem.tempstack= NULL;
    +  oldqh= qh_qh;
    +  qh_qh= NULL;
    +  return oldqh;
    +} /* save_qhull */
    +
    +#endif
    +
    diff --git a/xs/src/qhull/src/libqhull/index.htm b/xs/src/qhull/src/libqhull/index.htm
    new file mode 100644
    index 0000000000..62b9d99701
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/index.htm
    @@ -0,0 +1,264 @@
    +
    +
    +
    +
    +Qhull functions, macros, and data structures
    +
    +
    +
    +
    +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code
    +To: Qhull files
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser + +


    + + +

    Qhull functions, macros, and data structures

    +
    +

    The following sections provide an overview and index to +Qhull's functions, macros, and data structures. +Each section starts with an introduction. +See also Calling +Qhull from C programs and Calling Qhull from C++ programs.

    + +

    Qhull uses the following conventions:

    +
    + +
      +
    • in code, global variables start with "qh " +
    • in documentation, global variables start with 'qh.' +
    • constants start with an upper case word +
    • important globals include an '_' +
    • functions, macros, and constants start with "qh_"
    • +
    • data types end in "T"
    • +
    • macros with arguments end in "_"
    • +
    • iterators are macros that use local variables
    • +
    • iterators for sets start with "FOREACH"
    • +
    • iterators for lists start with "FORALL"
    • +
    • qhull options are in single quotes (e.g., 'Pdn')
    • +
    • lists are sorted alphabetically
    • +
    • preprocessor directives on left margin for older compilers
    • +
    +
    +

    +When reading the code, please note that the +global data structure, 'qh', is a macro. It +either expands to "qh_qh." or to +"qh_qh->". The later is used for +applications which run concurrent calls to qh_qhull(). +

    +When reading code with an editor, a search for +'"function' +will locate the header of qh_function. A search for '* function' +will locate the tail of qh_function. + +

    A useful starting point is libqhull.h. It defines most +of Qhull data structures and top-level functions. Search for 'PFn' to +determine the corresponding constant in Qhull. Search for 'Fp' to +determine the corresponding qh_PRINT... constant. +Search io.c to learn how the print function is implemented.

    + +

    If your web browser is configured for .c and .h files, the function, macro, and data type links +go to the corresponding source location. To configure your web browser for .c and .h files. +

      +
    • In the Download Preferences or Options panel, add file extensions 'c' and 'h' to mime type 'text/html'. +
    • Opera 12.10 +
        +
      1. In Tools > Preferences > Advanced > Downloads +
      2. Uncheck 'Hide file types opened with Opera' +
      3. Quick find 'html' +
      4. Select 'text/html' > Edit +
      5. Add File extensions 'c,h,' +
      6. Click 'OK' +
      +
    • Internet Explorer -- Mime types are not available from 'Internet Options'. Is there a registry key for these settings? +
    • Firefox -- Mime types are not available from 'Preferences'. Is there an add-on to change the file extensions for a mime type? +
    • Chrome -- Can Chrome be configured? +
    + +

    +Please report documentation and link errors +to qhull-bug@qhull.org. +

    + +

    Copyright © 1997-2015 C.B. Barber

    + +
    + +

    »Qhull files

    +
    + +

    This sections lists the .c and .h files for Qhull. Please +refer to these files for detailed information.

    +
    + +
    +
    Makefile, CMakeLists.txt
    +
    Makefile is preconfigured for gcc. CMakeLists.txt supports multiple +platforms with CMake. +Qhull includes project files for Visual Studio and Qt. +
    + +
     
    +
    libqhull.h
    +
    Include file for the Qhull library (libqhull.so, qhull.dll, libqhullstatic.a). +Data structures are documented under Poly. +Global variables are documented under Global. +Other data structures and variables are documented under +Qhull or Geom.
    + +
     
    +
    Geom, +geom.h, +geom.c, +geom2.c, +random.c, +random.h
    +
    Geometric routines. These routines implement mathematical +functions such as Gaussian elimination and geometric +routines needed for Qhull. Frequently used routines are +in geom.c while infrequent ones are in geom2.c. +
    + +
     
    +
    Global, +global.c, +libqhull.h
    +
    Global routines. Qhull uses a global data structure, qh, +to store globally defined constants, lists, sets, and +variables. +global.c initializes and frees these +structures.
    + +
     
    +
    Io, io.h, +io.c
    +
    Input and output routines. Qhull provides a wide range of +input and output options.
    + +
     
    +
    Mem, +mem.h, +mem.c
    +
    Memory routines. Qhull provides memory allocation and +deallocation. It uses quick-fit allocation.
    + +
     
    +
    Merge, +merge.h, +merge.c
    +
    Merge routines. Qhull handles precision problems by +merged facets or joggled input. These routines merge simplicial facets, +merge non-simplicial facets, merge cycles of facets, and +rename redundant vertices.
    + +
     
    +
    Poly, +poly.h, +poly.c, +poly2.c, +libqhull.h
    +
    Polyhedral routines. Qhull produces a polyhedron as a +list of facets with vertices, neighbors, ridges, and +geometric information. libqhull.h defines the main +data structures. Frequently used routines are in poly.c +while infrequent ones are in poly2.c.
    + +
     
    +
    Qhull, +libqhull.c, +libqhull.h, +qhull_a.h, +unix.c , +qconvex.c , +qdelaun.c , +qhalf.c , +qvoronoi.c
    +
    Top-level routines. The Quickhull algorithm is +implemented by libqhull.c. qhull_a.h +includes all header files.
    + +
     
    +
    Set, +qset.h, +qset.c
    +
    Set routines. Qhull implements its data structures as +sets. A set is an array of pointers that is expanded as +needed. This is a separate package that may be used in +other applications.
    + +
     
    +
    Stat, +stat.h, +stat.c
    +
    Statistical routines. Qhull maintains statistics about +its implementation.
    + +
     
    +
    User, +user.h, +user.c, +user_eg.c, +user_eg2.c, +
    +
    User-defined routines. Qhull allows the user to configure +the code with defined constants and specialized routines. +
    +
    +
    + +
    +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull files
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    + +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/io.c b/xs/src/qhull/src/libqhull/io.c new file mode 100644 index 0000000000..401987ec08 --- /dev/null +++ b/xs/src/qhull/src/libqhull/io.c @@ -0,0 +1,4062 @@ +/*
      ---------------------------------
    +
    +   io.c
    +   Input/Output routines of qhull application
    +
    +   see qh-io.htm and io.h
    +
    +   see user.c for qh_errprint and qh_printfacetlist
    +
    +   unix.c calls qh_readpoints and qh_produce_output
    +
    +   unix.c and user.c are the only callers of io.c functions
    +   This allows the user to avoid loading io.o from qhull.a
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/io.c#5 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*========= -functions in alphabetical order after qh_produce_output()  =====*/
    +
    +/*---------------------------------
    +
    +  qh_produce_output()
    +  qh_produce_output2()
    +    prints out the result of qhull in desired format
    +    qh_produce_output2() does not call qh_prepare_output()
    +    if qh.GETarea
    +      computes and prints area and volume
    +    qh.PRINTout[] is an array of output formats
    +
    +  notes:
    +    prints output in qh.PRINTout order
    +*/
    +void qh_produce_output(void) {
    +    int tempsize= qh_setsize(qhmem.tempstack);
    +
    +    qh_prepare_output();
    +    qh_produce_output2();
    +    if (qh_setsize(qhmem.tempstack) != tempsize) {
    +        qh_fprintf(qh ferr, 6206, "qhull internal error (qh_produce_output): temporary sets not empty(%d)\n",
    +            qh_setsize(qhmem.tempstack));
    +        qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +} /* produce_output */
    +
    +
    +void qh_produce_output2(void) {
    +  int i, tempsize= qh_setsize(qhmem.tempstack), d_1;
    +
    +  if (qh PRINTsummary)
    +    qh_printsummary(qh ferr);
    +  else if (qh PRINTout[0] == qh_PRINTnone)
    +    qh_printsummary(qh fout);
    +  for (i=0; i < qh_PRINTEND; i++)
    +    qh_printfacets(qh fout, qh PRINTout[i], qh facet_list, NULL, !qh_ALL);
    +  qh_allstatistics();
    +  if (qh PRINTprecision && !qh MERGING && (qh JOGGLEmax > REALmax/2 || qh RERUN))
    +    qh_printstats(qh ferr, qhstat precision, NULL);
    +  if (qh VERIFYoutput && (zzval_(Zridge) > 0 || zzval_(Zridgemid) > 0))
    +    qh_printstats(qh ferr, qhstat vridges, NULL);
    +  if (qh PRINTstatistics) {
    +    qh_printstatistics(qh ferr, "");
    +    qh_memstatistics(qh ferr);
    +    d_1= sizeof(setT) + (qh hull_dim - 1) * SETelemsize;
    +    qh_fprintf(qh ferr, 8040, "\
    +    size in bytes: merge %d ridge %d vertex %d facet %d\n\
    +         normal %d ridge vertices %d facet vertices or neighbors %d\n",
    +            (int)sizeof(mergeT), (int)sizeof(ridgeT),
    +            (int)sizeof(vertexT), (int)sizeof(facetT),
    +            qh normal_size, d_1, d_1 + SETelemsize);
    +  }
    +  if (qh_setsize(qhmem.tempstack) != tempsize) {
    +    qh_fprintf(qh ferr, 6065, "qhull internal error (qh_produce_output2): temporary sets not empty(%d)\n",
    +             qh_setsize(qhmem.tempstack));
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +} /* produce_output2 */
    +
    +/*---------------------------------
    +
    +  qh_dfacet( id )
    +    print facet by id, for debugging
    +
    +*/
    +void qh_dfacet(unsigned id) {
    +  facetT *facet;
    +
    +  FORALLfacets {
    +    if (facet->id == id) {
    +      qh_printfacet(qh fout, facet);
    +      break;
    +    }
    +  }
    +} /* dfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_dvertex( id )
    +    print vertex by id, for debugging
    +*/
    +void qh_dvertex(unsigned id) {
    +  vertexT *vertex;
    +
    +  FORALLvertices {
    +    if (vertex->id == id) {
    +      qh_printvertex(qh fout, vertex);
    +      break;
    +    }
    +  }
    +} /* dvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_compare_facetarea( p1, p2 )
    +    used by qsort() to order facets by area
    +*/
    +int qh_compare_facetarea(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +
    +  if (!a->isarea)
    +    return -1;
    +  if (!b->isarea)
    +    return 1;
    +  if (a->f.area > b->f.area)
    +    return 1;
    +  else if (a->f.area == b->f.area)
    +    return 0;
    +  return -1;
    +} /* compare_facetarea */
    +
    +/*---------------------------------
    +
    +  qh_compare_facetmerge( p1, p2 )
    +    used by qsort() to order facets by number of merges
    +*/
    +int qh_compare_facetmerge(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +
    +  return(a->nummerge - b->nummerge);
    +} /* compare_facetvisit */
    +
    +/*---------------------------------
    +
    +  qh_compare_facetvisit( p1, p2 )
    +    used by qsort() to order facets by visit id or id
    +*/
    +int qh_compare_facetvisit(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +  int i,j;
    +
    +  if (!(i= a->visitid))
    +    i= 0 - a->id; /* do not convert to int, sign distinguishes id from visitid */
    +  if (!(j= b->visitid))
    +    j= 0 - b->id;
    +  return(i - j);
    +} /* compare_facetvisit */
    +
    +/*---------------------------------
    +
    +  qh_compare_vertexpoint( p1, p2 )
    +    used by qsort() to order vertices by point id
    +
    +  Not used.  Not available in libqhull_r.h since qh_pointid depends on qh
    +*/
    +int qh_compare_vertexpoint(const void *p1, const void *p2) {
    +  const vertexT *a= *((vertexT *const*)p1), *b= *((vertexT *const*)p2);
    +
    +  return((qh_pointid(a->point) > qh_pointid(b->point)?1:-1));
    +} /* compare_vertexpoint */
    +
    +/*---------------------------------
    +
    +  qh_copyfilename( dest, size, source, length )
    +    copy filename identified by qh_skipfilename()
    +
    +  notes:
    +    see qh_skipfilename() for syntax
    +*/
    +void qh_copyfilename(char *filename, int size, const char* source, int length) {
    +  char c= *source;
    +
    +  if (length > size + 1) {
    +      qh_fprintf(qh ferr, 6040, "qhull error: filename is more than %d characters, %s\n",  size-1, source);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  strncpy(filename, source, length);
    +  filename[length]= '\0';
    +  if (c == '\'' || c == '"') {
    +    char *s= filename + 1;
    +    char *t= filename;
    +    while (*s) {
    +      if (*s == c) {
    +          if (s[-1] == '\\')
    +              t[-1]= c;
    +      }else
    +          *t++= *s;
    +      s++;
    +    }
    +    *t= '\0';
    +  }
    +} /* copyfilename */
    +
    +/*---------------------------------
    +
    +  qh_countfacets( facetlist, facets, printall,
    +          numfacets, numsimplicial, totneighbors, numridges, numcoplanar, numtricoplanars  )
    +    count good facets for printing and set visitid
    +    if allfacets, ignores qh_skipfacet()
    +
    +  notes:
    +    qh_printsummary and qh_countfacets must match counts
    +
    +  returns:
    +    numfacets, numsimplicial, total neighbors, numridges, coplanars
    +    each facet with ->visitid indicating 1-relative position
    +      ->visitid==0 indicates not good
    +
    +  notes
    +    numfacets >= numsimplicial
    +    if qh.NEWfacets,
    +      does not count visible facets (matches qh_printafacet)
    +
    +  design:
    +    for all facets on facetlist and in facets set
    +      unless facet is skipped or visible (i.e., will be deleted)
    +        mark facet->visitid
    +        update counts
    +*/
    +void qh_countfacets(facetT *facetlist, setT *facets, boolT printall,
    +    int *numfacetsp, int *numsimplicialp, int *totneighborsp, int *numridgesp, int *numcoplanarsp, int *numtricoplanarsp) {
    +  facetT *facet, **facetp;
    +  int numfacets= 0, numsimplicial= 0, numridges= 0, totneighbors= 0, numcoplanars= 0, numtricoplanars= 0;
    +
    +  FORALLfacet_(facetlist) {
    +    if ((facet->visible && qh NEWfacets)
    +    || (!printall && qh_skipfacet(facet)))
    +      facet->visitid= 0;
    +    else {
    +      facet->visitid= ++numfacets;
    +      totneighbors += qh_setsize(facet->neighbors);
    +      if (facet->simplicial) {
    +        numsimplicial++;
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else
    +        numridges += qh_setsize(facet->ridges);
    +      if (facet->coplanarset)
    +        numcoplanars += qh_setsize(facet->coplanarset);
    +    }
    +  }
    +
    +  FOREACHfacet_(facets) {
    +    if ((facet->visible && qh NEWfacets)
    +    || (!printall && qh_skipfacet(facet)))
    +      facet->visitid= 0;
    +    else {
    +      facet->visitid= ++numfacets;
    +      totneighbors += qh_setsize(facet->neighbors);
    +      if (facet->simplicial){
    +        numsimplicial++;
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else
    +        numridges += qh_setsize(facet->ridges);
    +      if (facet->coplanarset)
    +        numcoplanars += qh_setsize(facet->coplanarset);
    +    }
    +  }
    +  qh visit_id += numfacets+1;
    +  *numfacetsp= numfacets;
    +  *numsimplicialp= numsimplicial;
    +  *totneighborsp= totneighbors;
    +  *numridgesp= numridges;
    +  *numcoplanarsp= numcoplanars;
    +  *numtricoplanarsp= numtricoplanars;
    +} /* countfacets */
    +
    +/*---------------------------------
    +
    +  qh_detvnorm( vertex, vertexA, centers, offset )
    +    compute separating plane of the Voronoi diagram for a pair of input sites
    +    centers= set of facets (i.e., Voronoi vertices)
    +      facet->visitid= 0 iff vertex-at-infinity (i.e., unbounded)
    +
    +  assumes:
    +    qh_ASvoronoi and qh_vertexneighbors() already set
    +
    +  returns:
    +    norm
    +      a pointer into qh.gm_matrix to qh.hull_dim-1 reals
    +      copy the data before reusing qh.gm_matrix
    +    offset
    +      if 'QVn'
    +        sign adjusted so that qh.GOODvertexp is inside
    +      else
    +        sign adjusted so that vertex is inside
    +
    +    qh.gm_matrix= simplex of points from centers relative to first center
    +
    +  notes:
    +    in io.c so that code for 'v Tv' can be removed by removing io.c
    +    returns pointer into qh.gm_matrix to avoid tracking of temporary memory
    +
    +  design:
    +    determine midpoint of input sites
    +    build points as the set of Voronoi vertices
    +    select a simplex from points (if necessary)
    +      include midpoint if the Voronoi region is unbounded
    +    relocate the first vertex of the simplex to the origin
    +    compute the normalized hyperplane through the simplex
    +    orient the hyperplane toward 'QVn' or 'vertex'
    +    if 'Tv' or 'Ts'
    +      if bounded
    +        test that hyperplane is the perpendicular bisector of the input sites
    +      test that Voronoi vertices not in the simplex are still on the hyperplane
    +    free up temporary memory
    +*/
    +pointT *qh_detvnorm(vertexT *vertex, vertexT *vertexA, setT *centers, realT *offsetp) {
    +  facetT *facet, **facetp;
    +  int  i, k, pointid, pointidA, point_i, point_n;
    +  setT *simplex= NULL;
    +  pointT *point, **pointp, *point0, *midpoint, *normal, *inpoint;
    +  coordT *coord, *gmcoord, *normalp;
    +  setT *points= qh_settemp(qh TEMPsize);
    +  boolT nearzero= False;
    +  boolT unbounded= False;
    +  int numcenters= 0;
    +  int dim= qh hull_dim - 1;
    +  realT dist, offset, angle, zero= 0.0;
    +
    +  midpoint= qh gm_matrix + qh hull_dim * qh hull_dim;  /* last row */
    +  for (k=0; k < dim; k++)
    +    midpoint[k]= (vertex->point[k] + vertexA->point[k])/2;
    +  FOREACHfacet_(centers) {
    +    numcenters++;
    +    if (!facet->visitid)
    +      unbounded= True;
    +    else {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(facet->vertices);
    +      qh_setappend(&points, facet->center);
    +    }
    +  }
    +  if (numcenters > dim) {
    +    simplex= qh_settemp(qh TEMPsize);
    +    qh_setappend(&simplex, vertex->point);
    +    if (unbounded)
    +      qh_setappend(&simplex, midpoint);
    +    qh_maxsimplex(dim, points, NULL, 0, &simplex);
    +    qh_setdelnth(simplex, 0);
    +  }else if (numcenters == dim) {
    +    if (unbounded)
    +      qh_setappend(&points, midpoint);
    +    simplex= points;
    +  }else {
    +    qh_fprintf(qh ferr, 6216, "qhull internal error (qh_detvnorm): too few points(%d) to compute separating plane\n", numcenters);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  i= 0;
    +  gmcoord= qh gm_matrix;
    +  point0= SETfirstt_(simplex, pointT);
    +  FOREACHpoint_(simplex) {
    +    if (qh IStracing >= 4)
    +      qh_printmatrix(qh ferr, "qh_detvnorm: Voronoi vertex or midpoint",
    +                              &point, 1, dim);
    +    if (point != point0) {
    +      qh gm_row[i++]= gmcoord;
    +      coord= point0;
    +      for (k=dim; k--; )
    +        *(gmcoord++)= *point++ - *coord++;
    +    }
    +  }
    +  qh gm_row[i]= gmcoord;  /* does not overlap midpoint, may be used later for qh_areasimplex */
    +  normal= gmcoord;
    +  qh_sethyperplane_gauss(dim, qh gm_row, point0, True,
    +                normal, &offset, &nearzero);
    +  if (qh GOODvertexp == vertexA->point)
    +    inpoint= vertexA->point;
    +  else
    +    inpoint= vertex->point;
    +  zinc_(Zdistio);
    +  dist= qh_distnorm(dim, inpoint, normal, &offset);
    +  if (dist > 0) {
    +    offset= -offset;
    +    normalp= normal;
    +    for (k=dim; k--; ) {
    +      *normalp= -(*normalp);
    +      normalp++;
    +    }
    +  }
    +  if (qh VERIFYoutput || qh PRINTstatistics) {
    +    pointid= qh_pointid(vertex->point);
    +    pointidA= qh_pointid(vertexA->point);
    +    if (!unbounded) {
    +      zinc_(Zdiststat);
    +      dist= qh_distnorm(dim, midpoint, normal, &offset);
    +      if (dist < 0)
    +        dist= -dist;
    +      zzinc_(Zridgemid);
    +      wwmax_(Wridgemidmax, dist);
    +      wwadd_(Wridgemid, dist);
    +      trace4((qh ferr, 4014, "qh_detvnorm: points %d %d midpoint dist %2.2g\n",
    +                 pointid, pointidA, dist));
    +      for (k=0; k < dim; k++)
    +        midpoint[k]= vertexA->point[k] - vertex->point[k];  /* overwrites midpoint! */
    +      qh_normalize(midpoint, dim, False);
    +      angle= qh_distnorm(dim, midpoint, normal, &zero); /* qh_detangle uses dim+1 */
    +      if (angle < 0.0)
    +        angle= angle + 1.0;
    +      else
    +        angle= angle - 1.0;
    +      if (angle < 0.0)
    +        angle -= angle;
    +      trace4((qh ferr, 4015, "qh_detvnorm: points %d %d angle %2.2g nearzero %d\n",
    +                 pointid, pointidA, angle, nearzero));
    +      if (nearzero) {
    +        zzinc_(Zridge0);
    +        wwmax_(Wridge0max, angle);
    +        wwadd_(Wridge0, angle);
    +      }else {
    +        zzinc_(Zridgeok)
    +        wwmax_(Wridgeokmax, angle);
    +        wwadd_(Wridgeok, angle);
    +      }
    +    }
    +    if (simplex != points) {
    +      FOREACHpoint_i_(points) {
    +        if (!qh_setin(simplex, point)) {
    +          facet= SETelemt_(centers, point_i, facetT);
    +          zinc_(Zdiststat);
    +          dist= qh_distnorm(dim, point, normal, &offset);
    +          if (dist < 0)
    +            dist= -dist;
    +          zzinc_(Zridge);
    +          wwmax_(Wridgemax, dist);
    +          wwadd_(Wridge, dist);
    +          trace4((qh ferr, 4016, "qh_detvnorm: points %d %d Voronoi vertex %d dist %2.2g\n",
    +                             pointid, pointidA, facet->visitid, dist));
    +        }
    +      }
    +    }
    +  }
    +  *offsetp= offset;
    +  if (simplex != points)
    +    qh_settempfree(&simplex);
    +  qh_settempfree(&points);
    +  return normal;
    +} /* detvnorm */
    +
    +/*---------------------------------
    +
    +  qh_detvridge( vertexA )
    +    determine Voronoi ridge from 'seen' neighbors of vertexA
    +    include one vertex-at-infinite if an !neighbor->visitid
    +
    +  returns:
    +    temporary set of centers (facets, i.e., Voronoi vertices)
    +    sorted by center id
    +*/
    +setT *qh_detvridge(vertexT *vertex) {
    +  setT *centers= qh_settemp(qh TEMPsize);
    +  setT *tricenters= qh_settemp(qh TEMPsize);
    +  facetT *neighbor, **neighborp;
    +  boolT firstinf= True;
    +
    +  FOREACHneighbor_(vertex) {
    +    if (neighbor->seen) {
    +      if (neighbor->visitid) {
    +        if (!neighbor->tricoplanar || qh_setunique(&tricenters, neighbor->center))
    +          qh_setappend(¢ers, neighbor);
    +      }else if (firstinf) {
    +        firstinf= False;
    +        qh_setappend(¢ers, neighbor);
    +      }
    +    }
    +  }
    +  qsort(SETaddr_(centers, facetT), (size_t)qh_setsize(centers),
    +             sizeof(facetT *), qh_compare_facetvisit);
    +  qh_settempfree(&tricenters);
    +  return centers;
    +} /* detvridge */
    +
    +/*---------------------------------
    +
    +  qh_detvridge3( atvertex, vertex )
    +    determine 3-d Voronoi ridge from 'seen' neighbors of atvertex and vertex
    +    include one vertex-at-infinite for !neighbor->visitid
    +    assumes all facet->seen2= True
    +
    +  returns:
    +    temporary set of centers (facets, i.e., Voronoi vertices)
    +    listed in adjacency order (!oriented)
    +    all facet->seen2= True
    +
    +  design:
    +    mark all neighbors of atvertex
    +    for each adjacent neighbor of both atvertex and vertex
    +      if neighbor selected
    +        add neighbor to set of Voronoi vertices
    +*/
    +setT *qh_detvridge3(vertexT *atvertex, vertexT *vertex) {
    +  setT *centers= qh_settemp(qh TEMPsize);
    +  setT *tricenters= qh_settemp(qh TEMPsize);
    +  facetT *neighbor, **neighborp, *facet= NULL;
    +  boolT firstinf= True;
    +
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen2= False;
    +  FOREACHneighbor_(vertex) {
    +    if (!neighbor->seen2) {
    +      facet= neighbor;
    +      break;
    +    }
    +  }
    +  while (facet) {
    +    facet->seen2= True;
    +    if (neighbor->seen) {
    +      if (facet->visitid) {
    +        if (!facet->tricoplanar || qh_setunique(&tricenters, facet->center))
    +          qh_setappend(¢ers, facet);
    +      }else if (firstinf) {
    +        firstinf= False;
    +        qh_setappend(¢ers, facet);
    +      }
    +    }
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->seen2) {
    +        if (qh_setin(vertex->neighbors, neighbor))
    +          break;
    +        else
    +          neighbor->seen2= True;
    +      }
    +    }
    +    facet= neighbor;
    +  }
    +  if (qh CHECKfrequently) {
    +    FOREACHneighbor_(vertex) {
    +      if (!neighbor->seen2) {
    +          qh_fprintf(qh ferr, 6217, "qhull internal error (qh_detvridge3): neighbors of vertex p%d are not connected at facet %d\n",
    +                 qh_pointid(vertex->point), neighbor->id);
    +        qh_errexit(qh_ERRqhull, neighbor, NULL);
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen2= True;
    +  qh_settempfree(&tricenters);
    +  return centers;
    +} /* detvridge3 */
    +
    +/*---------------------------------
    +
    +  qh_eachvoronoi( fp, printvridge, vertex, visitall, innerouter, inorder )
    +    if visitall,
    +      visit all Voronoi ridges for vertex (i.e., an input site)
    +    else
    +      visit all unvisited Voronoi ridges for vertex
    +      all vertex->seen= False if unvisited
    +    assumes
    +      all facet->seen= False
    +      all facet->seen2= True (for qh_detvridge3)
    +      all facet->visitid == 0 if vertex_at_infinity
    +                         == index of Voronoi vertex
    +                         >= qh.num_facets if ignored
    +    innerouter:
    +      qh_RIDGEall--  both inner (bounded) and outer(unbounded) ridges
    +      qh_RIDGEinner- only inner
    +      qh_RIDGEouter- only outer
    +
    +    if inorder
    +      orders vertices for 3-d Voronoi diagrams
    +
    +  returns:
    +    number of visited ridges (does not include previously visited ridges)
    +
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers)
    +        fp== any pointer (assumes FILE*)
    +        vertex,vertexA= pair of input sites that define a Voronoi ridge
    +        centers= set of facets (i.e., Voronoi vertices)
    +                 ->visitid == index or 0 if vertex_at_infinity
    +                 ordered for 3-d Voronoi diagram
    +  notes:
    +    uses qh.vertex_visit
    +
    +  see:
    +    qh_eachvoronoi_all()
    +
    +  design:
    +    mark selected neighbors of atvertex
    +    for each selected neighbor (either Voronoi vertex or vertex-at-infinity)
    +      for each unvisited vertex
    +        if atvertex and vertex share more than d-1 neighbors
    +          bump totalcount
    +          if printvridge defined
    +            build the set of shared neighbors (i.e., Voronoi vertices)
    +            call printvridge
    +*/
    +int qh_eachvoronoi(FILE *fp, printvridgeT printvridge, vertexT *atvertex, boolT visitall, qh_RIDGE innerouter, boolT inorder) {
    +  boolT unbounded;
    +  int count;
    +  facetT *neighbor, **neighborp, *neighborA, **neighborAp;
    +  setT *centers;
    +  setT *tricenters= qh_settemp(qh TEMPsize);
    +
    +  vertexT *vertex, **vertexp;
    +  boolT firstinf;
    +  unsigned int numfacets= (unsigned int)qh num_facets;
    +  int totridges= 0;
    +
    +  qh vertex_visit++;
    +  atvertex->seen= True;
    +  if (visitall) {
    +    FORALLvertices
    +      vertex->seen= False;
    +  }
    +  FOREACHneighbor_(atvertex) {
    +    if (neighbor->visitid < numfacets)
    +      neighbor->seen= True;
    +  }
    +  FOREACHneighbor_(atvertex) {
    +    if (neighbor->seen) {
    +      FOREACHvertex_(neighbor->vertices) {
    +        if (vertex->visitid != qh vertex_visit && !vertex->seen) {
    +          vertex->visitid= qh vertex_visit;
    +          count= 0;
    +          firstinf= True;
    +          qh_settruncate(tricenters, 0);
    +          FOREACHneighborA_(vertex) {
    +            if (neighborA->seen) {
    +              if (neighborA->visitid) {
    +                if (!neighborA->tricoplanar || qh_setunique(&tricenters, neighborA->center))
    +                  count++;
    +              }else if (firstinf) {
    +                count++;
    +                firstinf= False;
    +              }
    +            }
    +          }
    +          if (count >= qh hull_dim - 1) {  /* e.g., 3 for 3-d Voronoi */
    +            if (firstinf) {
    +              if (innerouter == qh_RIDGEouter)
    +                continue;
    +              unbounded= False;
    +            }else {
    +              if (innerouter == qh_RIDGEinner)
    +                continue;
    +              unbounded= True;
    +            }
    +            totridges++;
    +            trace4((qh ferr, 4017, "qh_eachvoronoi: Voronoi ridge of %d vertices between sites %d and %d\n",
    +                  count, qh_pointid(atvertex->point), qh_pointid(vertex->point)));
    +            if (printvridge && fp) {
    +              if (inorder && qh hull_dim == 3+1) /* 3-d Voronoi diagram */
    +                centers= qh_detvridge3(atvertex, vertex);
    +              else
    +                centers= qh_detvridge(vertex);
    +              (*printvridge)(fp, atvertex, vertex, centers, unbounded);
    +              qh_settempfree(¢ers);
    +            }
    +          }
    +        }
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen= False;
    +  qh_settempfree(&tricenters);
    +  return totridges;
    +} /* eachvoronoi */
    +
    +
    +/*---------------------------------
    +
    +  qh_eachvoronoi_all( fp, printvridge, isUpper, innerouter, inorder )
    +    visit all Voronoi ridges
    +
    +    innerouter:
    +      see qh_eachvoronoi()
    +
    +    if inorder
    +      orders vertices for 3-d Voronoi diagrams
    +
    +  returns
    +    total number of ridges
    +
    +    if isUpper == facet->upperdelaunay  (i.e., a Vornoi vertex)
    +      facet->visitid= Voronoi vertex index(same as 'o' format)
    +    else
    +      facet->visitid= 0
    +
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers)
    +      [see qh_eachvoronoi]
    +
    +  notes:
    +    Not used for qhull.exe
    +    same effect as qh_printvdiagram but ridges not sorted by point id
    +*/
    +int qh_eachvoronoi_all(FILE *fp, printvridgeT printvridge, boolT isUpper, qh_RIDGE innerouter, boolT inorder) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  int numcenters= 1;  /* vertex 0 is vertex-at-infinity */
    +  int totridges= 0;
    +
    +  qh_clearcenters(qh_ASvoronoi);
    +  qh_vertexneighbors();
    +  maximize_(qh visit_id, (unsigned) qh num_facets);
    +  FORALLfacets {
    +    facet->visitid= 0;
    +    facet->seen= False;
    +    facet->seen2= True;
    +  }
    +  FORALLfacets {
    +    if (facet->upperdelaunay == isUpper)
    +      facet->visitid= numcenters++;
    +  }
    +  FORALLvertices
    +    vertex->seen= False;
    +  FORALLvertices {
    +    if (qh GOODvertex > 0 && qh_pointid(vertex->point)+1 != qh GOODvertex)
    +      continue;
    +    totridges += qh_eachvoronoi(fp, printvridge, vertex,
    +                   !qh_ALL, innerouter, inorder);
    +  }
    +  return totridges;
    +} /* eachvoronoi_all */
    +
    +/*---------------------------------
    +
    +  qh_facet2point( facet, point0, point1, mindist )
    +    return two projected temporary vertices for a 2-d facet
    +    may be non-simplicial
    +
    +  returns:
    +    point0 and point1 oriented and projected to the facet
    +    returns mindist (maximum distance below plane)
    +*/
    +void qh_facet2point(facetT *facet, pointT **point0, pointT **point1, realT *mindist) {
    +  vertexT *vertex0, *vertex1;
    +  realT dist;
    +
    +  if (facet->toporient ^ qh_ORIENTclock) {
    +    vertex0= SETfirstt_(facet->vertices, vertexT);
    +    vertex1= SETsecondt_(facet->vertices, vertexT);
    +  }else {
    +    vertex1= SETfirstt_(facet->vertices, vertexT);
    +    vertex0= SETsecondt_(facet->vertices, vertexT);
    +  }
    +  zadd_(Zdistio, 2);
    +  qh_distplane(vertex0->point, facet, &dist);
    +  *mindist= dist;
    +  *point0= qh_projectpoint(vertex0->point, facet, dist);
    +  qh_distplane(vertex1->point, facet, &dist);
    +  minimize_(*mindist, dist);
    +  *point1= qh_projectpoint(vertex1->point, facet, dist);
    +} /* facet2point */
    +
    +
    +/*---------------------------------
    +
    +  qh_facetvertices( facetlist, facets, allfacets )
    +    returns temporary set of vertices in a set and/or list of facets
    +    if allfacets, ignores qh_skipfacet()
    +
    +  returns:
    +    vertices with qh.vertex_visit
    +
    +  notes:
    +    optimized for allfacets of facet_list
    +
    +  design:
    +    if allfacets of facet_list
    +      create vertex set from vertex_list
    +    else
    +      for each selected facet in facets or facetlist
    +        append unvisited vertices to vertex set
    +*/
    +setT *qh_facetvertices(facetT *facetlist, setT *facets, boolT allfacets) {
    +  setT *vertices;
    +  facetT *facet, **facetp;
    +  vertexT *vertex, **vertexp;
    +
    +  qh vertex_visit++;
    +  if (facetlist == qh facet_list && allfacets && !facets) {
    +    vertices= qh_settemp(qh num_vertices);
    +    FORALLvertices {
    +      vertex->visitid= qh vertex_visit;
    +      qh_setappend(&vertices, vertex);
    +    }
    +  }else {
    +    vertices= qh_settemp(qh TEMPsize);
    +    FORALLfacet_(facetlist) {
    +      if (!allfacets && qh_skipfacet(facet))
    +        continue;
    +      FOREACHvertex_(facet->vertices) {
    +        if (vertex->visitid != qh vertex_visit) {
    +          vertex->visitid= qh vertex_visit;
    +          qh_setappend(&vertices, vertex);
    +        }
    +      }
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (!allfacets && qh_skipfacet(facet))
    +      continue;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh vertex_visit) {
    +        vertex->visitid= qh vertex_visit;
    +        qh_setappend(&vertices, vertex);
    +      }
    +    }
    +  }
    +  return vertices;
    +} /* facetvertices */
    +
    +/*---------------------------------
    +
    +  qh_geomplanes( facet, outerplane, innerplane )
    +    return outer and inner planes for Geomview
    +    qh.PRINTradius is size of vertices and points (includes qh.JOGGLEmax)
    +
    +  notes:
    +    assume precise calculations in io.c with roundoff covered by qh_GEOMepsilon
    +*/
    +void qh_geomplanes(facetT *facet, realT *outerplane, realT *innerplane) {
    +  realT radius;
    +
    +  if (qh MERGING || qh JOGGLEmax < REALmax/2) {
    +    qh_outerinner(facet, outerplane, innerplane);
    +    radius= qh PRINTradius;
    +    if (qh JOGGLEmax < REALmax/2)
    +      radius -= qh JOGGLEmax * sqrt((realT)qh hull_dim);  /* already accounted for in qh_outerinner() */
    +    *outerplane += radius;
    +    *innerplane -= radius;
    +    if (qh PRINTcoplanar || qh PRINTspheres) {
    +      *outerplane += qh MAXabs_coord * qh_GEOMepsilon;
    +      *innerplane -= qh MAXabs_coord * qh_GEOMepsilon;
    +    }
    +  }else
    +    *innerplane= *outerplane= 0;
    +} /* geomplanes */
    +
    +
    +/*---------------------------------
    +
    +  qh_markkeep( facetlist )
    +    mark good facets that meet qh.KEEParea, qh.KEEPmerge, and qh.KEEPminArea
    +    ignores visible facets (!part of convex hull)
    +
    +  returns:
    +    may clear facet->good
    +    recomputes qh.num_good
    +
    +  design:
    +    get set of good facets
    +    if qh.KEEParea
    +      sort facets by area
    +      clear facet->good for all but n largest facets
    +    if qh.KEEPmerge
    +      sort facets by merge count
    +      clear facet->good for all but n most merged facets
    +    if qh.KEEPminarea
    +      clear facet->good if area too small
    +    update qh.num_good
    +*/
    +void qh_markkeep(facetT *facetlist) {
    +  facetT *facet, **facetp;
    +  setT *facets= qh_settemp(qh num_facets);
    +  int size, count;
    +
    +  trace2((qh ferr, 2006, "qh_markkeep: only keep %d largest and/or %d most merged facets and/or min area %.2g\n",
    +          qh KEEParea, qh KEEPmerge, qh KEEPminArea));
    +  FORALLfacet_(facetlist) {
    +    if (!facet->visible && facet->good)
    +      qh_setappend(&facets, facet);
    +  }
    +  size= qh_setsize(facets);
    +  if (qh KEEParea) {
    +    qsort(SETaddr_(facets, facetT), (size_t)size,
    +             sizeof(facetT *), qh_compare_facetarea);
    +    if ((count= size - qh KEEParea) > 0) {
    +      FOREACHfacet_(facets) {
    +        facet->good= False;
    +        if (--count == 0)
    +          break;
    +      }
    +    }
    +  }
    +  if (qh KEEPmerge) {
    +    qsort(SETaddr_(facets, facetT), (size_t)size,
    +             sizeof(facetT *), qh_compare_facetmerge);
    +    if ((count= size - qh KEEPmerge) > 0) {
    +      FOREACHfacet_(facets) {
    +        facet->good= False;
    +        if (--count == 0)
    +          break;
    +      }
    +    }
    +  }
    +  if (qh KEEPminArea < REALmax/2) {
    +    FOREACHfacet_(facets) {
    +      if (!facet->isarea || facet->f.area < qh KEEPminArea)
    +        facet->good= False;
    +    }
    +  }
    +  qh_settempfree(&facets);
    +  count= 0;
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      count++;
    +  }
    +  qh num_good= count;
    +} /* markkeep */
    +
    +
    +/*---------------------------------
    +
    +  qh_markvoronoi( facetlist, facets, printall, isLower, numcenters )
    +    mark voronoi vertices for printing by site pairs
    +
    +  returns:
    +    temporary set of vertices indexed by pointid
    +    isLower set if printing lower hull (i.e., at least one facet is lower hull)
    +    numcenters= total number of Voronoi vertices
    +    bumps qh.printoutnum for vertex-at-infinity
    +    clears all facet->seen and sets facet->seen2
    +
    +    if selected
    +      facet->visitid= Voronoi vertex id
    +    else if upper hull (or 'Qu' and lower hull)
    +      facet->visitid= 0
    +    else
    +      facet->visitid >= qh num_facets
    +
    +  notes:
    +    ignores qh.ATinfinity, if defined
    +*/
    +setT *qh_markvoronoi(facetT *facetlist, setT *facets, boolT printall, boolT *isLowerp, int *numcentersp) {
    +  int numcenters=0;
    +  facetT *facet, **facetp;
    +  setT *vertices;
    +  boolT isLower= False;
    +
    +  qh printoutnum++;
    +  qh_clearcenters(qh_ASvoronoi);  /* in case, qh_printvdiagram2 called by user */
    +  qh_vertexneighbors();
    +  vertices= qh_pointvertex();
    +  if (qh ATinfinity)
    +    SETelem_(vertices, qh num_points-1)= NULL;
    +  qh visit_id++;
    +  maximize_(qh visit_id, (unsigned) qh num_facets);
    +  FORALLfacet_(facetlist) {
    +    if (printall || !qh_skipfacet(facet)) {
    +      if (!facet->upperdelaunay) {
    +        isLower= True;
    +        break;
    +      }
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (printall || !qh_skipfacet(facet)) {
    +      if (!facet->upperdelaunay) {
    +        isLower= True;
    +        break;
    +      }
    +    }
    +  }
    +  FORALLfacets {
    +    if (facet->normal && (facet->upperdelaunay == isLower))
    +      facet->visitid= 0;  /* facetlist or facets may overwrite */
    +    else
    +      facet->visitid= qh visit_id;
    +    facet->seen= False;
    +    facet->seen2= True;
    +  }
    +  numcenters++;  /* qh_INFINITE */
    +  FORALLfacet_(facetlist) {
    +    if (printall || !qh_skipfacet(facet))
    +      facet->visitid= numcenters++;
    +  }
    +  FOREACHfacet_(facets) {
    +    if (printall || !qh_skipfacet(facet))
    +      facet->visitid= numcenters++;
    +  }
    +  *isLowerp= isLower;
    +  *numcentersp= numcenters;
    +  trace2((qh ferr, 2007, "qh_markvoronoi: isLower %d numcenters %d\n", isLower, numcenters));
    +  return vertices;
    +} /* markvoronoi */
    +
    +/*---------------------------------
    +
    +  qh_order_vertexneighbors( vertex )
    +    order facet neighbors of a 2-d or 3-d vertex by adjacency
    +
    +  notes:
    +    does not orient the neighbors
    +
    +  design:
    +    initialize a new neighbor set with the first facet in vertex->neighbors
    +    while vertex->neighbors non-empty
    +      select next neighbor in the previous facet's neighbor set
    +    set vertex->neighbors to the new neighbor set
    +*/
    +void qh_order_vertexneighbors(vertexT *vertex) {
    +  setT *newset;
    +  facetT *facet, *neighbor, **neighborp;
    +
    +  trace4((qh ferr, 4018, "qh_order_vertexneighbors: order neighbors of v%d for 3-d\n", vertex->id));
    +  newset= qh_settemp(qh_setsize(vertex->neighbors));
    +  facet= (facetT*)qh_setdellast(vertex->neighbors);
    +  qh_setappend(&newset, facet);
    +  while (qh_setsize(vertex->neighbors)) {
    +    FOREACHneighbor_(vertex) {
    +      if (qh_setin(facet->neighbors, neighbor)) {
    +        qh_setdel(vertex->neighbors, neighbor);
    +        qh_setappend(&newset, neighbor);
    +        facet= neighbor;
    +        break;
    +      }
    +    }
    +    if (!neighbor) {
    +      qh_fprintf(qh ferr, 6066, "qhull internal error (qh_order_vertexneighbors): no neighbor of v%d for f%d\n",
    +        vertex->id, facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +  }
    +  qh_setfree(&vertex->neighbors);
    +  qh_settemppop();
    +  vertex->neighbors= newset;
    +} /* order_vertexneighbors */
    +
    +/*---------------------------------
    +
    +  qh_prepare_output( )
    +    prepare for qh_produce_output2() according to
    +      qh.KEEPminArea, KEEParea, KEEPmerge, GOODvertex, GOODthreshold, GOODpoint, ONLYgood, SPLITthresholds
    +    does not reset facet->good
    +
    +  notes
    +    except for PRINTstatistics, no-op if previously called with same options
    +*/
    +void qh_prepare_output(void) {
    +  if (qh VORONOI) {
    +    qh_clearcenters(qh_ASvoronoi);  /* must be before qh_triangulate */
    +    qh_vertexneighbors();
    +  }
    +  if (qh TRIangulate && !qh hasTriangulation) {
    +    qh_triangulate();
    +    if (qh VERIFYoutput && !qh CHECKfrequently)
    +      qh_checkpolygon(qh facet_list);
    +  }
    +  qh_findgood_all(qh facet_list);
    +  if (qh GETarea)
    +    qh_getarea(qh facet_list);
    +  if (qh KEEParea || qh KEEPmerge || qh KEEPminArea < REALmax/2)
    +    qh_markkeep(qh facet_list);
    +  if (qh PRINTstatistics)
    +    qh_collectstatistics();
    +}
    +
    +/*---------------------------------
    +
    +  qh_printafacet( fp, format, facet, printall )
    +    print facet to fp in given output format (see qh.PRINTout)
    +
    +  returns:
    +    nop if !printall and qh_skipfacet()
    +    nop if visible facet and NEWfacets and format != PRINTfacets
    +    must match qh_countfacets
    +
    +  notes
    +    preserves qh.visit_id
    +    facet->normal may be null if PREmerge/MERGEexact and STOPcone before merge
    +
    +  see
    +    qh_printbegin() and qh_printend()
    +
    +  design:
    +    test for printing facet
    +    call appropriate routine for format
    +    or output results directly
    +*/
    +void qh_printafacet(FILE *fp, qh_PRINT format, facetT *facet, boolT printall) {
    +  realT color[4], offset, dist, outerplane, innerplane;
    +  boolT zerodiv;
    +  coordT *point, *normp, *coordp, **pointp, *feasiblep;
    +  int k;
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +
    +  if (!printall && qh_skipfacet(facet))
    +    return;
    +  if (facet->visible && qh NEWfacets && format != qh_PRINTfacets)
    +    return;
    +  qh printoutnum++;
    +  switch (format) {
    +  case qh_PRINTarea:
    +    if (facet->isarea) {
    +      qh_fprintf(fp, 9009, qh_REAL_1, facet->f.area);
    +      qh_fprintf(fp, 9010, "\n");
    +    }else
    +      qh_fprintf(fp, 9011, "0\n");
    +    break;
    +  case qh_PRINTcoplanars:
    +    qh_fprintf(fp, 9012, "%d", qh_setsize(facet->coplanarset));
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_fprintf(fp, 9013, " %d", qh_pointid(point));
    +    qh_fprintf(fp, 9014, "\n");
    +    break;
    +  case qh_PRINTcentrums:
    +    qh_printcenter(fp, format, NULL, facet);
    +    break;
    +  case qh_PRINTfacets:
    +    qh_printfacet(fp, facet);
    +    break;
    +  case qh_PRINTfacets_xridge:
    +    qh_printfacetheader(fp, facet);
    +    break;
    +  case qh_PRINTgeom:  /* either 2 , 3, or 4-d by qh_printbegin */
    +    if (!facet->normal)
    +      break;
    +    for (k=qh hull_dim; k--; ) {
    +      color[k]= (facet->normal[k]+1.0)/2.0;
    +      maximize_(color[k], -1.0);
    +      minimize_(color[k], +1.0);
    +    }
    +    qh_projectdim3(color, color);
    +    if (qh PRINTdim != qh hull_dim)
    +      qh_normalize2(color, 3, True, NULL, NULL);
    +    if (qh hull_dim <= 2)
    +      qh_printfacet2geom(fp, facet, color);
    +    else if (qh hull_dim == 3) {
    +      if (facet->simplicial)
    +        qh_printfacet3geom_simplicial(fp, facet, color);
    +      else
    +        qh_printfacet3geom_nonsimplicial(fp, facet, color);
    +    }else {
    +      if (facet->simplicial)
    +        qh_printfacet4geom_simplicial(fp, facet, color);
    +      else
    +        qh_printfacet4geom_nonsimplicial(fp, facet, color);
    +    }
    +    break;
    +  case qh_PRINTids:
    +    qh_fprintf(fp, 9015, "%d\n", facet->id);
    +    break;
    +  case qh_PRINTincidences:
    +  case qh_PRINToff:
    +  case qh_PRINTtriangles:
    +    if (qh hull_dim == 3 && format != qh_PRINTtriangles)
    +      qh_printfacet3vertex(fp, facet, format);
    +    else if (facet->simplicial || qh hull_dim == 2 || format == qh_PRINToff)
    +      qh_printfacetNvertex_simplicial(fp, facet, format);
    +    else
    +      qh_printfacetNvertex_nonsimplicial(fp, facet, qh printoutvar++, format);
    +    break;
    +  case qh_PRINTinner:
    +    qh_outerinner(facet, NULL, &innerplane);
    +    offset= facet->offset - innerplane;
    +    goto LABELprintnorm;
    +    break; /* prevent warning */
    +  case qh_PRINTmerges:
    +    qh_fprintf(fp, 9016, "%d\n", facet->nummerge);
    +    break;
    +  case qh_PRINTnormals:
    +    offset= facet->offset;
    +    goto LABELprintnorm;
    +    break; /* prevent warning */
    +  case qh_PRINTouter:
    +    qh_outerinner(facet, &outerplane, NULL);
    +    offset= facet->offset - outerplane;
    +  LABELprintnorm:
    +    if (!facet->normal) {
    +      qh_fprintf(fp, 9017, "no normal for facet f%d\n", facet->id);
    +      break;
    +    }
    +    if (qh CDDoutput) {
    +      qh_fprintf(fp, 9018, qh_REAL_1, -offset);
    +      for (k=0; k < qh hull_dim; k++)
    +        qh_fprintf(fp, 9019, qh_REAL_1, -facet->normal[k]);
    +    }else {
    +      for (k=0; k < qh hull_dim; k++)
    +        qh_fprintf(fp, 9020, qh_REAL_1, facet->normal[k]);
    +      qh_fprintf(fp, 9021, qh_REAL_1, offset);
    +    }
    +    qh_fprintf(fp, 9022, "\n");
    +    break;
    +  case qh_PRINTmathematica:  /* either 2 or 3-d by qh_printbegin */
    +  case qh_PRINTmaple:
    +    if (qh hull_dim == 2)
    +      qh_printfacet2math(fp, facet, format, qh printoutvar++);
    +    else
    +      qh_printfacet3math(fp, facet, format, qh printoutvar++);
    +    break;
    +  case qh_PRINTneighbors:
    +    qh_fprintf(fp, 9023, "%d", qh_setsize(facet->neighbors));
    +    FOREACHneighbor_(facet)
    +      qh_fprintf(fp, 9024, " %d",
    +               neighbor->visitid ? neighbor->visitid - 1: 0 - neighbor->id);
    +    qh_fprintf(fp, 9025, "\n");
    +    break;
    +  case qh_PRINTpointintersect:
    +    if (!qh feasible_point) {
    +      qh_fprintf(qh ferr, 6067, "qhull input error (qh_printafacet): option 'Fp' needs qh feasible_point\n");
    +      qh_errexit( qh_ERRinput, NULL, NULL);
    +    }
    +    if (facet->offset > 0)
    +      goto LABELprintinfinite;
    +    point= coordp= (coordT*)qh_memalloc(qh normal_size);
    +    normp= facet->normal;
    +    feasiblep= qh feasible_point;
    +    if (facet->offset < -qh MINdenom) {
    +      for (k=qh hull_dim; k--; )
    +        *(coordp++)= (*(normp++) / - facet->offset) + *(feasiblep++);
    +    }else {
    +      for (k=qh hull_dim; k--; ) {
    +        *(coordp++)= qh_divzero(*(normp++), facet->offset, qh MINdenom_1,
    +                                 &zerodiv) + *(feasiblep++);
    +        if (zerodiv) {
    +          qh_memfree(point, qh normal_size);
    +          goto LABELprintinfinite;
    +        }
    +      }
    +    }
    +    qh_printpoint(fp, NULL, point);
    +    qh_memfree(point, qh normal_size);
    +    break;
    +  LABELprintinfinite:
    +    for (k=qh hull_dim; k--; )
    +      qh_fprintf(fp, 9026, qh_REAL_1, qh_INFINITE);
    +    qh_fprintf(fp, 9027, "\n");
    +    break;
    +  case qh_PRINTpointnearest:
    +    FOREACHpoint_(facet->coplanarset) {
    +      int id, id2;
    +      vertex= qh_nearvertex(facet, point, &dist);
    +      id= qh_pointid(vertex->point);
    +      id2= qh_pointid(point);
    +      qh_fprintf(fp, 9028, "%d %d %d " qh_REAL_1 "\n", id, id2, facet->id, dist);
    +    }
    +    break;
    +  case qh_PRINTpoints:  /* VORONOI only by qh_printbegin */
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9029, "1 ");
    +    qh_printcenter(fp, format, NULL, facet);
    +    break;
    +  case qh_PRINTvertices:
    +    qh_fprintf(fp, 9030, "%d", qh_setsize(facet->vertices));
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(fp, 9031, " %d", qh_pointid(vertex->point));
    +    qh_fprintf(fp, 9032, "\n");
    +    break;
    +  default:
    +    break;
    +  }
    +} /* printafacet */
    +
    +/*---------------------------------
    +
    +  qh_printbegin(  )
    +    prints header for all output formats
    +
    +  returns:
    +    checks for valid format
    +
    +  notes:
    +    uses qh.visit_id for 3/4off
    +    changes qh.interior_point if printing centrums
    +    qh_countfacets clears facet->visitid for non-good facets
    +
    +  see
    +    qh_printend() and qh_printafacet()
    +
    +  design:
    +    count facets and related statistics
    +    print header for format
    +*/
    +void qh_printbegin(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numcoplanars, numtricoplanars;
    +  int i, num;
    +  facetT *facet, **facetp;
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +  pointT *point, **pointp, *pointtemp;
    +
    +  qh printoutnum= 0;
    +  qh_countfacets(facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);
    +  switch (format) {
    +  case qh_PRINTnone:
    +    break;
    +  case qh_PRINTarea:
    +    qh_fprintf(fp, 9033, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTcoplanars:
    +    qh_fprintf(fp, 9034, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTcentrums:
    +    if (qh CENTERtype == qh_ASnone)
    +      qh_clearcenters(qh_AScentrum);
    +    qh_fprintf(fp, 9035, "%d\n%d\n", qh hull_dim, numfacets);
    +    break;
    +  case qh_PRINTfacets:
    +  case qh_PRINTfacets_xridge:
    +    if (facetlist)
    +      qh_printvertexlist(fp, "Vertices and facets:\n", facetlist, facets, printall);
    +    break;
    +  case qh_PRINTgeom:
    +    if (qh hull_dim > 4)  /* qh_initqhull_globals also checks */
    +      goto LABELnoformat;
    +    if (qh VORONOI && qh hull_dim > 3)  /* PRINTdim == DROPdim == hull_dim-1 */
    +      goto LABELnoformat;
    +    if (qh hull_dim == 2 && (qh PRINTridges || qh DOintersections))
    +      qh_fprintf(qh ferr, 7049, "qhull warning: output for ridges and intersections not implemented in 2-d\n");
    +    if (qh hull_dim == 4 && (qh PRINTinner || qh PRINTouter ||
    +                             (qh PRINTdim == 4 && qh PRINTcentrums)))
    +      qh_fprintf(qh ferr, 7050, "qhull warning: output for outer/inner planes and centrums not implemented in 4-d\n");
    +    if (qh PRINTdim == 4 && (qh PRINTspheres))
    +      qh_fprintf(qh ferr, 7051, "qhull warning: output for vertices not implemented in 4-d\n");
    +    if (qh PRINTdim == 4 && qh DOintersections && qh PRINTnoplanes)
    +      qh_fprintf(qh ferr, 7052, "qhull warning: 'Gnh' generates no output in 4-d\n");
    +    if (qh PRINTdim == 2) {
    +      qh_fprintf(fp, 9036, "{appearance {linewidth 3} LIST # %s | %s\n",
    +              qh rbox_command, qh qhull_command);
    +    }else if (qh PRINTdim == 3) {
    +      qh_fprintf(fp, 9037, "{appearance {+edge -evert linewidth 2} LIST # %s | %s\n",
    +              qh rbox_command, qh qhull_command);
    +    }else if (qh PRINTdim == 4) {
    +      qh visit_id++;
    +      num= 0;
    +      FORALLfacet_(facetlist)    /* get number of ridges to be printed */
    +        qh_printend4geom(NULL, facet, &num, printall);
    +      FOREACHfacet_(facets)
    +        qh_printend4geom(NULL, facet, &num, printall);
    +      qh ridgeoutnum= num;
    +      qh printoutvar= 0;  /* counts number of ridges in output */
    +      qh_fprintf(fp, 9038, "LIST # %s | %s\n", qh rbox_command, qh qhull_command);
    +    }
    +
    +    if (qh PRINTdots) {
    +      qh printoutnum++;
    +      num= qh num_points + qh_setsize(qh other_points);
    +      if (qh DELAUNAY && qh ATinfinity)
    +        num--;
    +      if (qh PRINTdim == 4)
    +        qh_fprintf(fp, 9039, "4VECT %d %d 1\n", num, num);
    +      else
    +        qh_fprintf(fp, 9040, "VECT %d %d 1\n", num, num);
    +
    +      for (i=num; i--; ) {
    +        if (i % 20 == 0)
    +          qh_fprintf(fp, 9041, "\n");
    +        qh_fprintf(fp, 9042, "1 ");
    +      }
    +      qh_fprintf(fp, 9043, "# 1 point per line\n1 ");
    +      for (i=num-1; i--; ) { /* num at least 3 for D2 */
    +        if (i % 20 == 0)
    +          qh_fprintf(fp, 9044, "\n");
    +        qh_fprintf(fp, 9045, "0 ");
    +      }
    +      qh_fprintf(fp, 9046, "# 1 color for all\n");
    +      FORALLpoints {
    +        if (!qh DELAUNAY || !qh ATinfinity || qh_pointid(point) != qh num_points-1) {
    +          if (qh PRINTdim == 4)
    +            qh_printpoint(fp, NULL, point);
    +            else
    +              qh_printpoint3(fp, point);
    +        }
    +      }
    +      FOREACHpoint_(qh other_points) {
    +        if (qh PRINTdim == 4)
    +          qh_printpoint(fp, NULL, point);
    +        else
    +          qh_printpoint3(fp, point);
    +      }
    +      qh_fprintf(fp, 9047, "0 1 1 1  # color of points\n");
    +    }
    +
    +    if (qh PRINTdim == 4  && !qh PRINTnoplanes)
    +      /* 4dview loads up multiple 4OFF objects slowly */
    +      qh_fprintf(fp, 9048, "4OFF %d %d 1\n", 3*qh ridgeoutnum, qh ridgeoutnum);
    +    qh PRINTcradius= 2 * qh DISTround;  /* include test DISTround */
    +    if (qh PREmerge) {
    +      maximize_(qh PRINTcradius, qh premerge_centrum + qh DISTround);
    +    }else if (qh POSTmerge)
    +      maximize_(qh PRINTcradius, qh postmerge_centrum + qh DISTround);
    +    qh PRINTradius= qh PRINTcradius;
    +    if (qh PRINTspheres + qh PRINTcoplanar)
    +      maximize_(qh PRINTradius, qh MAXabs_coord * qh_MINradius);
    +    if (qh premerge_cos < REALmax/2) {
    +      maximize_(qh PRINTradius, (1- qh premerge_cos) * qh MAXabs_coord);
    +    }else if (!qh PREmerge && qh POSTmerge && qh postmerge_cos < REALmax/2) {
    +      maximize_(qh PRINTradius, (1- qh postmerge_cos) * qh MAXabs_coord);
    +    }
    +    maximize_(qh PRINTradius, qh MINvisible);
    +    if (qh JOGGLEmax < REALmax/2)
    +      qh PRINTradius += qh JOGGLEmax * sqrt((realT)qh hull_dim);
    +    if (qh PRINTdim != 4 &&
    +        (qh PRINTcoplanar || qh PRINTspheres || qh PRINTcentrums)) {
    +      vertices= qh_facetvertices(facetlist, facets, printall);
    +      if (qh PRINTspheres && qh PRINTdim <= 3)
    +        qh_printspheres(fp, vertices, qh PRINTradius);
    +      if (qh PRINTcoplanar || qh PRINTcentrums) {
    +        qh firstcentrum= True;
    +        if (qh PRINTcoplanar&& !qh PRINTspheres) {
    +          FOREACHvertex_(vertices)
    +            qh_printpointvect2(fp, vertex->point, NULL, qh interior_point, qh PRINTradius);
    +        }
    +        FORALLfacet_(facetlist) {
    +          if (!printall && qh_skipfacet(facet))
    +            continue;
    +          if (!facet->normal)
    +            continue;
    +          if (qh PRINTcentrums && qh PRINTdim <= 3)
    +            qh_printcentrum(fp, facet, qh PRINTcradius);
    +          if (!qh PRINTcoplanar)
    +            continue;
    +          FOREACHpoint_(facet->coplanarset)
    +            qh_printpointvect2(fp, point, facet->normal, NULL, qh PRINTradius);
    +          FOREACHpoint_(facet->outsideset)
    +            qh_printpointvect2(fp, point, facet->normal, NULL, qh PRINTradius);
    +        }
    +        FOREACHfacet_(facets) {
    +          if (!printall && qh_skipfacet(facet))
    +            continue;
    +          if (!facet->normal)
    +            continue;
    +          if (qh PRINTcentrums && qh PRINTdim <= 3)
    +            qh_printcentrum(fp, facet, qh PRINTcradius);
    +          if (!qh PRINTcoplanar)
    +            continue;
    +          FOREACHpoint_(facet->coplanarset)
    +            qh_printpointvect2(fp, point, facet->normal, NULL, qh PRINTradius);
    +          FOREACHpoint_(facet->outsideset)
    +            qh_printpointvect2(fp, point, facet->normal, NULL, qh PRINTradius);
    +        }
    +      }
    +      qh_settempfree(&vertices);
    +    }
    +    qh visit_id++; /* for printing hyperplane intersections */
    +    break;
    +  case qh_PRINTids:
    +    qh_fprintf(fp, 9049, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTincidences:
    +    if (qh VORONOI && qh PRINTprecision)
    +      qh_fprintf(qh ferr, 7053, "qhull warning: writing Delaunay.  Use 'p' or 'o' for Voronoi centers\n");
    +    qh printoutvar= qh vertex_id;  /* centrum id for non-simplicial facets */
    +    if (qh hull_dim <= 3)
    +      qh_fprintf(fp, 9050, "%d\n", numfacets);
    +    else
    +      qh_fprintf(fp, 9051, "%d\n", numsimplicial+numridges);
    +    break;
    +  case qh_PRINTinner:
    +  case qh_PRINTnormals:
    +  case qh_PRINTouter:
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9052, "%s | %s\nbegin\n    %d %d real\n", qh rbox_command,
    +            qh qhull_command, numfacets, qh hull_dim+1);
    +    else
    +      qh_fprintf(fp, 9053, "%d\n%d\n", qh hull_dim+1, numfacets);
    +    break;
    +  case qh_PRINTmathematica:
    +  case qh_PRINTmaple:
    +    if (qh hull_dim > 3)  /* qh_initbuffers also checks */
    +      goto LABELnoformat;
    +    if (qh VORONOI)
    +      qh_fprintf(qh ferr, 7054, "qhull warning: output is the Delaunay triangulation\n");
    +    if (format == qh_PRINTmaple) {
    +      if (qh hull_dim == 2)
    +        qh_fprintf(fp, 9054, "PLOT(CURVES(\n");
    +      else
    +        qh_fprintf(fp, 9055, "PLOT3D(POLYGONS(\n");
    +    }else
    +      qh_fprintf(fp, 9056, "{\n");
    +    qh printoutvar= 0;   /* counts number of facets for notfirst */
    +    break;
    +  case qh_PRINTmerges:
    +    qh_fprintf(fp, 9057, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTpointintersect:
    +    qh_fprintf(fp, 9058, "%d\n%d\n", qh hull_dim, numfacets);
    +    break;
    +  case qh_PRINTneighbors:
    +    qh_fprintf(fp, 9059, "%d\n", numfacets);
    +    break;
    +  case qh_PRINToff:
    +  case qh_PRINTtriangles:
    +    if (qh VORONOI)
    +      goto LABELnoformat;
    +    num = qh hull_dim;
    +    if (format == qh_PRINToff || qh hull_dim == 2)
    +      qh_fprintf(fp, 9060, "%d\n%d %d %d\n", num,
    +        qh num_points+qh_setsize(qh other_points), numfacets, totneighbors/2);
    +    else { /* qh_PRINTtriangles */
    +      qh printoutvar= qh num_points+qh_setsize(qh other_points); /* first centrum */
    +      if (qh DELAUNAY)
    +        num--;  /* drop last dimension */
    +      qh_fprintf(fp, 9061, "%d\n%d %d %d\n", num, qh printoutvar
    +        + numfacets - numsimplicial, numsimplicial + numridges, totneighbors/2);
    +    }
    +    FORALLpoints
    +      qh_printpointid(qh fout, NULL, num, point, qh_IDunknown);
    +    FOREACHpoint_(qh other_points)
    +      qh_printpointid(qh fout, NULL, num, point, qh_IDunknown);
    +    if (format == qh_PRINTtriangles && qh hull_dim > 2) {
    +      FORALLfacets {
    +        if (!facet->simplicial && facet->visitid)
    +          qh_printcenter(qh fout, format, NULL, facet);
    +      }
    +    }
    +    break;
    +  case qh_PRINTpointnearest:
    +    qh_fprintf(fp, 9062, "%d\n", numcoplanars);
    +    break;
    +  case qh_PRINTpoints:
    +    if (!qh VORONOI)
    +      goto LABELnoformat;
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9063, "%s | %s\nbegin\n%d %d real\n", qh rbox_command,
    +           qh qhull_command, numfacets, qh hull_dim);
    +    else
    +      qh_fprintf(fp, 9064, "%d\n%d\n", qh hull_dim-1, numfacets);
    +    break;
    +  case qh_PRINTvertices:
    +    qh_fprintf(fp, 9065, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTsummary:
    +  default:
    +  LABELnoformat:
    +    qh_fprintf(qh ferr, 6068, "qhull internal error (qh_printbegin): can not use this format for dimension %d\n",
    +         qh hull_dim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +} /* printbegin */
    +
    +/*---------------------------------
    +
    +  qh_printcenter( fp, string, facet )
    +    print facet->center as centrum or Voronoi center
    +    string may be NULL.  Don't include '%' codes.
    +    nop if qh CENTERtype neither CENTERvoronoi nor CENTERcentrum
    +    if upper envelope of Delaunay triangulation and point at-infinity
    +      prints qh_INFINITE instead;
    +
    +  notes:
    +    defines facet->center if needed
    +    if format=PRINTgeom, adds a 0 if would otherwise be 2-d
    +    Same as QhullFacet::printCenter
    +*/
    +void qh_printcenter(FILE *fp, qh_PRINT format, const char *string, facetT *facet) {
    +  int k, num;
    +
    +  if (qh CENTERtype != qh_ASvoronoi && qh CENTERtype != qh_AScentrum)
    +    return;
    +  if (string)
    +    qh_fprintf(fp, 9066, string);
    +  if (qh CENTERtype == qh_ASvoronoi) {
    +    num= qh hull_dim-1;
    +    if (!facet->normal || !facet->upperdelaunay || !qh ATinfinity) {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(facet->vertices);
    +      for (k=0; k < num; k++)
    +        qh_fprintf(fp, 9067, qh_REAL_1, facet->center[k]);
    +    }else {
    +      for (k=0; k < num; k++)
    +        qh_fprintf(fp, 9068, qh_REAL_1, qh_INFINITE);
    +    }
    +  }else /* qh.CENTERtype == qh_AScentrum */ {
    +    num= qh hull_dim;
    +    if (format == qh_PRINTtriangles && qh DELAUNAY)
    +      num--;
    +    if (!facet->center)
    +      facet->center= qh_getcentrum(facet);
    +    for (k=0; k < num; k++)
    +      qh_fprintf(fp, 9069, qh_REAL_1, facet->center[k]);
    +  }
    +  if (format == qh_PRINTgeom && num == 2)
    +    qh_fprintf(fp, 9070, " 0\n");
    +  else
    +    qh_fprintf(fp, 9071, "\n");
    +} /* printcenter */
    +
    +/*---------------------------------
    +
    +  qh_printcentrum( fp, facet, radius )
    +    print centrum for a facet in OOGL format
    +    radius defines size of centrum
    +    2-d or 3-d only
    +
    +  returns:
    +    defines facet->center if needed
    +*/
    +void qh_printcentrum(FILE *fp, facetT *facet, realT radius) {
    +  pointT *centrum, *projpt;
    +  boolT tempcentrum= False;
    +  realT xaxis[4], yaxis[4], normal[4], dist;
    +  realT green[3]={0, 1, 0};
    +  vertexT *apex;
    +  int k;
    +
    +  if (qh CENTERtype == qh_AScentrum) {
    +    if (!facet->center)
    +      facet->center= qh_getcentrum(facet);
    +    centrum= facet->center;
    +  }else {
    +    centrum= qh_getcentrum(facet);
    +    tempcentrum= True;
    +  }
    +  qh_fprintf(fp, 9072, "{appearance {-normal -edge normscale 0} ");
    +  if (qh firstcentrum) {
    +    qh firstcentrum= False;
    +    qh_fprintf(fp, 9073, "{INST geom { define centrum CQUAD  # f%d\n\
    +-0.3 -0.3 0.0001     0 0 1 1\n\
    + 0.3 -0.3 0.0001     0 0 1 1\n\
    + 0.3  0.3 0.0001     0 0 1 1\n\
    +-0.3  0.3 0.0001     0 0 1 1 } transform { \n", facet->id);
    +  }else
    +    qh_fprintf(fp, 9074, "{INST geom { : centrum } transform { # f%d\n", facet->id);
    +  apex= SETfirstt_(facet->vertices, vertexT);
    +  qh_distplane(apex->point, facet, &dist);
    +  projpt= qh_projectpoint(apex->point, facet, dist);
    +  for (k=qh hull_dim; k--; ) {
    +    xaxis[k]= projpt[k] - centrum[k];
    +    normal[k]= facet->normal[k];
    +  }
    +  if (qh hull_dim == 2) {
    +    xaxis[2]= 0;
    +    normal[2]= 0;
    +  }else if (qh hull_dim == 4) {
    +    qh_projectdim3(xaxis, xaxis);
    +    qh_projectdim3(normal, normal);
    +    qh_normalize2(normal, qh PRINTdim, True, NULL, NULL);
    +  }
    +  qh_crossproduct(3, xaxis, normal, yaxis);
    +  qh_fprintf(fp, 9075, "%8.4g %8.4g %8.4g 0\n", xaxis[0], xaxis[1], xaxis[2]);
    +  qh_fprintf(fp, 9076, "%8.4g %8.4g %8.4g 0\n", yaxis[0], yaxis[1], yaxis[2]);
    +  qh_fprintf(fp, 9077, "%8.4g %8.4g %8.4g 0\n", normal[0], normal[1], normal[2]);
    +  qh_printpoint3(fp, centrum);
    +  qh_fprintf(fp, 9078, "1 }}}\n");
    +  qh_memfree(projpt, qh normal_size);
    +  qh_printpointvect(fp, centrum, facet->normal, NULL, radius, green);
    +  if (tempcentrum)
    +    qh_memfree(centrum, qh normal_size);
    +} /* printcentrum */
    +
    +/*---------------------------------
    +
    +  qh_printend( fp, format )
    +    prints trailer for all output formats
    +
    +  see:
    +    qh_printbegin() and qh_printafacet()
    +
    +*/
    +void qh_printend(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int num;
    +  facetT *facet, **facetp;
    +
    +  if (!qh printoutnum)
    +    qh_fprintf(qh ferr, 7055, "qhull warning: no facets printed\n");
    +  switch (format) {
    +  case qh_PRINTgeom:
    +    if (qh hull_dim == 4 && qh DROPdim < 0  && !qh PRINTnoplanes) {
    +      qh visit_id++;
    +      num= 0;
    +      FORALLfacet_(facetlist)
    +        qh_printend4geom(fp, facet,&num, printall);
    +      FOREACHfacet_(facets)
    +        qh_printend4geom(fp, facet, &num, printall);
    +      if (num != qh ridgeoutnum || qh printoutvar != qh ridgeoutnum) {
    +        qh_fprintf(qh ferr, 6069, "qhull internal error (qh_printend): number of ridges %d != number printed %d and at end %d\n", qh ridgeoutnum, qh printoutvar, num);
    +        qh_errexit(qh_ERRqhull, NULL, NULL);
    +      }
    +    }else
    +      qh_fprintf(fp, 9079, "}\n");
    +    break;
    +  case qh_PRINTinner:
    +  case qh_PRINTnormals:
    +  case qh_PRINTouter:
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9080, "end\n");
    +    break;
    +  case qh_PRINTmaple:
    +    qh_fprintf(fp, 9081, "));\n");
    +    break;
    +  case qh_PRINTmathematica:
    +    qh_fprintf(fp, 9082, "}\n");
    +    break;
    +  case qh_PRINTpoints:
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9083, "end\n");
    +    break;
    +  default:
    +    break;
    +  }
    +} /* printend */
    +
    +/*---------------------------------
    +
    +  qh_printend4geom( fp, facet, numridges, printall )
    +    helper function for qh_printbegin/printend
    +
    +  returns:
    +    number of printed ridges
    +
    +  notes:
    +    just counts printed ridges if fp=NULL
    +    uses facet->visitid
    +    must agree with qh_printfacet4geom...
    +
    +  design:
    +    computes color for facet from its normal
    +    prints each ridge of facet
    +*/
    +void qh_printend4geom(FILE *fp, facetT *facet, int *nump, boolT printall) {
    +  realT color[3];
    +  int i, num= *nump;
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (!printall && qh_skipfacet(facet))
    +    return;
    +  if (qh PRINTnoplanes || (facet->visible && qh NEWfacets))
    +    return;
    +  if (!facet->normal)
    +    return;
    +  if (fp) {
    +    for (i=0; i < 3; i++) {
    +      color[i]= (facet->normal[i]+1.0)/2.0;
    +      maximize_(color[i], -1.0);
    +      minimize_(color[i], +1.0);
    +    }
    +  }
    +  facet->visitid= qh visit_id;
    +  if (facet->simplicial) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh visit_id) {
    +        if (fp)
    +          qh_fprintf(fp, 9084, "3 %d %d %d %8.4g %8.4g %8.4g 1 # f%d f%d\n",
    +                 3*num, 3*num+1, 3*num+2, color[0], color[1], color[2],
    +                 facet->id, neighbor->id);
    +        num++;
    +      }
    +    }
    +  }else {
    +    FOREACHridge_(facet->ridges) {
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->visitid != qh visit_id) {
    +        if (fp)
    +          qh_fprintf(fp, 9085, "3 %d %d %d %8.4g %8.4g %8.4g 1 #r%d f%d f%d\n",
    +                 3*num, 3*num+1, 3*num+2, color[0], color[1], color[2],
    +                 ridge->id, facet->id, neighbor->id);
    +        num++;
    +      }
    +    }
    +  }
    +  *nump= num;
    +} /* printend4geom */
    +
    +/*---------------------------------
    +
    +  qh_printextremes( fp, facetlist, facets, printall )
    +    print extreme points for convex hulls or halfspace intersections
    +
    +  notes:
    +    #points, followed by ids, one per line
    +
    +    sorted by id
    +    same order as qh_printpoints_out if no coplanar/interior points
    +*/
    +void qh_printextremes(FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices, *points;
    +  pointT *point;
    +  vertexT *vertex, **vertexp;
    +  int id;
    +  int numpoints=0, point_i, point_n;
    +  int allpoints= qh num_points + qh_setsize(qh other_points);
    +
    +  points= qh_settemp(allpoints);
    +  qh_setzero(points, 0, allpoints);
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  FOREACHvertex_(vertices) {
    +    id= qh_pointid(vertex->point);
    +    if (id >= 0) {
    +      SETelem_(points, id)= vertex->point;
    +      numpoints++;
    +    }
    +  }
    +  qh_settempfree(&vertices);
    +  qh_fprintf(fp, 9086, "%d\n", numpoints);
    +  FOREACHpoint_i_(points) {
    +    if (point)
    +      qh_fprintf(fp, 9087, "%d\n", point_i);
    +  }
    +  qh_settempfree(&points);
    +} /* printextremes */
    +
    +/*---------------------------------
    +
    +  qh_printextremes_2d( fp, facetlist, facets, printall )
    +    prints point ids for facets in qh_ORIENTclock order
    +
    +  notes:
    +    #points, followed by ids, one per line
    +    if facetlist/facets are disjoint than the output includes skips
    +    errors if facets form a loop
    +    does not print coplanar points
    +*/
    +void qh_printextremes_2d(FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numridges, totneighbors, numcoplanars, numsimplicial, numtricoplanars;
    +  setT *vertices;
    +  facetT *facet, *startfacet, *nextfacet;
    +  vertexT *vertexA, *vertexB;
    +
    +  qh_countfacets(facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars); /* marks qh visit_id */
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  qh_fprintf(fp, 9088, "%d\n", qh_setsize(vertices));
    +  qh_settempfree(&vertices);
    +  if (!numfacets)
    +    return;
    +  facet= startfacet= facetlist ? facetlist : SETfirstt_(facets, facetT);
    +  qh vertex_visit++;
    +  qh visit_id++;
    +  do {
    +    if (facet->toporient ^ qh_ORIENTclock) {
    +      vertexA= SETfirstt_(facet->vertices, vertexT);
    +      vertexB= SETsecondt_(facet->vertices, vertexT);
    +      nextfacet= SETfirstt_(facet->neighbors, facetT);
    +    }else {
    +      vertexA= SETsecondt_(facet->vertices, vertexT);
    +      vertexB= SETfirstt_(facet->vertices, vertexT);
    +      nextfacet= SETsecondt_(facet->neighbors, facetT);
    +    }
    +    if (facet->visitid == qh visit_id) {
    +      qh_fprintf(qh ferr, 6218, "Qhull internal error (qh_printextremes_2d): loop in facet list.  facet %d nextfacet %d\n",
    +                 facet->id, nextfacet->id);
    +      qh_errexit2(qh_ERRqhull, facet, nextfacet);
    +    }
    +    if (facet->visitid) {
    +      if (vertexA->visitid != qh vertex_visit) {
    +        vertexA->visitid= qh vertex_visit;
    +        qh_fprintf(fp, 9089, "%d\n", qh_pointid(vertexA->point));
    +      }
    +      if (vertexB->visitid != qh vertex_visit) {
    +        vertexB->visitid= qh vertex_visit;
    +        qh_fprintf(fp, 9090, "%d\n", qh_pointid(vertexB->point));
    +      }
    +    }
    +    facet->visitid= qh visit_id;
    +    facet= nextfacet;
    +  }while (facet && facet != startfacet);
    +} /* printextremes_2d */
    +
    +/*---------------------------------
    +
    +  qh_printextremes_d( fp, facetlist, facets, printall )
    +    print extreme points of input sites for Delaunay triangulations
    +
    +  notes:
    +    #points, followed by ids, one per line
    +
    +    unordered
    +*/
    +void qh_printextremes_d(FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices;
    +  vertexT *vertex, **vertexp;
    +  boolT upperseen, lowerseen;
    +  facetT *neighbor, **neighborp;
    +  int numpoints=0;
    +
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  qh_vertexneighbors();
    +  FOREACHvertex_(vertices) {
    +    upperseen= lowerseen= False;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay)
    +        upperseen= True;
    +      else
    +        lowerseen= True;
    +    }
    +    if (upperseen && lowerseen) {
    +      vertex->seen= True;
    +      numpoints++;
    +    }else
    +      vertex->seen= False;
    +  }
    +  qh_fprintf(fp, 9091, "%d\n", numpoints);
    +  FOREACHvertex_(vertices) {
    +    if (vertex->seen)
    +      qh_fprintf(fp, 9092, "%d\n", qh_pointid(vertex->point));
    +  }
    +  qh_settempfree(&vertices);
    +} /* printextremes_d */
    +
    +/*---------------------------------
    +
    +  qh_printfacet( fp, facet )
    +    prints all fields of a facet to fp
    +
    +  notes:
    +    ridges printed in neighbor order
    +*/
    +void qh_printfacet(FILE *fp, facetT *facet) {
    +
    +  qh_printfacetheader(fp, facet);
    +  if (facet->ridges)
    +    qh_printfacetridges(fp, facet);
    +} /* printfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet2geom( fp, facet, color )
    +    print facet as part of a 2-d VECT for Geomview
    +
    +    notes:
    +      assume precise calculations in io.c with roundoff covered by qh_GEOMepsilon
    +      mindist is calculated within io.c.  maxoutside is calculated elsewhere
    +      so a DISTround error may have occurred.
    +*/
    +void qh_printfacet2geom(FILE *fp, facetT *facet, realT color[3]) {
    +  pointT *point0, *point1;
    +  realT mindist, innerplane, outerplane;
    +  int k;
    +
    +  qh_facet2point(facet, &point0, &point1, &mindist);
    +  qh_geomplanes(facet, &outerplane, &innerplane);
    +  if (qh PRINTouter || (!qh PRINTnoplanes && !qh PRINTinner))
    +    qh_printfacet2geom_points(fp, point0, point1, facet, outerplane, color);
    +  if (qh PRINTinner || (!qh PRINTnoplanes && !qh PRINTouter &&
    +                outerplane - innerplane > 2 * qh MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet2geom_points(fp, point0, point1, facet, innerplane, color);
    +  }
    +  qh_memfree(point1, qh normal_size);
    +  qh_memfree(point0, qh normal_size);
    +} /* printfacet2geom */
    +
    +/*---------------------------------
    +
    +  qh_printfacet2geom_points( fp, point1, point2, facet, offset, color )
    +    prints a 2-d facet as a VECT with 2 points at some offset.
    +    The points are on the facet's plane.
    +*/
    +void qh_printfacet2geom_points(FILE *fp, pointT *point1, pointT *point2,
    +                               facetT *facet, realT offset, realT color[3]) {
    +  pointT *p1= point1, *p2= point2;
    +
    +  qh_fprintf(fp, 9093, "VECT 1 2 1 2 1 # f%d\n", facet->id);
    +  if (offset != 0.0) {
    +    p1= qh_projectpoint(p1, facet, -offset);
    +    p2= qh_projectpoint(p2, facet, -offset);
    +  }
    +  qh_fprintf(fp, 9094, "%8.4g %8.4g %8.4g\n%8.4g %8.4g %8.4g\n",
    +           p1[0], p1[1], 0.0, p2[0], p2[1], 0.0);
    +  if (offset != 0.0) {
    +    qh_memfree(p1, qh normal_size);
    +    qh_memfree(p2, qh normal_size);
    +  }
    +  qh_fprintf(fp, 9095, "%8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +} /* printfacet2geom_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet2math( fp, facet, format, notfirst )
    +    print 2-d Maple or Mathematica output for a facet
    +    may be non-simplicial
    +
    +  notes:
    +    use %16.8f since Mathematica 2.2 does not handle exponential format
    +    see qh_printfacet3math
    +*/
    +void qh_printfacet2math(FILE *fp, facetT *facet, qh_PRINT format, int notfirst) {
    +  pointT *point0, *point1;
    +  realT mindist;
    +  const char *pointfmt;
    +
    +  qh_facet2point(facet, &point0, &point1, &mindist);
    +  if (notfirst)
    +    qh_fprintf(fp, 9096, ",");
    +  if (format == qh_PRINTmaple)
    +    pointfmt= "[[%16.8f, %16.8f], [%16.8f, %16.8f]]\n";
    +  else
    +    pointfmt= "Line[{{%16.8f, %16.8f}, {%16.8f, %16.8f}}]\n";
    +  qh_fprintf(fp, 9097, pointfmt, point0[0], point0[1], point1[0], point1[1]);
    +  qh_memfree(point1, qh normal_size);
    +  qh_memfree(point0, qh normal_size);
    +} /* printfacet2math */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_nonsimplicial( fp, facet, color )
    +    print Geomview OFF for a 3-d nonsimplicial facet.
    +    if DOintersections, prints ridges to unvisited neighbors(qh visit_id)
    +
    +  notes
    +    uses facet->visitid for intersections and ridges
    +*/
    +void qh_printfacet3geom_nonsimplicial(FILE *fp, facetT *facet, realT color[3]) {
    +  ridgeT *ridge, **ridgep;
    +  setT *projectedpoints, *vertices;
    +  vertexT *vertex, **vertexp, *vertexA, *vertexB;
    +  pointT *projpt, *point, **pointp;
    +  facetT *neighbor;
    +  realT dist, outerplane, innerplane;
    +  int cntvertices, k;
    +  realT black[3]={0, 0, 0}, green[3]={0, 1, 0};
    +
    +  qh_geomplanes(facet, &outerplane, &innerplane);
    +  vertices= qh_facet3vertex(facet); /* oriented */
    +  cntvertices= qh_setsize(vertices);
    +  projectedpoints= qh_settemp(cntvertices);
    +  FOREACHvertex_(vertices) {
    +    zinc_(Zdistio);
    +    qh_distplane(vertex->point, facet, &dist);
    +    projpt= qh_projectpoint(vertex->point, facet, dist);
    +    qh_setappend(&projectedpoints, projpt);
    +  }
    +  if (qh PRINTouter || (!qh PRINTnoplanes && !qh PRINTinner))
    +    qh_printfacet3geom_points(fp, projectedpoints, facet, outerplane, color);
    +  if (qh PRINTinner || (!qh PRINTnoplanes && !qh PRINTouter &&
    +                outerplane - innerplane > 2 * qh MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet3geom_points(fp, projectedpoints, facet, innerplane, color);
    +  }
    +  FOREACHpoint_(projectedpoints)
    +    qh_memfree(point, qh normal_size);
    +  qh_settempfree(&projectedpoints);
    +  qh_settempfree(&vertices);
    +  if ((qh DOintersections || qh PRINTridges)
    +  && (!facet->visible || !qh NEWfacets)) {
    +    facet->visitid= qh visit_id;
    +    FOREACHridge_(facet->ridges) {
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->visitid != qh visit_id) {
    +        if (qh DOintersections)
    +          qh_printhyperplaneintersection(fp, facet, neighbor, ridge->vertices, black);
    +        if (qh PRINTridges) {
    +          vertexA= SETfirstt_(ridge->vertices, vertexT);
    +          vertexB= SETsecondt_(ridge->vertices, vertexT);
    +          qh_printline3geom(fp, vertexA->point, vertexB->point, green);
    +        }
    +      }
    +    }
    +  }
    +} /* printfacet3geom_nonsimplicial */
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_points( fp, points, facet, offset )
    +    prints a 3-d facet as OFF Geomview object.
    +    offset is relative to the facet's hyperplane
    +    Facet is determined as a list of points
    +*/
    +void qh_printfacet3geom_points(FILE *fp, setT *points, facetT *facet, realT offset, realT color[3]) {
    +  int k, n= qh_setsize(points), i;
    +  pointT *point, **pointp;
    +  setT *printpoints;
    +
    +  qh_fprintf(fp, 9098, "{ OFF %d 1 1 # f%d\n", n, facet->id);
    +  if (offset != 0.0) {
    +    printpoints= qh_settemp(n);
    +    FOREACHpoint_(points)
    +      qh_setappend(&printpoints, qh_projectpoint(point, facet, -offset));
    +  }else
    +    printpoints= points;
    +  FOREACHpoint_(printpoints) {
    +    for (k=0; k < qh hull_dim; k++) {
    +      if (k == qh DROPdim)
    +        qh_fprintf(fp, 9099, "0 ");
    +      else
    +        qh_fprintf(fp, 9100, "%8.4g ", point[k]);
    +    }
    +    if (printpoints != points)
    +      qh_memfree(point, qh normal_size);
    +    qh_fprintf(fp, 9101, "\n");
    +  }
    +  if (printpoints != points)
    +    qh_settempfree(&printpoints);
    +  qh_fprintf(fp, 9102, "%d ", n);
    +  for (i=0; i < n; i++)
    +    qh_fprintf(fp, 9103, "%d ", i);
    +  qh_fprintf(fp, 9104, "%8.4g %8.4g %8.4g 1.0 }\n", color[0], color[1], color[2]);
    +} /* printfacet3geom_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_simplicial(  )
    +    print Geomview OFF for a 3-d simplicial facet.
    +
    +  notes:
    +    may flip color
    +    uses facet->visitid for intersections and ridges
    +
    +    assume precise calculations in io.c with roundoff covered by qh_GEOMepsilon
    +    innerplane may be off by qh DISTround.  Maxoutside is calculated elsewhere
    +    so a DISTround error may have occurred.
    +*/
    +void qh_printfacet3geom_simplicial(FILE *fp, facetT *facet, realT color[3]) {
    +  setT *points, *vertices;
    +  vertexT *vertex, **vertexp, *vertexA, *vertexB;
    +  facetT *neighbor, **neighborp;
    +  realT outerplane, innerplane;
    +  realT black[3]={0, 0, 0}, green[3]={0, 1, 0};
    +  int k;
    +
    +  qh_geomplanes(facet, &outerplane, &innerplane);
    +  vertices= qh_facet3vertex(facet);
    +  points= qh_settemp(qh TEMPsize);
    +  FOREACHvertex_(vertices)
    +    qh_setappend(&points, vertex->point);
    +  if (qh PRINTouter || (!qh PRINTnoplanes && !qh PRINTinner))
    +    qh_printfacet3geom_points(fp, points, facet, outerplane, color);
    +  if (qh PRINTinner || (!qh PRINTnoplanes && !qh PRINTouter &&
    +              outerplane - innerplane > 2 * qh MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet3geom_points(fp, points, facet, innerplane, color);
    +  }
    +  qh_settempfree(&points);
    +  qh_settempfree(&vertices);
    +  if ((qh DOintersections || qh PRINTridges)
    +  && (!facet->visible || !qh NEWfacets)) {
    +    facet->visitid= qh visit_id;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh visit_id) {
    +        vertices= qh_setnew_delnthsorted(facet->vertices, qh hull_dim,
    +                          SETindex_(facet->neighbors, neighbor), 0);
    +        if (qh DOintersections)
    +           qh_printhyperplaneintersection(fp, facet, neighbor, vertices, black);
    +        if (qh PRINTridges) {
    +          vertexA= SETfirstt_(vertices, vertexT);
    +          vertexB= SETsecondt_(vertices, vertexT);
    +          qh_printline3geom(fp, vertexA->point, vertexB->point, green);
    +        }
    +        qh_setfree(&vertices);
    +      }
    +    }
    +  }
    +} /* printfacet3geom_simplicial */
    +
    +/*---------------------------------
    +
    +  qh_printfacet3math( fp, facet, notfirst )
    +    print 3-d Maple or Mathematica output for a facet
    +
    +  notes:
    +    may be non-simplicial
    +    use %16.8f since Mathematica 2.2 does not handle exponential format
    +    see qh_printfacet2math
    +*/
    +void qh_printfacet3math(FILE *fp, facetT *facet, qh_PRINT format, int notfirst) {
    +  vertexT *vertex, **vertexp;
    +  setT *points, *vertices;
    +  pointT *point, **pointp;
    +  boolT firstpoint= True;
    +  realT dist;
    +  const char *pointfmt, *endfmt;
    +
    +  if (notfirst)
    +    qh_fprintf(fp, 9105, ",\n");
    +  vertices= qh_facet3vertex(facet);
    +  points= qh_settemp(qh_setsize(vertices));
    +  FOREACHvertex_(vertices) {
    +    zinc_(Zdistio);
    +    qh_distplane(vertex->point, facet, &dist);
    +    point= qh_projectpoint(vertex->point, facet, dist);
    +    qh_setappend(&points, point);
    +  }
    +  if (format == qh_PRINTmaple) {
    +    qh_fprintf(fp, 9106, "[");
    +    pointfmt= "[%16.8f, %16.8f, %16.8f]";
    +    endfmt= "]";
    +  }else {
    +    qh_fprintf(fp, 9107, "Polygon[{");
    +    pointfmt= "{%16.8f, %16.8f, %16.8f}";
    +    endfmt= "}]";
    +  }
    +  FOREACHpoint_(points) {
    +    if (firstpoint)
    +      firstpoint= False;
    +    else
    +      qh_fprintf(fp, 9108, ",\n");
    +    qh_fprintf(fp, 9109, pointfmt, point[0], point[1], point[2]);
    +  }
    +  FOREACHpoint_(points)
    +    qh_memfree(point, qh normal_size);
    +  qh_settempfree(&points);
    +  qh_settempfree(&vertices);
    +  qh_fprintf(fp, 9110, "%s", endfmt);
    +} /* printfacet3math */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3vertex( fp, facet, format )
    +    print vertices in a 3-d facet as point ids
    +
    +  notes:
    +    prints number of vertices first if format == qh_PRINToff
    +    the facet may be non-simplicial
    +*/
    +void qh_printfacet3vertex(FILE *fp, facetT *facet, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +
    +  vertices= qh_facet3vertex(facet);
    +  if (format == qh_PRINToff)
    +    qh_fprintf(fp, 9111, "%d ", qh_setsize(vertices));
    +  FOREACHvertex_(vertices)
    +    qh_fprintf(fp, 9112, "%d ", qh_pointid(vertex->point));
    +  qh_fprintf(fp, 9113, "\n");
    +  qh_settempfree(&vertices);
    +} /* printfacet3vertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet4geom_nonsimplicial(  )
    +    print Geomview 4OFF file for a 4d nonsimplicial facet
    +    prints all ridges to unvisited neighbors (qh.visit_id)
    +    if qh.DROPdim
    +      prints in OFF format
    +
    +  notes:
    +    must agree with printend4geom()
    +*/
    +void qh_printfacet4geom_nonsimplicial(FILE *fp, facetT *facet, realT color[3]) {
    +  facetT *neighbor;
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +  pointT *point;
    +  int k;
    +  realT dist;
    +
    +  facet->visitid= qh visit_id;
    +  if (qh PRINTnoplanes || (facet->visible && qh NEWfacets))
    +    return;
    +  FOREACHridge_(facet->ridges) {
    +    neighbor= otherfacet_(ridge, facet);
    +    if (neighbor->visitid == qh visit_id)
    +      continue;
    +    if (qh PRINTtransparent && !neighbor->good)
    +      continue;
    +    if (qh DOintersections)
    +      qh_printhyperplaneintersection(fp, facet, neighbor, ridge->vertices, color);
    +    else {
    +      if (qh DROPdim >= 0)
    +        qh_fprintf(fp, 9114, "OFF 3 1 1 # f%d\n", facet->id);
    +      else {
    +        qh printoutvar++;
    +        qh_fprintf(fp, 9115, "# r%d between f%d f%d\n", ridge->id, facet->id, neighbor->id);
    +      }
    +      FOREACHvertex_(ridge->vertices) {
    +        zinc_(Zdistio);
    +        qh_distplane(vertex->point,facet, &dist);
    +        point=qh_projectpoint(vertex->point,facet, dist);
    +        for (k=0; k < qh hull_dim; k++) {
    +          if (k != qh DROPdim)
    +            qh_fprintf(fp, 9116, "%8.4g ", point[k]);
    +        }
    +        qh_fprintf(fp, 9117, "\n");
    +        qh_memfree(point, qh normal_size);
    +      }
    +      if (qh DROPdim >= 0)
    +        qh_fprintf(fp, 9118, "3 0 1 2 %8.4g %8.4g %8.4g\n", color[0], color[1], color[2]);
    +    }
    +  }
    +} /* printfacet4geom_nonsimplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet4geom_simplicial( fp, facet, color )
    +    print Geomview 4OFF file for a 4d simplicial facet
    +    prints triangles for unvisited neighbors (qh.visit_id)
    +
    +  notes:
    +    must agree with printend4geom()
    +*/
    +void qh_printfacet4geom_simplicial(FILE *fp, facetT *facet, realT color[3]) {
    +  setT *vertices;
    +  facetT *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  int k;
    +
    +  facet->visitid= qh visit_id;
    +  if (qh PRINTnoplanes || (facet->visible && qh NEWfacets))
    +    return;
    +  FOREACHneighbor_(facet) {
    +    if (neighbor->visitid == qh visit_id)
    +      continue;
    +    if (qh PRINTtransparent && !neighbor->good)
    +      continue;
    +    vertices= qh_setnew_delnthsorted(facet->vertices, qh hull_dim,
    +                          SETindex_(facet->neighbors, neighbor), 0);
    +    if (qh DOintersections)
    +      qh_printhyperplaneintersection(fp, facet, neighbor, vertices, color);
    +    else {
    +      if (qh DROPdim >= 0)
    +        qh_fprintf(fp, 9119, "OFF 3 1 1 # ridge between f%d f%d\n",
    +                facet->id, neighbor->id);
    +      else {
    +        qh printoutvar++;
    +        qh_fprintf(fp, 9120, "# ridge between f%d f%d\n", facet->id, neighbor->id);
    +      }
    +      FOREACHvertex_(vertices) {
    +        for (k=0; k < qh hull_dim; k++) {
    +          if (k != qh DROPdim)
    +            qh_fprintf(fp, 9121, "%8.4g ", vertex->point[k]);
    +        }
    +        qh_fprintf(fp, 9122, "\n");
    +      }
    +      if (qh DROPdim >= 0)
    +        qh_fprintf(fp, 9123, "3 0 1 2 %8.4g %8.4g %8.4g\n", color[0], color[1], color[2]);
    +    }
    +    qh_setfree(&vertices);
    +  }
    +} /* printfacet4geom_simplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetNvertex_nonsimplicial( fp, facet, id, format )
    +    print vertices for an N-d non-simplicial facet
    +    triangulates each ridge to the id
    +*/
    +void qh_printfacetNvertex_nonsimplicial(FILE *fp, facetT *facet, int id, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (facet->visible && qh NEWfacets)
    +    return;
    +  FOREACHridge_(facet->ridges) {
    +    if (format == qh_PRINTtriangles)
    +      qh_fprintf(fp, 9124, "%d ", qh hull_dim);
    +    qh_fprintf(fp, 9125, "%d ", id);
    +    if ((ridge->top == facet) ^ qh_ORIENTclock) {
    +      FOREACHvertex_(ridge->vertices)
    +        qh_fprintf(fp, 9126, "%d ", qh_pointid(vertex->point));
    +    }else {
    +      FOREACHvertexreverse12_(ridge->vertices)
    +        qh_fprintf(fp, 9127, "%d ", qh_pointid(vertex->point));
    +    }
    +    qh_fprintf(fp, 9128, "\n");
    +  }
    +} /* printfacetNvertex_nonsimplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetNvertex_simplicial( fp, facet, format )
    +    print vertices for an N-d simplicial facet
    +    prints vertices for non-simplicial facets
    +      2-d facets (orientation preserved by qh_mergefacet2d)
    +      PRINToff ('o') for 4-d and higher
    +*/
    +void qh_printfacetNvertex_simplicial(FILE *fp, facetT *facet, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +
    +  if (format == qh_PRINToff || format == qh_PRINTtriangles)
    +    qh_fprintf(fp, 9129, "%d ", qh_setsize(facet->vertices));
    +  if ((facet->toporient ^ qh_ORIENTclock)
    +  || (qh hull_dim > 2 && !facet->simplicial)) {
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(fp, 9130, "%d ", qh_pointid(vertex->point));
    +  }else {
    +    FOREACHvertexreverse12_(facet->vertices)
    +      qh_fprintf(fp, 9131, "%d ", qh_pointid(vertex->point));
    +  }
    +  qh_fprintf(fp, 9132, "\n");
    +} /* printfacetNvertex_simplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetheader( fp, facet )
    +    prints header fields of a facet to fp
    +
    +  notes:
    +    for 'f' output and debugging
    +    Same as QhullFacet::printHeader()
    +*/
    +void qh_printfacetheader(FILE *fp, facetT *facet) {
    +  pointT *point, **pointp, *furthest;
    +  facetT *neighbor, **neighborp;
    +  realT dist;
    +
    +  if (facet == qh_MERGEridge) {
    +    qh_fprintf(fp, 9133, " MERGEridge\n");
    +    return;
    +  }else if (facet == qh_DUPLICATEridge) {
    +    qh_fprintf(fp, 9134, " DUPLICATEridge\n");
    +    return;
    +  }else if (!facet) {
    +    qh_fprintf(fp, 9135, " NULLfacet\n");
    +    return;
    +  }
    +  qh old_randomdist= qh RANDOMdist;
    +  qh RANDOMdist= False;
    +  qh_fprintf(fp, 9136, "- f%d\n", facet->id);
    +  qh_fprintf(fp, 9137, "    - flags:");
    +  if (facet->toporient)
    +    qh_fprintf(fp, 9138, " top");
    +  else
    +    qh_fprintf(fp, 9139, " bottom");
    +  if (facet->simplicial)
    +    qh_fprintf(fp, 9140, " simplicial");
    +  if (facet->tricoplanar)
    +    qh_fprintf(fp, 9141, " tricoplanar");
    +  if (facet->upperdelaunay)
    +    qh_fprintf(fp, 9142, " upperDelaunay");
    +  if (facet->visible)
    +    qh_fprintf(fp, 9143, " visible");
    +  if (facet->newfacet)
    +    qh_fprintf(fp, 9144, " new");
    +  if (facet->tested)
    +    qh_fprintf(fp, 9145, " tested");
    +  if (!facet->good)
    +    qh_fprintf(fp, 9146, " notG");
    +  if (facet->seen)
    +    qh_fprintf(fp, 9147, " seen");
    +  if (facet->coplanar)
    +    qh_fprintf(fp, 9148, " coplanar");
    +  if (facet->mergehorizon)
    +    qh_fprintf(fp, 9149, " mergehorizon");
    +  if (facet->keepcentrum)
    +    qh_fprintf(fp, 9150, " keepcentrum");
    +  if (facet->dupridge)
    +    qh_fprintf(fp, 9151, " dupridge");
    +  if (facet->mergeridge && !facet->mergeridge2)
    +    qh_fprintf(fp, 9152, " mergeridge1");
    +  if (facet->mergeridge2)
    +    qh_fprintf(fp, 9153, " mergeridge2");
    +  if (facet->newmerge)
    +    qh_fprintf(fp, 9154, " newmerge");
    +  if (facet->flipped)
    +    qh_fprintf(fp, 9155, " flipped");
    +  if (facet->notfurthest)
    +    qh_fprintf(fp, 9156, " notfurthest");
    +  if (facet->degenerate)
    +    qh_fprintf(fp, 9157, " degenerate");
    +  if (facet->redundant)
    +    qh_fprintf(fp, 9158, " redundant");
    +  qh_fprintf(fp, 9159, "\n");
    +  if (facet->isarea)
    +    qh_fprintf(fp, 9160, "    - area: %2.2g\n", facet->f.area);
    +  else if (qh NEWfacets && facet->visible && facet->f.replace)
    +    qh_fprintf(fp, 9161, "    - replacement: f%d\n", facet->f.replace->id);
    +  else if (facet->newfacet) {
    +    if (facet->f.samecycle && facet->f.samecycle != facet)
    +      qh_fprintf(fp, 9162, "    - shares same visible/horizon as f%d\n", facet->f.samecycle->id);
    +  }else if (facet->tricoplanar /* !isarea */) {
    +    if (facet->f.triowner)
    +      qh_fprintf(fp, 9163, "    - owner of normal & centrum is facet f%d\n", facet->f.triowner->id);
    +  }else if (facet->f.newcycle)
    +    qh_fprintf(fp, 9164, "    - was horizon to f%d\n", facet->f.newcycle->id);
    +  if (facet->nummerge)
    +    qh_fprintf(fp, 9165, "    - merges: %d\n", facet->nummerge);
    +  qh_printpointid(fp, "    - normal: ", qh hull_dim, facet->normal, qh_IDunknown);
    +  qh_fprintf(fp, 9166, "    - offset: %10.7g\n", facet->offset);
    +  if (qh CENTERtype == qh_ASvoronoi || facet->center)
    +    qh_printcenter(fp, qh_PRINTfacets, "    - center: ", facet);
    +#if qh_MAXoutside
    +  if (facet->maxoutside > qh DISTround)
    +    qh_fprintf(fp, 9167, "    - maxoutside: %10.7g\n", facet->maxoutside);
    +#endif
    +  if (!SETempty_(facet->outsideset)) {
    +    furthest= (pointT*)qh_setlast(facet->outsideset);
    +    if (qh_setsize(facet->outsideset) < 6) {
    +      qh_fprintf(fp, 9168, "    - outside set(furthest p%d):\n", qh_pointid(furthest));
    +      FOREACHpoint_(facet->outsideset)
    +        qh_printpoint(fp, "     ", point);
    +    }else if (qh_setsize(facet->outsideset) < 21) {
    +      qh_printpoints(fp, "    - outside set:", facet->outsideset);
    +    }else {
    +      qh_fprintf(fp, 9169, "    - outside set:  %d points.", qh_setsize(facet->outsideset));
    +      qh_printpoint(fp, "  Furthest", furthest);
    +    }
    +#if !qh_COMPUTEfurthest
    +    qh_fprintf(fp, 9170, "    - furthest distance= %2.2g\n", facet->furthestdist);
    +#endif
    +  }
    +  if (!SETempty_(facet->coplanarset)) {
    +    furthest= (pointT*)qh_setlast(facet->coplanarset);
    +    if (qh_setsize(facet->coplanarset) < 6) {
    +      qh_fprintf(fp, 9171, "    - coplanar set(furthest p%d):\n", qh_pointid(furthest));
    +      FOREACHpoint_(facet->coplanarset)
    +        qh_printpoint(fp, "     ", point);
    +    }else if (qh_setsize(facet->coplanarset) < 21) {
    +      qh_printpoints(fp, "    - coplanar set:", facet->coplanarset);
    +    }else {
    +      qh_fprintf(fp, 9172, "    - coplanar set:  %d points.", qh_setsize(facet->coplanarset));
    +      qh_printpoint(fp, "  Furthest", furthest);
    +    }
    +    zinc_(Zdistio);
    +    qh_distplane(furthest, facet, &dist);
    +    qh_fprintf(fp, 9173, "      furthest distance= %2.2g\n", dist);
    +  }
    +  qh_printvertices(fp, "    - vertices:", facet->vertices);
    +  qh_fprintf(fp, 9174, "    - neighboring facets:");
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      qh_fprintf(fp, 9175, " MERGE");
    +    else if (neighbor == qh_DUPLICATEridge)
    +      qh_fprintf(fp, 9176, " DUP");
    +    else
    +      qh_fprintf(fp, 9177, " f%d", neighbor->id);
    +  }
    +  qh_fprintf(fp, 9178, "\n");
    +  qh RANDOMdist= qh old_randomdist;
    +} /* printfacetheader */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetridges( fp, facet )
    +    prints ridges of a facet to fp
    +
    +  notes:
    +    ridges printed in neighbor order
    +    assumes the ridges exist
    +    for 'f' output
    +    same as QhullFacet::printRidges
    +*/
    +void qh_printfacetridges(FILE *fp, facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int numridges= 0;
    +
    +
    +  if (facet->visible && qh NEWfacets) {
    +    qh_fprintf(fp, 9179, "    - ridges(ids may be garbage):");
    +    FOREACHridge_(facet->ridges)
    +      qh_fprintf(fp, 9180, " r%d", ridge->id);
    +    qh_fprintf(fp, 9181, "\n");
    +  }else {
    +    qh_fprintf(fp, 9182, "    - ridges:\n");
    +    FOREACHridge_(facet->ridges)
    +      ridge->seen= False;
    +    if (qh hull_dim == 3) {
    +      ridge= SETfirstt_(facet->ridges, ridgeT);
    +      while (ridge && !ridge->seen) {
    +        ridge->seen= True;
    +        qh_printridge(fp, ridge);
    +        numridges++;
    +        ridge= qh_nextridge3d(ridge, facet, NULL);
    +        }
    +    }else {
    +      FOREACHneighbor_(facet) {
    +        FOREACHridge_(facet->ridges) {
    +          if (otherfacet_(ridge,facet) == neighbor) {
    +            ridge->seen= True;
    +            qh_printridge(fp, ridge);
    +            numridges++;
    +          }
    +        }
    +      }
    +    }
    +    if (numridges != qh_setsize(facet->ridges)) {
    +      qh_fprintf(fp, 9183, "     - all ridges:");
    +      FOREACHridge_(facet->ridges)
    +        qh_fprintf(fp, 9184, " r%d", ridge->id);
    +        qh_fprintf(fp, 9185, "\n");
    +    }
    +    FOREACHridge_(facet->ridges) {
    +      if (!ridge->seen)
    +        qh_printridge(fp, ridge);
    +    }
    +  }
    +} /* printfacetridges */
    +
    +/*---------------------------------
    +
    +  qh_printfacets( fp, format, facetlist, facets, printall )
    +    prints facetlist and/or facet set in output format
    +
    +  notes:
    +    also used for specialized formats ('FO' and summary)
    +    turns off 'Rn' option since want actual numbers
    +*/
    +void qh_printfacets(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numcoplanars, numtricoplanars;
    +  facetT *facet, **facetp;
    +  setT *vertices;
    +  coordT *center;
    +  realT outerplane, innerplane;
    +
    +  qh old_randomdist= qh RANDOMdist;
    +  qh RANDOMdist= False;
    +  if (qh CDDoutput && (format == qh_PRINTcentrums || format == qh_PRINTpointintersect || format == qh_PRINToff))
    +    qh_fprintf(qh ferr, 7056, "qhull warning: CDD format is not available for centrums, halfspace\nintersections, and OFF file format.\n");
    +  if (format == qh_PRINTnone)
    +    ; /* print nothing */
    +  else if (format == qh_PRINTaverage) {
    +    vertices= qh_facetvertices(facetlist, facets, printall);
    +    center= qh_getcenter(vertices);
    +    qh_fprintf(fp, 9186, "%d 1\n", qh hull_dim);
    +    qh_printpointid(fp, NULL, qh hull_dim, center, qh_IDunknown);
    +    qh_memfree(center, qh normal_size);
    +    qh_settempfree(&vertices);
    +  }else if (format == qh_PRINTextremes) {
    +    if (qh DELAUNAY)
    +      qh_printextremes_d(fp, facetlist, facets, printall);
    +    else if (qh hull_dim == 2)
    +      qh_printextremes_2d(fp, facetlist, facets, printall);
    +    else
    +      qh_printextremes(fp, facetlist, facets, printall);
    +  }else if (format == qh_PRINToptions)
    +    qh_fprintf(fp, 9187, "Options selected for Qhull %s:\n%s\n", qh_version, qh qhull_options);
    +  else if (format == qh_PRINTpoints && !qh VORONOI)
    +    qh_printpoints_out(fp, facetlist, facets, printall);
    +  else if (format == qh_PRINTqhull)
    +    qh_fprintf(fp, 9188, "%s | %s\n", qh rbox_command, qh qhull_command);
    +  else if (format == qh_PRINTsize) {
    +    qh_fprintf(fp, 9189, "0\n2 ");
    +    qh_fprintf(fp, 9190, qh_REAL_1, qh totarea);
    +    qh_fprintf(fp, 9191, qh_REAL_1, qh totvol);
    +    qh_fprintf(fp, 9192, "\n");
    +  }else if (format == qh_PRINTsummary) {
    +    qh_countfacets(facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);
    +    vertices= qh_facetvertices(facetlist, facets, printall);
    +    qh_fprintf(fp, 9193, "10 %d %d %d %d %d %d %d %d %d %d\n2 ", qh hull_dim,
    +                qh num_points + qh_setsize(qh other_points),
    +                qh num_vertices, qh num_facets - qh num_visible,
    +                qh_setsize(vertices), numfacets, numcoplanars,
    +                numfacets - numsimplicial, zzval_(Zdelvertextot),
    +                numtricoplanars);
    +    qh_settempfree(&vertices);
    +    qh_outerinner(NULL, &outerplane, &innerplane);
    +    qh_fprintf(fp, 9194, qh_REAL_2n, outerplane, innerplane);
    +  }else if (format == qh_PRINTvneighbors)
    +    qh_printvneighbors(fp, facetlist, facets, printall);
    +  else if (qh VORONOI && format == qh_PRINToff)
    +    qh_printvoronoi(fp, format, facetlist, facets, printall);
    +  else if (qh VORONOI && format == qh_PRINTgeom) {
    +    qh_printbegin(fp, format, facetlist, facets, printall);
    +    qh_printvoronoi(fp, format, facetlist, facets, printall);
    +    qh_printend(fp, format, facetlist, facets, printall);
    +  }else if (qh VORONOI
    +  && (format == qh_PRINTvertices || format == qh_PRINTinner || format == qh_PRINTouter))
    +    qh_printvdiagram(fp, format, facetlist, facets, printall);
    +  else {
    +    qh_printbegin(fp, format, facetlist, facets, printall);
    +    FORALLfacet_(facetlist)
    +      qh_printafacet(fp, format, facet, printall);
    +    FOREACHfacet_(facets)
    +      qh_printafacet(fp, format, facet, printall);
    +    qh_printend(fp, format, facetlist, facets, printall);
    +  }
    +  qh RANDOMdist= qh old_randomdist;
    +} /* printfacets */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhyperplaneintersection( fp, facet1, facet2, vertices, color )
    +    print Geomview OFF or 4OFF for the intersection of two hyperplanes in 3-d or 4-d
    +*/
    +void qh_printhyperplaneintersection(FILE *fp, facetT *facet1, facetT *facet2,
    +                   setT *vertices, realT color[3]) {
    +  realT costheta, denominator, dist1, dist2, s, t, mindenom, p[4];
    +  vertexT *vertex, **vertexp;
    +  int i, k;
    +  boolT nearzero1, nearzero2;
    +
    +  costheta= qh_getangle(facet1->normal, facet2->normal);
    +  denominator= 1 - costheta * costheta;
    +  i= qh_setsize(vertices);
    +  if (qh hull_dim == 3)
    +    qh_fprintf(fp, 9195, "VECT 1 %d 1 %d 1 ", i, i);
    +  else if (qh hull_dim == 4 && qh DROPdim >= 0)
    +    qh_fprintf(fp, 9196, "OFF 3 1 1 ");
    +  else
    +    qh printoutvar++;
    +  qh_fprintf(fp, 9197, "# intersect f%d f%d\n", facet1->id, facet2->id);
    +  mindenom= 1 / (10.0 * qh MAXabs_coord);
    +  FOREACHvertex_(vertices) {
    +    zadd_(Zdistio, 2);
    +    qh_distplane(vertex->point, facet1, &dist1);
    +    qh_distplane(vertex->point, facet2, &dist2);
    +    s= qh_divzero(-dist1 + costheta * dist2, denominator,mindenom,&nearzero1);
    +    t= qh_divzero(-dist2 + costheta * dist1, denominator,mindenom,&nearzero2);
    +    if (nearzero1 || nearzero2)
    +      s= t= 0.0;
    +    for (k=qh hull_dim; k--; )
    +      p[k]= vertex->point[k] + facet1->normal[k] * s + facet2->normal[k] * t;
    +    if (qh PRINTdim <= 3) {
    +      qh_projectdim3(p, p);
    +      qh_fprintf(fp, 9198, "%8.4g %8.4g %8.4g # ", p[0], p[1], p[2]);
    +    }else
    +      qh_fprintf(fp, 9199, "%8.4g %8.4g %8.4g %8.4g # ", p[0], p[1], p[2], p[3]);
    +    if (nearzero1+nearzero2)
    +      qh_fprintf(fp, 9200, "p%d(coplanar facets)\n", qh_pointid(vertex->point));
    +    else
    +      qh_fprintf(fp, 9201, "projected p%d\n", qh_pointid(vertex->point));
    +  }
    +  if (qh hull_dim == 3)
    +    qh_fprintf(fp, 9202, "%8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +  else if (qh hull_dim == 4 && qh DROPdim >= 0)
    +    qh_fprintf(fp, 9203, "3 0 1 2 %8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +} /* printhyperplaneintersection */
    +
    +/*---------------------------------
    +
    +  qh_printline3geom( fp, pointA, pointB, color )
    +    prints a line as a VECT
    +    prints 0's for qh.DROPdim
    +
    +  notes:
    +    if pointA == pointB,
    +      it's a 1 point VECT
    +*/
    +void qh_printline3geom(FILE *fp, pointT *pointA, pointT *pointB, realT color[3]) {
    +  int k;
    +  realT pA[4], pB[4];
    +
    +  qh_projectdim3(pointA, pA);
    +  qh_projectdim3(pointB, pB);
    +  if ((fabs(pA[0] - pB[0]) > 1e-3) ||
    +      (fabs(pA[1] - pB[1]) > 1e-3) ||
    +      (fabs(pA[2] - pB[2]) > 1e-3)) {
    +    qh_fprintf(fp, 9204, "VECT 1 2 1 2 1\n");
    +    for (k=0; k < 3; k++)
    +       qh_fprintf(fp, 9205, "%8.4g ", pB[k]);
    +    qh_fprintf(fp, 9206, " # p%d\n", qh_pointid(pointB));
    +  }else
    +    qh_fprintf(fp, 9207, "VECT 1 1 1 1 1\n");
    +  for (k=0; k < 3; k++)
    +    qh_fprintf(fp, 9208, "%8.4g ", pA[k]);
    +  qh_fprintf(fp, 9209, " # p%d\n", qh_pointid(pointA));
    +  qh_fprintf(fp, 9210, "%8.4g %8.4g %8.4g 1\n", color[0], color[1], color[2]);
    +}
    +
    +/*---------------------------------
    +
    +  qh_printneighborhood( fp, format, facetA, facetB, printall )
    +    print neighborhood of one or two facets
    +
    +  notes:
    +    calls qh_findgood_all()
    +    bumps qh.visit_id
    +*/
    +void qh_printneighborhood(FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall) {
    +  facetT *neighbor, **neighborp, *facet;
    +  setT *facets;
    +
    +  if (format == qh_PRINTnone)
    +    return;
    +  qh_findgood_all(qh facet_list);
    +  if (facetA == facetB)
    +    facetB= NULL;
    +  facets= qh_settemp(2*(qh_setsize(facetA->neighbors)+1));
    +  qh visit_id++;
    +  for (facet= facetA; facet; facet= ((facet == facetA) ? facetB : NULL)) {
    +    if (facet->visitid != qh visit_id) {
    +      facet->visitid= qh visit_id;
    +      qh_setappend(&facets, facet);
    +    }
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == qh visit_id)
    +        continue;
    +      neighbor->visitid= qh visit_id;
    +      if (printall || !qh_skipfacet(neighbor))
    +        qh_setappend(&facets, neighbor);
    +    }
    +  }
    +  qh_printfacets(fp, format, NULL, facets, printall);
    +  qh_settempfree(&facets);
    +} /* printneighborhood */
    +
    +/*---------------------------------
    +
    +  qh_printpoint( fp, string, point )
    +  qh_printpointid( fp, string, dim, point, id )
    +    prints the coordinates of a point
    +
    +  returns:
    +    if string is defined
    +      prints 'string p%d'.  Skips p%d if id=qh_IDunknown(-1) or qh_IDnone(-3)
    +
    +  notes:
    +    nop if point is NULL
    +    Same as QhullPoint's printPoint
    +*/
    +void qh_printpoint(FILE *fp, const char *string, pointT *point) {
    +  int id= qh_pointid( point);
    +
    +  qh_printpointid( fp, string, qh hull_dim, point, id);
    +} /* printpoint */
    +
    +void qh_printpointid(FILE *fp, const char *string, int dim, pointT *point, int id) {
    +  int k;
    +  realT r; /*bug fix*/
    +
    +  if (!point)
    +    return;
    +  if (string) {
    +    qh_fprintf(fp, 9211, "%s", string);
    +    if (id != qh_IDunknown && id != qh_IDnone)
    +      qh_fprintf(fp, 9212, " p%d: ", id);
    +  }
    +  for (k=dim; k--; ) {
    +    r= *point++;
    +    if (string)
    +      qh_fprintf(fp, 9213, " %8.4g", r);
    +    else
    +      qh_fprintf(fp, 9214, qh_REAL_1, r);
    +  }
    +  qh_fprintf(fp, 9215, "\n");
    +} /* printpointid */
    +
    +/*---------------------------------
    +
    +  qh_printpoint3( fp, point )
    +    prints 2-d, 3-d, or 4-d point as Geomview 3-d coordinates
    +*/
    +void qh_printpoint3(FILE *fp, pointT *point) {
    +  int k;
    +  realT p[4];
    +
    +  qh_projectdim3(point, p);
    +  for (k=0; k < 3; k++)
    +    qh_fprintf(fp, 9216, "%8.4g ", p[k]);
    +  qh_fprintf(fp, 9217, " # p%d\n", qh_pointid(point));
    +} /* printpoint3 */
    +
    +/*----------------------------------------
    +-printpoints- print pointids for a set of points starting at index
    +   see geom.c
    +*/
    +
    +/*---------------------------------
    +
    +  qh_printpoints_out( fp, facetlist, facets, printall )
    +    prints vertices, coplanar/inside points, for facets by their point coordinates
    +    allows qh.CDDoutput
    +
    +  notes:
    +    same format as qhull input
    +    if no coplanar/interior points,
    +      same order as qh_printextremes
    +*/
    +void qh_printpoints_out(FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  int allpoints= qh num_points + qh_setsize(qh other_points);
    +  int numpoints=0, point_i, point_n;
    +  setT *vertices, *points;
    +  facetT *facet, **facetp;
    +  pointT *point, **pointp;
    +  vertexT *vertex, **vertexp;
    +  int id;
    +
    +  points= qh_settemp(allpoints);
    +  qh_setzero(points, 0, allpoints);
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  FOREACHvertex_(vertices) {
    +    id= qh_pointid(vertex->point);
    +    if (id >= 0)
    +      SETelem_(points, id)= vertex->point;
    +  }
    +  if (qh KEEPinside || qh KEEPcoplanar || qh KEEPnearinside) {
    +    FORALLfacet_(facetlist) {
    +      if (!printall && qh_skipfacet(facet))
    +        continue;
    +      FOREACHpoint_(facet->coplanarset) {
    +        id= qh_pointid(point);
    +        if (id >= 0)
    +          SETelem_(points, id)= point;
    +      }
    +    }
    +    FOREACHfacet_(facets) {
    +      if (!printall && qh_skipfacet(facet))
    +        continue;
    +      FOREACHpoint_(facet->coplanarset) {
    +        id= qh_pointid(point);
    +        if (id >= 0)
    +          SETelem_(points, id)= point;
    +      }
    +    }
    +  }
    +  qh_settempfree(&vertices);
    +  FOREACHpoint_i_(points) {
    +    if (point)
    +      numpoints++;
    +  }
    +  if (qh CDDoutput)
    +    qh_fprintf(fp, 9218, "%s | %s\nbegin\n%d %d real\n", qh rbox_command,
    +             qh qhull_command, numpoints, qh hull_dim + 1);
    +  else
    +    qh_fprintf(fp, 9219, "%d\n%d\n", qh hull_dim, numpoints);
    +  FOREACHpoint_i_(points) {
    +    if (point) {
    +      if (qh CDDoutput)
    +        qh_fprintf(fp, 9220, "1 ");
    +      qh_printpoint(fp, NULL, point);
    +    }
    +  }
    +  if (qh CDDoutput)
    +    qh_fprintf(fp, 9221, "end\n");
    +  qh_settempfree(&points);
    +} /* printpoints_out */
    +
    +
    +/*---------------------------------
    +
    +  qh_printpointvect( fp, point, normal, center, radius, color )
    +    prints a 2-d, 3-d, or 4-d point as 3-d VECT's relative to normal or to center point
    +*/
    +void qh_printpointvect(FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius, realT color[3]) {
    +  realT diff[4], pointA[4];
    +  int k;
    +
    +  for (k=qh hull_dim; k--; ) {
    +    if (center)
    +      diff[k]= point[k]-center[k];
    +    else if (normal)
    +      diff[k]= normal[k];
    +    else
    +      diff[k]= 0;
    +  }
    +  if (center)
    +    qh_normalize2(diff, qh hull_dim, True, NULL, NULL);
    +  for (k=qh hull_dim; k--; )
    +    pointA[k]= point[k]+diff[k] * radius;
    +  qh_printline3geom(fp, point, pointA, color);
    +} /* printpointvect */
    +
    +/*---------------------------------
    +
    +  qh_printpointvect2( fp, point, normal, center, radius )
    +    prints a 2-d, 3-d, or 4-d point as 2 3-d VECT's for an imprecise point
    +*/
    +void qh_printpointvect2(FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius) {
    +  realT red[3]={1, 0, 0}, yellow[3]={1, 1, 0};
    +
    +  qh_printpointvect(fp, point, normal, center, radius, red);
    +  qh_printpointvect(fp, point, normal, center, -radius, yellow);
    +} /* printpointvect2 */
    +
    +/*---------------------------------
    +
    +  qh_printridge( fp, ridge )
    +    prints the information in a ridge
    +
    +  notes:
    +    for qh_printfacetridges()
    +    same as operator<< [QhullRidge.cpp]
    +*/
    +void qh_printridge(FILE *fp, ridgeT *ridge) {
    +
    +  qh_fprintf(fp, 9222, "     - r%d", ridge->id);
    +  if (ridge->tested)
    +    qh_fprintf(fp, 9223, " tested");
    +  if (ridge->nonconvex)
    +    qh_fprintf(fp, 9224, " nonconvex");
    +  qh_fprintf(fp, 9225, "\n");
    +  qh_printvertices(fp, "           vertices:", ridge->vertices);
    +  if (ridge->top && ridge->bottom)
    +    qh_fprintf(fp, 9226, "           between f%d and f%d\n",
    +            ridge->top->id, ridge->bottom->id);
    +} /* printridge */
    +
    +/*---------------------------------
    +
    +  qh_printspheres( fp, vertices, radius )
    +    prints 3-d vertices as OFF spheres
    +
    +  notes:
    +    inflated octahedron from Stuart Levy earth/mksphere2
    +*/
    +void qh_printspheres(FILE *fp, setT *vertices, realT radius) {
    +  vertexT *vertex, **vertexp;
    +
    +  qh printoutnum++;
    +  qh_fprintf(fp, 9227, "{appearance {-edge -normal normscale 0} {\n\
    +INST geom {define vsphere OFF\n\
    +18 32 48\n\
    +\n\
    +0 0 1\n\
    +1 0 0\n\
    +0 1 0\n\
    +-1 0 0\n\
    +0 -1 0\n\
    +0 0 -1\n\
    +0.707107 0 0.707107\n\
    +0 -0.707107 0.707107\n\
    +0.707107 -0.707107 0\n\
    +-0.707107 0 0.707107\n\
    +-0.707107 -0.707107 0\n\
    +0 0.707107 0.707107\n\
    +-0.707107 0.707107 0\n\
    +0.707107 0.707107 0\n\
    +0.707107 0 -0.707107\n\
    +0 0.707107 -0.707107\n\
    +-0.707107 0 -0.707107\n\
    +0 -0.707107 -0.707107\n\
    +\n\
    +3 0 6 11\n\
    +3 0 7 6 \n\
    +3 0 9 7 \n\
    +3 0 11 9\n\
    +3 1 6 8 \n\
    +3 1 8 14\n\
    +3 1 13 6\n\
    +3 1 14 13\n\
    +3 2 11 13\n\
    +3 2 12 11\n\
    +3 2 13 15\n\
    +3 2 15 12\n\
    +3 3 9 12\n\
    +3 3 10 9\n\
    +3 3 12 16\n\
    +3 3 16 10\n\
    +3 4 7 10\n\
    +3 4 8 7\n\
    +3 4 10 17\n\
    +3 4 17 8\n\
    +3 5 14 17\n\
    +3 5 15 14\n\
    +3 5 16 15\n\
    +3 5 17 16\n\
    +3 6 13 11\n\
    +3 7 8 6\n\
    +3 9 10 7\n\
    +3 11 12 9\n\
    +3 14 8 17\n\
    +3 15 13 14\n\
    +3 16 12 15\n\
    +3 17 10 16\n} transforms { TLIST\n");
    +  FOREACHvertex_(vertices) {
    +    qh_fprintf(fp, 9228, "%8.4g 0 0 0 # v%d\n 0 %8.4g 0 0\n0 0 %8.4g 0\n",
    +      radius, vertex->id, radius, radius);
    +    qh_printpoint3(fp, vertex->point);
    +    qh_fprintf(fp, 9229, "1\n");
    +  }
    +  qh_fprintf(fp, 9230, "}}}\n");
    +} /* printspheres */
    +
    +
    +/*----------------------------------------------
    +-printsummary-
    +                see libqhull.c
    +*/
    +
    +/*---------------------------------
    +
    +  qh_printvdiagram( fp, format, facetlist, facets, printall )
    +    print voronoi diagram
    +      # of pairs of input sites
    +      #indices site1 site2 vertex1 ...
    +
    +    sites indexed by input point id
    +      point 0 is the first input point
    +    vertices indexed by 'o' and 'p' order
    +      vertex 0 is the 'vertex-at-infinity'
    +      vertex 1 is the first Voronoi vertex
    +
    +  see:
    +    qh_printvoronoi()
    +    qh_eachvoronoi_all()
    +
    +  notes:
    +    if all facets are upperdelaunay,
    +      prints upper hull (furthest-site Voronoi diagram)
    +*/
    +void qh_printvdiagram(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices;
    +  int totcount, numcenters;
    +  boolT isLower;
    +  qh_RIDGE innerouter= qh_RIDGEall;
    +  printvridgeT printvridge= NULL;
    +
    +  if (format == qh_PRINTvertices) {
    +    innerouter= qh_RIDGEall;
    +    printvridge= qh_printvridge;
    +  }else if (format == qh_PRINTinner) {
    +    innerouter= qh_RIDGEinner;
    +    printvridge= qh_printvnorm;
    +  }else if (format == qh_PRINTouter) {
    +    innerouter= qh_RIDGEouter;
    +    printvridge= qh_printvnorm;
    +  }else {
    +    qh_fprintf(qh ferr, 6219, "Qhull internal error (qh_printvdiagram): unknown print format %d.\n", format);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  vertices= qh_markvoronoi(facetlist, facets, printall, &isLower, &numcenters);
    +  totcount= qh_printvdiagram2(NULL, NULL, vertices, innerouter, False);
    +  qh_fprintf(fp, 9231, "%d\n", totcount);
    +  totcount= qh_printvdiagram2(fp, printvridge, vertices, innerouter, True /* inorder*/);
    +  qh_settempfree(&vertices);
    +#if 0  /* for testing qh_eachvoronoi_all */
    +  qh_fprintf(fp, 9232, "\n");
    +  totcount= qh_eachvoronoi_all(fp, printvridge, qh UPPERdelaunay, innerouter, True /* inorder*/);
    +  qh_fprintf(fp, 9233, "%d\n", totcount);
    +#endif
    +} /* printvdiagram */
    +
    +/*---------------------------------
    +
    +  qh_printvdiagram2( fp, printvridge, vertices, innerouter, inorder )
    +    visit all pairs of input sites (vertices) for selected Voronoi vertices
    +    vertices may include NULLs
    +
    +  innerouter:
    +    qh_RIDGEall   print inner ridges(bounded) and outer ridges(unbounded)
    +    qh_RIDGEinner print only inner ridges
    +    qh_RIDGEouter print only outer ridges
    +
    +  inorder:
    +    print 3-d Voronoi vertices in order
    +
    +  assumes:
    +    qh_markvoronoi marked facet->visitid for Voronoi vertices
    +    all facet->seen= False
    +    all facet->seen2= True
    +
    +  returns:
    +    total number of Voronoi ridges
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers) for each ridge
    +      [see qh_eachvoronoi()]
    +
    +  see:
    +    qh_eachvoronoi_all()
    +*/
    +int qh_printvdiagram2(FILE *fp, printvridgeT printvridge, setT *vertices, qh_RIDGE innerouter, boolT inorder) {
    +  int totcount= 0;
    +  int vertex_i, vertex_n;
    +  vertexT *vertex;
    +
    +  FORALLvertices
    +    vertex->seen= False;
    +  FOREACHvertex_i_(vertices) {
    +    if (vertex) {
    +      if (qh GOODvertex > 0 && qh_pointid(vertex->point)+1 != qh GOODvertex)
    +        continue;
    +      totcount += qh_eachvoronoi(fp, printvridge, vertex, !qh_ALL, innerouter, inorder);
    +    }
    +  }
    +  return totcount;
    +} /* printvdiagram2 */
    +
    +/*---------------------------------
    +
    +  qh_printvertex( fp, vertex )
    +    prints the information in a vertex
    +    Duplicated as operator<< [QhullVertex.cpp]
    +*/
    +void qh_printvertex(FILE *fp, vertexT *vertex) {
    +  pointT *point;
    +  int k, count= 0;
    +  facetT *neighbor, **neighborp;
    +  realT r; /*bug fix*/
    +
    +  if (!vertex) {
    +    qh_fprintf(fp, 9234, "  NULLvertex\n");
    +    return;
    +  }
    +  qh_fprintf(fp, 9235, "- p%d(v%d):", qh_pointid(vertex->point), vertex->id);
    +  point= vertex->point;
    +  if (point) {
    +    for (k=qh hull_dim; k--; ) {
    +      r= *point++;
    +      qh_fprintf(fp, 9236, " %5.2g", r);
    +    }
    +  }
    +  if (vertex->deleted)
    +    qh_fprintf(fp, 9237, " deleted");
    +  if (vertex->delridge)
    +    qh_fprintf(fp, 9238, " ridgedeleted");
    +  qh_fprintf(fp, 9239, "\n");
    +  if (vertex->neighbors) {
    +    qh_fprintf(fp, 9240, "  neighbors:");
    +    FOREACHneighbor_(vertex) {
    +      if (++count % 100 == 0)
    +        qh_fprintf(fp, 9241, "\n     ");
    +      qh_fprintf(fp, 9242, " f%d", neighbor->id);
    +    }
    +    qh_fprintf(fp, 9243, "\n");
    +  }
    +} /* printvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_printvertexlist( fp, string, facetlist, facets, printall )
    +    prints vertices used by a facetlist or facet set
    +    tests qh_skipfacet() if !printall
    +*/
    +void qh_printvertexlist(FILE *fp, const char* string, facetT *facetlist,
    +                         setT *facets, boolT printall) {
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  qh_fprintf(fp, 9244, "%s", string);
    +  FOREACHvertex_(vertices)
    +    qh_printvertex(fp, vertex);
    +  qh_settempfree(&vertices);
    +} /* printvertexlist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printvertices( fp, string, vertices )
    +    prints vertices in a set
    +    duplicated as printVertexSet [QhullVertex.cpp]
    +*/
    +void qh_printvertices(FILE *fp, const char* string, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  qh_fprintf(fp, 9245, "%s", string);
    +  FOREACHvertex_(vertices)
    +    qh_fprintf(fp, 9246, " p%d(v%d)", qh_pointid(vertex->point), vertex->id);
    +  qh_fprintf(fp, 9247, "\n");
    +} /* printvertices */
    +
    +/*---------------------------------
    +
    +  qh_printvneighbors( fp, facetlist, facets, printall )
    +    print vertex neighbors of vertices in facetlist and facets ('FN')
    +
    +  notes:
    +    qh_countfacets clears facet->visitid for non-printed facets
    +
    +  design:
    +    collect facet count and related statistics
    +    if necessary, build neighbor sets for each vertex
    +    collect vertices in facetlist and facets
    +    build a point array for point->vertex and point->coplanar facet
    +    for each point
    +      list vertex neighbors or coplanar facet
    +*/
    +void qh_printvneighbors(FILE *fp, facetT* facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numneighbors, numcoplanars, numtricoplanars;
    +  setT *vertices, *vertex_points, *coplanar_points;
    +  int numpoints= qh num_points + qh_setsize(qh other_points);
    +  vertexT *vertex, **vertexp;
    +  int vertex_i, vertex_n;
    +  facetT *facet, **facetp, *neighbor, **neighborp;
    +  pointT *point, **pointp;
    +
    +  qh_countfacets(facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);  /* sets facet->visitid */
    +  qh_fprintf(fp, 9248, "%d\n", numpoints);
    +  qh_vertexneighbors();
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  vertex_points= qh_settemp(numpoints);
    +  coplanar_points= qh_settemp(numpoints);
    +  qh_setzero(vertex_points, 0, numpoints);
    +  qh_setzero(coplanar_points, 0, numpoints);
    +  FOREACHvertex_(vertices)
    +    qh_point_add(vertex_points, vertex->point, vertex);
    +  FORALLfacet_(facetlist) {
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(coplanar_points, point, facet);
    +  }
    +  FOREACHfacet_(facets) {
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(coplanar_points, point, facet);
    +  }
    +  FOREACHvertex_i_(vertex_points) {
    +    if (vertex) {
    +      numneighbors= qh_setsize(vertex->neighbors);
    +      qh_fprintf(fp, 9249, "%d", numneighbors);
    +      if (qh hull_dim == 3)
    +        qh_order_vertexneighbors(vertex);
    +      else if (qh hull_dim >= 4)
    +        qsort(SETaddr_(vertex->neighbors, facetT), (size_t)numneighbors,
    +             sizeof(facetT *), qh_compare_facetvisit);
    +      FOREACHneighbor_(vertex)
    +        qh_fprintf(fp, 9250, " %d",
    +                 neighbor->visitid ? neighbor->visitid - 1 : 0 - neighbor->id);
    +      qh_fprintf(fp, 9251, "\n");
    +    }else if ((facet= SETelemt_(coplanar_points, vertex_i, facetT)))
    +      qh_fprintf(fp, 9252, "1 %d\n",
    +                  facet->visitid ? facet->visitid - 1 : 0 - facet->id);
    +    else
    +      qh_fprintf(fp, 9253, "0\n");
    +  }
    +  qh_settempfree(&coplanar_points);
    +  qh_settempfree(&vertex_points);
    +  qh_settempfree(&vertices);
    +} /* printvneighbors */
    +
    +/*---------------------------------
    +
    +  qh_printvoronoi( fp, format, facetlist, facets, printall )
    +    print voronoi diagram in 'o' or 'G' format
    +    for 'o' format
    +      prints voronoi centers for each facet and for infinity
    +      for each vertex, lists ids of printed facets or infinity
    +      assumes facetlist and facets are disjoint
    +    for 'G' format
    +      prints an OFF object
    +      adds a 0 coordinate to center
    +      prints infinity but does not list in vertices
    +
    +  see:
    +    qh_printvdiagram()
    +
    +  notes:
    +    if 'o',
    +      prints a line for each point except "at-infinity"
    +    if all facets are upperdelaunay,
    +      reverses lower and upper hull
    +*/
    +void qh_printvoronoi(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int k, numcenters, numvertices= 0, numneighbors, numinf, vid=1, vertex_i, vertex_n;
    +  facetT *facet, **facetp, *neighbor, **neighborp;
    +  setT *vertices;
    +  vertexT *vertex;
    +  boolT isLower;
    +  unsigned int numfacets= (unsigned int) qh num_facets;
    +
    +  vertices= qh_markvoronoi(facetlist, facets, printall, &isLower, &numcenters);
    +  FOREACHvertex_i_(vertices) {
    +    if (vertex) {
    +      numvertices++;
    +      numneighbors = numinf = 0;
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visitid == 0)
    +          numinf= 1;
    +        else if (neighbor->visitid < numfacets)
    +          numneighbors++;
    +      }
    +      if (numinf && !numneighbors) {
    +        SETelem_(vertices, vertex_i)= NULL;
    +        numvertices--;
    +      }
    +    }
    +  }
    +  if (format == qh_PRINTgeom)
    +    qh_fprintf(fp, 9254, "{appearance {+edge -face} OFF %d %d 1 # Voronoi centers and cells\n",
    +                numcenters, numvertices);
    +  else
    +    qh_fprintf(fp, 9255, "%d\n%d %d 1\n", qh hull_dim-1, numcenters, qh_setsize(vertices));
    +  if (format == qh_PRINTgeom) {
    +    for (k=qh hull_dim-1; k--; )
    +      qh_fprintf(fp, 9256, qh_REAL_1, 0.0);
    +    qh_fprintf(fp, 9257, " 0 # infinity not used\n");
    +  }else {
    +    for (k=qh hull_dim-1; k--; )
    +      qh_fprintf(fp, 9258, qh_REAL_1, qh_INFINITE);
    +    qh_fprintf(fp, 9259, "\n");
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->visitid && facet->visitid < numfacets) {
    +      if (format == qh_PRINTgeom)
    +        qh_fprintf(fp, 9260, "# %d f%d\n", vid++, facet->id);
    +      qh_printcenter(fp, format, NULL, facet);
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (facet->visitid && facet->visitid < numfacets) {
    +      if (format == qh_PRINTgeom)
    +        qh_fprintf(fp, 9261, "# %d f%d\n", vid++, facet->id);
    +      qh_printcenter(fp, format, NULL, facet);
    +    }
    +  }
    +  FOREACHvertex_i_(vertices) {
    +    numneighbors= 0;
    +    numinf=0;
    +    if (vertex) {
    +      if (qh hull_dim == 3)
    +        qh_order_vertexneighbors(vertex);
    +      else if (qh hull_dim >= 4)
    +        qsort(SETaddr_(vertex->neighbors, facetT),
    +             (size_t)qh_setsize(vertex->neighbors),
    +             sizeof(facetT *), qh_compare_facetvisit);
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visitid == 0)
    +          numinf= 1;
    +        else if (neighbor->visitid < numfacets)
    +          numneighbors++;
    +      }
    +    }
    +    if (format == qh_PRINTgeom) {
    +      if (vertex) {
    +        qh_fprintf(fp, 9262, "%d", numneighbors);
    +        FOREACHneighbor_(vertex) {
    +          if (neighbor->visitid && neighbor->visitid < numfacets)
    +            qh_fprintf(fp, 9263, " %d", neighbor->visitid);
    +        }
    +        qh_fprintf(fp, 9264, " # p%d(v%d)\n", vertex_i, vertex->id);
    +      }else
    +        qh_fprintf(fp, 9265, " # p%d is coplanar or isolated\n", vertex_i);
    +    }else {
    +      if (numinf)
    +        numneighbors++;
    +      qh_fprintf(fp, 9266, "%d", numneighbors);
    +      if (vertex) {
    +        FOREACHneighbor_(vertex) {
    +          if (neighbor->visitid == 0) {
    +            if (numinf) {
    +              numinf= 0;
    +              qh_fprintf(fp, 9267, " %d", neighbor->visitid);
    +            }
    +          }else if (neighbor->visitid < numfacets)
    +            qh_fprintf(fp, 9268, " %d", neighbor->visitid);
    +        }
    +      }
    +      qh_fprintf(fp, 9269, "\n");
    +    }
    +  }
    +  if (format == qh_PRINTgeom)
    +    qh_fprintf(fp, 9270, "}\n");
    +  qh_settempfree(&vertices);
    +} /* printvoronoi */
    +
    +/*---------------------------------
    +
    +  qh_printvnorm( fp, vertex, vertexA, centers, unbounded )
    +    print one separating plane of the Voronoi diagram for a pair of input sites
    +    unbounded==True if centers includes vertex-at-infinity
    +
    +  assumes:
    +    qh_ASvoronoi and qh_vertexneighbors() already set
    +
    +  note:
    +    parameter unbounded is UNUSED by this callback
    +
    +  see:
    +    qh_printvdiagram()
    +    qh_eachvoronoi()
    +*/
    +void qh_printvnorm(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded) {
    +  pointT *normal;
    +  realT offset;
    +  int k;
    +  QHULL_UNUSED(unbounded);
    +
    +  normal= qh_detvnorm(vertex, vertexA, centers, &offset);
    +  qh_fprintf(fp, 9271, "%d %d %d ",
    +      2+qh hull_dim, qh_pointid(vertex->point), qh_pointid(vertexA->point));
    +  for (k=0; k< qh hull_dim-1; k++)
    +    qh_fprintf(fp, 9272, qh_REAL_1, normal[k]);
    +  qh_fprintf(fp, 9273, qh_REAL_1, offset);
    +  qh_fprintf(fp, 9274, "\n");
    +} /* printvnorm */
    +
    +/*---------------------------------
    +
    +  qh_printvridge( fp, vertex, vertexA, centers, unbounded )
    +    print one ridge of the Voronoi diagram for a pair of input sites
    +    unbounded==True if centers includes vertex-at-infinity
    +
    +  see:
    +    qh_printvdiagram()
    +
    +  notes:
    +    the user may use a different function
    +    parameter unbounded is UNUSED
    +*/
    +void qh_printvridge(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded) {
    +  facetT *facet, **facetp;
    +  QHULL_UNUSED(unbounded);
    +
    +  qh_fprintf(fp, 9275, "%d %d %d", qh_setsize(centers)+2,
    +       qh_pointid(vertex->point), qh_pointid(vertexA->point));
    +  FOREACHfacet_(centers)
    +    qh_fprintf(fp, 9276, " %d", facet->visitid);
    +  qh_fprintf(fp, 9277, "\n");
    +} /* printvridge */
    +
    +/*---------------------------------
    +
    +  qh_projectdim3( source, destination )
    +    project 2-d 3-d or 4-d point to a 3-d point
    +    uses qh.DROPdim and qh.hull_dim
    +    source and destination may be the same
    +
    +  notes:
    +    allocate 4 elements to destination just in case
    +*/
    +void qh_projectdim3(pointT *source, pointT *destination) {
    +  int i,k;
    +
    +  for (k=0, i=0; k < qh hull_dim; k++) {
    +    if (qh hull_dim == 4) {
    +      if (k != qh DROPdim)
    +        destination[i++]= source[k];
    +    }else if (k == qh DROPdim)
    +      destination[i++]= 0;
    +    else
    +      destination[i++]= source[k];
    +  }
    +  while (i < 3)
    +    destination[i++]= 0.0;
    +} /* projectdim3 */
    +
    +/*---------------------------------
    +
    +  qh_readfeasible( dim, curline )
    +    read feasible point from current line and qh.fin
    +
    +  returns:
    +    number of lines read from qh.fin
    +    sets qh.feasible_point with malloc'd coordinates
    +
    +  notes:
    +    checks for qh.HALFspace
    +    assumes dim > 1
    +
    +  see:
    +    qh_setfeasible
    +*/
    +int qh_readfeasible(int dim, const char *curline) {
    +  boolT isfirst= True;
    +  int linecount= 0, tokcount= 0;
    +  const char *s;
    +  char *t, firstline[qh_MAXfirst+1];
    +  coordT *coords, value;
    +
    +  if (!qh HALFspace) {
    +    qh_fprintf(qh ferr, 6070, "qhull input error: feasible point(dim 1 coords) is only valid for halfspace intersection\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh feasible_string)
    +    qh_fprintf(qh ferr, 7057, "qhull input warning: feasible point(dim 1 coords) overrides 'Hn,n,n' feasible point for halfspace intersection\n");
    +  if (!(qh feasible_point= (coordT*)qh_malloc(dim* sizeof(coordT)))) {
    +    qh_fprintf(qh ferr, 6071, "qhull error: insufficient memory for feasible point\n");
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  coords= qh feasible_point;
    +  while ((s= (isfirst ?  curline : fgets(firstline, qh_MAXfirst, qh fin)))) {
    +    if (isfirst)
    +      isfirst= False;
    +    else
    +      linecount++;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      value= qh_strtod(s, &t);
    +      if (s == t)
    +        break;
    +      s= t;
    +      *(coords++)= value;
    +      if (++tokcount == dim) {
    +        while (isspace(*s))
    +          s++;
    +        qh_strtod(s, &t);
    +        if (s != t) {
    +          qh_fprintf(qh ferr, 6072, "qhull input error: coordinates for feasible point do not finish out the line: %s\n",
    +               s);
    +          qh_errexit(qh_ERRinput, NULL, NULL);
    +        }
    +        return linecount;
    +      }
    +    }
    +  }
    +  qh_fprintf(qh ferr, 6073, "qhull input error: only %d coordinates.  Could not read %d-d feasible point.\n",
    +           tokcount, dim);
    +  qh_errexit(qh_ERRinput, NULL, NULL);
    +  return 0;
    +} /* readfeasible */
    +
    +/*---------------------------------
    +
    +  qh_readpoints( numpoints, dimension, ismalloc )
    +    read points from qh.fin into qh.first_point, qh.num_points
    +    qh.fin is lines of coordinates, one per vertex, first line number of points
    +    if 'rbox D4',
    +      gives message
    +    if qh.ATinfinity,
    +      adds point-at-infinity for Delaunay triangulations
    +
    +  returns:
    +    number of points, array of point coordinates, dimension, ismalloc True
    +    if qh.DELAUNAY & !qh.PROJECTinput, projects points to paraboloid
    +        and clears qh.PROJECTdelaunay
    +    if qh.HALFspace, reads optional feasible point, reads halfspaces,
    +        converts to dual.
    +
    +  for feasible point in "cdd format" in 3-d:
    +    3 1
    +    coordinates
    +    comments
    +    begin
    +    n 4 real/integer
    +    ...
    +    end
    +
    +  notes:
    +    dimension will change in qh_initqhull_globals if qh.PROJECTinput
    +    uses malloc() since qh_mem not initialized
    +    FIXUP QH11012: qh_readpoints needs rewriting, too long
    +*/
    +coordT *qh_readpoints(int *numpoints, int *dimension, boolT *ismalloc) {
    +  coordT *points, *coords, *infinity= NULL;
    +  realT paraboloid, maxboloid= -REALmax, value;
    +  realT *coordp= NULL, *offsetp= NULL, *normalp= NULL;
    +  char *s= 0, *t, firstline[qh_MAXfirst+1];
    +  int diminput=0, numinput=0, dimfeasible= 0, newnum, k, tempi;
    +  int firsttext=0, firstshort=0, firstlong=0, firstpoint=0;
    +  int tokcount= 0, linecount=0, maxcount, coordcount=0;
    +  boolT islong, isfirst= True, wasbegin= False;
    +  boolT isdelaunay= qh DELAUNAY && !qh PROJECTinput;
    +
    +  if (qh CDDinput) {
    +    while ((s= fgets(firstline, qh_MAXfirst, qh fin))) {
    +      linecount++;
    +      if (qh HALFspace && linecount == 1 && isdigit(*s)) {
    +        dimfeasible= qh_strtol(s, &s);
    +        while (isspace(*s))
    +          s++;
    +        if (qh_strtol(s, &s) == 1)
    +          linecount += qh_readfeasible(dimfeasible, s);
    +        else
    +          dimfeasible= 0;
    +      }else if (!memcmp(firstline, "begin", (size_t)5) || !memcmp(firstline, "BEGIN", (size_t)5))
    +        break;
    +      else if (!*qh rbox_command)
    +        strncat(qh rbox_command, s, sizeof(qh rbox_command)-1);
    +    }
    +    if (!s) {
    +      qh_fprintf(qh ferr, 6074, "qhull input error: missing \"begin\" for cdd-formated input\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +  }
    +  while (!numinput && (s= fgets(firstline, qh_MAXfirst, qh fin))) {
    +    linecount++;
    +    if (!memcmp(s, "begin", (size_t)5) || !memcmp(s, "BEGIN", (size_t)5))
    +      wasbegin= True;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      if (!*s)
    +        break;
    +      if (!isdigit(*s)) {
    +        if (!*qh rbox_command) {
    +          strncat(qh rbox_command, s, sizeof(qh rbox_command)-1);
    +          firsttext= linecount;
    +        }
    +        break;
    +      }
    +      if (!diminput)
    +        diminput= qh_strtol(s, &s);
    +      else {
    +        numinput= qh_strtol(s, &s);
    +        if (numinput == 1 && diminput >= 2 && qh HALFspace && !qh CDDinput) {
    +          linecount += qh_readfeasible(diminput, s); /* checks if ok */
    +          dimfeasible= diminput;
    +          diminput= numinput= 0;
    +        }else
    +          break;
    +      }
    +    }
    +  }
    +  if (!s) {
    +    qh_fprintf(qh ferr, 6075, "qhull input error: short input file.  Did not find dimension and number of points\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (diminput > numinput) {
    +    tempi= diminput;    /* exchange dim and n, e.g., for cdd input format */
    +    diminput= numinput;
    +    numinput= tempi;
    +  }
    +  if (diminput < 2) {
    +    qh_fprintf(qh ferr, 6220,"qhull input error: dimension %d(first number) should be at least 2\n",
    +            diminput);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (isdelaunay) {
    +    qh PROJECTdelaunay= False;
    +    if (qh CDDinput)
    +      *dimension= diminput;
    +    else
    +      *dimension= diminput+1;
    +    *numpoints= numinput;
    +    if (qh ATinfinity)
    +      (*numpoints)++;
    +  }else if (qh HALFspace) {
    +    *dimension= diminput - 1;
    +    *numpoints= numinput;
    +    if (diminput < 3) {
    +      qh_fprintf(qh ferr, 6221,"qhull input error: dimension %d(first number, includes offset) should be at least 3 for halfspaces\n",
    +            diminput);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (dimfeasible) {
    +      if (dimfeasible != *dimension) {
    +        qh_fprintf(qh ferr, 6222,"qhull input error: dimension %d of feasible point is not one less than dimension %d for halfspaces\n",
    +          dimfeasible, diminput);
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +    }else
    +      qh_setfeasible(*dimension);
    +  }else {
    +    if (qh CDDinput)
    +      *dimension= diminput-1;
    +    else
    +      *dimension= diminput;
    +    *numpoints= numinput;
    +  }
    +  qh normal_size= *dimension * sizeof(coordT); /* for tracing with qh_printpoint */
    +  if (qh HALFspace) {
    +    qh half_space= coordp= (coordT*)qh_malloc(qh normal_size + sizeof(coordT));
    +    if (qh CDDinput) {
    +      offsetp= qh half_space;
    +      normalp= offsetp + 1;
    +    }else {
    +      normalp= qh half_space;
    +      offsetp= normalp + *dimension;
    +    }
    +  }
    +  qh maxline= diminput * (qh_REALdigits + 5);
    +  maximize_(qh maxline, 500);
    +  qh line= (char*)qh_malloc((qh maxline+1) * sizeof(char));
    +  *ismalloc= True;  /* use malloc since memory not setup */
    +  coords= points= qh temp_malloc=  /* numinput and diminput >=2 by QH6220 */
    +        (coordT*)qh_malloc((*numpoints)*(*dimension)*sizeof(coordT));
    +  if (!coords || !qh line || (qh HALFspace && !qh half_space)) {
    +    qh_fprintf(qh ferr, 6076, "qhull error: insufficient memory to read %d points\n",
    +            numinput);
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  if (isdelaunay && qh ATinfinity) {
    +    infinity= points + numinput * (*dimension);
    +    for (k= (*dimension) - 1; k--; )
    +      infinity[k]= 0.0;
    +  }
    +  maxcount= numinput * diminput;
    +  paraboloid= 0.0;
    +  while ((s= (isfirst ?  s : fgets(qh line, qh maxline, qh fin)))) {
    +    if (!isfirst) {
    +      linecount++;
    +      if (*s == 'e' || *s == 'E') {
    +        if (!memcmp(s, "end", (size_t)3) || !memcmp(s, "END", (size_t)3)) {
    +          if (qh CDDinput )
    +            break;
    +          else if (wasbegin)
    +            qh_fprintf(qh ferr, 7058, "qhull input warning: the input appears to be in cdd format.  If so, use 'Fd'\n");
    +        }
    +      }
    +    }
    +    islong= False;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      value= qh_strtod(s, &t);
    +      if (s == t) {
    +        if (!*qh rbox_command)
    +         strncat(qh rbox_command, s, sizeof(qh rbox_command)-1);
    +        if (*s && !firsttext)
    +          firsttext= linecount;
    +        if (!islong && !firstshort && coordcount)
    +          firstshort= linecount;
    +        break;
    +      }
    +      if (!firstpoint)
    +        firstpoint= linecount;
    +      s= t;
    +      if (++tokcount > maxcount)
    +        continue;
    +      if (qh HALFspace) {
    +        if (qh CDDinput)
    +          *(coordp++)= -value; /* both coefficients and offset */
    +        else
    +          *(coordp++)= value;
    +      }else {
    +        *(coords++)= value;
    +        if (qh CDDinput && !coordcount) {
    +          if (value != 1.0) {
    +            qh_fprintf(qh ferr, 6077, "qhull input error: for cdd format, point at line %d does not start with '1'\n",
    +                   linecount);
    +            qh_errexit(qh_ERRinput, NULL, NULL);
    +          }
    +          coords--;
    +        }else if (isdelaunay) {
    +          paraboloid += value * value;
    +          if (qh ATinfinity) {
    +            if (qh CDDinput)
    +              infinity[coordcount-1] += value;
    +            else
    +              infinity[coordcount] += value;
    +          }
    +        }
    +      }
    +      if (++coordcount == diminput) {
    +        coordcount= 0;
    +        if (isdelaunay) {
    +          *(coords++)= paraboloid;
    +          maximize_(maxboloid, paraboloid);
    +          paraboloid= 0.0;
    +        }else if (qh HALFspace) {
    +          if (!qh_sethalfspace(*dimension, coords, &coords, normalp, offsetp, qh feasible_point)) {
    +            qh_fprintf(qh ferr, 8048, "The halfspace was on line %d\n", linecount);
    +            if (wasbegin)
    +              qh_fprintf(qh ferr, 8049, "The input appears to be in cdd format.  If so, you should use option 'Fd'\n");
    +            qh_errexit(qh_ERRinput, NULL, NULL);
    +          }
    +          coordp= qh half_space;
    +        }
    +        while (isspace(*s))
    +          s++;
    +        if (*s) {
    +          islong= True;
    +          if (!firstlong)
    +            firstlong= linecount;
    +        }
    +      }
    +    }
    +    if (!islong && !firstshort && coordcount)
    +      firstshort= linecount;
    +    if (!isfirst && s - qh line >= qh maxline) {
    +      qh_fprintf(qh ferr, 6078, "qhull input error: line %d contained more than %d characters\n",
    +              linecount, (int) (s - qh line));   /* WARN64 */
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    isfirst= False;
    +  }
    +  if (tokcount != maxcount) {
    +    newnum= fmin_(numinput, tokcount/diminput);
    +    qh_fprintf(qh ferr, 7073,"\
    +qhull warning: instead of %d %d-dimensional points, input contains\n\
    +%d points and %d extra coordinates.  Line %d is the first\npoint",
    +       numinput, diminput, tokcount/diminput, tokcount % diminput, firstpoint);
    +    if (firsttext)
    +      qh_fprintf(qh ferr, 8051, ", line %d is the first comment", firsttext);
    +    if (firstshort)
    +      qh_fprintf(qh ferr, 8052, ", line %d is the first short\nline", firstshort);
    +    if (firstlong)
    +      qh_fprintf(qh ferr, 8053, ", line %d is the first long line", firstlong);
    +    qh_fprintf(qh ferr, 8054, ".  Continue with %d points.\n", newnum);
    +    numinput= newnum;
    +    if (isdelaunay && qh ATinfinity) {
    +      for (k= tokcount % diminput; k--; )
    +        infinity[k] -= *(--coords);
    +      *numpoints= newnum+1;
    +    }else {
    +      coords -= tokcount % diminput;
    +      *numpoints= newnum;
    +    }
    +  }
    +  if (isdelaunay && qh ATinfinity) {
    +    for (k= (*dimension) -1; k--; )
    +      infinity[k] /= numinput;
    +    if (coords == infinity)
    +      coords += (*dimension) -1;
    +    else {
    +      for (k=0; k < (*dimension) -1; k++)
    +        *(coords++)= infinity[k];
    +    }
    +    *(coords++)= maxboloid * 1.1;
    +  }
    +  if (qh rbox_command[0]) {
    +    qh rbox_command[strlen(qh rbox_command)-1]= '\0';
    +    if (!strcmp(qh rbox_command, "./rbox D4"))
    +      qh_fprintf(qh ferr, 8055, "\n\
    +This is the qhull test case.  If any errors or core dumps occur,\n\
    +recompile qhull with 'make new'.  If errors still occur, there is\n\
    +an incompatibility.  You should try a different compiler.  You can also\n\
    +change the choices in user.h.  If you discover the source of the problem,\n\
    +please send mail to qhull_bug@qhull.org.\n\
    +\n\
    +Type 'qhull' for a short list of options.\n");
    +  }
    +  qh_free(qh line);
    +  qh line= NULL;
    +  if (qh half_space) {
    +    qh_free(qh half_space);
    +    qh half_space= NULL;
    +  }
    +  qh temp_malloc= NULL;
    +  trace1((qh ferr, 1008,"qh_readpoints: read in %d %d-dimensional points\n",
    +          numinput, diminput));
    +  return(points);
    +} /* readpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfeasible( dim )
    +    set qh.feasible_point from qh.feasible_string in "n,n,n" or "n n n" format
    +
    +  notes:
    +    "n,n,n" already checked by qh_initflags()
    +    see qh_readfeasible()
    +    called only once from qh_new_qhull, otherwise leaks memory
    +*/
    +void qh_setfeasible(int dim) {
    +  int tokcount= 0;
    +  char *s;
    +  coordT *coords, value;
    +
    +  if (!(s= qh feasible_string)) {
    +    qh_fprintf(qh ferr, 6223, "\
    +qhull input error: halfspace intersection needs a feasible point.\n\
    +Either prepend the input with 1 point or use 'Hn,n,n'.  See manual.\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (!(qh feasible_point= (pointT*)qh_malloc(dim * sizeof(coordT)))) {
    +    qh_fprintf(qh ferr, 6079, "qhull error: insufficient memory for 'Hn,n,n'\n");
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  coords= qh feasible_point;
    +  while (*s) {
    +    value= qh_strtod(s, &s);
    +    if (++tokcount > dim) {
    +      qh_fprintf(qh ferr, 7059, "qhull input warning: more coordinates for 'H%s' than dimension %d\n",
    +          qh feasible_string, dim);
    +      break;
    +    }
    +    *(coords++)= value;
    +    if (*s)
    +      s++;
    +  }
    +  while (++tokcount <= dim)
    +    *(coords++)= 0.0;
    +} /* setfeasible */
    +
    +/*---------------------------------
    +
    +  qh_skipfacet( facet )
    +    returns 'True' if this facet is not to be printed
    +
    +  notes:
    +    based on the user provided slice thresholds and 'good' specifications
    +*/
    +boolT qh_skipfacet(facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +
    +  if (qh PRINTneighbors) {
    +    if (facet->good)
    +      return !qh PRINTgood;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->good)
    +        return False;
    +    }
    +    return True;
    +  }else if (qh PRINTgood)
    +    return !facet->good;
    +  else if (!facet->normal)
    +    return True;
    +  return(!qh_inthresholds(facet->normal, NULL));
    +} /* skipfacet */
    +
    +/*---------------------------------
    +
    +  qh_skipfilename( string )
    +    returns pointer to character after filename
    +
    +  notes:
    +    skips leading spaces
    +    ends with spacing or eol
    +    if starts with ' or " ends with the same, skipping \' or \"
    +    For qhull, qh_argv_to_command() only uses double quotes
    +*/
    +char *qh_skipfilename(char *filename) {
    +  char *s= filename;  /* non-const due to return */
    +  char c;
    +
    +  while (*s && isspace(*s))
    +    s++;
    +  c= *s++;
    +  if (c == '\0') {
    +    qh_fprintf(qh ferr, 6204, "qhull input error: filename expected, none found.\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (c == '\'' || c == '"') {
    +    while (*s !=c || s[-1] == '\\') {
    +      if (!*s) {
    +        qh_fprintf(qh ferr, 6203, "qhull input error: missing quote after filename -- %s\n", filename);
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +      s++;
    +    }
    +    s++;
    +  }
    +  else while (*s && !isspace(*s))
    +      s++;
    +  return s;
    +} /* skipfilename */
    +
    diff --git a/xs/src/qhull/src/libqhull/io.h b/xs/src/qhull/src/libqhull/io.h
    new file mode 100644
    index 0000000000..eca0369d30
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/io.h
    @@ -0,0 +1,159 @@
    +/*
      ---------------------------------
    +
    +   io.h
    +   declarations of Input/Output functions
    +
    +   see README, libqhull.h and io.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/io.h#1 $$Change: 1981 $
    +   $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFio
    +#define qhDEFio 1
    +
    +#include "libqhull.h"
    +
    +/*============ constants and flags ==================*/
    +
    +/*----------------------------------
    +
    +  qh_MAXfirst
    +    maximum length of first two lines of stdin
    +*/
    +#define qh_MAXfirst  200
    +
    +/*----------------------------------
    +
    +  qh_MINradius
    +    min radius for Gp and Gv, fraction of maxcoord
    +*/
    +#define qh_MINradius 0.02
    +
    +/*----------------------------------
    +
    +  qh_GEOMepsilon
    +    adjust outer planes for 'lines closer' and geomview roundoff.
    +    This prevents bleed through.
    +*/
    +#define qh_GEOMepsilon 2e-3
    +
    +/*----------------------------------
    +
    +  qh_WHITESPACE
    +    possible values of white space
    +*/
    +#define qh_WHITESPACE " \n\t\v\r\f"
    +
    +
    +/*----------------------------------
    +
    +  qh_RIDGE
    +    to select which ridges to print in qh_eachvoronoi
    +*/
    +typedef enum
    +{
    +    qh_RIDGEall = 0, qh_RIDGEinner, qh_RIDGEouter
    +}
    +qh_RIDGE;
    +
    +/*----------------------------------
    +
    +  printvridgeT
    +    prints results of qh_printvdiagram
    +
    +  see:
    +    qh_printvridge for an example
    +*/
    +typedef void (*printvridgeT)(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +
    +/*============== -prototypes in alphabetical order =========*/
    +
    +void    qh_dfacet(unsigned id);
    +void    qh_dvertex(unsigned id);
    +int     qh_compare_facetarea(const void *p1, const void *p2);
    +int     qh_compare_facetmerge(const void *p1, const void *p2);
    +int     qh_compare_facetvisit(const void *p1, const void *p2);
    +int     qh_compare_vertexpoint(const void *p1, const void *p2); /* not used, not in libqhull_r.h */
    +void    qh_copyfilename(char *filename, int size, const char* source, int length);
    +void    qh_countfacets(facetT *facetlist, setT *facets, boolT printall,
    +              int *numfacetsp, int *numsimplicialp, int *totneighborsp,
    +              int *numridgesp, int *numcoplanarsp, int *numnumtricoplanarsp);
    +pointT *qh_detvnorm(vertexT *vertex, vertexT *vertexA, setT *centers, realT *offsetp);
    +setT   *qh_detvridge(vertexT *vertex);
    +setT   *qh_detvridge3(vertexT *atvertex, vertexT *vertex);
    +int     qh_eachvoronoi(FILE *fp, printvridgeT printvridge, vertexT *atvertex, boolT visitall, qh_RIDGE innerouter, boolT inorder);
    +int     qh_eachvoronoi_all(FILE *fp, printvridgeT printvridge, boolT isUpper, qh_RIDGE innerouter, boolT inorder);
    +void    qh_facet2point(facetT *facet, pointT **point0, pointT **point1, realT *mindist);
    +setT   *qh_facetvertices(facetT *facetlist, setT *facets, boolT allfacets);
    +void    qh_geomplanes(facetT *facet, realT *outerplane, realT *innerplane);
    +void    qh_markkeep(facetT *facetlist);
    +setT   *qh_markvoronoi(facetT *facetlist, setT *facets, boolT printall, boolT *isLowerp, int *numcentersp);
    +void    qh_order_vertexneighbors(vertexT *vertex);
    +void    qh_prepare_output(void);
    +void    qh_printafacet(FILE *fp, qh_PRINT format, facetT *facet, boolT printall);
    +void    qh_printbegin(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printcenter(FILE *fp, qh_PRINT format, const char *string, facetT *facet);
    +void    qh_printcentrum(FILE *fp, facetT *facet, realT radius);
    +void    qh_printend(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printend4geom(FILE *fp, facetT *facet, int *num, boolT printall);
    +void    qh_printextremes(FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printextremes_2d(FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printextremes_d(FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printfacet(FILE *fp, facetT *facet);
    +void    qh_printfacet2math(FILE *fp, facetT *facet, qh_PRINT format, int notfirst);
    +void    qh_printfacet2geom(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet2geom_points(FILE *fp, pointT *point1, pointT *point2,
    +                               facetT *facet, realT offset, realT color[3]);
    +void    qh_printfacet3math(FILE *fp, facetT *facet, qh_PRINT format, int notfirst);
    +void    qh_printfacet3geom_nonsimplicial(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet3geom_points(FILE *fp, setT *points, facetT *facet, realT offset, realT color[3]);
    +void    qh_printfacet3geom_simplicial(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet3vertex(FILE *fp, facetT *facet, qh_PRINT format);
    +void    qh_printfacet4geom_nonsimplicial(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet4geom_simplicial(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacetNvertex_nonsimplicial(FILE *fp, facetT *facet, int id, qh_PRINT format);
    +void    qh_printfacetNvertex_simplicial(FILE *fp, facetT *facet, qh_PRINT format);
    +void    qh_printfacetheader(FILE *fp, facetT *facet);
    +void    qh_printfacetridges(FILE *fp, facetT *facet);
    +void    qh_printfacets(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printhyperplaneintersection(FILE *fp, facetT *facet1, facetT *facet2,
    +                   setT *vertices, realT color[3]);
    +void    qh_printneighborhood(FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall);
    +void    qh_printline3geom(FILE *fp, pointT *pointA, pointT *pointB, realT color[3]);
    +void    qh_printpoint(FILE *fp, const char *string, pointT *point);
    +void    qh_printpointid(FILE *fp, const char *string, int dim, pointT *point, int id);
    +void    qh_printpoint3(FILE *fp, pointT *point);
    +void    qh_printpoints_out(FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printpointvect(FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius, realT color[3]);
    +void    qh_printpointvect2(FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius);
    +void    qh_printridge(FILE *fp, ridgeT *ridge);
    +void    qh_printspheres(FILE *fp, setT *vertices, realT radius);
    +void    qh_printvdiagram(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +int     qh_printvdiagram2(FILE *fp, printvridgeT printvridge, setT *vertices, qh_RIDGE innerouter, boolT inorder);
    +void    qh_printvertex(FILE *fp, vertexT *vertex);
    +void    qh_printvertexlist(FILE *fp, const char* string, facetT *facetlist,
    +                         setT *facets, boolT printall);
    +void    qh_printvertices(FILE *fp, const char* string, setT *vertices);
    +void    qh_printvneighbors(FILE *fp, facetT* facetlist, setT *facets, boolT printall);
    +void    qh_printvoronoi(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printvnorm(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +void    qh_printvridge(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +void    qh_produce_output(void);
    +void    qh_produce_output2(void);
    +void    qh_projectdim3(pointT *source, pointT *destination);
    +int     qh_readfeasible(int dim, const char *curline);
    +coordT *qh_readpoints(int *numpoints, int *dimension, boolT *ismalloc);
    +void    qh_setfeasible(int dim);
    +boolT   qh_skipfacet(facetT *facet);
    +char   *qh_skipfilename(char *filename);
    +
    +#endif /* qhDEFio */
    diff --git a/xs/src/qhull/src/libqhull/libqhull.c b/xs/src/qhull/src/libqhull/libqhull.c
    new file mode 100644
    index 0000000000..7696a8a9fe
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/libqhull.c
    @@ -0,0 +1,1403 @@
    +/*
      ---------------------------------
    +
    +   libqhull.c
    +   Quickhull algorithm for convex hulls
    +
    +   qhull() and top-level routines
    +
    +   see qh-qhull.htm, libqhull.h, unix.c
    +
    +   see qhull_a.h for internal functions
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/libqhull.c#3 $$Change: 2047 $
    +   $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*============= functions in alphabetic order after qhull() =======*/
    +
    +/*---------------------------------
    +
    +  qh_qhull()
    +    compute DIM3 convex hull of qh.num_points starting at qh.first_point
    +    qh contains all global options and variables
    +
    +  returns:
    +    returns polyhedron
    +      qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices,
    +
    +    returns global variables
    +      qh.hulltime, qh.max_outside, qh.interior_point, qh.max_vertex, qh.min_vertex
    +
    +    returns precision constants
    +      qh.ANGLEround, centrum_radius, cos_max, DISTround, MAXabs_coord, ONEmerge
    +
    +  notes:
    +    unless needed for output
    +      qh.max_vertex and qh.min_vertex are max/min due to merges
    +
    +  see:
    +    to add individual points to either qh.num_points
    +      use qh_addpoint()
    +
    +    if qh.GETarea
    +      qh_produceoutput() returns qh.totarea and qh.totvol via qh_getarea()
    +
    +  design:
    +    record starting time
    +    initialize hull and partition points
    +    build convex hull
    +    unless early termination
    +      update facet->maxoutside for vertices, coplanar, and near-inside points
    +    error if temporary sets exist
    +    record end time
    +*/
    +
    +void qh_qhull(void) {
    +  int numoutside;
    +
    +  qh hulltime= qh_CPUclock;
    +  if (qh RERUN || qh JOGGLEmax < REALmax/2)
    +    qh_build_withrestart();
    +  else {
    +    qh_initbuild();
    +    qh_buildhull();
    +  }
    +  if (!qh STOPpoint && !qh STOPcone) {
    +    if (qh ZEROall_ok && !qh TESTvneighbors && qh MERGEexact)
    +      qh_checkzero( qh_ALL);
    +    if (qh ZEROall_ok && !qh TESTvneighbors && !qh WAScoplanar) {
    +      trace2((qh ferr, 2055, "qh_qhull: all facets are clearly convex and no coplanar points.  Post-merging and check of maxout not needed.\n"));
    +      qh DOcheckmax= False;
    +    }else {
    +      if (qh MERGEexact || (qh hull_dim > qh_DIMreduceBuild && qh PREmerge))
    +        qh_postmerge("First post-merge", qh premerge_centrum, qh premerge_cos,
    +             (qh POSTmerge ? False : qh TESTvneighbors));
    +      else if (!qh POSTmerge && qh TESTvneighbors)
    +        qh_postmerge("For testing vertex neighbors", qh premerge_centrum,
    +             qh premerge_cos, True);
    +      if (qh POSTmerge)
    +        qh_postmerge("For post-merging", qh postmerge_centrum,
    +             qh postmerge_cos, qh TESTvneighbors);
    +      if (qh visible_list == qh facet_list) { /* i.e., merging done */
    +        qh findbestnew= True;
    +        qh_partitionvisible(/*qh.visible_list*/ !qh_ALL, &numoutside);
    +        qh findbestnew= False;
    +        qh_deletevisible(/*qh.visible_list*/);
    +        qh_resetlists(False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +      }
    +    }
    +    if (qh DOcheckmax){
    +      if (qh REPORTfreq) {
    +        qh_buildtracing(NULL, NULL);
    +        qh_fprintf(qh ferr, 8115, "\nTesting all coplanar points.\n");
    +      }
    +      qh_check_maxout();
    +    }
    +    if (qh KEEPnearinside && !qh maxoutdone)
    +      qh_nearcoplanar();
    +  }
    +  if (qh_setsize(qhmem.tempstack) != 0) {
    +    qh_fprintf(qh ferr, 6164, "qhull internal error (qh_qhull): temporary sets not empty(%d)\n",
    +             qh_setsize(qhmem.tempstack));
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh hulltime= qh_CPUclock - qh hulltime;
    +  qh QHULLfinished= True;
    +  trace1((qh ferr, 1036, "Qhull: algorithm completed\n"));
    +} /* qhull */
    +
    +/*---------------------------------
    +
    +  qh_addpoint( furthest, facet, checkdist )
    +    add point (usually furthest point) above facet to hull
    +    if checkdist,
    +      check that point is above facet.
    +      if point is not outside of the hull, uses qh_partitioncoplanar()
    +      assumes that facet is defined by qh_findbestfacet()
    +    else if facet specified,
    +      assumes that point is above facet (major damage if below)
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    returns False if user requested an early termination
    +     qh.visible_list, newfacet_list, delvertex_list, NEWfacets may be defined
    +    updates qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices
    +    clear qh.maxoutdone (will need to call qh_check_maxout() for facet->maxoutside)
    +    if unknown point, adds a pointer to qh.other_points
    +      do not deallocate the point's coordinates
    +
    +  notes:
    +    assumes point is near its best facet and not at a local minimum of a lens
    +      distributions.  Use qh_findbestfacet to avoid this case.
    +    uses qh.visible_list, qh.newfacet_list, qh.delvertex_list, qh.NEWfacets
    +
    +  see also:
    +    qh_triangulate() -- triangulate non-simplicial facets
    +
    +  design:
    +    add point to other_points if needed
    +    if checkdist
    +      if point not above facet
    +        partition coplanar point
    +        exit
    +    exit if pre STOPpoint requested
    +    find horizon and visible facets for point
    +    make new facets for point to horizon
    +    make hyperplanes for point
    +    compute balance statistics
    +    match neighboring new facets
    +    update vertex neighbors and delete interior vertices
    +    exit if STOPcone requested
    +    merge non-convex new facets
    +    if merge found, many merges, or 'Qf'
    +       use qh_findbestnew() instead of qh_findbest()
    +    partition outside points from visible facets
    +    delete visible facets
    +    check polyhedron if requested
    +    exit if post STOPpoint requested
    +    reset working lists of facets and vertices
    +*/
    +boolT qh_addpoint(pointT *furthest, facetT *facet, boolT checkdist) {
    +  int goodvisible, goodhorizon;
    +  vertexT *vertex;
    +  facetT *newfacet;
    +  realT dist, newbalance, pbalance;
    +  boolT isoutside= False;
    +  int numpart, numpoints, numnew, firstnew;
    +
    +  qh maxoutdone= False;
    +  if (qh_pointid(furthest) == qh_IDunknown)
    +    qh_setappend(&qh other_points, furthest);
    +  if (!facet) {
    +    qh_fprintf(qh ferr, 6213, "qhull internal error (qh_addpoint): NULL facet.  Need to call qh_findbestfacet first\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (checkdist) {
    +    facet= qh_findbest(furthest, facet, !qh_ALL, !qh_ISnewfacets, !qh_NOupper,
    +                        &dist, &isoutside, &numpart);
    +    zzadd_(Zpartition, numpart);
    +    if (!isoutside) {
    +      zinc_(Znotmax);  /* last point of outsideset is no longer furthest. */
    +      facet->notfurthest= True;
    +      qh_partitioncoplanar(furthest, facet, &dist);
    +      return True;
    +    }
    +  }
    +  qh_buildtracing(furthest, facet);
    +  if (qh STOPpoint < 0 && qh furthest_id == -qh STOPpoint-1) {
    +    facet->notfurthest= True;
    +    return False;
    +  }
    +  qh_findhorizon(furthest, facet, &goodvisible, &goodhorizon);
    +  if (qh ONLYgood && !(goodvisible+goodhorizon) && !qh GOODclosest) {
    +    zinc_(Znotgood);
    +    facet->notfurthest= True;
    +    /* last point of outsideset is no longer furthest.  This is ok
    +       since all points of the outside are likely to be bad */
    +    qh_resetlists(False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +    return True;
    +  }
    +  zzinc_(Zprocessed);
    +  firstnew= qh facet_id;
    +  vertex= qh_makenewfacets(furthest /*visible_list, attaches if !ONLYgood */);
    +  qh_makenewplanes(/* newfacet_list */);
    +  numnew= qh facet_id - firstnew;
    +  newbalance= numnew - (realT) (qh num_facets-qh num_visible)
    +                         * qh hull_dim/qh num_vertices;
    +  wadd_(Wnewbalance, newbalance);
    +  wadd_(Wnewbalance2, newbalance * newbalance);
    +  if (qh ONLYgood
    +  && !qh_findgood(qh newfacet_list, goodhorizon) && !qh GOODclosest) {
    +    FORALLnew_facets
    +      qh_delfacet(newfacet);
    +    qh_delvertex(vertex);
    +    qh_resetlists(True, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +    zinc_(Znotgoodnew);
    +    facet->notfurthest= True;
    +    return True;
    +  }
    +  if (qh ONLYgood)
    +    qh_attachnewfacets(/*visible_list*/);
    +  qh_matchnewfacets();
    +  qh_updatevertices();
    +  if (qh STOPcone && qh furthest_id == qh STOPcone-1) {
    +    facet->notfurthest= True;
    +    return False;  /* visible_list etc. still defined */
    +  }
    +  qh findbestnew= False;
    +  if (qh PREmerge || qh MERGEexact) {
    +    qh_premerge(vertex, qh premerge_centrum, qh premerge_cos);
    +    if (qh_USEfindbestnew)
    +      qh findbestnew= True;
    +    else {
    +      FORALLnew_facets {
    +        if (!newfacet->simplicial) {
    +          qh findbestnew= True;  /* use qh_findbestnew instead of qh_findbest*/
    +          break;
    +        }
    +      }
    +    }
    +  }else if (qh BESToutside)
    +    qh findbestnew= True;
    +  qh_partitionvisible(/*qh.visible_list*/ !qh_ALL, &numpoints);
    +  qh findbestnew= False;
    +  qh findbest_notsharp= False;
    +  zinc_(Zpbalance);
    +  pbalance= numpoints - (realT) qh hull_dim /* assumes all points extreme */
    +                * (qh num_points - qh num_vertices)/qh num_vertices;
    +  wadd_(Wpbalance, pbalance);
    +  wadd_(Wpbalance2, pbalance * pbalance);
    +  qh_deletevisible(/*qh.visible_list*/);
    +  zmax_(Zmaxvertex, qh num_vertices);
    +  qh NEWfacets= False;
    +  if (qh IStracing >= 4) {
    +    if (qh num_facets < 2000)
    +      qh_printlists();
    +    qh_printfacetlist(qh newfacet_list, NULL, True);
    +    qh_checkpolygon(qh facet_list);
    +  }else if (qh CHECKfrequently) {
    +    if (qh num_facets < 50)
    +      qh_checkpolygon(qh facet_list);
    +    else
    +      qh_checkpolygon(qh newfacet_list);
    +  }
    +  if (qh STOPpoint > 0 && qh furthest_id == qh STOPpoint-1)
    +    return False;
    +  qh_resetlists(True, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +  /* qh_triangulate(); to test qh.TRInormals */
    +  trace2((qh ferr, 2056, "qh_addpoint: added p%d new facets %d new balance %2.2g point balance %2.2g\n",
    +    qh_pointid(furthest), numnew, newbalance, pbalance));
    +  return True;
    +} /* addpoint */
    +
    +/*---------------------------------
    +
    +  qh_build_withrestart()
    +    allow restarts due to qh.JOGGLEmax while calling qh_buildhull()
    +       qh_errexit always undoes qh_build_withrestart()
    +    qh.FIRSTpoint/qh.NUMpoints is point array
    +        it may be moved by qh_joggleinput()
    +*/
    +void qh_build_withrestart(void) {
    +  int restart;
    +
    +  qh ALLOWrestart= True;
    +  while (True) {
    +    restart= setjmp(qh restartexit); /* simple statement for CRAY J916 */
    +    if (restart) {       /* only from qh_precision() */
    +      zzinc_(Zretry);
    +      wmax_(Wretrymax, qh JOGGLEmax);
    +      /* QH7078 warns about using 'TCn' with 'QJn' */
    +      qh STOPcone= qh_IDunknown; /* if break from joggle, prevents normal output */
    +    }
    +    if (!qh RERUN && qh JOGGLEmax < REALmax/2) {
    +      if (qh build_cnt > qh_JOGGLEmaxretry) {
    +        qh_fprintf(qh ferr, 6229, "qhull precision error: %d attempts to construct a convex hull\n\
    +        with joggled input.  Increase joggle above 'QJ%2.2g'\n\
    +        or modify qh_JOGGLE... parameters in user.h\n",
    +           qh build_cnt, qh JOGGLEmax);
    +        qh_errexit(qh_ERRqhull, NULL, NULL);
    +      }
    +      if (qh build_cnt && !restart)
    +        break;
    +    }else if (qh build_cnt && qh build_cnt >= qh RERUN)
    +      break;
    +    qh STOPcone= 0;
    +    qh_freebuild(True);  /* first call is a nop */
    +    qh build_cnt++;
    +    if (!qh qhull_optionsiz)
    +      qh qhull_optionsiz= (int)strlen(qh qhull_options);   /* WARN64 */
    +    else {
    +      qh qhull_options [qh qhull_optionsiz]= '\0';
    +      qh qhull_optionlen= qh_OPTIONline;  /* starts a new line */
    +    }
    +    qh_option("_run", &qh build_cnt, NULL);
    +    if (qh build_cnt == qh RERUN) {
    +      qh IStracing= qh TRACElastrun;  /* duplicated from qh_initqhull_globals */
    +      if (qh TRACEpoint != qh_IDunknown || qh TRACEdist < REALmax/2 || qh TRACEmerge) {
    +        qh TRACElevel= (qh IStracing? qh IStracing : 3);
    +        qh IStracing= 0;
    +      }
    +      qhmem.IStracing= qh IStracing;
    +    }
    +    if (qh JOGGLEmax < REALmax/2)
    +      qh_joggleinput();
    +    qh_initbuild();
    +    qh_buildhull();
    +    if (qh JOGGLEmax < REALmax/2 && !qh MERGING)
    +      qh_checkconvex(qh facet_list, qh_ALGORITHMfault);
    +  }
    +  qh ALLOWrestart= False;
    +} /* qh_build_withrestart */
    +
    +/*---------------------------------
    +
    +  qh_buildhull()
    +    construct a convex hull by adding outside points one at a time
    +
    +  returns:
    +
    +  notes:
    +    may be called multiple times
    +    checks facet and vertex lists for incorrect flags
    +    to recover from STOPcone, call qh_deletevisible and qh_resetlists
    +
    +  design:
    +    check visible facet and newfacet flags
    +    check newlist vertex flags and qh.STOPcone/STOPpoint
    +    for each facet with a furthest outside point
    +      add point to facet
    +      exit if qh.STOPcone or qh.STOPpoint requested
    +    if qh.NARROWhull for initial simplex
    +      partition remaining outside points to coplanar sets
    +*/
    +void qh_buildhull(void) {
    +  facetT *facet;
    +  pointT *furthest;
    +  vertexT *vertex;
    +  int id;
    +
    +  trace1((qh ferr, 1037, "qh_buildhull: start build hull\n"));
    +  FORALLfacets {
    +    if (facet->visible || facet->newfacet) {
    +      qh_fprintf(qh ferr, 6165, "qhull internal error (qh_buildhull): visible or new facet f%d in facet list\n",
    +                   facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +  }
    +  FORALLvertices {
    +    if (vertex->newlist) {
    +      qh_fprintf(qh ferr, 6166, "qhull internal error (qh_buildhull): new vertex f%d in vertex list\n",
    +                   vertex->id);
    +      qh_errprint("ERRONEOUS", NULL, NULL, NULL, vertex);
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +    id= qh_pointid(vertex->point);
    +    if ((qh STOPpoint>0 && id == qh STOPpoint-1) ||
    +        (qh STOPpoint<0 && id == -qh STOPpoint-1) ||
    +        (qh STOPcone>0 && id == qh STOPcone-1)) {
    +      trace1((qh ferr, 1038,"qh_buildhull: stop point or cone P%d in initial hull\n", id));
    +      return;
    +    }
    +  }
    +  qh facet_next= qh facet_list;      /* advance facet when processed */
    +  while ((furthest= qh_nextfurthest(&facet))) {
    +    qh num_outside--;  /* if ONLYmax, furthest may not be outside */
    +    if (!qh_addpoint(furthest, facet, qh ONLYmax))
    +      break;
    +  }
    +  if (qh NARROWhull) /* move points from outsideset to coplanarset */
    +    qh_outcoplanar( /* facet_list */ );
    +  if (qh num_outside && !furthest) {
    +    qh_fprintf(qh ferr, 6167, "qhull internal error (qh_buildhull): %d outside points were never processed.\n", qh num_outside);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  trace1((qh ferr, 1039, "qh_buildhull: completed the hull construction\n"));
    +} /* buildhull */
    +
    +
    +/*---------------------------------
    +
    +  qh_buildtracing( furthest, facet )
    +    trace an iteration of qh_buildhull() for furthest point and facet
    +    if !furthest, prints progress message
    +
    +  returns:
    +    tracks progress with qh.lastreport
    +    updates qh.furthest_id (-3 if furthest is NULL)
    +    also resets visit_id, vertext_visit on wrap around
    +
    +  see:
    +    qh_tracemerging()
    +
    +  design:
    +    if !furthest
    +      print progress message
    +      exit
    +    if 'TFn' iteration
    +      print progress message
    +    else if tracing
    +      trace furthest point and facet
    +    reset qh.visit_id and qh.vertex_visit if overflow may occur
    +    set qh.furthest_id for tracing
    +*/
    +void qh_buildtracing(pointT *furthest, facetT *facet) {
    +  realT dist= 0;
    +  float cpu;
    +  int total, furthestid;
    +  time_t timedata;
    +  struct tm *tp;
    +  vertexT *vertex;
    +
    +  qh old_randomdist= qh RANDOMdist;
    +  qh RANDOMdist= False;
    +  if (!furthest) {
    +    time(&timedata);
    +    tp= localtime(&timedata);
    +    cpu= (float)qh_CPUclock - (float)qh hulltime;
    +    cpu /= (float)qh_SECticks;
    +    total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +    qh_fprintf(qh ferr, 8118, "\n\
    +At %02d:%02d:%02d & %2.5g CPU secs, qhull has created %d facets and merged %d.\n\
    + The current hull contains %d facets and %d vertices.  Last point was p%d\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, qh facet_id -1,
    +      total, qh num_facets, qh num_vertices, qh furthest_id);
    +    return;
    +  }
    +  furthestid= qh_pointid(furthest);
    +  if (qh TRACEpoint == furthestid) {
    +    qh IStracing= qh TRACElevel;
    +    qhmem.IStracing= qh TRACElevel;
    +  }else if (qh TRACEpoint != qh_IDunknown && qh TRACEdist < REALmax/2) {
    +    qh IStracing= 0;
    +    qhmem.IStracing= 0;
    +  }
    +  if (qh REPORTfreq && (qh facet_id-1 > qh lastreport+qh REPORTfreq)) {
    +    qh lastreport= qh facet_id-1;
    +    time(&timedata);
    +    tp= localtime(&timedata);
    +    cpu= (float)qh_CPUclock - (float)qh hulltime;
    +    cpu /= (float)qh_SECticks;
    +    total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +    zinc_(Zdistio);
    +    qh_distplane(furthest, facet, &dist);
    +    qh_fprintf(qh ferr, 8119, "\n\
    +At %02d:%02d:%02d & %2.5g CPU secs, qhull has created %d facets and merged %d.\n\
    + The current hull contains %d facets and %d vertices.  There are %d\n\
    + outside points.  Next is point p%d(v%d), %2.2g above f%d.\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, qh facet_id -1,
    +      total, qh num_facets, qh num_vertices, qh num_outside+1,
    +      furthestid, qh vertex_id, dist, getid_(facet));
    +  }else if (qh IStracing >=1) {
    +    cpu= (float)qh_CPUclock - (float)qh hulltime;
    +    cpu /= (float)qh_SECticks;
    +    qh_distplane(furthest, facet, &dist);
    +    qh_fprintf(qh ferr, 8120, "qh_addpoint: add p%d(v%d) to hull of %d facets(%2.2g above f%d) and %d outside at %4.4g CPU secs.  Previous was p%d.\n",
    +      furthestid, qh vertex_id, qh num_facets, dist,
    +      getid_(facet), qh num_outside+1, cpu, qh furthest_id);
    +  }
    +  zmax_(Zvisit2max, (int)qh visit_id/2);
    +  if (qh visit_id > (unsigned) INT_MAX) { /* 31 bits */
    +    zinc_(Zvisit);
    +    qh visit_id= 0;
    +    FORALLfacets
    +      facet->visitid= 0;
    +  }
    +  zmax_(Zvvisit2max, (int)qh vertex_visit/2);
    +  if (qh vertex_visit > (unsigned) INT_MAX) { /* 31 bits */
    +    zinc_(Zvvisit);
    +    qh vertex_visit= 0;
    +    FORALLvertices
    +      vertex->visitid= 0;
    +  }
    +  qh furthest_id= furthestid;
    +  qh RANDOMdist= qh old_randomdist;
    +} /* buildtracing */
    +
    +/*---------------------------------
    +
    +  qh_errexit2( exitcode, facet, otherfacet )
    +    return exitcode to system after an error
    +    report two facets
    +
    +  returns:
    +    assumes exitcode non-zero
    +
    +  see:
    +    normally use qh_errexit() in user.c(reports a facet and a ridge)
    +*/
    +void qh_errexit2(int exitcode, facetT *facet, facetT *otherfacet) {
    +
    +  qh_errprint("ERRONEOUS", facet, otherfacet, NULL, NULL);
    +  qh_errexit(exitcode, NULL, NULL);
    +} /* errexit2 */
    +
    +
    +/*---------------------------------
    +
    +  qh_findhorizon( point, facet, goodvisible, goodhorizon )
    +    given a visible facet, find the point's horizon and visible facets
    +    for all facets, !facet-visible
    +
    +  returns:
    +    returns qh.visible_list/num_visible with all visible facets
    +      marks visible facets with ->visible
    +    updates count of good visible and good horizon facets
    +    updates qh.max_outside, qh.max_vertex, facet->maxoutside
    +
    +  see:
    +    similar to qh_delpoint()
    +
    +  design:
    +    move facet to qh.visible_list at end of qh.facet_list
    +    for all visible facets
    +     for each unvisited neighbor of a visible facet
    +       compute distance of point to neighbor
    +       if point above neighbor
    +         move neighbor to end of qh.visible_list
    +       else if point is coplanar with neighbor
    +         update qh.max_outside, qh.max_vertex, neighbor->maxoutside
    +         mark neighbor coplanar (will create a samecycle later)
    +         update horizon statistics
    +*/
    +void qh_findhorizon(pointT *point, facetT *facet, int *goodvisible, int *goodhorizon) {
    +  facetT *neighbor, **neighborp, *visible;
    +  int numhorizon= 0, coplanar= 0;
    +  realT dist;
    +
    +  trace1((qh ferr, 1040,"qh_findhorizon: find horizon for point p%d facet f%d\n",qh_pointid(point),facet->id));
    +  *goodvisible= *goodhorizon= 0;
    +  zinc_(Ztotvisible);
    +  qh_removefacet(facet);  /* visible_list at end of qh facet_list */
    +  qh_appendfacet(facet);
    +  qh num_visible= 1;
    +  if (facet->good)
    +    (*goodvisible)++;
    +  qh visible_list= facet;
    +  facet->visible= True;
    +  facet->f.replace= NULL;
    +  if (qh IStracing >=4)
    +    qh_errprint("visible", facet, NULL, NULL, NULL);
    +  qh visit_id++;
    +  FORALLvisible_facets {
    +    if (visible->tricoplanar && !qh TRInormals) {
    +      qh_fprintf(qh ferr, 6230, "Qhull internal error (qh_findhorizon): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit(qh_ERRqhull, visible, NULL);
    +    }
    +    visible->visitid= qh visit_id;
    +    FOREACHneighbor_(visible) {
    +      if (neighbor->visitid == qh visit_id)
    +        continue;
    +      neighbor->visitid= qh visit_id;
    +      zzinc_(Znumvisibility);
    +      qh_distplane(point, neighbor, &dist);
    +      if (dist > qh MINvisible) {
    +        zinc_(Ztotvisible);
    +        qh_removefacet(neighbor);  /* append to end of qh visible_list */
    +        qh_appendfacet(neighbor);
    +        neighbor->visible= True;
    +        neighbor->f.replace= NULL;
    +        qh num_visible++;
    +        if (neighbor->good)
    +          (*goodvisible)++;
    +        if (qh IStracing >=4)
    +          qh_errprint("visible", neighbor, NULL, NULL, NULL);
    +      }else {
    +        if (dist > - qh MAXcoplanar) {
    +          neighbor->coplanar= True;
    +          zzinc_(Zcoplanarhorizon);
    +          qh_precision("coplanar horizon");
    +          coplanar++;
    +          if (qh MERGING) {
    +            if (dist > 0) {
    +              maximize_(qh max_outside, dist);
    +              maximize_(qh max_vertex, dist);
    +#if qh_MAXoutside
    +              maximize_(neighbor->maxoutside, dist);
    +#endif
    +            }else
    +              minimize_(qh min_vertex, dist);  /* due to merge later */
    +          }
    +          trace2((qh ferr, 2057, "qh_findhorizon: point p%d is coplanar to horizon f%d, dist=%2.7g < qh MINvisible(%2.7g)\n",
    +              qh_pointid(point), neighbor->id, dist, qh MINvisible));
    +        }else
    +          neighbor->coplanar= False;
    +        zinc_(Ztothorizon);
    +        numhorizon++;
    +        if (neighbor->good)
    +          (*goodhorizon)++;
    +        if (qh IStracing >=4)
    +          qh_errprint("horizon", neighbor, NULL, NULL, NULL);
    +      }
    +    }
    +  }
    +  if (!numhorizon) {
    +    qh_precision("empty horizon");
    +    qh_fprintf(qh ferr, 6168, "qhull precision error (qh_findhorizon): empty horizon\n\
    +QhullPoint p%d was above all facets.\n", qh_pointid(point));
    +    qh_printfacetlist(qh facet_list, NULL, True);
    +    qh_errexit(qh_ERRprec, NULL, NULL);
    +  }
    +  trace1((qh ferr, 1041, "qh_findhorizon: %d horizon facets(good %d), %d visible(good %d), %d coplanar\n",
    +       numhorizon, *goodhorizon, qh num_visible, *goodvisible, coplanar));
    +  if (qh IStracing >= 4 && qh num_facets < 50)
    +    qh_printlists();
    +} /* findhorizon */
    +
    +/*---------------------------------
    +
    +  qh_nextfurthest( visible )
    +    returns next furthest point and visible facet for qh_addpoint()
    +    starts search at qh.facet_next
    +
    +  returns:
    +    removes furthest point from outside set
    +    NULL if none available
    +    advances qh.facet_next over facets with empty outside sets
    +
    +  design:
    +    for each facet from qh.facet_next
    +      if empty outside set
    +        advance qh.facet_next
    +      else if qh.NARROWhull
    +        determine furthest outside point
    +        if furthest point is not outside
    +          advance qh.facet_next(point will be coplanar)
    +    remove furthest point from outside set
    +*/
    +pointT *qh_nextfurthest(facetT **visible) {
    +  facetT *facet;
    +  int size, idx;
    +  realT randr, dist;
    +  pointT *furthest;
    +
    +  while ((facet= qh facet_next) != qh facet_tail) {
    +    if (!facet->outsideset) {
    +      qh facet_next= facet->next;
    +      continue;
    +    }
    +    SETreturnsize_(facet->outsideset, size);
    +    if (!size) {
    +      qh_setfree(&facet->outsideset);
    +      qh facet_next= facet->next;
    +      continue;
    +    }
    +    if (qh NARROWhull) {
    +      if (facet->notfurthest)
    +        qh_furthestout(facet);
    +      furthest= (pointT*)qh_setlast(facet->outsideset);
    +#if qh_COMPUTEfurthest
    +      qh_distplane(furthest, facet, &dist);
    +      zinc_(Zcomputefurthest);
    +#else
    +      dist= facet->furthestdist;
    +#endif
    +      if (dist < qh MINoutside) { /* remainder of outside set is coplanar for qh_outcoplanar */
    +        qh facet_next= facet->next;
    +        continue;
    +      }
    +    }
    +    if (!qh RANDOMoutside && !qh VIRTUALmemory) {
    +      if (qh PICKfurthest) {
    +        qh_furthestnext(/* qh.facet_list */);
    +        facet= qh facet_next;
    +      }
    +      *visible= facet;
    +      return((pointT*)qh_setdellast(facet->outsideset));
    +    }
    +    if (qh RANDOMoutside) {
    +      int outcoplanar = 0;
    +      if (qh NARROWhull) {
    +        FORALLfacets {
    +          if (facet == qh facet_next)
    +            break;
    +          if (facet->outsideset)
    +            outcoplanar += qh_setsize( facet->outsideset);
    +        }
    +      }
    +      randr= qh_RANDOMint;
    +      randr= randr/(qh_RANDOMmax+1);
    +      idx= (int)floor((qh num_outside - outcoplanar) * randr);
    +      FORALLfacet_(qh facet_next) {
    +        if (facet->outsideset) {
    +          SETreturnsize_(facet->outsideset, size);
    +          if (!size)
    +            qh_setfree(&facet->outsideset);
    +          else if (size > idx) {
    +            *visible= facet;
    +            return((pointT*)qh_setdelnth(facet->outsideset, idx));
    +          }else
    +            idx -= size;
    +        }
    +      }
    +      qh_fprintf(qh ferr, 6169, "qhull internal error (qh_nextfurthest): num_outside %d is too low\nby at least %d, or a random real %g >= 1.0\n",
    +              qh num_outside, idx+1, randr);
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }else { /* VIRTUALmemory */
    +      facet= qh facet_tail->previous;
    +      if (!(furthest= (pointT*)qh_setdellast(facet->outsideset))) {
    +        if (facet->outsideset)
    +          qh_setfree(&facet->outsideset);
    +        qh_removefacet(facet);
    +        qh_prependfacet(facet, &qh facet_list);
    +        continue;
    +      }
    +      *visible= facet;
    +      return furthest;
    +    }
    +  }
    +  return NULL;
    +} /* nextfurthest */
    +
    +/*---------------------------------
    +
    +  qh_partitionall( vertices, points, numpoints )
    +    partitions all points in points/numpoints to the outsidesets of facets
    +    vertices= vertices in qh.facet_list(!partitioned)
    +
    +  returns:
    +    builds facet->outsideset
    +    does not partition qh.GOODpoint
    +    if qh.ONLYgood && !qh.MERGING,
    +      does not partition qh.GOODvertex
    +
    +  notes:
    +    faster if qh.facet_list sorted by anticipated size of outside set
    +
    +  design:
    +    initialize pointset with all points
    +    remove vertices from pointset
    +    remove qh.GOODpointp from pointset (unless it's qh.STOPcone or qh.STOPpoint)
    +    for all facets
    +      for all remaining points in pointset
    +        compute distance from point to facet
    +        if point is outside facet
    +          remove point from pointset (by not reappending)
    +          update bestpoint
    +          append point or old bestpoint to facet's outside set
    +      append bestpoint to facet's outside set (furthest)
    +    for all points remaining in pointset
    +      partition point into facets' outside sets and coplanar sets
    +*/
    +void qh_partitionall(setT *vertices, pointT *points, int numpoints){
    +  setT *pointset;
    +  vertexT *vertex, **vertexp;
    +  pointT *point, **pointp, *bestpoint;
    +  int size, point_i, point_n, point_end, remaining, i, id;
    +  facetT *facet;
    +  realT bestdist= -REALmax, dist, distoutside;
    +
    +  trace1((qh ferr, 1042, "qh_partitionall: partition all points into outside sets\n"));
    +  pointset= qh_settemp(numpoints);
    +  qh num_outside= 0;
    +  pointp= SETaddr_(pointset, pointT);
    +  for (i=numpoints, point= points; i--; point += qh hull_dim)
    +    *(pointp++)= point;
    +  qh_settruncate(pointset, numpoints);
    +  FOREACHvertex_(vertices) {
    +    if ((id= qh_pointid(vertex->point)) >= 0)
    +      SETelem_(pointset, id)= NULL;
    +  }
    +  id= qh_pointid(qh GOODpointp);
    +  if (id >=0 && qh STOPcone-1 != id && -qh STOPpoint-1 != id)
    +    SETelem_(pointset, id)= NULL;
    +  if (qh GOODvertexp && qh ONLYgood && !qh MERGING) { /* matches qhull()*/
    +    if ((id= qh_pointid(qh GOODvertexp)) >= 0)
    +      SETelem_(pointset, id)= NULL;
    +  }
    +  if (!qh BESToutside) {  /* matches conditional for qh_partitionpoint below */
    +    distoutside= qh_DISToutside; /* multiple of qh.MINoutside & qh.max_outside, see user.h */
    +    zval_(Ztotpartition)= qh num_points - qh hull_dim - 1; /*misses GOOD... */
    +    remaining= qh num_facets;
    +    point_end= numpoints;
    +    FORALLfacets {
    +      size= point_end/(remaining--) + 100;
    +      facet->outsideset= qh_setnew(size);
    +      bestpoint= NULL;
    +      point_end= 0;
    +      FOREACHpoint_i_(pointset) {
    +        if (point) {
    +          zzinc_(Zpartitionall);
    +          qh_distplane(point, facet, &dist);
    +          if (dist < distoutside)
    +            SETelem_(pointset, point_end++)= point;
    +          else {
    +            qh num_outside++;
    +            if (!bestpoint) {
    +              bestpoint= point;
    +              bestdist= dist;
    +            }else if (dist > bestdist) {
    +              qh_setappend(&facet->outsideset, bestpoint);
    +              bestpoint= point;
    +              bestdist= dist;
    +            }else
    +              qh_setappend(&facet->outsideset, point);
    +          }
    +        }
    +      }
    +      if (bestpoint) {
    +        qh_setappend(&facet->outsideset, bestpoint);
    +#if !qh_COMPUTEfurthest
    +        facet->furthestdist= bestdist;
    +#endif
    +      }else
    +        qh_setfree(&facet->outsideset);
    +      qh_settruncate(pointset, point_end);
    +    }
    +  }
    +  /* if !qh BESToutside, pointset contains points not assigned to outsideset */
    +  if (qh BESToutside || qh MERGING || qh KEEPcoplanar || qh KEEPinside) {
    +    qh findbestnew= True;
    +    FOREACHpoint_i_(pointset) {
    +      if (point)
    +        qh_partitionpoint(point, qh facet_list);
    +    }
    +    qh findbestnew= False;
    +  }
    +  zzadd_(Zpartitionall, zzval_(Zpartition));
    +  zzval_(Zpartition)= 0;
    +  qh_settempfree(&pointset);
    +  if (qh IStracing >= 4)
    +    qh_printfacetlist(qh facet_list, NULL, True);
    +} /* partitionall */
    +
    +
    +/*---------------------------------
    +
    +  qh_partitioncoplanar( point, facet, dist )
    +    partition coplanar point to a facet
    +    dist is distance from point to facet
    +    if dist NULL,
    +      searches for bestfacet and does nothing if inside
    +    if qh.findbestnew set,
    +      searches new facets instead of using qh_findbest()
    +
    +  returns:
    +    qh.max_ouside updated
    +    if qh.KEEPcoplanar or qh.KEEPinside
    +      point assigned to best coplanarset
    +
    +  notes:
    +    facet->maxoutside is updated at end by qh_check_maxout
    +
    +  design:
    +    if dist undefined
    +      find best facet for point
    +      if point sufficiently below facet (depends on qh.NEARinside and qh.KEEPinside)
    +        exit
    +    if keeping coplanar/nearinside/inside points
    +      if point is above furthest coplanar point
    +        append point to coplanar set (it is the new furthest)
    +        update qh.max_outside
    +      else
    +        append point one before end of coplanar set
    +    else if point is clearly outside of qh.max_outside and bestfacet->coplanarset
    +    and bestfacet is more than perpendicular to facet
    +      repartition the point using qh_findbest() -- it may be put on an outsideset
    +    else
    +      update qh.max_outside
    +*/
    +void qh_partitioncoplanar(pointT *point, facetT *facet, realT *dist) {
    +  facetT *bestfacet;
    +  pointT *oldfurthest;
    +  realT bestdist, dist2= 0, angle;
    +  int numpart= 0, oldfindbest;
    +  boolT isoutside;
    +
    +  qh WAScoplanar= True;
    +  if (!dist) {
    +    if (qh findbestnew)
    +      bestfacet= qh_findbestnew(point, facet, &bestdist, qh_ALL, &isoutside, &numpart);
    +    else
    +      bestfacet= qh_findbest(point, facet, qh_ALL, !qh_ISnewfacets, qh DELAUNAY,
    +                          &bestdist, &isoutside, &numpart);
    +    zinc_(Ztotpartcoplanar);
    +    zzadd_(Zpartcoplanar, numpart);
    +    if (!qh DELAUNAY && !qh KEEPinside) { /*  for 'd', bestdist skips upperDelaunay facets */
    +      if (qh KEEPnearinside) {
    +        if (bestdist < -qh NEARinside) {
    +          zinc_(Zcoplanarinside);
    +          trace4((qh ferr, 4062, "qh_partitioncoplanar: point p%d is more than near-inside facet f%d dist %2.2g findbestnew %d\n",
    +                  qh_pointid(point), bestfacet->id, bestdist, qh findbestnew));
    +          return;
    +        }
    +      }else if (bestdist < -qh MAXcoplanar) {
    +          trace4((qh ferr, 4063, "qh_partitioncoplanar: point p%d is inside facet f%d dist %2.2g findbestnew %d\n",
    +                  qh_pointid(point), bestfacet->id, bestdist, qh findbestnew));
    +        zinc_(Zcoplanarinside);
    +        return;
    +      }
    +    }
    +  }else {
    +    bestfacet= facet;
    +    bestdist= *dist;
    +  }
    +  if (bestdist > qh max_outside) {
    +    if (!dist && facet != bestfacet) {
    +      zinc_(Zpartangle);
    +      angle= qh_getangle(facet->normal, bestfacet->normal);
    +      if (angle < 0) {
    +        /* typically due to deleted vertex and coplanar facets, e.g.,
    +             RBOX 1000 s Z1 G1e-13 t1001185205 | QHULL Tv */
    +        zinc_(Zpartflip);
    +        trace2((qh ferr, 2058, "qh_partitioncoplanar: repartition point p%d from f%d.  It is above flipped facet f%d dist %2.2g\n",
    +                qh_pointid(point), facet->id, bestfacet->id, bestdist));
    +        oldfindbest= qh findbestnew;
    +        qh findbestnew= False;
    +        qh_partitionpoint(point, bestfacet);
    +        qh findbestnew= oldfindbest;
    +        return;
    +      }
    +    }
    +    qh max_outside= bestdist;
    +    if (bestdist > qh TRACEdist) {
    +      qh_fprintf(qh ferr, 8122, "qh_partitioncoplanar: ====== p%d from f%d increases max_outside to %2.2g of f%d last p%d\n",
    +                     qh_pointid(point), facet->id, bestdist, bestfacet->id, qh furthest_id);
    +      qh_errprint("DISTANT", facet, bestfacet, NULL, NULL);
    +    }
    +  }
    +  if (qh KEEPcoplanar + qh KEEPinside + qh KEEPnearinside) {
    +    oldfurthest= (pointT*)qh_setlast(bestfacet->coplanarset);
    +    if (oldfurthest) {
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(oldfurthest, bestfacet, &dist2);
    +    }
    +    if (!oldfurthest || dist2 < bestdist)
    +      qh_setappend(&bestfacet->coplanarset, point);
    +    else
    +      qh_setappend2ndlast(&bestfacet->coplanarset, point);
    +  }
    +  trace4((qh ferr, 4064, "qh_partitioncoplanar: point p%d is coplanar with facet f%d(or inside) dist %2.2g\n",
    +          qh_pointid(point), bestfacet->id, bestdist));
    +} /* partitioncoplanar */
    +
    +/*---------------------------------
    +
    +  qh_partitionpoint( point, facet )
    +    assigns point to an outside set, coplanar set, or inside set (i.e., dropt)
    +    if qh.findbestnew
    +      uses qh_findbestnew() to search all new facets
    +    else
    +      uses qh_findbest()
    +
    +  notes:
    +    after qh_distplane(), this and qh_findbest() are most expensive in 3-d
    +
    +  design:
    +    find best facet for point
    +      (either exhaustive search of new facets or directed search from facet)
    +    if qh.NARROWhull
    +      retain coplanar and nearinside points as outside points
    +    if point is outside bestfacet
    +      if point above furthest point for bestfacet
    +        append point to outside set (it becomes the new furthest)
    +        if outside set was empty
    +          move bestfacet to end of qh.facet_list (i.e., after qh.facet_next)
    +        update bestfacet->furthestdist
    +      else
    +        append point one before end of outside set
    +    else if point is coplanar to bestfacet
    +      if keeping coplanar points or need to update qh.max_outside
    +        partition coplanar point into bestfacet
    +    else if near-inside point
    +      partition as coplanar point into bestfacet
    +    else is an inside point
    +      if keeping inside points
    +        partition as coplanar point into bestfacet
    +*/
    +void qh_partitionpoint(pointT *point, facetT *facet) {
    +  realT bestdist;
    +  boolT isoutside;
    +  facetT *bestfacet;
    +  int numpart;
    +#if qh_COMPUTEfurthest
    +  realT dist;
    +#endif
    +
    +  if (qh findbestnew)
    +    bestfacet= qh_findbestnew(point, facet, &bestdist, qh BESToutside, &isoutside, &numpart);
    +  else
    +    bestfacet= qh_findbest(point, facet, qh BESToutside, qh_ISnewfacets, !qh_NOupper,
    +                          &bestdist, &isoutside, &numpart);
    +  zinc_(Ztotpartition);
    +  zzadd_(Zpartition, numpart);
    +  if (qh NARROWhull) {
    +    if (qh DELAUNAY && !isoutside && bestdist >= -qh MAXcoplanar)
    +      qh_precision("nearly incident point(narrow hull)");
    +    if (qh KEEPnearinside) {
    +      if (bestdist >= -qh NEARinside)
    +        isoutside= True;
    +    }else if (bestdist >= -qh MAXcoplanar)
    +      isoutside= True;
    +  }
    +
    +  if (isoutside) {
    +    if (!bestfacet->outsideset
    +    || !qh_setlast(bestfacet->outsideset)) {
    +      qh_setappend(&(bestfacet->outsideset), point);
    +      if (!bestfacet->newfacet) {
    +        qh_removefacet(bestfacet);  /* make sure it's after qh facet_next */
    +        qh_appendfacet(bestfacet);
    +      }
    +#if !qh_COMPUTEfurthest
    +      bestfacet->furthestdist= bestdist;
    +#endif
    +    }else {
    +#if qh_COMPUTEfurthest
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(oldfurthest, bestfacet, &dist);
    +      if (dist < bestdist)
    +        qh_setappend(&(bestfacet->outsideset), point);
    +      else
    +        qh_setappend2ndlast(&(bestfacet->outsideset), point);
    +#else
    +      if (bestfacet->furthestdist < bestdist) {
    +        qh_setappend(&(bestfacet->outsideset), point);
    +        bestfacet->furthestdist= bestdist;
    +      }else
    +        qh_setappend2ndlast(&(bestfacet->outsideset), point);
    +#endif
    +    }
    +    qh num_outside++;
    +    trace4((qh ferr, 4065, "qh_partitionpoint: point p%d is outside facet f%d new? %d (or narrowhull)\n",
    +          qh_pointid(point), bestfacet->id, bestfacet->newfacet));
    +  }else if (qh DELAUNAY || bestdist >= -qh MAXcoplanar) { /* for 'd', bestdist skips upperDelaunay facets */
    +    zzinc_(Zcoplanarpart);
    +    if (qh DELAUNAY)
    +      qh_precision("nearly incident point");
    +    if ((qh KEEPcoplanar + qh KEEPnearinside) || bestdist > qh max_outside)
    +      qh_partitioncoplanar(point, bestfacet, &bestdist);
    +    else {
    +      trace4((qh ferr, 4066, "qh_partitionpoint: point p%d is coplanar to facet f%d (dropped)\n",
    +          qh_pointid(point), bestfacet->id));
    +    }
    +  }else if (qh KEEPnearinside && bestdist > -qh NEARinside) {
    +    zinc_(Zpartnear);
    +    qh_partitioncoplanar(point, bestfacet, &bestdist);
    +  }else {
    +    zinc_(Zpartinside);
    +    trace4((qh ferr, 4067, "qh_partitionpoint: point p%d is inside all facets, closest to f%d dist %2.2g\n",
    +          qh_pointid(point), bestfacet->id, bestdist));
    +    if (qh KEEPinside)
    +      qh_partitioncoplanar(point, bestfacet, &bestdist);
    +  }
    +} /* partitionpoint */
    +
    +/*---------------------------------
    +
    +  qh_partitionvisible( allpoints, numoutside )
    +    partitions points in visible facets to qh.newfacet_list
    +    qh.visible_list= visible facets
    +    for visible facets
    +      1st neighbor (if any) points to a horizon facet or a new facet
    +    if allpoints(!used),
    +      repartitions coplanar points
    +
    +  returns:
    +    updates outside sets and coplanar sets of qh.newfacet_list
    +    updates qh.num_outside (count of outside points)
    +
    +  notes:
    +    qh.findbest_notsharp should be clear (extra work if set)
    +
    +  design:
    +    for all visible facets with outside set or coplanar set
    +      select a newfacet for visible facet
    +      if outside set
    +        partition outside set into new facets
    +      if coplanar set and keeping coplanar/near-inside/inside points
    +        if allpoints
    +          partition coplanar set into new facets, may be assigned outside
    +        else
    +          partition coplanar set into coplanar sets of new facets
    +    for each deleted vertex
    +      if allpoints
    +        partition vertex into new facets, may be assigned outside
    +      else
    +        partition vertex into coplanar sets of new facets
    +*/
    +void qh_partitionvisible(/*qh.visible_list*/ boolT allpoints, int *numoutside) {
    +  facetT *visible, *newfacet;
    +  pointT *point, **pointp;
    +  int coplanar=0, size;
    +  unsigned count;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh ONLYmax)
    +    maximize_(qh MINoutside, qh max_vertex);
    +  *numoutside= 0;
    +  FORALLvisible_facets {
    +    if (!visible->outsideset && !visible->coplanarset)
    +      continue;
    +    newfacet= visible->f.replace;
    +    count= 0;
    +    while (newfacet && newfacet->visible) {
    +      newfacet= newfacet->f.replace;
    +      if (count++ > qh facet_id)
    +        qh_infiniteloop(visible);
    +    }
    +    if (!newfacet)
    +      newfacet= qh newfacet_list;
    +    if (newfacet == qh facet_tail) {
    +      qh_fprintf(qh ferr, 6170, "qhull precision error (qh_partitionvisible): all new facets deleted as\n        degenerate facets. Can not continue.\n");
    +      qh_errexit(qh_ERRprec, NULL, NULL);
    +    }
    +    if (visible->outsideset) {
    +      size= qh_setsize(visible->outsideset);
    +      *numoutside += size;
    +      qh num_outside -= size;
    +      FOREACHpoint_(visible->outsideset)
    +        qh_partitionpoint(point, newfacet);
    +    }
    +    if (visible->coplanarset && (qh KEEPcoplanar + qh KEEPinside + qh KEEPnearinside)) {
    +      size= qh_setsize(visible->coplanarset);
    +      coplanar += size;
    +      FOREACHpoint_(visible->coplanarset) {
    +        if (allpoints) /* not used */
    +          qh_partitionpoint(point, newfacet);
    +        else
    +          qh_partitioncoplanar(point, newfacet, NULL);
    +      }
    +    }
    +  }
    +  FOREACHvertex_(qh del_vertices) {
    +    if (vertex->point) {
    +      if (allpoints) /* not used */
    +        qh_partitionpoint(vertex->point, qh newfacet_list);
    +      else
    +        qh_partitioncoplanar(vertex->point, qh newfacet_list, NULL);
    +    }
    +  }
    +  trace1((qh ferr, 1043,"qh_partitionvisible: partitioned %d points from outsidesets and %d points from coplanarsets\n", *numoutside, coplanar));
    +} /* partitionvisible */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_precision( reason )
    +    restart on precision errors if not merging and if 'QJn'
    +*/
    +void qh_precision(const char *reason) {
    +
    +  if (qh ALLOWrestart && !qh PREmerge && !qh MERGEexact) {
    +    if (qh JOGGLEmax < REALmax/2) {
    +      trace0((qh ferr, 26, "qh_precision: qhull restart because of %s\n", reason));
    +      /* May be called repeatedly if qh->ALLOWrestart */
    +      longjmp(qh restartexit, qh_ERRprec);
    +    }
    +  }
    +} /* qh_precision */
    +
    +/*---------------------------------
    +
    +  qh_printsummary( fp )
    +    prints summary to fp
    +
    +  notes:
    +    not in io.c so that user_eg.c can prevent io.c from loading
    +    qh_printsummary and qh_countfacets must match counts
    +
    +  design:
    +    determine number of points, vertices, and coplanar points
    +    print summary
    +*/
    +void qh_printsummary(FILE *fp) {
    +  realT ratio, outerplane, innerplane;
    +  float cpu;
    +  int size, id, nummerged, numvertices, numcoplanars= 0, nonsimplicial=0;
    +  int goodused;
    +  facetT *facet;
    +  const char *s;
    +  int numdel= zzval_(Zdelvertextot);
    +  int numtricoplanars= 0;
    +
    +  size= qh num_points + qh_setsize(qh other_points);
    +  numvertices= qh num_vertices - qh_setsize(qh del_vertices);
    +  id= qh_pointid(qh GOODpointp);
    +  FORALLfacets {
    +    if (facet->coplanarset)
    +      numcoplanars += qh_setsize( facet->coplanarset);
    +    if (facet->good) {
    +      if (facet->simplicial) {
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else if (qh_setsize(facet->vertices) != qh hull_dim)
    +        nonsimplicial++;
    +    }
    +  }
    +  if (id >=0 && qh STOPcone-1 != id && -qh STOPpoint-1 != id)
    +    size--;
    +  if (qh STOPcone || qh STOPpoint)
    +      qh_fprintf(fp, 9288, "\nAt a premature exit due to 'TVn', 'TCn', 'TRn', or precision error with 'QJn'.");
    +  if (qh UPPERdelaunay)
    +    goodused= qh GOODvertex + qh GOODpoint + qh SPLITthresholds;
    +  else if (qh DELAUNAY)
    +    goodused= qh GOODvertex + qh GOODpoint + qh GOODthreshold;
    +  else
    +    goodused= qh num_good;
    +  nummerged= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +  if (qh VORONOI) {
    +    if (qh UPPERdelaunay)
    +      qh_fprintf(fp, 9289, "\n\
    +Furthest-site Voronoi vertices by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    else
    +      qh_fprintf(fp, 9290, "\n\
    +Voronoi diagram by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    qh_fprintf(fp, 9291, "  Number of Voronoi regions%s: %d\n",
    +              qh ATinfinity ? " and at-infinity" : "", numvertices);
    +    if (numdel)
    +      qh_fprintf(fp, 9292, "  Total number of deleted points due to merging: %d\n", numdel);
    +    if (numcoplanars - numdel > 0)
    +      qh_fprintf(fp, 9293, "  Number of nearly incident points: %d\n", numcoplanars - numdel);
    +    else if (size - numvertices - numdel > 0)
    +      qh_fprintf(fp, 9294, "  Total number of nearly incident points: %d\n", size - numvertices - numdel);
    +    qh_fprintf(fp, 9295, "  Number of%s Voronoi vertices: %d\n",
    +              goodused ? " 'good'" : "", qh num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(fp, 9296, "  Number of%s non-simplicial Voronoi vertices: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else if (qh DELAUNAY) {
    +    if (qh UPPERdelaunay)
    +      qh_fprintf(fp, 9297, "\n\
    +Furthest-site Delaunay triangulation by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    else
    +      qh_fprintf(fp, 9298, "\n\
    +Delaunay triangulation by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    qh_fprintf(fp, 9299, "  Number of input sites%s: %d\n",
    +              qh ATinfinity ? " and at-infinity" : "", numvertices);
    +    if (numdel)
    +      qh_fprintf(fp, 9300, "  Total number of deleted points due to merging: %d\n", numdel);
    +    if (numcoplanars - numdel > 0)
    +      qh_fprintf(fp, 9301, "  Number of nearly incident points: %d\n", numcoplanars - numdel);
    +    else if (size - numvertices - numdel > 0)
    +      qh_fprintf(fp, 9302, "  Total number of nearly incident points: %d\n", size - numvertices - numdel);
    +    qh_fprintf(fp, 9303, "  Number of%s Delaunay regions: %d\n",
    +              goodused ? " 'good'" : "", qh num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(fp, 9304, "  Number of%s non-simplicial Delaunay regions: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else if (qh HALFspace) {
    +    qh_fprintf(fp, 9305, "\n\
    +Halfspace intersection by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    qh_fprintf(fp, 9306, "  Number of halfspaces: %d\n", size);
    +    qh_fprintf(fp, 9307, "  Number of non-redundant halfspaces: %d\n", numvertices);
    +    if (numcoplanars) {
    +      if (qh KEEPinside && qh KEEPcoplanar)
    +        s= "similar and redundant";
    +      else if (qh KEEPinside)
    +        s= "redundant";
    +      else
    +        s= "similar";
    +      qh_fprintf(fp, 9308, "  Number of %s halfspaces: %d\n", s, numcoplanars);
    +    }
    +    qh_fprintf(fp, 9309, "  Number of intersection points: %d\n", qh num_facets - qh num_visible);
    +    if (goodused)
    +      qh_fprintf(fp, 9310, "  Number of 'good' intersection points: %d\n", qh num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(fp, 9311, "  Number of%s non-simplicial intersection points: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else {
    +    qh_fprintf(fp, 9312, "\n\
    +Convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    qh_fprintf(fp, 9313, "  Number of vertices: %d\n", numvertices);
    +    if (numcoplanars) {
    +      if (qh KEEPinside && qh KEEPcoplanar)
    +        s= "coplanar and interior";
    +      else if (qh KEEPinside)
    +        s= "interior";
    +      else
    +        s= "coplanar";
    +      qh_fprintf(fp, 9314, "  Number of %s points: %d\n", s, numcoplanars);
    +    }
    +    qh_fprintf(fp, 9315, "  Number of facets: %d\n", qh num_facets - qh num_visible);
    +    if (goodused)
    +      qh_fprintf(fp, 9316, "  Number of 'good' facets: %d\n", qh num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(fp, 9317, "  Number of%s non-simplicial facets: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }
    +  if (numtricoplanars)
    +      qh_fprintf(fp, 9318, "  Number of triangulated facets: %d\n", numtricoplanars);
    +  qh_fprintf(fp, 9319, "\nStatistics for: %s | %s",
    +                      qh rbox_command, qh qhull_command);
    +  if (qh ROTATErandom != INT_MIN)
    +    qh_fprintf(fp, 9320, " QR%d\n\n", qh ROTATErandom);
    +  else
    +    qh_fprintf(fp, 9321, "\n\n");
    +  qh_fprintf(fp, 9322, "  Number of points processed: %d\n", zzval_(Zprocessed));
    +  qh_fprintf(fp, 9323, "  Number of hyperplanes created: %d\n", zzval_(Zsetplane));
    +  if (qh DELAUNAY)
    +    qh_fprintf(fp, 9324, "  Number of facets in hull: %d\n", qh num_facets - qh num_visible);
    +  qh_fprintf(fp, 9325, "  Number of distance tests for qhull: %d\n", zzval_(Zpartition)+
    +      zzval_(Zpartitionall)+zzval_(Znumvisibility)+zzval_(Zpartcoplanar));
    +#if 0  /* NOTE: must print before printstatistics() */
    +  {realT stddev, ave;
    +  qh_fprintf(fp, 9326, "  average new facet balance: %2.2g\n",
    +          wval_(Wnewbalance)/zval_(Zprocessed));
    +  stddev= qh_stddev(zval_(Zprocessed), wval_(Wnewbalance),
    +                                 wval_(Wnewbalance2), &ave);
    +  qh_fprintf(fp, 9327, "  new facet standard deviation: %2.2g\n", stddev);
    +  qh_fprintf(fp, 9328, "  average partition balance: %2.2g\n",
    +          wval_(Wpbalance)/zval_(Zpbalance));
    +  stddev= qh_stddev(zval_(Zpbalance), wval_(Wpbalance),
    +                                 wval_(Wpbalance2), &ave);
    +  qh_fprintf(fp, 9329, "  partition standard deviation: %2.2g\n", stddev);
    +  }
    +#endif
    +  if (nummerged) {
    +    qh_fprintf(fp, 9330,"  Number of distance tests for merging: %d\n",zzval_(Zbestdist)+
    +          zzval_(Zcentrumtests)+zzval_(Zdistconvex)+zzval_(Zdistcheck)+
    +          zzval_(Zdistzero));
    +    qh_fprintf(fp, 9331,"  Number of distance tests for checking: %d\n",zzval_(Zcheckpart));
    +    qh_fprintf(fp, 9332,"  Number of merged facets: %d\n", nummerged);
    +  }
    +  if (!qh RANDOMoutside && qh QHULLfinished) {
    +    cpu= (float)qh hulltime;
    +    cpu /= (float)qh_SECticks;
    +    wval_(Wcpu)= cpu;
    +    qh_fprintf(fp, 9333, "  CPU seconds to compute hull (after input): %2.4g\n", cpu);
    +  }
    +  if (qh RERUN) {
    +    if (!qh PREmerge && !qh MERGEexact)
    +      qh_fprintf(fp, 9334, "  Percentage of runs with precision errors: %4.1f\n",
    +           zzval_(Zretry)*100.0/qh build_cnt);  /* careful of order */
    +  }else if (qh JOGGLEmax < REALmax/2) {
    +    if (zzval_(Zretry))
    +      qh_fprintf(fp, 9335, "  After %d retries, input joggled by: %2.2g\n",
    +         zzval_(Zretry), qh JOGGLEmax);
    +    else
    +      qh_fprintf(fp, 9336, "  Input joggled by: %2.2g\n", qh JOGGLEmax);
    +  }
    +  if (qh totarea != 0.0)
    +    qh_fprintf(fp, 9337, "  %s facet area:   %2.8g\n",
    +            zzval_(Ztotmerge) ? "Approximate" : "Total", qh totarea);
    +  if (qh totvol != 0.0)
    +    qh_fprintf(fp, 9338, "  %s volume:       %2.8g\n",
    +            zzval_(Ztotmerge) ? "Approximate" : "Total", qh totvol);
    +  if (qh MERGING) {
    +    qh_outerinner(NULL, &outerplane, &innerplane);
    +    if (outerplane > 2 * qh DISTround) {
    +      qh_fprintf(fp, 9339, "  Maximum distance of %spoint above facet: %2.2g",
    +            (qh QHULLfinished ? "" : "merged "), outerplane);
    +      ratio= outerplane/(qh ONEmerge + qh DISTround);
    +      /* don't report ratio if MINoutside is large */
    +      if (ratio > 0.05 && 2* qh ONEmerge > qh MINoutside && qh JOGGLEmax > REALmax/2)
    +        qh_fprintf(fp, 9340, " (%.1fx)\n", ratio);
    +      else
    +        qh_fprintf(fp, 9341, "\n");
    +    }
    +    if (innerplane < -2 * qh DISTround) {
    +      qh_fprintf(fp, 9342, "  Maximum distance of %svertex below facet: %2.2g",
    +            (qh QHULLfinished ? "" : "merged "), innerplane);
    +      ratio= -innerplane/(qh ONEmerge+qh DISTround);
    +      if (ratio > 0.05 && qh JOGGLEmax > REALmax/2)
    +        qh_fprintf(fp, 9343, " (%.1fx)\n", ratio);
    +      else
    +        qh_fprintf(fp, 9344, "\n");
    +    }
    +  }
    +  qh_fprintf(fp, 9345, "\n");
    +} /* printsummary */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/libqhull.h b/xs/src/qhull/src/libqhull/libqhull.h
    new file mode 100644
    index 0000000000..677085808d
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/libqhull.h
    @@ -0,0 +1,1140 @@
    +/*
      ---------------------------------
    +
    +   libqhull.h
    +   user-level header file for using qhull.a library
    +
    +   see qh-qhull.htm, qhull_a.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/libqhull.h#7 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +
    +   NOTE: access to qh_qh is via the 'qh' macro.  This allows
    +   qh_qh to be either a pointer or a structure.  An example
    +   of using qh is "qh.DROPdim" which accesses the DROPdim
    +   field of qh_qh.  Similarly, access to qh_qhstat is via
    +   the 'qhstat' macro.
    +
    +   includes function prototypes for libqhull.c, geom.c, global.c, io.c, user.c
    +
    +   use mem.h for mem.c
    +   use qset.h for qset.c
    +
    +   see unix.c for an example of using libqhull.h
    +
    +   recompile qhull if you change this file
    +*/
    +
    +#ifndef qhDEFlibqhull
    +#define qhDEFlibqhull 1
    +
    +/*=========================== -included files ==============*/
    +
    +/* user_r.h first for QHULL_CRTDBG */
    +#include "user.h"      /* user definable constants (e.g., qh_QHpointer) */
    +
    +#include "mem.h"   /* Needed qhT in libqhull_r.h.  Here for compatibility */
    +#include "qset.h"   /* Needed for QHULL_LIB_CHECK */
    +/* include stat_r.h after defining boolT.  Needed for qhT in libqhull_r.h.  Here for compatibility and statT */
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __MWERKS__ && __POWERPC__
    +#include  
    +#include  
    +#include        
    +#endif
    +
    +#ifndef __STDC__
    +#ifndef __cplusplus
    +#if     !_MSC_VER
    +#error  Neither __STDC__ nor __cplusplus is defined.  Please use strict ANSI C or C++ to compile
    +#error  Qhull.  You may need to turn off compiler extensions in your project configuration.  If
    +#error  your compiler is a standard C compiler, you can delete this warning from libqhull.h
    +#endif
    +#endif
    +#endif
    +
    +/*============ constants and basic types ====================*/
    +
    +extern const char qh_version[]; /* defined in global.c */
    +extern const char qh_version2[]; /* defined in global.c */
    +
    +/*----------------------------------
    +
    +  coordT
    +    coordinates and coefficients are stored as realT (i.e., double)
    +
    +  notes:
    +    Qhull works well if realT is 'float'.  If so joggle (QJ) is not effective.
    +
    +    Could use 'float' for data and 'double' for calculations (realT vs. coordT)
    +      This requires many type casts, and adjusted error bounds.
    +      Also C compilers may do expressions in double anyway.
    +*/
    +#define coordT realT
    +
    +/*----------------------------------
    +
    +  pointT
    +    a point is an array of coordinates, usually qh.hull_dim
    +    qh_pointid returns
    +      qh_IDnone if point==0 or qh is undefined
    +      qh_IDinterior for qh.interior_point
    +      qh_IDunknown if point is neither in qh.first_point... nor qh.other_points
    +
    +  notes:
    +    qh.STOPcone and qh.STOPpoint assume that qh_IDunknown==-1 (other negative numbers indicate points)
    +    qh_IDunknown is also returned by getid_() for unknown facet, ridge, or vertex
    +*/
    +#define pointT coordT
    +typedef enum
    +{
    +    qh_IDnone = -3, qh_IDinterior = -2, qh_IDunknown = -1
    +}
    +qh_pointT;
    +
    +/*----------------------------------
    +
    +  flagT
    +    Boolean flag as a bit
    +*/
    +#define flagT unsigned int
    +
    +/*----------------------------------
    +
    +  boolT
    +    boolean value, either True or False
    +
    +  notes:
    +    needed for portability
    +    Use qh_False/qh_True as synonyms
    +*/
    +#define boolT unsigned int
    +#ifdef False
    +#undef False
    +#endif
    +#ifdef True
    +#undef True
    +#endif
    +#define False 0
    +#define True 1
    +#define qh_False 0
    +#define qh_True 1
    +
    +#include "stat.h"  /* after define of boolT */
    +
    +/*----------------------------------
    +
    +  qh_CENTER
    +    to distinguish facet->center
    +*/
    +typedef enum
    +{
    +    qh_ASnone = 0,   /* If not MERGING and not VORONOI */
    +    qh_ASvoronoi,    /* Set by qh_clearcenters on qh_prepare_output, or if not MERGING and VORONOI */
    +    qh_AScentrum     /* If MERGING (assumed during merging) */
    +}
    +qh_CENTER;
    +
    +/*----------------------------------
    +
    +  qh_PRINT
    +    output formats for printing (qh.PRINTout).
    +    'Fa' 'FV' 'Fc' 'FC'
    +
    +
    +   notes:
    +   some of these names are similar to qhT names.  The similar names are only
    +   used in switch statements in qh_printbegin() etc.
    +*/
    +typedef enum {qh_PRINTnone= 0,
    +  qh_PRINTarea, qh_PRINTaverage,           /* 'Fa' 'FV' 'Fc' 'FC' */
    +  qh_PRINTcoplanars, qh_PRINTcentrums,
    +  qh_PRINTfacets, qh_PRINTfacets_xridge,   /* 'f' 'FF' 'G' 'FI' 'Fi' 'Fn' */
    +  qh_PRINTgeom, qh_PRINTids, qh_PRINTinner, qh_PRINTneighbors,
    +  qh_PRINTnormals, qh_PRINTouter, qh_PRINTmaple, /* 'n' 'Fo' 'i' 'm' 'Fm' 'FM', 'o' */
    +  qh_PRINTincidences, qh_PRINTmathematica, qh_PRINTmerges, qh_PRINToff,
    +  qh_PRINToptions, qh_PRINTpointintersect, /* 'FO' 'Fp' 'FP' 'p' 'FQ' 'FS' */
    +  qh_PRINTpointnearest, qh_PRINTpoints, qh_PRINTqhull, qh_PRINTsize,
    +  qh_PRINTsummary, qh_PRINTtriangles,      /* 'Fs' 'Ft' 'Fv' 'FN' 'Fx' */
    +  qh_PRINTvertices, qh_PRINTvneighbors, qh_PRINTextremes,
    +  qh_PRINTEND} qh_PRINT;
    +
    +/*----------------------------------
    +
    +  qh_ALL
    +    argument flag for selecting everything
    +*/
    +#define qh_ALL      True
    +#define qh_NOupper  True     /* argument for qh_findbest */
    +#define qh_IScheckmax  True     /* argument for qh_findbesthorizon */
    +#define qh_ISnewfacets  True     /* argument for qh_findbest */
    +#define qh_RESETvisible  True     /* argument for qh_resetlists */
    +
    +/*----------------------------------
    +
    +  qh_ERR
    +    Qhull exit codes, for indicating errors
    +    See: MSG_ERROR and MSG_WARNING [user.h]
    +*/
    +#define qh_ERRnone  0    /* no error occurred during qhull */
    +#define qh_ERRinput 1    /* input inconsistency */
    +#define qh_ERRsingular 2 /* singular input data */
    +#define qh_ERRprec  3    /* precision error */
    +#define qh_ERRmem   4    /* insufficient memory, matches mem.h */
    +#define qh_ERRqhull 5    /* internal error detected, matches mem.h */
    +
    +/*----------------------------------
    +
    +qh_FILEstderr
    +Fake stderr to distinguish error output from normal output
    +For C++ interface.  Must redefine qh_fprintf_qhull
    +*/
    +#define qh_FILEstderr ((FILE*)1)
    +
    +/* ============ -structures- ====================
    +   each of the following structures is defined by a typedef
    +   all realT and coordT fields occur at the beginning of a structure
    +        (otherwise space may be wasted due to alignment)
    +   define all flags together and pack into 32-bit number
    +   DEFsetT is likewise defined in
    +   mem.h and qset.h
    +*/
    +
    +typedef struct vertexT vertexT;
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;          /* defined in qset.h */
    +#endif
    +
    +/*----------------------------------
    +
    +  facetT
    +    defines a facet
    +
    +  notes:
    +   qhull() generates the hull as a list of facets.
    +
    +  topological information:
    +    f.previous,next     doubly-linked list of facets
    +    f.vertices          set of vertices
    +    f.ridges            set of ridges
    +    f.neighbors         set of neighbors
    +    f.toporient         True if facet has top-orientation (else bottom)
    +
    +  geometric information:
    +    f.offset,normal     hyperplane equation
    +    f.maxoutside        offset to outer plane -- all points inside
    +    f.center            centrum for testing convexity
    +    f.simplicial        True if facet is simplicial
    +    f.flipped           True if facet does not include qh.interior_point
    +
    +  for constructing hull:
    +    f.visible           True if facet on list of visible facets (will be deleted)
    +    f.newfacet          True if facet on list of newly created facets
    +    f.coplanarset       set of points coplanar with this facet
    +                        (includes near-inside points for later testing)
    +    f.outsideset        set of points outside of this facet
    +    f.furthestdist      distance to furthest point of outside set
    +    f.visitid           marks visited facets during a loop
    +    f.replace           replacement facet for to-be-deleted, visible facets
    +    f.samecycle,newcycle cycle of facets for merging into horizon facet
    +
    +  see below for other flags and fields
    +*/
    +struct facetT {
    +#if !qh_COMPUTEfurthest
    +  coordT   furthestdist;/* distance to furthest point of outsideset */
    +#endif
    +#if qh_MAXoutside
    +  coordT   maxoutside;  /* max computed distance of point to facet
    +                        Before QHULLfinished this is an approximation
    +                        since maxdist not always set for mergefacet
    +                        Actual outer plane is +DISTround and
    +                        computed outer plane is +2*DISTround */
    +#endif
    +  coordT   offset;      /* exact offset of hyperplane from origin */
    +  coordT  *normal;      /* normal of hyperplane, hull_dim coefficients */
    +                        /*   if tricoplanar, shared with a neighbor */
    +  union {               /* in order of testing */
    +   realT   area;        /* area of facet, only in io.c if  ->isarea */
    +   facetT *replace;     /*  replacement facet if ->visible and NEWfacets
    +                             is NULL only if qh_mergedegen_redundant or interior */
    +   facetT *samecycle;   /*  cycle of facets from the same visible/horizon intersection,
    +                             if ->newfacet */
    +   facetT *newcycle;    /*  in horizon facet, current samecycle of new facets */
    +   facetT *trivisible;  /* visible facet for ->tricoplanar facets during qh_triangulate() */
    +   facetT *triowner;    /* owner facet for ->tricoplanar, !isarea facets w/ ->keepcentrum */
    +  }f;
    +  coordT  *center;      /* set according to qh.CENTERtype */
    +                        /*   qh_ASnone:    no center (not MERGING) */
    +                        /*   qh_AScentrum: centrum for testing convexity (qh_getcentrum) */
    +                        /*                 assumed qh_AScentrum while merging */
    +                        /*   qh_ASvoronoi: Voronoi center (qh_facetcenter) */
    +                        /* after constructing the hull, it may be changed (qh_clearcenter) */
    +                        /* if tricoplanar and !keepcentrum, shared with a neighbor */
    +  facetT  *previous;    /* previous facet in the facet_list */
    +  facetT  *next;        /* next facet in the facet_list */
    +  setT    *vertices;    /* vertices for this facet, inverse sorted by ID
    +                           if simplicial, 1st vertex was apex/furthest */
    +  setT    *ridges;      /* explicit ridges for nonsimplicial facets.
    +                           for simplicial facets, neighbors define the ridges */
    +  setT    *neighbors;   /* neighbors of the facet.  If simplicial, the kth
    +                           neighbor is opposite the kth vertex, and the first
    +                           neighbor is the horizon facet for the first vertex*/
    +  setT    *outsideset;  /* set of points outside this facet
    +                           if non-empty, last point is furthest
    +                           if NARROWhull, includes coplanars for partitioning*/
    +  setT    *coplanarset; /* set of points coplanar with this facet
    +                           > qh.min_vertex and <= facet->max_outside
    +                           a point is assigned to the furthest facet
    +                           if non-empty, last point is furthest away */
    +  unsigned visitid;     /* visit_id, for visiting all neighbors,
    +                           all uses are independent */
    +  unsigned id;          /* unique identifier from qh.facet_id */
    +  unsigned nummerge:9;  /* number of merges */
    +#define qh_MAXnummerge 511 /*     2^9-1, 32 flags total, see "flags:" in io.c */
    +  flagT    tricoplanar:1; /* True if TRIangulate and simplicial and coplanar with a neighbor */
    +                          /*   all tricoplanars share the same apex */
    +                          /*   all tricoplanars share the same ->center, ->normal, ->offset, ->maxoutside */
    +                          /*     ->keepcentrum is true for the owner.  It has the ->coplanareset */
    +                          /*   if ->degenerate, does not span facet (one logical ridge) */
    +                          /*   during qh_triangulate, f.trivisible points to original facet */
    +  flagT    newfacet:1;  /* True if facet on qh.newfacet_list (new or merged) */
    +  flagT    visible:1;   /* True if visible facet (will be deleted) */
    +  flagT    toporient:1; /* True if created with top orientation
    +                           after merging, use ridge orientation */
    +  flagT    simplicial:1;/* True if simplicial facet, ->ridges may be implicit */
    +  flagT    seen:1;      /* used to perform operations only once, like visitid */
    +  flagT    seen2:1;     /* used to perform operations only once, like visitid */
    +  flagT    flipped:1;   /* True if facet is flipped */
    +  flagT    upperdelaunay:1; /* True if facet is upper envelope of Delaunay triangulation */
    +  flagT    notfurthest:1; /* True if last point of outsideset is not furthest*/
    +
    +/*-------- flags primarily for output ---------*/
    +  flagT    good:1;      /* True if a facet marked good for output */
    +  flagT    isarea:1;    /* True if facet->f.area is defined */
    +
    +/*-------- flags for merging ------------------*/
    +  flagT    dupridge:1;  /* True if duplicate ridge in facet */
    +  flagT    mergeridge:1; /* True if facet or neighbor contains a qh_MERGEridge
    +                            ->normal defined (also defined for mergeridge2) */
    +  flagT    mergeridge2:1; /* True if neighbor contains a qh_MERGEridge (mark_dupridges */
    +  flagT    coplanar:1;  /* True if horizon facet is coplanar at last use */
    +  flagT     mergehorizon:1; /* True if will merge into horizon (->coplanar) */
    +  flagT     cycledone:1;/* True if mergecycle_all already done */
    +  flagT    tested:1;    /* True if facet convexity has been tested (false after merge */
    +  flagT    keepcentrum:1; /* True if keep old centrum after a merge, or marks owner for ->tricoplanar */
    +  flagT    newmerge:1;  /* True if facet is newly merged for reducevertices */
    +  flagT    degenerate:1; /* True if facet is degenerate (degen_mergeset or ->tricoplanar) */
    +  flagT    redundant:1;  /* True if facet is redundant (degen_mergeset) */
    +};
    +
    +
    +/*----------------------------------
    +
    +  ridgeT
    +    defines a ridge
    +
    +  notes:
    +  a ridge is hull_dim-1 simplex between two neighboring facets.  If the
    +  facets are non-simplicial, there may be more than one ridge between
    +  two facets.  E.G. a 4-d hypercube has two triangles between each pair
    +  of neighboring facets.
    +
    +  topological information:
    +    vertices            a set of vertices
    +    top,bottom          neighboring facets with orientation
    +
    +  geometric information:
    +    tested              True if ridge is clearly convex
    +    nonconvex           True if ridge is non-convex
    +*/
    +struct ridgeT {
    +  setT    *vertices;    /* vertices belonging to this ridge, inverse sorted by ID
    +                           NULL if a degen ridge (matchsame) */
    +  facetT  *top;         /* top facet this ridge is part of */
    +  facetT  *bottom;      /* bottom facet this ridge is part of */
    +  unsigned id;          /* unique identifier.  Same size as vertex_id and ridge_id */
    +  flagT    seen:1;      /* used to perform operations only once */
    +  flagT    tested:1;    /* True when ridge is tested for convexity */
    +  flagT    nonconvex:1; /* True if getmergeset detected a non-convex neighbor
    +                           only one ridge between neighbors may have nonconvex */
    +};
    +
    +/*----------------------------------
    +
    +  vertexT
    +     defines a vertex
    +
    +  topological information:
    +    next,previous       doubly-linked list of all vertices
    +    neighbors           set of adjacent facets (only if qh.VERTEXneighbors)
    +
    +  geometric information:
    +    point               array of DIM3 coordinates
    +*/
    +struct vertexT {
    +  vertexT *next;        /* next vertex in vertex_list */
    +  vertexT *previous;    /* previous vertex in vertex_list */
    +  pointT  *point;       /* hull_dim coordinates (coordT) */
    +  setT    *neighbors;   /* neighboring facets of vertex, qh_vertexneighbors()
    +                           inits in io.c or after first merge */
    +  unsigned id;          /* unique identifier.  Same size as qh.vertex_id and qh.ridge_id */
    +  unsigned visitid;     /* for use with qh.vertex_visit, size must match */
    +  flagT    seen:1;      /* used to perform operations only once */
    +  flagT    seen2:1;     /* another seen flag */
    +  flagT    delridge:1;  /* vertex was part of a deleted ridge */
    +  flagT    deleted:1;   /* true if vertex on qh.del_vertices */
    +  flagT    newlist:1;   /* true if vertex on qh.newvertex_list */
    +};
    +
    +/*======= -global variables -qh ============================*/
    +
    +/*----------------------------------
    +
    +  qh
    +   all global variables for qhull are in qh, qhmem, and qhstat
    +
    +  notes:
    +   qhmem is defined in mem.h, qhstat is defined in stat.h, qhrbox is defined in rboxpoints.h
    +   Access to qh_qh is via the "qh" macro.  See qh_QHpointer in user.h
    +
    +   All global variables for qhull are in qh, qhmem, and qhstat
    +   qh must be unique for each instance of qhull
    +   qhstat may be shared between qhull instances.
    +   qhmem may be shared across multiple instances of Qhull.
    +   Rbox uses global variables rbox_inuse and rbox, but does not persist data across calls.
    +
    +   Qhull is not multi-threaded.  Global state could be stored in thread-local storage.
    +
    +   QHULL_LIB_CHECK checks that a program and the corresponding
    +   qhull library were built with the same type of header files.
    +*/
    +
    +typedef struct qhT qhT;
    +
    +#define QHULL_NON_REENTRANT 0
    +#define QHULL_QH_POINTER 1
    +#define QHULL_REENTRANT 2
    +
    +#if qh_QHpointer_dllimport
    +#define qh qh_qh->
    +__declspec(dllimport) extern qhT *qh_qh;     /* allocated in global.c */
    +#define QHULL_LIB_TYPE QHULL_QH_POINTER
    +
    +#elif qh_QHpointer
    +#define qh qh_qh->
    +extern qhT *qh_qh;     /* allocated in global.c */
    +#define QHULL_LIB_TYPE QHULL_QH_POINTER
    +
    +#elif qh_dllimport
    +#define qh qh_qh.
    +__declspec(dllimport) extern qhT qh_qh;      /* allocated in global.c */
    +#define QHULL_LIB_TYPE QHULL_NON_REENTRANT
    +
    +#else
    +#define qh qh_qh.
    +extern qhT qh_qh;
    +#define QHULL_LIB_TYPE QHULL_NON_REENTRANT
    +#endif
    +
    +#define QHULL_LIB_CHECK qh_lib_check(QHULL_LIB_TYPE, sizeof(qhT), sizeof(vertexT), sizeof(ridgeT), sizeof(facetT), sizeof(setT), sizeof(qhmemT));
    +#define QHULL_LIB_CHECK_RBOX qh_lib_check(QHULL_LIB_TYPE, sizeof(qhT), sizeof(vertexT), sizeof(ridgeT), sizeof(facetT), 0, 0);
    +
    +struct qhT {
    +
    +/*----------------------------------
    +
    +  qh constants
    +    configuration flags and constants for Qhull
    +
    +  notes:
    +    The user configures Qhull by defining flags.  They are
    +    copied into qh by qh_setflags().  qh-quick.htm#options defines the flags.
    +*/
    +  boolT ALLpoints;        /* true 'Qs' if search all points for initial simplex */
    +  boolT ANGLEmerge;       /* true 'Qa' if sort potential merges by angle */
    +  boolT APPROXhull;       /* true 'Wn' if MINoutside set */
    +  realT   MINoutside;     /*   'Wn' min. distance for an outside point */
    +  boolT ANNOTATEoutput;   /* true 'Ta' if annotate output with message codes */
    +  boolT ATinfinity;       /* true 'Qz' if point num_points-1 is "at-infinity"
    +                             for improving precision in Delaunay triangulations */
    +  boolT AVOIDold;         /* true 'Q4' if avoid old->new merges */
    +  boolT BESToutside;      /* true 'Qf' if partition points into best outsideset */
    +  boolT CDDinput;         /* true 'Pc' if input uses CDD format (1.0/offset first) */
    +  boolT CDDoutput;        /* true 'PC' if print normals in CDD format (offset first) */
    +  boolT CHECKfrequently;  /* true 'Tc' if checking frequently */
    +  realT premerge_cos;     /*   'A-n'   cos_max when pre merging */
    +  realT postmerge_cos;    /*   'An'    cos_max when post merging */
    +  boolT DELAUNAY;         /* true 'd' if computing DELAUNAY triangulation */
    +  boolT DOintersections;  /* true 'Gh' if print hyperplane intersections */
    +  int   DROPdim;          /* drops dim 'GDn' for 4-d -> 3-d output */
    +  boolT FORCEoutput;      /* true 'Po' if forcing output despite degeneracies */
    +  int   GOODpoint;        /* 1+n for 'QGn', good facet if visible/not(-) from point n*/
    +  pointT *GOODpointp;     /*   the actual point */
    +  boolT GOODthreshold;    /* true if qh.lower_threshold/upper_threshold defined
    +                             false if qh.SPLITthreshold */
    +  int   GOODvertex;       /* 1+n, good facet if vertex for point n */
    +  pointT *GOODvertexp;     /*   the actual point */
    +  boolT HALFspace;        /* true 'Hn,n,n' if halfspace intersection */
    +  boolT ISqhullQh;        /* Set by Qhull.cpp on initialization */
    +  int   IStracing;        /* trace execution, 0=none, 1=least, 4=most, -1=events */
    +  int   KEEParea;         /* 'PAn' number of largest facets to keep */
    +  boolT KEEPcoplanar;     /* true 'Qc' if keeping nearest facet for coplanar points */
    +  boolT KEEPinside;       /* true 'Qi' if keeping nearest facet for inside points
    +                              set automatically if 'd Qc' */
    +  int   KEEPmerge;        /* 'PMn' number of facets to keep with most merges */
    +  realT KEEPminArea;      /* 'PFn' minimum facet area to keep */
    +  realT MAXcoplanar;      /* 'Un' max distance below a facet to be coplanar*/
    +  boolT MERGEexact;       /* true 'Qx' if exact merges (coplanar, degen, dupridge, flipped) */
    +  boolT MERGEindependent; /* true 'Q2' if merging independent sets */
    +  boolT MERGING;          /* true if exact-, pre- or post-merging, with angle and centrum tests */
    +  realT   premerge_centrum;  /*   'C-n' centrum_radius when pre merging.  Default is round-off */
    +  realT   postmerge_centrum; /*   'Cn' centrum_radius when post merging.  Default is round-off */
    +  boolT MERGEvertices;    /* true 'Q3' if merging redundant vertices */
    +  realT MINvisible;       /* 'Vn' min. distance for a facet to be visible */
    +  boolT NOnarrow;         /* true 'Q10' if no special processing for narrow distributions */
    +  boolT NOnearinside;     /* true 'Q8' if ignore near-inside points when partitioning */
    +  boolT NOpremerge;       /* true 'Q0' if no defaults for C-0 or Qx */
    +  boolT NOwide;           /* true 'Q12' if no error on wide merge due to duplicate ridge */
    +  boolT ONLYgood;         /* true 'Qg' if process points with good visible or horizon facets */
    +  boolT ONLYmax;          /* true 'Qm' if only process points that increase max_outside */
    +  boolT PICKfurthest;     /* true 'Q9' if process furthest of furthest points*/
    +  boolT POSTmerge;        /* true if merging after buildhull (Cn or An) */
    +  boolT PREmerge;         /* true if merging during buildhull (C-n or A-n) */
    +                        /* NOTE: some of these names are similar to qh_PRINT names */
    +  boolT PRINTcentrums;    /* true 'Gc' if printing centrums */
    +  boolT PRINTcoplanar;    /* true 'Gp' if printing coplanar points */
    +  int   PRINTdim;         /* print dimension for Geomview output */
    +  boolT PRINTdots;        /* true 'Ga' if printing all points as dots */
    +  boolT PRINTgood;        /* true 'Pg' if printing good facets */
    +  boolT PRINTinner;       /* true 'Gi' if printing inner planes */
    +  boolT PRINTneighbors;   /* true 'PG' if printing neighbors of good facets */
    +  boolT PRINTnoplanes;    /* true 'Gn' if printing no planes */
    +  boolT PRINToptions1st;  /* true 'FO' if printing options to stderr */
    +  boolT PRINTouter;       /* true 'Go' if printing outer planes */
    +  boolT PRINTprecision;   /* false 'Pp' if not reporting precision problems */
    +  qh_PRINT PRINTout[qh_PRINTEND]; /* list of output formats to print */
    +  boolT PRINTridges;      /* true 'Gr' if print ridges */
    +  boolT PRINTspheres;     /* true 'Gv' if print vertices as spheres */
    +  boolT PRINTstatistics;  /* true 'Ts' if printing statistics to stderr */
    +  boolT PRINTsummary;     /* true 's' if printing summary to stderr */
    +  boolT PRINTtransparent; /* true 'Gt' if print transparent outer ridges */
    +  boolT PROJECTdelaunay;  /* true if DELAUNAY, no readpoints() and
    +                             need projectinput() for Delaunay in qh_init_B */
    +  int   PROJECTinput;     /* number of projected dimensions 'bn:0Bn:0' */
    +  boolT QUICKhelp;        /* true if quick help message for degen input */
    +  boolT RANDOMdist;       /* true if randomly change distplane and setfacetplane */
    +  realT RANDOMfactor;     /*    maximum random perturbation */
    +  realT RANDOMa;          /*    qh_randomfactor is randr * RANDOMa + RANDOMb */
    +  realT RANDOMb;
    +  boolT RANDOMoutside;    /* true if select a random outside point */
    +  int   REPORTfreq;       /* buildtracing reports every n facets */
    +  int   REPORTfreq2;      /* tracemerging reports every REPORTfreq/2 facets */
    +  int   RERUN;            /* 'TRn' rerun qhull n times (qh.build_cnt) */
    +  int   ROTATErandom;     /* 'QRn' seed, 0 time, >= rotate input */
    +  boolT SCALEinput;       /* true 'Qbk' if scaling input */
    +  boolT SCALElast;        /* true 'Qbb' if scale last coord to max prev coord */
    +  boolT SETroundoff;      /* true 'E' if qh.DISTround is predefined */
    +  boolT SKIPcheckmax;     /* true 'Q5' if skip qh_check_maxout */
    +  boolT SKIPconvex;       /* true 'Q6' if skip convexity testing during pre-merge */
    +  boolT SPLITthresholds;  /* true if upper_/lower_threshold defines a region
    +                               used only for printing (!for qh.ONLYgood) */
    +  int   STOPcone;         /* 'TCn' 1+n for stopping after cone for point n */
    +                          /*       also used by qh_build_withresart for err exit*/
    +  int   STOPpoint;        /* 'TVn' 'TV-n' 1+n for stopping after/before(-)
    +                                        adding point n */
    +  int   TESTpoints;       /* 'QTn' num of test points after qh.num_points.  Test points always coplanar. */
    +  boolT TESTvneighbors;   /*  true 'Qv' if test vertex neighbors at end */
    +  int   TRACElevel;       /* 'Tn' conditional IStracing level */
    +  int   TRACElastrun;     /*  qh.TRACElevel applies to last qh.RERUN */
    +  int   TRACEpoint;       /* 'TPn' start tracing when point n is a vertex */
    +  realT TRACEdist;        /* 'TWn' start tracing when merge distance too big */
    +  int   TRACEmerge;       /* 'TMn' start tracing before this merge */
    +  boolT TRIangulate;      /* true 'Qt' if triangulate non-simplicial facets */
    +  boolT TRInormals;       /* true 'Q11' if triangulate duplicates ->normal and ->center (sets Qt) */
    +  boolT UPPERdelaunay;    /* true 'Qu' if computing furthest-site Delaunay */
    +  boolT USEstdout;        /* true 'Tz' if using stdout instead of stderr */
    +  boolT VERIFYoutput;     /* true 'Tv' if verify output at end of qhull */
    +  boolT VIRTUALmemory;    /* true 'Q7' if depth-first processing in buildhull */
    +  boolT VORONOI;          /* true 'v' if computing Voronoi diagram */
    +
    +  /*--------input constants ---------*/
    +  realT AREAfactor;       /* 1/(hull_dim-1)! for converting det's to area */
    +  boolT DOcheckmax;       /* true if calling qh_check_maxout (qh_initqhull_globals) */
    +  char  *feasible_string;  /* feasible point 'Hn,n,n' for halfspace intersection */
    +  coordT *feasible_point;  /*    as coordinates, both malloc'd */
    +  boolT GETarea;          /* true 'Fa', 'FA', 'FS', 'PAn', 'PFn' if compute facet area/Voronoi volume in io.c */
    +  boolT KEEPnearinside;   /* true if near-inside points in coplanarset */
    +  int   hull_dim;         /* dimension of hull, set by initbuffers */
    +  int   input_dim;        /* dimension of input, set by initbuffers */
    +  int   num_points;       /* number of input points */
    +  pointT *first_point;    /* array of input points, see POINTSmalloc */
    +  boolT POINTSmalloc;     /*   true if qh.first_point/num_points allocated */
    +  pointT *input_points;   /* copy of original qh.first_point for input points for qh_joggleinput */
    +  boolT input_malloc;     /* true if qh.input_points malloc'd */
    +  char  qhull_command[256];/* command line that invoked this program */
    +  int   qhull_commandsiz2; /*    size of qhull_command at qh_clear_outputflags */
    +  char  rbox_command[256]; /* command line that produced the input points */
    +  char  qhull_options[512];/* descriptive list of options */
    +  int   qhull_optionlen;  /*    length of last line */
    +  int   qhull_optionsiz;  /*    size of qhull_options at qh_build_withrestart */
    +  int   qhull_optionsiz2; /*    size of qhull_options at qh_clear_outputflags */
    +  int   run_id;           /* non-zero, random identifier for this instance of qhull */
    +  boolT VERTEXneighbors;  /* true if maintaining vertex neighbors */
    +  boolT ZEROcentrum;      /* true if 'C-0' or 'C-0 Qx'.  sets ZEROall_ok */
    +  realT *upper_threshold; /* don't print if facet->normal[k]>=upper_threshold[k]
    +                             must set either GOODthreshold or SPLITthreshold
    +                             if Delaunay, default is 0.0 for upper envelope */
    +  realT *lower_threshold; /* don't print if facet->normal[k] <=lower_threshold[k] */
    +  realT *upper_bound;     /* scale point[k] to new upper bound */
    +  realT *lower_bound;     /* scale point[k] to new lower bound
    +                             project if both upper_ and lower_bound == 0 */
    +
    +/*----------------------------------
    +
    +  qh precision constants
    +    precision constants for Qhull
    +
    +  notes:
    +    qh_detroundoff() computes the maximum roundoff error for distance
    +    and other computations.  It also sets default values for the
    +    qh constants above.
    +*/
    +  realT ANGLEround;       /* max round off error for angles */
    +  realT centrum_radius;   /* max centrum radius for convexity (roundoff added) */
    +  realT cos_max;          /* max cosine for convexity (roundoff added) */
    +  realT DISTround;        /* max round off error for distances, 'E' overrides qh_distround() */
    +  realT MAXabs_coord;     /* max absolute coordinate */
    +  realT MAXlastcoord;     /* max last coordinate for qh_scalelast */
    +  realT MAXsumcoord;      /* max sum of coordinates */
    +  realT MAXwidth;         /* max rectilinear width of point coordinates */
    +  realT MINdenom_1;       /* min. abs. value for 1/x */
    +  realT MINdenom;         /*    use divzero if denominator < MINdenom */
    +  realT MINdenom_1_2;     /* min. abs. val for 1/x that allows normalization */
    +  realT MINdenom_2;       /*    use divzero if denominator < MINdenom_2 */
    +  realT MINlastcoord;     /* min. last coordinate for qh_scalelast */
    +  boolT NARROWhull;       /* set in qh_initialhull if angle < qh_MAXnarrow */
    +  realT *NEARzero;        /* hull_dim array for near zero in gausselim */
    +  realT NEARinside;       /* keep points for qh_check_maxout if close to facet */
    +  realT ONEmerge;         /* max distance for merging simplicial facets */
    +  realT outside_err;      /* application's epsilon for coplanar points
    +                             qh_check_bestdist() qh_check_points() reports error if point outside */
    +  realT WIDEfacet;        /* size of wide facet for skipping ridge in
    +                             area computation and locking centrum */
    +
    +/*----------------------------------
    +
    +  qh internal constants
    +    internal constants for Qhull
    +*/
    +  char qhull[sizeof("qhull")]; /* "qhull" for checking ownership while debugging */
    +  jmp_buf errexit;        /* exit label for qh_errexit, defined by setjmp() and NOerrexit */
    +  char jmpXtra[40];       /* extra bytes in case jmp_buf is defined wrong by compiler */
    +  jmp_buf restartexit;    /* restart label for qh_errexit, defined by setjmp() and ALLOWrestart */
    +  char jmpXtra2[40];      /* extra bytes in case jmp_buf is defined wrong by compiler*/
    +  FILE *fin;              /* pointer to input file, init by qh_initqhull_start2 */
    +  FILE *fout;             /* pointer to output file */
    +  FILE *ferr;             /* pointer to error file */
    +  pointT *interior_point; /* center point of the initial simplex*/
    +  int normal_size;     /* size in bytes for facet normals and point coords*/
    +  int center_size;     /* size in bytes for Voronoi centers */
    +  int   TEMPsize;         /* size for small, temporary sets (in quick mem) */
    +
    +/*----------------------------------
    +
    +  qh facet and vertex lists
    +    defines lists of facets, new facets, visible facets, vertices, and
    +    new vertices.  Includes counts, next ids, and trace ids.
    +  see:
    +    qh_resetlists()
    +*/
    +  facetT *facet_list;     /* first facet */
    +  facetT  *facet_tail;     /* end of facet_list (dummy facet) */
    +  facetT *facet_next;     /* next facet for buildhull()
    +                             previous facets do not have outside sets
    +                             NARROWhull: previous facets may have coplanar outside sets for qh_outcoplanar */
    +  facetT *newfacet_list;  /* list of new facets to end of facet_list */
    +  facetT *visible_list;   /* list of visible facets preceding newfacet_list,
    +                             facet->visible set */
    +  int       num_visible;  /* current number of visible facets */
    +  unsigned tracefacet_id;  /* set at init, then can print whenever */
    +  facetT *tracefacet;     /*   set in newfacet/mergefacet, undone in delfacet*/
    +  unsigned tracevertex_id;  /* set at buildtracing, can print whenever */
    +  vertexT *tracevertex;     /*   set in newvertex, undone in delvertex*/
    +  vertexT *vertex_list;     /* list of all vertices, to vertex_tail */
    +  vertexT  *vertex_tail;    /*      end of vertex_list (dummy vertex) */
    +  vertexT *newvertex_list; /* list of vertices in newfacet_list, to vertex_tail
    +                             all vertices have 'newlist' set */
    +  int   num_facets;       /* number of facets in facet_list
    +                             includes visible faces (num_visible) */
    +  int   num_vertices;     /* number of vertices in facet_list */
    +  int   num_outside;      /* number of points in outsidesets (for tracing and RANDOMoutside)
    +                               includes coplanar outsideset points for NARROWhull/qh_outcoplanar() */
    +  int   num_good;         /* number of good facets (after findgood_all) */
    +  unsigned facet_id;      /* ID of next, new facet from newfacet() */
    +  unsigned ridge_id;      /* ID of next, new ridge from newridge() */
    +  unsigned vertex_id;     /* ID of next, new vertex from newvertex() */
    +
    +/*----------------------------------
    +
    +  qh global variables
    +    defines minimum and maximum distances, next visit ids, several flags,
    +    and other global variables.
    +    initialize in qh_initbuild or qh_maxmin if used in qh_buildhull
    +*/
    +  unsigned long hulltime; /* ignore time to set up input and randomize */
    +                          /*   use unsigned to avoid wrap-around errors */
    +  boolT ALLOWrestart;     /* true if qh_precision can use qh.restartexit */
    +  int   build_cnt;        /* number of calls to qh_initbuild */
    +  qh_CENTER CENTERtype;   /* current type of facet->center, qh_CENTER */
    +  int   furthest_id;      /* pointid of furthest point, for tracing */
    +  facetT *GOODclosest;    /* closest facet to GOODthreshold in qh_findgood */
    +  boolT hasAreaVolume;    /* true if totarea, totvol was defined by qh_getarea */
    +  boolT hasTriangulation; /* true if triangulation created by qh_triangulate */
    +  realT JOGGLEmax;        /* set 'QJn' if randomly joggle input */
    +  boolT maxoutdone;       /* set qh_check_maxout(), cleared by qh_addpoint() */
    +  realT max_outside;      /* maximum distance from a point to a facet,
    +                               before roundoff, not simplicial vertices
    +                               actual outer plane is +DISTround and
    +                               computed outer plane is +2*DISTround */
    +  realT max_vertex;       /* maximum distance (>0) from vertex to a facet,
    +                               before roundoff, due to a merge */
    +  realT min_vertex;       /* minimum distance (<0) from vertex to a facet,
    +                               before roundoff, due to a merge
    +                               if qh.JOGGLEmax, qh_makenewplanes sets it
    +                               recomputed if qh.DOcheckmax, default -qh.DISTround */
    +  boolT NEWfacets;        /* true while visible facets invalid due to new or merge
    +                              from makecone/attachnewfacets to deletevisible */
    +  boolT findbestnew;      /* true if partitioning calls qh_findbestnew */
    +  boolT findbest_notsharp; /* true if new facets are at least 90 degrees */
    +  boolT NOerrexit;        /* true if qh.errexit is not available, cleared after setjmp */
    +  realT PRINTcradius;     /* radius for printing centrums */
    +  realT PRINTradius;      /* radius for printing vertex spheres and points */
    +  boolT POSTmerging;      /* true when post merging */
    +  int   printoutvar;      /* temporary variable for qh_printbegin, etc. */
    +  int   printoutnum;      /* number of facets printed */
    +  boolT QHULLfinished;    /* True after qhull() is finished */
    +  realT totarea;          /* 'FA': total facet area computed by qh_getarea, hasAreaVolume */
    +  realT totvol;           /* 'FA': total volume computed by qh_getarea, hasAreaVolume */
    +  unsigned int visit_id;  /* unique ID for searching neighborhoods, */
    +  unsigned int vertex_visit; /* unique ID for searching vertices, reset with qh_buildtracing */
    +  boolT ZEROall_ok;       /* True if qh_checkzero always succeeds */
    +  boolT WAScoplanar;      /* True if qh_partitioncoplanar (qh_check_maxout) */
    +
    +/*----------------------------------
    +
    +  qh global sets
    +    defines sets for merging, initial simplex, hashing, extra input points,
    +    and deleted vertices
    +*/
    +  setT *facet_mergeset;   /* temporary set of merges to be done */
    +  setT *degen_mergeset;   /* temporary set of degenerate and redundant merges */
    +  setT *hash_table;       /* hash table for matching ridges in qh_matchfacets
    +                             size is setsize() */
    +  setT *other_points;     /* additional points */
    +  setT *del_vertices;     /* vertices to partition and delete with visible
    +                             facets.  Have deleted set for checkfacet */
    +
    +/*----------------------------------
    +
    +  qh global buffers
    +    defines buffers for maxtrix operations, input, and error messages
    +*/
    +  coordT *gm_matrix;      /* (dim+1)Xdim matrix for geom.c */
    +  coordT **gm_row;        /* array of gm_matrix rows */
    +  char* line;             /* malloc'd input line of maxline+1 chars */
    +  int maxline;
    +  coordT *half_space;     /* malloc'd input array for halfspace (qh normal_size+coordT) */
    +  coordT *temp_malloc;    /* malloc'd input array for points */
    +
    +/*----------------------------------
    +
    +  qh static variables
    +    defines static variables for individual functions
    +
    +  notes:
    +    do not use 'static' within a function.  Multiple instances of qhull
    +    may exist.
    +
    +    do not assume zero initialization, 'QPn' may cause a restart
    +*/
    +  boolT ERREXITcalled;    /* true during qh_errexit (prevents duplicate calls */
    +  boolT firstcentrum;     /* for qh_printcentrum */
    +  boolT old_randomdist;   /* save RANDOMdist flag during io, tracing, or statistics */
    +  setT *coplanarfacetset;  /* set of coplanar facets for searching qh_findbesthorizon() */
    +  realT last_low;         /* qh_scalelast parameters for qh_setdelaunay */
    +  realT last_high;
    +  realT last_newhigh;
    +  unsigned lastreport;    /* for qh_buildtracing */
    +  int mergereport;        /* for qh_tracemerging */
    +  qhstatT *old_qhstat;    /* for saving qh_qhstat in save_qhull() and UsingLibQhull.  Free with qh_free() */
    +  setT *old_tempstack;    /* for saving qhmem.tempstack in save_qhull */
    +  int   ridgeoutnum;      /* number of ridges for 4OFF output (qh_printbegin,etc) */
    +};
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  otherfacet_(ridge, facet)
    +    return neighboring facet for a ridge in facet
    +*/
    +#define otherfacet_(ridge, facet) \
    +                        (((ridge)->top == (facet)) ? (ridge)->bottom : (ridge)->top)
    +
    +/*----------------------------------
    +
    +  getid_(p)
    +    return int ID for facet, ridge, or vertex
    +    return qh_IDunknown(-1) if NULL
    +*/
    +#define getid_(p)       ((p) ? (int)((p)->id) : qh_IDunknown)
    +
    +/*============== FORALL macros ===================*/
    +
    +/*----------------------------------
    +
    +  FORALLfacets { ... }
    +    assign 'facet' to each facet in qh.facet_list
    +
    +  notes:
    +    uses 'facetT *facet;'
    +    assumes last facet is a sentinel
    +
    +  see:
    +    FORALLfacet_( facetlist )
    +*/
    +#define FORALLfacets for (facet=qh facet_list;facet && facet->next;facet=facet->next)
    +
    +/*----------------------------------
    +
    +  FORALLpoints { ... }
    +    assign 'point' to each point in qh.first_point, qh.num_points
    +
    +  declare:
    +    coordT *point, *pointtemp;
    +*/
    +#define FORALLpoints FORALLpoint_(qh first_point, qh num_points)
    +
    +/*----------------------------------
    +
    +  FORALLpoint_( points, num) { ... }
    +    assign 'point' to each point in points array of num points
    +
    +  declare:
    +    coordT *point, *pointtemp;
    +*/
    +#define FORALLpoint_(points, num) for (point= (points), \
    +      pointtemp= (points)+qh hull_dim*(num); point < pointtemp; point += qh hull_dim)
    +
    +/*----------------------------------
    +
    +  FORALLvertices { ... }
    +    assign 'vertex' to each vertex in qh.vertex_list
    +
    +  declare:
    +    vertexT *vertex;
    +
    +  notes:
    +    assumes qh.vertex_list terminated with a sentinel
    +*/
    +#define FORALLvertices for (vertex=qh vertex_list;vertex && vertex->next;vertex= vertex->next)
    +
    +/*----------------------------------
    +
    +  FOREACHfacet_( facets ) { ... }
    +    assign 'facet' to each facet in facets
    +
    +  declare:
    +    facetT *facet, **facetp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHfacet_(facets)    FOREACHsetelement_(facetT, facets, facet)
    +
    +/*----------------------------------
    +
    +  FOREACHneighbor_( facet ) { ... }
    +    assign 'neighbor' to each neighbor in facet->neighbors
    +
    +  FOREACHneighbor_( vertex ) { ... }
    +    assign 'neighbor' to each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighbor, **neighborp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHneighbor_(facet)  FOREACHsetelement_(facetT, facet->neighbors, neighbor)
    +
    +/*----------------------------------
    +
    +  FOREACHpoint_( points ) { ... }
    +    assign 'point' to each point in points set
    +
    +  declare:
    +    pointT *point, **pointp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHpoint_(points)    FOREACHsetelement_(pointT, points, point)
    +
    +/*----------------------------------
    +
    +  FOREACHridge_( ridges ) { ... }
    +    assign 'ridge' to each ridge in ridges set
    +
    +  declare:
    +    ridgeT *ridge, **ridgep;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHridge_(ridges)    FOREACHsetelement_(ridgeT, ridges, ridge)
    +
    +/*----------------------------------
    +
    +  FOREACHvertex_( vertices ) { ... }
    +    assign 'vertex' to each vertex in vertices set
    +
    +  declare:
    +    vertexT *vertex, **vertexp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHvertex_(vertices) FOREACHsetelement_(vertexT, vertices,vertex)
    +
    +/*----------------------------------
    +
    +  FOREACHfacet_i_( facets ) { ... }
    +    assign 'facet' and 'facet_i' for each facet in facets set
    +
    +  declare:
    +    facetT *facet;
    +    int     facet_n, facet_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHfacet_i_(facets)    FOREACHsetelement_i_(facetT, facets, facet)
    +
    +/*----------------------------------
    +
    +  FOREACHneighbor_i_( facet ) { ... }
    +    assign 'neighbor' and 'neighbor_i' for each neighbor in facet->neighbors
    +
    +  FOREACHneighbor_i_( vertex ) { ... }
    +    assign 'neighbor' and 'neighbor_i' for each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighbor;
    +    int     neighbor_n, neighbor_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHneighbor_i_(facet)  FOREACHsetelement_i_(facetT, facet->neighbors, neighbor)
    +
    +/*----------------------------------
    +
    +  FOREACHpoint_i_( points ) { ... }
    +    assign 'point' and 'point_i' for each point in points set
    +
    +  declare:
    +    pointT *point;
    +    int     point_n, point_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHpoint_i_(points)    FOREACHsetelement_i_(pointT, points, point)
    +
    +/*----------------------------------
    +
    +  FOREACHridge_i_( ridges ) { ... }
    +    assign 'ridge' and 'ridge_i' for each ridge in ridges set
    +
    +  declare:
    +    ridgeT *ridge;
    +    int     ridge_n, ridge_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHridge_i_(ridges)    FOREACHsetelement_i_(ridgeT, ridges, ridge)
    +
    +/*----------------------------------
    +
    +  FOREACHvertex_i_( vertices ) { ... }
    +    assign 'vertex' and 'vertex_i' for each vertex in vertices set
    +
    +  declare:
    +    vertexT *vertex;
    +    int     vertex_n, vertex_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHvertex_i_(vertices) FOREACHsetelement_i_(vertexT, vertices,vertex)
    +
    +/********* -libqhull.c prototypes (duplicated from qhull_a.h) **********************/
    +
    +void    qh_qhull(void);
    +boolT   qh_addpoint(pointT *furthest, facetT *facet, boolT checkdist);
    +void    qh_printsummary(FILE *fp);
    +
    +/********* -user.c prototypes (alphabetical) **********************/
    +
    +void    qh_errexit(int exitcode, facetT *facet, ridgeT *ridge);
    +void    qh_errprint(const char* string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex);
    +int     qh_new_qhull(int dim, int numpoints, coordT *points, boolT ismalloc,
    +                char *qhull_cmd, FILE *outfile, FILE *errfile);
    +void    qh_printfacetlist(facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printhelp_degenerate(FILE *fp);
    +void    qh_printhelp_narrowhull(FILE *fp, realT minangle);
    +void    qh_printhelp_singular(FILE *fp);
    +void    qh_user_memsizes(void);
    +
    +/********* -usermem.c prototypes (alphabetical) **********************/
    +void    qh_exit(int exitcode);
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +
    +/********* -userprintf.c and userprintf_rbox.c prototypes **********************/
    +void    qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_fprintf_rbox(FILE *fp, int msgcode, const char *fmt, ... );
    +
    +/***** -geom.c/geom2.c/random.c prototypes (duplicated from geom.h, random.h) ****************/
    +
    +facetT *qh_findbest(pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT newfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart);
    +facetT *qh_findbestnew(pointT *point, facetT *startfacet,
    +                     realT *dist, boolT bestoutside, boolT *isoutside, int *numpart);
    +boolT   qh_gram_schmidt(int dim, realT **rows);
    +void    qh_outerinner(facetT *facet, realT *outerplane, realT *innerplane);
    +void    qh_printsummary(FILE *fp);
    +void    qh_projectinput(void);
    +void    qh_randommatrix(realT *buffer, int dim, realT **row);
    +void    qh_rotateinput(realT **rows);
    +void    qh_scaleinput(void);
    +void    qh_setdelaunay(int dim, int count, pointT *points);
    +coordT  *qh_sethalfspace_all(int dim, int count, coordT *halfspaces, pointT *feasible);
    +
    +/***** -global.c prototypes (alphabetical) ***********************/
    +
    +unsigned long qh_clock(void);
    +void    qh_checkflags(char *command, char *hiddenflags);
    +void    qh_clear_outputflags(void);
    +void    qh_freebuffers(void);
    +void    qh_freeqhull(boolT allmem);
    +void    qh_freeqhull2(boolT allmem);
    +void    qh_init_A(FILE *infile, FILE *outfile, FILE *errfile, int argc, char *argv[]);
    +void    qh_init_B(coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_init_qhull_command(int argc, char *argv[]);
    +void    qh_initbuffers(coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_initflags(char *command);
    +void    qh_initqhull_buffers(void);
    +void    qh_initqhull_globals(coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_initqhull_mem(void);
    +void    qh_initqhull_outputflags(void);
    +void    qh_initqhull_start(FILE *infile, FILE *outfile, FILE *errfile);
    +void    qh_initqhull_start2(FILE *infile, FILE *outfile, FILE *errfile);
    +void    qh_initthresholds(char *command);
    +void    qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeTsize, int facetTsize, int setTsize, int qhmemTsize);
    +void    qh_option(const char *option, int *i, realT *r);
    +#if qh_QHpointer
    +void    qh_restore_qhull(qhT **oldqh);
    +qhT    *qh_save_qhull(void);
    +#endif
    +
    +/***** -io.c prototypes (duplicated from io.h) ***********************/
    +
    +void    qh_dfacet(unsigned id);
    +void    qh_dvertex(unsigned id);
    +void    qh_printneighborhood(FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall);
    +void    qh_produce_output(void);
    +coordT *qh_readpoints(int *numpoints, int *dimension, boolT *ismalloc);
    +
    +
    +/********* -mem.c prototypes (duplicated from mem.h) **********************/
    +
    +void qh_meminit(FILE *ferr);
    +void qh_memfreeshort(int *curlong, int *totlong);
    +
    +/********* -poly.c/poly2.c prototypes (duplicated from poly.h) **********************/
    +
    +void    qh_check_output(void);
    +void    qh_check_points(void);
    +setT   *qh_facetvertices(facetT *facetlist, setT *facets, boolT allfacets);
    +facetT *qh_findbestfacet(pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside);
    +vertexT *qh_nearvertex(facetT *facet, pointT *point, realT *bestdistp);
    +pointT *qh_point(int id);
    +setT   *qh_pointfacet(void /*qh.facet_list*/);
    +int     qh_pointid(pointT *point);
    +setT   *qh_pointvertex(void /*qh.facet_list*/);
    +void    qh_setvoronoi_all(void);
    +void    qh_triangulate(void /*qh.facet_list*/);
    +
    +/********* -rboxlib.c prototypes **********************/
    +int     qh_rboxpoints(FILE* fout, FILE* ferr, char* rbox_command);
    +void    qh_errexit_rbox(int exitcode);
    +
    +/********* -stat.c prototypes (duplicated from stat.h) **********************/
    +
    +void    qh_collectstatistics(void);
    +void    qh_printallstatistics(FILE *fp, const char *string);
    +
    +#endif /* qhDEFlibqhull */
    diff --git a/xs/src/qhull/src/libqhull/libqhull.pro b/xs/src/qhull/src/libqhull/libqhull.pro
    new file mode 100644
    index 0000000000..18005da59d
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/libqhull.pro
    @@ -0,0 +1,67 @@
    +# -------------------------------------------------
    +# libqhull.pro -- Qt project for Qhull shared library
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +DESTDIR = ../../lib
    +DLLDESTDIR = ../../bin
    +TEMPLATE = lib
    +CONFIG += shared warn_on
    +CONFIG -= qt
    +
    +build_pass:CONFIG(debug, debug|release):{
    +    TARGET = qhull_d
    +    OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +    TARGET = qhull
    +    OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +win32-msvc* : DEF_FILE += ../../src/libqhull/qhull-exports.def
    +
    +# Order object files by frequency of execution.  Small files at end.
    +
    +# libqhull/libqhull.pro and ../qhull-libqhull-src.pri have the same SOURCES and HEADERS
    +SOURCES += ../libqhull/global.c
    +SOURCES += ../libqhull/stat.c
    +SOURCES += ../libqhull/geom2.c
    +SOURCES += ../libqhull/poly2.c
    +SOURCES += ../libqhull/merge.c
    +SOURCES += ../libqhull/libqhull.c
    +SOURCES += ../libqhull/geom.c
    +SOURCES += ../libqhull/poly.c
    +SOURCES += ../libqhull/qset.c
    +SOURCES += ../libqhull/mem.c
    +SOURCES += ../libqhull/random.c
    +SOURCES += ../libqhull/usermem.c
    +SOURCES += ../libqhull/userprintf.c
    +SOURCES += ../libqhull/io.c
    +SOURCES += ../libqhull/user.c
    +SOURCES += ../libqhull/rboxlib.c
    +SOURCES += ../libqhull/userprintf_rbox.c
    +
    +HEADERS += ../libqhull/geom.h
    +HEADERS += ../libqhull/io.h
    +HEADERS += ../libqhull/libqhull.h
    +HEADERS += ../libqhull/mem.h
    +HEADERS += ../libqhull/merge.h
    +HEADERS += ../libqhull/poly.h
    +HEADERS += ../libqhull/random.h
    +HEADERS += ../libqhull/qhull_a.h
    +HEADERS += ../libqhull/qset.h
    +HEADERS += ../libqhull/stat.h
    +HEADERS += ../libqhull/user.h
    +
    +OTHER_FILES += Mborland
    +OTHER_FILES += qh-geom.htm
    +OTHER_FILES += qh-globa.htm
    +OTHER_FILES += qh-io.htm
    +OTHER_FILES += qh-mem.htm
    +OTHER_FILES += qh-merge.htm
    +OTHER_FILES += qh-poly.htm
    +OTHER_FILES += qh-qhull.htm
    +OTHER_FILES += qh-set.htm
    +OTHER_FILES += qh-stat.htm
    +OTHER_FILES += qh-user.htm
    diff --git a/xs/src/qhull/src/libqhull/mem.c b/xs/src/qhull/src/libqhull/mem.c
    new file mode 100644
    index 0000000000..db72bb4e19
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/mem.c
    @@ -0,0 +1,576 @@
    +/*
      ---------------------------------
    +
    +  mem.c
    +    memory management routines for qhull
    +
    +  This is a standalone program.
    +
    +  To initialize memory:
    +
    +    qh_meminit(stderr);
    +    qh_meminitbuffers(qh IStracing, qh_MEMalign, 7, qh_MEMbufsize,qh_MEMinitbuf);
    +    qh_memsize((int)sizeof(facetT));
    +    qh_memsize((int)sizeof(facetT));
    +    ...
    +    qh_memsetup();
    +
    +  To free up all memory buffers:
    +    qh_memfreeshort(&curlong, &totlong);
    +
    +  if qh_NOmem,
    +    malloc/free is used instead of mem.c
    +
    +  notes:
    +    uses Quickfit algorithm (freelists for commonly allocated sizes)
    +    assumes small sizes for freelists (it discards the tail of memory buffers)
    +
    +  see:
    +    qh-mem.htm and mem.h
    +    global.c (qh_initbuffers) for an example of using mem.c
    +
    +  Copyright (c) 1993-2015 The Geometry Center.
    +  $Id: //main/2015/qhull/src/libqhull/mem.c#7 $$Change: 2065 $
    +  $DateTime: 2016/01/18 13:51:04 $$Author: bbarber $
    +*/
    +
    +#include "user.h"  /* for QHULL_CRTDBG */
    +#include "mem.h"
    +#include 
    +#include 
    +#include 
    +
    +#ifndef qhDEFlibqhull
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4127)  /* conditional expression is constant */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#endif
    +void    qh_errexit(int exitcode, facetT *, ridgeT *);
    +void    qh_exit(int exitcode);
    +void    qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +#endif
    +
    +/*============ -global data structure ==============
    +    see mem.h for definition
    +*/
    +
    +qhmemT qhmem= {0,0,0,0,0,0,0,0,0,0,0,
    +               0,0,0,0,0,0,0,0,0,0,0,
    +               0,0,0,0,0,0,0};     /* remove "= {0}" if this causes a compiler error */
    +
    +#ifndef qh_NOmem
    +
    +/*============= internal functions ==============*/
    +
    +static int qh_intcompare(const void *i, const void *j);
    +
    +/*========== functions in alphabetical order ======== */
    +
    +/*---------------------------------
    +
    +  qh_intcompare( i, j )
    +    used by qsort and bsearch to compare two integers
    +*/
    +static int qh_intcompare(const void *i, const void *j) {
    +  return(*((const int *)i) - *((const int *)j));
    +} /* intcompare */
    +
    +
    +/*----------------------------------
    +
    +  qh_memalloc( insize )
    +    returns object of insize bytes
    +    qhmem is the global memory structure
    +
    +  returns:
    +    pointer to allocated memory
    +    errors if insufficient memory
    +
    +  notes:
    +    use explicit type conversion to avoid type warnings on some compilers
    +    actual object may be larger than insize
    +    use qh_memalloc_() for inline code for quick allocations
    +    logs allocations if 'T5'
    +    caller is responsible for freeing the memory.
    +    short memory is freed on shutdown by qh_memfreeshort unless qh_NOmem
    +
    +  design:
    +    if size < qhmem.LASTsize
    +      if qhmem.freelists[size] non-empty
    +        return first object on freelist
    +      else
    +        round up request to size of qhmem.freelists[size]
    +        allocate new allocation buffer if necessary
    +        allocate object from allocation buffer
    +    else
    +      allocate object with qh_malloc() in user.c
    +*/
    +void *qh_memalloc(int insize) {
    +  void **freelistp, *newbuffer;
    +  int idx, size, n;
    +  int outsize, bufsize;
    +  void *object;
    +
    +  if (insize<0) {
    +      qh_fprintf(qhmem.ferr, 6235, "qhull error (qh_memalloc): negative request size (%d).  Did int overflow due to high-D?\n", insize); /* WARN64 */
    +      qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (insize>=0 && insize <= qhmem.LASTsize) {
    +    idx= qhmem.indextable[insize];
    +    outsize= qhmem.sizetable[idx];
    +    qhmem.totshort += outsize;
    +    freelistp= qhmem.freelists+idx;
    +    if ((object= *freelistp)) {
    +      qhmem.cntquick++;
    +      qhmem.totfree -= outsize;
    +      *freelistp= *((void **)*freelistp);  /* replace freelist with next object */
    +#ifdef qh_TRACEshort
    +      n= qhmem.cntshort+qhmem.cntquick+qhmem.freeshort;
    +      if (qhmem.IStracing >= 5)
    +          qh_fprintf(qhmem.ferr, 8141, "qh_mem %p n %8d alloc quick: %d bytes (tot %d cnt %d)\n", object, n, outsize, qhmem.totshort, qhmem.cntshort+qhmem.cntquick-qhmem.freeshort);
    +#endif
    +      return(object);
    +    }else {
    +      qhmem.cntshort++;
    +      if (outsize > qhmem.freesize) {
    +        qhmem.totdropped += qhmem.freesize;
    +        if (!qhmem.curbuffer)
    +          bufsize= qhmem.BUFinit;
    +        else
    +          bufsize= qhmem.BUFsize;
    +        if (!(newbuffer= qh_malloc((size_t)bufsize))) {
    +          qh_fprintf(qhmem.ferr, 6080, "qhull error (qh_memalloc): insufficient memory to allocate short memory buffer (%d bytes)\n", bufsize);
    +          qh_errexit(qhmem_ERRmem, NULL, NULL);
    +        }
    +        *((void **)newbuffer)= qhmem.curbuffer;  /* prepend newbuffer to curbuffer
    +                                                    list.  newbuffer!=0 by QH6080 */
    +        qhmem.curbuffer= newbuffer;
    +        size= (sizeof(void **) + qhmem.ALIGNmask) & ~qhmem.ALIGNmask;
    +        qhmem.freemem= (void *)((char *)newbuffer+size);
    +        qhmem.freesize= bufsize - size;
    +        qhmem.totbuffer += bufsize - size; /* easier to check */
    +        /* Periodically test totbuffer.  It matches at beginning and exit of every call */
    +        n = qhmem.totshort + qhmem.totfree + qhmem.totdropped + qhmem.freesize - outsize;
    +        if (qhmem.totbuffer != n) {
    +            qh_fprintf(qhmem.ferr, 6212, "qh_memalloc internal error: short totbuffer %d != totshort+totfree... %d\n", qhmem.totbuffer, n);
    +            qh_errexit(qhmem_ERRmem, NULL, NULL);
    +        }
    +      }
    +      object= qhmem.freemem;
    +      qhmem.freemem= (void *)((char *)qhmem.freemem + outsize);
    +      qhmem.freesize -= outsize;
    +      qhmem.totunused += outsize - insize;
    +#ifdef qh_TRACEshort
    +      n= qhmem.cntshort+qhmem.cntquick+qhmem.freeshort;
    +      if (qhmem.IStracing >= 5)
    +          qh_fprintf(qhmem.ferr, 8140, "qh_mem %p n %8d alloc short: %d bytes (tot %d cnt %d)\n", object, n, outsize, qhmem.totshort, qhmem.cntshort+qhmem.cntquick-qhmem.freeshort);
    +#endif
    +      return object;
    +    }
    +  }else {                     /* long allocation */
    +    if (!qhmem.indextable) {
    +      qh_fprintf(qhmem.ferr, 6081, "qhull internal error (qh_memalloc): qhmem has not been initialized.\n");
    +      qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +    }
    +    outsize= insize;
    +    qhmem.cntlong++;
    +    qhmem.totlong += outsize;
    +    if (qhmem.maxlong < qhmem.totlong)
    +      qhmem.maxlong= qhmem.totlong;
    +    if (!(object= qh_malloc((size_t)outsize))) {
    +      qh_fprintf(qhmem.ferr, 6082, "qhull error (qh_memalloc): insufficient memory to allocate %d bytes\n", outsize);
    +      qh_errexit(qhmem_ERRmem, NULL, NULL);
    +    }
    +    if (qhmem.IStracing >= 5)
    +      qh_fprintf(qhmem.ferr, 8057, "qh_mem %p n %8d alloc long: %d bytes (tot %d cnt %d)\n", object, qhmem.cntlong+qhmem.freelong, outsize, qhmem.totlong, qhmem.cntlong-qhmem.freelong);
    +  }
    +  return(object);
    +} /* memalloc */
    +
    +
    +/*----------------------------------
    +
    +  qh_memcheck( )
    +*/
    +void qh_memcheck(void) {
    +  int i, count, totfree= 0;
    +  void *object;
    +
    +  if (qhmem.ferr == 0 || qhmem.IStracing < 0 || qhmem.IStracing > 10 || (((qhmem.ALIGNmask+1) & qhmem.ALIGNmask) != 0)) {
    +    qh_fprintf_stderr(6244, "qh_memcheck error: either qhmem is overwritten or qhmem is not initialized.  Call qh_meminit() or qh_new_qhull() before calling qh_mem routines.  ferr 0x%x IsTracing %d ALIGNmask 0x%x", qhmem.ferr, qhmem.IStracing, qhmem.ALIGNmask);
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (qhmem.IStracing != 0)
    +    qh_fprintf(qhmem.ferr, 8143, "qh_memcheck: check size of freelists on qhmem\nqh_memcheck: A segmentation fault indicates an overwrite of qhmem\n");
    +  for (i=0; i < qhmem.TABLEsize; i++) {
    +    count=0;
    +    for (object= qhmem.freelists[i]; object; object= *((void **)object))
    +      count++;
    +    totfree += qhmem.sizetable[i] * count;
    +  }
    +  if (totfree != qhmem.totfree) {
    +    qh_fprintf(qhmem.ferr, 6211, "Qhull internal error (qh_memcheck): totfree %d not equal to freelist total %d\n", qhmem.totfree, totfree);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  if (qhmem.IStracing != 0)
    +    qh_fprintf(qhmem.ferr, 8144, "qh_memcheck: total size of freelists totfree is the same as qhmem.totfree\n", totfree);
    +} /* memcheck */
    +
    +/*----------------------------------
    +
    +  qh_memfree( object, insize )
    +    free up an object of size bytes
    +    size is insize from qh_memalloc
    +
    +  notes:
    +    object may be NULL
    +    type checking warns if using (void **)object
    +    use qh_memfree_() for quick free's of small objects
    +
    +  design:
    +    if size <= qhmem.LASTsize
    +      append object to corresponding freelist
    +    else
    +      call qh_free(object)
    +*/
    +void qh_memfree(void *object, int insize) {
    +  void **freelistp;
    +  int idx, outsize;
    +
    +  if (!object)
    +    return;
    +  if (insize <= qhmem.LASTsize) {
    +    qhmem.freeshort++;
    +    idx= qhmem.indextable[insize];
    +    outsize= qhmem.sizetable[idx];
    +    qhmem.totfree += outsize;
    +    qhmem.totshort -= outsize;
    +    freelistp= qhmem.freelists + idx;
    +    *((void **)object)= *freelistp;
    +    *freelistp= object;
    +#ifdef qh_TRACEshort
    +    idx= qhmem.cntshort+qhmem.cntquick+qhmem.freeshort;
    +    if (qhmem.IStracing >= 5)
    +        qh_fprintf(qhmem.ferr, 8142, "qh_mem %p n %8d free short: %d bytes (tot %d cnt %d)\n", object, idx, outsize, qhmem.totshort, qhmem.cntshort+qhmem.cntquick-qhmem.freeshort);
    +#endif
    +  }else {
    +    qhmem.freelong++;
    +    qhmem.totlong -= insize;
    +    if (qhmem.IStracing >= 5)
    +      qh_fprintf(qhmem.ferr, 8058, "qh_mem %p n %8d free long: %d bytes (tot %d cnt %d)\n", object, qhmem.cntlong+qhmem.freelong, insize, qhmem.totlong, qhmem.cntlong-qhmem.freelong);
    +    qh_free(object);
    +  }
    +} /* memfree */
    +
    +
    +/*---------------------------------
    +
    +  qh_memfreeshort( curlong, totlong )
    +    frees up all short and qhmem memory allocations
    +
    +  returns:
    +    number and size of current long allocations
    +
    +  see:
    +    qh_freeqhull(allMem)
    +    qh_memtotal(curlong, totlong, curshort, totshort, maxlong, totbuffer);
    +*/
    +void qh_memfreeshort(int *curlong, int *totlong) {
    +  void *buffer, *nextbuffer;
    +  FILE *ferr;
    +
    +  *curlong= qhmem.cntlong - qhmem.freelong;
    +  *totlong= qhmem.totlong;
    +  for (buffer= qhmem.curbuffer; buffer; buffer= nextbuffer) {
    +    nextbuffer= *((void **) buffer);
    +    qh_free(buffer);
    +  }
    +  qhmem.curbuffer= NULL;
    +  if (qhmem.LASTsize) {
    +    qh_free(qhmem.indextable);
    +    qh_free(qhmem.freelists);
    +    qh_free(qhmem.sizetable);
    +  }
    +  ferr= qhmem.ferr;
    +  memset((char *)&qhmem, 0, sizeof(qhmem));  /* every field is 0, FALSE, NULL */
    +  qhmem.ferr= ferr;
    +} /* memfreeshort */
    +
    +
    +/*----------------------------------
    +
    +  qh_meminit( ferr )
    +    initialize qhmem and test sizeof( void*)
    +    Does not throw errors.  qh_exit on failure
    +*/
    +void qh_meminit(FILE *ferr) {
    +
    +  memset((char *)&qhmem, 0, sizeof(qhmem));  /* every field is 0, FALSE, NULL */
    +  if (ferr)
    +    qhmem.ferr= ferr;
    +  else
    +    qhmem.ferr= stderr;
    +  if (sizeof(void*) < sizeof(int)) {
    +    qh_fprintf(qhmem.ferr, 6083, "qhull internal error (qh_meminit): sizeof(void*) %d < sizeof(int) %d.  qset.c will not work\n", (int)sizeof(void*), (int)sizeof(int));
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (sizeof(void*) > sizeof(ptr_intT)) {
    +      qh_fprintf(qhmem.ferr, 6084, "qhull internal error (qh_meminit): sizeof(void*) %d > sizeof(ptr_intT) %d. Change ptr_intT in mem.h to 'long long'\n", (int)sizeof(void*), (int)sizeof(ptr_intT));
    +      qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  qh_memcheck();
    +} /* meminit */
    +
    +/*---------------------------------
    +
    +  qh_meminitbuffers( tracelevel, alignment, numsizes, bufsize, bufinit )
    +    initialize qhmem
    +    if tracelevel >= 5, trace memory allocations
    +    alignment= desired address alignment for memory allocations
    +    numsizes= number of freelists
    +    bufsize=  size of additional memory buffers for short allocations
    +    bufinit=  size of initial memory buffer for short allocations
    +*/
    +void qh_meminitbuffers(int tracelevel, int alignment, int numsizes, int bufsize, int bufinit) {
    +
    +  qhmem.IStracing= tracelevel;
    +  qhmem.NUMsizes= numsizes;
    +  qhmem.BUFsize= bufsize;
    +  qhmem.BUFinit= bufinit;
    +  qhmem.ALIGNmask= alignment-1;
    +  if (qhmem.ALIGNmask & ~qhmem.ALIGNmask) {
    +    qh_fprintf(qhmem.ferr, 6085, "qhull internal error (qh_meminit): memory alignment %d is not a power of 2\n", alignment);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qhmem.sizetable= (int *) calloc((size_t)numsizes, sizeof(int));
    +  qhmem.freelists= (void **) calloc((size_t)numsizes, sizeof(void *));
    +  if (!qhmem.sizetable || !qhmem.freelists) {
    +    qh_fprintf(qhmem.ferr, 6086, "qhull error (qh_meminit): insufficient memory\n");
    +    qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (qhmem.IStracing >= 1)
    +    qh_fprintf(qhmem.ferr, 8059, "qh_meminitbuffers: memory initialized with alignment %d\n", alignment);
    +} /* meminitbuffers */
    +
    +/*---------------------------------
    +
    +  qh_memsetup()
    +    set up memory after running memsize()
    +*/
    +void qh_memsetup(void) {
    +  int k,i;
    +
    +  qsort(qhmem.sizetable, (size_t)qhmem.TABLEsize, sizeof(int), qh_intcompare);
    +  qhmem.LASTsize= qhmem.sizetable[qhmem.TABLEsize-1];
    +  if (qhmem.LASTsize >= qhmem.BUFsize || qhmem.LASTsize >= qhmem.BUFinit) {
    +    qh_fprintf(qhmem.ferr, 6087, "qhull error (qh_memsetup): largest mem size %d is >= buffer size %d or initial buffer size %d\n",
    +            qhmem.LASTsize, qhmem.BUFsize, qhmem.BUFinit);
    +    qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (!(qhmem.indextable= (int *)qh_malloc((qhmem.LASTsize+1) * sizeof(int)))) {
    +    qh_fprintf(qhmem.ferr, 6088, "qhull error (qh_memsetup): insufficient memory\n");
    +    qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  for (k=qhmem.LASTsize+1; k--; )
    +    qhmem.indextable[k]= k;
    +  i= 0;
    +  for (k=0; k <= qhmem.LASTsize; k++) {
    +    if (qhmem.indextable[k] <= qhmem.sizetable[i])
    +      qhmem.indextable[k]= i;
    +    else
    +      qhmem.indextable[k]= ++i;
    +  }
    +} /* memsetup */
    +
    +/*---------------------------------
    +
    +  qh_memsize( size )
    +    define a free list for this size
    +*/
    +void qh_memsize(int size) {
    +  int k;
    +
    +  if (qhmem.LASTsize) {
    +    qh_fprintf(qhmem.ferr, 6089, "qhull error (qh_memsize): called after qhmem_setup\n");
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  size= (size + qhmem.ALIGNmask) & ~qhmem.ALIGNmask;
    +  for (k=qhmem.TABLEsize; k--; ) {
    +    if (qhmem.sizetable[k] == size)
    +      return;
    +  }
    +  if (qhmem.TABLEsize < qhmem.NUMsizes)
    +    qhmem.sizetable[qhmem.TABLEsize++]= size;
    +  else
    +    qh_fprintf(qhmem.ferr, 7060, "qhull warning (memsize): free list table has room for only %d sizes\n", qhmem.NUMsizes);
    +} /* memsize */
    +
    +
    +/*---------------------------------
    +
    +  qh_memstatistics( fp )
    +    print out memory statistics
    +
    +    Verifies that qhmem.totfree == sum of freelists
    +*/
    +void qh_memstatistics(FILE *fp) {
    +  int i;
    +  int count;
    +  void *object;
    +
    +  qh_memcheck();
    +  qh_fprintf(fp, 9278, "\nmemory statistics:\n\
    +%7d quick allocations\n\
    +%7d short allocations\n\
    +%7d long allocations\n\
    +%7d short frees\n\
    +%7d long frees\n\
    +%7d bytes of short memory in use\n\
    +%7d bytes of short memory in freelists\n\
    +%7d bytes of dropped short memory\n\
    +%7d bytes of unused short memory (estimated)\n\
    +%7d bytes of long memory allocated (max, except for input)\n\
    +%7d bytes of long memory in use (in %d pieces)\n\
    +%7d bytes of short memory buffers (minus links)\n\
    +%7d bytes per short memory buffer (initially %d bytes)\n",
    +           qhmem.cntquick, qhmem.cntshort, qhmem.cntlong,
    +           qhmem.freeshort, qhmem.freelong,
    +           qhmem.totshort, qhmem.totfree,
    +           qhmem.totdropped + qhmem.freesize, qhmem.totunused,
    +           qhmem.maxlong, qhmem.totlong, qhmem.cntlong - qhmem.freelong,
    +           qhmem.totbuffer, qhmem.BUFsize, qhmem.BUFinit);
    +  if (qhmem.cntlarger) {
    +    qh_fprintf(fp, 9279, "%7d calls to qh_setlarger\n%7.2g     average copy size\n",
    +           qhmem.cntlarger, ((float)qhmem.totlarger)/(float)qhmem.cntlarger);
    +    qh_fprintf(fp, 9280, "  freelists(bytes->count):");
    +  }
    +  for (i=0; i < qhmem.TABLEsize; i++) {
    +    count=0;
    +    for (object= qhmem.freelists[i]; object; object= *((void **)object))
    +      count++;
    +    qh_fprintf(fp, 9281, " %d->%d", qhmem.sizetable[i], count);
    +  }
    +  qh_fprintf(fp, 9282, "\n\n");
    +} /* memstatistics */
    +
    +
    +/*---------------------------------
    +
    +  qh_NOmem
    +    turn off quick-fit memory allocation
    +
    +  notes:
    +    uses qh_malloc() and qh_free() instead
    +*/
    +#else /* qh_NOmem */
    +
    +void *qh_memalloc(int insize) {
    +  void *object;
    +
    +  if (!(object= qh_malloc((size_t)insize))) {
    +    qh_fprintf(qhmem.ferr, 6090, "qhull error (qh_memalloc): insufficient memory\n");
    +    qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  qhmem.cntlong++;
    +  qhmem.totlong += insize;
    +  if (qhmem.maxlong < qhmem.totlong)
    +      qhmem.maxlong= qhmem.totlong;
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8060, "qh_mem %p n %8d alloc long: %d bytes (tot %d cnt %d)\n", object, qhmem.cntlong+qhmem.freelong, insize, qhmem.totlong, qhmem.cntlong-qhmem.freelong);
    +  return object;
    +}
    +
    +void qh_memfree(void *object, int insize) {
    +
    +  if (!object)
    +    return;
    +  qh_free(object);
    +  qhmem.freelong++;
    +  qhmem.totlong -= insize;
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8061, "qh_mem %p n %8d free long: %d bytes (tot %d cnt %d)\n", object, qhmem.cntlong+qhmem.freelong, insize, qhmem.totlong, qhmem.cntlong-qhmem.freelong);
    +}
    +
    +void qh_memfreeshort(int *curlong, int *totlong) {
    +  *totlong= qhmem.totlong;
    +  *curlong= qhmem.cntlong - qhmem.freelong;
    +  memset((char *)&qhmem, 0, sizeof(qhmem));  /* every field is 0, FALSE, NULL */
    +}
    +
    +void qh_meminit(FILE *ferr) {
    +
    +  memset((char *)&qhmem, 0, sizeof(qhmem));  /* every field is 0, FALSE, NULL */
    +  if (ferr)
    +      qhmem.ferr= ferr;
    +  else
    +      qhmem.ferr= stderr;
    +  if (sizeof(void*) < sizeof(int)) {
    +    qh_fprintf(qhmem.ferr, 6091, "qhull internal error (qh_meminit): sizeof(void*) %d < sizeof(int) %d.  qset.c will not work\n", (int)sizeof(void*), (int)sizeof(int));
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +}
    +
    +void qh_meminitbuffers(int tracelevel, int alignment, int numsizes, int bufsize, int bufinit) {
    +
    +  qhmem.IStracing= tracelevel;
    +}
    +
    +void qh_memsetup(void) {
    +
    +}
    +
    +void qh_memsize(int size) {
    +
    +}
    +
    +void qh_memstatistics(FILE *fp) {
    +
    +  qh_fprintf(fp, 9409, "\nmemory statistics:\n\
    +%7d long allocations\n\
    +%7d long frees\n\
    +%7d bytes of long memory allocated (max, except for input)\n\
    +%7d bytes of long memory in use (in %d pieces)\n",
    +           qhmem.cntlong,
    +           qhmem.freelong,
    +           qhmem.maxlong, qhmem.totlong, qhmem.cntlong - qhmem.freelong);
    +}
    +
    +#endif /* qh_NOmem */
    +
    +/*---------------------------------
    +
    +  qh_memtotal( totlong, curlong, totshort, curshort, maxlong, totbuffer )
    +    Return the total, allocated long and short memory
    +
    +  returns:
    +    Returns the total current bytes of long and short allocations
    +    Returns the current count of long and short allocations
    +    Returns the maximum long memory and total short buffer (minus one link per buffer)
    +    Does not error (UsingLibQhull.cpp)
    +*/
    +void qh_memtotal(int *totlong, int *curlong, int *totshort, int *curshort, int *maxlong, int *totbuffer) {
    +    *totlong= qhmem.totlong;
    +    *curlong= qhmem.cntlong - qhmem.freelong;
    +    *totshort= qhmem.totshort;
    +    *curshort= qhmem.cntshort + qhmem.cntquick - qhmem.freeshort;
    +    *maxlong= qhmem.maxlong;
    +    *totbuffer= qhmem.totbuffer;
    +} /* memtotlong */
    +
    diff --git a/xs/src/qhull/src/libqhull/mem.h b/xs/src/qhull/src/libqhull/mem.h
    new file mode 100644
    index 0000000000..453f319df3
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/mem.h
    @@ -0,0 +1,222 @@
    +/*
      ---------------------------------
    +
    +   mem.h
    +     prototypes for memory management functions
    +
    +   see qh-mem.htm, mem.c and qset.h
    +
    +   for error handling, writes message and calls
    +     qh_errexit(qhmem_ERRmem, NULL, NULL) if insufficient memory
    +       and
    +     qh_errexit(qhmem_ERRqhull, NULL, NULL) otherwise
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/mem.h#2 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFmem
    +#define qhDEFmem 1
    +
    +#include 
    +
    +/*---------------------------------
    +
    +  qh_NOmem
    +    turn off quick-fit memory allocation
    +
    +  notes:
    +    mem.c implements Quickfit memory allocation for about 20% time
    +    savings.  If it fails on your machine, try to locate the
    +    problem, and send the answer to qhull@qhull.org.  If this can
    +    not be done, define qh_NOmem to use malloc/free instead.
    +
    +   #define qh_NOmem
    +*/
    +
    +/*---------------------------------
    +
    +qh_TRACEshort
    +Trace short and quick memory allocations at T5
    +
    +*/
    +#define qh_TRACEshort
    +
    +/*-------------------------------------------
    +    to avoid bus errors, memory allocation must consider alignment requirements.
    +    malloc() automatically takes care of alignment.   Since mem.c manages
    +    its own memory, we need to explicitly specify alignment in
    +    qh_meminitbuffers().
    +
    +    A safe choice is sizeof(double).  sizeof(float) may be used if doubles
    +    do not occur in data structures and pointers are the same size.  Be careful
    +    of machines (e.g., DEC Alpha) with large pointers.  If gcc is available,
    +    use __alignof__(double) or fmax_(__alignof__(float), __alignof__(void *)).
    +
    +   see qh_MEMalign in user.h for qhull's alignment
    +*/
    +
    +#define qhmem_ERRmem 4    /* matches qh_ERRmem in libqhull.h */
    +#define qhmem_ERRqhull 5  /* matches qh_ERRqhull in libqhull.h */
    +
    +/*----------------------------------
    +
    +  ptr_intT
    +    for casting a void * to an integer-type that holds a pointer
    +    Used for integer expressions (e.g., computing qh_gethash() in poly.c)
    +
    +  notes:
    +    WARN64 -- these notes indicate 64-bit issues
    +    On 64-bit machines, a pointer may be larger than an 'int'.
    +    qh_meminit()/mem.c checks that 'ptr_intT' holds a 'void*'
    +    ptr_intT is typically a signed value, but not necessarily so
    +    size_t is typically unsigned, but should match the parameter type
    +    Qhull uses int instead of size_t except for system calls such as malloc, qsort, qh_malloc, etc.
    +    This matches Qt convention and is easier to work with.
    +*/
    +#if (defined(__MINGW64__)) && defined(_WIN64)
    +typedef long long ptr_intT;
    +#elif (_MSC_VER) && defined(_WIN64)
    +typedef long long ptr_intT;
    +#else
    +typedef long ptr_intT;
    +#endif
    +
    +/*----------------------------------
    +
    +  qhmemT
    +    global memory structure for mem.c
    +
    + notes:
    +   users should ignore qhmem except for writing extensions
    +   qhmem is allocated in mem.c
    +
    +   qhmem could be swapable like qh and qhstat, but then
    +   multiple qh's and qhmem's would need to keep in synch.
    +   A swapable qhmem would also waste memory buffers.  As long
    +   as memory operations are atomic, there is no problem with
    +   multiple qh structures being active at the same time.
    +   If you need separate address spaces, you can swap the
    +   contents of qhmem.
    +*/
    +typedef struct qhmemT qhmemT;
    +extern qhmemT qhmem;
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;          /* defined in qset.h */
    +#endif
    +
    +/* Update qhmem in mem.c if add or remove fields */
    +struct qhmemT {               /* global memory management variables */
    +  int      BUFsize;           /* size of memory allocation buffer */
    +  int      BUFinit;           /* initial size of memory allocation buffer */
    +  int      TABLEsize;         /* actual number of sizes in free list table */
    +  int      NUMsizes;          /* maximum number of sizes in free list table */
    +  int      LASTsize;          /* last size in free list table */
    +  int      ALIGNmask;         /* worst-case alignment, must be 2^n-1 */
    +  void   **freelists;          /* free list table, linked by offset 0 */
    +  int     *sizetable;         /* size of each freelist */
    +  int     *indextable;        /* size->index table */
    +  void    *curbuffer;         /* current buffer, linked by offset 0 */
    +  void    *freemem;           /*   free memory in curbuffer */
    +  int      freesize;          /*   size of freemem in bytes */
    +  setT    *tempstack;         /* stack of temporary memory, managed by users */
    +  FILE    *ferr;              /* file for reporting errors when 'qh' may be undefined */
    +  int      IStracing;         /* =5 if tracing memory allocations */
    +  int      cntquick;          /* count of quick allocations */
    +                              /* Note: removing statistics doesn't effect speed */
    +  int      cntshort;          /* count of short allocations */
    +  int      cntlong;           /* count of long allocations */
    +  int      freeshort;         /* count of short memfrees */
    +  int      freelong;          /* count of long memfrees */
    +  int      totbuffer;         /* total short memory buffers minus buffer links */
    +  int      totdropped;        /* total dropped memory at end of short memory buffers (e.g., freesize) */
    +  int      totfree;           /* total size of free, short memory on freelists */
    +  int      totlong;           /* total size of long memory in use */
    +  int      maxlong;           /*   maximum totlong */
    +  int      totshort;          /* total size of short memory in use */
    +  int      totunused;         /* total unused short memory (estimated, short size - request size of first allocations) */
    +  int      cntlarger;         /* count of setlarger's */
    +  int      totlarger;         /* total copied by setlarger */
    +};
    +
    +
    +/*==================== -macros ====================*/
    +
    +/*----------------------------------
    +
    +  qh_memalloc_(insize, freelistp, object, type)
    +    returns object of size bytes
    +        assumes size<=qhmem.LASTsize and void **freelistp is a temp
    +*/
    +
    +#if defined qh_NOmem
    +#define qh_memalloc_(insize, freelistp, object, type) {\
    +  object= (type*)qh_memalloc(insize); }
    +#elif defined qh_TRACEshort
    +#define qh_memalloc_(insize, freelistp, object, type) {\
    +    freelistp= NULL; /* Avoid warnings */ \
    +    object= (type*)qh_memalloc(insize); }
    +#else /* !qh_NOmem */
    +
    +#define qh_memalloc_(insize, freelistp, object, type) {\
    +  freelistp= qhmem.freelists + qhmem.indextable[insize];\
    +  if ((object= (type*)*freelistp)) {\
    +    qhmem.totshort += qhmem.sizetable[qhmem.indextable[insize]]; \
    +    qhmem.totfree -= qhmem.sizetable[qhmem.indextable[insize]]; \
    +    qhmem.cntquick++;  \
    +    *freelistp= *((void **)*freelistp);\
    +  }else object= (type*)qh_memalloc(insize);}
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_memfree_(object, insize, freelistp)
    +    free up an object
    +
    +  notes:
    +    object may be NULL
    +    assumes size<=qhmem.LASTsize and void **freelistp is a temp
    +*/
    +#if defined qh_NOmem
    +#define qh_memfree_(object, insize, freelistp) {\
    +  qh_memfree(object, insize); }
    +#elif defined qh_TRACEshort
    +#define qh_memfree_(object, insize, freelistp) {\
    +    freelistp= NULL; /* Avoid warnings */ \
    +    qh_memfree(object, insize); }
    +#else /* !qh_NOmem */
    +
    +#define qh_memfree_(object, insize, freelistp) {\
    +  if (object) { \
    +    qhmem.freeshort++;\
    +    freelistp= qhmem.freelists + qhmem.indextable[insize];\
    +    qhmem.totshort -= qhmem.sizetable[qhmem.indextable[insize]]; \
    +    qhmem.totfree += qhmem.sizetable[qhmem.indextable[insize]]; \
    +    *((void **)object)= *freelistp;\
    +    *freelistp= object;}}
    +#endif
    +
    +/*=============== prototypes in alphabetical order ============*/
    +
    +void *qh_memalloc(int insize);
    +void qh_memcheck(void);
    +void qh_memfree(void *object, int insize);
    +void qh_memfreeshort(int *curlong, int *totlong);
    +void qh_meminit(FILE *ferr);
    +void qh_meminitbuffers(int tracelevel, int alignment, int numsizes,
    +                        int bufsize, int bufinit);
    +void qh_memsetup(void);
    +void qh_memsize(int size);
    +void qh_memstatistics(FILE *fp);
    +void qh_memtotal(int *totlong, int *curlong, int *totshort, int *curshort, int *maxlong, int *totbuffer);
    +
    +#endif /* qhDEFmem */
    diff --git a/xs/src/qhull/src/libqhull/merge.c b/xs/src/qhull/src/libqhull/merge.c
    new file mode 100644
    index 0000000000..22104dc031
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/merge.c
    @@ -0,0 +1,3628 @@
    +/*
      ---------------------------------
    +
    +   merge.c
    +   merges non-convex facets
    +
    +   see qh-merge.htm and merge.h
    +
    +   other modules call qh_premerge() and qh_postmerge()
    +
    +   the user may call qh_postmerge() to perform additional merges.
    +
    +   To remove deleted facets and vertices (qhull() in libqhull.c):
    +     qh_partitionvisible(!qh_ALL, &numoutside);  // visible_list, newfacet_list
    +     qh_deletevisible();         // qh.visible_list
    +     qh_resetlists(False, qh_RESETvisible);       // qh.visible_list newvertex_list newfacet_list
    +
    +   assumes qh.CENTERtype= centrum
    +
    +   merges occur in qh_mergefacet and in qh_mergecycle
    +   vertex->neighbors not set until the first merge occurs
    +
    +   Copyright (c) 1993-2015 C.B. Barber.
    +   $Id: //main/2015/qhull/src/libqhull/merge.c#4 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +#ifndef qh_NOmerge
    +
    +/*===== functions(alphabetical after premerge and postmerge) ======*/
    +
    +/*---------------------------------
    +
    +  qh_premerge( apex, maxcentrum )
    +    pre-merge nonconvex facets in qh.newfacet_list for apex
    +    maxcentrum defines coplanar and concave (qh_test_appendmerge)
    +
    +  returns:
    +    deleted facets added to qh.visible_list with facet->visible set
    +
    +  notes:
    +    uses globals, qh.MERGEexact, qh.PREmerge
    +
    +  design:
    +    mark duplicate ridges in qh.newfacet_list
    +    merge facet cycles in qh.newfacet_list
    +    merge duplicate ridges and concave facets in qh.newfacet_list
    +    check merged facet cycles for degenerate and redundant facets
    +    merge degenerate and redundant facets
    +    collect coplanar and concave facets
    +    merge concave, coplanar, degenerate, and redundant facets
    +*/
    +void qh_premerge(vertexT *apex, realT maxcentrum, realT maxangle) {
    +  boolT othermerge= False;
    +  facetT *newfacet;
    +
    +  if (qh ZEROcentrum && qh_checkzero(!qh_ALL))
    +    return;
    +  trace2((qh ferr, 2008, "qh_premerge: premerge centrum %2.2g angle %2.2g for apex v%d facetlist f%d\n",
    +            maxcentrum, maxangle, apex->id, getid_(qh newfacet_list)));
    +  if (qh IStracing >= 4 && qh num_facets < 50)
    +    qh_printlists();
    +  qh centrum_radius= maxcentrum;
    +  qh cos_max= maxangle;
    +  qh degen_mergeset= qh_settemp(qh TEMPsize);
    +  qh facet_mergeset= qh_settemp(qh TEMPsize);
    +  if (qh hull_dim >=3) {
    +    qh_mark_dupridges(qh newfacet_list); /* facet_mergeset */
    +    qh_mergecycle_all(qh newfacet_list, &othermerge);
    +    qh_forcedmerges(&othermerge /* qh.facet_mergeset */);
    +    FORALLnew_facets {  /* test samecycle merges */
    +      if (!newfacet->simplicial && !newfacet->mergeridge)
    +        qh_degen_redundant_neighbors(newfacet, NULL);
    +    }
    +    if (qh_merge_degenredundant())
    +      othermerge= True;
    +  }else /* qh.hull_dim == 2 */
    +    qh_mergecycle_all(qh newfacet_list, &othermerge);
    +  qh_flippedmerges(qh newfacet_list, &othermerge);
    +  if (!qh MERGEexact || zzval_(Ztotmerge)) {
    +    zinc_(Zpremergetot);
    +    qh POSTmerging= False;
    +    qh_getmergeset_initial(qh newfacet_list);
    +    qh_all_merges(othermerge, False);
    +  }
    +  qh_settempfree(&qh facet_mergeset);
    +  qh_settempfree(&qh degen_mergeset);
    +} /* premerge */
    +
    +/*---------------------------------
    +
    +  qh_postmerge( reason, maxcentrum, maxangle, vneighbors )
    +    post-merge nonconvex facets as defined by maxcentrum and maxangle
    +    'reason' is for reporting progress
    +    if vneighbors,
    +      calls qh_test_vneighbors at end of qh_all_merge
    +    if firstmerge,
    +      calls qh_reducevertices before qh_getmergeset
    +
    +  returns:
    +    if first call (qh.visible_list != qh.facet_list),
    +      builds qh.facet_newlist, qh.newvertex_list
    +    deleted facets added to qh.visible_list with facet->visible
    +    qh.visible_list == qh.facet_list
    +
    +  notes:
    +
    +
    +  design:
    +    if first call
    +      set qh.visible_list and qh.newfacet_list to qh.facet_list
    +      add all facets to qh.newfacet_list
    +      mark non-simplicial facets, facet->newmerge
    +      set qh.newvertext_list to qh.vertex_list
    +      add all vertices to qh.newvertex_list
    +      if a pre-merge occured
    +        set vertex->delridge {will retest the ridge}
    +        if qh.MERGEexact
    +          call qh_reducevertices()
    +      if no pre-merging
    +        merge flipped facets
    +    determine non-convex facets
    +    merge all non-convex facets
    +*/
    +void qh_postmerge(const char *reason, realT maxcentrum, realT maxangle,
    +                      boolT vneighbors) {
    +  facetT *newfacet;
    +  boolT othermerges= False;
    +  vertexT *vertex;
    +
    +  if (qh REPORTfreq || qh IStracing) {
    +    qh_buildtracing(NULL, NULL);
    +    qh_printsummary(qh ferr);
    +    if (qh PRINTstatistics)
    +      qh_printallstatistics(qh ferr, "reason");
    +    qh_fprintf(qh ferr, 8062, "\n%s with 'C%.2g' and 'A%.2g'\n",
    +        reason, maxcentrum, maxangle);
    +  }
    +  trace2((qh ferr, 2009, "qh_postmerge: postmerge.  test vneighbors? %d\n",
    +            vneighbors));
    +  qh centrum_radius= maxcentrum;
    +  qh cos_max= maxangle;
    +  qh POSTmerging= True;
    +  qh degen_mergeset= qh_settemp(qh TEMPsize);
    +  qh facet_mergeset= qh_settemp(qh TEMPsize);
    +  if (qh visible_list != qh facet_list) {  /* first call */
    +    qh NEWfacets= True;
    +    qh visible_list= qh newfacet_list= qh facet_list;
    +    FORALLnew_facets {
    +      newfacet->newfacet= True;
    +       if (!newfacet->simplicial)
    +        newfacet->newmerge= True;
    +     zinc_(Zpostfacets);
    +    }
    +    qh newvertex_list= qh vertex_list;
    +    FORALLvertices
    +      vertex->newlist= True;
    +    if (qh VERTEXneighbors) { /* a merge has occurred */
    +      FORALLvertices
    +        vertex->delridge= True; /* test for redundant, needed? */
    +      if (qh MERGEexact) {
    +        if (qh hull_dim <= qh_DIMreduceBuild)
    +          qh_reducevertices(); /* was skipped during pre-merging */
    +      }
    +    }
    +    if (!qh PREmerge && !qh MERGEexact)
    +      qh_flippedmerges(qh newfacet_list, &othermerges);
    +  }
    +  qh_getmergeset_initial(qh newfacet_list);
    +  qh_all_merges(False, vneighbors);
    +  qh_settempfree(&qh facet_mergeset);
    +  qh_settempfree(&qh degen_mergeset);
    +} /* post_merge */
    +
    +/*---------------------------------
    +
    +  qh_all_merges( othermerge, vneighbors )
    +    merge all non-convex facets
    +
    +    set othermerge if already merged facets (for qh_reducevertices)
    +    if vneighbors
    +      tests vertex neighbors for convexity at end
    +    qh.facet_mergeset lists the non-convex ridges in qh_newfacet_list
    +    qh.degen_mergeset is defined
    +    if qh.MERGEexact && !qh.POSTmerging,
    +      does not merge coplanar facets
    +
    +  returns:
    +    deleted facets added to qh.visible_list with facet->visible
    +    deleted vertices added qh.delvertex_list with vertex->delvertex
    +
    +  notes:
    +    unless !qh.MERGEindependent,
    +      merges facets in independent sets
    +    uses qh.newfacet_list as argument since merges call qh_removefacet()
    +
    +  design:
    +    while merges occur
    +      for each merge in qh.facet_mergeset
    +        unless one of the facets was already merged in this pass
    +          merge the facets
    +        test merged facets for additional merges
    +        add merges to qh.facet_mergeset
    +      if vertices record neighboring facets
    +        rename redundant vertices
    +          update qh.facet_mergeset
    +    if vneighbors ??
    +      tests vertex neighbors for convexity at end
    +*/
    +void qh_all_merges(boolT othermerge, boolT vneighbors) {
    +  facetT *facet1, *facet2;
    +  mergeT *merge;
    +  boolT wasmerge= True, isreduce;
    +  void **freelistp;  /* used if !qh_NOmem by qh_memfree_() */
    +  vertexT *vertex;
    +  mergeType mergetype;
    +  int numcoplanar=0, numconcave=0, numdegenredun= 0, numnewmerges= 0;
    +
    +  trace2((qh ferr, 2010, "qh_all_merges: starting to merge facets beginning from f%d\n",
    +            getid_(qh newfacet_list)));
    +  while (True) {
    +    wasmerge= False;
    +    while (qh_setsize(qh facet_mergeset)) {
    +      while ((merge= (mergeT*)qh_setdellast(qh facet_mergeset))) {
    +        facet1= merge->facet1;
    +        facet2= merge->facet2;
    +        mergetype= merge->type;
    +        qh_memfree_(merge, (int)sizeof(mergeT), freelistp);
    +        if (facet1->visible || facet2->visible) /*deleted facet*/
    +          continue;
    +        if ((facet1->newfacet && !facet1->tested)
    +                || (facet2->newfacet && !facet2->tested)) {
    +          if (qh MERGEindependent && mergetype <= MRGanglecoplanar)
    +            continue;      /* perform independent sets of merges */
    +        }
    +        qh_merge_nonconvex(facet1, facet2, mergetype);
    +        numdegenredun += qh_merge_degenredundant();
    +        numnewmerges++;
    +        wasmerge= True;
    +        if (mergetype == MRGconcave)
    +          numconcave++;
    +        else /* MRGcoplanar or MRGanglecoplanar */
    +          numcoplanar++;
    +      } /* while setdellast */
    +      if (qh POSTmerging && qh hull_dim <= qh_DIMreduceBuild
    +      && numnewmerges > qh_MAXnewmerges) {
    +        numnewmerges= 0;
    +        qh_reducevertices();  /* otherwise large post merges too slow */
    +      }
    +      qh_getmergeset(qh newfacet_list); /* facet_mergeset */
    +    } /* while mergeset */
    +    if (qh VERTEXneighbors) {
    +      isreduce= False;
    +      if (qh hull_dim >=4 && qh POSTmerging) {
    +        FORALLvertices
    +          vertex->delridge= True;
    +        isreduce= True;
    +      }
    +      if ((wasmerge || othermerge) && (!qh MERGEexact || qh POSTmerging)
    +          && qh hull_dim <= qh_DIMreduceBuild) {
    +        othermerge= False;
    +        isreduce= True;
    +      }
    +      if (isreduce) {
    +        if (qh_reducevertices()) {
    +          qh_getmergeset(qh newfacet_list); /* facet_mergeset */
    +          continue;
    +        }
    +      }
    +    }
    +    if (vneighbors && qh_test_vneighbors(/* qh.newfacet_list */))
    +      continue;
    +    break;
    +  } /* while (True) */
    +  if (qh CHECKfrequently && !qh MERGEexact) {
    +    qh old_randomdist= qh RANDOMdist;
    +    qh RANDOMdist= False;
    +    qh_checkconvex(qh newfacet_list, qh_ALGORITHMfault);
    +    /* qh_checkconnect(); [this is slow and it changes the facet order] */
    +    qh RANDOMdist= qh old_randomdist;
    +  }
    +  trace1((qh ferr, 1009, "qh_all_merges: merged %d coplanar facets %d concave facets and %d degen or redundant facets.\n",
    +    numcoplanar, numconcave, numdegenredun));
    +  if (qh IStracing >= 4 && qh num_facets < 50)
    +    qh_printlists();
    +} /* all_merges */
    +
    +
    +/*---------------------------------
    +
    +  qh_appendmergeset( facet, neighbor, mergetype, angle )
    +    appends an entry to qh.facet_mergeset or qh.degen_mergeset
    +
    +    angle ignored if NULL or !qh.ANGLEmerge
    +
    +  returns:
    +    merge appended to facet_mergeset or degen_mergeset
    +      sets ->degenerate or ->redundant if degen_mergeset
    +
    +  see:
    +    qh_test_appendmerge()
    +
    +  design:
    +    allocate merge entry
    +    if regular merge
    +      append to qh.facet_mergeset
    +    else if degenerate merge and qh.facet_mergeset is all degenerate
    +      append to qh.degen_mergeset
    +    else if degenerate merge
    +      prepend to qh.degen_mergeset
    +    else if redundant merge
    +      append to qh.degen_mergeset
    +*/
    +void qh_appendmergeset(facetT *facet, facetT *neighbor, mergeType mergetype, realT *angle) {
    +  mergeT *merge, *lastmerge;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  if (facet->redundant)
    +    return;
    +  if (facet->degenerate && mergetype == MRGdegen)
    +    return;
    +  qh_memalloc_((int)sizeof(mergeT), freelistp, merge, mergeT);
    +  merge->facet1= facet;
    +  merge->facet2= neighbor;
    +  merge->type= mergetype;
    +  if (angle && qh ANGLEmerge)
    +    merge->angle= *angle;
    +  if (mergetype < MRGdegen)
    +    qh_setappend(&(qh facet_mergeset), merge);
    +  else if (mergetype == MRGdegen) {
    +    facet->degenerate= True;
    +    if (!(lastmerge= (mergeT*)qh_setlast(qh degen_mergeset))
    +    || lastmerge->type == MRGdegen)
    +      qh_setappend(&(qh degen_mergeset), merge);
    +    else
    +      qh_setaddnth(&(qh degen_mergeset), 0, merge);
    +  }else if (mergetype == MRGredundant) {
    +    facet->redundant= True;
    +    qh_setappend(&(qh degen_mergeset), merge);
    +  }else /* mergetype == MRGmirror */ {
    +    if (facet->redundant || neighbor->redundant) {
    +      qh_fprintf(qh ferr, 6092, "qhull error (qh_appendmergeset): facet f%d or f%d is already a mirrored facet\n",
    +           facet->id, neighbor->id);
    +      qh_errexit2(qh_ERRqhull, facet, neighbor);
    +    }
    +    if (!qh_setequal(facet->vertices, neighbor->vertices)) {
    +      qh_fprintf(qh ferr, 6093, "qhull error (qh_appendmergeset): mirrored facets f%d and f%d do not have the same vertices\n",
    +           facet->id, neighbor->id);
    +      qh_errexit2(qh_ERRqhull, facet, neighbor);
    +    }
    +    facet->redundant= True;
    +    neighbor->redundant= True;
    +    qh_setappend(&(qh degen_mergeset), merge);
    +  }
    +} /* appendmergeset */
    +
    +
    +/*---------------------------------
    +
    +  qh_basevertices( samecycle )
    +    return temporary set of base vertices for samecycle
    +    samecycle is first facet in the cycle
    +    assumes apex is SETfirst_( samecycle->vertices )
    +
    +  returns:
    +    vertices(settemp)
    +    all ->seen are cleared
    +
    +  notes:
    +    uses qh_vertex_visit;
    +
    +  design:
    +    for each facet in samecycle
    +      for each unseen vertex in facet->vertices
    +        append to result
    +*/
    +setT *qh_basevertices(facetT *samecycle) {
    +  facetT *same;
    +  vertexT *apex, *vertex, **vertexp;
    +  setT *vertices= qh_settemp(qh TEMPsize);
    +
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  apex->visitid= ++qh vertex_visit;
    +  FORALLsame_cycle_(samecycle) {
    +    if (same->mergeridge)
    +      continue;
    +    FOREACHvertex_(same->vertices) {
    +      if (vertex->visitid != qh vertex_visit) {
    +        qh_setappend(&vertices, vertex);
    +        vertex->visitid= qh vertex_visit;
    +        vertex->seen= False;
    +      }
    +    }
    +  }
    +  trace4((qh ferr, 4019, "qh_basevertices: found %d vertices\n",
    +         qh_setsize(vertices)));
    +  return vertices;
    +} /* basevertices */
    +
    +/*---------------------------------
    +
    +  qh_checkconnect()
    +    check that new facets are connected
    +    new facets are on qh.newfacet_list
    +
    +  notes:
    +    this is slow and it changes the order of the facets
    +    uses qh.visit_id
    +
    +  design:
    +    move first new facet to end of qh.facet_list
    +    for all newly appended facets
    +      append unvisited neighbors to end of qh.facet_list
    +    for all new facets
    +      report error if unvisited
    +*/
    +void qh_checkconnect(void /* qh.newfacet_list */) {
    +  facetT *facet, *newfacet, *errfacet= NULL, *neighbor, **neighborp;
    +
    +  facet= qh newfacet_list;
    +  qh_removefacet(facet);
    +  qh_appendfacet(facet);
    +  facet->visitid= ++qh visit_id;
    +  FORALLfacet_(facet) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh visit_id) {
    +        qh_removefacet(neighbor);
    +        qh_appendfacet(neighbor);
    +        neighbor->visitid= qh visit_id;
    +      }
    +    }
    +  }
    +  FORALLnew_facets {
    +    if (newfacet->visitid == qh visit_id)
    +      break;
    +    qh_fprintf(qh ferr, 6094, "qhull error: f%d is not attached to the new facets\n",
    +         newfacet->id);
    +    errfacet= newfacet;
    +  }
    +  if (errfacet)
    +    qh_errexit(qh_ERRqhull, errfacet, NULL);
    +} /* checkconnect */
    +
    +/*---------------------------------
    +
    +  qh_checkzero( testall )
    +    check that facets are clearly convex for qh.DISTround with qh.MERGEexact
    +
    +    if testall,
    +      test all facets for qh.MERGEexact post-merging
    +    else
    +      test qh.newfacet_list
    +
    +    if qh.MERGEexact,
    +      allows coplanar ridges
    +      skips convexity test while qh.ZEROall_ok
    +
    +  returns:
    +    True if all facets !flipped, !dupridge, normal
    +         if all horizon facets are simplicial
    +         if all vertices are clearly below neighbor
    +         if all opposite vertices of horizon are below
    +    clears qh.ZEROall_ok if any problems or coplanar facets
    +
    +  notes:
    +    uses qh.vertex_visit
    +    horizon facets may define multiple new facets
    +
    +  design:
    +    for all facets in qh.newfacet_list or qh.facet_list
    +      check for flagged faults (flipped, etc.)
    +    for all facets in qh.newfacet_list or qh.facet_list
    +      for each neighbor of facet
    +        skip horizon facets for qh.newfacet_list
    +        test the opposite vertex
    +      if qh.newfacet_list
    +        test the other vertices in the facet's horizon facet
    +*/
    +boolT qh_checkzero(boolT testall) {
    +  facetT *facet, *neighbor, **neighborp;
    +  facetT *horizon, *facetlist;
    +  int neighbor_i;
    +  vertexT *vertex, **vertexp;
    +  realT dist;
    +
    +  if (testall)
    +    facetlist= qh facet_list;
    +  else {
    +    facetlist= qh newfacet_list;
    +    FORALLfacet_(facetlist) {
    +      horizon= SETfirstt_(facet->neighbors, facetT);
    +      if (!horizon->simplicial)
    +        goto LABELproblem;
    +      if (facet->flipped || facet->dupridge || !facet->normal)
    +        goto LABELproblem;
    +    }
    +    if (qh MERGEexact && qh ZEROall_ok) {
    +      trace2((qh ferr, 2011, "qh_checkzero: skip convexity check until first pre-merge\n"));
    +      return True;
    +    }
    +  }
    +  FORALLfacet_(facetlist) {
    +    qh vertex_visit++;
    +    neighbor_i= 0;
    +    horizon= NULL;
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor_i && !testall) {
    +        horizon= neighbor;
    +        neighbor_i++;
    +        continue; /* horizon facet tested in qh_findhorizon */
    +      }
    +      vertex= SETelemt_(facet->vertices, neighbor_i++, vertexT);
    +      vertex->visitid= qh vertex_visit;
    +      zzinc_(Zdistzero);
    +      qh_distplane(vertex->point, neighbor, &dist);
    +      if (dist >= -qh DISTround) {
    +        qh ZEROall_ok= False;
    +        if (!qh MERGEexact || testall || dist > qh DISTround)
    +          goto LABELnonconvex;
    +      }
    +    }
    +    if (!testall && horizon) {
    +      FOREACHvertex_(horizon->vertices) {
    +        if (vertex->visitid != qh vertex_visit) {
    +          zzinc_(Zdistzero);
    +          qh_distplane(vertex->point, facet, &dist);
    +          if (dist >= -qh DISTround) {
    +            qh ZEROall_ok= False;
    +            if (!qh MERGEexact || dist > qh DISTround)
    +              goto LABELnonconvex;
    +          }
    +          break;
    +        }
    +      }
    +    }
    +  }
    +  trace2((qh ferr, 2012, "qh_checkzero: testall %d, facets are %s\n", testall,
    +        (qh MERGEexact && !testall) ?
    +           "not concave, flipped, or duplicate ridged" : "clearly convex"));
    +  return True;
    +
    + LABELproblem:
    +  qh ZEROall_ok= False;
    +  trace2((qh ferr, 2013, "qh_checkzero: facet f%d needs pre-merging\n",
    +       facet->id));
    +  return False;
    +
    + LABELnonconvex:
    +  trace2((qh ferr, 2014, "qh_checkzero: facet f%d and f%d are not clearly convex.  v%d dist %.2g\n",
    +         facet->id, neighbor->id, vertex->id, dist));
    +  return False;
    +} /* checkzero */
    +
    +/*---------------------------------
    +
    +  qh_compareangle( angle1, angle2 )
    +    used by qsort() to order merges by angle
    +*/
    +int qh_compareangle(const void *p1, const void *p2) {
    +  const mergeT *a= *((mergeT *const*)p1), *b= *((mergeT *const*)p2);
    +
    +  return((a->angle > b->angle) ? 1 : -1);
    +} /* compareangle */
    +
    +/*---------------------------------
    +
    +  qh_comparemerge( merge1, merge2 )
    +    used by qsort() to order merges
    +*/
    +int qh_comparemerge(const void *p1, const void *p2) {
    +  const mergeT *a= *((mergeT *const*)p1), *b= *((mergeT *const*)p2);
    +
    +  return(a->type - b->type);
    +} /* comparemerge */
    +
    +/*---------------------------------
    +
    +  qh_comparevisit( vertex1, vertex2 )
    +    used by qsort() to order vertices by their visitid
    +*/
    +int qh_comparevisit(const void *p1, const void *p2) {
    +  const vertexT *a= *((vertexT *const*)p1), *b= *((vertexT *const*)p2);
    +
    +  return(a->visitid - b->visitid);
    +} /* comparevisit */
    +
    +/*---------------------------------
    +
    +  qh_copynonconvex( atridge )
    +    set non-convex flag on other ridges (if any) between same neighbors
    +
    +  notes:
    +    may be faster if use smaller ridge set
    +
    +  design:
    +    for each ridge of atridge's top facet
    +      if ridge shares the same neighbor
    +        set nonconvex flag
    +*/
    +void qh_copynonconvex(ridgeT *atridge) {
    +  facetT *facet, *otherfacet;
    +  ridgeT *ridge, **ridgep;
    +
    +  facet= atridge->top;
    +  otherfacet= atridge->bottom;
    +  FOREACHridge_(facet->ridges) {
    +    if (otherfacet == otherfacet_(ridge, facet) && ridge != atridge) {
    +      ridge->nonconvex= True;
    +      trace4((qh ferr, 4020, "qh_copynonconvex: moved nonconvex flag from r%d to r%d\n",
    +              atridge->id, ridge->id));
    +      break;
    +    }
    +  }
    +} /* copynonconvex */
    +
    +/*---------------------------------
    +
    +  qh_degen_redundant_facet( facet )
    +    check facet for degen. or redundancy
    +
    +  notes:
    +    bumps vertex_visit
    +    called if a facet was redundant but no longer is (qh_merge_degenredundant)
    +    qh_appendmergeset() only appends first reference to facet (i.e., redundant)
    +
    +  see:
    +    qh_degen_redundant_neighbors()
    +
    +  design:
    +    test for redundant neighbor
    +    test for degenerate facet
    +*/
    +void qh_degen_redundant_facet(facetT *facet) {
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +
    +  trace4((qh ferr, 4021, "qh_degen_redundant_facet: test facet f%d for degen/redundant\n",
    +          facet->id));
    +  FOREACHneighbor_(facet) {
    +    qh vertex_visit++;
    +    FOREACHvertex_(neighbor->vertices)
    +      vertex->visitid= qh vertex_visit;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh vertex_visit)
    +        break;
    +    }
    +    if (!vertex) {
    +      qh_appendmergeset(facet, neighbor, MRGredundant, NULL);
    +      trace2((qh ferr, 2015, "qh_degen_redundant_facet: f%d is contained in f%d.  merge\n", facet->id, neighbor->id));
    +      return;
    +    }
    +  }
    +  if (qh_setsize(facet->neighbors) < qh hull_dim) {
    +    qh_appendmergeset(facet, facet, MRGdegen, NULL);
    +    trace2((qh ferr, 2016, "qh_degen_redundant_neighbors: f%d is degenerate.\n", facet->id));
    +  }
    +} /* degen_redundant_facet */
    +
    +
    +/*---------------------------------
    +
    +  qh_degen_redundant_neighbors( facet, delfacet,  )
    +    append degenerate and redundant neighbors to facet_mergeset
    +    if delfacet,
    +      only checks neighbors of both delfacet and facet
    +    also checks current facet for degeneracy
    +
    +  notes:
    +    bumps vertex_visit
    +    called for each qh_mergefacet() and qh_mergecycle()
    +    merge and statistics occur in merge_nonconvex
    +    qh_appendmergeset() only appends first reference to facet (i.e., redundant)
    +      it appends redundant facets after degenerate ones
    +
    +    a degenerate facet has fewer than hull_dim neighbors
    +    a redundant facet's vertices is a subset of its neighbor's vertices
    +    tests for redundant merges first (appendmergeset is nop for others)
    +    in a merge, only needs to test neighbors of merged facet
    +
    +  see:
    +    qh_merge_degenredundant() and qh_degen_redundant_facet()
    +
    +  design:
    +    test for degenerate facet
    +    test for redundant neighbor
    +    test for degenerate neighbor
    +*/
    +void qh_degen_redundant_neighbors(facetT *facet, facetT *delfacet) {
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +  int size;
    +
    +  trace4((qh ferr, 4022, "qh_degen_redundant_neighbors: test neighbors of f%d with delfacet f%d\n",
    +          facet->id, getid_(delfacet)));
    +  if ((size= qh_setsize(facet->neighbors)) < qh hull_dim) {
    +    qh_appendmergeset(facet, facet, MRGdegen, NULL);
    +    trace2((qh ferr, 2017, "qh_degen_redundant_neighbors: f%d is degenerate with %d neighbors.\n", facet->id, size));
    +  }
    +  if (!delfacet)
    +    delfacet= facet;
    +  qh vertex_visit++;
    +  FOREACHvertex_(facet->vertices)
    +    vertex->visitid= qh vertex_visit;
    +  FOREACHneighbor_(delfacet) {
    +    /* uses early out instead of checking vertex count */
    +    if (neighbor == facet)
    +      continue;
    +    FOREACHvertex_(neighbor->vertices) {
    +      if (vertex->visitid != qh vertex_visit)
    +        break;
    +    }
    +    if (!vertex) {
    +      qh_appendmergeset(neighbor, facet, MRGredundant, NULL);
    +      trace2((qh ferr, 2018, "qh_degen_redundant_neighbors: f%d is contained in f%d.  merge\n", neighbor->id, facet->id));
    +    }
    +  }
    +  FOREACHneighbor_(delfacet) {   /* redundant merges occur first */
    +    if (neighbor == facet)
    +      continue;
    +    if ((size= qh_setsize(neighbor->neighbors)) < qh hull_dim) {
    +      qh_appendmergeset(neighbor, neighbor, MRGdegen, NULL);
    +      trace2((qh ferr, 2019, "qh_degen_redundant_neighbors: f%d is degenerate with %d neighbors.  Neighbor of f%d.\n", neighbor->id, size, facet->id));
    +    }
    +  }
    +} /* degen_redundant_neighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_find_newvertex( oldvertex, vertices, ridges )
    +    locate new vertex for renaming old vertex
    +    vertices is a set of possible new vertices
    +      vertices sorted by number of deleted ridges
    +
    +  returns:
    +    newvertex or NULL
    +      each ridge includes both vertex and oldvertex
    +    vertices sorted by number of deleted ridges
    +
    +  notes:
    +    modifies vertex->visitid
    +    new vertex is in one of the ridges
    +    renaming will not cause a duplicate ridge
    +    renaming will minimize the number of deleted ridges
    +    newvertex may not be adjacent in the dual (though unlikely)
    +
    +  design:
    +    for each vertex in vertices
    +      set vertex->visitid to number of references in ridges
    +    remove unvisited vertices
    +    set qh.vertex_visit above all possible values
    +    sort vertices by number of references in ridges
    +    add each ridge to qh.hash_table
    +    for each vertex in vertices
    +      look for a vertex that would not cause a duplicate ridge after a rename
    +*/
    +vertexT *qh_find_newvertex(vertexT *oldvertex, setT *vertices, setT *ridges) {
    +  vertexT *vertex, **vertexp;
    +  setT *newridges;
    +  ridgeT *ridge, **ridgep;
    +  int size, hashsize;
    +  int hash;
    +
    +#ifndef qh_NOtrace
    +  if (qh IStracing >= 4) {
    +    qh_fprintf(qh ferr, 8063, "qh_find_newvertex: find new vertex for v%d from ",
    +             oldvertex->id);
    +    FOREACHvertex_(vertices)
    +      qh_fprintf(qh ferr, 8064, "v%d ", vertex->id);
    +    FOREACHridge_(ridges)
    +      qh_fprintf(qh ferr, 8065, "r%d ", ridge->id);
    +    qh_fprintf(qh ferr, 8066, "\n");
    +  }
    +#endif
    +  FOREACHvertex_(vertices)
    +    vertex->visitid= 0;
    +  FOREACHridge_(ridges) {
    +    FOREACHvertex_(ridge->vertices)
    +      vertex->visitid++;
    +  }
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->visitid) {
    +      qh_setdelnth(vertices, SETindex_(vertices,vertex));
    +      vertexp--; /* repeat since deleted this vertex */
    +    }
    +  }
    +  qh vertex_visit += (unsigned int)qh_setsize(ridges);
    +  if (!qh_setsize(vertices)) {
    +    trace4((qh ferr, 4023, "qh_find_newvertex: vertices not in ridges for v%d\n",
    +            oldvertex->id));
    +    return NULL;
    +  }
    +  qsort(SETaddr_(vertices, vertexT), (size_t)qh_setsize(vertices),
    +                sizeof(vertexT *), qh_comparevisit);
    +  /* can now use qh vertex_visit */
    +  if (qh PRINTstatistics) {
    +    size= qh_setsize(vertices);
    +    zinc_(Zintersect);
    +    zadd_(Zintersecttot, size);
    +    zmax_(Zintersectmax, size);
    +  }
    +  hashsize= qh_newhashtable(qh_setsize(ridges));
    +  FOREACHridge_(ridges)
    +    qh_hashridge(qh hash_table, hashsize, ridge, oldvertex);
    +  FOREACHvertex_(vertices) {
    +    newridges= qh_vertexridges(vertex);
    +    FOREACHridge_(newridges) {
    +      if (qh_hashridge_find(qh hash_table, hashsize, ridge, vertex, oldvertex, &hash)) {
    +        zinc_(Zdupridge);
    +        break;
    +      }
    +    }
    +    qh_settempfree(&newridges);
    +    if (!ridge)
    +      break;  /* found a rename */
    +  }
    +  if (vertex) {
    +    /* counted in qh_renamevertex */
    +    trace2((qh ferr, 2020, "qh_find_newvertex: found v%d for old v%d from %d vertices and %d ridges.\n",
    +      vertex->id, oldvertex->id, qh_setsize(vertices), qh_setsize(ridges)));
    +  }else {
    +    zinc_(Zfindfail);
    +    trace0((qh ferr, 14, "qh_find_newvertex: no vertex for renaming v%d(all duplicated ridges) during p%d\n",
    +      oldvertex->id, qh furthest_id));
    +  }
    +  qh_setfree(&qh hash_table);
    +  return vertex;
    +} /* find_newvertex */
    +
    +/*---------------------------------
    +
    +  qh_findbest_test( testcentrum, facet, neighbor, bestfacet, dist, mindist, maxdist )
    +    test neighbor of facet for qh_findbestneighbor()
    +    if testcentrum,
    +      tests centrum (assumes it is defined)
    +    else
    +      tests vertices
    +
    +  returns:
    +    if a better facet (i.e., vertices/centrum of facet closer to neighbor)
    +      updates bestfacet, dist, mindist, and maxdist
    +*/
    +void qh_findbest_test(boolT testcentrum, facetT *facet, facetT *neighbor,
    +      facetT **bestfacet, realT *distp, realT *mindistp, realT *maxdistp) {
    +  realT dist, mindist, maxdist;
    +
    +  if (testcentrum) {
    +    zzinc_(Zbestdist);
    +    qh_distplane(facet->center, neighbor, &dist);
    +    dist *= qh hull_dim; /* estimate furthest vertex */
    +    if (dist < 0) {
    +      maxdist= 0;
    +      mindist= dist;
    +      dist= -dist;
    +    }else {
    +      mindist= 0;
    +      maxdist= dist;
    +    }
    +  }else
    +    dist= qh_getdistance(facet, neighbor, &mindist, &maxdist);
    +  if (dist < *distp) {
    +    *bestfacet= neighbor;
    +    *mindistp= mindist;
    +    *maxdistp= maxdist;
    +    *distp= dist;
    +  }
    +} /* findbest_test */
    +
    +/*---------------------------------
    +
    +  qh_findbestneighbor( facet, dist, mindist, maxdist )
    +    finds best neighbor (least dist) of a facet for merging
    +
    +  returns:
    +    returns min and max distances and their max absolute value
    +
    +  notes:
    +    error if qh_ASvoronoi
    +    avoids merging old into new
    +    assumes ridge->nonconvex only set on one ridge between a pair of facets
    +    could use an early out predicate but not worth it
    +
    +  design:
    +    if a large facet
    +      will test centrum
    +    else
    +      will test vertices
    +    if a large facet
    +      test nonconvex neighbors for best merge
    +    else
    +      test all neighbors for the best merge
    +    if testing centrum
    +      get distance information
    +*/
    +facetT *qh_findbestneighbor(facetT *facet, realT *distp, realT *mindistp, realT *maxdistp) {
    +  facetT *neighbor, **neighborp, *bestfacet= NULL;
    +  ridgeT *ridge, **ridgep;
    +  boolT nonconvex= True, testcentrum= False;
    +  int size= qh_setsize(facet->vertices);
    +
    +  if(qh CENTERtype==qh_ASvoronoi){
    +    qh_fprintf(qh ferr, 6272, "qhull error: cannot call qh_findbestneighor for f%d while qh.CENTERtype is qh_ASvoronoi\n", facet->id);
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  *distp= REALmax;
    +  if (size > qh_BESTcentrum2 * qh hull_dim + qh_BESTcentrum) {
    +    testcentrum= True;
    +    zinc_(Zbestcentrum);
    +    if (!facet->center)
    +       facet->center= qh_getcentrum(facet);
    +  }
    +  if (size > qh hull_dim + qh_BESTnonconvex) {
    +    FOREACHridge_(facet->ridges) {
    +      if (ridge->nonconvex) {
    +        neighbor= otherfacet_(ridge, facet);
    +        qh_findbest_test(testcentrum, facet, neighbor,
    +                          &bestfacet, distp, mindistp, maxdistp);
    +      }
    +    }
    +  }
    +  if (!bestfacet) {
    +    nonconvex= False;
    +    FOREACHneighbor_(facet)
    +      qh_findbest_test(testcentrum, facet, neighbor,
    +                        &bestfacet, distp, mindistp, maxdistp);
    +  }
    +  if (!bestfacet) {
    +    qh_fprintf(qh ferr, 6095, "qhull internal error (qh_findbestneighbor): no neighbors for f%d\n", facet->id);
    +
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  if (testcentrum)
    +    qh_getdistance(facet, bestfacet, mindistp, maxdistp);
    +  trace3((qh ferr, 3002, "qh_findbestneighbor: f%d is best neighbor for f%d testcentrum? %d nonconvex? %d dist %2.2g min %2.2g max %2.2g\n",
    +     bestfacet->id, facet->id, testcentrum, nonconvex, *distp, *mindistp, *maxdistp));
    +  return(bestfacet);
    +} /* findbestneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_flippedmerges( facetlist, wasmerge )
    +    merge flipped facets into best neighbor
    +    assumes qh.facet_mergeset at top of temporary stack
    +
    +  returns:
    +    no flipped facets on facetlist
    +    sets wasmerge if merge occurred
    +    degen/redundant merges passed through
    +
    +  notes:
    +    othermerges not needed since qh.facet_mergeset is empty before & after
    +      keep it in case of change
    +
    +  design:
    +    append flipped facets to qh.facetmergeset
    +    for each flipped merge
    +      find best neighbor
    +      merge facet into neighbor
    +      merge degenerate and redundant facets
    +    remove flipped merges from qh.facet_mergeset
    +*/
    +void qh_flippedmerges(facetT *facetlist, boolT *wasmerge) {
    +  facetT *facet, *neighbor, *facet1;
    +  realT dist, mindist, maxdist;
    +  mergeT *merge, **mergep;
    +  setT *othermerges;
    +  int nummerge=0;
    +
    +  trace4((qh ferr, 4024, "qh_flippedmerges: begin\n"));
    +  FORALLfacet_(facetlist) {
    +    if (facet->flipped && !facet->visible)
    +      qh_appendmergeset(facet, facet, MRGflip, NULL);
    +  }
    +  othermerges= qh_settemppop(); /* was facet_mergeset */
    +  qh facet_mergeset= qh_settemp(qh TEMPsize);
    +  qh_settemppush(othermerges);
    +  FOREACHmerge_(othermerges) {
    +    facet1= merge->facet1;
    +    if (merge->type != MRGflip || facet1->visible)
    +      continue;
    +    if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +      qhmem.IStracing= qh IStracing= qh TRACElevel;
    +    neighbor= qh_findbestneighbor(facet1, &dist, &mindist, &maxdist);
    +    trace0((qh ferr, 15, "qh_flippedmerges: merge flipped f%d into f%d dist %2.2g during p%d\n",
    +      facet1->id, neighbor->id, dist, qh furthest_id));
    +    qh_mergefacet(facet1, neighbor, &mindist, &maxdist, !qh_MERGEapex);
    +    nummerge++;
    +    if (qh PRINTstatistics) {
    +      zinc_(Zflipped);
    +      wadd_(Wflippedtot, dist);
    +      wmax_(Wflippedmax, dist);
    +    }
    +    qh_merge_degenredundant();
    +  }
    +  FOREACHmerge_(othermerges) {
    +    if (merge->facet1->visible || merge->facet2->visible)
    +      qh_memfree(merge, (int)sizeof(mergeT));
    +    else
    +      qh_setappend(&qh facet_mergeset, merge);
    +  }
    +  qh_settempfree(&othermerges);
    +  if (nummerge)
    +    *wasmerge= True;
    +  trace1((qh ferr, 1010, "qh_flippedmerges: merged %d flipped facets into a good neighbor\n", nummerge));
    +} /* flippedmerges */
    +
    +
    +/*---------------------------------
    +
    +  qh_forcedmerges( wasmerge )
    +    merge duplicated ridges
    +
    +  returns:
    +    removes all duplicate ridges on facet_mergeset
    +    wasmerge set if merge
    +    qh.facet_mergeset may include non-forced merges(none for now)
    +    qh.degen_mergeset includes degen/redun merges
    +
    +  notes:
    +    duplicate ridges occur when the horizon is pinched,
    +        i.e. a subridge occurs in more than two horizon ridges.
    +     could rename vertices that pinch the horizon
    +    assumes qh_merge_degenredundant() has not be called
    +    othermerges isn't needed since facet_mergeset is empty afterwards
    +      keep it in case of change
    +
    +  design:
    +    for each duplicate ridge
    +      find current facets by chasing f.replace links
    +      check for wide merge due to duplicate ridge
    +      determine best direction for facet
    +      merge one facet into the other
    +      remove duplicate ridges from qh.facet_mergeset
    +*/
    +void qh_forcedmerges(boolT *wasmerge) {
    +  facetT *facet1, *facet2;
    +  mergeT *merge, **mergep;
    +  realT dist1, dist2, mindist1, mindist2, maxdist1, maxdist2;
    +  setT *othermerges;
    +  int nummerge=0, numflip=0;
    +
    +  if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +    qhmem.IStracing= qh IStracing= qh TRACElevel;
    +  trace4((qh ferr, 4025, "qh_forcedmerges: begin\n"));
    +  othermerges= qh_settemppop(); /* was facet_mergeset */
    +  qh facet_mergeset= qh_settemp(qh TEMPsize);
    +  qh_settemppush(othermerges);
    +  FOREACHmerge_(othermerges) {
    +    if (merge->type != MRGridge)
    +        continue;
    +    if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +        qhmem.IStracing= qh IStracing= qh TRACElevel;
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    while (facet1->visible)      /* must exist, no qh_merge_degenredunant */
    +      facet1= facet1->f.replace; /* previously merged facet */
    +    while (facet2->visible)
    +      facet2= facet2->f.replace; /* previously merged facet */
    +    if (facet1 == facet2)
    +      continue;
    +    if (!qh_setin(facet2->neighbors, facet1)) {
    +      qh_fprintf(qh ferr, 6096, "qhull internal error (qh_forcedmerges): f%d and f%d had a duplicate ridge but as f%d and f%d they are no longer neighbors\n",
    +               merge->facet1->id, merge->facet2->id, facet1->id, facet2->id);
    +      qh_errexit2(qh_ERRqhull, facet1, facet2);
    +    }
    +    dist1= qh_getdistance(facet1, facet2, &mindist1, &maxdist1);
    +    dist2= qh_getdistance(facet2, facet1, &mindist2, &maxdist2);
    +    qh_check_dupridge(facet1, dist1, facet2, dist2);
    +    if (dist1 < dist2)
    +      qh_mergefacet(facet1, facet2, &mindist1, &maxdist1, !qh_MERGEapex);
    +    else {
    +      qh_mergefacet(facet2, facet1, &mindist2, &maxdist2, !qh_MERGEapex);
    +      dist1= dist2;
    +      facet1= facet2;
    +    }
    +    if (facet1->flipped) {
    +      zinc_(Zmergeflipdup);
    +      numflip++;
    +    }else
    +      nummerge++;
    +    if (qh PRINTstatistics) {
    +      zinc_(Zduplicate);
    +      wadd_(Wduplicatetot, dist1);
    +      wmax_(Wduplicatemax, dist1);
    +    }
    +  }
    +  FOREACHmerge_(othermerges) {
    +    if (merge->type == MRGridge)
    +      qh_memfree(merge, (int)sizeof(mergeT));
    +    else
    +      qh_setappend(&qh facet_mergeset, merge);
    +  }
    +  qh_settempfree(&othermerges);
    +  if (nummerge)
    +    *wasmerge= True;
    +  trace1((qh ferr, 1011, "qh_forcedmerges: merged %d facets and %d flipped facets across duplicated ridges\n",
    +                nummerge, numflip));
    +} /* forcedmerges */
    +
    +
    +/*---------------------------------
    +
    +  qh_getmergeset( facetlist )
    +    determines nonconvex facets on facetlist
    +    tests !tested ridges and nonconvex ridges of !tested facets
    +
    +  returns:
    +    returns sorted qh.facet_mergeset of facet-neighbor pairs to be merged
    +    all ridges tested
    +
    +  notes:
    +    assumes no nonconvex ridges with both facets tested
    +    uses facet->tested/ridge->tested to prevent duplicate tests
    +    can not limit tests to modified ridges since the centrum changed
    +    uses qh.visit_id
    +
    +  see:
    +    qh_getmergeset_initial()
    +
    +  design:
    +    for each facet on facetlist
    +      for each ridge of facet
    +        if untested ridge
    +          test ridge for convexity
    +          if non-convex
    +            append ridge to qh.facet_mergeset
    +    sort qh.facet_mergeset by angle
    +*/
    +void qh_getmergeset(facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int nummerges;
    +
    +  nummerges= qh_setsize(qh facet_mergeset);
    +  trace4((qh ferr, 4026, "qh_getmergeset: started.\n"));
    +  qh visit_id++;
    +  FORALLfacet_(facetlist) {
    +    if (facet->tested)
    +      continue;
    +    facet->visitid= qh visit_id;
    +    facet->tested= True;  /* must be non-simplicial due to merge */
    +    FOREACHneighbor_(facet)
    +      neighbor->seen= False;
    +    FOREACHridge_(facet->ridges) {
    +      if (ridge->tested && !ridge->nonconvex)
    +        continue;
    +      /* if tested & nonconvex, need to append merge */
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->seen) {
    +        ridge->tested= True;
    +        ridge->nonconvex= False;
    +      }else if (neighbor->visitid != qh visit_id) {
    +        ridge->tested= True;
    +        ridge->nonconvex= False;
    +        neighbor->seen= True;      /* only one ridge is marked nonconvex */
    +        if (qh_test_appendmerge(facet, neighbor))
    +          ridge->nonconvex= True;
    +      }
    +    }
    +  }
    +  nummerges= qh_setsize(qh facet_mergeset);
    +  if (qh ANGLEmerge)
    +    qsort(SETaddr_(qh facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compareangle);
    +  else
    +    qsort(SETaddr_(qh facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_comparemerge);
    +  if (qh POSTmerging) {
    +    zadd_(Zmergesettot2, nummerges);
    +  }else {
    +    zadd_(Zmergesettot, nummerges);
    +    zmax_(Zmergesetmax, nummerges);
    +  }
    +  trace2((qh ferr, 2021, "qh_getmergeset: %d merges found\n", nummerges));
    +} /* getmergeset */
    +
    +
    +/*---------------------------------
    +
    +  qh_getmergeset_initial( facetlist )
    +    determine initial qh.facet_mergeset for facets
    +    tests all facet/neighbor pairs on facetlist
    +
    +  returns:
    +    sorted qh.facet_mergeset with nonconvex ridges
    +    sets facet->tested, ridge->tested, and ridge->nonconvex
    +
    +  notes:
    +    uses visit_id, assumes ridge->nonconvex is False
    +
    +  see:
    +    qh_getmergeset()
    +
    +  design:
    +    for each facet on facetlist
    +      for each untested neighbor of facet
    +        test facet and neighbor for convexity
    +        if non-convex
    +          append merge to qh.facet_mergeset
    +          mark one of the ridges as nonconvex
    +    sort qh.facet_mergeset by angle
    +*/
    +void qh_getmergeset_initial(facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int nummerges;
    +
    +  qh visit_id++;
    +  FORALLfacet_(facetlist) {
    +    facet->visitid= qh visit_id;
    +    facet->tested= True;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh visit_id) {
    +        if (qh_test_appendmerge(facet, neighbor)) {
    +          FOREACHridge_(neighbor->ridges) {
    +            if (facet == otherfacet_(ridge, neighbor)) {
    +              ridge->nonconvex= True;
    +              break;    /* only one ridge is marked nonconvex */
    +            }
    +          }
    +        }
    +      }
    +    }
    +    FOREACHridge_(facet->ridges)
    +      ridge->tested= True;
    +  }
    +  nummerges= qh_setsize(qh facet_mergeset);
    +  if (qh ANGLEmerge)
    +    qsort(SETaddr_(qh facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compareangle);
    +  else
    +    qsort(SETaddr_(qh facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_comparemerge);
    +  if (qh POSTmerging) {
    +    zadd_(Zmergeinittot2, nummerges);
    +  }else {
    +    zadd_(Zmergeinittot, nummerges);
    +    zmax_(Zmergeinitmax, nummerges);
    +  }
    +  trace2((qh ferr, 2022, "qh_getmergeset_initial: %d merges found\n", nummerges));
    +} /* getmergeset_initial */
    +
    +
    +/*---------------------------------
    +
    +  qh_hashridge( hashtable, hashsize, ridge, oldvertex )
    +    add ridge to hashtable without oldvertex
    +
    +  notes:
    +    assumes hashtable is large enough
    +
    +  design:
    +    determine hash value for ridge without oldvertex
    +    find next empty slot for ridge
    +*/
    +void qh_hashridge(setT *hashtable, int hashsize, ridgeT *ridge, vertexT *oldvertex) {
    +  int hash;
    +  ridgeT *ridgeA;
    +
    +  hash= qh_gethash(hashsize, ridge->vertices, qh hull_dim-1, 0, oldvertex);
    +  while (True) {
    +    if (!(ridgeA= SETelemt_(hashtable, hash, ridgeT))) {
    +      SETelem_(hashtable, hash)= ridge;
    +      break;
    +    }else if (ridgeA == ridge)
    +      break;
    +    if (++hash == hashsize)
    +      hash= 0;
    +  }
    +} /* hashridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_hashridge_find( hashtable, hashsize, ridge, vertex, oldvertex, hashslot )
    +    returns matching ridge without oldvertex in hashtable
    +      for ridge without vertex
    +    if oldvertex is NULL
    +      matches with any one skip
    +
    +  returns:
    +    matching ridge or NULL
    +    if no match,
    +      if ridge already in   table
    +        hashslot= -1
    +      else
    +        hashslot= next NULL index
    +
    +  notes:
    +    assumes hashtable is large enough
    +    can't match ridge to itself
    +
    +  design:
    +    get hash value for ridge without vertex
    +    for each hashslot
    +      return match if ridge matches ridgeA without oldvertex
    +*/
    +ridgeT *qh_hashridge_find(setT *hashtable, int hashsize, ridgeT *ridge,
    +              vertexT *vertex, vertexT *oldvertex, int *hashslot) {
    +  int hash;
    +  ridgeT *ridgeA;
    +
    +  *hashslot= 0;
    +  zinc_(Zhashridge);
    +  hash= qh_gethash(hashsize, ridge->vertices, qh hull_dim-1, 0, vertex);
    +  while ((ridgeA= SETelemt_(hashtable, hash, ridgeT))) {
    +    if (ridgeA == ridge)
    +      *hashslot= -1;
    +    else {
    +      zinc_(Zhashridgetest);
    +      if (qh_setequal_except(ridge->vertices, vertex, ridgeA->vertices, oldvertex))
    +        return ridgeA;
    +    }
    +    if (++hash == hashsize)
    +      hash= 0;
    +  }
    +  if (!*hashslot)
    +    *hashslot= hash;
    +  return NULL;
    +} /* hashridge_find */
    +
    +
    +/*---------------------------------
    +
    +  qh_makeridges( facet )
    +    creates explicit ridges between simplicial facets
    +
    +  returns:
    +    facet with ridges and without qh_MERGEridge
    +    ->simplicial is False
    +
    +  notes:
    +    allows qh_MERGEridge flag
    +    uses existing ridges
    +    duplicate neighbors ok if ridges already exist (qh_mergecycle_ridges)
    +
    +  see:
    +    qh_mergecycle_ridges()
    +
    +  design:
    +    look for qh_MERGEridge neighbors
    +    mark neighbors that already have ridges
    +    for each unprocessed neighbor of facet
    +      create a ridge for neighbor and facet
    +    if any qh_MERGEridge neighbors
    +      delete qh_MERGEridge flags (already handled by qh_mark_dupridges)
    +*/
    +void qh_makeridges(facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int neighbor_i, neighbor_n;
    +  boolT toporient, mergeridge= False;
    +
    +  if (!facet->simplicial)
    +    return;
    +  trace4((qh ferr, 4027, "qh_makeridges: make ridges for f%d\n", facet->id));
    +  facet->simplicial= False;
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      mergeridge= True;
    +    else
    +      neighbor->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges)
    +    otherfacet_(ridge, facet)->seen= True;
    +  FOREACHneighbor_i_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      continue;  /* fixed by qh_mark_dupridges */
    +    else if (!neighbor->seen) {  /* no current ridges */
    +      ridge= qh_newridge();
    +      ridge->vertices= qh_setnew_delnthsorted(facet->vertices, qh hull_dim,
    +                                                          neighbor_i, 0);
    +      toporient= facet->toporient ^ (neighbor_i & 0x1);
    +      if (toporient) {
    +        ridge->top= facet;
    +        ridge->bottom= neighbor;
    +      }else {
    +        ridge->top= neighbor;
    +        ridge->bottom= facet;
    +      }
    +#if 0 /* this also works */
    +      flip= (facet->toporient ^ neighbor->toporient)^(skip1 & 0x1) ^ (skip2 & 0x1);
    +      if (facet->toporient ^ (skip1 & 0x1) ^ flip) {
    +        ridge->top= neighbor;
    +        ridge->bottom= facet;
    +      }else {
    +        ridge->top= facet;
    +        ridge->bottom= neighbor;
    +      }
    +#endif
    +      qh_setappend(&(facet->ridges), ridge);
    +      qh_setappend(&(neighbor->ridges), ridge);
    +    }
    +  }
    +  if (mergeridge) {
    +    while (qh_setdel(facet->neighbors, qh_MERGEridge))
    +      ; /* delete each one */
    +  }
    +} /* makeridges */
    +
    +
    +/*---------------------------------
    +
    +  qh_mark_dupridges( facetlist )
    +    add duplicated ridges to qh.facet_mergeset
    +    facet->dupridge is true
    +
    +  returns:
    +    duplicate ridges on qh.facet_mergeset
    +    ->mergeridge/->mergeridge2 set
    +    duplicate ridges marked by qh_MERGEridge and both sides facet->dupridge
    +    no MERGEridges in neighbor sets
    +
    +  notes:
    +    duplicate ridges occur when the horizon is pinched,
    +        i.e. a subridge occurs in more than two horizon ridges.
    +    could rename vertices that pinch the horizon (thus removing subridge)
    +    uses qh.visit_id
    +
    +  design:
    +    for all facets on facetlist
    +      if facet contains a duplicate ridge
    +        for each neighbor of facet
    +          if neighbor marked qh_MERGEridge (one side of the merge)
    +            set facet->mergeridge
    +          else
    +            if neighbor contains a duplicate ridge
    +            and the back link is qh_MERGEridge
    +              append duplicate ridge to qh.facet_mergeset
    +   for each duplicate ridge
    +     make ridge sets in preparation for merging
    +     remove qh_MERGEridge from neighbor set
    +   for each duplicate ridge
    +     restore the missing neighbor from the neighbor set that was qh_MERGEridge
    +     add the missing ridge for this neighbor
    +*/
    +void qh_mark_dupridges(facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  int nummerge=0;
    +  mergeT *merge, **mergep;
    +
    +
    +  trace4((qh ferr, 4028, "qh_mark_dupridges: identify duplicate ridges\n"));
    +  FORALLfacet_(facetlist) {
    +    if (facet->dupridge) {
    +      FOREACHneighbor_(facet) {
    +        if (neighbor == qh_MERGEridge) {
    +          facet->mergeridge= True;
    +          continue;
    +        }
    +        if (neighbor->dupridge
    +        && !qh_setin(neighbor->neighbors, facet)) { /* qh_MERGEridge */
    +          qh_appendmergeset(facet, neighbor, MRGridge, NULL);
    +          facet->mergeridge2= True;
    +          facet->mergeridge= True;
    +          nummerge++;
    +        }
    +      }
    +    }
    +  }
    +  if (!nummerge)
    +    return;
    +  FORALLfacet_(facetlist) {            /* gets rid of qh_MERGEridge */
    +    if (facet->mergeridge && !facet->mergeridge2)
    +      qh_makeridges(facet);
    +  }
    +  FOREACHmerge_(qh facet_mergeset) {   /* restore the missing neighbors */
    +    if (merge->type == MRGridge) {
    +      qh_setappend(&merge->facet2->neighbors, merge->facet1);
    +      qh_makeridges(merge->facet1);   /* and the missing ridges */
    +    }
    +  }
    +  trace1((qh ferr, 1012, "qh_mark_dupridges: found %d duplicated ridges\n",
    +                nummerge));
    +} /* mark_dupridges */
    +
    +/*---------------------------------
    +
    +  qh_maydropneighbor( facet )
    +    drop neighbor relationship if no ridge between facet and neighbor
    +
    +  returns:
    +    neighbor sets updated
    +    appends degenerate facets to qh.facet_mergeset
    +
    +  notes:
    +    won't cause redundant facets since vertex inclusion is the same
    +    may drop vertex and neighbor if no ridge
    +    uses qh.visit_id
    +
    +  design:
    +    visit all neighbors with ridges
    +    for each unvisited neighbor of facet
    +      delete neighbor and facet from the neighbor sets
    +      if neighbor becomes degenerate
    +        append neighbor to qh.degen_mergeset
    +    if facet is degenerate
    +      append facet to qh.degen_mergeset
    +*/
    +void qh_maydropneighbor(facetT *facet) {
    +  ridgeT *ridge, **ridgep;
    +  realT angledegen= qh_ANGLEdegen;
    +  facetT *neighbor, **neighborp;
    +
    +  qh visit_id++;
    +  trace4((qh ferr, 4029, "qh_maydropneighbor: test f%d for no ridges to a neighbor\n",
    +          facet->id));
    +  FOREACHridge_(facet->ridges) {
    +    ridge->top->visitid= qh visit_id;
    +    ridge->bottom->visitid= qh visit_id;
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (neighbor->visitid != qh visit_id) {
    +      trace0((qh ferr, 17, "qh_maydropneighbor: facets f%d and f%d are no longer neighbors during p%d\n",
    +            facet->id, neighbor->id, qh furthest_id));
    +      zinc_(Zdropneighbor);
    +      qh_setdel(facet->neighbors, neighbor);
    +      neighborp--;  /* repeat, deleted a neighbor */
    +      qh_setdel(neighbor->neighbors, facet);
    +      if (qh_setsize(neighbor->neighbors) < qh hull_dim) {
    +        zinc_(Zdropdegen);
    +        qh_appendmergeset(neighbor, neighbor, MRGdegen, &angledegen);
    +        trace2((qh ferr, 2023, "qh_maydropneighbors: f%d is degenerate.\n", neighbor->id));
    +      }
    +    }
    +  }
    +  if (qh_setsize(facet->neighbors) < qh hull_dim) {
    +    zinc_(Zdropdegen);
    +    qh_appendmergeset(facet, facet, MRGdegen, &angledegen);
    +    trace2((qh ferr, 2024, "qh_maydropneighbors: f%d is degenerate.\n", facet->id));
    +  }
    +} /* maydropneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_merge_degenredundant()
    +    merge all degenerate and redundant facets
    +    qh.degen_mergeset contains merges from qh_degen_redundant_neighbors()
    +
    +  returns:
    +    number of merges performed
    +    resets facet->degenerate/redundant
    +    if deleted (visible) facet has no neighbors
    +      sets ->f.replace to NULL
    +
    +  notes:
    +    redundant merges happen before degenerate ones
    +    merging and renaming vertices can result in degen/redundant facets
    +
    +  design:
    +    for each merge on qh.degen_mergeset
    +      if redundant merge
    +        if non-redundant facet merged into redundant facet
    +          recheck facet for redundancy
    +        else
    +          merge redundant facet into other facet
    +*/
    +int qh_merge_degenredundant(void) {
    +  int size;
    +  mergeT *merge;
    +  facetT *bestneighbor, *facet1, *facet2;
    +  realT dist, mindist, maxdist;
    +  vertexT *vertex, **vertexp;
    +  int nummerges= 0;
    +  mergeType mergetype;
    +
    +  while ((merge= (mergeT*)qh_setdellast(qh degen_mergeset))) {
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    mergetype= merge->type;
    +    qh_memfree(merge, (int)sizeof(mergeT));
    +    if (facet1->visible)
    +      continue;
    +    facet1->degenerate= False;
    +    facet1->redundant= False;
    +    if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +      qhmem.IStracing= qh IStracing= qh TRACElevel;
    +    if (mergetype == MRGredundant) {
    +      zinc_(Zneighbor);
    +      while (facet2->visible) {
    +        if (!facet2->f.replace) {
    +          qh_fprintf(qh ferr, 6097, "qhull internal error (qh_merge_degenredunant): f%d redundant but f%d has no replacement\n",
    +               facet1->id, facet2->id);
    +          qh_errexit2(qh_ERRqhull, facet1, facet2);
    +        }
    +        facet2= facet2->f.replace;
    +      }
    +      if (facet1 == facet2) {
    +        qh_degen_redundant_facet(facet1); /* in case of others */
    +        continue;
    +      }
    +      trace2((qh ferr, 2025, "qh_merge_degenredundant: facet f%d is contained in f%d, will merge\n",
    +            facet1->id, facet2->id));
    +      qh_mergefacet(facet1, facet2, NULL, NULL, !qh_MERGEapex);
    +      /* merge distance is already accounted for */
    +      nummerges++;
    +    }else {  /* mergetype == MRGdegen, other merges may have fixed */
    +      if (!(size= qh_setsize(facet1->neighbors))) {
    +        zinc_(Zdelfacetdup);
    +        trace2((qh ferr, 2026, "qh_merge_degenredundant: facet f%d has no neighbors.  Deleted\n", facet1->id));
    +        qh_willdelete(facet1, NULL);
    +        FOREACHvertex_(facet1->vertices) {
    +          qh_setdel(vertex->neighbors, facet1);
    +          if (!SETfirst_(vertex->neighbors)) {
    +            zinc_(Zdegenvertex);
    +            trace2((qh ferr, 2027, "qh_merge_degenredundant: deleted v%d because f%d has no neighbors\n",
    +                 vertex->id, facet1->id));
    +            vertex->deleted= True;
    +            qh_setappend(&qh del_vertices, vertex);
    +          }
    +        }
    +        nummerges++;
    +      }else if (size < qh hull_dim) {
    +        bestneighbor= qh_findbestneighbor(facet1, &dist, &mindist, &maxdist);
    +        trace2((qh ferr, 2028, "qh_merge_degenredundant: facet f%d has %d neighbors, merge into f%d dist %2.2g\n",
    +              facet1->id, size, bestneighbor->id, dist));
    +        qh_mergefacet(facet1, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +        nummerges++;
    +        if (qh PRINTstatistics) {
    +          zinc_(Zdegen);
    +          wadd_(Wdegentot, dist);
    +          wmax_(Wdegenmax, dist);
    +        }
    +      } /* else, another merge fixed the degeneracy and redundancy tested */
    +    }
    +  }
    +  return nummerges;
    +} /* merge_degenredundant */
    +
    +/*---------------------------------
    +
    +  qh_merge_nonconvex( facet1, facet2, mergetype )
    +    remove non-convex ridge between facet1 into facet2
    +    mergetype gives why the facet's are non-convex
    +
    +  returns:
    +    merges one of the facets into the best neighbor
    +
    +  design:
    +    if one of the facets is a new facet
    +      prefer merging new facet into old facet
    +    find best neighbors for both facets
    +    merge the nearest facet into its best neighbor
    +    update the statistics
    +*/
    +void qh_merge_nonconvex(facetT *facet1, facetT *facet2, mergeType mergetype) {
    +  facetT *bestfacet, *bestneighbor, *neighbor;
    +  realT dist, dist2, mindist, mindist2, maxdist, maxdist2;
    +
    +  if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +    qhmem.IStracing= qh IStracing= qh TRACElevel;
    +  trace3((qh ferr, 3003, "qh_merge_nonconvex: merge #%d for f%d and f%d type %d\n",
    +      zzval_(Ztotmerge) + 1, facet1->id, facet2->id, mergetype));
    +  /* concave or coplanar */
    +  if (!facet1->newfacet) {
    +    bestfacet= facet2;   /* avoid merging old facet if new is ok */
    +    facet2= facet1;
    +    facet1= bestfacet;
    +  }else
    +    bestfacet= facet1;
    +  bestneighbor= qh_findbestneighbor(bestfacet, &dist, &mindist, &maxdist);
    +  neighbor= qh_findbestneighbor(facet2, &dist2, &mindist2, &maxdist2);
    +  if (dist < dist2) {
    +    qh_mergefacet(bestfacet, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +  }else if (qh AVOIDold && !facet2->newfacet
    +  && ((mindist >= -qh MAXcoplanar && maxdist <= qh max_outside)
    +       || dist * 1.5 < dist2)) {
    +    zinc_(Zavoidold);
    +    wadd_(Wavoidoldtot, dist);
    +    wmax_(Wavoidoldmax, dist);
    +    trace2((qh ferr, 2029, "qh_merge_nonconvex: avoid merging old facet f%d dist %2.2g.  Use f%d dist %2.2g instead\n",
    +           facet2->id, dist2, facet1->id, dist2));
    +    qh_mergefacet(bestfacet, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +  }else {
    +    qh_mergefacet(facet2, neighbor, &mindist2, &maxdist2, !qh_MERGEapex);
    +    dist= dist2;
    +  }
    +  if (qh PRINTstatistics) {
    +    if (mergetype == MRGanglecoplanar) {
    +      zinc_(Zacoplanar);
    +      wadd_(Wacoplanartot, dist);
    +      wmax_(Wacoplanarmax, dist);
    +    }else if (mergetype == MRGconcave) {
    +      zinc_(Zconcave);
    +      wadd_(Wconcavetot, dist);
    +      wmax_(Wconcavemax, dist);
    +    }else { /* MRGcoplanar */
    +      zinc_(Zcoplanar);
    +      wadd_(Wcoplanartot, dist);
    +      wmax_(Wcoplanarmax, dist);
    +    }
    +  }
    +} /* merge_nonconvex */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle( samecycle, newfacet )
    +    merge a cycle of facets starting at samecycle into a newfacet
    +    newfacet is a horizon facet with ->normal
    +    samecycle facets are simplicial from an apex
    +
    +  returns:
    +    initializes vertex neighbors on first merge
    +    samecycle deleted (placed on qh.visible_list)
    +    newfacet at end of qh.facet_list
    +    deleted vertices on qh.del_vertices
    +
    +  see:
    +    qh_mergefacet()
    +    called by qh_mergecycle_all() for multiple, same cycle facets
    +
    +  design:
    +    make vertex neighbors if necessary
    +    make ridges for newfacet
    +    merge neighbor sets of samecycle into newfacet
    +    merge ridges of samecycle into newfacet
    +    merge vertex neighbors of samecycle into newfacet
    +    make apex of samecycle the apex of newfacet
    +    if newfacet wasn't a new facet
    +      add its vertices to qh.newvertex_list
    +    delete samecycle facets a make newfacet a newfacet
    +*/
    +void qh_mergecycle(facetT *samecycle, facetT *newfacet) {
    +  int traceonce= False, tracerestore= 0;
    +  vertexT *apex;
    +#ifndef qh_NOtrace
    +  facetT *same;
    +#endif
    +
    +  if (newfacet->tricoplanar) {
    +    if (!qh TRInormals) {
    +      qh_fprintf(qh ferr, 6224, "Qhull internal error (qh_mergecycle): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit(qh_ERRqhull, newfacet, NULL);
    +    }
    +    newfacet->tricoplanar= False;
    +    newfacet->keepcentrum= False;
    +  }
    +  if (!qh VERTEXneighbors)
    +    qh_vertexneighbors();
    +  zzinc_(Ztotmerge);
    +  if (qh REPORTfreq2 && qh POSTmerging) {
    +    if (zzval_(Ztotmerge) > qh mergereport + qh REPORTfreq2)
    +      qh_tracemerging();
    +  }
    +#ifndef qh_NOtrace
    +  if (qh TRACEmerge == zzval_(Ztotmerge))
    +    qhmem.IStracing= qh IStracing= qh TRACElevel;
    +  trace2((qh ferr, 2030, "qh_mergecycle: merge #%d for facets from cycle f%d into coplanar horizon f%d\n",
    +        zzval_(Ztotmerge), samecycle->id, newfacet->id));
    +  if (newfacet == qh tracefacet) {
    +    tracerestore= qh IStracing;
    +    qh IStracing= 4;
    +    qh_fprintf(qh ferr, 8068, "qh_mergecycle: ========= trace merge %d of samecycle %d into trace f%d, furthest is p%d\n",
    +               zzval_(Ztotmerge), samecycle->id, newfacet->id,  qh furthest_id);
    +    traceonce= True;
    +  }
    +  if (qh IStracing >=4) {
    +    qh_fprintf(qh ferr, 8069, "  same cycle:");
    +    FORALLsame_cycle_(samecycle)
    +      qh_fprintf(qh ferr, 8070, " f%d", same->id);
    +    qh_fprintf(qh ferr, 8071, "\n");
    +  }
    +  if (qh IStracing >=4)
    +    qh_errprint("MERGING CYCLE", samecycle, newfacet, NULL, NULL);
    +#endif /* !qh_NOtrace */
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  qh_makeridges(newfacet);
    +  qh_mergecycle_neighbors(samecycle, newfacet);
    +  qh_mergecycle_ridges(samecycle, newfacet);
    +  qh_mergecycle_vneighbors(samecycle, newfacet);
    +  if (SETfirstt_(newfacet->vertices, vertexT) != apex)
    +    qh_setaddnth(&newfacet->vertices, 0, apex);  /* apex has last id */
    +  if (!newfacet->newfacet)
    +    qh_newvertices(newfacet->vertices);
    +  qh_mergecycle_facets(samecycle, newfacet);
    +  qh_tracemerge(samecycle, newfacet);
    +  /* check for degen_redundant_neighbors after qh_forcedmerges() */
    +  if (traceonce) {
    +    qh_fprintf(qh ferr, 8072, "qh_mergecycle: end of trace facet\n");
    +    qh IStracing= tracerestore;
    +  }
    +} /* mergecycle */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_all( facetlist, wasmerge )
    +    merge all samecycles of coplanar facets into horizon
    +    don't merge facets with ->mergeridge (these already have ->normal)
    +    all facets are simplicial from apex
    +    all facet->cycledone == False
    +
    +  returns:
    +    all newfacets merged into coplanar horizon facets
    +    deleted vertices on  qh.del_vertices
    +    sets wasmerge if any merge
    +
    +  see:
    +    calls qh_mergecycle for multiple, same cycle facets
    +
    +  design:
    +    for each facet on facetlist
    +      skip facets with duplicate ridges and normals
    +      check that facet is in a samecycle (->mergehorizon)
    +      if facet only member of samecycle
    +        sets vertex->delridge for all vertices except apex
    +        merge facet into horizon
    +      else
    +        mark all facets in samecycle
    +        remove facets with duplicate ridges from samecycle
    +        merge samecycle into horizon (deletes facets from facetlist)
    +*/
    +void qh_mergecycle_all(facetT *facetlist, boolT *wasmerge) {
    +  facetT *facet, *same, *prev, *horizon;
    +  facetT *samecycle= NULL, *nextfacet, *nextsame;
    +  vertexT *apex, *vertex, **vertexp;
    +  int cycles=0, total=0, facets, nummerge;
    +
    +  trace2((qh ferr, 2031, "qh_mergecycle_all: begin\n"));
    +  for (facet= facetlist; facet && (nextfacet= facet->next); facet= nextfacet) {
    +    if (facet->normal)
    +      continue;
    +    if (!facet->mergehorizon) {
    +      qh_fprintf(qh ferr, 6225, "Qhull internal error (qh_mergecycle_all): f%d without normal\n", facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +    horizon= SETfirstt_(facet->neighbors, facetT);
    +    if (facet->f.samecycle == facet) {
    +      zinc_(Zonehorizon);
    +      /* merge distance done in qh_findhorizon */
    +      apex= SETfirstt_(facet->vertices, vertexT);
    +      FOREACHvertex_(facet->vertices) {
    +        if (vertex != apex)
    +          vertex->delridge= True;
    +      }
    +      horizon->f.newcycle= NULL;
    +      qh_mergefacet(facet, horizon, NULL, NULL, qh_MERGEapex);
    +    }else {
    +      samecycle= facet;
    +      facets= 0;
    +      prev= facet;
    +      for (same= facet->f.samecycle; same;  /* FORALLsame_cycle_(facet) */
    +           same= (same == facet ? NULL :nextsame)) { /* ends at facet */
    +        nextsame= same->f.samecycle;
    +        if (same->cycledone || same->visible)
    +          qh_infiniteloop(same);
    +        same->cycledone= True;
    +        if (same->normal) {
    +          prev->f.samecycle= same->f.samecycle; /* unlink ->mergeridge */
    +          same->f.samecycle= NULL;
    +        }else {
    +          prev= same;
    +          facets++;
    +        }
    +      }
    +      while (nextfacet && nextfacet->cycledone)  /* will delete samecycle */
    +        nextfacet= nextfacet->next;
    +      horizon->f.newcycle= NULL;
    +      qh_mergecycle(samecycle, horizon);
    +      nummerge= horizon->nummerge + facets;
    +      if (nummerge > qh_MAXnummerge)
    +        horizon->nummerge= qh_MAXnummerge;
    +      else
    +        horizon->nummerge= (short unsigned int)nummerge;
    +      zzinc_(Zcyclehorizon);
    +      total += facets;
    +      zzadd_(Zcyclefacettot, facets);
    +      zmax_(Zcyclefacetmax, facets);
    +    }
    +    cycles++;
    +  }
    +  if (cycles)
    +    *wasmerge= True;
    +  trace1((qh ferr, 1013, "qh_mergecycle_all: merged %d same cycles or facets into coplanar horizons\n", cycles));
    +} /* mergecycle_all */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_facets( samecycle, newfacet )
    +    finish merge of samecycle into newfacet
    +
    +  returns:
    +    samecycle prepended to visible_list for later deletion and partitioning
    +      each facet->f.replace == newfacet
    +
    +    newfacet moved to end of qh.facet_list
    +      makes newfacet a newfacet (get's facet1->id if it was old)
    +      sets newfacet->newmerge
    +      clears newfacet->center (unless merging into a large facet)
    +      clears newfacet->tested and ridge->tested for facet1
    +
    +    adds neighboring facets to facet_mergeset if redundant or degenerate
    +
    +  design:
    +    make newfacet a new facet and set its flags
    +    move samecycle facets to qh.visible_list for later deletion
    +    unless newfacet is large
    +      remove its centrum
    +*/
    +void qh_mergecycle_facets(facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *next;
    +
    +  trace4((qh ferr, 4030, "qh_mergecycle_facets: make newfacet new and samecycle deleted\n"));
    +  qh_removefacet(newfacet);  /* append as a newfacet to end of qh facet_list */
    +  qh_appendfacet(newfacet);
    +  newfacet->newfacet= True;
    +  newfacet->simplicial= False;
    +  newfacet->newmerge= True;
    +
    +  for (same= samecycle->f.samecycle; same; same= (same == samecycle ?  NULL : next)) {
    +    next= same->f.samecycle;  /* reused by willdelete */
    +    qh_willdelete(same, newfacet);
    +  }
    +  if (newfacet->center
    +      && qh_setsize(newfacet->vertices) <= qh hull_dim + qh_MAXnewcentrum) {
    +    qh_memfree(newfacet->center, qh normal_size);
    +    newfacet->center= NULL;
    +  }
    +  trace3((qh ferr, 3004, "qh_mergecycle_facets: merged facets from cycle f%d into f%d\n",
    +             samecycle->id, newfacet->id));
    +} /* mergecycle_facets */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_neighbors( samecycle, newfacet )
    +    add neighbors for samecycle facets to newfacet
    +
    +  returns:
    +    newfacet with updated neighbors and vice-versa
    +    newfacet has ridges
    +    all neighbors of newfacet marked with qh.visit_id
    +    samecycle facets marked with qh.visit_id-1
    +    ridges updated for simplicial neighbors of samecycle with a ridge
    +
    +  notes:
    +    assumes newfacet not in samecycle
    +    usually, samecycle facets are new, simplicial facets without internal ridges
    +      not so if horizon facet is coplanar to two different samecycles
    +
    +  see:
    +    qh_mergeneighbors()
    +
    +  design:
    +    check samecycle
    +    delete neighbors from newfacet that are also in samecycle
    +    for each neighbor of a facet in samecycle
    +      if neighbor is simplicial
    +        if first visit
    +          move the neighbor relation to newfacet
    +          update facet links for its ridges
    +        else
    +          make ridges for neighbor
    +          remove samecycle reference
    +      else
    +        update neighbor sets
    +*/
    +void qh_mergecycle_neighbors(facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *neighbor, **neighborp;
    +  int delneighbors= 0, newneighbors= 0;
    +  unsigned int samevisitid;
    +  ridgeT *ridge, **ridgep;
    +
    +  samevisitid= ++qh visit_id;
    +  FORALLsame_cycle_(samecycle) {
    +    if (same->visitid == samevisitid || same->visible)
    +      qh_infiniteloop(samecycle);
    +    same->visitid= samevisitid;
    +  }
    +  newfacet->visitid= ++qh visit_id;
    +  trace4((qh ferr, 4031, "qh_mergecycle_neighbors: delete shared neighbors from newfacet\n"));
    +  FOREACHneighbor_(newfacet) {
    +    if (neighbor->visitid == samevisitid) {
    +      SETref_(neighbor)= NULL;  /* samecycle neighbors deleted */
    +      delneighbors++;
    +    }else
    +      neighbor->visitid= qh visit_id;
    +  }
    +  qh_setcompact(newfacet->neighbors);
    +
    +  trace4((qh ferr, 4032, "qh_mergecycle_neighbors: update neighbors\n"));
    +  FORALLsame_cycle_(samecycle) {
    +    FOREACHneighbor_(same) {
    +      if (neighbor->visitid == samevisitid)
    +        continue;
    +      if (neighbor->simplicial) {
    +        if (neighbor->visitid != qh visit_id) {
    +          qh_setappend(&newfacet->neighbors, neighbor);
    +          qh_setreplace(neighbor->neighbors, same, newfacet);
    +          newneighbors++;
    +          neighbor->visitid= qh visit_id;
    +          FOREACHridge_(neighbor->ridges) { /* update ridge in case of qh_makeridges */
    +            if (ridge->top == same) {
    +              ridge->top= newfacet;
    +              break;
    +            }else if (ridge->bottom == same) {
    +              ridge->bottom= newfacet;
    +              break;
    +            }
    +          }
    +        }else {
    +          qh_makeridges(neighbor);
    +          qh_setdel(neighbor->neighbors, same);
    +          /* same can't be horizon facet for neighbor */
    +        }
    +      }else { /* non-simplicial neighbor */
    +        qh_setdel(neighbor->neighbors, same);
    +        if (neighbor->visitid != qh visit_id) {
    +          qh_setappend(&neighbor->neighbors, newfacet);
    +          qh_setappend(&newfacet->neighbors, neighbor);
    +          neighbor->visitid= qh visit_id;
    +          newneighbors++;
    +        }
    +      }
    +    }
    +  }
    +  trace2((qh ferr, 2032, "qh_mergecycle_neighbors: deleted %d neighbors and added %d\n",
    +             delneighbors, newneighbors));
    +} /* mergecycle_neighbors */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_ridges( samecycle, newfacet )
    +    add ridges/neighbors for facets in samecycle to newfacet
    +    all new/old neighbors of newfacet marked with qh.visit_id
    +    facets in samecycle marked with qh.visit_id-1
    +    newfacet marked with qh.visit_id
    +
    +  returns:
    +    newfacet has merged ridges
    +
    +  notes:
    +    ridge already updated for simplicial neighbors of samecycle with a ridge
    +
    +  see:
    +    qh_mergeridges()
    +    qh_makeridges()
    +
    +  design:
    +    remove ridges between newfacet and samecycle
    +    for each facet in samecycle
    +      for each ridge in facet
    +        update facet pointers in ridge
    +        skip ridges processed in qh_mergecycle_neighors
    +        free ridges between newfacet and samecycle
    +        free ridges between facets of samecycle (on 2nd visit)
    +        append remaining ridges to newfacet
    +      if simpilicial facet
    +        for each neighbor of facet
    +          if simplicial facet
    +          and not samecycle facet or newfacet
    +            make ridge between neighbor and newfacet
    +*/
    +void qh_mergecycle_ridges(facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *neighbor= NULL;
    +  int numold=0, numnew=0;
    +  int neighbor_i, neighbor_n;
    +  unsigned int samevisitid;
    +  ridgeT *ridge, **ridgep;
    +  boolT toporient;
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  trace4((qh ferr, 4033, "qh_mergecycle_ridges: delete shared ridges from newfacet\n"));
    +  samevisitid= qh visit_id -1;
    +  FOREACHridge_(newfacet->ridges) {
    +    neighbor= otherfacet_(ridge, newfacet);
    +    if (neighbor->visitid == samevisitid)
    +      SETref_(ridge)= NULL; /* ridge free'd below */
    +  }
    +  qh_setcompact(newfacet->ridges);
    +
    +  trace4((qh ferr, 4034, "qh_mergecycle_ridges: add ridges to newfacet\n"));
    +  FORALLsame_cycle_(samecycle) {
    +    FOREACHridge_(same->ridges) {
    +      if (ridge->top == same) {
    +        ridge->top= newfacet;
    +        neighbor= ridge->bottom;
    +      }else if (ridge->bottom == same) {
    +        ridge->bottom= newfacet;
    +        neighbor= ridge->top;
    +      }else if (ridge->top == newfacet || ridge->bottom == newfacet) {
    +        qh_setappend(&newfacet->ridges, ridge);
    +        numold++;  /* already set by qh_mergecycle_neighbors */
    +        continue;
    +      }else {
    +        qh_fprintf(qh ferr, 6098, "qhull internal error (qh_mergecycle_ridges): bad ridge r%d\n", ridge->id);
    +        qh_errexit(qh_ERRqhull, NULL, ridge);
    +      }
    +      if (neighbor == newfacet) {
    +        qh_setfree(&(ridge->vertices));
    +        qh_memfree_(ridge, (int)sizeof(ridgeT), freelistp);
    +        numold++;
    +      }else if (neighbor->visitid == samevisitid) {
    +        qh_setdel(neighbor->ridges, ridge);
    +        qh_setfree(&(ridge->vertices));
    +        qh_memfree_(ridge, (int)sizeof(ridgeT), freelistp);
    +        numold++;
    +      }else {
    +        qh_setappend(&newfacet->ridges, ridge);
    +        numold++;
    +      }
    +    }
    +    if (same->ridges)
    +      qh_settruncate(same->ridges, 0);
    +    if (!same->simplicial)
    +      continue;
    +    FOREACHneighbor_i_(same) {       /* note: !newfact->simplicial */
    +      if (neighbor->visitid != samevisitid && neighbor->simplicial) {
    +        ridge= qh_newridge();
    +        ridge->vertices= qh_setnew_delnthsorted(same->vertices, qh hull_dim,
    +                                                          neighbor_i, 0);
    +        toporient= same->toporient ^ (neighbor_i & 0x1);
    +        if (toporient) {
    +          ridge->top= newfacet;
    +          ridge->bottom= neighbor;
    +        }else {
    +          ridge->top= neighbor;
    +          ridge->bottom= newfacet;
    +        }
    +        qh_setappend(&(newfacet->ridges), ridge);
    +        qh_setappend(&(neighbor->ridges), ridge);
    +        numnew++;
    +      }
    +    }
    +  }
    +
    +  trace2((qh ferr, 2033, "qh_mergecycle_ridges: found %d old ridges and %d new ones\n",
    +             numold, numnew));
    +} /* mergecycle_ridges */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_vneighbors( samecycle, newfacet )
    +    create vertex neighbors for newfacet from vertices of facets in samecycle
    +    samecycle marked with visitid == qh.visit_id - 1
    +
    +  returns:
    +    newfacet vertices with updated neighbors
    +    marks newfacet with qh.visit_id-1
    +    deletes vertices that are merged away
    +    sets delridge on all vertices (faster here than in mergecycle_ridges)
    +
    +  see:
    +    qh_mergevertex_neighbors()
    +
    +  design:
    +    for each vertex of samecycle facet
    +      set vertex->delridge
    +      delete samecycle facets from vertex neighbors
    +      append newfacet to vertex neighbors
    +      if vertex only in newfacet
    +        delete it from newfacet
    +        add it to qh.del_vertices for later deletion
    +*/
    +void qh_mergecycle_vneighbors(facetT *samecycle, facetT *newfacet) {
    +  facetT *neighbor, **neighborp;
    +  unsigned int mergeid;
    +  vertexT *vertex, **vertexp, *apex;
    +  setT *vertices;
    +
    +  trace4((qh ferr, 4035, "qh_mergecycle_vneighbors: update vertex neighbors for newfacet\n"));
    +  mergeid= qh visit_id - 1;
    +  newfacet->visitid= mergeid;
    +  vertices= qh_basevertices(samecycle); /* temp */
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  qh_setappend(&vertices, apex);
    +  FOREACHvertex_(vertices) {
    +    vertex->delridge= True;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->visitid == mergeid)
    +        SETref_(neighbor)= NULL;
    +    }
    +    qh_setcompact(vertex->neighbors);
    +    qh_setappend(&vertex->neighbors, newfacet);
    +    if (!SETsecond_(vertex->neighbors)) {
    +      zinc_(Zcyclevertex);
    +      trace2((qh ferr, 2034, "qh_mergecycle_vneighbors: deleted v%d when merging cycle f%d into f%d\n",
    +        vertex->id, samecycle->id, newfacet->id));
    +      qh_setdelsorted(newfacet->vertices, vertex);
    +      vertex->deleted= True;
    +      qh_setappend(&qh del_vertices, vertex);
    +    }
    +  }
    +  qh_settempfree(&vertices);
    +  trace3((qh ferr, 3005, "qh_mergecycle_vneighbors: merged vertices from cycle f%d into f%d\n",
    +             samecycle->id, newfacet->id));
    +} /* mergecycle_vneighbors */
    +
    +/*---------------------------------
    +
    +  qh_mergefacet( facet1, facet2, mindist, maxdist, mergeapex )
    +    merges facet1 into facet2
    +    mergeapex==qh_MERGEapex if merging new facet into coplanar horizon
    +
    +  returns:
    +    qh.max_outside and qh.min_vertex updated
    +    initializes vertex neighbors on first merge
    +
    +  returns:
    +    facet2 contains facet1's vertices, neighbors, and ridges
    +      facet2 moved to end of qh.facet_list
    +      makes facet2 a newfacet
    +      sets facet2->newmerge set
    +      clears facet2->center (unless merging into a large facet)
    +      clears facet2->tested and ridge->tested for facet1
    +
    +    facet1 prepended to visible_list for later deletion and partitioning
    +      facet1->f.replace == facet2
    +
    +    adds neighboring facets to facet_mergeset if redundant or degenerate
    +
    +  notes:
    +    mindist/maxdist may be NULL (only if both NULL)
    +    traces merge if fmax_(maxdist,-mindist) > TRACEdist
    +
    +  see:
    +    qh_mergecycle()
    +
    +  design:
    +    trace merge and check for degenerate simplex
    +    make ridges for both facets
    +    update qh.max_outside, qh.max_vertex, qh.min_vertex
    +    update facet2->maxoutside and keepcentrum
    +    update facet2->nummerge
    +    update tested flags for facet2
    +    if facet1 is simplicial
    +      merge facet1 into facet2
    +    else
    +      merge facet1's neighbors into facet2
    +      merge facet1's ridges into facet2
    +      merge facet1's vertices into facet2
    +      merge facet1's vertex neighbors into facet2
    +      add facet2's vertices to qh.new_vertexlist
    +      unless qh_MERGEapex
    +        test facet2 for degenerate or redundant neighbors
    +      move facet1 to qh.visible_list for later deletion
    +      move facet2 to end of qh.newfacet_list
    +*/
    +void qh_mergefacet(facetT *facet1, facetT *facet2, realT *mindist, realT *maxdist, boolT mergeapex) {
    +  boolT traceonce= False;
    +  vertexT *vertex, **vertexp;
    +  int tracerestore=0, nummerge;
    +
    +  if (facet1->tricoplanar || facet2->tricoplanar) {
    +    if (!qh TRInormals) {
    +      qh_fprintf(qh ferr, 6226, "Qhull internal error (qh_mergefacet): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit2(qh_ERRqhull, facet1, facet2);
    +    }
    +    if (facet2->tricoplanar) {
    +      facet2->tricoplanar= False;
    +      facet2->keepcentrum= False;
    +    }
    +  }
    +  zzinc_(Ztotmerge);
    +  if (qh REPORTfreq2 && qh POSTmerging) {
    +    if (zzval_(Ztotmerge) > qh mergereport + qh REPORTfreq2)
    +      qh_tracemerging();
    +  }
    +#ifndef qh_NOtrace
    +  if (qh build_cnt >= qh RERUN) {
    +    if (mindist && (-*mindist > qh TRACEdist || *maxdist > qh TRACEdist)) {
    +      tracerestore= 0;
    +      qh IStracing= qh TRACElevel;
    +      traceonce= True;
    +      qh_fprintf(qh ferr, 8075, "qh_mergefacet: ========= trace wide merge #%d(%2.2g) for f%d into f%d, last point was p%d\n", zzval_(Ztotmerge),
    +             fmax_(-*mindist, *maxdist), facet1->id, facet2->id, qh furthest_id);
    +    }else if (facet1 == qh tracefacet || facet2 == qh tracefacet) {
    +      tracerestore= qh IStracing;
    +      qh IStracing= 4;
    +      traceonce= True;
    +      qh_fprintf(qh ferr, 8076, "qh_mergefacet: ========= trace merge #%d involving f%d, furthest is p%d\n",
    +                 zzval_(Ztotmerge), qh tracefacet_id,  qh furthest_id);
    +    }
    +  }
    +  if (qh IStracing >= 2) {
    +    realT mergemin= -2;
    +    realT mergemax= -2;
    +
    +    if (mindist) {
    +      mergemin= *mindist;
    +      mergemax= *maxdist;
    +    }
    +    qh_fprintf(qh ferr, 8077, "qh_mergefacet: #%d merge f%d into f%d, mindist= %2.2g, maxdist= %2.2g\n",
    +    zzval_(Ztotmerge), facet1->id, facet2->id, mergemin, mergemax);
    +  }
    +#endif /* !qh_NOtrace */
    +  if (facet1 == facet2 || facet1->visible || facet2->visible) {
    +    qh_fprintf(qh ferr, 6099, "qhull internal error (qh_mergefacet): either f%d and f%d are the same or one is a visible facet\n",
    +             facet1->id, facet2->id);
    +    qh_errexit2(qh_ERRqhull, facet1, facet2);
    +  }
    +  if (qh num_facets - qh num_visible <= qh hull_dim + 1) {
    +    qh_fprintf(qh ferr, 6227, "\n\
    +qhull precision error: Only %d facets remain.  Can not merge another\n\
    +pair.  The input is too degenerate or the convexity constraints are\n\
    +too strong.\n", qh hull_dim+1);
    +    if (qh hull_dim >= 5 && !qh MERGEexact)
    +      qh_fprintf(qh ferr, 8079, "Option 'Qx' may avoid this problem.\n");
    +    qh_errexit(qh_ERRprec, NULL, NULL);
    +  }
    +  if (!qh VERTEXneighbors)
    +    qh_vertexneighbors();
    +  qh_makeridges(facet1);
    +  qh_makeridges(facet2);
    +  if (qh IStracing >=4)
    +    qh_errprint("MERGING", facet1, facet2, NULL, NULL);
    +  if (mindist) {
    +    maximize_(qh max_outside, *maxdist);
    +    maximize_(qh max_vertex, *maxdist);
    +#if qh_MAXoutside
    +    maximize_(facet2->maxoutside, *maxdist);
    +#endif
    +    minimize_(qh min_vertex, *mindist);
    +    if (!facet2->keepcentrum
    +    && (*maxdist > qh WIDEfacet || *mindist < -qh WIDEfacet)) {
    +      facet2->keepcentrum= True;
    +      zinc_(Zwidefacet);
    +    }
    +  }
    +  nummerge= facet1->nummerge + facet2->nummerge + 1;
    +  if (nummerge >= qh_MAXnummerge)
    +    facet2->nummerge= qh_MAXnummerge;
    +  else
    +    facet2->nummerge= (short unsigned int)nummerge;
    +  facet2->newmerge= True;
    +  facet2->dupridge= False;
    +  qh_updatetested(facet1, facet2);
    +  if (qh hull_dim > 2 && qh_setsize(facet1->vertices) == qh hull_dim)
    +    qh_mergesimplex(facet1, facet2, mergeapex);
    +  else {
    +    qh vertex_visit++;
    +    FOREACHvertex_(facet2->vertices)
    +      vertex->visitid= qh vertex_visit;
    +    if (qh hull_dim == 2)
    +      qh_mergefacet2d(facet1, facet2);
    +    else {
    +      qh_mergeneighbors(facet1, facet2);
    +      qh_mergevertices(facet1->vertices, &facet2->vertices);
    +    }
    +    qh_mergeridges(facet1, facet2);
    +    qh_mergevertex_neighbors(facet1, facet2);
    +    if (!facet2->newfacet)
    +      qh_newvertices(facet2->vertices);
    +  }
    +  if (!mergeapex)
    +    qh_degen_redundant_neighbors(facet2, facet1);
    +  if (facet2->coplanar || !facet2->newfacet) {
    +    zinc_(Zmergeintohorizon);
    +  }else if (!facet1->newfacet && facet2->newfacet) {
    +    zinc_(Zmergehorizon);
    +  }else {
    +    zinc_(Zmergenew);
    +  }
    +  qh_willdelete(facet1, facet2);
    +  qh_removefacet(facet2);  /* append as a newfacet to end of qh facet_list */
    +  qh_appendfacet(facet2);
    +  facet2->newfacet= True;
    +  facet2->tested= False;
    +  qh_tracemerge(facet1, facet2);
    +  if (traceonce) {
    +    qh_fprintf(qh ferr, 8080, "qh_mergefacet: end of wide tracing\n");
    +    qh IStracing= tracerestore;
    +  }
    +} /* mergefacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergefacet2d( facet1, facet2 )
    +    in 2d, merges neighbors and vertices of facet1 into facet2
    +
    +  returns:
    +    build ridges for neighbors if necessary
    +    facet2 looks like a simplicial facet except for centrum, ridges
    +      neighbors are opposite the corresponding vertex
    +      maintains orientation of facet2
    +
    +  notes:
    +    qh_mergefacet() retains non-simplicial structures
    +      they are not needed in 2d, but later routines may use them
    +    preserves qh.vertex_visit for qh_mergevertex_neighbors()
    +
    +  design:
    +    get vertices and neighbors
    +    determine new vertices and neighbors
    +    set new vertices and neighbors and adjust orientation
    +    make ridges for new neighbor if needed
    +*/
    +void qh_mergefacet2d(facetT *facet1, facetT *facet2) {
    +  vertexT *vertex1A, *vertex1B, *vertex2A, *vertex2B, *vertexA, *vertexB;
    +  facetT *neighbor1A, *neighbor1B, *neighbor2A, *neighbor2B, *neighborA, *neighborB;
    +
    +  vertex1A= SETfirstt_(facet1->vertices, vertexT);
    +  vertex1B= SETsecondt_(facet1->vertices, vertexT);
    +  vertex2A= SETfirstt_(facet2->vertices, vertexT);
    +  vertex2B= SETsecondt_(facet2->vertices, vertexT);
    +  neighbor1A= SETfirstt_(facet1->neighbors, facetT);
    +  neighbor1B= SETsecondt_(facet1->neighbors, facetT);
    +  neighbor2A= SETfirstt_(facet2->neighbors, facetT);
    +  neighbor2B= SETsecondt_(facet2->neighbors, facetT);
    +  if (vertex1A == vertex2A) {
    +    vertexA= vertex1B;
    +    vertexB= vertex2B;
    +    neighborA= neighbor2A;
    +    neighborB= neighbor1A;
    +  }else if (vertex1A == vertex2B) {
    +    vertexA= vertex1B;
    +    vertexB= vertex2A;
    +    neighborA= neighbor2B;
    +    neighborB= neighbor1A;
    +  }else if (vertex1B == vertex2A) {
    +    vertexA= vertex1A;
    +    vertexB= vertex2B;
    +    neighborA= neighbor2A;
    +    neighborB= neighbor1B;
    +  }else { /* 1B == 2B */
    +    vertexA= vertex1A;
    +    vertexB= vertex2A;
    +    neighborA= neighbor2B;
    +    neighborB= neighbor1B;
    +  }
    +  /* vertexB always from facet2, neighborB always from facet1 */
    +  if (vertexA->id > vertexB->id) {
    +    SETfirst_(facet2->vertices)= vertexA;
    +    SETsecond_(facet2->vertices)= vertexB;
    +    if (vertexB == vertex2A)
    +      facet2->toporient= !facet2->toporient;
    +    SETfirst_(facet2->neighbors)= neighborA;
    +    SETsecond_(facet2->neighbors)= neighborB;
    +  }else {
    +    SETfirst_(facet2->vertices)= vertexB;
    +    SETsecond_(facet2->vertices)= vertexA;
    +    if (vertexB == vertex2B)
    +      facet2->toporient= !facet2->toporient;
    +    SETfirst_(facet2->neighbors)= neighborB;
    +    SETsecond_(facet2->neighbors)= neighborA;
    +  }
    +  qh_makeridges(neighborB);
    +  qh_setreplace(neighborB->neighbors, facet1, facet2);
    +  trace4((qh ferr, 4036, "qh_mergefacet2d: merged v%d and neighbor f%d of f%d into f%d\n",
    +       vertexA->id, neighborB->id, facet1->id, facet2->id));
    +} /* mergefacet2d */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergeneighbors( facet1, facet2 )
    +    merges the neighbors of facet1 into facet2
    +
    +  see:
    +    qh_mergecycle_neighbors()
    +
    +  design:
    +    for each neighbor of facet1
    +      if neighbor is also a neighbor of facet2
    +        if neighbor is simpilicial
    +          make ridges for later deletion as a degenerate facet
    +        update its neighbor set
    +      else
    +        move the neighbor relation to facet2
    +    remove the neighbor relation for facet1 and facet2
    +*/
    +void qh_mergeneighbors(facetT *facet1, facetT *facet2) {
    +  facetT *neighbor, **neighborp;
    +
    +  trace4((qh ferr, 4037, "qh_mergeneighbors: merge neighbors of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  qh visit_id++;
    +  FOREACHneighbor_(facet2) {
    +    neighbor->visitid= qh visit_id;
    +  }
    +  FOREACHneighbor_(facet1) {
    +    if (neighbor->visitid == qh visit_id) {
    +      if (neighbor->simplicial)    /* is degen, needs ridges */
    +        qh_makeridges(neighbor);
    +      if (SETfirstt_(neighbor->neighbors, facetT) != facet1) /*keep newfacet->horizon*/
    +        qh_setdel(neighbor->neighbors, facet1);
    +      else {
    +        qh_setdel(neighbor->neighbors, facet2);
    +        qh_setreplace(neighbor->neighbors, facet1, facet2);
    +      }
    +    }else if (neighbor != facet2) {
    +      qh_setappend(&(facet2->neighbors), neighbor);
    +      qh_setreplace(neighbor->neighbors, facet1, facet2);
    +    }
    +  }
    +  qh_setdel(facet1->neighbors, facet2);  /* here for makeridges */
    +  qh_setdel(facet2->neighbors, facet1);
    +} /* mergeneighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergeridges( facet1, facet2 )
    +    merges the ridge set of facet1 into facet2
    +
    +  returns:
    +    may delete all ridges for a vertex
    +    sets vertex->delridge on deleted ridges
    +
    +  see:
    +    qh_mergecycle_ridges()
    +
    +  design:
    +    delete ridges between facet1 and facet2
    +      mark (delridge) vertices on these ridges for later testing
    +    for each remaining ridge
    +      rename facet1 to facet2
    +*/
    +void qh_mergeridges(facetT *facet1, facetT *facet2) {
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +
    +  trace4((qh ferr, 4038, "qh_mergeridges: merge ridges of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  FOREACHridge_(facet2->ridges) {
    +    if ((ridge->top == facet1) || (ridge->bottom == facet1)) {
    +      FOREACHvertex_(ridge->vertices)
    +        vertex->delridge= True;
    +      qh_delridge(ridge);  /* expensive in high-d, could rebuild */
    +      ridgep--; /*repeat*/
    +    }
    +  }
    +  FOREACHridge_(facet1->ridges) {
    +    if (ridge->top == facet1)
    +      ridge->top= facet2;
    +    else
    +      ridge->bottom= facet2;
    +    qh_setappend(&(facet2->ridges), ridge);
    +  }
    +} /* mergeridges */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergesimplex( facet1, facet2, mergeapex )
    +    merge simplicial facet1 into facet2
    +    mergeapex==qh_MERGEapex if merging samecycle into horizon facet
    +      vertex id is latest (most recently created)
    +    facet1 may be contained in facet2
    +    ridges exist for both facets
    +
    +  returns:
    +    facet2 with updated vertices, ridges, neighbors
    +    updated neighbors for facet1's vertices
    +    facet1 not deleted
    +    sets vertex->delridge on deleted ridges
    +
    +  notes:
    +    special case code since this is the most common merge
    +    called from qh_mergefacet()
    +
    +  design:
    +    if qh_MERGEapex
    +      add vertices of facet2 to qh.new_vertexlist if necessary
    +      add apex to facet2
    +    else
    +      for each ridge between facet1 and facet2
    +        set vertex->delridge
    +      determine the apex for facet1 (i.e., vertex to be merged)
    +      unless apex already in facet2
    +        insert apex into vertices for facet2
    +      add vertices of facet2 to qh.new_vertexlist if necessary
    +      add apex to qh.new_vertexlist if necessary
    +      for each vertex of facet1
    +        if apex
    +          rename facet1 to facet2 in its vertex neighbors
    +        else
    +          delete facet1 from vertex neighors
    +          if only in facet2
    +            add vertex to qh.del_vertices for later deletion
    +      for each ridge of facet1
    +        delete ridges between facet1 and facet2
    +        append other ridges to facet2 after renaming facet to facet2
    +*/
    +void qh_mergesimplex(facetT *facet1, facetT *facet2, boolT mergeapex) {
    +  vertexT *vertex, **vertexp, *apex;
    +  ridgeT *ridge, **ridgep;
    +  boolT issubset= False;
    +  int vertex_i= -1, vertex_n;
    +  facetT *neighbor, **neighborp, *otherfacet;
    +
    +  if (mergeapex) {
    +    if (!facet2->newfacet)
    +      qh_newvertices(facet2->vertices);  /* apex is new */
    +    apex= SETfirstt_(facet1->vertices, vertexT);
    +    if (SETfirstt_(facet2->vertices, vertexT) != apex)
    +      qh_setaddnth(&facet2->vertices, 0, apex);  /* apex has last id */
    +    else
    +      issubset= True;
    +  }else {
    +    zinc_(Zmergesimplex);
    +    FOREACHvertex_(facet1->vertices)
    +      vertex->seen= False;
    +    FOREACHridge_(facet1->ridges) {
    +      if (otherfacet_(ridge, facet1) == facet2) {
    +        FOREACHvertex_(ridge->vertices) {
    +          vertex->seen= True;
    +          vertex->delridge= True;
    +        }
    +        break;
    +      }
    +    }
    +    FOREACHvertex_(facet1->vertices) {
    +      if (!vertex->seen)
    +        break;  /* must occur */
    +    }
    +    apex= vertex;
    +    trace4((qh ferr, 4039, "qh_mergesimplex: merge apex v%d of f%d into facet f%d\n",
    +          apex->id, facet1->id, facet2->id));
    +    FOREACHvertex_i_(facet2->vertices) {
    +      if (vertex->id < apex->id) {
    +        break;
    +      }else if (vertex->id == apex->id) {
    +        issubset= True;
    +        break;
    +      }
    +    }
    +    if (!issubset)
    +      qh_setaddnth(&facet2->vertices, vertex_i, apex);
    +    if (!facet2->newfacet)
    +      qh_newvertices(facet2->vertices);
    +    else if (!apex->newlist) {
    +      qh_removevertex(apex);
    +      qh_appendvertex(apex);
    +    }
    +  }
    +  trace4((qh ferr, 4040, "qh_mergesimplex: update vertex neighbors of f%d\n",
    +          facet1->id));
    +  FOREACHvertex_(facet1->vertices) {
    +    if (vertex == apex && !issubset)
    +      qh_setreplace(vertex->neighbors, facet1, facet2);
    +    else {
    +      qh_setdel(vertex->neighbors, facet1);
    +      if (!SETsecond_(vertex->neighbors))
    +        qh_mergevertex_del(vertex, facet1, facet2);
    +    }
    +  }
    +  trace4((qh ferr, 4041, "qh_mergesimplex: merge ridges and neighbors of f%d into f%d\n",
    +          facet1->id, facet2->id));
    +  qh visit_id++;
    +  FOREACHneighbor_(facet2)
    +    neighbor->visitid= qh visit_id;
    +  FOREACHridge_(facet1->ridges) {
    +    otherfacet= otherfacet_(ridge, facet1);
    +    if (otherfacet == facet2) {
    +      qh_setdel(facet2->ridges, ridge);
    +      qh_setfree(&(ridge->vertices));
    +      qh_memfree(ridge, (int)sizeof(ridgeT));
    +      qh_setdel(facet2->neighbors, facet1);
    +    }else {
    +      qh_setappend(&facet2->ridges, ridge);
    +      if (otherfacet->visitid != qh visit_id) {
    +        qh_setappend(&facet2->neighbors, otherfacet);
    +        qh_setreplace(otherfacet->neighbors, facet1, facet2);
    +        otherfacet->visitid= qh visit_id;
    +      }else {
    +        if (otherfacet->simplicial)    /* is degen, needs ridges */
    +          qh_makeridges(otherfacet);
    +        if (SETfirstt_(otherfacet->neighbors, facetT) != facet1)
    +          qh_setdel(otherfacet->neighbors, facet1);
    +        else {   /*keep newfacet->neighbors->horizon*/
    +          qh_setdel(otherfacet->neighbors, facet2);
    +          qh_setreplace(otherfacet->neighbors, facet1, facet2);
    +        }
    +      }
    +      if (ridge->top == facet1) /* wait until after qh_makeridges */
    +        ridge->top= facet2;
    +      else
    +        ridge->bottom= facet2;
    +    }
    +  }
    +  SETfirst_(facet1->ridges)= NULL; /* it will be deleted */
    +  trace3((qh ferr, 3006, "qh_mergesimplex: merged simplex f%d apex v%d into facet f%d\n",
    +          facet1->id, getid_(apex), facet2->id));
    +} /* mergesimplex */
    +
    +/*---------------------------------
    +
    +  qh_mergevertex_del( vertex, facet1, facet2 )
    +    delete a vertex because of merging facet1 into facet2
    +
    +  returns:
    +    deletes vertex from facet2
    +    adds vertex to qh.del_vertices for later deletion
    +*/
    +void qh_mergevertex_del(vertexT *vertex, facetT *facet1, facetT *facet2) {
    +
    +  zinc_(Zmergevertex);
    +  trace2((qh ferr, 2035, "qh_mergevertex_del: deleted v%d when merging f%d into f%d\n",
    +          vertex->id, facet1->id, facet2->id));
    +  qh_setdelsorted(facet2->vertices, vertex);
    +  vertex->deleted= True;
    +  qh_setappend(&qh del_vertices, vertex);
    +} /* mergevertex_del */
    +
    +/*---------------------------------
    +
    +  qh_mergevertex_neighbors( facet1, facet2 )
    +    merge the vertex neighbors of facet1 to facet2
    +
    +  returns:
    +    if vertex is current qh.vertex_visit
    +      deletes facet1 from vertex->neighbors
    +    else
    +      renames facet1 to facet2 in vertex->neighbors
    +    deletes vertices if only one neighbor
    +
    +  notes:
    +    assumes vertex neighbor sets are good
    +*/
    +void qh_mergevertex_neighbors(facetT *facet1, facetT *facet2) {
    +  vertexT *vertex, **vertexp;
    +
    +  trace4((qh ferr, 4042, "qh_mergevertex_neighbors: merge vertex neighbors of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  if (qh tracevertex) {
    +    qh_fprintf(qh ferr, 8081, "qh_mergevertex_neighbors: of f%d and f%d at furthest p%d f0= %p\n",
    +             facet1->id, facet2->id, qh furthest_id, qh tracevertex->neighbors->e[0].p);
    +    qh_errprint("TRACE", NULL, NULL, NULL, qh tracevertex);
    +  }
    +  FOREACHvertex_(facet1->vertices) {
    +    if (vertex->visitid != qh vertex_visit)
    +      qh_setreplace(vertex->neighbors, facet1, facet2);
    +    else {
    +      qh_setdel(vertex->neighbors, facet1);
    +      if (!SETsecond_(vertex->neighbors))
    +        qh_mergevertex_del(vertex, facet1, facet2);
    +    }
    +  }
    +  if (qh tracevertex)
    +    qh_errprint("TRACE", NULL, NULL, NULL, qh tracevertex);
    +} /* mergevertex_neighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergevertices( vertices1, vertices2 )
    +    merges the vertex set of facet1 into facet2
    +
    +  returns:
    +    replaces vertices2 with merged set
    +    preserves vertex_visit for qh_mergevertex_neighbors
    +    updates qh.newvertex_list
    +
    +  design:
    +    create a merged set of both vertices (in inverse id order)
    +*/
    +void qh_mergevertices(setT *vertices1, setT **vertices2) {
    +  int newsize= qh_setsize(vertices1)+qh_setsize(*vertices2) - qh hull_dim + 1;
    +  setT *mergedvertices;
    +  vertexT *vertex, **vertexp, **vertex2= SETaddr_(*vertices2, vertexT);
    +
    +  mergedvertices= qh_settemp(newsize);
    +  FOREACHvertex_(vertices1) {
    +    if (!*vertex2 || vertex->id > (*vertex2)->id)
    +      qh_setappend(&mergedvertices, vertex);
    +    else {
    +      while (*vertex2 && (*vertex2)->id > vertex->id)
    +        qh_setappend(&mergedvertices, *vertex2++);
    +      if (!*vertex2 || (*vertex2)->id < vertex->id)
    +        qh_setappend(&mergedvertices, vertex);
    +      else
    +        qh_setappend(&mergedvertices, *vertex2++);
    +    }
    +  }
    +  while (*vertex2)
    +    qh_setappend(&mergedvertices, *vertex2++);
    +  if (newsize < qh_setsize(mergedvertices)) {
    +    qh_fprintf(qh ferr, 6100, "qhull internal error (qh_mergevertices): facets did not share a ridge\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh_setfree(vertices2);
    +  *vertices2= mergedvertices;
    +  qh_settemppop();
    +} /* mergevertices */
    +
    +
    +/*---------------------------------
    +
    +  qh_neighbor_intersections( vertex )
    +    return intersection of all vertices in vertex->neighbors except for vertex
    +
    +  returns:
    +    returns temporary set of vertices
    +    does not include vertex
    +    NULL if a neighbor is simplicial
    +    NULL if empty set
    +
    +  notes:
    +    used for renaming vertices
    +
    +  design:
    +    initialize the intersection set with vertices of the first two neighbors
    +    delete vertex from the intersection
    +    for each remaining neighbor
    +      intersect its vertex set with the intersection set
    +      return NULL if empty
    +    return the intersection set
    +*/
    +setT *qh_neighbor_intersections(vertexT *vertex) {
    +  facetT *neighbor, **neighborp, *neighborA, *neighborB;
    +  setT *intersect;
    +  int neighbor_i, neighbor_n;
    +
    +  FOREACHneighbor_(vertex) {
    +    if (neighbor->simplicial)
    +      return NULL;
    +  }
    +  neighborA= SETfirstt_(vertex->neighbors, facetT);
    +  neighborB= SETsecondt_(vertex->neighbors, facetT);
    +  zinc_(Zintersectnum);
    +  if (!neighborA)
    +    return NULL;
    +  if (!neighborB)
    +    intersect= qh_setcopy(neighborA->vertices, 0);
    +  else
    +    intersect= qh_vertexintersect_new(neighborA->vertices, neighborB->vertices);
    +  qh_settemppush(intersect);
    +  qh_setdelsorted(intersect, vertex);
    +  FOREACHneighbor_i_(vertex) {
    +    if (neighbor_i >= 2) {
    +      zinc_(Zintersectnum);
    +      qh_vertexintersect(&intersect, neighbor->vertices);
    +      if (!SETfirst_(intersect)) {
    +        zinc_(Zintersectfail);
    +        qh_settempfree(&intersect);
    +        return NULL;
    +      }
    +    }
    +  }
    +  trace3((qh ferr, 3007, "qh_neighbor_intersections: %d vertices in neighbor intersection of v%d\n",
    +          qh_setsize(intersect), vertex->id));
    +  return intersect;
    +} /* neighbor_intersections */
    +
    +/*---------------------------------
    +
    +  qh_newvertices( vertices )
    +    add vertices to end of qh.vertex_list (marks as new vertices)
    +
    +  returns:
    +    vertices on qh.newvertex_list
    +    vertex->newlist set
    +*/
    +void qh_newvertices(setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->newlist) {
    +      qh_removevertex(vertex);
    +      qh_appendvertex(vertex);
    +    }
    +  }
    +} /* newvertices */
    +
    +/*---------------------------------
    +
    +  qh_reducevertices()
    +    reduce extra vertices, shared vertices, and redundant vertices
    +    facet->newmerge is set if merged since last call
    +    if !qh.MERGEvertices, only removes extra vertices
    +
    +  returns:
    +    True if also merged degen_redundant facets
    +    vertices are renamed if possible
    +    clears facet->newmerge and vertex->delridge
    +
    +  notes:
    +    ignored if 2-d
    +
    +  design:
    +    merge any degenerate or redundant facets
    +    for each newly merged facet
    +      remove extra vertices
    +    if qh.MERGEvertices
    +      for each newly merged facet
    +        for each vertex
    +          if vertex was on a deleted ridge
    +            rename vertex if it is shared
    +      remove delridge flag from new vertices
    +*/
    +boolT qh_reducevertices(void) {
    +  int numshare=0, numrename= 0;
    +  boolT degenredun= False;
    +  facetT *newfacet;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh hull_dim == 2)
    +    return False;
    +  if (qh_merge_degenredundant())
    +    degenredun= True;
    + LABELrestart:
    +  FORALLnew_facets {
    +    if (newfacet->newmerge) {
    +      if (!qh MERGEvertices)
    +        newfacet->newmerge= False;
    +      qh_remove_extravertices(newfacet);
    +    }
    +  }
    +  if (!qh MERGEvertices)
    +    return False;
    +  FORALLnew_facets {
    +    if (newfacet->newmerge) {
    +      newfacet->newmerge= False;
    +      FOREACHvertex_(newfacet->vertices) {
    +        if (vertex->delridge) {
    +          if (qh_rename_sharedvertex(vertex, newfacet)) {
    +            numshare++;
    +            vertexp--; /* repeat since deleted vertex */
    +          }
    +        }
    +      }
    +    }
    +  }
    +  FORALLvertex_(qh newvertex_list) {
    +    if (vertex->delridge && !vertex->deleted) {
    +      vertex->delridge= False;
    +      if (qh hull_dim >= 4 && qh_redundant_vertex(vertex)) {
    +        numrename++;
    +        if (qh_merge_degenredundant()) {
    +          degenredun= True;
    +          goto LABELrestart;
    +        }
    +      }
    +    }
    +  }
    +  trace1((qh ferr, 1014, "qh_reducevertices: renamed %d shared vertices and %d redundant vertices. Degen? %d\n",
    +          numshare, numrename, degenredun));
    +  return degenredun;
    +} /* reducevertices */
    +
    +/*---------------------------------
    +
    +  qh_redundant_vertex( vertex )
    +    detect and rename a redundant vertex
    +    vertices have full vertex->neighbors
    +
    +  returns:
    +    returns true if find a redundant vertex
    +      deletes vertex(vertex->deleted)
    +
    +  notes:
    +    only needed if vertex->delridge and hull_dim >= 4
    +    may add degenerate facets to qh.facet_mergeset
    +    doesn't change vertex->neighbors or create redundant facets
    +
    +  design:
    +    intersect vertices of all facet neighbors of vertex
    +    determine ridges for these vertices
    +    if find a new vertex for vertex amoung these ridges and vertices
    +      rename vertex to the new vertex
    +*/
    +vertexT *qh_redundant_vertex(vertexT *vertex) {
    +  vertexT *newvertex= NULL;
    +  setT *vertices, *ridges;
    +
    +  trace3((qh ferr, 3008, "qh_redundant_vertex: check if v%d can be renamed\n", vertex->id));
    +  if ((vertices= qh_neighbor_intersections(vertex))) {
    +    ridges= qh_vertexridges(vertex);
    +    if ((newvertex= qh_find_newvertex(vertex, vertices, ridges)))
    +      qh_renamevertex(vertex, newvertex, ridges, NULL, NULL);
    +    qh_settempfree(&ridges);
    +    qh_settempfree(&vertices);
    +  }
    +  return newvertex;
    +} /* redundant_vertex */
    +
    +/*---------------------------------
    +
    +  qh_remove_extravertices( facet )
    +    remove extra vertices from non-simplicial facets
    +
    +  returns:
    +    returns True if it finds them
    +
    +  design:
    +    for each vertex in facet
    +      if vertex not in a ridge (i.e., no longer used)
    +        delete vertex from facet
    +        delete facet from vertice's neighbors
    +        unless vertex in another facet
    +          add vertex to qh.del_vertices for later deletion
    +*/
    +boolT qh_remove_extravertices(facetT *facet) {
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +  boolT foundrem= False;
    +
    +  trace4((qh ferr, 4043, "qh_remove_extravertices: test f%d for extra vertices\n",
    +          facet->id));
    +  FOREACHvertex_(facet->vertices)
    +    vertex->seen= False;
    +  FOREACHridge_(facet->ridges) {
    +    FOREACHvertex_(ridge->vertices)
    +      vertex->seen= True;
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    if (!vertex->seen) {
    +      foundrem= True;
    +      zinc_(Zremvertex);
    +      qh_setdelsorted(facet->vertices, vertex);
    +      qh_setdel(vertex->neighbors, facet);
    +      if (!qh_setsize(vertex->neighbors)) {
    +        vertex->deleted= True;
    +        qh_setappend(&qh del_vertices, vertex);
    +        zinc_(Zremvertexdel);
    +        trace2((qh ferr, 2036, "qh_remove_extravertices: v%d deleted because it's lost all ridges\n", vertex->id));
    +      }else
    +        trace3((qh ferr, 3009, "qh_remove_extravertices: v%d removed from f%d because it's lost all ridges\n", vertex->id, facet->id));
    +      vertexp--; /*repeat*/
    +    }
    +  }
    +  return foundrem;
    +} /* remove_extravertices */
    +
    +/*---------------------------------
    +
    +  qh_rename_sharedvertex( vertex, facet )
    +    detect and rename if shared vertex in facet
    +    vertices have full ->neighbors
    +
    +  returns:
    +    newvertex or NULL
    +    the vertex may still exist in other facets (i.e., a neighbor was pinched)
    +    does not change facet->neighbors
    +    updates vertex->neighbors
    +
    +  notes:
    +    a shared vertex for a facet is only in ridges to one neighbor
    +    this may undo a pinched facet
    +
    +    it does not catch pinches involving multiple facets.  These appear
    +      to be difficult to detect, since an exhaustive search is too expensive.
    +
    +  design:
    +    if vertex only has two neighbors
    +      determine the ridges that contain the vertex
    +      determine the vertices shared by both neighbors
    +      if can find a new vertex in this set
    +        rename the vertex to the new vertex
    +*/
    +vertexT *qh_rename_sharedvertex(vertexT *vertex, facetT *facet) {
    +  facetT *neighbor, **neighborp, *neighborA= NULL;
    +  setT *vertices, *ridges;
    +  vertexT *newvertex;
    +
    +  if (qh_setsize(vertex->neighbors) == 2) {
    +    neighborA= SETfirstt_(vertex->neighbors, facetT);
    +    if (neighborA == facet)
    +      neighborA= SETsecondt_(vertex->neighbors, facetT);
    +  }else if (qh hull_dim == 3)
    +    return NULL;
    +  else {
    +    qh visit_id++;
    +    FOREACHneighbor_(facet)
    +      neighbor->visitid= qh visit_id;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->visitid == qh visit_id) {
    +        if (neighborA)
    +          return NULL;
    +        neighborA= neighbor;
    +      }
    +    }
    +    if (!neighborA) {
    +      qh_fprintf(qh ferr, 6101, "qhull internal error (qh_rename_sharedvertex): v%d's neighbors not in f%d\n",
    +        vertex->id, facet->id);
    +      qh_errprint("ERRONEOUS", facet, NULL, NULL, vertex);
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +  }
    +  /* the vertex is shared by facet and neighborA */
    +  ridges= qh_settemp(qh TEMPsize);
    +  neighborA->visitid= ++qh visit_id;
    +  qh_vertexridges_facet(vertex, facet, &ridges);
    +  trace2((qh ferr, 2037, "qh_rename_sharedvertex: p%d(v%d) is shared by f%d(%d ridges) and f%d\n",
    +    qh_pointid(vertex->point), vertex->id, facet->id, qh_setsize(ridges), neighborA->id));
    +  zinc_(Zintersectnum);
    +  vertices= qh_vertexintersect_new(facet->vertices, neighborA->vertices);
    +  qh_setdel(vertices, vertex);
    +  qh_settemppush(vertices);
    +  if ((newvertex= qh_find_newvertex(vertex, vertices, ridges)))
    +    qh_renamevertex(vertex, newvertex, ridges, facet, neighborA);
    +  qh_settempfree(&vertices);
    +  qh_settempfree(&ridges);
    +  return newvertex;
    +} /* rename_sharedvertex */
    +
    +/*---------------------------------
    +
    +  qh_renameridgevertex( ridge, oldvertex, newvertex )
    +    renames oldvertex as newvertex in ridge
    +
    +  returns:
    +
    +  design:
    +    delete oldvertex from ridge
    +    if newvertex already in ridge
    +      copy ridge->noconvex to another ridge if possible
    +      delete the ridge
    +    else
    +      insert newvertex into the ridge
    +      adjust the ridge's orientation
    +*/
    +void qh_renameridgevertex(ridgeT *ridge, vertexT *oldvertex, vertexT *newvertex) {
    +  int nth= 0, oldnth;
    +  facetT *temp;
    +  vertexT *vertex, **vertexp;
    +
    +  oldnth= qh_setindex(ridge->vertices, oldvertex);
    +  qh_setdelnthsorted(ridge->vertices, oldnth);
    +  FOREACHvertex_(ridge->vertices) {
    +    if (vertex == newvertex) {
    +      zinc_(Zdelridge);
    +      if (ridge->nonconvex) /* only one ridge has nonconvex set */
    +        qh_copynonconvex(ridge);
    +      trace2((qh ferr, 2038, "qh_renameridgevertex: ridge r%d deleted.  It contained both v%d and v%d\n",
    +        ridge->id, oldvertex->id, newvertex->id));
    +      qh_delridge(ridge);
    +      return;
    +    }
    +    if (vertex->id < newvertex->id)
    +      break;
    +    nth++;
    +  }
    +  qh_setaddnth(&ridge->vertices, nth, newvertex);
    +  if (abs(oldnth - nth)%2) {
    +    trace3((qh ferr, 3010, "qh_renameridgevertex: swapped the top and bottom of ridge r%d\n",
    +            ridge->id));
    +    temp= ridge->top;
    +    ridge->top= ridge->bottom;
    +    ridge->bottom= temp;
    +  }
    +} /* renameridgevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_renamevertex( oldvertex, newvertex, ridges, oldfacet, neighborA )
    +    renames oldvertex as newvertex in ridges
    +    gives oldfacet/neighborA if oldvertex is shared between two facets
    +
    +  returns:
    +    oldvertex may still exist afterwards
    +
    +
    +  notes:
    +    can not change neighbors of newvertex (since it's a subset)
    +
    +  design:
    +    for each ridge in ridges
    +      rename oldvertex to newvertex and delete degenerate ridges
    +    if oldfacet not defined
    +      for each neighbor of oldvertex
    +        delete oldvertex from neighbor's vertices
    +        remove extra vertices from neighbor
    +      add oldvertex to qh.del_vertices
    +    else if oldvertex only between oldfacet and neighborA
    +      delete oldvertex from oldfacet and neighborA
    +      add oldvertex to qh.del_vertices
    +    else oldvertex is in oldfacet and neighborA and other facets (i.e., pinched)
    +      delete oldvertex from oldfacet
    +      delete oldfacet from oldvertice's neighbors
    +      remove extra vertices (e.g., oldvertex) from neighborA
    +*/
    +void qh_renamevertex(vertexT *oldvertex, vertexT *newvertex, setT *ridges, facetT *oldfacet, facetT *neighborA) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  boolT istrace= False;
    +
    +  if (qh IStracing >= 2 || oldvertex->id == qh tracevertex_id ||
    +        newvertex->id == qh tracevertex_id)
    +    istrace= True;
    +  FOREACHridge_(ridges)
    +    qh_renameridgevertex(ridge, oldvertex, newvertex);
    +  if (!oldfacet) {
    +    zinc_(Zrenameall);
    +    if (istrace)
    +      qh_fprintf(qh ferr, 8082, "qh_renamevertex: renamed v%d to v%d in several facets\n",
    +               oldvertex->id, newvertex->id);
    +    FOREACHneighbor_(oldvertex) {
    +      qh_maydropneighbor(neighbor);
    +      qh_setdelsorted(neighbor->vertices, oldvertex);
    +      if (qh_remove_extravertices(neighbor))
    +        neighborp--; /* neighbor may be deleted */
    +    }
    +    if (!oldvertex->deleted) {
    +      oldvertex->deleted= True;
    +      qh_setappend(&qh del_vertices, oldvertex);
    +    }
    +  }else if (qh_setsize(oldvertex->neighbors) == 2) {
    +    zinc_(Zrenameshare);
    +    if (istrace)
    +      qh_fprintf(qh ferr, 8083, "qh_renamevertex: renamed v%d to v%d in oldfacet f%d\n",
    +               oldvertex->id, newvertex->id, oldfacet->id);
    +    FOREACHneighbor_(oldvertex)
    +      qh_setdelsorted(neighbor->vertices, oldvertex);
    +    oldvertex->deleted= True;
    +    qh_setappend(&qh del_vertices, oldvertex);
    +  }else {
    +    zinc_(Zrenamepinch);
    +    if (istrace || qh IStracing)
    +      qh_fprintf(qh ferr, 8084, "qh_renamevertex: renamed pinched v%d to v%d between f%d and f%d\n",
    +               oldvertex->id, newvertex->id, oldfacet->id, neighborA->id);
    +    qh_setdelsorted(oldfacet->vertices, oldvertex);
    +    qh_setdel(oldvertex->neighbors, oldfacet);
    +    qh_remove_extravertices(neighborA);
    +  }
    +} /* renamevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_test_appendmerge( facet, neighbor )
    +    tests facet/neighbor for convexity
    +    appends to mergeset if non-convex
    +    if pre-merging,
    +      nop if qh.SKIPconvex, or qh.MERGEexact and coplanar
    +
    +  returns:
    +    true if appends facet/neighbor to mergeset
    +    sets facet->center as needed
    +    does not change facet->seen
    +
    +  design:
    +    if qh.cos_max is defined
    +      if the angle between facet normals is too shallow
    +        append an angle-coplanar merge to qh.mergeset
    +        return True
    +    make facet's centrum if needed
    +    if facet's centrum is above the neighbor
    +      set isconcave
    +    else
    +      if facet's centrum is not below the neighbor
    +        set iscoplanar
    +      make neighbor's centrum if needed
    +      if neighbor's centrum is above the facet
    +        set isconcave
    +      else if neighbor's centrum is not below the facet
    +        set iscoplanar
    +   if isconcave or iscoplanar
    +     get angle if needed
    +     append concave or coplanar merge to qh.mergeset
    +*/
    +boolT qh_test_appendmerge(facetT *facet, facetT *neighbor) {
    +  realT dist, dist2= -REALmax, angle= -REALmax;
    +  boolT isconcave= False, iscoplanar= False, okangle= False;
    +
    +  if (qh SKIPconvex && !qh POSTmerging)
    +    return False;
    +  if ((!qh MERGEexact || qh POSTmerging) && qh cos_max < REALmax/2) {
    +    angle= qh_getangle(facet->normal, neighbor->normal);
    +    zinc_(Zangletests);
    +    if (angle > qh cos_max) {
    +      zinc_(Zcoplanarangle);
    +      qh_appendmergeset(facet, neighbor, MRGanglecoplanar, &angle);
    +      trace2((qh ferr, 2039, "qh_test_appendmerge: coplanar angle %4.4g between f%d and f%d\n",
    +         angle, facet->id, neighbor->id));
    +      return True;
    +    }else
    +      okangle= True;
    +  }
    +  if (!facet->center)
    +    facet->center= qh_getcentrum(facet);
    +  zzinc_(Zcentrumtests);
    +  qh_distplane(facet->center, neighbor, &dist);
    +  if (dist > qh centrum_radius)
    +    isconcave= True;
    +  else {
    +    if (dist > -qh centrum_radius)
    +      iscoplanar= True;
    +    if (!neighbor->center)
    +      neighbor->center= qh_getcentrum(neighbor);
    +    zzinc_(Zcentrumtests);
    +    qh_distplane(neighbor->center, facet, &dist2);
    +    if (dist2 > qh centrum_radius)
    +      isconcave= True;
    +    else if (!iscoplanar && dist2 > -qh centrum_radius)
    +      iscoplanar= True;
    +  }
    +  if (!isconcave && (!iscoplanar || (qh MERGEexact && !qh POSTmerging)))
    +    return False;
    +  if (!okangle && qh ANGLEmerge) {
    +    angle= qh_getangle(facet->normal, neighbor->normal);
    +    zinc_(Zangletests);
    +  }
    +  if (isconcave) {
    +    zinc_(Zconcaveridge);
    +    if (qh ANGLEmerge)
    +      angle += qh_ANGLEconcave + 0.5;
    +    qh_appendmergeset(facet, neighbor, MRGconcave, &angle);
    +    trace0((qh ferr, 18, "qh_test_appendmerge: concave f%d to f%d dist %4.4g and reverse dist %4.4g angle %4.4g during p%d\n",
    +           facet->id, neighbor->id, dist, dist2, angle, qh furthest_id));
    +  }else /* iscoplanar */ {
    +    zinc_(Zcoplanarcentrum);
    +    qh_appendmergeset(facet, neighbor, MRGcoplanar, &angle);
    +    trace2((qh ferr, 2040, "qh_test_appendmerge: coplanar f%d to f%d dist %4.4g, reverse dist %4.4g angle %4.4g\n",
    +              facet->id, neighbor->id, dist, dist2, angle));
    +  }
    +  return True;
    +} /* test_appendmerge */
    +
    +/*---------------------------------
    +
    +  qh_test_vneighbors()
    +    test vertex neighbors for convexity
    +    tests all facets on qh.newfacet_list
    +
    +  returns:
    +    true if non-convex vneighbors appended to qh.facet_mergeset
    +    initializes vertex neighbors if needed
    +
    +  notes:
    +    assumes all facet neighbors have been tested
    +    this can be expensive
    +    this does not guarantee that a centrum is below all facets
    +      but it is unlikely
    +    uses qh.visit_id
    +
    +  design:
    +    build vertex neighbors if necessary
    +    for all new facets
    +      for all vertices
    +        for each unvisited facet neighbor of the vertex
    +          test new facet and neighbor for convexity
    +*/
    +boolT qh_test_vneighbors(void /* qh.newfacet_list */) {
    +  facetT *newfacet, *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  int nummerges= 0;
    +
    +  trace1((qh ferr, 1015, "qh_test_vneighbors: testing vertex neighbors for convexity\n"));
    +  if (!qh VERTEXneighbors)
    +    qh_vertexneighbors();
    +  FORALLnew_facets
    +    newfacet->seen= False;
    +  FORALLnew_facets {
    +    newfacet->seen= True;
    +    newfacet->visitid= qh visit_id++;
    +    FOREACHneighbor_(newfacet)
    +      newfacet->visitid= qh visit_id;
    +    FOREACHvertex_(newfacet->vertices) {
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->seen || neighbor->visitid == qh visit_id)
    +          continue;
    +        if (qh_test_appendmerge(newfacet, neighbor))
    +          nummerges++;
    +      }
    +    }
    +  }
    +  zadd_(Ztestvneighbor, nummerges);
    +  trace1((qh ferr, 1016, "qh_test_vneighbors: found %d non-convex, vertex neighbors\n",
    +           nummerges));
    +  return (nummerges > 0);
    +} /* test_vneighbors */
    +
    +/*---------------------------------
    +
    +  qh_tracemerge( facet1, facet2 )
    +    print trace message after merge
    +*/
    +void qh_tracemerge(facetT *facet1, facetT *facet2) {
    +  boolT waserror= False;
    +
    +#ifndef qh_NOtrace
    +  if (qh IStracing >= 4)
    +    qh_errprint("MERGED", facet2, NULL, NULL, NULL);
    +  if (facet2 == qh tracefacet || (qh tracevertex && qh tracevertex->newlist)) {
    +    qh_fprintf(qh ferr, 8085, "qh_tracemerge: trace facet and vertex after merge of f%d and f%d, furthest p%d\n", facet1->id, facet2->id, qh furthest_id);
    +    if (facet2 != qh tracefacet)
    +      qh_errprint("TRACE", qh tracefacet,
    +        (qh tracevertex && qh tracevertex->neighbors) ?
    +           SETfirstt_(qh tracevertex->neighbors, facetT) : NULL,
    +        NULL, qh tracevertex);
    +  }
    +  if (qh tracevertex) {
    +    if (qh tracevertex->deleted)
    +      qh_fprintf(qh ferr, 8086, "qh_tracemerge: trace vertex deleted at furthest p%d\n",
    +            qh furthest_id);
    +    else
    +      qh_checkvertex(qh tracevertex);
    +  }
    +  if (qh tracefacet) {
    +    qh_checkfacet(qh tracefacet, True, &waserror);
    +    if (waserror)
    +      qh_errexit(qh_ERRqhull, qh tracefacet, NULL);
    +  }
    +#endif /* !qh_NOtrace */
    +  if (qh CHECKfrequently || qh IStracing >= 4) { /* can't check polygon here */
    +    qh_checkfacet(facet2, True, &waserror);
    +    if (waserror)
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +} /* tracemerge */
    +
    +/*---------------------------------
    +
    +  qh_tracemerging()
    +    print trace message during POSTmerging
    +
    +  returns:
    +    updates qh.mergereport
    +
    +  notes:
    +    called from qh_mergecycle() and qh_mergefacet()
    +
    +  see:
    +    qh_buildtracing()
    +*/
    +void qh_tracemerging(void) {
    +  realT cpu;
    +  int total;
    +  time_t timedata;
    +  struct tm *tp;
    +
    +  qh mergereport= zzval_(Ztotmerge);
    +  time(&timedata);
    +  tp= localtime(&timedata);
    +  cpu= qh_CPUclock;
    +  cpu /= qh_SECticks;
    +  total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +  qh_fprintf(qh ferr, 8087, "\n\
    +At %d:%d:%d & %2.5g CPU secs, qhull has merged %d facets.  The hull\n\
    +  contains %d facets and %d vertices.\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu,
    +      total, qh num_facets - qh num_visible,
    +      qh num_vertices-qh_setsize(qh del_vertices));
    +} /* tracemerging */
    +
    +/*---------------------------------
    +
    +  qh_updatetested( facet1, facet2 )
    +    clear facet2->tested and facet1->ridge->tested for merge
    +
    +  returns:
    +    deletes facet2->center unless it's already large
    +      if so, clears facet2->ridge->tested
    +
    +  design:
    +    clear facet2->tested
    +    clear ridge->tested for facet1's ridges
    +    if facet2 has a centrum
    +      if facet2 is large
    +        set facet2->keepcentrum
    +      else if facet2 has 3 vertices due to many merges, or not large and post merging
    +        clear facet2->keepcentrum
    +      unless facet2->keepcentrum
    +        clear facet2->center to recompute centrum later
    +        clear ridge->tested for facet2's ridges
    +*/
    +void qh_updatetested(facetT *facet1, facetT *facet2) {
    +  ridgeT *ridge, **ridgep;
    +  int size;
    +
    +  facet2->tested= False;
    +  FOREACHridge_(facet1->ridges)
    +    ridge->tested= False;
    +  if (!facet2->center)
    +    return;
    +  size= qh_setsize(facet2->vertices);
    +  if (!facet2->keepcentrum) {
    +    if (size > qh hull_dim + qh_MAXnewcentrum) {
    +      facet2->keepcentrum= True;
    +      zinc_(Zwidevertices);
    +    }
    +  }else if (size <= qh hull_dim + qh_MAXnewcentrum) {
    +    /* center and keepcentrum was set */
    +    if (size == qh hull_dim || qh POSTmerging)
    +      facet2->keepcentrum= False; /* if many merges need to recompute centrum */
    +  }
    +  if (!facet2->keepcentrum) {
    +    qh_memfree(facet2->center, qh normal_size);
    +    facet2->center= NULL;
    +    FOREACHridge_(facet2->ridges)
    +      ridge->tested= False;
    +  }
    +} /* updatetested */
    +
    +/*---------------------------------
    +
    +  qh_vertexridges( vertex )
    +    return temporary set of ridges adjacent to a vertex
    +    vertex->neighbors defined
    +
    +  ntoes:
    +    uses qh.visit_id
    +    does not include implicit ridges for simplicial facets
    +
    +  design:
    +    for each neighbor of vertex
    +      add ridges that include the vertex to ridges
    +*/
    +setT *qh_vertexridges(vertexT *vertex) {
    +  facetT *neighbor, **neighborp;
    +  setT *ridges= qh_settemp(qh TEMPsize);
    +  int size;
    +
    +  qh visit_id++;
    +  FOREACHneighbor_(vertex)
    +    neighbor->visitid= qh visit_id;
    +  FOREACHneighbor_(vertex) {
    +    if (*neighborp)   /* no new ridges in last neighbor */
    +      qh_vertexridges_facet(vertex, neighbor, &ridges);
    +  }
    +  if (qh PRINTstatistics || qh IStracing) {
    +    size= qh_setsize(ridges);
    +    zinc_(Zvertexridge);
    +    zadd_(Zvertexridgetot, size);
    +    zmax_(Zvertexridgemax, size);
    +    trace3((qh ferr, 3011, "qh_vertexridges: found %d ridges for v%d\n",
    +             size, vertex->id));
    +  }
    +  return ridges;
    +} /* vertexridges */
    +
    +/*---------------------------------
    +
    +  qh_vertexridges_facet( vertex, facet, ridges )
    +    add adjacent ridges for vertex in facet
    +    neighbor->visitid==qh.visit_id if it hasn't been visited
    +
    +  returns:
    +    ridges updated
    +    sets facet->visitid to qh.visit_id-1
    +
    +  design:
    +    for each ridge of facet
    +      if ridge of visited neighbor (i.e., unprocessed)
    +        if vertex in ridge
    +          append ridge to vertex
    +    mark facet processed
    +*/
    +void qh_vertexridges_facet(vertexT *vertex, facetT *facet, setT **ridges) {
    +  ridgeT *ridge, **ridgep;
    +  facetT *neighbor;
    +
    +  FOREACHridge_(facet->ridges) {
    +    neighbor= otherfacet_(ridge, facet);
    +    if (neighbor->visitid == qh visit_id
    +    && qh_setin(ridge->vertices, vertex))
    +      qh_setappend(ridges, ridge);
    +  }
    +  facet->visitid= qh visit_id-1;
    +} /* vertexridges_facet */
    +
    +/*---------------------------------
    +
    +  qh_willdelete( facet, replace )
    +    moves facet to visible list
    +    sets facet->f.replace to replace (may be NULL)
    +
    +  returns:
    +    bumps qh.num_visible
    +*/
    +void qh_willdelete(facetT *facet, facetT *replace) {
    +
    +  qh_removefacet(facet);
    +  qh_prependfacet(facet, &qh visible_list);
    +  qh num_visible++;
    +  facet->visible= True;
    +  facet->f.replace= replace;
    +} /* willdelete */
    +
    +#else /* qh_NOmerge */
    +void qh_premerge(vertexT *apex, realT maxcentrum, realT maxangle) {
    +}
    +void qh_postmerge(const char *reason, realT maxcentrum, realT maxangle,
    +                      boolT vneighbors) {
    +}
    +boolT qh_checkzero(boolT testall) {
    +   }
    +#endif /* qh_NOmerge */
    +
    diff --git a/xs/src/qhull/src/libqhull/merge.h b/xs/src/qhull/src/libqhull/merge.h
    new file mode 100644
    index 0000000000..7f5ec3fb61
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/merge.h
    @@ -0,0 +1,178 @@
    +/*
      ---------------------------------
    +
    +   merge.h
    +   header file for merge.c
    +
    +   see qh-merge.htm and merge.c
    +
    +   Copyright (c) 1993-2015 C.B. Barber.
    +   $Id: //main/2015/qhull/src/libqhull/merge.h#1 $$Change: 1981 $
    +   $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFmerge
    +#define qhDEFmerge 1
    +
    +#include "libqhull.h"
    +
    +
    +/*============ -constants- ==============*/
    +
    +/*----------------------------------
    +
    +  qh_ANGLEredundant
    +    indicates redundant merge in mergeT->angle
    +*/
    +#define qh_ANGLEredundant 6.0
    +
    +/*----------------------------------
    +
    +  qh_ANGLEdegen
    +    indicates degenerate facet in mergeT->angle
    +*/
    +#define qh_ANGLEdegen     5.0
    +
    +/*----------------------------------
    +
    +  qh_ANGLEconcave
    +    offset to indicate concave facets in mergeT->angle
    +
    +  notes:
    +    concave facets are assigned the range of [2,4] in mergeT->angle
    +    roundoff error may make the angle less than 2
    +*/
    +#define qh_ANGLEconcave  1.5
    +
    +/*----------------------------------
    +
    +  MRG... (mergeType)
    +    indicates the type of a merge (mergeT->type)
    +*/
    +typedef enum {  /* in sort order for facet_mergeset */
    +  MRGnone= 0,
    +  MRGcoplanar,          /* centrum coplanar */
    +  MRGanglecoplanar,     /* angle coplanar */
    +                        /* could detect half concave ridges */
    +  MRGconcave,           /* concave ridge */
    +  MRGflip,              /* flipped facet. facet1 == facet2 */
    +  MRGridge,             /* duplicate ridge (qh_MERGEridge) */
    +                        /* degen and redundant go onto degen_mergeset */
    +  MRGdegen,             /* degenerate facet (!enough neighbors) facet1 == facet2 */
    +  MRGredundant,         /* redundant facet (vertex subset) */
    +                        /* merge_degenredundant assumes degen < redundant */
    +  MRGmirror,            /* mirror facet from qh_triangulate */
    +  ENDmrg
    +} mergeType;
    +
    +/*----------------------------------
    +
    +  qh_MERGEapex
    +    flag for qh_mergefacet() to indicate an apex merge
    +*/
    +#define qh_MERGEapex     True
    +
    +/*============ -structures- ====================*/
    +
    +/*----------------------------------
    +
    +  mergeT
    +    structure used to merge facets
    +*/
    +
    +typedef struct mergeT mergeT;
    +struct mergeT {         /* initialize in qh_appendmergeset */
    +  realT   angle;        /* angle between normals of facet1 and facet2 */
    +  facetT *facet1;       /* will merge facet1 into facet2 */
    +  facetT *facet2;
    +  mergeType type;
    +};
    +
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  FOREACHmerge_( merges ) {...}
    +    assign 'merge' to each merge in merges
    +
    +  notes:
    +    uses 'mergeT *merge, **mergep;'
    +    if qh_mergefacet(),
    +      restart since qh.facet_mergeset may change
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHmerge_( merges ) FOREACHsetelement_(mergeT, merges, merge)
    +
    +/*============ prototypes in alphabetical order after pre/postmerge =======*/
    +
    +void    qh_premerge(vertexT *apex, realT maxcentrum, realT maxangle);
    +void    qh_postmerge(const char *reason, realT maxcentrum, realT maxangle,
    +             boolT vneighbors);
    +void    qh_all_merges(boolT othermerge, boolT vneighbors);
    +void    qh_appendmergeset(facetT *facet, facetT *neighbor, mergeType mergetype, realT *angle);
    +setT   *qh_basevertices( facetT *samecycle);
    +void    qh_checkconnect(void /* qh.new_facets */);
    +boolT   qh_checkzero(boolT testall);
    +int     qh_compareangle(const void *p1, const void *p2);
    +int     qh_comparemerge(const void *p1, const void *p2);
    +int     qh_comparevisit(const void *p1, const void *p2);
    +void    qh_copynonconvex(ridgeT *atridge);
    +void    qh_degen_redundant_facet(facetT *facet);
    +void    qh_degen_redundant_neighbors(facetT *facet, facetT *delfacet);
    +vertexT *qh_find_newvertex(vertexT *oldvertex, setT *vertices, setT *ridges);
    +void    qh_findbest_test(boolT testcentrum, facetT *facet, facetT *neighbor,
    +           facetT **bestfacet, realT *distp, realT *mindistp, realT *maxdistp);
    +facetT *qh_findbestneighbor(facetT *facet, realT *distp, realT *mindistp, realT *maxdistp);
    +void    qh_flippedmerges(facetT *facetlist, boolT *wasmerge);
    +void    qh_forcedmerges( boolT *wasmerge);
    +void    qh_getmergeset(facetT *facetlist);
    +void    qh_getmergeset_initial(facetT *facetlist);
    +void    qh_hashridge(setT *hashtable, int hashsize, ridgeT *ridge, vertexT *oldvertex);
    +ridgeT *qh_hashridge_find(setT *hashtable, int hashsize, ridgeT *ridge,
    +              vertexT *vertex, vertexT *oldvertex, int *hashslot);
    +void    qh_makeridges(facetT *facet);
    +void    qh_mark_dupridges(facetT *facetlist);
    +void    qh_maydropneighbor(facetT *facet);
    +int     qh_merge_degenredundant(void);
    +void    qh_merge_nonconvex( facetT *facet1, facetT *facet2, mergeType mergetype);
    +void    qh_mergecycle(facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_all(facetT *facetlist, boolT *wasmerge);
    +void    qh_mergecycle_facets( facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_neighbors(facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_ridges(facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_vneighbors( facetT *samecycle, facetT *newfacet);
    +void    qh_mergefacet(facetT *facet1, facetT *facet2, realT *mindist, realT *maxdist, boolT mergeapex);
    +void    qh_mergefacet2d(facetT *facet1, facetT *facet2);
    +void    qh_mergeneighbors(facetT *facet1, facetT *facet2);
    +void    qh_mergeridges(facetT *facet1, facetT *facet2);
    +void    qh_mergesimplex(facetT *facet1, facetT *facet2, boolT mergeapex);
    +void    qh_mergevertex_del(vertexT *vertex, facetT *facet1, facetT *facet2);
    +void    qh_mergevertex_neighbors(facetT *facet1, facetT *facet2);
    +void    qh_mergevertices(setT *vertices1, setT **vertices);
    +setT   *qh_neighbor_intersections(vertexT *vertex);
    +void    qh_newvertices(setT *vertices);
    +boolT   qh_reducevertices(void);
    +vertexT *qh_redundant_vertex(vertexT *vertex);
    +boolT   qh_remove_extravertices(facetT *facet);
    +vertexT *qh_rename_sharedvertex(vertexT *vertex, facetT *facet);
    +void    qh_renameridgevertex(ridgeT *ridge, vertexT *oldvertex, vertexT *newvertex);
    +void    qh_renamevertex(vertexT *oldvertex, vertexT *newvertex, setT *ridges,
    +                        facetT *oldfacet, facetT *neighborA);
    +boolT   qh_test_appendmerge(facetT *facet, facetT *neighbor);
    +boolT   qh_test_vneighbors(void /* qh.newfacet_list */);
    +void    qh_tracemerge(facetT *facet1, facetT *facet2);
    +void    qh_tracemerging(void);
    +void    qh_updatetested( facetT *facet1, facetT *facet2);
    +setT   *qh_vertexridges(vertexT *vertex);
    +void    qh_vertexridges_facet(vertexT *vertex, facetT *facet, setT **ridges);
    +void    qh_willdelete(facetT *facet, facetT *replace);
    +
    +#endif /* qhDEFmerge */
    diff --git a/xs/src/qhull/src/libqhull/poly.c b/xs/src/qhull/src/libqhull/poly.c
    new file mode 100644
    index 0000000000..b8db6a9ef7
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/poly.c
    @@ -0,0 +1,1205 @@
    +/*
      ---------------------------------
    +
    +   poly.c
    +   implements polygons and simplices
    +
    +   see qh-poly.htm, poly.h and libqhull.h
    +
    +   infrequent code is in poly2.c
    +   (all but top 50 and their callers 12/3/95)
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/poly.c#3 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*======== functions in alphabetical order ==========*/
    +
    +/*---------------------------------
    +
    +  qh_appendfacet( facet )
    +    appends facet to end of qh.facet_list,
    +
    +  returns:
    +    updates qh.newfacet_list, facet_next, facet_list
    +    increments qh.numfacets
    +
    +  notes:
    +    assumes qh.facet_list/facet_tail is defined (createsimplex)
    +
    +  see:
    +    qh_removefacet()
    +
    +*/
    +void qh_appendfacet(facetT *facet) {
    +  facetT *tail= qh facet_tail;
    +
    +  if (tail == qh newfacet_list)
    +    qh newfacet_list= facet;
    +  if (tail == qh facet_next)
    +    qh facet_next= facet;
    +  facet->previous= tail->previous;
    +  facet->next= tail;
    +  if (tail->previous)
    +    tail->previous->next= facet;
    +  else
    +    qh facet_list= facet;
    +  tail->previous= facet;
    +  qh num_facets++;
    +  trace4((qh ferr, 4044, "qh_appendfacet: append f%d to facet_list\n", facet->id));
    +} /* appendfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_appendvertex( vertex )
    +    appends vertex to end of qh.vertex_list,
    +
    +  returns:
    +    sets vertex->newlist
    +    updates qh.vertex_list, newvertex_list
    +    increments qh.num_vertices
    +
    +  notes:
    +    assumes qh.vertex_list/vertex_tail is defined (createsimplex)
    +
    +*/
    +void qh_appendvertex(vertexT *vertex) {
    +  vertexT *tail= qh vertex_tail;
    +
    +  if (tail == qh newvertex_list)
    +    qh newvertex_list= vertex;
    +  vertex->newlist= True;
    +  vertex->previous= tail->previous;
    +  vertex->next= tail;
    +  if (tail->previous)
    +    tail->previous->next= vertex;
    +  else
    +    qh vertex_list= vertex;
    +  tail->previous= vertex;
    +  qh num_vertices++;
    +  trace4((qh ferr, 4045, "qh_appendvertex: append v%d to vertex_list\n", vertex->id));
    +} /* appendvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_attachnewfacets( )
    +    attach horizon facets to new facets in qh.newfacet_list
    +    newfacets have neighbor and ridge links to horizon but not vice versa
    +    only needed for qh.ONLYgood
    +
    +  returns:
    +    set qh.NEWfacets
    +    horizon facets linked to new facets
    +      ridges changed from visible facets to new facets
    +      simplicial ridges deleted
    +    qh.visible_list, no ridges valid
    +    facet->f.replace is a newfacet (if any)
    +
    +  design:
    +    delete interior ridges and neighbor sets by
    +      for each visible, non-simplicial facet
    +        for each ridge
    +          if last visit or if neighbor is simplicial
    +            if horizon neighbor
    +              delete ridge for horizon's ridge set
    +            delete ridge
    +        erase neighbor set
    +    attach horizon facets and new facets by
    +      for all new facets
    +        if corresponding horizon facet is simplicial
    +          locate corresponding visible facet {may be more than one}
    +          link visible facet to new facet
    +          replace visible facet with new facet in horizon
    +        else it's non-simplicial
    +          for all visible neighbors of the horizon facet
    +            link visible neighbor to new facet
    +            delete visible neighbor from horizon facet
    +          append new facet to horizon's neighbors
    +          the first ridge of the new facet is the horizon ridge
    +          link the new facet into the horizon ridge
    +*/
    +void qh_attachnewfacets(void /* qh.visible_list, newfacet_list */) {
    +  facetT *newfacet= NULL, *neighbor, **neighborp, *horizon, *visible;
    +  ridgeT *ridge, **ridgep;
    +
    +  qh NEWfacets= True;
    +  trace3((qh ferr, 3012, "qh_attachnewfacets: delete interior ridges\n"));
    +  qh visit_id++;
    +  FORALLvisible_facets {
    +    visible->visitid= qh visit_id;
    +    if (visible->ridges) {
    +      FOREACHridge_(visible->ridges) {
    +        neighbor= otherfacet_(ridge, visible);
    +        if (neighbor->visitid == qh visit_id
    +            || (!neighbor->visible && neighbor->simplicial)) {
    +          if (!neighbor->visible)  /* delete ridge for simplicial horizon */
    +            qh_setdel(neighbor->ridges, ridge);
    +          qh_setfree(&(ridge->vertices)); /* delete on 2nd visit */
    +          qh_memfree(ridge, (int)sizeof(ridgeT));
    +        }
    +      }
    +      SETfirst_(visible->ridges)= NULL;
    +    }
    +    SETfirst_(visible->neighbors)= NULL;
    +  }
    +  trace1((qh ferr, 1017, "qh_attachnewfacets: attach horizon facets to new facets\n"));
    +  FORALLnew_facets {
    +    horizon= SETfirstt_(newfacet->neighbors, facetT);
    +    if (horizon->simplicial) {
    +      visible= NULL;
    +      FOREACHneighbor_(horizon) {   /* may have more than one horizon ridge */
    +        if (neighbor->visible) {
    +          if (visible) {
    +            if (qh_setequal_skip(newfacet->vertices, 0, horizon->vertices,
    +                                  SETindex_(horizon->neighbors, neighbor))) {
    +              visible= neighbor;
    +              break;
    +            }
    +          }else
    +            visible= neighbor;
    +        }
    +      }
    +      if (visible) {
    +        visible->f.replace= newfacet;
    +        qh_setreplace(horizon->neighbors, visible, newfacet);
    +      }else {
    +        qh_fprintf(qh ferr, 6102, "qhull internal error (qh_attachnewfacets): couldn't find visible facet for horizon f%d of newfacet f%d\n",
    +                 horizon->id, newfacet->id);
    +        qh_errexit2(qh_ERRqhull, horizon, newfacet);
    +      }
    +    }else { /* non-simplicial, with a ridge for newfacet */
    +      FOREACHneighbor_(horizon) {    /* may hold for many new facets */
    +        if (neighbor->visible) {
    +          neighbor->f.replace= newfacet;
    +          qh_setdelnth(horizon->neighbors,
    +                        SETindex_(horizon->neighbors, neighbor));
    +          neighborp--; /* repeat */
    +        }
    +      }
    +      qh_setappend(&horizon->neighbors, newfacet);
    +      ridge= SETfirstt_(newfacet->ridges, ridgeT);
    +      if (ridge->top == horizon)
    +        ridge->bottom= newfacet;
    +      else
    +        ridge->top= newfacet;
    +      }
    +  } /* newfacets */
    +  if (qh PRINTstatistics) {
    +    FORALLvisible_facets {
    +      if (!visible->f.replace)
    +        zinc_(Zinsidevisible);
    +    }
    +  }
    +} /* attachnewfacets */
    +
    +/*---------------------------------
    +
    +  qh_checkflipped( facet, dist, allerror )
    +    checks facet orientation to interior point
    +
    +    if allerror set,
    +      tests against qh.DISTround
    +    else
    +      tests against 0 since tested against DISTround before
    +
    +  returns:
    +    False if it flipped orientation (sets facet->flipped)
    +    distance if non-NULL
    +*/
    +boolT qh_checkflipped(facetT *facet, realT *distp, boolT allerror) {
    +  realT dist;
    +
    +  if (facet->flipped && !distp)
    +    return False;
    +  zzinc_(Zdistcheck);
    +  qh_distplane(qh interior_point, facet, &dist);
    +  if (distp)
    +    *distp= dist;
    +  if ((allerror && dist > -qh DISTround)|| (!allerror && dist >= 0.0)) {
    +    facet->flipped= True;
    +    zzinc_(Zflippedfacets);
    +    trace0((qh ferr, 19, "qh_checkflipped: facet f%d is flipped, distance= %6.12g during p%d\n",
    +              facet->id, dist, qh furthest_id));
    +    qh_precision("flipped facet");
    +    return False;
    +  }
    +  return True;
    +} /* checkflipped */
    +
    +/*---------------------------------
    +
    +  qh_delfacet( facet )
    +    removes facet from facet_list and frees up its memory
    +
    +  notes:
    +    assumes vertices and ridges already freed
    +*/
    +void qh_delfacet(facetT *facet) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  trace4((qh ferr, 4046, "qh_delfacet: delete f%d\n", facet->id));
    +  if (facet == qh tracefacet)
    +    qh tracefacet= NULL;
    +  if (facet == qh GOODclosest)
    +    qh GOODclosest= NULL;
    +  qh_removefacet(facet);
    +  if (!facet->tricoplanar || facet->keepcentrum) {
    +    qh_memfree_(facet->normal, qh normal_size, freelistp);
    +    if (qh CENTERtype == qh_ASvoronoi) {   /* braces for macro calls */
    +      qh_memfree_(facet->center, qh center_size, freelistp);
    +    }else /* AScentrum */ {
    +      qh_memfree_(facet->center, qh normal_size, freelistp);
    +    }
    +  }
    +  qh_setfree(&(facet->neighbors));
    +  if (facet->ridges)
    +    qh_setfree(&(facet->ridges));
    +  qh_setfree(&(facet->vertices));
    +  if (facet->outsideset)
    +    qh_setfree(&(facet->outsideset));
    +  if (facet->coplanarset)
    +    qh_setfree(&(facet->coplanarset));
    +  qh_memfree_(facet, (int)sizeof(facetT), freelistp);
    +} /* delfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_deletevisible()
    +    delete visible facets and vertices
    +
    +  returns:
    +    deletes each facet and removes from facetlist
    +    at exit, qh.visible_list empty (== qh.newfacet_list)
    +
    +  notes:
    +    ridges already deleted
    +    horizon facets do not reference facets on qh.visible_list
    +    new facets in qh.newfacet_list
    +    uses   qh.visit_id;
    +*/
    +void qh_deletevisible(void /*qh.visible_list*/) {
    +  facetT *visible, *nextfacet;
    +  vertexT *vertex, **vertexp;
    +  int numvisible= 0, numdel= qh_setsize(qh del_vertices);
    +
    +  trace1((qh ferr, 1018, "qh_deletevisible: delete %d visible facets and %d vertices\n",
    +         qh num_visible, numdel));
    +  for (visible= qh visible_list; visible && visible->visible;
    +                visible= nextfacet) { /* deleting current */
    +    nextfacet= visible->next;
    +    numvisible++;
    +    qh_delfacet(visible);
    +  }
    +  if (numvisible != qh num_visible) {
    +    qh_fprintf(qh ferr, 6103, "qhull internal error (qh_deletevisible): qh num_visible %d is not number of visible facets %d\n",
    +             qh num_visible, numvisible);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh num_visible= 0;
    +  zadd_(Zvisfacettot, numvisible);
    +  zmax_(Zvisfacetmax, numvisible);
    +  zzadd_(Zdelvertextot, numdel);
    +  zmax_(Zdelvertexmax, numdel);
    +  FOREACHvertex_(qh del_vertices)
    +    qh_delvertex(vertex);
    +  qh_settruncate(qh del_vertices, 0);
    +} /* deletevisible */
    +
    +/*---------------------------------
    +
    +  qh_facetintersect( facetA, facetB, skipa, skipB, prepend )
    +    return vertices for intersection of two simplicial facets
    +    may include 1 prepended entry (if more, need to settemppush)
    +
    +  returns:
    +    returns set of qh.hull_dim-1 + prepend vertices
    +    returns skipped index for each test and checks for exactly one
    +
    +  notes:
    +    does not need settemp since set in quick memory
    +
    +  see also:
    +    qh_vertexintersect and qh_vertexintersect_new
    +    use qh_setnew_delnthsorted to get nth ridge (no skip information)
    +
    +  design:
    +    locate skipped vertex by scanning facet A's neighbors
    +    locate skipped vertex by scanning facet B's neighbors
    +    intersect the vertex sets
    +*/
    +setT *qh_facetintersect(facetT *facetA, facetT *facetB,
    +                         int *skipA,int *skipB, int prepend) {
    +  setT *intersect;
    +  int dim= qh hull_dim, i, j;
    +  facetT **neighborsA, **neighborsB;
    +
    +  neighborsA= SETaddr_(facetA->neighbors, facetT);
    +  neighborsB= SETaddr_(facetB->neighbors, facetT);
    +  i= j= 0;
    +  if (facetB == *neighborsA++)
    +    *skipA= 0;
    +  else if (facetB == *neighborsA++)
    +    *skipA= 1;
    +  else if (facetB == *neighborsA++)
    +    *skipA= 2;
    +  else {
    +    for (i=3; i < dim; i++) {
    +      if (facetB == *neighborsA++) {
    +        *skipA= i;
    +        break;
    +      }
    +    }
    +  }
    +  if (facetA == *neighborsB++)
    +    *skipB= 0;
    +  else if (facetA == *neighborsB++)
    +    *skipB= 1;
    +  else if (facetA == *neighborsB++)
    +    *skipB= 2;
    +  else {
    +    for (j=3; j < dim; j++) {
    +      if (facetA == *neighborsB++) {
    +        *skipB= j;
    +        break;
    +      }
    +    }
    +  }
    +  if (i >= dim || j >= dim) {
    +    qh_fprintf(qh ferr, 6104, "qhull internal error (qh_facetintersect): f%d or f%d not in others neighbors\n",
    +            facetA->id, facetB->id);
    +    qh_errexit2(qh_ERRqhull, facetA, facetB);
    +  }
    +  intersect= qh_setnew_delnthsorted(facetA->vertices, qh hull_dim, *skipA, prepend);
    +  trace4((qh ferr, 4047, "qh_facetintersect: f%d skip %d matches f%d skip %d\n",
    +          facetA->id, *skipA, facetB->id, *skipB));
    +  return(intersect);
    +} /* facetintersect */
    +
    +/*---------------------------------
    +
    +  qh_gethash( hashsize, set, size, firstindex, skipelem )
    +    return hashvalue for a set with firstindex and skipelem
    +
    +  notes:
    +    returned hash is in [0,hashsize)
    +    assumes at least firstindex+1 elements
    +    assumes skipelem is NULL, in set, or part of hash
    +
    +    hashes memory addresses which may change over different runs of the same data
    +    using sum for hash does badly in high d
    +*/
    +int qh_gethash(int hashsize, setT *set, int size, int firstindex, void *skipelem) {
    +  void **elemp= SETelemaddr_(set, firstindex, void);
    +  ptr_intT hash = 0, elem;
    +  unsigned result;
    +  int i;
    +#ifdef _MSC_VER                   /* Microsoft Visual C++ -- warn about 64-bit issues */
    +#pragma warning( push)            /* WARN64 -- ptr_intT holds a 64-bit pointer */
    +#pragma warning( disable : 4311)  /* 'type cast': pointer truncation from 'void*' to 'ptr_intT' */
    +#endif
    +
    +  switch (size-firstindex) {
    +  case 1:
    +    hash= (ptr_intT)(*elemp) - (ptr_intT) skipelem;
    +    break;
    +  case 2:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] - (ptr_intT) skipelem;
    +    break;
    +  case 3:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      - (ptr_intT) skipelem;
    +    break;
    +  case 4:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] - (ptr_intT) skipelem;
    +    break;
    +  case 5:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] + (ptr_intT)elemp[4] - (ptr_intT) skipelem;
    +    break;
    +  case 6:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] + (ptr_intT)elemp[4]+ (ptr_intT)elemp[5]
    +      - (ptr_intT) skipelem;
    +    break;
    +  default:
    +    hash= 0;
    +    i= 3;
    +    do {     /* this is about 10% in 10-d */
    +      if ((elem= (ptr_intT)*elemp++) != (ptr_intT)skipelem) {
    +        hash ^= (elem << i) + (elem >> (32-i));
    +        i += 3;
    +        if (i >= 32)
    +          i -= 32;
    +      }
    +    }while (*elemp);
    +    break;
    +  }
    +  if (hashsize<0) {
    +    qh_fprintf(qh ferr, 6202, "qhull internal error: negative hashsize %d passed to qh_gethash [poly.c]\n", hashsize);
    +    qh_errexit2(qh_ERRqhull, NULL, NULL);
    +  }
    +  result= (unsigned)hash;
    +  result %= (unsigned)hashsize;
    +  /* result= 0; for debugging */
    +  return result;
    +#ifdef _MSC_VER
    +#pragma warning( pop)
    +#endif
    +} /* gethash */
    +
    +/*---------------------------------
    +
    +  qh_makenewfacet( vertices, toporient, horizon )
    +    creates a toporient? facet from vertices
    +
    +  returns:
    +    returns newfacet
    +      adds newfacet to qh.facet_list
    +      newfacet->vertices= vertices
    +      if horizon
    +        newfacet->neighbor= horizon, but not vice versa
    +    newvertex_list updated with vertices
    +*/
    +facetT *qh_makenewfacet(setT *vertices, boolT toporient,facetT *horizon) {
    +  facetT *newfacet;
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->newlist) {
    +      qh_removevertex(vertex);
    +      qh_appendvertex(vertex);
    +    }
    +  }
    +  newfacet= qh_newfacet();
    +  newfacet->vertices= vertices;
    +  newfacet->toporient= (unsigned char)toporient;
    +  if (horizon)
    +    qh_setappend(&(newfacet->neighbors), horizon);
    +  qh_appendfacet(newfacet);
    +  return(newfacet);
    +} /* makenewfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_makenewplanes()
    +    make new hyperplanes for facets on qh.newfacet_list
    +
    +  returns:
    +    all facets have hyperplanes or are marked for   merging
    +    doesn't create hyperplane if horizon is coplanar (will merge)
    +    updates qh.min_vertex if qh.JOGGLEmax
    +
    +  notes:
    +    facet->f.samecycle is defined for facet->mergehorizon facets
    +*/
    +void qh_makenewplanes(void /* newfacet_list */) {
    +  facetT *newfacet;
    +
    +  FORALLnew_facets {
    +    if (!newfacet->mergehorizon)
    +      qh_setfacetplane(newfacet);
    +  }
    +  if (qh JOGGLEmax < REALmax/2)
    +    minimize_(qh min_vertex, -wwval_(Wnewvertexmax));
    +} /* makenewplanes */
    +
    +/*---------------------------------
    +
    +  qh_makenew_nonsimplicial( visible, apex, numnew )
    +    make new facets for ridges of a visible facet
    +
    +  returns:
    +    first newfacet, bumps numnew as needed
    +    attaches new facets if !qh.ONLYgood
    +    marks ridge neighbors for simplicial visible
    +    if (qh.ONLYgood)
    +      ridges on newfacet, horizon, and visible
    +    else
    +      ridge and neighbors between newfacet and   horizon
    +      visible facet's ridges are deleted
    +
    +  notes:
    +    qh.visit_id if visible has already been processed
    +    sets neighbor->seen for building f.samecycle
    +      assumes all 'seen' flags initially false
    +
    +  design:
    +    for each ridge of visible facet
    +      get neighbor of visible facet
    +      if neighbor was already processed
    +        delete the ridge (will delete all visible facets later)
    +      if neighbor is a horizon facet
    +        create a new facet
    +        if neighbor coplanar
    +          adds newfacet to f.samecycle for later merging
    +        else
    +          updates neighbor's neighbor set
    +          (checks for non-simplicial facet with multiple ridges to visible facet)
    +        updates neighbor's ridge set
    +        (checks for simplicial neighbor to non-simplicial visible facet)
    +        (deletes ridge if neighbor is simplicial)
    +
    +*/
    +#ifndef qh_NOmerge
    +facetT *qh_makenew_nonsimplicial(facetT *visible, vertexT *apex, int *numnew) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +  ridgeT *ridge, **ridgep;
    +  facetT *neighbor, *newfacet= NULL, *samecycle;
    +  setT *vertices;
    +  boolT toporient;
    +  int ridgeid;
    +
    +  FOREACHridge_(visible->ridges) {
    +    ridgeid= ridge->id;
    +    neighbor= otherfacet_(ridge, visible);
    +    if (neighbor->visible) {
    +      if (!qh ONLYgood) {
    +        if (neighbor->visitid == qh visit_id) {
    +          qh_setfree(&(ridge->vertices));  /* delete on 2nd visit */
    +          qh_memfree_(ridge, (int)sizeof(ridgeT), freelistp);
    +        }
    +      }
    +    }else {  /* neighbor is an horizon facet */
    +      toporient= (ridge->top == visible);
    +      vertices= qh_setnew(qh hull_dim); /* makes sure this is quick */
    +      qh_setappend(&vertices, apex);
    +      qh_setappend_set(&vertices, ridge->vertices);
    +      newfacet= qh_makenewfacet(vertices, toporient, neighbor);
    +      (*numnew)++;
    +      if (neighbor->coplanar) {
    +        newfacet->mergehorizon= True;
    +        if (!neighbor->seen) {
    +          newfacet->f.samecycle= newfacet;
    +          neighbor->f.newcycle= newfacet;
    +        }else {
    +          samecycle= neighbor->f.newcycle;
    +          newfacet->f.samecycle= samecycle->f.samecycle;
    +          samecycle->f.samecycle= newfacet;
    +        }
    +      }
    +      if (qh ONLYgood) {
    +        if (!neighbor->simplicial)
    +          qh_setappend(&(newfacet->ridges), ridge);
    +      }else {  /* qh_attachnewfacets */
    +        if (neighbor->seen) {
    +          if (neighbor->simplicial) {
    +            qh_fprintf(qh ferr, 6105, "qhull internal error (qh_makenew_nonsimplicial): simplicial f%d sharing two ridges with f%d\n",
    +                   neighbor->id, visible->id);
    +            qh_errexit2(qh_ERRqhull, neighbor, visible);
    +          }
    +          qh_setappend(&(neighbor->neighbors), newfacet);
    +        }else
    +          qh_setreplace(neighbor->neighbors, visible, newfacet);
    +        if (neighbor->simplicial) {
    +          qh_setdel(neighbor->ridges, ridge);
    +          qh_setfree(&(ridge->vertices));
    +          qh_memfree(ridge, (int)sizeof(ridgeT));
    +        }else {
    +          qh_setappend(&(newfacet->ridges), ridge);
    +          if (toporient)
    +            ridge->top= newfacet;
    +          else
    +            ridge->bottom= newfacet;
    +        }
    +      trace4((qh ferr, 4048, "qh_makenew_nonsimplicial: created facet f%d from v%d and r%d of horizon f%d\n",
    +            newfacet->id, apex->id, ridgeid, neighbor->id));
    +      }
    +    }
    +    neighbor->seen= True;
    +  } /* for each ridge */
    +  if (!qh ONLYgood)
    +    SETfirst_(visible->ridges)= NULL;
    +  return newfacet;
    +} /* makenew_nonsimplicial */
    +#else /* qh_NOmerge */
    +facetT *qh_makenew_nonsimplicial(facetT *visible, vertexT *apex, int *numnew) {
    +  return NULL;
    +}
    +#endif /* qh_NOmerge */
    +
    +/*---------------------------------
    +
    +  qh_makenew_simplicial( visible, apex, numnew )
    +    make new facets for simplicial visible facet and apex
    +
    +  returns:
    +    attaches new facets if (!qh.ONLYgood)
    +      neighbors between newfacet and horizon
    +
    +  notes:
    +    nop if neighbor->seen or neighbor->visible(see qh_makenew_nonsimplicial)
    +
    +  design:
    +    locate neighboring horizon facet for visible facet
    +    determine vertices and orientation
    +    create new facet
    +    if coplanar,
    +      add new facet to f.samecycle
    +    update horizon facet's neighbor list
    +*/
    +facetT *qh_makenew_simplicial(facetT *visible, vertexT *apex, int *numnew) {
    +  facetT *neighbor, **neighborp, *newfacet= NULL;
    +  setT *vertices;
    +  boolT flip, toporient;
    +  int horizonskip= 0, visibleskip= 0;
    +
    +  FOREACHneighbor_(visible) {
    +    if (!neighbor->seen && !neighbor->visible) {
    +      vertices= qh_facetintersect(neighbor,visible, &horizonskip, &visibleskip, 1);
    +      SETfirst_(vertices)= apex;
    +      flip= ((horizonskip & 0x1) ^ (visibleskip & 0x1));
    +      if (neighbor->toporient)
    +        toporient= horizonskip & 0x1;
    +      else
    +        toporient= (horizonskip & 0x1) ^ 0x1;
    +      newfacet= qh_makenewfacet(vertices, toporient, neighbor);
    +      (*numnew)++;
    +      if (neighbor->coplanar && (qh PREmerge || qh MERGEexact)) {
    +#ifndef qh_NOmerge
    +        newfacet->f.samecycle= newfacet;
    +        newfacet->mergehorizon= True;
    +#endif
    +      }
    +      if (!qh ONLYgood)
    +        SETelem_(neighbor->neighbors, horizonskip)= newfacet;
    +      trace4((qh ferr, 4049, "qh_makenew_simplicial: create facet f%d top %d from v%d and horizon f%d skip %d top %d and visible f%d skip %d, flip? %d\n",
    +            newfacet->id, toporient, apex->id, neighbor->id, horizonskip,
    +              neighbor->toporient, visible->id, visibleskip, flip));
    +    }
    +  }
    +  return newfacet;
    +} /* makenew_simplicial */
    +
    +/*---------------------------------
    +
    +  qh_matchneighbor( newfacet, newskip, hashsize, hashcount )
    +    either match subridge of newfacet with neighbor or add to hash_table
    +
    +  returns:
    +    duplicate ridges are unmatched and marked by qh_DUPLICATEridge
    +
    +  notes:
    +    ridge is newfacet->vertices w/o newskip vertex
    +    do not allocate memory (need to free hash_table cleanly)
    +    uses linear hash chains
    +
    +  see also:
    +    qh_matchduplicates
    +
    +  design:
    +    for each possible matching facet in qh.hash_table
    +      if vertices match
    +        set ismatch, if facets have opposite orientation
    +        if ismatch and matching facet doesn't have a match
    +          match the facets by updating their neighbor sets
    +        else
    +          indicate a duplicate ridge
    +          set facet hyperplane for later testing
    +          add facet to hashtable
    +          unless the other facet was already a duplicate ridge
    +            mark both facets with a duplicate ridge
    +            add other facet (if defined) to hash table
    +*/
    +void qh_matchneighbor(facetT *newfacet, int newskip, int hashsize, int *hashcount) {
    +  boolT newfound= False;   /* True, if new facet is already in hash chain */
    +  boolT same, ismatch;
    +  int hash, scan;
    +  facetT *facet, *matchfacet;
    +  int skip, matchskip;
    +
    +  hash= qh_gethash(hashsize, newfacet->vertices, qh hull_dim, 1,
    +                     SETelem_(newfacet->vertices, newskip));
    +  trace4((qh ferr, 4050, "qh_matchneighbor: newfacet f%d skip %d hash %d hashcount %d\n",
    +          newfacet->id, newskip, hash, *hashcount));
    +  zinc_(Zhashlookup);
    +  for (scan= hash; (facet= SETelemt_(qh hash_table, scan, facetT));
    +       scan= (++scan >= hashsize ? 0 : scan)) {
    +    if (facet == newfacet) {
    +      newfound= True;
    +      continue;
    +    }
    +    zinc_(Zhashtests);
    +    if (qh_matchvertices(1, newfacet->vertices, newskip, facet->vertices, &skip, &same)) {
    +      if (SETelem_(newfacet->vertices, newskip) ==
    +          SETelem_(facet->vertices, skip)) {
    +        qh_precision("two facets with the same vertices");
    +        qh_fprintf(qh ferr, 6106, "qhull precision error: Vertex sets are the same for f%d and f%d.  Can not force output.\n",
    +          facet->id, newfacet->id);
    +        qh_errexit2(qh_ERRprec, facet, newfacet);
    +      }
    +      ismatch= (same == (boolT)((newfacet->toporient ^ facet->toporient)));
    +      matchfacet= SETelemt_(facet->neighbors, skip, facetT);
    +      if (ismatch && !matchfacet) {
    +        SETelem_(facet->neighbors, skip)= newfacet;
    +        SETelem_(newfacet->neighbors, newskip)= facet;
    +        (*hashcount)--;
    +        trace4((qh ferr, 4051, "qh_matchneighbor: f%d skip %d matched with new f%d skip %d\n",
    +           facet->id, skip, newfacet->id, newskip));
    +        return;
    +      }
    +      if (!qh PREmerge && !qh MERGEexact) {
    +        qh_precision("a ridge with more than two neighbors");
    +        qh_fprintf(qh ferr, 6107, "qhull precision error: facets f%d, f%d and f%d meet at a ridge with more than 2 neighbors.  Can not continue.\n",
    +                 facet->id, newfacet->id, getid_(matchfacet));
    +        qh_errexit2(qh_ERRprec, facet, newfacet);
    +      }
    +      SETelem_(newfacet->neighbors, newskip)= qh_DUPLICATEridge;
    +      newfacet->dupridge= True;
    +      if (!newfacet->normal)
    +        qh_setfacetplane(newfacet);
    +      qh_addhash(newfacet, qh hash_table, hashsize, hash);
    +      (*hashcount)++;
    +      if (!facet->normal)
    +        qh_setfacetplane(facet);
    +      if (matchfacet != qh_DUPLICATEridge) {
    +        SETelem_(facet->neighbors, skip)= qh_DUPLICATEridge;
    +        facet->dupridge= True;
    +        if (!facet->normal)
    +          qh_setfacetplane(facet);
    +        if (matchfacet) {
    +          matchskip= qh_setindex(matchfacet->neighbors, facet);
    +          if (matchskip<0) {
    +              qh_fprintf(qh ferr, 6260, "qhull internal error (qh_matchneighbor): matchfacet f%d is in f%d neighbors but not vice versa.  Can not continue.\n",
    +                  matchfacet->id, facet->id);
    +              qh_errexit2(qh_ERRqhull, matchfacet, facet);
    +          }
    +          SETelem_(matchfacet->neighbors, matchskip)= qh_DUPLICATEridge; /* matchskip>=0 by QH6260 */
    +          matchfacet->dupridge= True;
    +          if (!matchfacet->normal)
    +            qh_setfacetplane(matchfacet);
    +          qh_addhash(matchfacet, qh hash_table, hashsize, hash);
    +          *hashcount += 2;
    +        }
    +      }
    +      trace4((qh ferr, 4052, "qh_matchneighbor: new f%d skip %d duplicates ridge for f%d skip %d matching f%d ismatch %d at hash %d\n",
    +           newfacet->id, newskip, facet->id, skip,
    +           (matchfacet == qh_DUPLICATEridge ? -2 : getid_(matchfacet)),
    +           ismatch, hash));
    +      return; /* end of duplicate ridge */
    +    }
    +  }
    +  if (!newfound)
    +    SETelem_(qh hash_table, scan)= newfacet;  /* same as qh_addhash */
    +  (*hashcount)++;
    +  trace4((qh ferr, 4053, "qh_matchneighbor: no match for f%d skip %d at hash %d\n",
    +           newfacet->id, newskip, hash));
    +} /* matchneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_matchnewfacets()
    +    match newfacets in qh.newfacet_list to their newfacet neighbors
    +
    +  returns:
    +    qh.newfacet_list with full neighbor sets
    +      get vertices with nth neighbor by deleting nth vertex
    +    if qh.PREmerge/MERGEexact or qh.FORCEoutput
    +      sets facet->flippped if flipped normal (also prevents point partitioning)
    +    if duplicate ridges and qh.PREmerge/MERGEexact
    +      sets facet->dupridge
    +      missing neighbor links identifies extra ridges to be merging (qh_MERGEridge)
    +
    +  notes:
    +    newfacets already have neighbor[0] (horizon facet)
    +    assumes qh.hash_table is NULL
    +    vertex->neighbors has not been updated yet
    +    do not allocate memory after qh.hash_table (need to free it cleanly)
    +
    +  design:
    +    delete neighbor sets for all new facets
    +    initialize a hash table
    +    for all new facets
    +      match facet with neighbors
    +    if unmatched facets (due to duplicate ridges)
    +      for each new facet with a duplicate ridge
    +        match it with a facet
    +    check for flipped facets
    +*/
    +void qh_matchnewfacets(void /* qh.newfacet_list */) {
    +  int numnew=0, hashcount=0, newskip;
    +  facetT *newfacet, *neighbor;
    +  int dim= qh hull_dim, hashsize, neighbor_i, neighbor_n;
    +  setT *neighbors;
    +#ifndef qh_NOtrace
    +  int facet_i, facet_n, numfree= 0;
    +  facetT *facet;
    +#endif
    +
    +  trace1((qh ferr, 1019, "qh_matchnewfacets: match neighbors for new facets.\n"));
    +  FORALLnew_facets {
    +    numnew++;
    +    {  /* inline qh_setzero(newfacet->neighbors, 1, qh hull_dim); */
    +      neighbors= newfacet->neighbors;
    +      neighbors->e[neighbors->maxsize].i= dim+1; /*may be overwritten*/
    +      memset((char *)SETelemaddr_(neighbors, 1, void), 0, dim * SETelemsize);
    +    }
    +  }
    +
    +  qh_newhashtable(numnew*(qh hull_dim-1)); /* twice what is normally needed,
    +                                     but every ridge could be DUPLICATEridge */
    +  hashsize= qh_setsize(qh hash_table);
    +  FORALLnew_facets {
    +    for (newskip=1; newskip0 because hull_dim>1 and numnew>0 */
    +      qh_matchneighbor(newfacet, newskip, hashsize, &hashcount);
    +#if 0   /* use the following to trap hashcount errors */
    +    {
    +      int count= 0, k;
    +      facetT *facet, *neighbor;
    +
    +      count= 0;
    +      FORALLfacet_(qh newfacet_list) {  /* newfacet already in use */
    +        for (k=1; k < qh hull_dim; k++) {
    +          neighbor= SETelemt_(facet->neighbors, k, facetT);
    +          if (!neighbor || neighbor == qh_DUPLICATEridge)
    +            count++;
    +        }
    +        if (facet == newfacet)
    +          break;
    +      }
    +      if (count != hashcount) {
    +        qh_fprintf(qh ferr, 8088, "qh_matchnewfacets: after adding facet %d, hashcount %d != count %d\n",
    +                 newfacet->id, hashcount, count);
    +        qh_errexit(qh_ERRqhull, newfacet, NULL);
    +      }
    +    }
    +#endif  /* end of trap code */
    +  }
    +  if (hashcount) {
    +    FORALLnew_facets {
    +      if (newfacet->dupridge) {
    +        FOREACHneighbor_i_(newfacet) {
    +          if (neighbor == qh_DUPLICATEridge) {
    +            qh_matchduplicates(newfacet, neighbor_i, hashsize, &hashcount);
    +                    /* this may report MERGEfacet */
    +          }
    +        }
    +      }
    +    }
    +  }
    +  if (hashcount) {
    +    qh_fprintf(qh ferr, 6108, "qhull internal error (qh_matchnewfacets): %d neighbors did not match up\n",
    +        hashcount);
    +    qh_printhashtable(qh ferr);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +#ifndef qh_NOtrace
    +  if (qh IStracing >= 2) {
    +    FOREACHfacet_i_(qh hash_table) {
    +      if (!facet)
    +        numfree++;
    +    }
    +    qh_fprintf(qh ferr, 8089, "qh_matchnewfacets: %d new facets, %d unused hash entries .  hashsize %d\n",
    +             numnew, numfree, qh_setsize(qh hash_table));
    +  }
    +#endif /* !qh_NOtrace */
    +  qh_setfree(&qh hash_table);
    +  if (qh PREmerge || qh MERGEexact) {
    +    if (qh IStracing >= 4)
    +      qh_printfacetlist(qh newfacet_list, NULL, qh_ALL);
    +    FORALLnew_facets {
    +      if (newfacet->normal)
    +        qh_checkflipped(newfacet, NULL, qh_ALL);
    +    }
    +  }else if (qh FORCEoutput)
    +    qh_checkflipped_all(qh newfacet_list);  /* prints warnings for flipped */
    +} /* matchnewfacets */
    +
    +
    +/*---------------------------------
    +
    +  qh_matchvertices( firstindex, verticesA, skipA, verticesB, skipB, same )
    +    tests whether vertices match with a single skip
    +    starts match at firstindex since all new facets have a common vertex
    +
    +  returns:
    +    true if matched vertices
    +    skip index for each set
    +    sets same iff vertices have the same orientation
    +
    +  notes:
    +    assumes skipA is in A and both sets are the same size
    +
    +  design:
    +    set up pointers
    +    scan both sets checking for a match
    +    test orientation
    +*/
    +boolT qh_matchvertices(int firstindex, setT *verticesA, int skipA,
    +       setT *verticesB, int *skipB, boolT *same) {
    +  vertexT **elemAp, **elemBp, **skipBp=NULL, **skipAp;
    +
    +  elemAp= SETelemaddr_(verticesA, firstindex, vertexT);
    +  elemBp= SETelemaddr_(verticesB, firstindex, vertexT);
    +  skipAp= SETelemaddr_(verticesA, skipA, vertexT);
    +  do if (elemAp != skipAp) {
    +    while (*elemAp != *elemBp++) {
    +      if (skipBp)
    +        return False;
    +      skipBp= elemBp;  /* one extra like FOREACH */
    +    }
    +  }while (*(++elemAp));
    +  if (!skipBp)
    +    skipBp= ++elemBp;
    +  *skipB= SETindex_(verticesB, skipB); /* i.e., skipBp - verticesB */
    +  *same= !((skipA & 0x1) ^ (*skipB & 0x1)); /* result is 0 or 1 */
    +  trace4((qh ferr, 4054, "qh_matchvertices: matched by skip %d(v%d) and skip %d(v%d) same? %d\n",
    +          skipA, (*skipAp)->id, *skipB, (*(skipBp-1))->id, *same));
    +  return(True);
    +} /* matchvertices */
    +
    +/*---------------------------------
    +
    +  qh_newfacet()
    +    return a new facet
    +
    +  returns:
    +    all fields initialized or cleared   (NULL)
    +    preallocates neighbors set
    +*/
    +facetT *qh_newfacet(void) {
    +  facetT *facet;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_((int)sizeof(facetT), freelistp, facet, facetT);
    +  memset((char *)facet, (size_t)0, sizeof(facetT));
    +  if (qh facet_id == qh tracefacet_id)
    +    qh tracefacet= facet;
    +  facet->id= qh facet_id++;
    +  facet->neighbors= qh_setnew(qh hull_dim);
    +#if !qh_COMPUTEfurthest
    +  facet->furthestdist= 0.0;
    +#endif
    +#if qh_MAXoutside
    +  if (qh FORCEoutput && qh APPROXhull)
    +    facet->maxoutside= qh MINoutside;
    +  else
    +    facet->maxoutside= qh DISTround;
    +#endif
    +  facet->simplicial= True;
    +  facet->good= True;
    +  facet->newfacet= True;
    +  trace4((qh ferr, 4055, "qh_newfacet: created facet f%d\n", facet->id));
    +  return(facet);
    +} /* newfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_newridge()
    +    return a new ridge
    +*/
    +ridgeT *qh_newridge(void) {
    +  ridgeT *ridge;
    +  void **freelistp;   /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_((int)sizeof(ridgeT), freelistp, ridge, ridgeT);
    +  memset((char *)ridge, (size_t)0, sizeof(ridgeT));
    +  zinc_(Ztotridges);
    +  if (qh ridge_id == UINT_MAX) {
    +    qh_fprintf(qh ferr, 7074, "\
    +qhull warning: more than 2^32 ridges.  Qhull results are OK.  Since the ridge ID wraps around to 0, two ridges may have the same identifier.\n");
    +  }
    +  ridge->id= qh ridge_id++;
    +  trace4((qh ferr, 4056, "qh_newridge: created ridge r%d\n", ridge->id));
    +  return(ridge);
    +} /* newridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_pointid(  point )
    +    return id for a point,
    +    returns qh_IDnone(-3) if null, qh_IDinterior(-2) if interior, or qh_IDunknown(-1) if not known
    +
    +  alternative code if point is in qh.first_point...
    +    unsigned long id;
    +    id= ((unsigned long)point - (unsigned long)qh.first_point)/qh.normal_size;
    +
    +  notes:
    +    Valid points are non-negative
    +    WARN64 -- id truncated to 32-bits, at most 2G points
    +    NOerrors returned (QhullPoint::id)
    +    if point not in point array
    +      the code does a comparison of unrelated pointers.
    +*/
    +int qh_pointid(pointT *point) {
    +  ptr_intT offset, id;
    +
    +  if (!point)
    +    return qh_IDnone;
    +  else if (point == qh interior_point)
    +    return qh_IDinterior;
    +  else if (point >= qh first_point
    +  && point < qh first_point + qh num_points * qh hull_dim) {
    +    offset= (ptr_intT)(point - qh first_point);
    +    id= offset / qh hull_dim;
    +  }else if ((id= qh_setindex(qh other_points, point)) != -1)
    +    id += qh num_points;
    +  else
    +    return qh_IDunknown;
    +  return (int)id;
    +} /* pointid */
    +
    +/*---------------------------------
    +
    +  qh_removefacet( facet )
    +    unlinks facet from qh.facet_list,
    +
    +  returns:
    +    updates qh.facet_list .newfacet_list .facet_next visible_list
    +    decrements qh.num_facets
    +
    +  see:
    +    qh_appendfacet
    +*/
    +void qh_removefacet(facetT *facet) {
    +  facetT *next= facet->next, *previous= facet->previous;
    +
    +  if (facet == qh newfacet_list)
    +    qh newfacet_list= next;
    +  if (facet == qh facet_next)
    +    qh facet_next= next;
    +  if (facet == qh visible_list)
    +    qh visible_list= next;
    +  if (previous) {
    +    previous->next= next;
    +    next->previous= previous;
    +  }else {  /* 1st facet in qh facet_list */
    +    qh facet_list= next;
    +    qh facet_list->previous= NULL;
    +  }
    +  qh num_facets--;
    +  trace4((qh ferr, 4057, "qh_removefacet: remove f%d from facet_list\n", facet->id));
    +} /* removefacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_removevertex( vertex )
    +    unlinks vertex from qh.vertex_list,
    +
    +  returns:
    +    updates qh.vertex_list .newvertex_list
    +    decrements qh.num_vertices
    +*/
    +void qh_removevertex(vertexT *vertex) {
    +  vertexT *next= vertex->next, *previous= vertex->previous;
    +
    +  if (vertex == qh newvertex_list)
    +    qh newvertex_list= next;
    +  if (previous) {
    +    previous->next= next;
    +    next->previous= previous;
    +  }else {  /* 1st vertex in qh vertex_list */
    +    qh vertex_list= vertex->next;
    +    qh vertex_list->previous= NULL;
    +  }
    +  qh num_vertices--;
    +  trace4((qh ferr, 4058, "qh_removevertex: remove v%d from vertex_list\n", vertex->id));
    +} /* removevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_updatevertices()
    +    update vertex neighbors and delete interior vertices
    +
    +  returns:
    +    if qh.VERTEXneighbors, updates neighbors for each vertex
    +      if qh.newvertex_list,
    +         removes visible neighbors  from vertex neighbors
    +      if qh.newfacet_list
    +         adds new facets to vertex neighbors
    +    if qh.visible_list
    +       interior vertices added to qh.del_vertices for later partitioning
    +
    +  design:
    +    if qh.VERTEXneighbors
    +      deletes references to visible facets from vertex neighbors
    +      appends new facets to the neighbor list for each vertex
    +      checks all vertices of visible facets
    +        removes visible facets from neighbor lists
    +        marks unused vertices for deletion
    +*/
    +void qh_updatevertices(void /*qh.newvertex_list, newfacet_list, visible_list*/) {
    +  facetT *newfacet= NULL, *neighbor, **neighborp, *visible;
    +  vertexT *vertex, **vertexp;
    +
    +  trace3((qh ferr, 3013, "qh_updatevertices: delete interior vertices and update vertex->neighbors\n"));
    +  if (qh VERTEXneighbors) {
    +    FORALLvertex_(qh newvertex_list) {
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visible)
    +          SETref_(neighbor)= NULL;
    +      }
    +      qh_setcompact(vertex->neighbors);
    +    }
    +    FORALLnew_facets {
    +      FOREACHvertex_(newfacet->vertices)
    +        qh_setappend(&vertex->neighbors, newfacet);
    +    }
    +    FORALLvisible_facets {
    +      FOREACHvertex_(visible->vertices) {
    +        if (!vertex->newlist && !vertex->deleted) {
    +          FOREACHneighbor_(vertex) { /* this can happen under merging */
    +            if (!neighbor->visible)
    +              break;
    +          }
    +          if (neighbor)
    +            qh_setdel(vertex->neighbors, visible);
    +          else {
    +            vertex->deleted= True;
    +            qh_setappend(&qh del_vertices, vertex);
    +            trace2((qh ferr, 2041, "qh_updatevertices: delete vertex p%d(v%d) in f%d\n",
    +                  qh_pointid(vertex->point), vertex->id, visible->id));
    +          }
    +        }
    +      }
    +    }
    +  }else {  /* !VERTEXneighbors */
    +    FORALLvisible_facets {
    +      FOREACHvertex_(visible->vertices) {
    +        if (!vertex->newlist && !vertex->deleted) {
    +          vertex->deleted= True;
    +          qh_setappend(&qh del_vertices, vertex);
    +          trace2((qh ferr, 2042, "qh_updatevertices: delete vertex p%d(v%d) in f%d\n",
    +                  qh_pointid(vertex->point), vertex->id, visible->id));
    +        }
    +      }
    +    }
    +  }
    +} /* updatevertices */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/poly.h b/xs/src/qhull/src/libqhull/poly.h
    new file mode 100644
    index 0000000000..af8b42077f
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/poly.h
    @@ -0,0 +1,296 @@
    +/*
      ---------------------------------
    +
    +   poly.h
    +   header file for poly.c and poly2.c
    +
    +   see qh-poly.htm, libqhull.h and poly.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/poly.h#3 $$Change: 2047 $
    +   $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFpoly
    +#define qhDEFpoly 1
    +
    +#include "libqhull.h"
    +
    +/*===============   constants ========================== */
    +
    +/*----------------------------------
    +
    +  ALGORITHMfault
    +    use as argument to checkconvex() to report errors during buildhull
    +*/
    +#define qh_ALGORITHMfault 0
    +
    +/*----------------------------------
    +
    +  DATAfault
    +    use as argument to checkconvex() to report errors during initialhull
    +*/
    +#define qh_DATAfault 1
    +
    +/*----------------------------------
    +
    +  DUPLICATEridge
    +    special value for facet->neighbor to indicate a duplicate ridge
    +
    +  notes:
    +    set by matchneighbor, used by matchmatch and mark_dupridge
    +*/
    +#define qh_DUPLICATEridge (facetT *)1L
    +
    +/*----------------------------------
    +
    +  MERGEridge       flag in facet
    +    special value for facet->neighbor to indicate a merged ridge
    +
    +  notes:
    +    set by matchneighbor, used by matchmatch and mark_dupridge
    +*/
    +#define qh_MERGEridge (facetT *)2L
    +
    +
    +/*============ -structures- ====================*/
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  FORALLfacet_( facetlist ) { ... }
    +    assign 'facet' to each facet in facetlist
    +
    +  notes:
    +    uses 'facetT *facet;'
    +    assumes last facet is a sentinel
    +
    +  see:
    +    FORALLfacets
    +*/
    +#define FORALLfacet_( facetlist ) if (facetlist ) for ( facet=( facetlist ); facet && facet->next; facet= facet->next )
    +
    +/*----------------------------------
    +
    +  FORALLnew_facets { ... }
    +    assign 'newfacet' to each facet in qh.newfacet_list
    +
    +  notes:
    +    uses 'facetT *newfacet;'
    +    at exit, newfacet==NULL
    +*/
    +#define FORALLnew_facets for ( newfacet=qh newfacet_list;newfacet && newfacet->next;newfacet=newfacet->next )
    +
    +/*----------------------------------
    +
    +  FORALLvertex_( vertexlist ) { ... }
    +    assign 'vertex' to each vertex in vertexlist
    +
    +  notes:
    +    uses 'vertexT *vertex;'
    +    at exit, vertex==NULL
    +*/
    +#define FORALLvertex_( vertexlist ) for (vertex=( vertexlist );vertex && vertex->next;vertex= vertex->next )
    +
    +/*----------------------------------
    +
    +  FORALLvisible_facets { ... }
    +    assign 'visible' to each visible facet in qh.visible_list
    +
    +  notes:
    +    uses 'vacetT *visible;'
    +    at exit, visible==NULL
    +*/
    +#define FORALLvisible_facets for (visible=qh visible_list; visible && visible->visible; visible= visible->next)
    +
    +/*----------------------------------
    +
    +  FORALLsame_( newfacet ) { ... }
    +    assign 'same' to each facet in newfacet->f.samecycle
    +
    +  notes:
    +    uses 'facetT *same;'
    +    stops when it returns to newfacet
    +*/
    +#define FORALLsame_(newfacet) for (same= newfacet->f.samecycle; same != newfacet; same= same->f.samecycle)
    +
    +/*----------------------------------
    +
    +  FORALLsame_cycle_( newfacet ) { ... }
    +    assign 'same' to each facet in newfacet->f.samecycle
    +
    +  notes:
    +    uses 'facetT *same;'
    +    at exit, same == NULL
    +*/
    +#define FORALLsame_cycle_(newfacet) \
    +     for (same= newfacet->f.samecycle; \
    +         same; same= (same == newfacet ?  NULL : same->f.samecycle))
    +
    +/*----------------------------------
    +
    +  FOREACHneighborA_( facet ) { ... }
    +    assign 'neighborA' to each neighbor in facet->neighbors
    +
    +  FOREACHneighborA_( vertex ) { ... }
    +    assign 'neighborA' to each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighborA, **neighborAp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHneighborA_(facet)  FOREACHsetelement_(facetT, facet->neighbors, neighborA)
    +
    +/*----------------------------------
    +
    +  FOREACHvisible_( facets ) { ... }
    +    assign 'visible' to each facet in facets
    +
    +  notes:
    +    uses 'facetT *facet, *facetp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvisible_(facets) FOREACHsetelement_(facetT, facets, visible)
    +
    +/*----------------------------------
    +
    +  FOREACHnewfacet_( facets ) { ... }
    +    assign 'newfacet' to each facet in facets
    +
    +  notes:
    +    uses 'facetT *newfacet, *newfacetp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHnewfacet_(facets) FOREACHsetelement_(facetT, facets, newfacet)
    +
    +/*----------------------------------
    +
    +  FOREACHvertexA_( vertices ) { ... }
    +    assign 'vertexA' to each vertex in vertices
    +
    +  notes:
    +    uses 'vertexT *vertexA, *vertexAp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvertexA_(vertices) FOREACHsetelement_(vertexT, vertices, vertexA)
    +
    +/*----------------------------------
    +
    +  FOREACHvertexreverse12_( vertices ) { ... }
    +    assign 'vertex' to each vertex in vertices
    +    reverse order of first two vertices
    +
    +  notes:
    +    uses 'vertexT *vertex, *vertexp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvertexreverse12_(vertices) FOREACHsetelementreverse12_(vertexT, vertices, vertex)
    +
    +
    +/*=============== prototypes poly.c in alphabetical order ================*/
    +
    +void    qh_appendfacet(facetT *facet);
    +void    qh_appendvertex(vertexT *vertex);
    +void    qh_attachnewfacets(void /* qh.visible_list, qh.newfacet_list */);
    +boolT   qh_checkflipped(facetT *facet, realT *dist, boolT allerror);
    +void    qh_delfacet(facetT *facet);
    +void    qh_deletevisible(void /*qh.visible_list, qh.horizon_list*/);
    +setT   *qh_facetintersect(facetT *facetA, facetT *facetB, int *skipAp,int *skipBp, int extra);
    +int     qh_gethash(int hashsize, setT *set, int size, int firstindex, void *skipelem);
    +facetT *qh_makenewfacet(setT *vertices, boolT toporient, facetT *facet);
    +void    qh_makenewplanes(void /* newfacet_list */);
    +facetT *qh_makenew_nonsimplicial(facetT *visible, vertexT *apex, int *numnew);
    +facetT *qh_makenew_simplicial(facetT *visible, vertexT *apex, int *numnew);
    +void    qh_matchneighbor(facetT *newfacet, int newskip, int hashsize,
    +                          int *hashcount);
    +void    qh_matchnewfacets(void);
    +boolT   qh_matchvertices(int firstindex, setT *verticesA, int skipA,
    +                          setT *verticesB, int *skipB, boolT *same);
    +facetT *qh_newfacet(void);
    +ridgeT *qh_newridge(void);
    +int     qh_pointid(pointT *point);
    +void    qh_removefacet(facetT *facet);
    +void    qh_removevertex(vertexT *vertex);
    +void    qh_updatevertices(void);
    +
    +
    +/*========== -prototypes poly2.c in alphabetical order ===========*/
    +
    +void    qh_addhash(void* newelem, setT *hashtable, int hashsize, int hash);
    +void    qh_check_bestdist(void);
    +void    qh_check_dupridge(facetT *facet1, realT dist1, facetT *facet2, realT dist2);
    +void    qh_check_maxout(void);
    +void    qh_check_output(void);
    +void    qh_check_point(pointT *point, facetT *facet, realT *maxoutside, realT *maxdist, facetT **errfacet1, facetT **errfacet2);
    +void    qh_check_points(void);
    +void    qh_checkconvex(facetT *facetlist, int fault);
    +void    qh_checkfacet(facetT *facet, boolT newmerge, boolT *waserrorp);
    +void    qh_checkflipped_all(facetT *facetlist);
    +void    qh_checkpolygon(facetT *facetlist);
    +void    qh_checkvertex(vertexT *vertex);
    +void    qh_clearcenters(qh_CENTER type);
    +void    qh_createsimplex(setT *vertices);
    +void    qh_delridge(ridgeT *ridge);
    +void    qh_delvertex(vertexT *vertex);
    +setT   *qh_facet3vertex(facetT *facet);
    +facetT *qh_findbestfacet(pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside);
    +facetT *qh_findbestlower(facetT *upperfacet, pointT *point, realT *bestdistp, int *numpart);
    +facetT *qh_findfacet_all(pointT *point, realT *bestdist, boolT *isoutside,
    +                          int *numpart);
    +int     qh_findgood(facetT *facetlist, int goodhorizon);
    +void    qh_findgood_all(facetT *facetlist);
    +void    qh_furthestnext(void /* qh.facet_list */);
    +void    qh_furthestout(facetT *facet);
    +void    qh_infiniteloop(facetT *facet);
    +void    qh_initbuild(void);
    +void    qh_initialhull(setT *vertices);
    +setT   *qh_initialvertices(int dim, setT *maxpoints, pointT *points, int numpoints);
    +vertexT *qh_isvertex(pointT *point, setT *vertices);
    +vertexT *qh_makenewfacets(pointT *point /*horizon_list, visible_list*/);
    +void    qh_matchduplicates(facetT *atfacet, int atskip, int hashsize, int *hashcount);
    +void    qh_nearcoplanar(void /* qh.facet_list */);
    +vertexT *qh_nearvertex(facetT *facet, pointT *point, realT *bestdistp);
    +int     qh_newhashtable(int newsize);
    +vertexT *qh_newvertex(pointT *point);
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp);
    +void    qh_outcoplanar(void /* facet_list */);
    +pointT *qh_point(int id);
    +void    qh_point_add(setT *set, pointT *point, void *elem);
    +setT   *qh_pointfacet(void /*qh.facet_list*/);
    +setT   *qh_pointvertex(void /*qh.facet_list*/);
    +void    qh_prependfacet(facetT *facet, facetT **facetlist);
    +void    qh_printhashtable(FILE *fp);
    +void    qh_printlists(void);
    +void    qh_resetlists(boolT stats, boolT resetVisible /*qh.newvertex_list qh.newfacet_list qh.visible_list*/);
    +void    qh_setvoronoi_all(void);
    +void    qh_triangulate(void /*qh.facet_list*/);
    +void    qh_triangulate_facet(facetT *facetA, vertexT **first_vertex);
    +void    qh_triangulate_link(facetT *oldfacetA, facetT *facetA, facetT *oldfacetB, facetT *facetB);
    +void    qh_triangulate_mirror(facetT *facetA, facetT *facetB);
    +void    qh_triangulate_null(facetT *facetA);
    +void    qh_vertexintersect(setT **vertexsetA,setT *vertexsetB);
    +setT   *qh_vertexintersect_new(setT *vertexsetA,setT *vertexsetB);
    +void    qh_vertexneighbors(void /*qh.facet_list*/);
    +boolT   qh_vertexsubset(setT *vertexsetA, setT *vertexsetB);
    +
    +
    +#endif /* qhDEFpoly */
    diff --git a/xs/src/qhull/src/libqhull/poly2.c b/xs/src/qhull/src/libqhull/poly2.c
    new file mode 100644
    index 0000000000..de3e6ad0bb
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/poly2.c
    @@ -0,0 +1,3222 @@
    +/*
      ---------------------------------
    +
    +   poly2.c
    +   implements polygons and simplices
    +
    +   see qh-poly.htm, poly.h and libqhull.h
    +
    +   frequently used code is in poly.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/poly2.c#11 $$Change: 2069 $
    +   $DateTime: 2016/01/18 22:05:03 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*======== functions in alphabetical order ==========*/
    +
    +/*---------------------------------
    +
    +  qh_addhash( newelem, hashtable, hashsize, hash )
    +    add newelem to linear hash table at hash if not already there
    +*/
    +void qh_addhash(void* newelem, setT *hashtable, int hashsize, int hash) {
    +  int scan;
    +  void *elem;
    +
    +  for (scan= (int)hash; (elem= SETelem_(hashtable, scan));
    +       scan= (++scan >= hashsize ? 0 : scan)) {
    +    if (elem == newelem)
    +      break;
    +  }
    +  /* loop terminates because qh_HASHfactor >= 1.1 by qh_initbuffers */
    +  if (!elem)
    +    SETelem_(hashtable, scan)= newelem;
    +} /* addhash */
    +
    +/*---------------------------------
    +
    +  qh_check_bestdist()
    +    check that all points are within max_outside of the nearest facet
    +    if qh.ONLYgood,
    +      ignores !good facets
    +
    +  see:
    +    qh_check_maxout(), qh_outerinner()
    +
    +  notes:
    +    only called from qh_check_points()
    +      seldom used since qh.MERGING is almost always set
    +    if notverified>0 at end of routine
    +      some points were well inside the hull.  If the hull contains
    +      a lens-shaped component, these points were not verified.  Use
    +      options 'Qi Tv' to verify all points.  (Exhaustive check also verifies)
    +
    +  design:
    +    determine facet for each point (if any)
    +    for each point
    +      start with the assigned facet or with the first facet
    +      find the best facet for the point and check all coplanar facets
    +      error if point is outside of facet
    +*/
    +void qh_check_bestdist(void) {
    +  boolT waserror= False, unassigned;
    +  facetT *facet, *bestfacet, *errfacet1= NULL, *errfacet2= NULL;
    +  facetT *facetlist;
    +  realT dist, maxoutside, maxdist= -REALmax;
    +  pointT *point;
    +  int numpart= 0, facet_i, facet_n, notgood= 0, notverified= 0;
    +  setT *facets;
    +
    +  trace1((qh ferr, 1020, "qh_check_bestdist: check points below nearest facet.  Facet_list f%d\n",
    +      qh facet_list->id));
    +  maxoutside= qh_maxouter();
    +  maxoutside += qh DISTround;
    +  /* one more qh.DISTround for check computation */
    +  trace1((qh ferr, 1021, "qh_check_bestdist: check that all points are within %2.2g of best facet\n", maxoutside));
    +  facets= qh_pointfacet(/*qh.facet_list*/);
    +  if (!qh_QUICKhelp && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 8091, "\n\
    +qhull output completed.  Verifying that %d points are\n\
    +below %2.2g of the nearest %sfacet.\n",
    +             qh_setsize(facets), maxoutside, (qh ONLYgood ?  "good " : ""));
    +  FOREACHfacet_i_(facets) {  /* for each point with facet assignment */
    +    if (facet)
    +      unassigned= False;
    +    else {
    +      unassigned= True;
    +      facet= qh facet_list;
    +    }
    +    point= qh_point(facet_i);
    +    if (point == qh GOODpointp)
    +      continue;
    +    qh_distplane(point, facet, &dist);
    +    numpart++;
    +    bestfacet= qh_findbesthorizon(!qh_IScheckmax, point, facet, qh_NOupper, &dist, &numpart);
    +    /* occurs after statistics reported */
    +    maximize_(maxdist, dist);
    +    if (dist > maxoutside) {
    +      if (qh ONLYgood && !bestfacet->good
    +          && !((bestfacet= qh_findgooddist(point, bestfacet, &dist, &facetlist))
    +               && dist > maxoutside))
    +        notgood++;
    +      else {
    +        waserror= True;
    +        qh_fprintf(qh ferr, 6109, "qhull precision error: point p%d is outside facet f%d, distance= %6.8g maxoutside= %6.8g\n",
    +                facet_i, bestfacet->id, dist, maxoutside);
    +        if (errfacet1 != bestfacet) {
    +          errfacet2= errfacet1;
    +          errfacet1= bestfacet;
    +        }
    +      }
    +    }else if (unassigned && dist < -qh MAXcoplanar)
    +      notverified++;
    +  }
    +  qh_settempfree(&facets);
    +  if (notverified && !qh DELAUNAY && !qh_QUICKhelp && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 8092, "\n%d points were well inside the hull.  If the hull contains\n\
    +a lens-shaped component, these points were not verified.  Use\n\
    +options 'Qci Tv' to verify all points.\n", notverified);
    +  if (maxdist > qh outside_err) {
    +    qh_fprintf(qh ferr, 6110, "qhull precision error (qh_check_bestdist): a coplanar point is %6.2g from convex hull.  The maximum value(qh.outside_err) is %6.2g\n",
    +              maxdist, qh outside_err);
    +    qh_errexit2(qh_ERRprec, errfacet1, errfacet2);
    +  }else if (waserror && qh outside_err > REALmax/2)
    +    qh_errexit2(qh_ERRprec, errfacet1, errfacet2);
    +  /* else if waserror, the error was logged to qh.ferr but does not effect the output */
    +  trace0((qh ferr, 20, "qh_check_bestdist: max distance outside %2.2g\n", maxdist));
    +} /* check_bestdist */
    +
    +/*---------------------------------
    +
    +  qh_check_dupridge(facet1, dist1, facet2, dist2)
    +    Check duplicate ridge between facet1 and facet2 for wide merge
    +    dist1 is the maximum distance of facet1's vertices to facet2
    +    dist2 is the maximum distance of facet2's vertices to facet1
    +
    +  Returns
    +    Level 1 log of the duplicate ridge with the minimum distance between vertices
    +    Throws error if the merge will increase the maximum facet width by qh_WIDEduplicate (100x)
    +
    +  called from:
    +    qh_forcedmerges()
    +*/
    +#ifndef qh_NOmerge
    +void qh_check_dupridge(facetT *facet1, realT dist1, facetT *facet2, realT dist2) {
    +  vertexT *vertex, **vertexp, *vertexA, **vertexAp;
    +  realT dist, innerplane, mergedist, outerplane, prevdist, ratio;
    +  realT minvertex= REALmax;
    +
    +  mergedist= fmin_(dist1, dist2);
    +  qh_outerinner(NULL, &outerplane, &innerplane);  /* ratio from qh_printsummary */
    +  prevdist= fmax_(outerplane, innerplane);
    +  maximize_(prevdist, qh ONEmerge + qh DISTround);
    +  maximize_(prevdist, qh MINoutside + qh DISTround);
    +  ratio= mergedist/prevdist;
    +  FOREACHvertex_(facet1->vertices) {     /* The duplicate ridge is between facet1 and facet2, so either facet can be tested */
    +    FOREACHvertexA_(facet1->vertices) {
    +      if (vertex > vertexA){   /* Test each pair once */
    +        dist= qh_pointdist(vertex->point, vertexA->point, qh hull_dim);
    +        minimize_(minvertex, dist);
    +      }
    +    }
    +  }
    +  trace0((qh ferr, 16, "qh_check_dupridge: duplicate ridge between f%d and f%d due to nearly-coincident vertices (%2.2g), dist %2.2g, reverse dist %2.2g, ratio %2.2g while processing p%d\n",
    +        facet1->id, facet2->id, minvertex, dist1, dist2, ratio, qh furthest_id));
    +  if (ratio > qh_WIDEduplicate) {
    +    qh_fprintf(qh ferr, 6271, "qhull precision error (qh_check_dupridge): wide merge (%.0f times wider) due to duplicate ridge with nearly coincident points (%2.2g) between f%d and f%d, merge dist %2.2g, while processing p%d\n- Ignore error with option 'Q12'\n- To be fixed in a later version of Qhull\n",
    +          ratio, minvertex, facet1->id, facet2->id, mergedist, qh furthest_id);
    +    if (qh DELAUNAY)
    +      qh_fprintf(qh ferr, 8145, "- A bounding box for the input sites may alleviate this error.\n");
    +    if(minvertex > qh_WIDEduplicate*prevdist)
    +      qh_fprintf(qh ferr, 8146, "- Vertex distance %2.2g is greater than %d times maximum distance %2.2g\n  Please report to bradb@shore.net with steps to reproduce and all output\n",
    +          minvertex, qh_WIDEduplicate, prevdist);
    +    if (!qh NOwide)
    +      qh_errexit2(qh_ERRqhull, facet1, facet2);
    +  }
    +} /* check_dupridge */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_check_maxout()
    +    updates qh.max_outside by checking all points against bestfacet
    +    if qh.ONLYgood, ignores !good facets
    +
    +  returns:
    +    updates facet->maxoutside via qh_findbesthorizon()
    +    sets qh.maxoutdone
    +    if printing qh.min_vertex (qh_outerinner),
    +      it is updated to the current vertices
    +    removes inside/coplanar points from coplanarset as needed
    +
    +  notes:
    +    defines coplanar as min_vertex instead of MAXcoplanar
    +    may not need to check near-inside points because of qh.MAXcoplanar
    +      and qh.KEEPnearinside (before it was -DISTround)
    +
    +  see also:
    +    qh_check_bestdist()
    +
    +  design:
    +    if qh.min_vertex is needed
    +      for all neighbors of all vertices
    +        test distance from vertex to neighbor
    +    determine facet for each point (if any)
    +    for each point with an assigned facet
    +      find the best facet for the point and check all coplanar facets
    +        (updates outer planes)
    +    remove near-inside points from coplanar sets
    +*/
    +#ifndef qh_NOmerge
    +void qh_check_maxout(void) {
    +  facetT *facet, *bestfacet, *neighbor, **neighborp, *facetlist;
    +  realT dist, maxoutside, minvertex, old_maxoutside;
    +  pointT *point;
    +  int numpart= 0, facet_i, facet_n, notgood= 0;
    +  setT *facets, *vertices;
    +  vertexT *vertex;
    +
    +  trace1((qh ferr, 1022, "qh_check_maxout: check and update maxoutside for each facet.\n"));
    +  maxoutside= minvertex= 0;
    +  if (qh VERTEXneighbors
    +  && (qh PRINTsummary || qh KEEPinside || qh KEEPcoplanar
    +        || qh TRACElevel || qh PRINTstatistics
    +        || qh PRINTout[0] == qh_PRINTsummary || qh PRINTout[0] == qh_PRINTnone)) {
    +    trace1((qh ferr, 1023, "qh_check_maxout: determine actual maxoutside and minvertex\n"));
    +    vertices= qh_pointvertex(/*qh.facet_list*/);
    +    FORALLvertices {
    +      FOREACHneighbor_(vertex) {
    +        zinc_(Zdistvertex);  /* distance also computed by main loop below */
    +        qh_distplane(vertex->point, neighbor, &dist);
    +        minimize_(minvertex, dist);
    +        if (-dist > qh TRACEdist || dist > qh TRACEdist
    +        || neighbor == qh tracefacet || vertex == qh tracevertex)
    +          qh_fprintf(qh ferr, 8093, "qh_check_maxout: p%d(v%d) is %.2g from f%d\n",
    +                    qh_pointid(vertex->point), vertex->id, dist, neighbor->id);
    +      }
    +    }
    +    if (qh MERGING) {
    +      wmin_(Wminvertex, qh min_vertex);
    +    }
    +    qh min_vertex= minvertex;
    +    qh_settempfree(&vertices);
    +  }
    +  facets= qh_pointfacet(/*qh.facet_list*/);
    +  do {
    +    old_maxoutside= fmax_(qh max_outside, maxoutside);
    +    FOREACHfacet_i_(facets) {     /* for each point with facet assignment */
    +      if (facet) {
    +        point= qh_point(facet_i);
    +        if (point == qh GOODpointp)
    +          continue;
    +        zzinc_(Ztotcheck);
    +        qh_distplane(point, facet, &dist);
    +        numpart++;
    +        bestfacet= qh_findbesthorizon(qh_IScheckmax, point, facet, !qh_NOupper, &dist, &numpart);
    +        if (bestfacet && dist > maxoutside) {
    +          if (qh ONLYgood && !bestfacet->good
    +          && !((bestfacet= qh_findgooddist(point, bestfacet, &dist, &facetlist))
    +               && dist > maxoutside))
    +            notgood++;
    +          else
    +            maxoutside= dist;
    +        }
    +        if (dist > qh TRACEdist || (bestfacet && bestfacet == qh tracefacet))
    +          qh_fprintf(qh ferr, 8094, "qh_check_maxout: p%d is %.2g above f%d\n",
    +                     qh_pointid(point), dist, (bestfacet ? bestfacet->id : UINT_MAX));
    +      }
    +    }
    +  }while
    +    (maxoutside > 2*old_maxoutside);
    +    /* if qh.maxoutside increases substantially, qh_SEARCHdist is not valid
    +          e.g., RBOX 5000 s Z1 G1e-13 t1001200614 | qhull */
    +  zzadd_(Zcheckpart, numpart);
    +  qh_settempfree(&facets);
    +  wval_(Wmaxout)= maxoutside - qh max_outside;
    +  wmax_(Wmaxoutside, qh max_outside);
    +  qh max_outside= maxoutside;
    +  qh_nearcoplanar(/*qh.facet_list*/);
    +  qh maxoutdone= True;
    +  trace1((qh ferr, 1024, "qh_check_maxout: maxoutside %2.2g, min_vertex %2.2g, outside of not good %d\n",
    +       maxoutside, qh min_vertex, notgood));
    +} /* check_maxout */
    +#else /* qh_NOmerge */
    +void qh_check_maxout(void) {
    +}
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_check_output()
    +    performs the checks at the end of qhull algorithm
    +    Maybe called after voronoi output.  Will recompute otherwise centrums are Voronoi centers instead
    +*/
    +void qh_check_output(void) {
    +  int i;
    +
    +  if (qh STOPcone)
    +    return;
    +  if (qh VERIFYoutput | qh IStracing | qh CHECKfrequently) {
    +    qh_checkpolygon(qh facet_list);
    +    qh_checkflipped_all(qh facet_list);
    +    qh_checkconvex(qh facet_list, qh_ALGORITHMfault);
    +  }else if (!qh MERGING && qh_newstats(qhstat precision, &i)) {
    +    qh_checkflipped_all(qh facet_list);
    +    qh_checkconvex(qh facet_list, qh_ALGORITHMfault);
    +  }
    +} /* check_output */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_check_point( point, facet, maxoutside, maxdist, errfacet1, errfacet2 )
    +    check that point is less than maxoutside from facet
    +*/
    +void qh_check_point(pointT *point, facetT *facet, realT *maxoutside, realT *maxdist, facetT **errfacet1, facetT **errfacet2) {
    +  realT dist;
    +
    +  /* occurs after statistics reported */
    +  qh_distplane(point, facet, &dist);
    +  if (dist > *maxoutside) {
    +    if (*errfacet1 != facet) {
    +      *errfacet2= *errfacet1;
    +      *errfacet1= facet;
    +    }
    +    qh_fprintf(qh ferr, 6111, "qhull precision error: point p%d is outside facet f%d, distance= %6.8g maxoutside= %6.8g\n",
    +              qh_pointid(point), facet->id, dist, *maxoutside);
    +  }
    +  maximize_(*maxdist, dist);
    +} /* qh_check_point */
    +
    +
    +/*---------------------------------
    +
    +  qh_check_points()
    +    checks that all points are inside all facets
    +
    +  notes:
    +    if many points and qh_check_maxout not called (i.e., !qh.MERGING),
    +       calls qh_findbesthorizon (seldom done).
    +    ignores flipped facets
    +    maxoutside includes 2 qh.DISTrounds
    +      one qh.DISTround for the computed distances in qh_check_points
    +    qh_printafacet and qh_printsummary needs only one qh.DISTround
    +    the computation for qh.VERIFYdirect does not account for qh.other_points
    +
    +  design:
    +    if many points
    +      use qh_check_bestdist()
    +    else
    +      for all facets
    +        for all points
    +          check that point is inside facet
    +*/
    +void qh_check_points(void) {
    +  facetT *facet, *errfacet1= NULL, *errfacet2= NULL;
    +  realT total, maxoutside, maxdist= -REALmax;
    +  pointT *point, **pointp, *pointtemp;
    +  boolT testouter;
    +
    +  maxoutside= qh_maxouter();
    +  maxoutside += qh DISTround;
    +  /* one more qh.DISTround for check computation */
    +  trace1((qh ferr, 1025, "qh_check_points: check all points below %2.2g of all facet planes\n",
    +          maxoutside));
    +  if (qh num_good)   /* miss counts other_points and !good facets */
    +     total= (float)qh num_good * (float)qh num_points;
    +  else
    +     total= (float)qh num_facets * (float)qh num_points;
    +  if (total >= qh_VERIFYdirect && !qh maxoutdone) {
    +    if (!qh_QUICKhelp && qh SKIPcheckmax && qh MERGING)
    +      qh_fprintf(qh ferr, 7075, "qhull input warning: merging without checking outer planes('Q5' or 'Po').\n\
    +Verify may report that a point is outside of a facet.\n");
    +    qh_check_bestdist();
    +  }else {
    +    if (qh_MAXoutside && qh maxoutdone)
    +      testouter= True;
    +    else
    +      testouter= False;
    +    if (!qh_QUICKhelp) {
    +      if (qh MERGEexact)
    +        qh_fprintf(qh ferr, 7076, "qhull input warning: exact merge ('Qx').  Verify may report that a point\n\
    +is outside of a facet.  See qh-optq.htm#Qx\n");
    +      else if (qh SKIPcheckmax || qh NOnearinside)
    +        qh_fprintf(qh ferr, 7077, "qhull input warning: no outer plane check ('Q5') or no processing of\n\
    +near-inside points ('Q8').  Verify may report that a point is outside\n\
    +of a facet.\n");
    +    }
    +    if (qh PRINTprecision) {
    +      if (testouter)
    +        qh_fprintf(qh ferr, 8098, "\n\
    +Output completed.  Verifying that all points are below outer planes of\n\
    +all %sfacets.  Will make %2.0f distance computations.\n",
    +              (qh ONLYgood ?  "good " : ""), total);
    +      else
    +        qh_fprintf(qh ferr, 8099, "\n\
    +Output completed.  Verifying that all points are below %2.2g of\n\
    +all %sfacets.  Will make %2.0f distance computations.\n",
    +              maxoutside, (qh ONLYgood ?  "good " : ""), total);
    +    }
    +    FORALLfacets {
    +      if (!facet->good && qh ONLYgood)
    +        continue;
    +      if (facet->flipped)
    +        continue;
    +      if (!facet->normal) {
    +        qh_fprintf(qh ferr, 7061, "qhull warning (qh_check_points): missing normal for facet f%d\n", facet->id);
    +        continue;
    +      }
    +      if (testouter) {
    +#if qh_MAXoutside
    +        maxoutside= facet->maxoutside + 2* qh DISTround;
    +        /* one DISTround to actual point and another to computed point */
    +#endif
    +      }
    +      FORALLpoints {
    +        if (point != qh GOODpointp)
    +          qh_check_point(point, facet, &maxoutside, &maxdist, &errfacet1, &errfacet2);
    +      }
    +      FOREACHpoint_(qh other_points) {
    +        if (point != qh GOODpointp)
    +          qh_check_point(point, facet, &maxoutside, &maxdist, &errfacet1, &errfacet2);
    +      }
    +    }
    +    if (maxdist > qh outside_err) {
    +      qh_fprintf(qh ferr, 6112, "qhull precision error (qh_check_points): a coplanar point is %6.2g from convex hull.  The maximum value(qh.outside_err) is %6.2g\n",
    +                maxdist, qh outside_err );
    +      qh_errexit2( qh_ERRprec, errfacet1, errfacet2 );
    +    }else if (errfacet1 && qh outside_err > REALmax/2)
    +        qh_errexit2( qh_ERRprec, errfacet1, errfacet2 );
    +    /* else if errfacet1, the error was logged to qh.ferr but does not effect the output */
    +    trace0((qh ferr, 21, "qh_check_points: max distance outside %2.2g\n", maxdist));
    +  }
    +} /* check_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkconvex( facetlist, fault )
    +    check that each ridge in facetlist is convex
    +    fault = qh_DATAfault if reporting errors
    +          = qh_ALGORITHMfault otherwise
    +
    +  returns:
    +    counts Zconcaveridges and Zcoplanarridges
    +    errors if concaveridge or if merging an coplanar ridge
    +
    +  note:
    +    if not merging,
    +      tests vertices for neighboring simplicial facets
    +    else if ZEROcentrum,
    +      tests vertices for neighboring simplicial   facets
    +    else
    +      tests centrums of neighboring facets
    +
    +  design:
    +    for all facets
    +      report flipped facets
    +      if ZEROcentrum and simplicial neighbors
    +        test vertices for neighboring simplicial facets
    +      else
    +        test centrum against all neighbors
    +*/
    +void qh_checkconvex(facetT *facetlist, int fault) {
    +  facetT *facet, *neighbor, **neighborp, *errfacet1=NULL, *errfacet2=NULL;
    +  vertexT *vertex;
    +  realT dist;
    +  pointT *centrum;
    +  boolT waserror= False, centrum_warning= False, tempcentrum= False, allsimplicial;
    +  int neighbor_i;
    +
    +  trace1((qh ferr, 1026, "qh_checkconvex: check all ridges are convex\n"));
    +  if (!qh RERUN) {
    +    zzval_(Zconcaveridges)= 0;
    +    zzval_(Zcoplanarridges)= 0;
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->flipped) {
    +      qh_precision("flipped facet");
    +      qh_fprintf(qh ferr, 6113, "qhull precision error: f%d is flipped(interior point is outside)\n",
    +               facet->id);
    +      errfacet1= facet;
    +      waserror= True;
    +      continue;
    +    }
    +    if (qh MERGING && (!qh ZEROcentrum || !facet->simplicial || facet->tricoplanar))
    +      allsimplicial= False;
    +    else {
    +      allsimplicial= True;
    +      neighbor_i= 0;
    +      FOREACHneighbor_(facet) {
    +        vertex= SETelemt_(facet->vertices, neighbor_i++, vertexT);
    +        if (!neighbor->simplicial || neighbor->tricoplanar) {
    +          allsimplicial= False;
    +          continue;
    +        }
    +        qh_distplane(vertex->point, neighbor, &dist);
    +        if (dist > -qh DISTround) {
    +          if (fault == qh_DATAfault) {
    +            qh_precision("coplanar or concave ridge");
    +            qh_fprintf(qh ferr, 6114, "qhull precision error: initial simplex is not convex. Distance=%.2g\n", dist);
    +            qh_errexit(qh_ERRsingular, NULL, NULL);
    +          }
    +          if (dist > qh DISTround) {
    +            zzinc_(Zconcaveridges);
    +            qh_precision("concave ridge");
    +            qh_fprintf(qh ferr, 6115, "qhull precision error: f%d is concave to f%d, since p%d(v%d) is %6.4g above\n",
    +              facet->id, neighbor->id, qh_pointid(vertex->point), vertex->id, dist);
    +            errfacet1= facet;
    +            errfacet2= neighbor;
    +            waserror= True;
    +          }else if (qh ZEROcentrum) {
    +            if (dist > 0) {     /* qh_checkzero checks that dist < - qh DISTround */
    +              zzinc_(Zcoplanarridges);
    +              qh_precision("coplanar ridge");
    +              qh_fprintf(qh ferr, 6116, "qhull precision error: f%d is clearly not convex to f%d, since p%d(v%d) is %6.4g above\n",
    +                facet->id, neighbor->id, qh_pointid(vertex->point), vertex->id, dist);
    +              errfacet1= facet;
    +              errfacet2= neighbor;
    +              waserror= True;
    +            }
    +          }else {
    +            zzinc_(Zcoplanarridges);
    +            qh_precision("coplanar ridge");
    +            trace0((qh ferr, 22, "qhull precision error: f%d may be coplanar to f%d, since p%d(v%d) is within %6.4g during p%d\n",
    +              facet->id, neighbor->id, qh_pointid(vertex->point), vertex->id, dist, qh furthest_id));
    +          }
    +        }
    +      }
    +    }
    +    if (!allsimplicial) {
    +      if (qh CENTERtype == qh_AScentrum) {
    +        if (!facet->center)
    +          facet->center= qh_getcentrum(facet);
    +        centrum= facet->center;
    +      }else {
    +        if (!centrum_warning && (!facet->simplicial || facet->tricoplanar)) {
    +           centrum_warning= True;
    +           qh_fprintf(qh ferr, 7062, "qhull warning: recomputing centrums for convexity test.  This may lead to false, precision errors.\n");
    +        }
    +        centrum= qh_getcentrum(facet);
    +        tempcentrum= True;
    +      }
    +      FOREACHneighbor_(facet) {
    +        if (qh ZEROcentrum && facet->simplicial && neighbor->simplicial)
    +          continue;
    +        if (facet->tricoplanar || neighbor->tricoplanar)
    +          continue;
    +        zzinc_(Zdistconvex);
    +        qh_distplane(centrum, neighbor, &dist);
    +        if (dist > qh DISTround) {
    +          zzinc_(Zconcaveridges);
    +          qh_precision("concave ridge");
    +          qh_fprintf(qh ferr, 6117, "qhull precision error: f%d is concave to f%d.  Centrum of f%d is %6.4g above f%d\n",
    +            facet->id, neighbor->id, facet->id, dist, neighbor->id);
    +          errfacet1= facet;
    +          errfacet2= neighbor;
    +          waserror= True;
    +        }else if (dist >= 0.0) {   /* if arithmetic always rounds the same,
    +                                     can test against centrum radius instead */
    +          zzinc_(Zcoplanarridges);
    +          qh_precision("coplanar ridge");
    +          qh_fprintf(qh ferr, 6118, "qhull precision error: f%d is coplanar or concave to f%d.  Centrum of f%d is %6.4g above f%d\n",
    +            facet->id, neighbor->id, facet->id, dist, neighbor->id);
    +          errfacet1= facet;
    +          errfacet2= neighbor;
    +          waserror= True;
    +        }
    +      }
    +      if (tempcentrum)
    +        qh_memfree(centrum, qh normal_size);
    +    }
    +  }
    +  if (waserror && !qh FORCEoutput)
    +    qh_errexit2(qh_ERRprec, errfacet1, errfacet2);
    +} /* checkconvex */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkfacet( facet, newmerge, waserror )
    +    checks for consistency errors in facet
    +    newmerge set if from merge.c
    +
    +  returns:
    +    sets waserror if any error occurs
    +
    +  checks:
    +    vertex ids are inverse sorted
    +    unless newmerge, at least hull_dim neighbors and vertices (exactly if simplicial)
    +    if non-simplicial, at least as many ridges as neighbors
    +    neighbors are not duplicated
    +    ridges are not duplicated
    +    in 3-d, ridges=verticies
    +    (qh.hull_dim-1) ridge vertices
    +    neighbors are reciprocated
    +    ridge neighbors are facet neighbors and a ridge for every neighbor
    +    simplicial neighbors match facetintersect
    +    vertex intersection matches vertices of common ridges
    +    vertex neighbors and facet vertices agree
    +    all ridges have distinct vertex sets
    +
    +  notes:
    +    uses neighbor->seen
    +
    +  design:
    +    check sets
    +    check vertices
    +    check sizes of neighbors and vertices
    +    check for qh_MERGEridge and qh_DUPLICATEridge flags
    +    check neighbor set
    +    check ridge set
    +    check ridges, neighbors, and vertices
    +*/
    +void qh_checkfacet(facetT *facet, boolT newmerge, boolT *waserrorp) {
    +  facetT *neighbor, **neighborp, *errother=NULL;
    +  ridgeT *ridge, **ridgep, *errridge= NULL, *ridge2;
    +  vertexT *vertex, **vertexp;
    +  unsigned previousid= INT_MAX;
    +  int numneighbors, numvertices, numridges=0, numRvertices=0;
    +  boolT waserror= False;
    +  int skipA, skipB, ridge_i, ridge_n, i;
    +  setT *intersection;
    +
    +  if (facet->visible) {
    +    qh_fprintf(qh ferr, 6119, "qhull internal error (qh_checkfacet): facet f%d is on the visible_list\n",
    +      facet->id);
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  if (!facet->normal) {
    +    qh_fprintf(qh ferr, 6120, "qhull internal error (qh_checkfacet): facet f%d does not have  a normal\n",
    +      facet->id);
    +    waserror= True;
    +  }
    +  qh_setcheck(facet->vertices, "vertices for f", facet->id);
    +  qh_setcheck(facet->ridges, "ridges for f", facet->id);
    +  qh_setcheck(facet->outsideset, "outsideset for f", facet->id);
    +  qh_setcheck(facet->coplanarset, "coplanarset for f", facet->id);
    +  qh_setcheck(facet->neighbors, "neighbors for f", facet->id);
    +  FOREACHvertex_(facet->vertices) {
    +    if (vertex->deleted) {
    +      qh_fprintf(qh ferr, 6121, "qhull internal error (qh_checkfacet): deleted vertex v%d in f%d\n", vertex->id, facet->id);
    +      qh_errprint("ERRONEOUS", NULL, NULL, NULL, vertex);
    +      waserror= True;
    +    }
    +    if (vertex->id >= previousid) {
    +      qh_fprintf(qh ferr, 6122, "qhull internal error (qh_checkfacet): vertices of f%d are not in descending id order at v%d\n", facet->id, vertex->id);
    +      waserror= True;
    +      break;
    +    }
    +    previousid= vertex->id;
    +  }
    +  numneighbors= qh_setsize(facet->neighbors);
    +  numvertices= qh_setsize(facet->vertices);
    +  numridges= qh_setsize(facet->ridges);
    +  if (facet->simplicial) {
    +    if (numvertices+numneighbors != 2*qh hull_dim
    +    && !facet->degenerate && !facet->redundant) {
    +      qh_fprintf(qh ferr, 6123, "qhull internal error (qh_checkfacet): for simplicial facet f%d, #vertices %d + #neighbors %d != 2*qh hull_dim\n",
    +                facet->id, numvertices, numneighbors);
    +      qh_setprint(qh ferr, "", facet->neighbors);
    +      waserror= True;
    +    }
    +  }else { /* non-simplicial */
    +    if (!newmerge
    +    &&(numvertices < qh hull_dim || numneighbors < qh hull_dim)
    +    && !facet->degenerate && !facet->redundant) {
    +      qh_fprintf(qh ferr, 6124, "qhull internal error (qh_checkfacet): for facet f%d, #vertices %d or #neighbors %d < qh hull_dim\n",
    +         facet->id, numvertices, numneighbors);
    +       waserror= True;
    +    }
    +    /* in 3-d, can get a vertex twice in an edge list, e.g., RBOX 1000 s W1e-13 t995849315 D2 | QHULL d Tc Tv TP624 TW1e-13 T4 */
    +    if (numridges < numneighbors
    +    ||(qh hull_dim == 3 && numvertices > numridges && !qh NEWfacets)
    +    ||(qh hull_dim == 2 && numridges + numvertices + numneighbors != 6)) {
    +      if (!facet->degenerate && !facet->redundant) {
    +        qh_fprintf(qh ferr, 6125, "qhull internal error (qh_checkfacet): for facet f%d, #ridges %d < #neighbors %d or(3-d) > #vertices %d or(2-d) not all 2\n",
    +            facet->id, numridges, numneighbors, numvertices);
    +        waserror= True;
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge || neighbor == qh_DUPLICATEridge) {
    +      qh_fprintf(qh ferr, 6126, "qhull internal error (qh_checkfacet): facet f%d still has a MERGE or DUP neighbor\n", facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +    neighbor->seen= True;
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (!qh_setin(neighbor->neighbors, facet)) {
    +      qh_fprintf(qh ferr, 6127, "qhull internal error (qh_checkfacet): facet f%d has neighbor f%d, but f%d does not have neighbor f%d\n",
    +              facet->id, neighbor->id, neighbor->id, facet->id);
    +      errother= neighbor;
    +      waserror= True;
    +    }
    +    if (!neighbor->seen) {
    +      qh_fprintf(qh ferr, 6128, "qhull internal error (qh_checkfacet): facet f%d has a duplicate neighbor f%d\n",
    +              facet->id, neighbor->id);
    +      errother= neighbor;
    +      waserror= True;
    +    }
    +    neighbor->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges) {
    +    qh_setcheck(ridge->vertices, "vertices for r", ridge->id);
    +    ridge->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges) {
    +    if (ridge->seen) {
    +      qh_fprintf(qh ferr, 6129, "qhull internal error (qh_checkfacet): facet f%d has a duplicate ridge r%d\n",
    +              facet->id, ridge->id);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +    ridge->seen= True;
    +    numRvertices= qh_setsize(ridge->vertices);
    +    if (numRvertices != qh hull_dim - 1) {
    +      qh_fprintf(qh ferr, 6130, "qhull internal error (qh_checkfacet): ridge between f%d and f%d has %d vertices\n",
    +                ridge->top->id, ridge->bottom->id, numRvertices);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +    neighbor= otherfacet_(ridge, facet);
    +    neighbor->seen= True;
    +    if (!qh_setin(facet->neighbors, neighbor)) {
    +      qh_fprintf(qh ferr, 6131, "qhull internal error (qh_checkfacet): for facet f%d, neighbor f%d of ridge r%d not in facet\n",
    +           facet->id, neighbor->id, ridge->id);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +  }
    +  if (!facet->simplicial) {
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->seen) {
    +        qh_fprintf(qh ferr, 6132, "qhull internal error (qh_checkfacet): facet f%d does not have a ridge for neighbor f%d\n",
    +              facet->id, neighbor->id);
    +        errother= neighbor;
    +        waserror= True;
    +      }
    +      intersection= qh_vertexintersect_new(facet->vertices, neighbor->vertices);
    +      qh_settemppush(intersection);
    +      FOREACHvertex_(facet->vertices) {
    +        vertex->seen= False;
    +        vertex->seen2= False;
    +      }
    +      FOREACHvertex_(intersection)
    +        vertex->seen= True;
    +      FOREACHridge_(facet->ridges) {
    +        if (neighbor != otherfacet_(ridge, facet))
    +            continue;
    +        FOREACHvertex_(ridge->vertices) {
    +          if (!vertex->seen) {
    +            qh_fprintf(qh ferr, 6133, "qhull internal error (qh_checkfacet): vertex v%d in r%d not in f%d intersect f%d\n",
    +                  vertex->id, ridge->id, facet->id, neighbor->id);
    +            qh_errexit(qh_ERRqhull, facet, ridge);
    +          }
    +          vertex->seen2= True;
    +        }
    +      }
    +      if (!newmerge) {
    +        FOREACHvertex_(intersection) {
    +          if (!vertex->seen2) {
    +            if (qh IStracing >=3 || !qh MERGING) {
    +              qh_fprintf(qh ferr, 6134, "qhull precision error (qh_checkfacet): vertex v%d in f%d intersect f%d but\n\
    + not in a ridge.  This is ok under merging.  Last point was p%d\n",
    +                     vertex->id, facet->id, neighbor->id, qh furthest_id);
    +              if (!qh FORCEoutput && !qh MERGING) {
    +                qh_errprint("ERRONEOUS", facet, neighbor, NULL, vertex);
    +                if (!qh MERGING)
    +                  qh_errexit(qh_ERRqhull, NULL, NULL);
    +              }
    +            }
    +          }
    +        }
    +      }
    +      qh_settempfree(&intersection);
    +    }
    +  }else { /* simplicial */
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->simplicial) {
    +        skipA= SETindex_(facet->neighbors, neighbor);
    +        skipB= qh_setindex(neighbor->neighbors, facet);
    +        if (skipA<0 || skipB<0 || !qh_setequal_skip(facet->vertices, skipA, neighbor->vertices, skipB)) {
    +          qh_fprintf(qh ferr, 6135, "qhull internal error (qh_checkfacet): facet f%d skip %d and neighbor f%d skip %d do not match \n",
    +                   facet->id, skipA, neighbor->id, skipB);
    +          errother= neighbor;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (qh hull_dim < 5 && (qh IStracing > 2 || qh CHECKfrequently)) {
    +    FOREACHridge_i_(facet->ridges) {           /* expensive */
    +      for (i=ridge_i+1; i < ridge_n; i++) {
    +        ridge2= SETelemt_(facet->ridges, i, ridgeT);
    +        if (qh_setequal(ridge->vertices, ridge2->vertices)) {
    +          qh_fprintf(qh ferr, 6227, "Qhull internal error (qh_checkfacet): ridges r%d and r%d have the same vertices\n",
    +                  ridge->id, ridge2->id);
    +          errridge= ridge;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_errprint("ERRONEOUS", facet, errother, errridge, NULL);
    +    *waserrorp= True;
    +  }
    +} /* checkfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkflipped_all( facetlist )
    +    checks orientation of facets in list against interior point
    +*/
    +void qh_checkflipped_all(facetT *facetlist) {
    +  facetT *facet;
    +  boolT waserror= False;
    +  realT dist;
    +
    +  if (facetlist == qh facet_list)
    +    zzval_(Zflippedfacets)= 0;
    +  FORALLfacet_(facetlist) {
    +    if (facet->normal && !qh_checkflipped(facet, &dist, !qh_ALL)) {
    +      qh_fprintf(qh ferr, 6136, "qhull precision error: facet f%d is flipped, distance= %6.12g\n",
    +              facet->id, dist);
    +      if (!qh FORCEoutput) {
    +        qh_errprint("ERRONEOUS", facet, NULL, NULL, NULL);
    +        waserror= True;
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_fprintf(qh ferr, 8101, "\n\
    +A flipped facet occurs when its distance to the interior point is\n\
    +greater than %2.2g, the maximum roundoff error.\n", -qh DISTround);
    +    qh_errexit(qh_ERRprec, NULL, NULL);
    +  }
    +} /* checkflipped_all */
    +
    +/*---------------------------------
    +
    +  qh_checkpolygon( facetlist )
    +    checks the correctness of the structure
    +
    +  notes:
    +    call with either qh.facet_list or qh.newfacet_list
    +    checks num_facets and num_vertices if qh.facet_list
    +
    +  design:
    +    for each facet
    +      checks facet and outside set
    +    initializes vertexlist
    +    for each facet
    +      checks vertex set
    +    if checking all facets(qh.facetlist)
    +      check facet count
    +      if qh.VERTEXneighbors
    +        check vertex neighbors and count
    +      check vertex count
    +*/
    +void qh_checkpolygon(facetT *facetlist) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp, *vertexlist;
    +  int numfacets= 0, numvertices= 0, numridges= 0;
    +  int totvneighbors= 0, totvertices= 0;
    +  boolT waserror= False, nextseen= False, visibleseen= False;
    +
    +  trace1((qh ferr, 1027, "qh_checkpolygon: check all facets from f%d\n", facetlist->id));
    +  if (facetlist != qh facet_list || qh ONLYgood)
    +    nextseen= True;
    +  FORALLfacet_(facetlist) {
    +    if (facet == qh visible_list)
    +      visibleseen= True;
    +    if (!facet->visible) {
    +      if (!nextseen) {
    +        if (facet == qh facet_next)
    +          nextseen= True;
    +        else if (qh_setsize(facet->outsideset)) {
    +          if (!qh NARROWhull
    +#if !qh_COMPUTEfurthest
    +               || facet->furthestdist >= qh MINoutside
    +#endif
    +                        ) {
    +            qh_fprintf(qh ferr, 6137, "qhull internal error (qh_checkpolygon): f%d has outside points before qh facet_next\n",
    +                     facet->id);
    +            qh_errexit(qh_ERRqhull, facet, NULL);
    +          }
    +        }
    +      }
    +      numfacets++;
    +      qh_checkfacet(facet, False, &waserror);
    +    }
    +  }
    +  if (qh visible_list && !visibleseen && facetlist == qh facet_list) {
    +    qh_fprintf(qh ferr, 6138, "qhull internal error (qh_checkpolygon): visible list f%d no longer on facet list\n", qh visible_list->id);
    +    qh_printlists();
    +    qh_errexit(qh_ERRqhull, qh visible_list, NULL);
    +  }
    +  if (facetlist == qh facet_list)
    +    vertexlist= qh vertex_list;
    +  else if (facetlist == qh newfacet_list)
    +    vertexlist= qh newvertex_list;
    +  else
    +    vertexlist= NULL;
    +  FORALLvertex_(vertexlist) {
    +    vertex->seen= False;
    +    vertex->visitid= 0;
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->visible)
    +      continue;
    +    if (facet->simplicial)
    +      numridges += qh hull_dim;
    +    else
    +      numridges += qh_setsize(facet->ridges);
    +    FOREACHvertex_(facet->vertices) {
    +      vertex->visitid++;
    +      if (!vertex->seen) {
    +        vertex->seen= True;
    +        numvertices++;
    +        if (qh_pointid(vertex->point) == qh_IDunknown) {
    +          qh_fprintf(qh ferr, 6139, "qhull internal error (qh_checkpolygon): unknown point %p for vertex v%d first_point %p\n",
    +                   vertex->point, vertex->id, qh first_point);
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  qh vertex_visit += (unsigned int)numfacets;
    +  if (facetlist == qh facet_list) {
    +    if (numfacets != qh num_facets - qh num_visible) {
    +      qh_fprintf(qh ferr, 6140, "qhull internal error (qh_checkpolygon): actual number of facets is %d, cumulative facet count is %d - %d visible facets\n",
    +              numfacets, qh num_facets, qh num_visible);
    +      waserror= True;
    +    }
    +    qh vertex_visit++;
    +    if (qh VERTEXneighbors) {
    +      FORALLvertices {
    +        qh_setcheck(vertex->neighbors, "neighbors for v", vertex->id);
    +        if (vertex->deleted)
    +          continue;
    +        totvneighbors += qh_setsize(vertex->neighbors);
    +      }
    +      FORALLfacet_(facetlist)
    +        totvertices += qh_setsize(facet->vertices);
    +      if (totvneighbors != totvertices) {
    +        qh_fprintf(qh ferr, 6141, "qhull internal error (qh_checkpolygon): vertex neighbors inconsistent.  Totvneighbors %d, totvertices %d\n",
    +                totvneighbors, totvertices);
    +        waserror= True;
    +      }
    +    }
    +    if (numvertices != qh num_vertices - qh_setsize(qh del_vertices)) {
    +      qh_fprintf(qh ferr, 6142, "qhull internal error (qh_checkpolygon): actual number of vertices is %d, cumulative vertex count is %d\n",
    +              numvertices, qh num_vertices - qh_setsize(qh del_vertices));
    +      waserror= True;
    +    }
    +    if (qh hull_dim == 2 && numvertices != numfacets) {
    +      qh_fprintf(qh ferr, 6143, "qhull internal error (qh_checkpolygon): #vertices %d != #facets %d\n",
    +        numvertices, numfacets);
    +      waserror= True;
    +    }
    +    if (qh hull_dim == 3 && numvertices + numfacets - numridges/2 != 2) {
    +      qh_fprintf(qh ferr, 7063, "qhull warning: #vertices %d + #facets %d - #edges %d != 2\n\
    +        A vertex appears twice in a edge list.  May occur during merging.",
    +        numvertices, numfacets, numridges/2);
    +      /* occurs if lots of merging and a vertex ends up twice in an edge list.  e.g., RBOX 1000 s W1e-13 t995849315 D2 | QHULL d Tc Tv */
    +    }
    +  }
    +  if (waserror)
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +} /* checkpolygon */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkvertex( vertex )
    +    check vertex for consistency
    +    checks vertex->neighbors
    +
    +  notes:
    +    neighbors checked efficiently in checkpolygon
    +*/
    +void qh_checkvertex(vertexT *vertex) {
    +  boolT waserror= False;
    +  facetT *neighbor, **neighborp, *errfacet=NULL;
    +
    +  if (qh_pointid(vertex->point) == qh_IDunknown) {
    +    qh_fprintf(qh ferr, 6144, "qhull internal error (qh_checkvertex): unknown point id %p\n", vertex->point);
    +    waserror= True;
    +  }
    +  if (vertex->id >= qh vertex_id) {
    +    qh_fprintf(qh ferr, 6145, "qhull internal error (qh_checkvertex): unknown vertex id %d\n", vertex->id);
    +    waserror= True;
    +  }
    +  if (!waserror && !vertex->deleted) {
    +    if (qh_setsize(vertex->neighbors)) {
    +      FOREACHneighbor_(vertex) {
    +        if (!qh_setin(neighbor->vertices, vertex)) {
    +          qh_fprintf(qh ferr, 6146, "qhull internal error (qh_checkvertex): neighbor f%d does not contain v%d\n", neighbor->id, vertex->id);
    +          errfacet= neighbor;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_errprint("ERRONEOUS", NULL, NULL, NULL, vertex);
    +    qh_errexit(qh_ERRqhull, errfacet, NULL);
    +  }
    +} /* checkvertex */
    +
    +/*---------------------------------
    +
    +  qh_clearcenters( type )
    +    clear old data from facet->center
    +
    +  notes:
    +    sets new centertype
    +    nop if CENTERtype is the same
    +*/
    +void qh_clearcenters(qh_CENTER type) {
    +  facetT *facet;
    +
    +  if (qh CENTERtype != type) {
    +    FORALLfacets {
    +      if (facet->tricoplanar && !facet->keepcentrum)
    +          facet->center= NULL;  /* center is owned by the ->keepcentrum facet */
    +      else if (qh CENTERtype == qh_ASvoronoi){
    +        if (facet->center) {
    +          qh_memfree(facet->center, qh center_size);
    +          facet->center= NULL;
    +        }
    +      }else /* qh.CENTERtype == qh_AScentrum */ {
    +        if (facet->center) {
    +          qh_memfree(facet->center, qh normal_size);
    +          facet->center= NULL;
    +        }
    +      }
    +    }
    +    qh CENTERtype= type;
    +  }
    +  trace2((qh ferr, 2043, "qh_clearcenters: switched to center type %d\n", type));
    +} /* clearcenters */
    +
    +/*---------------------------------
    +
    +  qh_createsimplex( vertices )
    +    creates a simplex from a set of vertices
    +
    +  returns:
    +    initializes qh.facet_list to the simplex
    +    initializes qh.newfacet_list, .facet_tail
    +    initializes qh.vertex_list, .newvertex_list, .vertex_tail
    +
    +  design:
    +    initializes lists
    +    for each vertex
    +      create a new facet
    +    for each new facet
    +      create its neighbor set
    +*/
    +void qh_createsimplex(setT *vertices) {
    +  facetT *facet= NULL, *newfacet;
    +  boolT toporient= True;
    +  int vertex_i, vertex_n, nth;
    +  setT *newfacets= qh_settemp(qh hull_dim+1);
    +  vertexT *vertex;
    +
    +  qh facet_list= qh newfacet_list= qh facet_tail= qh_newfacet();
    +  qh num_facets= qh num_vertices= qh num_visible= 0;
    +  qh vertex_list= qh newvertex_list= qh vertex_tail= qh_newvertex(NULL);
    +  FOREACHvertex_i_(vertices) {
    +    newfacet= qh_newfacet();
    +    newfacet->vertices= qh_setnew_delnthsorted(vertices, vertex_n,
    +                                                vertex_i, 0);
    +    newfacet->toporient= (unsigned char)toporient;
    +    qh_appendfacet(newfacet);
    +    newfacet->newfacet= True;
    +    qh_appendvertex(vertex);
    +    qh_setappend(&newfacets, newfacet);
    +    toporient ^= True;
    +  }
    +  FORALLnew_facets {
    +    nth= 0;
    +    FORALLfacet_(qh newfacet_list) {
    +      if (facet != newfacet)
    +        SETelem_(newfacet->neighbors, nth++)= facet;
    +    }
    +    qh_settruncate(newfacet->neighbors, qh hull_dim);
    +  }
    +  qh_settempfree(&newfacets);
    +  trace1((qh ferr, 1028, "qh_createsimplex: created simplex\n"));
    +} /* createsimplex */
    +
    +/*---------------------------------
    +
    +  qh_delridge( ridge )
    +    deletes ridge from data structures it belongs to
    +    frees up its memory
    +
    +  notes:
    +    in merge.c, caller sets vertex->delridge for each vertex
    +    ridges also freed in qh_freeqhull
    +*/
    +void qh_delridge(ridgeT *ridge) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  qh_setdel(ridge->top->ridges, ridge);
    +  qh_setdel(ridge->bottom->ridges, ridge);
    +  qh_setfree(&(ridge->vertices));
    +  qh_memfree_(ridge, (int)sizeof(ridgeT), freelistp);
    +} /* delridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_delvertex( vertex )
    +    deletes a vertex and frees its memory
    +
    +  notes:
    +    assumes vertex->adjacencies have been updated if needed
    +    unlinks from vertex_list
    +*/
    +void qh_delvertex(vertexT *vertex) {
    +
    +  if (vertex == qh tracevertex)
    +    qh tracevertex= NULL;
    +  qh_removevertex(vertex);
    +  qh_setfree(&vertex->neighbors);
    +  qh_memfree(vertex, (int)sizeof(vertexT));
    +} /* delvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_facet3vertex(  )
    +    return temporary set of 3-d vertices in qh_ORIENTclock order
    +
    +  design:
    +    if simplicial facet
    +      build set from facet->vertices with facet->toporient
    +    else
    +      for each ridge in order
    +        build set from ridge's vertices
    +*/
    +setT *qh_facet3vertex(facetT *facet) {
    +  ridgeT *ridge, *firstridge;
    +  vertexT *vertex;
    +  int cntvertices, cntprojected=0;
    +  setT *vertices;
    +
    +  cntvertices= qh_setsize(facet->vertices);
    +  vertices= qh_settemp(cntvertices);
    +  if (facet->simplicial) {
    +    if (cntvertices != 3) {
    +      qh_fprintf(qh ferr, 6147, "qhull internal error (qh_facet3vertex): only %d vertices for simplicial facet f%d\n",
    +                  cntvertices, facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +    qh_setappend(&vertices, SETfirst_(facet->vertices));
    +    if (facet->toporient ^ qh_ORIENTclock)
    +      qh_setappend(&vertices, SETsecond_(facet->vertices));
    +    else
    +      qh_setaddnth(&vertices, 0, SETsecond_(facet->vertices));
    +    qh_setappend(&vertices, SETelem_(facet->vertices, 2));
    +  }else {
    +    ridge= firstridge= SETfirstt_(facet->ridges, ridgeT);   /* no infinite */
    +    while ((ridge= qh_nextridge3d(ridge, facet, &vertex))) {
    +      qh_setappend(&vertices, vertex);
    +      if (++cntprojected > cntvertices || ridge == firstridge)
    +        break;
    +    }
    +    if (!ridge || cntprojected != cntvertices) {
    +      qh_fprintf(qh ferr, 6148, "qhull internal error (qh_facet3vertex): ridges for facet %d don't match up.  got at least %d\n",
    +                  facet->id, cntprojected);
    +      qh_errexit(qh_ERRqhull, facet, ridge);
    +    }
    +  }
    +  return vertices;
    +} /* facet3vertex */
    +
    +/*---------------------------------
    +
    +  qh_findbestfacet( point, bestoutside, bestdist, isoutside )
    +    find facet that is furthest below a point
    +
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    if bestoutside is set (e.g., qh_ALL)
    +      returns best facet that is not upperdelaunay
    +      if Delaunay and inside, point is outside circumsphere of bestfacet
    +    else
    +      returns first facet below point
    +      if point is inside, returns nearest, !upperdelaunay facet
    +    distance to facet
    +    isoutside set if outside of facet
    +
    +  notes:
    +    For tricoplanar facets, this finds one of the tricoplanar facets closest
    +    to the point.  For Delaunay triangulations, the point may be inside a
    +    different tricoplanar facet. See locate a facet with qh_findbestfacet()
    +
    +    If inside, qh_findbestfacet performs an exhaustive search
    +       this may be too conservative.  Sometimes it is clearly required.
    +
    +    qh_findbestfacet is not used by qhull.
    +    uses qh.visit_id and qh.coplanarset
    +
    +  see:
    +    qh_findbest
    +*/
    +facetT *qh_findbestfacet(pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside) {
    +  facetT *bestfacet= NULL;
    +  int numpart, totpart= 0;
    +
    +  bestfacet= qh_findbest(point, qh facet_list,
    +                            bestoutside, !qh_ISnewfacets, bestoutside /* qh_NOupper */,
    +                            bestdist, isoutside, &totpart);
    +  if (*bestdist < -qh DISTround) {
    +    bestfacet= qh_findfacet_all(point, bestdist, isoutside, &numpart);
    +    totpart += numpart;
    +    if ((isoutside && *isoutside && bestoutside)
    +    || (isoutside && !*isoutside && bestfacet->upperdelaunay)) {
    +      bestfacet= qh_findbest(point, bestfacet,
    +                            bestoutside, False, bestoutside,
    +                            bestdist, isoutside, &totpart);
    +      totpart += numpart;
    +    }
    +  }
    +  trace3((qh ferr, 3014, "qh_findbestfacet: f%d dist %2.2g isoutside %d totpart %d\n",
    +      bestfacet->id, *bestdist, (isoutside ? *isoutside : UINT_MAX), totpart));
    +  return bestfacet;
    +} /* findbestfacet */
    +
    +/*---------------------------------
    +
    +  qh_findbestlower( facet, point, bestdist, numpart )
    +    returns best non-upper, non-flipped neighbor of facet for point
    +    if needed, searches vertex neighbors
    +
    +  returns:
    +    returns bestdist and updates numpart
    +
    +  notes:
    +    if Delaunay and inside, point is outside of circumsphere of bestfacet
    +    called by qh_findbest() for points above an upperdelaunay facet
    +
    +*/
    +facetT *qh_findbestlower(facetT *upperfacet, pointT *point, realT *bestdistp, int *numpart) {
    +  facetT *neighbor, **neighborp, *bestfacet= NULL;
    +  realT bestdist= -REALmax/2 /* avoid underflow */;
    +  realT dist;
    +  vertexT *vertex;
    +  boolT isoutside= False;  /* not used */
    +
    +  zinc_(Zbestlower);
    +  FOREACHneighbor_(upperfacet) {
    +    if (neighbor->upperdelaunay || neighbor->flipped)
    +      continue;
    +    (*numpart)++;
    +    qh_distplane(point, neighbor, &dist);
    +    if (dist > bestdist) {
    +      bestfacet= neighbor;
    +      bestdist= dist;
    +    }
    +  }
    +  if (!bestfacet) {
    +    zinc_(Zbestlowerv);
    +    /* rarely called, numpart does not count nearvertex computations */
    +    vertex= qh_nearvertex(upperfacet, point, &dist);
    +    qh_vertexneighbors();
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay || neighbor->flipped)
    +        continue;
    +      (*numpart)++;
    +      qh_distplane(point, neighbor, &dist);
    +      if (dist > bestdist) {
    +        bestfacet= neighbor;
    +        bestdist= dist;
    +      }
    +    }
    +  }
    +  if (!bestfacet) {
    +    zinc_(Zbestlowerall);  /* invoked once per point in outsideset */
    +    zmax_(Zbestloweralln, qh num_facets);
    +    /* [dec'15] Previously reported as QH6228 */
    +    trace3((qh ferr, 3025, "qh_findbestlower: all neighbors of facet %d are flipped or upper Delaunay.  Search all facets\n",
    +        upperfacet->id));
    +    /* rarely called */
    +    bestfacet= qh_findfacet_all(point, &bestdist, &isoutside, numpart);
    +  }
    +  *bestdistp= bestdist;
    +  trace3((qh ferr, 3015, "qh_findbestlower: f%d dist %2.2g for f%d p%d\n",
    +          bestfacet->id, bestdist, upperfacet->id, qh_pointid(point)));
    +  return bestfacet;
    +} /* findbestlower */
    +
    +/*---------------------------------
    +
    +  qh_findfacet_all( point, bestdist, isoutside, numpart )
    +    exhaustive search for facet below a point
    +
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    returns first facet below point
    +    if point is inside,
    +      returns nearest facet
    +    distance to facet
    +    isoutside if point is outside of the hull
    +    number of distance tests
    +
    +  notes:
    +    primarily for library users, rarely used by Qhull
    +*/
    +facetT *qh_findfacet_all(pointT *point, realT *bestdist, boolT *isoutside,
    +                          int *numpart) {
    +  facetT *bestfacet= NULL, *facet;
    +  realT dist;
    +  int totpart= 0;
    +
    +  *bestdist= -REALmax;
    +  *isoutside= False;
    +  FORALLfacets {
    +    if (facet->flipped || !facet->normal)
    +      continue;
    +    totpart++;
    +    qh_distplane(point, facet, &dist);
    +    if (dist > *bestdist) {
    +      *bestdist= dist;
    +      bestfacet= facet;
    +      if (dist > qh MINoutside) {
    +        *isoutside= True;
    +        break;
    +      }
    +    }
    +  }
    +  *numpart= totpart;
    +  trace3((qh ferr, 3016, "qh_findfacet_all: f%d dist %2.2g isoutside %d totpart %d\n",
    +          getid_(bestfacet), *bestdist, *isoutside, totpart));
    +  return bestfacet;
    +} /* findfacet_all */
    +
    +/*---------------------------------
    +
    +  qh_findgood( facetlist, goodhorizon )
    +    identify good facets for qh.PRINTgood
    +    if qh.GOODvertex>0
    +      facet includes point as vertex
    +      if !match, returns goodhorizon
    +      inactive if qh.MERGING
    +    if qh.GOODpoint
    +      facet is visible or coplanar (>0) or not visible (<0)
    +    if qh.GOODthreshold
    +      facet->normal matches threshold
    +    if !goodhorizon and !match,
    +      selects facet with closest angle
    +      sets GOODclosest
    +
    +  returns:
    +    number of new, good facets found
    +    determines facet->good
    +    may update qh.GOODclosest
    +
    +  notes:
    +    qh_findgood_all further reduces the good region
    +
    +  design:
    +    count good facets
    +    mark good facets for qh.GOODpoint
    +    mark good facets for qh.GOODthreshold
    +    if necessary
    +      update qh.GOODclosest
    +*/
    +int qh_findgood(facetT *facetlist, int goodhorizon) {
    +  facetT *facet, *bestfacet= NULL;
    +  realT angle, bestangle= REALmax, dist;
    +  int  numgood=0;
    +
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      numgood++;
    +  }
    +  if (qh GOODvertex>0 && !qh MERGING) {
    +    FORALLfacet_(facetlist) {
    +      if (!qh_isvertex(qh GOODvertexp, facet->vertices)) {
    +        facet->good= False;
    +        numgood--;
    +      }
    +    }
    +  }
    +  if (qh GOODpoint && numgood) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && facet->normal) {
    +        zinc_(Zdistgood);
    +        qh_distplane(qh GOODpointp, facet, &dist);
    +        if ((qh GOODpoint > 0) ^ (dist > 0.0)) {
    +          facet->good= False;
    +          numgood--;
    +        }
    +      }
    +    }
    +  }
    +  if (qh GOODthreshold && (numgood || goodhorizon || qh GOODclosest)) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && facet->normal) {
    +        if (!qh_inthresholds(facet->normal, &angle)) {
    +          facet->good= False;
    +          numgood--;
    +          if (angle < bestangle) {
    +            bestangle= angle;
    +            bestfacet= facet;
    +          }
    +        }
    +      }
    +    }
    +    if (!numgood && (!goodhorizon || qh GOODclosest)) {
    +      if (qh GOODclosest) {
    +        if (qh GOODclosest->visible)
    +          qh GOODclosest= NULL;
    +        else {
    +          qh_inthresholds(qh GOODclosest->normal, &angle);
    +          if (angle < bestangle)
    +            bestfacet= qh GOODclosest;
    +        }
    +      }
    +      if (bestfacet && bestfacet != qh GOODclosest) {
    +        if (qh GOODclosest)
    +          qh GOODclosest->good= False;
    +        qh GOODclosest= bestfacet;
    +        bestfacet->good= True;
    +        numgood++;
    +        trace2((qh ferr, 2044, "qh_findgood: f%d is closest(%2.2g) to thresholds\n",
    +           bestfacet->id, bestangle));
    +        return numgood;
    +      }
    +    }else if (qh GOODclosest) { /* numgood > 0 */
    +      qh GOODclosest->good= False;
    +      qh GOODclosest= NULL;
    +    }
    +  }
    +  zadd_(Zgoodfacet, numgood);
    +  trace2((qh ferr, 2045, "qh_findgood: found %d good facets with %d good horizon\n",
    +               numgood, goodhorizon));
    +  if (!numgood && qh GOODvertex>0 && !qh MERGING)
    +    return goodhorizon;
    +  return numgood;
    +} /* findgood */
    +
    +/*---------------------------------
    +
    +  qh_findgood_all( facetlist )
    +    apply other constraints for good facets (used by qh.PRINTgood)
    +    if qh.GOODvertex
    +      facet includes (>0) or doesn't include (<0) point as vertex
    +      if last good facet and ONLYgood, prints warning and continues
    +    if qh.SPLITthresholds
    +      facet->normal matches threshold, or if none, the closest one
    +    calls qh_findgood
    +    nop if good not used
    +
    +  returns:
    +    clears facet->good if not good
    +    sets qh.num_good
    +
    +  notes:
    +    this is like qh_findgood but more restrictive
    +
    +  design:
    +    uses qh_findgood to mark good facets
    +    marks facets for qh.GOODvertex
    +    marks facets for qh.SPLITthreholds
    +*/
    +void qh_findgood_all(facetT *facetlist) {
    +  facetT *facet, *bestfacet=NULL;
    +  realT angle, bestangle= REALmax;
    +  int  numgood=0, startgood;
    +
    +  if (!qh GOODvertex && !qh GOODthreshold && !qh GOODpoint
    +  && !qh SPLITthresholds)
    +    return;
    +  if (!qh ONLYgood)
    +    qh_findgood(qh facet_list, 0);
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      numgood++;
    +  }
    +  if (qh GOODvertex <0 || (qh GOODvertex > 0 && qh MERGING)) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && ((qh GOODvertex > 0) ^ !!qh_isvertex(qh GOODvertexp, facet->vertices))) {
    +        if (!--numgood) {
    +          if (qh ONLYgood) {
    +            qh_fprintf(qh ferr, 7064, "qhull warning: good vertex p%d does not match last good facet f%d.  Ignored.\n",
    +               qh_pointid(qh GOODvertexp), facet->id);
    +            return;
    +          }else if (qh GOODvertex > 0)
    +            qh_fprintf(qh ferr, 7065, "qhull warning: point p%d is not a vertex('QV%d').\n",
    +                qh GOODvertex-1, qh GOODvertex-1);
    +          else
    +            qh_fprintf(qh ferr, 7066, "qhull warning: point p%d is a vertex for every facet('QV-%d').\n",
    +                -qh GOODvertex - 1, -qh GOODvertex - 1);
    +        }
    +        facet->good= False;
    +      }
    +    }
    +  }
    +  startgood= numgood;
    +  if (qh SPLITthresholds) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good) {
    +        if (!qh_inthresholds(facet->normal, &angle)) {
    +          facet->good= False;
    +          numgood--;
    +          if (angle < bestangle) {
    +            bestangle= angle;
    +            bestfacet= facet;
    +          }
    +        }
    +      }
    +    }
    +    if (!numgood && bestfacet) {
    +      bestfacet->good= True;
    +      numgood++;
    +      trace0((qh ferr, 23, "qh_findgood_all: f%d is closest(%2.2g) to thresholds\n",
    +           bestfacet->id, bestangle));
    +      return;
    +    }
    +  }
    +  qh num_good= numgood;
    +  trace0((qh ferr, 24, "qh_findgood_all: %d good facets remain out of %d facets\n",
    +        numgood, startgood));
    +} /* findgood_all */
    +
    +/*---------------------------------
    +
    +  qh_furthestnext()
    +    set qh.facet_next to facet with furthest of all furthest points
    +    searches all facets on qh.facet_list
    +
    +  notes:
    +    this may help avoid precision problems
    +*/
    +void qh_furthestnext(void /* qh.facet_list */) {
    +  facetT *facet, *bestfacet= NULL;
    +  realT dist, bestdist= -REALmax;
    +
    +  FORALLfacets {
    +    if (facet->outsideset) {
    +#if qh_COMPUTEfurthest
    +      pointT *furthest;
    +      furthest= (pointT*)qh_setlast(facet->outsideset);
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(furthest, facet, &dist);
    +#else
    +      dist= facet->furthestdist;
    +#endif
    +      if (dist > bestdist) {
    +        bestfacet= facet;
    +        bestdist= dist;
    +      }
    +    }
    +  }
    +  if (bestfacet) {
    +    qh_removefacet(bestfacet);
    +    qh_prependfacet(bestfacet, &qh facet_next);
    +    trace1((qh ferr, 1029, "qh_furthestnext: made f%d next facet(dist %.2g)\n",
    +            bestfacet->id, bestdist));
    +  }
    +} /* furthestnext */
    +
    +/*---------------------------------
    +
    +  qh_furthestout( facet )
    +    make furthest outside point the last point of outsideset
    +
    +  returns:
    +    updates facet->outsideset
    +    clears facet->notfurthest
    +    sets facet->furthestdist
    +
    +  design:
    +    determine best point of outsideset
    +    make it the last point of outsideset
    +*/
    +void qh_furthestout(facetT *facet) {
    +  pointT *point, **pointp, *bestpoint= NULL;
    +  realT dist, bestdist= -REALmax;
    +
    +  FOREACHpoint_(facet->outsideset) {
    +    qh_distplane(point, facet, &dist);
    +    zinc_(Zcomputefurthest);
    +    if (dist > bestdist) {
    +      bestpoint= point;
    +      bestdist= dist;
    +    }
    +  }
    +  if (bestpoint) {
    +    qh_setdel(facet->outsideset, point);
    +    qh_setappend(&facet->outsideset, point);
    +#if !qh_COMPUTEfurthest
    +    facet->furthestdist= bestdist;
    +#endif
    +  }
    +  facet->notfurthest= False;
    +  trace3((qh ferr, 3017, "qh_furthestout: p%d is furthest outside point of f%d\n",
    +          qh_pointid(point), facet->id));
    +} /* furthestout */
    +
    +
    +/*---------------------------------
    +
    +  qh_infiniteloop( facet )
    +    report infinite loop error due to facet
    +*/
    +void qh_infiniteloop(facetT *facet) {
    +
    +  qh_fprintf(qh ferr, 6149, "qhull internal error (qh_infiniteloop): potential infinite loop detected\n");
    +  qh_errexit(qh_ERRqhull, facet, NULL);
    +} /* qh_infiniteloop */
    +
    +/*---------------------------------
    +
    +  qh_initbuild()
    +    initialize hull and outside sets with point array
    +    qh.FIRSTpoint/qh.NUMpoints is point array
    +    if qh.GOODpoint
    +      adds qh.GOODpoint to initial hull
    +
    +  returns:
    +    qh_facetlist with initial hull
    +    points partioned into outside sets, coplanar sets, or inside
    +    initializes qh.GOODpointp, qh.GOODvertexp,
    +
    +  design:
    +    initialize global variables used during qh_buildhull
    +    determine precision constants and points with max/min coordinate values
    +      if qh.SCALElast, scale last coordinate(for 'd')
    +    build initial simplex
    +    partition input points into facets of initial simplex
    +    set up lists
    +    if qh.ONLYgood
    +      check consistency
    +      add qh.GOODvertex if defined
    +*/
    +void qh_initbuild( void) {
    +  setT *maxpoints, *vertices;
    +  facetT *facet;
    +  int i, numpart;
    +  realT dist;
    +  boolT isoutside;
    +
    +  qh furthest_id= qh_IDunknown;
    +  qh lastreport= 0;
    +  qh facet_id= qh vertex_id= qh ridge_id= 0;
    +  qh visit_id= qh vertex_visit= 0;
    +  qh maxoutdone= False;
    +
    +  if (qh GOODpoint > 0)
    +    qh GOODpointp= qh_point(qh GOODpoint-1);
    +  else if (qh GOODpoint < 0)
    +    qh GOODpointp= qh_point(-qh GOODpoint-1);
    +  if (qh GOODvertex > 0)
    +    qh GOODvertexp= qh_point(qh GOODvertex-1);
    +  else if (qh GOODvertex < 0)
    +    qh GOODvertexp= qh_point(-qh GOODvertex-1);
    +  if ((qh GOODpoint
    +       && (qh GOODpointp < qh first_point  /* also catches !GOODpointp */
    +           || qh GOODpointp > qh_point(qh num_points-1)))
    +    || (qh GOODvertex
    +        && (qh GOODvertexp < qh first_point  /* also catches !GOODvertexp */
    +            || qh GOODvertexp > qh_point(qh num_points-1)))) {
    +    qh_fprintf(qh ferr, 6150, "qhull input error: either QGn or QVn point is > p%d\n",
    +             qh num_points-1);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  maxpoints= qh_maxmin(qh first_point, qh num_points, qh hull_dim);
    +  if (qh SCALElast)
    +    qh_scalelast(qh first_point, qh num_points, qh hull_dim,
    +               qh MINlastcoord, qh MAXlastcoord, qh MAXwidth);
    +  qh_detroundoff();
    +  if (qh DELAUNAY && qh upper_threshold[qh hull_dim-1] > REALmax/2
    +                  && qh lower_threshold[qh hull_dim-1] < -REALmax/2) {
    +    for (i=qh_PRINTEND; i--; ) {
    +      if (qh PRINTout[i] == qh_PRINTgeom && qh DROPdim < 0
    +          && !qh GOODthreshold && !qh SPLITthresholds)
    +        break;  /* in this case, don't set upper_threshold */
    +    }
    +    if (i < 0) {
    +      if (qh UPPERdelaunay) { /* matches qh.upperdelaunay in qh_setfacetplane */
    +        qh lower_threshold[qh hull_dim-1]= qh ANGLEround * qh_ZEROdelaunay;
    +        qh GOODthreshold= True;
    +      }else {
    +        qh upper_threshold[qh hull_dim-1]= -qh ANGLEround * qh_ZEROdelaunay;
    +        if (!qh GOODthreshold)
    +          qh SPLITthresholds= True; /* build upper-convex hull even if Qg */
    +          /* qh_initqhull_globals errors if Qg without Pdk/etc. */
    +      }
    +    }
    +  }
    +  vertices= qh_initialvertices(qh hull_dim, maxpoints, qh first_point, qh num_points);
    +  qh_initialhull(vertices);  /* initial qh facet_list */
    +  qh_partitionall(vertices, qh first_point, qh num_points);
    +  if (qh PRINToptions1st || qh TRACElevel || qh IStracing) {
    +    if (qh TRACElevel || qh IStracing)
    +      qh_fprintf(qh ferr, 8103, "\nTrace level %d for %s | %s\n",
    +         qh IStracing ? qh IStracing : qh TRACElevel, qh rbox_command, qh qhull_command);
    +    qh_fprintf(qh ferr, 8104, "Options selected for Qhull %s:\n%s\n", qh_version, qh qhull_options);
    +  }
    +  qh_resetlists(False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +  qh facet_next= qh facet_list;
    +  qh_furthestnext(/* qh.facet_list */);
    +  if (qh PREmerge) {
    +    qh cos_max= qh premerge_cos;
    +    qh centrum_radius= qh premerge_centrum;
    +  }
    +  if (qh ONLYgood) {
    +    if (qh GOODvertex > 0 && qh MERGING) {
    +      qh_fprintf(qh ferr, 6151, "qhull input error: 'Qg QVn' (only good vertex) does not work with merging.\nUse 'QJ' to joggle the input or 'Q0' to turn off merging.\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (!(qh GOODthreshold || qh GOODpoint
    +         || (!qh MERGEexact && !qh PREmerge && qh GOODvertexp))) {
    +      qh_fprintf(qh ferr, 6152, "qhull input error: 'Qg' (ONLYgood) needs a good threshold('Pd0D0'), a\n\
    +good point(QGn or QG-n), or a good vertex with 'QJ' or 'Q0' (QVn).\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh GOODvertex > 0  && !qh MERGING  /* matches qh_partitionall */
    +        && !qh_isvertex(qh GOODvertexp, vertices)) {
    +      facet= qh_findbestnew(qh GOODvertexp, qh facet_list,
    +                          &dist, !qh_ALL, &isoutside, &numpart);
    +      zadd_(Zdistgood, numpart);
    +      if (!isoutside) {
    +        qh_fprintf(qh ferr, 6153, "qhull input error: point for QV%d is inside initial simplex.  It can not be made a vertex.\n",
    +               qh_pointid(qh GOODvertexp));
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +      if (!qh_addpoint(qh GOODvertexp, facet, False)) {
    +        qh_settempfree(&vertices);
    +        qh_settempfree(&maxpoints);
    +        return;
    +      }
    +    }
    +    qh_findgood(qh facet_list, 0);
    +  }
    +  qh_settempfree(&vertices);
    +  qh_settempfree(&maxpoints);
    +  trace1((qh ferr, 1030, "qh_initbuild: initial hull created and points partitioned\n"));
    +} /* initbuild */
    +
    +/*---------------------------------
    +
    +  qh_initialhull( vertices )
    +    constructs the initial hull as a DIM3 simplex of vertices
    +
    +  design:
    +    creates a simplex (initializes lists)
    +    determines orientation of simplex
    +    sets hyperplanes for facets
    +    doubles checks orientation (in case of axis-parallel facets with Gaussian elimination)
    +    checks for flipped facets and qh.NARROWhull
    +    checks the result
    +*/
    +void qh_initialhull(setT *vertices) {
    +  facetT *facet, *firstfacet, *neighbor, **neighborp;
    +  realT dist, angle, minangle= REALmax;
    +#ifndef qh_NOtrace
    +  int k;
    +#endif
    +
    +  qh_createsimplex(vertices);  /* qh.facet_list */
    +  qh_resetlists(False, qh_RESETvisible);
    +  qh facet_next= qh facet_list;      /* advance facet when processed */
    +  qh interior_point= qh_getcenter(vertices);
    +  firstfacet= qh facet_list;
    +  qh_setfacetplane(firstfacet);
    +  zinc_(Znumvisibility); /* needs to be in printsummary */
    +  qh_distplane(qh interior_point, firstfacet, &dist);
    +  if (dist > 0) {
    +    FORALLfacets
    +      facet->toporient ^= (unsigned char)True;
    +  }
    +  FORALLfacets
    +    qh_setfacetplane(facet);
    +  FORALLfacets {
    +    if (!qh_checkflipped(facet, NULL, qh_ALL)) {/* due to axis-parallel facet */
    +      trace1((qh ferr, 1031, "qh_initialhull: initial orientation incorrect.  Correct all facets\n"));
    +      facet->flipped= False;
    +      FORALLfacets {
    +        facet->toporient ^= (unsigned char)True;
    +        qh_orientoutside(facet);
    +      }
    +      break;
    +    }
    +  }
    +  FORALLfacets {
    +    if (!qh_checkflipped(facet, NULL, !qh_ALL)) {  /* can happen with 'R0.1' */
    +      if (qh DELAUNAY && ! qh ATinfinity) {
    +        if (qh UPPERdelaunay)
    +          qh_fprintf(qh ferr, 6240, "Qhull precision error: Initial simplex is cocircular or cospherical.  Option 'Qs' searches all points.  Can not compute the upper Delaunay triangulation or upper Voronoi diagram of cocircular/cospherical points.\n");
    +        else
    +          qh_fprintf(qh ferr, 6239, "Qhull precision error: Initial simplex is cocircular or cospherical.  Use option 'Qz' for the Delaunay triangulation or Voronoi diagram of cocircular/cospherical points.  Option 'Qz' adds a point \"at infinity\".    Use option 'Qs' to search all points for the initial simplex.\n");
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +      qh_precision("initial simplex is flat");
    +      qh_fprintf(qh ferr, 6154, "Qhull precision error: Initial simplex is flat (facet %d is coplanar with the interior point)\n",
    +                 facet->id);
    +      qh_errexit(qh_ERRsingular, NULL, NULL);  /* calls qh_printhelp_singular */
    +    }
    +    FOREACHneighbor_(facet) {
    +      angle= qh_getangle(facet->normal, neighbor->normal);
    +      minimize_( minangle, angle);
    +    }
    +  }
    +  if (minangle < qh_MAXnarrow && !qh NOnarrow) {
    +    realT diff= 1.0 + minangle;
    +
    +    qh NARROWhull= True;
    +    qh_option("_narrow-hull", NULL, &diff);
    +    if (minangle < qh_WARNnarrow && !qh RERUN && qh PRINTprecision)
    +      qh_printhelp_narrowhull(qh ferr, minangle);
    +  }
    +  zzval_(Zprocessed)= qh hull_dim+1;
    +  qh_checkpolygon(qh facet_list);
    +  qh_checkconvex(qh facet_list,   qh_DATAfault);
    +#ifndef qh_NOtrace
    +  if (qh IStracing >= 1) {
    +    qh_fprintf(qh ferr, 8105, "qh_initialhull: simplex constructed, interior point:");
    +    for (k=0; k < qh hull_dim; k++)
    +      qh_fprintf(qh ferr, 8106, " %6.4g", qh interior_point[k]);
    +    qh_fprintf(qh ferr, 8107, "\n");
    +  }
    +#endif
    +} /* initialhull */
    +
    +/*---------------------------------
    +
    +  qh_initialvertices( dim, maxpoints, points, numpoints )
    +    determines a non-singular set of initial vertices
    +    maxpoints may include duplicate points
    +
    +  returns:
    +    temporary set of dim+1 vertices in descending order by vertex id
    +    if qh.RANDOMoutside && !qh.ALLpoints
    +      picks random points
    +    if dim >= qh_INITIALmax,
    +      uses min/max x and max points with non-zero determinants
    +
    +  notes:
    +    unless qh.ALLpoints,
    +      uses maxpoints as long as determinate is non-zero
    +*/
    +setT *qh_initialvertices(int dim, setT *maxpoints, pointT *points, int numpoints) {
    +  pointT *point, **pointp;
    +  setT *vertices, *simplex, *tested;
    +  realT randr;
    +  int idx, point_i, point_n, k;
    +  boolT nearzero= False;
    +
    +  vertices= qh_settemp(dim + 1);
    +  simplex= qh_settemp(dim+1);
    +  if (qh ALLpoints)
    +    qh_maxsimplex(dim, NULL, points, numpoints, &simplex);
    +  else if (qh RANDOMoutside) {
    +    while (qh_setsize(simplex) != dim+1) {
    +      randr= qh_RANDOMint;
    +      randr= randr/(qh_RANDOMmax+1);
    +      idx= (int)floor(qh num_points * randr);
    +      while (qh_setin(simplex, qh_point(idx))) {
    +        idx++; /* in case qh_RANDOMint always returns the same value */
    +        idx= idx < qh num_points ? idx : 0;
    +      }
    +      qh_setappend(&simplex, qh_point(idx));
    +    }
    +  }else if (qh hull_dim >= qh_INITIALmax) {
    +    tested= qh_settemp(dim+1);
    +    qh_setappend(&simplex, SETfirst_(maxpoints));   /* max and min X coord */
    +    qh_setappend(&simplex, SETsecond_(maxpoints));
    +    qh_maxsimplex(fmin_(qh_INITIALsearch, dim), maxpoints, points, numpoints, &simplex);
    +    k= qh_setsize(simplex);
    +    FOREACHpoint_i_(maxpoints) {
    +      if (point_i & 0x1) {     /* first pick up max. coord. points */
    +        if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +          qh_detsimplex(point, simplex, k, &nearzero);
    +          if (nearzero)
    +            qh_setappend(&tested, point);
    +          else {
    +            qh_setappend(&simplex, point);
    +            if (++k == dim)  /* use search for last point */
    +              break;
    +          }
    +        }
    +      }
    +    }
    +    while (k != dim && (point= (pointT*)qh_setdellast(maxpoints))) {
    +      if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +        qh_detsimplex(point, simplex, k, &nearzero);
    +        if (nearzero)
    +          qh_setappend(&tested, point);
    +        else {
    +          qh_setappend(&simplex, point);
    +          k++;
    +        }
    +      }
    +    }
    +    idx= 0;
    +    while (k != dim && (point= qh_point(idx++))) {
    +      if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +        qh_detsimplex(point, simplex, k, &nearzero);
    +        if (!nearzero){
    +          qh_setappend(&simplex, point);
    +          k++;
    +        }
    +      }
    +    }
    +    qh_settempfree(&tested);
    +    qh_maxsimplex(dim, maxpoints, points, numpoints, &simplex);
    +  }else
    +    qh_maxsimplex(dim, maxpoints, points, numpoints, &simplex);
    +  FOREACHpoint_(simplex)
    +    qh_setaddnth(&vertices, 0, qh_newvertex(point)); /* descending order */
    +  qh_settempfree(&simplex);
    +  return vertices;
    +} /* initialvertices */
    +
    +
    +/*---------------------------------
    +
    +  qh_isvertex( point, vertices )
    +    returns vertex if point is in vertex set, else returns NULL
    +
    +  notes:
    +    for qh.GOODvertex
    +*/
    +vertexT *qh_isvertex(pointT *point, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (vertex->point == point)
    +      return vertex;
    +  }
    +  return NULL;
    +} /* isvertex */
    +
    +/*---------------------------------
    +
    +  qh_makenewfacets( point )
    +    make new facets from point and qh.visible_list
    +
    +  returns:
    +    qh.newfacet_list= list of new facets with hyperplanes and ->newfacet
    +    qh.newvertex_list= list of vertices in new facets with ->newlist set
    +
    +    if (qh.ONLYgood)
    +      newfacets reference horizon facets, but not vice versa
    +      ridges reference non-simplicial horizon ridges, but not vice versa
    +      does not change existing facets
    +    else
    +      sets qh.NEWfacets
    +      new facets attached to horizon facets and ridges
    +      for visible facets,
    +        visible->r.replace is corresponding new facet
    +
    +  see also:
    +    qh_makenewplanes() -- make hyperplanes for facets
    +    qh_attachnewfacets() -- attachnewfacets if not done here(qh ONLYgood)
    +    qh_matchnewfacets() -- match up neighbors
    +    qh_updatevertices() -- update vertex neighbors and delvertices
    +    qh_deletevisible() -- delete visible facets
    +    qh_checkpolygon() --check the result
    +    qh_triangulate() -- triangulate a non-simplicial facet
    +
    +  design:
    +    for each visible facet
    +      make new facets to its horizon facets
    +      update its f.replace
    +      clear its neighbor set
    +*/
    +vertexT *qh_makenewfacets(pointT *point /*visible_list*/) {
    +  facetT *visible, *newfacet= NULL, *newfacet2= NULL, *neighbor, **neighborp;
    +  vertexT *apex;
    +  int numnew=0;
    +
    +  qh newfacet_list= qh facet_tail;
    +  qh newvertex_list= qh vertex_tail;
    +  apex= qh_newvertex(point);
    +  qh_appendvertex(apex);
    +  qh visit_id++;
    +  if (!qh ONLYgood)
    +    qh NEWfacets= True;
    +  FORALLvisible_facets {
    +    FOREACHneighbor_(visible)
    +      neighbor->seen= False;
    +    if (visible->ridges) {
    +      visible->visitid= qh visit_id;
    +      newfacet2= qh_makenew_nonsimplicial(visible, apex, &numnew);
    +    }
    +    if (visible->simplicial)
    +      newfacet= qh_makenew_simplicial(visible, apex, &numnew);
    +    if (!qh ONLYgood) {
    +      if (newfacet2)  /* newfacet is null if all ridges defined */
    +        newfacet= newfacet2;
    +      if (newfacet)
    +        visible->f.replace= newfacet;
    +      else
    +        zinc_(Zinsidevisible);
    +      SETfirst_(visible->neighbors)= NULL;
    +    }
    +  }
    +  trace1((qh ferr, 1032, "qh_makenewfacets: created %d new facets from point p%d to horizon\n",
    +          numnew, qh_pointid(point)));
    +  if (qh IStracing >= 4)
    +    qh_printfacetlist(qh newfacet_list, NULL, qh_ALL);
    +  return apex;
    +} /* makenewfacets */
    +
    +/*---------------------------------
    +
    +  qh_matchduplicates( atfacet, atskip, hashsize, hashcount )
    +    match duplicate ridges in qh.hash_table for atfacet/atskip
    +    duplicates marked with ->dupridge and qh_DUPLICATEridge
    +
    +  returns:
    +    picks match with worst merge (min distance apart)
    +    updates hashcount
    +
    +  see also:
    +    qh_matchneighbor
    +
    +  notes:
    +
    +  design:
    +    compute hash value for atfacet and atskip
    +    repeat twice -- once to make best matches, once to match the rest
    +      for each possible facet in qh.hash_table
    +        if it is a matching facet and pass 2
    +          make match
    +          unless tricoplanar, mark match for merging (qh_MERGEridge)
    +          [e.g., tricoplanar RBOX s 1000 t993602376 | QHULL C-1e-3 d Qbb FA Qt]
    +        if it is a matching facet and pass 1
    +          test if this is a better match
    +      if pass 1,
    +        make best match (it will not be merged)
    +*/
    +#ifndef qh_NOmerge
    +void qh_matchduplicates(facetT *atfacet, int atskip, int hashsize, int *hashcount) {
    +  boolT same, ismatch;
    +  int hash, scan;
    +  facetT *facet, *newfacet, *maxmatch= NULL, *maxmatch2= NULL, *nextfacet;
    +  int skip, newskip, nextskip= 0, maxskip= 0, maxskip2= 0, makematch;
    +  realT maxdist= -REALmax, mindist, dist2, low, high;
    +
    +  hash= qh_gethash(hashsize, atfacet->vertices, qh hull_dim, 1,
    +                     SETelem_(atfacet->vertices, atskip));
    +  trace2((qh ferr, 2046, "qh_matchduplicates: find duplicate matches for f%d skip %d hash %d hashcount %d\n",
    +          atfacet->id, atskip, hash, *hashcount));
    +  for (makematch= 0; makematch < 2; makematch++) {
    +    qh visit_id++;
    +    for (newfacet= atfacet, newskip= atskip; newfacet; newfacet= nextfacet, newskip= nextskip) {
    +      zinc_(Zhashlookup);
    +      nextfacet= NULL;
    +      newfacet->visitid= qh visit_id;
    +      for (scan= hash; (facet= SETelemt_(qh hash_table, scan, facetT));
    +           scan= (++scan >= hashsize ? 0 : scan)) {
    +        if (!facet->dupridge || facet->visitid == qh visit_id)
    +          continue;
    +        zinc_(Zhashtests);
    +        if (qh_matchvertices(1, newfacet->vertices, newskip, facet->vertices, &skip, &same)) {
    +          ismatch= (same == (boolT)(newfacet->toporient ^ facet->toporient));
    +          if (SETelemt_(facet->neighbors, skip, facetT) != qh_DUPLICATEridge) {
    +            if (!makematch) {
    +              qh_fprintf(qh ferr, 6155, "qhull internal error (qh_matchduplicates): missing dupridge at f%d skip %d for new f%d skip %d hash %d\n",
    +                     facet->id, skip, newfacet->id, newskip, hash);
    +              qh_errexit2(qh_ERRqhull, facet, newfacet);
    +            }
    +          }else if (ismatch && makematch) {
    +            if (SETelemt_(newfacet->neighbors, newskip, facetT) == qh_DUPLICATEridge) {
    +              SETelem_(facet->neighbors, skip)= newfacet;
    +              if (newfacet->tricoplanar)
    +                SETelem_(newfacet->neighbors, newskip)= facet;
    +              else
    +                SETelem_(newfacet->neighbors, newskip)= qh_MERGEridge;
    +              *hashcount -= 2; /* removed two unmatched facets */
    +              trace4((qh ferr, 4059, "qh_matchduplicates: duplicate f%d skip %d matched with new f%d skip %d merge\n",
    +                    facet->id, skip, newfacet->id, newskip));
    +            }
    +          }else if (ismatch) {
    +            mindist= qh_getdistance(facet, newfacet, &low, &high);
    +            dist2= qh_getdistance(newfacet, facet, &low, &high);
    +            minimize_(mindist, dist2);
    +            if (mindist > maxdist) {
    +              maxdist= mindist;
    +              maxmatch= facet;
    +              maxskip= skip;
    +              maxmatch2= newfacet;
    +              maxskip2= newskip;
    +            }
    +            trace3((qh ferr, 3018, "qh_matchduplicates: duplicate f%d skip %d new f%d skip %d at dist %2.2g, max is now f%d f%d\n",
    +                    facet->id, skip, newfacet->id, newskip, mindist,
    +                    maxmatch->id, maxmatch2->id));
    +          }else { /* !ismatch */
    +            nextfacet= facet;
    +            nextskip= skip;
    +          }
    +        }
    +        if (makematch && !facet
    +        && SETelemt_(facet->neighbors, skip, facetT) == qh_DUPLICATEridge) {
    +          qh_fprintf(qh ferr, 6156, "qhull internal error (qh_matchduplicates): no MERGEridge match for duplicate f%d skip %d at hash %d\n",
    +                     newfacet->id, newskip, hash);
    +          qh_errexit(qh_ERRqhull, newfacet, NULL);
    +        }
    +      }
    +    } /* end of for each new facet at hash */
    +    if (!makematch) {
    +      if (!maxmatch) {
    +        qh_fprintf(qh ferr, 6157, "qhull internal error (qh_matchduplicates): no maximum match at duplicate f%d skip %d at hash %d\n",
    +                     atfacet->id, atskip, hash);
    +        qh_errexit(qh_ERRqhull, atfacet, NULL);
    +      }
    +      SETelem_(maxmatch->neighbors, maxskip)= maxmatch2; /* maxmatch!=0 by QH6157 */
    +      SETelem_(maxmatch2->neighbors, maxskip2)= maxmatch;
    +      *hashcount -= 2; /* removed two unmatched facets */
    +      zzinc_(Zmultiridge);
    +      trace0((qh ferr, 25, "qh_matchduplicates: duplicate f%d skip %d matched with new f%d skip %d keep\n",
    +              maxmatch->id, maxskip, maxmatch2->id, maxskip2));
    +      qh_precision("ridge with multiple neighbors");
    +      if (qh IStracing >= 4)
    +        qh_errprint("DUPLICATED/MATCH", maxmatch, maxmatch2, NULL, NULL);
    +    }
    +  }
    +} /* matchduplicates */
    +
    +/*---------------------------------
    +
    +  qh_nearcoplanar()
    +    for all facets, remove near-inside points from facet->coplanarset
    +    coplanar points defined by innerplane from qh_outerinner()
    +
    +  returns:
    +    if qh KEEPcoplanar && !qh KEEPinside
    +      facet->coplanarset only contains coplanar points
    +    if qh.JOGGLEmax
    +      drops inner plane by another qh.JOGGLEmax diagonal since a
    +        vertex could shift out while a coplanar point shifts in
    +
    +  notes:
    +    used for qh.PREmerge and qh.JOGGLEmax
    +    must agree with computation of qh.NEARcoplanar in qh_detroundoff()
    +  design:
    +    if not keeping coplanar or inside points
    +      free all coplanar sets
    +    else if not keeping both coplanar and inside points
    +      remove !coplanar or !inside points from coplanar sets
    +*/
    +void qh_nearcoplanar(void /* qh.facet_list */) {
    +  facetT *facet;
    +  pointT *point, **pointp;
    +  int numpart;
    +  realT dist, innerplane;
    +
    +  if (!qh KEEPcoplanar && !qh KEEPinside) {
    +    FORALLfacets {
    +      if (facet->coplanarset)
    +        qh_setfree( &facet->coplanarset);
    +    }
    +  }else if (!qh KEEPcoplanar || !qh KEEPinside) {
    +    qh_outerinner(NULL, NULL, &innerplane);
    +    if (qh JOGGLEmax < REALmax/2)
    +      innerplane -= qh JOGGLEmax * sqrt((realT)qh hull_dim);
    +    numpart= 0;
    +    FORALLfacets {
    +      if (facet->coplanarset) {
    +        FOREACHpoint_(facet->coplanarset) {
    +          numpart++;
    +          qh_distplane(point, facet, &dist);
    +          if (dist < innerplane) {
    +            if (!qh KEEPinside)
    +              SETref_(point)= NULL;
    +          }else if (!qh KEEPcoplanar)
    +            SETref_(point)= NULL;
    +        }
    +        qh_setcompact(facet->coplanarset);
    +      }
    +    }
    +    zzadd_(Zcheckpart, numpart);
    +  }
    +} /* nearcoplanar */
    +
    +/*---------------------------------
    +
    +  qh_nearvertex( facet, point, bestdist )
    +    return nearest vertex in facet to point
    +
    +  returns:
    +    vertex and its distance
    +
    +  notes:
    +    if qh.DELAUNAY
    +      distance is measured in the input set
    +    searches neighboring tricoplanar facets (requires vertexneighbors)
    +      Slow implementation.  Recomputes vertex set for each point.
    +    The vertex set could be stored in the qh.keepcentrum facet.
    +*/
    +vertexT *qh_nearvertex(facetT *facet, pointT *point, realT *bestdistp) {
    +  realT bestdist= REALmax, dist;
    +  vertexT *bestvertex= NULL, *vertex, **vertexp, *apex;
    +  coordT *center;
    +  facetT *neighbor, **neighborp;
    +  setT *vertices;
    +  int dim= qh hull_dim;
    +
    +  if (qh DELAUNAY)
    +    dim--;
    +  if (facet->tricoplanar) {
    +    if (!qh VERTEXneighbors || !facet->center) {
    +      qh_fprintf(qh ferr, 6158, "qhull internal error (qh_nearvertex): qh.VERTEXneighbors and facet->center required for tricoplanar facets\n");
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +    vertices= qh_settemp(qh TEMPsize);
    +    apex= SETfirstt_(facet->vertices, vertexT);
    +    center= facet->center;
    +    FOREACHneighbor_(apex) {
    +      if (neighbor->center == center) {
    +        FOREACHvertex_(neighbor->vertices)
    +          qh_setappend(&vertices, vertex);
    +      }
    +    }
    +  }else
    +    vertices= facet->vertices;
    +  FOREACHvertex_(vertices) {
    +    dist= qh_pointdist(vertex->point, point, -dim);
    +    if (dist < bestdist) {
    +      bestdist= dist;
    +      bestvertex= vertex;
    +    }
    +  }
    +  if (facet->tricoplanar)
    +    qh_settempfree(&vertices);
    +  *bestdistp= sqrt(bestdist);
    +  if (!bestvertex) {
    +      qh_fprintf(qh ferr, 6261, "qhull internal error (qh_nearvertex): did not find bestvertex for f%d p%d\n", facet->id, qh_pointid(point));
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  trace3((qh ferr, 3019, "qh_nearvertex: v%d dist %2.2g for f%d p%d\n",
    +        bestvertex->id, *bestdistp, facet->id, qh_pointid(point))); /* bestvertex!=0 by QH2161 */
    +  return bestvertex;
    +} /* nearvertex */
    +
    +/*---------------------------------
    +
    +  qh_newhashtable( newsize )
    +    returns size of qh.hash_table of at least newsize slots
    +
    +  notes:
    +    assumes qh.hash_table is NULL
    +    qh_HASHfactor determines the number of extra slots
    +    size is not divisible by 2, 3, or 5
    +*/
    +int qh_newhashtable(int newsize) {
    +  int size;
    +
    +  size= ((newsize+1)*qh_HASHfactor) | 0x1;  /* odd number */
    +  while (True) {
    +    if (newsize<0 || size<0) {
    +        qh_fprintf(qhmem.ferr, 6236, "qhull error (qh_newhashtable): negative request (%d) or size (%d).  Did int overflow due to high-D?\n", newsize, size); /* WARN64 */
    +        qh_errexit(qhmem_ERRmem, NULL, NULL);
    +    }
    +    if ((size%3) && (size%5))
    +      break;
    +    size += 2;
    +    /* loop terminates because there is an infinite number of primes */
    +  }
    +  qh hash_table= qh_setnew(size);
    +  qh_setzero(qh hash_table, 0, size);
    +  return size;
    +} /* newhashtable */
    +
    +/*---------------------------------
    +
    +  qh_newvertex( point )
    +    returns a new vertex for point
    +*/
    +vertexT *qh_newvertex(pointT *point) {
    +  vertexT *vertex;
    +
    +  zinc_(Ztotvertices);
    +  vertex= (vertexT *)qh_memalloc((int)sizeof(vertexT));
    +  memset((char *) vertex, (size_t)0, sizeof(vertexT));
    +  if (qh vertex_id == UINT_MAX) {
    +    qh_memfree(vertex, (int)sizeof(vertexT));
    +    qh_fprintf(qh ferr, 6159, "qhull error: more than 2^32 vertices.  vertexT.id field overflows.  Vertices would not be sorted correctly.\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (qh vertex_id == qh tracevertex_id)
    +    qh tracevertex= vertex;
    +  vertex->id= qh vertex_id++;
    +  vertex->point= point;
    +  trace4((qh ferr, 4060, "qh_newvertex: vertex p%d(v%d) created\n", qh_pointid(vertex->point),
    +          vertex->id));
    +  return(vertex);
    +} /* newvertex */
    +
    +/*---------------------------------
    +
    +  qh_nextridge3d( atridge, facet, vertex )
    +    return next ridge and vertex for a 3d facet
    +    returns NULL on error
    +    [for QhullFacet::nextRidge3d] Does not call qh_errexit nor access qh_qh.
    +
    +  notes:
    +    in qh_ORIENTclock order
    +    this is a O(n^2) implementation to trace all ridges
    +    be sure to stop on any 2nd visit
    +    same as QhullRidge::nextRidge3d
    +    does not use qh_qh or qh_errexit [QhullFacet.cpp]
    +
    +  design:
    +    for each ridge
    +      exit if it is the ridge after atridge
    +*/
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp) {
    +  vertexT *atvertex, *vertex, *othervertex;
    +  ridgeT *ridge, **ridgep;
    +
    +  if ((atridge->top == facet) ^ qh_ORIENTclock)
    +    atvertex= SETsecondt_(atridge->vertices, vertexT);
    +  else
    +    atvertex= SETfirstt_(atridge->vertices, vertexT);
    +  FOREACHridge_(facet->ridges) {
    +    if (ridge == atridge)
    +      continue;
    +    if ((ridge->top == facet) ^ qh_ORIENTclock) {
    +      othervertex= SETsecondt_(ridge->vertices, vertexT);
    +      vertex= SETfirstt_(ridge->vertices, vertexT);
    +    }else {
    +      vertex= SETsecondt_(ridge->vertices, vertexT);
    +      othervertex= SETfirstt_(ridge->vertices, vertexT);
    +    }
    +    if (vertex == atvertex) {
    +      if (vertexp)
    +        *vertexp= othervertex;
    +      return ridge;
    +    }
    +  }
    +  return NULL;
    +} /* nextridge3d */
    +#else /* qh_NOmerge */
    +void qh_matchduplicates(facetT *atfacet, int atskip, int hashsize, int *hashcount) {
    +}
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp) {
    +
    +  return NULL;
    +}
    +#endif /* qh_NOmerge */
    +
    +/*---------------------------------
    +
    +  qh_outcoplanar()
    +    move points from all facets' outsidesets to their coplanarsets
    +
    +  notes:
    +    for post-processing under qh.NARROWhull
    +
    +  design:
    +    for each facet
    +      for each outside point for facet
    +        partition point into coplanar set
    +*/
    +void qh_outcoplanar(void /* facet_list */) {
    +  pointT *point, **pointp;
    +  facetT *facet;
    +  realT dist;
    +
    +  trace1((qh ferr, 1033, "qh_outcoplanar: move outsideset to coplanarset for qh NARROWhull\n"));
    +  FORALLfacets {
    +    FOREACHpoint_(facet->outsideset) {
    +      qh num_outside--;
    +      if (qh KEEPcoplanar || qh KEEPnearinside) {
    +        qh_distplane(point, facet, &dist);
    +        zinc_(Zpartition);
    +        qh_partitioncoplanar(point, facet, &dist);
    +      }
    +    }
    +    qh_setfree(&facet->outsideset);
    +  }
    +} /* outcoplanar */
    +
    +/*---------------------------------
    +
    +  qh_point( id )
    +    return point for a point id, or NULL if unknown
    +
    +  alternative code:
    +    return((pointT *)((unsigned   long)qh.first_point
    +           + (unsigned long)((id)*qh.normal_size)));
    +*/
    +pointT *qh_point(int id) {
    +
    +  if (id < 0)
    +    return NULL;
    +  if (id < qh num_points)
    +    return qh first_point + id * qh hull_dim;
    +  id -= qh num_points;
    +  if (id < qh_setsize(qh other_points))
    +    return SETelemt_(qh other_points, id, pointT);
    +  return NULL;
    +} /* point */
    +
    +/*---------------------------------
    +
    +  qh_point_add( set, point, elem )
    +    stores elem at set[point.id]
    +
    +  returns:
    +    access function for qh_pointfacet and qh_pointvertex
    +
    +  notes:
    +    checks point.id
    +*/
    +void qh_point_add(setT *set, pointT *point, void *elem) {
    +  int id, size;
    +
    +  SETreturnsize_(set, size);
    +  if ((id= qh_pointid(point)) < 0)
    +    qh_fprintf(qh ferr, 7067, "qhull internal warning (point_add): unknown point %p id %d\n",
    +      point, id);
    +  else if (id >= size) {
    +    qh_fprintf(qh ferr, 6160, "qhull internal errror(point_add): point p%d is out of bounds(%d)\n",
    +             id, size);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }else
    +    SETelem_(set, id)= elem;
    +} /* point_add */
    +
    +
    +/*---------------------------------
    +
    +  qh_pointfacet()
    +    return temporary set of facet for each point
    +    the set is indexed by point id
    +
    +  notes:
    +    vertices assigned to one of the facets
    +    coplanarset assigned to the facet
    +    outside set assigned to the facet
    +    NULL if no facet for point (inside)
    +      includes qh.GOODpointp
    +
    +  access:
    +    FOREACHfacet_i_(facets) { ... }
    +    SETelem_(facets, i)
    +
    +  design:
    +    for each facet
    +      add each vertex
    +      add each coplanar point
    +      add each outside point
    +*/
    +setT *qh_pointfacet(void /*qh.facet_list*/) {
    +  int numpoints= qh num_points + qh_setsize(qh other_points);
    +  setT *facets;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  pointT *point, **pointp;
    +
    +  facets= qh_settemp(numpoints);
    +  qh_setzero(facets, 0, numpoints);
    +  qh vertex_visit++;
    +  FORALLfacets {
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh vertex_visit) {
    +        vertex->visitid= qh vertex_visit;
    +        qh_point_add(facets, vertex->point, facet);
    +      }
    +    }
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(facets, point, facet);
    +    FOREACHpoint_(facet->outsideset)
    +      qh_point_add(facets, point, facet);
    +  }
    +  return facets;
    +} /* pointfacet */
    +
    +/*---------------------------------
    +
    +  qh_pointvertex(  )
    +    return temporary set of vertices indexed by point id
    +    entry is NULL if no vertex for a point
    +      this will include qh.GOODpointp
    +
    +  access:
    +    FOREACHvertex_i_(vertices) { ... }
    +    SETelem_(vertices, i)
    +*/
    +setT *qh_pointvertex(void /*qh.facet_list*/) {
    +  int numpoints= qh num_points + qh_setsize(qh other_points);
    +  setT *vertices;
    +  vertexT *vertex;
    +
    +  vertices= qh_settemp(numpoints);
    +  qh_setzero(vertices, 0, numpoints);
    +  FORALLvertices
    +    qh_point_add(vertices, vertex->point, vertex);
    +  return vertices;
    +} /* pointvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_prependfacet( facet, facetlist )
    +    prepend facet to the start of a facetlist
    +
    +  returns:
    +    increments qh.numfacets
    +    updates facetlist, qh.facet_list, facet_next
    +
    +  notes:
    +    be careful of prepending since it can lose a pointer.
    +      e.g., can lose _next by deleting and then prepending before _next
    +*/
    +void qh_prependfacet(facetT *facet, facetT **facetlist) {
    +  facetT *prevfacet, *list;
    +
    +
    +  trace4((qh ferr, 4061, "qh_prependfacet: prepend f%d before f%d\n",
    +          facet->id, getid_(*facetlist)));
    +  if (!*facetlist)
    +    (*facetlist)= qh facet_tail;
    +  list= *facetlist;
    +  prevfacet= list->previous;
    +  facet->previous= prevfacet;
    +  if (prevfacet)
    +    prevfacet->next= facet;
    +  list->previous= facet;
    +  facet->next= *facetlist;
    +  if (qh facet_list == list)  /* this may change *facetlist */
    +    qh facet_list= facet;
    +  if (qh facet_next == list)
    +    qh facet_next= facet;
    +  *facetlist= facet;
    +  qh num_facets++;
    +} /* prependfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhashtable( fp )
    +    print hash table to fp
    +
    +  notes:
    +    not in I/O to avoid bringing io.c in
    +
    +  design:
    +    for each hash entry
    +      if defined
    +        if unmatched or will merge (NULL, qh_MERGEridge, qh_DUPLICATEridge)
    +          print entry and neighbors
    +*/
    +void qh_printhashtable(FILE *fp) {
    +  facetT *facet, *neighbor;
    +  int id, facet_i, facet_n, neighbor_i= 0, neighbor_n= 0;
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHfacet_i_(qh hash_table) {
    +    if (facet) {
    +      FOREACHneighbor_i_(facet) {
    +        if (!neighbor || neighbor == qh_MERGEridge || neighbor == qh_DUPLICATEridge)
    +          break;
    +      }
    +      if (neighbor_i == neighbor_n)
    +        continue;
    +      qh_fprintf(fp, 9283, "hash %d f%d ", facet_i, facet->id);
    +      FOREACHvertex_(facet->vertices)
    +        qh_fprintf(fp, 9284, "v%d ", vertex->id);
    +      qh_fprintf(fp, 9285, "\n neighbors:");
    +      FOREACHneighbor_i_(facet) {
    +        if (neighbor == qh_MERGEridge)
    +          id= -3;
    +        else if (neighbor == qh_DUPLICATEridge)
    +          id= -2;
    +        else
    +          id= getid_(neighbor);
    +        qh_fprintf(fp, 9286, " %d", id);
    +      }
    +      qh_fprintf(fp, 9287, "\n");
    +    }
    +  }
    +} /* printhashtable */
    +
    +
    +/*---------------------------------
    +
    +  qh_printlists( fp )
    +    print out facet and vertex list for debugging (without 'f/v' tags)
    +*/
    +void qh_printlists(void) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  int count= 0;
    +
    +  qh_fprintf(qh ferr, 8108, "qh_printlists: facets:");
    +  FORALLfacets {
    +    if (++count % 100 == 0)
    +      qh_fprintf(qh ferr, 8109, "\n     ");
    +    qh_fprintf(qh ferr, 8110, " %d", facet->id);
    +  }
    +  qh_fprintf(qh ferr, 8111, "\n  new facets %d visible facets %d next facet for qh_addpoint %d\n  vertices(new %d):",
    +     getid_(qh newfacet_list), getid_(qh visible_list), getid_(qh facet_next),
    +     getid_(qh newvertex_list));
    +  count = 0;
    +  FORALLvertices {
    +    if (++count % 100 == 0)
    +      qh_fprintf(qh ferr, 8112, "\n     ");
    +    qh_fprintf(qh ferr, 8113, " %d", vertex->id);
    +  }
    +  qh_fprintf(qh ferr, 8114, "\n");
    +} /* printlists */
    +
    +/*---------------------------------
    +
    +  qh_resetlists( stats, qh_RESETvisible )
    +    reset newvertex_list, newfacet_list, visible_list
    +    if stats,
    +      maintains statistics
    +
    +  returns:
    +    visible_list is empty if qh_deletevisible was called
    +*/
    +void qh_resetlists(boolT stats, boolT resetVisible /*qh.newvertex_list newfacet_list visible_list*/) {
    +  vertexT *vertex;
    +  facetT *newfacet, *visible;
    +  int totnew=0, totver=0;
    +
    +  if (stats) {
    +    FORALLvertex_(qh newvertex_list)
    +      totver++;
    +    FORALLnew_facets
    +      totnew++;
    +    zadd_(Zvisvertextot, totver);
    +    zmax_(Zvisvertexmax, totver);
    +    zadd_(Znewfacettot, totnew);
    +    zmax_(Znewfacetmax, totnew);
    +  }
    +  FORALLvertex_(qh newvertex_list)
    +    vertex->newlist= False;
    +  qh newvertex_list= NULL;
    +  FORALLnew_facets
    +    newfacet->newfacet= False;
    +  qh newfacet_list= NULL;
    +  if (resetVisible) {
    +    FORALLvisible_facets {
    +      visible->f.replace= NULL;
    +      visible->visible= False;
    +    }
    +    qh num_visible= 0;
    +  }
    +  qh visible_list= NULL; /* may still have visible facets via qh_triangulate */
    +  qh NEWfacets= False;
    +} /* resetlists */
    +
    +/*---------------------------------
    +
    +  qh_setvoronoi_all()
    +    compute Voronoi centers for all facets
    +    includes upperDelaunay facets if qh.UPPERdelaunay ('Qu')
    +
    +  returns:
    +    facet->center is the Voronoi center
    +
    +  notes:
    +    this is unused/untested code
    +      please email bradb@shore.net if this works ok for you
    +
    +  use:
    +    FORALLvertices {...} to locate the vertex for a point.
    +    FOREACHneighbor_(vertex) {...} to visit the Voronoi centers for a Voronoi cell.
    +*/
    +void qh_setvoronoi_all(void) {
    +  facetT *facet;
    +
    +  qh_clearcenters(qh_ASvoronoi);
    +  qh_vertexneighbors();
    +
    +  FORALLfacets {
    +    if (!facet->normal || !facet->upperdelaunay || qh UPPERdelaunay) {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(facet->vertices);
    +    }
    +  }
    +} /* setvoronoi_all */
    +
    +#ifndef qh_NOmerge
    +
    +/*---------------------------------
    +
    +  qh_triangulate()
    +    triangulate non-simplicial facets on qh.facet_list,
    +    if qh VORONOI, sets Voronoi centers of non-simplicial facets
    +    nop if hasTriangulation
    +
    +  returns:
    +    all facets simplicial
    +    each tricoplanar facet has ->f.triowner == owner of ->center,normal,etc.
    +
    +  notes:
    +    call after qh_check_output since may switch to Voronoi centers
    +    Output may overwrite ->f.triowner with ->f.area
    +*/
    +void qh_triangulate(void /*qh.facet_list*/) {
    +  facetT *facet, *nextfacet, *owner;
    +  int onlygood= qh ONLYgood;
    +  facetT *neighbor, *visible= NULL, *facet1, *facet2, *new_facet_list= NULL;
    +  facetT *orig_neighbor= NULL, *otherfacet;
    +  vertexT *new_vertex_list= NULL;
    +  mergeT *merge;
    +  mergeType mergetype;
    +  int neighbor_i, neighbor_n;
    +
    +  if (qh hasTriangulation)
    +      return;
    +  trace1((qh ferr, 1034, "qh_triangulate: triangulate non-simplicial facets\n"));
    +  if (qh hull_dim == 2)
    +    return;
    +  if (qh VORONOI) {  /* otherwise lose Voronoi centers [could rebuild vertex set from tricoplanar] */
    +    qh_clearcenters(qh_ASvoronoi);
    +    qh_vertexneighbors();
    +  }
    +  qh ONLYgood= False; /* for makenew_nonsimplicial */
    +  qh visit_id++;
    +  qh NEWfacets= True;
    +  qh degen_mergeset= qh_settemp(qh TEMPsize);
    +  qh newvertex_list= qh vertex_tail;
    +  for (facet= qh facet_list; facet && facet->next; facet= nextfacet) { /* non-simplicial facets moved to end */
    +    nextfacet= facet->next;
    +    if (facet->visible || facet->simplicial)
    +      continue;
    +    /* triangulate all non-simplicial facets, otherwise merging does not work, e.g., RBOX c P-0.1 P+0.1 P+0.1 D3 | QHULL d Qt Tv */
    +    if (!new_facet_list)
    +      new_facet_list= facet;  /* will be moved to end */
    +    qh_triangulate_facet(facet, &new_vertex_list);
    +  }
    +  trace2((qh ferr, 2047, "qh_triangulate: delete null facets from f%d -- apex same as second vertex\n", getid_(new_facet_list)));
    +  for (facet= new_facet_list; facet && facet->next; facet= nextfacet) { /* null facets moved to end */
    +    nextfacet= facet->next;
    +    if (facet->visible)
    +      continue;
    +    if (facet->ridges) {
    +      if (qh_setsize(facet->ridges) > 0) {
    +        qh_fprintf(qh ferr, 6161, "qhull error (qh_triangulate): ridges still defined for f%d\n", facet->id);
    +        qh_errexit(qh_ERRqhull, facet, NULL);
    +      }
    +      qh_setfree(&facet->ridges);
    +    }
    +    if (SETfirst_(facet->vertices) == SETsecond_(facet->vertices)) {
    +      zinc_(Ztrinull);
    +      qh_triangulate_null(facet);
    +    }
    +  }
    +  trace2((qh ferr, 2048, "qh_triangulate: delete %d or more mirror facets -- same vertices and neighbors\n", qh_setsize(qh degen_mergeset)));
    +  qh visible_list= qh facet_tail;
    +  while ((merge= (mergeT*)qh_setdellast(qh degen_mergeset))) {
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    mergetype= merge->type;
    +    qh_memfree(merge, (int)sizeof(mergeT));
    +    if (mergetype == MRGmirror) {
    +      zinc_(Ztrimirror);
    +      qh_triangulate_mirror(facet1, facet2);
    +    }
    +  }
    +  qh_settempfree(&qh degen_mergeset);
    +  trace2((qh ferr, 2049, "qh_triangulate: update neighbor lists for vertices from v%d\n", getid_(new_vertex_list)));
    +  qh newvertex_list= new_vertex_list;  /* all vertices of new facets */
    +  qh visible_list= NULL;
    +  qh_updatevertices(/*qh.newvertex_list, empty newfacet_list and visible_list*/);
    +  qh_resetlists(False, !qh_RESETvisible /*qh.newvertex_list, empty newfacet_list and visible_list*/);
    +
    +  trace2((qh ferr, 2050, "qh_triangulate: identify degenerate tricoplanar facets from f%d\n", getid_(new_facet_list)));
    +  trace2((qh ferr, 2051, "qh_triangulate: and replace facet->f.triowner with tricoplanar facets that own center, normal, etc.\n"));
    +  FORALLfacet_(new_facet_list) {
    +    if (facet->tricoplanar && !facet->visible) {
    +      FOREACHneighbor_i_(facet) {
    +        if (neighbor_i == 0) {  /* first iteration */
    +          if (neighbor->tricoplanar)
    +            orig_neighbor= neighbor->f.triowner;
    +          else
    +            orig_neighbor= neighbor;
    +        }else {
    +          if (neighbor->tricoplanar)
    +            otherfacet= neighbor->f.triowner;
    +          else
    +            otherfacet= neighbor;
    +          if (orig_neighbor == otherfacet) {
    +            zinc_(Ztridegen);
    +            facet->degenerate= True;
    +            break;
    +          }
    +        }
    +      }
    +    }
    +  }
    +
    +  trace2((qh ferr, 2052, "qh_triangulate: delete visible facets -- non-simplicial, null, and mirrored facets\n"));
    +  owner= NULL;
    +  visible= NULL;
    +  for (facet= new_facet_list; facet && facet->next; facet= nextfacet) { /* may delete facet */
    +    nextfacet= facet->next;
    +    if (facet->visible) {
    +      if (facet->tricoplanar) { /* a null or mirrored facet */
    +        qh_delfacet(facet);
    +        qh num_visible--;
    +      }else {  /* a non-simplicial facet followed by its tricoplanars */
    +        if (visible && !owner) {
    +          /*  RBOX 200 s D5 t1001471447 | QHULL Qt C-0.01 Qx Qc Tv Qt -- f4483 had 6 vertices/neighbors and 8 ridges */
    +          trace2((qh ferr, 2053, "qh_triangulate: all tricoplanar facets degenerate for non-simplicial facet f%d\n",
    +                       visible->id));
    +          qh_delfacet(visible);
    +          qh num_visible--;
    +        }
    +        visible= facet;
    +        owner= NULL;
    +      }
    +    }else if (facet->tricoplanar) {
    +      if (facet->f.triowner != visible || visible==NULL) {
    +        qh_fprintf(qh ferr, 6162, "qhull error (qh_triangulate): tricoplanar facet f%d not owned by its visible, non-simplicial facet f%d\n", facet->id, getid_(visible));
    +        qh_errexit2(qh_ERRqhull, facet, visible);
    +      }
    +      if (owner)
    +        facet->f.triowner= owner;
    +      else if (!facet->degenerate) {
    +        owner= facet;
    +        nextfacet= visible->next; /* rescan tricoplanar facets with owner, visible!=0 by QH6162 */
    +        facet->keepcentrum= True;  /* one facet owns ->normal, etc. */
    +        facet->coplanarset= visible->coplanarset;
    +        facet->outsideset= visible->outsideset;
    +        visible->coplanarset= NULL;
    +        visible->outsideset= NULL;
    +        if (!qh TRInormals) { /* center and normal copied to tricoplanar facets */
    +          visible->center= NULL;
    +          visible->normal= NULL;
    +        }
    +        qh_delfacet(visible);
    +        qh num_visible--;
    +      }
    +    }
    +  }
    +  if (visible && !owner) {
    +    trace2((qh ferr, 2054, "qh_triangulate: all tricoplanar facets degenerate for last non-simplicial facet f%d\n",
    +                 visible->id));
    +    qh_delfacet(visible);
    +    qh num_visible--;
    +  }
    +  qh NEWfacets= False;
    +  qh ONLYgood= onlygood; /* restore value */
    +  if (qh CHECKfrequently)
    +    qh_checkpolygon(qh facet_list);
    +  qh hasTriangulation= True;
    +} /* triangulate */
    +
    +
    +/*---------------------------------
    +
    +  qh_triangulate_facet(qh, facetA, &firstVertex )
    +    triangulate a non-simplicial facet
    +      if qh.CENTERtype=qh_ASvoronoi, sets its Voronoi center
    +  returns:
    +    qh.newfacet_list == simplicial facets
    +      facet->tricoplanar set and ->keepcentrum false
    +      facet->degenerate set if duplicated apex
    +      facet->f.trivisible set to facetA
    +      facet->center copied from facetA (created if qh_ASvoronoi)
    +        qh_eachvoronoi, qh_detvridge, qh_detvridge3 assume centers copied
    +      facet->normal,offset,maxoutside copied from facetA
    +
    +  notes:
    +      only called by qh_triangulate
    +      qh_makenew_nonsimplicial uses neighbor->seen for the same
    +      if qh.TRInormals, newfacet->normal will need qh_free
    +        if qh.TRInormals and qh_AScentrum, newfacet->center will need qh_free
    +        keepcentrum is also set on Zwidefacet in qh_mergefacet
    +        freed by qh_clearcenters
    +
    +  see also:
    +      qh_addpoint() -- add a point
    +      qh_makenewfacets() -- construct a cone of facets for a new vertex
    +
    +  design:
    +      if qh_ASvoronoi,
    +         compute Voronoi center (facet->center)
    +      select first vertex (highest ID to preserve ID ordering of ->vertices)
    +      triangulate from vertex to ridges
    +      copy facet->center, normal, offset
    +      update vertex neighbors
    +*/
    +void qh_triangulate_facet(facetT *facetA, vertexT **first_vertex) {
    +  facetT *newfacet;
    +  facetT *neighbor, **neighborp;
    +  vertexT *apex;
    +  int numnew=0;
    +
    +  trace3((qh ferr, 3020, "qh_triangulate_facet: triangulate facet f%d\n", facetA->id));
    +
    +  if (qh IStracing >= 4)
    +    qh_printfacet(qh ferr, facetA);
    +  FOREACHneighbor_(facetA) {
    +    neighbor->seen= False;
    +    neighbor->coplanar= False;
    +  }
    +  if (qh CENTERtype == qh_ASvoronoi && !facetA->center  /* matches upperdelaunay in qh_setfacetplane() */
    +        && fabs_(facetA->normal[qh hull_dim -1]) >= qh ANGLEround * qh_ZEROdelaunay) {
    +    facetA->center= qh_facetcenter(facetA->vertices);
    +  }
    +  qh_willdelete(facetA, NULL);
    +  qh newfacet_list= qh facet_tail;
    +  facetA->visitid= qh visit_id;
    +  apex= SETfirstt_(facetA->vertices, vertexT);
    +  qh_makenew_nonsimplicial(facetA, apex, &numnew);
    +  SETfirst_(facetA->neighbors)= NULL;
    +  FORALLnew_facets {
    +    newfacet->tricoplanar= True;
    +    newfacet->f.trivisible= facetA;
    +    newfacet->degenerate= False;
    +    newfacet->upperdelaunay= facetA->upperdelaunay;
    +    newfacet->good= facetA->good;
    +    if (qh TRInormals) { /* 'Q11' triangulate duplicates ->normal and ->center */
    +      newfacet->keepcentrum= True;
    +      if(facetA->normal){
    +        newfacet->normal= qh_memalloc(qh normal_size);
    +        memcpy((char *)newfacet->normal, facetA->normal, qh normal_size);
    +      }
    +      if (qh CENTERtype == qh_AScentrum)
    +        newfacet->center= qh_getcentrum(newfacet);
    +      else if (qh CENTERtype == qh_ASvoronoi && facetA->center){
    +        newfacet->center= qh_memalloc(qh center_size);
    +        memcpy((char *)newfacet->center, facetA->center, qh center_size);
    +      }
    +    }else {
    +      newfacet->keepcentrum= False;
    +      /* one facet will have keepcentrum=True at end of qh_triangulate */
    +      newfacet->normal= facetA->normal;
    +      newfacet->center= facetA->center;
    +    }
    +    newfacet->offset= facetA->offset;
    +#if qh_MAXoutside
    +    newfacet->maxoutside= facetA->maxoutside;
    +#endif
    +  }
    +  qh_matchnewfacets(/*qh.newfacet_list*/);
    +  zinc_(Ztricoplanar);
    +  zadd_(Ztricoplanartot, numnew);
    +  zmax_(Ztricoplanarmax, numnew);
    +  qh visible_list= NULL;
    +  if (!(*first_vertex))
    +    (*first_vertex)= qh newvertex_list;
    +  qh newvertex_list= NULL;
    +  qh_updatevertices(/*qh.newfacet_list, qh.empty visible_list and qh.newvertex_list*/);
    +  qh_resetlists(False, !qh_RESETvisible /*qh.newfacet_list, qh.empty visible_list and qh.newvertex_list*/);
    +} /* triangulate_facet */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_link(oldfacetA, facetA, oldfacetB, facetB)
    +    relink facetA to facetB via oldfacets
    +  returns:
    +    adds mirror facets to qh degen_mergeset (4-d and up only)
    +  design:
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_link(facetT *oldfacetA, facetT *facetA, facetT *oldfacetB, facetT *facetB) {
    +  int errmirror= False;
    +
    +  trace3((qh ferr, 3021, "qh_triangulate_link: relink old facets f%d and f%d between neighbors f%d and f%d\n",
    +         oldfacetA->id, oldfacetB->id, facetA->id, facetB->id));
    +  if (qh_setin(facetA->neighbors, facetB)) {
    +    if (!qh_setin(facetB->neighbors, facetA))
    +      errmirror= True;
    +    else
    +      qh_appendmergeset(facetA, facetB, MRGmirror, NULL);
    +  }else if (qh_setin(facetB->neighbors, facetA))
    +    errmirror= True;
    +  if (errmirror) {
    +    qh_fprintf(qh ferr, 6163, "qhull error (qh_triangulate_link): mirror facets f%d and f%d do not match for old facets f%d and f%d\n",
    +       facetA->id, facetB->id, oldfacetA->id, oldfacetB->id);
    +    qh_errexit2(qh_ERRqhull, facetA, facetB);
    +  }
    +  qh_setreplace(facetB->neighbors, oldfacetB, facetA);
    +  qh_setreplace(facetA->neighbors, oldfacetA, facetB);
    +} /* triangulate_link */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_mirror(facetA, facetB)
    +    delete mirrored facets from qh_triangulate_null() and qh_triangulate_mirror
    +      a mirrored facet shares the same vertices of a logical ridge
    +  design:
    +    since a null facet duplicates the first two vertices, the opposing neighbors absorb the null facet
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_mirror(facetT *facetA, facetT *facetB) {
    +  facetT *neighbor, *neighborB;
    +  int neighbor_i, neighbor_n;
    +
    +  trace3((qh ferr, 3022, "qh_triangulate_mirror: delete mirrored facets f%d and f%d\n",
    +         facetA->id, facetB->id));
    +  FOREACHneighbor_i_(facetA) {
    +    neighborB= SETelemt_(facetB->neighbors, neighbor_i, facetT);
    +    if (neighbor == neighborB)
    +      continue; /* occurs twice */
    +    qh_triangulate_link(facetA, neighbor, facetB, neighborB);
    +  }
    +  qh_willdelete(facetA, NULL);
    +  qh_willdelete(facetB, NULL);
    +} /* triangulate_mirror */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_null(facetA)
    +    remove null facetA from qh_triangulate_facet()
    +      a null facet has vertex #1 (apex) == vertex #2
    +  returns:
    +    adds facetA to ->visible for deletion after qh_updatevertices
    +    qh degen_mergeset contains mirror facets (4-d and up only)
    +  design:
    +    since a null facet duplicates the first two vertices, the opposing neighbors absorb the null facet
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_null(facetT *facetA) {
    +  facetT *neighbor, *otherfacet;
    +
    +  trace3((qh ferr, 3023, "qh_triangulate_null: delete null facet f%d\n", facetA->id));
    +  neighbor= SETfirstt_(facetA->neighbors, facetT);
    +  otherfacet= SETsecondt_(facetA->neighbors, facetT);
    +  qh_triangulate_link(facetA, neighbor, facetA, otherfacet);
    +  qh_willdelete(facetA, NULL);
    +} /* triangulate_null */
    +
    +#else /* qh_NOmerge */
    +void qh_triangulate(void) {
    +}
    +#endif /* qh_NOmerge */
    +
    +   /*---------------------------------
    +
    +  qh_vertexintersect( vertexsetA, vertexsetB )
    +    intersects two vertex sets (inverse id ordered)
    +    vertexsetA is a temporary set at the top of qhmem.tempstack
    +
    +  returns:
    +    replaces vertexsetA with the intersection
    +
    +  notes:
    +    could overwrite vertexsetA if currently too slow
    +*/
    +void qh_vertexintersect(setT **vertexsetA,setT *vertexsetB) {
    +  setT *intersection;
    +
    +  intersection= qh_vertexintersect_new(*vertexsetA, vertexsetB);
    +  qh_settempfree(vertexsetA);
    +  *vertexsetA= intersection;
    +  qh_settemppush(intersection);
    +} /* vertexintersect */
    +
    +/*---------------------------------
    +
    +  qh_vertexintersect_new(  )
    +    intersects two vertex sets (inverse id ordered)
    +
    +  returns:
    +    a new set
    +*/
    +setT *qh_vertexintersect_new(setT *vertexsetA,setT *vertexsetB) {
    +  setT *intersection= qh_setnew(qh hull_dim - 1);
    +  vertexT **vertexA= SETaddr_(vertexsetA, vertexT);
    +  vertexT **vertexB= SETaddr_(vertexsetB, vertexT);
    +
    +  while (*vertexA && *vertexB) {
    +    if (*vertexA  == *vertexB) {
    +      qh_setappend(&intersection, *vertexA);
    +      vertexA++; vertexB++;
    +    }else {
    +      if ((*vertexA)->id > (*vertexB)->id)
    +        vertexA++;
    +      else
    +        vertexB++;
    +    }
    +  }
    +  return intersection;
    +} /* vertexintersect_new */
    +
    +/*---------------------------------
    +
    +  qh_vertexneighbors()
    +    for each vertex in qh.facet_list,
    +      determine its neighboring facets
    +
    +  returns:
    +    sets qh.VERTEXneighbors
    +      nop if qh.VERTEXneighbors already set
    +      qh_addpoint() will maintain them
    +
    +  notes:
    +    assumes all vertex->neighbors are NULL
    +
    +  design:
    +    for each facet
    +      for each vertex
    +        append facet to vertex->neighbors
    +*/
    +void qh_vertexneighbors(void /*qh.facet_list*/) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh VERTEXneighbors)
    +    return;
    +  trace1((qh ferr, 1035, "qh_vertexneighbors: determining neighboring facets for each vertex\n"));
    +  qh vertex_visit++;
    +  FORALLfacets {
    +    if (facet->visible)
    +      continue;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh vertex_visit) {
    +        vertex->visitid= qh vertex_visit;
    +        vertex->neighbors= qh_setnew(qh hull_dim);
    +      }
    +      qh_setappend(&vertex->neighbors, facet);
    +    }
    +  }
    +  qh VERTEXneighbors= True;
    +} /* vertexneighbors */
    +
    +/*---------------------------------
    +
    +  qh_vertexsubset( vertexsetA, vertexsetB )
    +    returns True if vertexsetA is a subset of vertexsetB
    +    assumes vertexsets are sorted
    +
    +  note:
    +    empty set is a subset of any other set
    +*/
    +boolT qh_vertexsubset(setT *vertexsetA, setT *vertexsetB) {
    +  vertexT **vertexA= (vertexT **) SETaddr_(vertexsetA, vertexT);
    +  vertexT **vertexB= (vertexT **) SETaddr_(vertexsetB, vertexT);
    +
    +  while (True) {
    +    if (!*vertexA)
    +      return True;
    +    if (!*vertexB)
    +      return False;
    +    if ((*vertexA)->id > (*vertexB)->id)
    +      return False;
    +    if (*vertexA  == *vertexB)
    +      vertexA++;
    +    vertexB++;
    +  }
    +  return False; /* avoid warnings */
    +} /* vertexsubset */
    diff --git a/xs/src/qhull/src/libqhull/qh-geom.htm b/xs/src/qhull/src/libqhull/qh-geom.htm
    new file mode 100644
    index 0000000000..6dc7465ebe
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/qh-geom.htm
    @@ -0,0 +1,295 @@
    +
    +
    +
    +
    +geom.c, geom2.c -- geometric and floating point routines
    +
    +
    +
    +
    +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + + +

    geom.c, geom2.c, random.c -- geometric and floating point routines

    +
    +

    Geometrically, a vertex is a point with d coordinates +and a facet is a halfspace. A halfspace is defined by an +oriented hyperplane through the facet's vertices. A hyperplane +is defined by d normalized coefficients and an offset. A +point is above a facet if its distance to the facet is +positive.

    + +

    Qhull uses floating point coordinates for input points, +vertices, halfspace equations, centrums, and an interior point.

    + +

    Qhull may be configured for single precision or double +precision floating point arithmetic (see realT +).

    + +

    Each floating point operation may incur round-off error (see +Merge). The maximum error for distance +computations is determined at initialization. The roundoff error +in halfspace computation is accounted for by computing the +distance from vertices to the halfspace.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly • +QhullSet • +StatUser

    + +

    Index to geom.c, +geom2.c, geom.h, +random.c, random.h +

    + + + +

    »geometric data types +and constants

    + +
      +
    • coordT coordinates and +coefficients are stored as realT
    • +
    • pointT a point is an array +of DIM3 coordinates
    • +
    + +

    »mathematical macros

    + +
      +
    • fabs_ returns the absolute +value of a
    • +
    • fmax_ returns the maximum +value of a and b
    • +
    • fmin_ returns the minimum +value of a and b
    • +
    • maximize_ maximize a value +
    • +
    • minimize_ minimize a value +
    • +
    • det2_ compute a 2-d +determinate
    • +
    • det3_ compute a 3-d +determinate
    • +
    • dX, dY, dZ compute the difference +between two coordinates
    • +
    + +

    »mathematical functions

    + + + +

    »computational geometry functions

    + + + +

    »point array functions

    + + +

    »geometric facet functions

    + + +

    »geometric roundoff functions

    +
      +
    • qh_detjoggle determine +default joggle for points and distance roundoff error
    • +
    • qh_detroundoff +determine maximum roundoff error and other precision constants
    • +
    • qh_distround compute +maximum roundoff error due to a distance computation to a +normalized hyperplane
    • +
    • qh_divzero divide by a +number that is nearly zero
    • +
    • qh_maxouter return maximum outer +plane
    • +
    • qh_outerinner return actual +outer and inner planes +
    + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    + + +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-globa.htm b/xs/src/qhull/src/libqhull/qh-globa.htm new file mode 100644 index 0000000000..c87508b663 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-globa.htm @@ -0,0 +1,165 @@ + + + + +global.c -- global variables and their functions + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + + +

    global.c -- global variables and their functions

    +
    +

    Qhull uses a global data structure, qh, to store +globally defined constants, lists, sets, and variables. This +allows multiple instances of Qhull to execute at the same time. +The structure may be statically allocated or +dynamically allocated with malloc(). See +QHpointer. +

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly • +QhullSet • +StatUser

    + +

    Index to global.c and +libqhull.h

    + + + +

    »Qhull's global +variables

    + + + +

    »Global variable and +initialization routines

    + + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-io.htm b/xs/src/qhull/src/libqhull/qh-io.htm new file mode 100644 index 0000000000..5cb591d877 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-io.htm @@ -0,0 +1,305 @@ + + + + +io.c -- input and output operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    io.c -- input and output operations

    +
    + +

    Qhull provides a wide range of input +and output options. To organize the code, most output formats use +the same driver:

    + +
    +    qh_printbegin( fp, format, facetlist, facets, printall );
    +
    +    FORALLfacet_( facetlist )
    +      qh_printafacet( fp, format, facet, printall );
    +
    +    FOREACHfacet_( facets )
    +      qh_printafacet( fp, format, facet, printall );
    +
    +    qh_printend( fp, format );
    +
    + +

    Note the 'printall' flag. It selects whether or not +qh_skipfacet() is tested.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom +GlobalIo • +MemMerge • +PolyQhull • +SetStat • +User

    + +

    Index to io.c and io.h

    + + + +

    »io.h constants and types

    + +
      +
    • qh_MAXfirst maximum length +of first two lines of stdin
    • +
    • qh_WHITESPACE possible +values of white space
    • +
    • printvridgeT function to +print results of qh_printvdiagram or qh_eachvoronoi
    • +
    + +

    »User level functions

    + + + +

    »Print functions for all +output formats

    + + + +

    »Text output functions

    + + +

    »Text utility functions

    + + +

    »Geomview output functions

    + +

    »Geomview utility functions

    + +

    +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-mem.htm b/xs/src/qhull/src/libqhull/qh-mem.htm new file mode 100644 index 0000000000..b993b22297 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-mem.htm @@ -0,0 +1,145 @@ + + + + +mem.c -- memory operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    mem.c -- memory operations

    +
    +

    Qhull uses quick-fit memory allocation. It maintains a +set of free lists for a variety of small allocations. A +small request returns a block from the best fitting free +list. If the free list is empty, Qhull allocates a block +from a reserved buffer.

    +

    Use 'T5' to trace memory allocations.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to mem.c and +mem.h

    + +

    »mem.h data types and constants

    +
      +
    • ptr_intT for casting +a void* to an integer-type
    • +
    • qhmemT global memory +structure for mem.c
    • +
    • qh_NOmem disable memory allocation
    • +
    +

    »mem.h macros

    + +

    »User level +functions

    + + +

    »Initialization and +termination functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-merge.htm b/xs/src/qhull/src/libqhull/qh-merge.htm new file mode 100644 index 0000000000..54b97c88ee --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-merge.htm @@ -0,0 +1,366 @@ + + + + +merge.c -- facet merge operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    merge.c -- facet merge operations

    +
    +

    Qhull handles precision problems by merged facets or joggled input. +Except for redundant vertices, it corrects a problem by +merging two facets. When done, all facets are clearly +convex. See Imprecision in Qhull +for further information.

    +

    Users may joggle the input ('QJn') +instead of merging facets.

    +

    Qhull detects and corrects the following problems:

    +
      +
    • More than two facets meeting at a ridge. When +Qhull creates facets, it creates an even number +of facets for each ridge. A convex hull always +has two facets for each ridge. More than two +facets may be created if non-adjacent facets +share a vertex. This is called a duplicate +ridge. In 2-d, a duplicate ridge would +create a loop of facets.
    • +
    +
      +
    • A facet contained in another facet. Facet +merging may leave all vertices of one facet as a +subset of the vertices of another facet. This is +called a redundant facet.
    • +
    +
      +
    • A facet with fewer than three neighbors. Facet +merging may leave a facet with one or two +neighbors. This is called a degenerate facet. +
    • +
    +
      +
    • A facet with flipped orientation. A +facet's hyperplane may define a halfspace that +does not include the interior point.This is +called a flipped facet.
    • +
    +
      +
    • A coplanar horizon facet. A +newly processed point may be coplanar with an +horizon facet. Qhull creates a new facet without +a hyperplane. It links new facets for the same +horizon facet together. This is called a samecycle. +The new facet or samecycle is merged into the +horizon facet.
    • +
    +
      +
    • Concave facets. A facet's centrum may be +above a neighboring facet. If so, the facets meet +at a concave angle.
    • +
    +
      +
    • Coplanar facets. A facet's centrum may be +coplanar with a neighboring facet (i.e., it is +neither clearly below nor clearly above the +facet's hyperplane). Qhull removes coplanar +facets in independent sets sorted by angle.
    • +
    +
      +
    • Redundant vertex. A vertex may have fewer +than three neighboring facets. If so, it is +redundant and may be renamed to an adjacent +vertex without changing the topological +structure.This is called a redundant vertex. +
    • +
    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to merge.c and +merge.h

    + + +

    »merge.h data +types, macros, and global sets

    +
      +
    • mergeT structure to +identify a merge of two facets
    • +
    • FOREACHmerge_ +assign 'merge' to each merge in merges
    • +
    • qh global sets +qh.facet_mergeset contains non-convex merges +while qh.degen_mergeset contains degenerate and +redundant facets
    • +
    +

    »merge.h +constants

    + +

    »top-level merge +functions

    + + +

    »functions for +identifying merges

    + + +

    »functions for +determining the best merge

    + + +

    »functions for +merging facets

    + + +

    »functions for +merging a cycle of facets

    +

    If a point is coplanar with an horizon facet, the +corresponding new facets are linked together (a samecycle) +for merging.

    + +

    »functions +for renaming a vertex

    + + +

    »functions +for identifying vertices for renaming

    + + +

    »functions for check and +trace

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-poly.htm b/xs/src/qhull/src/libqhull/qh-poly.htm new file mode 100644 index 0000000000..c8f6b38b0d --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-poly.htm @@ -0,0 +1,485 @@ + + + + +poly.c, poly2.c -- polyhedron operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    poly.c, poly2.c -- polyhedron operations

    +
    + +

    Qhull uses dimension-free terminology. Qhull builds a +polyhedron in dimension d. A polyhedron is a +simplicial complex of faces with geometric information for the +top and bottom-level faces. A (d-1)-face is a facet, +a (d-2)-face is a ridge, and a 0-face +is a vertex. For example in 3-d, a facet is a polygon +and a ridge is an edge. A facet is built from a ridge (the base) +and a vertex (the apex). See +Qhull's data structures.

    + +

    Qhull's primary data structure is a polyhedron. A +polyhedron is a list of facets. Each facet has a set of +neighboring facets and a set of vertices. Each facet has a +hyperplane. For example, a tetrahedron has four facets. +If its vertices are a, b, c, d, and its facets +are 1, 2, 3, 4, the tetrahedron is

    +
    +
      +
    • facet 1
        +
      • vertices: b c d
      • +
      • neighbors: 2 3 4
      • +
      +
    • +
    • facet 2
        +
      • vertices: a c d
      • +
      • neighbors: 1 3 4
      • +
      +
    • +
    • facet 3
        +
      • vertices: a b d
      • +
      • neighbors: 1 2 4
      • +
      +
    • +
    • facet 4
        +
      • vertices: a b c
      • +
      • neighbors: 1 2 3
      • +
      +
    • +
    +
    +

    A facet may be simplicial or non-simplicial. In 3-d, a +simplicial facet has three vertices and three +neighbors. A nonsimplicial facet has more than +three vertices and more than three neighbors. A +nonsimplicial facet has a set of ridges and a centrum.

    +

    +A simplicial facet has an orientation. An orientation +is either top or bottom. +The flag, facet->toporient, +defines the orientation of the facet's vertices. For example in 3-d, +'top' is left-handed orientation (i.e., the vertex order follows the direction +of the left-hand fingers when the thumb is pointing away from the center). +Except for axis-parallel facets in 5-d and higher, topological orientation +determines the geometric orientation of the facet's hyperplane. + +

    A nonsimplicial facet is due to merging two or more +facets. The facet's ridge set determine a simplicial +decomposition of the facet. Each ridge is a 1-face (i.e., +it has two vertices and two neighboring facets). The +orientation of a ridge is determined by the order of the +neighboring facets. The flag, facet->toporient,is +ignored.

    +

    A nonsimplicial facet has a centrum for testing +convexity. A centrum is a point on the facet's +hyperplane that is near the center of the facet. Except +for large facets, it is the arithmetic average of the +facet's vertices.

    +

    A nonsimplicial facet is an approximation that is +defined by offsets from the facet's hyperplane. When +Qhull finishes, the outer plane is above all +points while the inner plane is below the facet's +vertices. This guarantees that any exact convex hull +passes between the inner and outer planes. The outer +plane is defined by facet->maxoutside while +the inner plane is computed from the facet's vertices.

    + +

    Qhull 3.1 includes triangulation of non-simplicial facets +('Qt'). +These facets, +called tricoplanar, share the same normal. centrum, and Voronoi center. +One facet (keepcentrum) owns these data structures. +While tricoplanar facets are more accurate than the simplicial facets from +joggled input, they +may have zero area or flipped orientation. + +

    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to poly.c, +poly2.c, poly.h, +and libqhull.h

    + +

    »Data +types and global lists for polyhedrons

    + +

    »poly.h constants

    +
      +
    • ALGORITHMfault +flag to not report errors in qh_checkconvex()
    • +
    • DATAfault flag to +report errors in qh_checkconvex()
    • +
    • DUPLICATEridge +special value for facet->neighbor to indicate +a duplicate ridge
    • +
    • MERGEridge +special value for facet->neighbor to indicate +a merged ridge
    • +
    +

    »Global FORALL +macros

    + +

    »FORALL macros

    + +

    »FOREACH macros

    + +

    »Indexed +FOREACH macros

    +
      +
    • FOREACHfacet_i_ +assign 'facet' and 'facet_i' to each facet in +facet set
    • +
    • FOREACHneighbor_i_ +assign 'neighbor' and 'neighbor_i' to each facet +in facet->neighbors or vertex->neighbors
    • +
    • FOREACHpoint_i_ +assign 'point' and 'point_i' to each point in +points set
    • +
    • FOREACHridge_i_ +assign 'ridge' and 'ridge_i' to each ridge in +ridges set
    • +
    • FOREACHvertex_i_ +assign 'vertex' and 'vertex_i' to each vertex in +vertices set
    • +
    • FOREACHvertexreverse12_ +assign 'vertex' to each vertex in vertex set; +reverse the order of first two vertices
    • +
    +

    »Other macros for polyhedrons

    +
      +
    • getid_ return ID for +a facet, ridge, or vertex
    • +
    • otherfacet_ +return neighboring facet for a ridge in a facet
    • +
    +

    »Facetlist +functions

    + +

    »Facet +functions

    + +

    »Vertex, +ridge, and point functions

    + +

    »Hashtable functions

    + +

    »Allocation and +deallocation functions

    + +

    »Check +functions

    + + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-qhull.htm b/xs/src/qhull/src/libqhull/qh-qhull.htm new file mode 100644 index 0000000000..5212c64226 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-qhull.htm @@ -0,0 +1,279 @@ + + + + +libqhull.c -- top-level functions and basic data types + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    libqhull.c -- top-level functions and basic data types

    +
    +

    Qhull implements the Quickhull algorithm for computing +the convex hull. The Quickhull algorithm combines two +well-known algorithms: the 2-d quickhull algorithm and +the n-d beneath-beyond algorithm. See +Description of Qhull.

    +

    This section provides an index to the top-level +functions and base data types. The top-level header file, libqhull.h, +contains prototypes for these functions.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to libqhull.c, +libqhull.h, and +unix.c

    + + +

    »libqhull.h and unix.c +data types and constants

    +
      +
    • flagT Boolean flag as +a bit
    • +
    • boolT boolean value, +either True or False
    • +
    • CENTERtype to +distinguish facet->center
    • +
    • qh_PRINT output +formats for printing (qh.PRINTout)
    • +
    • qh_ALL argument flag +for selecting everything
    • +
    • qh_ERR Qhull exit +codes for indicating errors
    • +
    • qh_FILEstderr Fake stderr +to distinguish error output from normal output [C++ only]
    • +
    • qh_prompt version and long prompt for Qhull
    • +
    • qh_prompt2 synopsis for Qhull
    • +
    • qh_prompt3 concise prompt for Qhull
    • +
    • qh_version version stamp
    • +
    + +

    »libqhull.h other +macros

    +
      +
    • traceN print trace +message if qh.IStracing >= N.
    • +
    • QHULL_UNUSED declare an + unused variable to avoid warnings.
    • +
    + +

    »Quickhull +routines in call order

    + + +

    »Top-level routines for initializing and terminating Qhull (in other modules)

    + + +

    »Top-level routines for reading and modifying the input (in other modules)

    + + +

    »Top-level routines for calling Qhull (in other modules)

    + + +

    »Top-level routines for returning results (in other modules)

    + + +

    »Top-level routines for testing and debugging (in other modules)

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-set.htm b/xs/src/qhull/src/libqhull/qh-set.htm new file mode 100644 index 0000000000..06e71bbc92 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-set.htm @@ -0,0 +1,308 @@ + + + + +qset.c -- set data type and operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    qset.c -- set data type and operations

    +
    +

    Qhull's data structures are constructed from sets. The +functions and macros in qset.c construct, iterate, and +modify these sets. They are the most frequently called +functions in Qhull. For this reason, efficiency is the +primary concern.

    +

    In Qhull, a set is represented by an unordered +array of pointers with a maximum size and a NULL +terminator (setT). +Most sets correspond to mathematical sets +(i.e., the pointers are unique). Some sets are sorted to +enforce uniqueness. Some sets are ordered. For example, +the order of vertices in a ridge determine the ridge's +orientation. If you reverse the order of adjacent +vertices, the orientation reverses. Some sets are not +mathematical sets. They may be indexed as an array and +they may include NULL pointers.

    +

    The most common operation on a set is to iterate its +members. This is done with a 'FOREACH...' macro. Each set +has a custom macro. For example, 'FOREACHvertex_' +iterates over a set of vertices. Each vertex is assigned +to the variable 'vertex' from the pointer 'vertexp'.

    +

    Most sets are constructed by appending elements to the +set. The last element of a set is either NULL or the +index of the terminating NULL for a partially full set. +If a set is full, appending an element copies the set to +a larger array.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly +• QhullSet +• StatUser +

    +

    Index to qset.c and +qset.h

    + +

    »Data types and +constants

    +
      +
    • SETelemsize size +of a set element in bytes
    • +
    • setT a set with a +maximum size and a current size
    • +
    • qh global sets +global sets for temporary sets, etc.
    • +
    +

    »FOREACH macros

    + +

    »Access and +size macros

    + +

    »Internal macros

    +
      +
    • SETsizeaddr_ +return pointer to end element of a set (indicates +current size)
    • +
    + +

    »address macros

    +
      +
    • SETaddr_ return +address of a set's elements
    • +
    • SETelemaddr_ +return address of the n'th element of a set
    • +
    • SETref_ l.h.s. for +modifying the current element in a FOREACH +iteration
    • +
    + +

    »Allocation and +deallocation functions

    + + +

    »Access and +predicate functions

    + + +

    »Add functions

    + + +

    »Check and print functions

    + + +

    »Copy, compact, and zero functions

    + + +

    »Delete functions

    + + +

    »Temporary set functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-stat.htm b/xs/src/qhull/src/libqhull/qh-stat.htm new file mode 100644 index 0000000000..b968540312 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-stat.htm @@ -0,0 +1,163 @@ + + + + +stat.c -- statistical operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    stat.c -- statistical operations

    +
    +

    Qhull records many statistics. These functions and +macros make it inexpensive to add a statistic. +

    As with Qhull's global variables, the statistics data structure is +accessed by a macro, 'qhstat'. If qh_QHpointer is defined, the macro +is 'qh_qhstat->', otherwise the macro is 'qh_qhstat.'. +Statistics +may be turned off in user.h. If so, all but the 'zz' +statistics are ignored.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to stat.c and +stat.h

    + + +

    »stat.h types

    +
      +
    • intrealT union of +integer and real
    • +
    • qhstat global data +structure for statistics
    • +
    +

    »stat.h +constants

    +
      +
    • qh_KEEPstatistics 0 turns off most statistics
    • +
    • Z..., W... integer (Z) and real (W) statistics +
    • +
    • ZZstat Z.../W... statistics that +remain defined if qh_KEEPstatistics=0 +
    • +
    • ztype zdoc, zinc, etc. +for definining statistics
    • +
    +

    »stat.h macros

    +
      +
    • MAYdebugx called +frequently for error trapping
    • +
    • zadd_/wadd_ add value +to an integer or real statistic
    • +
    • zdef_ define a +statistic
    • +
    • zinc_ increment an +integer statistic
    • +
    • zmax_/wmax_ update +integer or real maximum statistic
    • +
    • zmin_/wmin_ update +integer or real minimum statistic
    • +
    • zval_/wval_ set or +return value of a statistic
    • +
    + +

    »stat.c +functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-user.htm b/xs/src/qhull/src/libqhull/qh-user.htm new file mode 100644 index 0000000000..6682f4b2fb --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-user.htm @@ -0,0 +1,271 @@ + + + + +user.c -- user-definable operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    +

    user.c -- user-definable operations

    +
    +

    This section contains functions and constants that the +user may want to change.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to user.c, usermem.c, userprintf.c, userprintf_rbox.c and +user.h

    + + +

    »Qhull library constants

    + + + +

    »user.h data +types and configuration macros

    + + +

    »definition constants

    +
      +
    • qh_DEFAULTbox +define default box size for rbox, 'Qbb', and 'QbB' (Geomview expects 0.5)
    • +
    • qh_INFINITE on +output, indicates Voronoi center at infinity
    • +
    • qh_ORIENTclock +define convention for orienting facets
    • +
    • qh_ZEROdelaunay +define facets that are ignored in Delaunay triangulations
    • +
    + +

    »joggle constants

    + + +

    »performance +related constants

    + + +

    »memory constants

    + + +

    »conditional compilation

    +
      +
    • compiler defined symbols, +e.g., _STDC_ and _cplusplus + +
    • qh_COMPUTEfurthest + compute furthest distance to an outside point instead of storing it with the facet +
    • qh_KEEPstatistics + enable statistic gathering and reporting with option 'Ts' +
    • qh_MAXoutside +record outer plane for each facet +
    • qh_NOmerge +disable facet merging +
    • qh_NOtrace +disable tracing with option 'T4' +
    • qh_QHpointer +access global data with pointer or static structure +
    • qh_QUICKhelp +use abbreviated help messages, e.g., for degenerate inputs +
    + +

    »merge +constants

    + + +

    »user.c +functions

    + + +

    »usermem.c +functions

    +
      +
    • qh_exit exit program, same as exit(). May be redefined as throw "QH10003.." by libqhullcpp/usermem_r-cpp.cpp
    • +
    • qh_fprintf_stderr print to stderr when qh.ferr is not defined.
    • +
    • qh_free free memory, same as free().
    • +
    • qh_malloc allocate memory, same as malloc()
    • +
    + +

    »userprintf.c + and userprintf_rbox,c functions

    +
      +
    • qh_fprintf print +information from Qhull, sames as fprintf().
    • +
    • qh_fprintf_rbox print +information from Rbox, sames as fprintf().
    • +
    + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qhull-exports.def b/xs/src/qhull/src/libqhull/qhull-exports.def new file mode 100644 index 0000000000..11a42b57ee --- /dev/null +++ b/xs/src/qhull/src/libqhull/qhull-exports.def @@ -0,0 +1,417 @@ +; qhull-exports.def -- msvc module-definition file +; +; Generated from depends.exe by cut-and-paste of exported symbols by mingw gcc +; [mar'11] 399 symbols +; Annotate as DATA qh_last_random qh_qh qh_qhstat qhmem rbox rbox_inuse +; Annotate as __declspec for outside access in win32 -- qh_qh qh_qhstat +; Same as ../libqhullp/qhull_p-exports.def without qh_save_qhull and qh_restore_qhull +; +; $Id: //main/2015/qhull/src/libqhull/qhull-exports.def#3 $$Change: 2047 $ +; $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $ +; +; Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, and qhull-warn.pri +VERSION 7.0 +EXPORTS +qh_addhash +qh_addpoint +qh_all_merges +qh_allstatA +qh_allstatB +qh_allstatC +qh_allstatD +qh_allstatE +qh_allstatE2 +qh_allstatF +qh_allstatG +qh_allstatH +qh_allstatI +qh_allstatistics +qh_appendfacet +qh_appendmergeset +qh_appendprint +qh_appendvertex +qh_argv_to_command +qh_argv_to_command_size +qh_attachnewfacets +qh_backnormal +qh_basevertices +qh_build_withrestart +qh_buildhull +qh_buildtracing +qh_check_bestdist +qh_check_dupridge +qh_check_maxout +qh_check_output +qh_check_point +qh_check_points +qh_checkconnect +qh_checkconvex +qh_checkfacet +qh_checkflags +qh_checkflipped +qh_checkflipped_all +qh_checkpolygon +qh_checkvertex +qh_checkzero +qh_clear_outputflags +qh_clearcenters +qh_clock +qh_collectstatistics +qh_compare_facetarea +qh_compare_facetmerge +qh_compare_facetvisit +qh_compare_vertexpoint +qh_compareangle +qh_comparemerge +qh_comparevisit +qh_copyfilename +qh_copynonconvex +qh_copypoints +qh_countfacets +qh_createsimplex +qh_crossproduct +qh_degen_redundant_facet +qh_degen_redundant_neighbors +qh_deletevisible +qh_delfacet +qh_delridge +qh_delvertex +qh_determinant +qh_detjoggle +qh_detroundoff +qh_detsimplex +qh_detvnorm +qh_detvridge +qh_detvridge3 +qh_dfacet +qh_distnorm +qh_distplane +qh_distround +qh_divzero +qh_dvertex +qh_eachvoronoi +qh_eachvoronoi_all +qh_errexit +qh_errexit2 +qh_errexit_rbox +qh_errprint +qh_exit +qh_facet2point +qh_facet3vertex +qh_facetarea +qh_facetarea_simplex +qh_facetcenter +qh_facetintersect +qh_facetvertices +qh_find_newvertex +qh_findbest +qh_findbest_test +qh_findbestfacet +qh_findbesthorizon +qh_findbestlower +qh_findbestneighbor +qh_findbestnew +qh_findfacet_all +qh_findgood +qh_findgood_all +qh_findgooddist +qh_findhorizon +qh_flippedmerges +qh_forcedmerges +qh_fprintf +qh_fprintf_rbox +qh_fprintf_stderr +qh_free +qh_freebuffers +qh_freebuild +qh_freeqhull +qh_freeqhull2 +qh_freestatistics +qh_furthestnext +qh_furthestout +qh_gausselim +qh_geomplanes +qh_getangle +qh_getarea +qh_getcenter +qh_getcentrum +qh_getdistance +qh_gethash +qh_getmergeset +qh_getmergeset_initial +qh_gram_schmidt +qh_hashridge +qh_hashridge_find +qh_infiniteloop +qh_init_A +qh_init_B +qh_init_qhull_command +qh_initbuild +qh_initflags +qh_initialhull +qh_initialvertices +qh_initqhull_buffers +qh_initqhull_globals +qh_initqhull_mem +qh_initqhull_outputflags +qh_initqhull_start +qh_initqhull_start2 +qh_initstatistics +qh_initthresholds +qh_inthresholds +qh_isvertex +qh_joggleinput +; Mark as DATA, otherwise links a separate qh_last_random. No __declspec. +qh_last_random DATA +qh_lib_check +qh_makenew_nonsimplicial +qh_makenew_simplicial +qh_makenewfacet +qh_makenewfacets +qh_makenewplanes +qh_makeridges +qh_malloc +qh_mark_dupridges +qh_markkeep +qh_markvoronoi +qh_matchduplicates +qh_matchneighbor +qh_matchnewfacets +qh_matchvertices +qh_maxabsval +qh_maxmin +qh_maxouter +qh_maxsimplex +qh_maydropneighbor +qh_memalloc +qh_memfree +qh_memfreeshort +qh_meminit +qh_meminitbuffers +qh_memsetup +qh_memsize +qh_memstatistics +qh_memtotal +qh_merge_degenredundant +qh_merge_nonconvex +qh_mergecycle +qh_mergecycle_all +qh_mergecycle_facets +qh_mergecycle_neighbors +qh_mergecycle_ridges +qh_mergecycle_vneighbors +qh_mergefacet +qh_mergefacet2d +qh_mergeneighbors +qh_mergeridges +qh_mergesimplex +qh_mergevertex_del +qh_mergevertex_neighbors +qh_mergevertices +qh_minabsval +qh_mindiff +qh_nearcoplanar +qh_nearvertex +qh_neighbor_intersections +qh_new_qhull +qh_newfacet +qh_newhashtable +qh_newridge +qh_newstats +qh_newvertex +qh_newvertices +qh_nextfurthest +qh_nextridge3d +qh_normalize +qh_normalize2 +qh_nostatistic +qh_option +qh_order_vertexneighbors +qh_orientoutside +qh_out1 +qh_out2n +qh_out3n +qh_outcoplanar +qh_outerinner +qh_partitionall +qh_partitioncoplanar +qh_partitionpoint +qh_partitionvisible +qh_point +qh_point_add +qh_pointdist +qh_pointfacet +qh_pointid +qh_pointvertex +qh_postmerge +qh_precision +qh_premerge +qh_prepare_output +qh_prependfacet +qh_printafacet +qh_printallstatistics +qh_printbegin +qh_printcenter +qh_printcentrum +qh_printend +qh_printend4geom +qh_printextremes +qh_printextremes_2d +qh_printextremes_d +qh_printfacet +qh_printfacet2geom +qh_printfacet2geom_points +qh_printfacet2math +qh_printfacet3geom_nonsimplicial +qh_printfacet3geom_points +qh_printfacet3geom_simplicial +qh_printfacet3math +qh_printfacet3vertex +qh_printfacet4geom_nonsimplicial +qh_printfacet4geom_simplicial +qh_printfacetNvertex_nonsimplicial +qh_printfacetNvertex_simplicial +qh_printfacetheader +qh_printfacetlist +qh_printfacetridges +qh_printfacets +qh_printhashtable +qh_printhelp_degenerate +qh_printhelp_narrowhull +qh_printhelp_singular +qh_printhyperplaneintersection +qh_printline3geom +qh_printlists +qh_printmatrix +qh_printneighborhood +qh_printpoint +qh_printpoint3 +qh_printpointid +qh_printpoints +qh_printpoints_out +qh_printpointvect +qh_printpointvect2 +qh_printridge +qh_printspheres +qh_printstatistics +qh_printstatlevel +qh_printstats +qh_printsummary +qh_printvdiagram +qh_printvdiagram2 +qh_printvertex +qh_printvertexlist +qh_printvertices +qh_printvneighbors +qh_printvnorm +qh_printvoronoi +qh_printvridge +qh_produce_output +qh_produce_output2 +qh_projectdim3 +qh_projectinput +qh_projectpoint +qh_projectpoints +; Mark as DATA, otherwise links a separate qh_qh. qh_qh and qh_qhstat requires __declspec +qh_qh DATA +qh_qhstat DATA +qh_qhull +qh_rand +qh_randomfactor +qh_randommatrix +qh_rboxpoints +qh_readfeasible +qh_readpoints +qh_reducevertices +qh_redundant_vertex +qh_remove_extravertices +qh_removefacet +qh_removevertex +qh_rename_sharedvertex +qh_renameridgevertex +qh_renamevertex +qh_resetlists +qh_rotateinput +qh_rotatepoints +qh_roundi +qh_scaleinput +qh_scalelast +qh_scalepoints +qh_setaddnth +qh_setaddsorted +qh_setappend +qh_setappend2ndlast +qh_setappend_set +qh_setcheck +qh_setcompact +qh_setcopy +qh_setdel +qh_setdelaunay +qh_setdellast +qh_setdelnth +qh_setdelnthsorted +qh_setdelsorted +qh_setduplicate +qh_setequal +qh_setequal_except +qh_setequal_skip +qh_setfacetplane +qh_setfeasible +qh_setfree +qh_setfree2 +qh_setfreelong +qh_sethalfspace +qh_sethalfspace_all +qh_sethyperplane_det +qh_sethyperplane_gauss +qh_setin +qh_setindex +qh_setlarger +qh_setlast +qh_setnew +qh_setnew_delnthsorted +qh_setprint +qh_setreplace +qh_setsize +qh_settemp +qh_settempfree +qh_settempfree_all +qh_settemppop +qh_settemppush +qh_settruncate +qh_setunique +qh_setvoronoi_all +qh_setzero +qh_sharpnewfacets +qh_skipfacet +qh_skipfilename +qh_srand +qh_stddev +qh_strtod +qh_strtol +qh_test_appendmerge +qh_test_vneighbors +qh_tracemerge +qh_tracemerging +qh_triangulate +qh_triangulate_facet +qh_triangulate_link +qh_triangulate_mirror +qh_triangulate_null +qh_updatetested +qh_updatevertices +qh_user_memsizes +qh_version +qh_version2 +qh_vertexintersect +qh_vertexintersect_new +qh_vertexneighbors +qh_vertexridges +qh_vertexridges_facet +qh_vertexsubset +qh_voronoi_center +qh_willdelete +; Mark as DATA, otherwise links a separate qhmem. No __declspec +qhmem DATA +rbox DATA +rbox_inuse DATA diff --git a/xs/src/qhull/src/libqhull/qhull_a.h b/xs/src/qhull/src/libqhull/qhull_a.h new file mode 100644 index 0000000000..729b723276 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qhull_a.h @@ -0,0 +1,150 @@ +/*
      ---------------------------------
    +
    +   qhull_a.h
    +   all header files for compiling qhull with non-reentrant code
    +   included before C++ headers for user_r.h:QHULL_CRTDBG
    +
    +   see qh-qhull.htm
    +
    +   see libqhull.h for user-level definitions
    +
    +   see user.h for user-definable constants
    +
    +   defines internal functions for libqhull.c global.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/qhull_a.h#4 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +
    +   Notes:  grep for ((" and (" to catch fprintf("lkasdjf");
    +           full parens around (x?y:z)
    +           use '#include "libqhull/qhull_a.h"' to avoid name clashes
    +*/
    +
    +#ifndef qhDEFqhulla
    +#define qhDEFqhulla 1
    +
    +#include "libqhull.h"  /* Includes user_r.h and data types */
    +
    +#include "stat.h"
    +#include "random.h"
    +#include "mem.h"
    +#include "qset.h"
    +#include "geom.h"
    +#include "merge.h"
    +#include "poly.h"
    +#include "io.h"
    +
    +#include 
    +#include 
    +#include 
    +#include     /* some compilers will not need float.h */
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +/*** uncomment here and qset.c
    +     if string.h does not define memcpy()
    +#include 
    +*/
    +
    +#if qh_CLOCKtype == 2  /* defined in user.h from libqhull.h */
    +#include 
    +#include 
    +#include 
    +#endif
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4100)  /* unreferenced formal parameter */
    +#pragma warning( disable : 4127)  /* conditional expression is constant */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#pragma warning( disable : 4996)  /* function was declared deprecated(strcpy, localtime, etc.) */
    +#endif
    +
    +/* ======= -macros- =========== */
    +
    +/*----------------------------------
    +
    +  traceN((qh ferr, 0Nnnn, "format\n", vars));
    +    calls qh_fprintf if qh.IStracing >= N
    +
    +    Add debugging traps to the end of qh_fprintf
    +
    +  notes:
    +    removing tracing reduces code size but doesn't change execution speed
    +*/
    +#ifndef qh_NOtrace
    +#define trace0(args) {if (qh IStracing) qh_fprintf args;}
    +#define trace1(args) {if (qh IStracing >= 1) qh_fprintf args;}
    +#define trace2(args) {if (qh IStracing >= 2) qh_fprintf args;}
    +#define trace3(args) {if (qh IStracing >= 3) qh_fprintf args;}
    +#define trace4(args) {if (qh IStracing >= 4) qh_fprintf args;}
    +#define trace5(args) {if (qh IStracing >= 5) qh_fprintf args;}
    +#else /* qh_NOtrace */
    +#define trace0(args) {}
    +#define trace1(args) {}
    +#define trace2(args) {}
    +#define trace3(args) {}
    +#define trace4(args) {}
    +#define trace5(args) {}
    +#endif /* qh_NOtrace */
    +
    +/*----------------------------------
    +
    +  Define an unused variable to avoid compiler warnings
    +
    +  Derived from Qt's corelib/global/qglobal.h
    +
    +*/
    +
    +#if defined(__cplusplus) && defined(__INTEL_COMPILER) && !defined(QHULL_OS_WIN)
    +template 
    +inline void qhullUnused(T &x) { (void)x; }
    +#  define QHULL_UNUSED(x) qhullUnused(x);
    +#else
    +#  define QHULL_UNUSED(x) (void)x;
    +#endif
    +
    +/***** -libqhull.c prototypes (alphabetical after qhull) ********************/
    +
    +void    qh_qhull(void);
    +boolT   qh_addpoint(pointT *furthest, facetT *facet, boolT checkdist);
    +void    qh_buildhull(void);
    +void    qh_buildtracing(pointT *furthest, facetT *facet);
    +void    qh_build_withrestart(void);
    +void    qh_errexit2(int exitcode, facetT *facet, facetT *otherfacet);
    +void    qh_findhorizon(pointT *point, facetT *facet, int *goodvisible,int *goodhorizon);
    +pointT *qh_nextfurthest(facetT **visible);
    +void    qh_partitionall(setT *vertices, pointT *points,int npoints);
    +void    qh_partitioncoplanar(pointT *point, facetT *facet, realT *dist);
    +void    qh_partitionpoint(pointT *point, facetT *facet);
    +void    qh_partitionvisible(boolT allpoints, int *numpoints);
    +void    qh_precision(const char *reason);
    +void    qh_printsummary(FILE *fp);
    +
    +/***** -global.c internal prototypes (alphabetical) ***********************/
    +
    +void    qh_appendprint(qh_PRINT format);
    +void    qh_freebuild(boolT allmem);
    +void    qh_freebuffers(void);
    +void    qh_initbuffers(coordT *points, int numpoints, int dim, boolT ismalloc);
    +
    +/***** -stat.c internal prototypes (alphabetical) ***********************/
    +
    +void    qh_allstatA(void);
    +void    qh_allstatB(void);
    +void    qh_allstatC(void);
    +void    qh_allstatD(void);
    +void    qh_allstatE(void);
    +void    qh_allstatE2(void);
    +void    qh_allstatF(void);
    +void    qh_allstatG(void);
    +void    qh_allstatH(void);
    +void    qh_freebuffers(void);
    +void    qh_initbuffers(coordT *points, int numpoints, int dim, boolT ismalloc);
    +
    +#endif /* qhDEFqhulla */
    diff --git a/xs/src/qhull/src/libqhull/qhull_p-exports.def b/xs/src/qhull/src/libqhull/qhull_p-exports.def
    new file mode 100644
    index 0000000000..cadf8a4fa2
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/qhull_p-exports.def
    @@ -0,0 +1,418 @@
    +; qhull_p-exports.def -- msvc module-definition file
    +;
    +;   Generated from depends.exe by cut-and-paste of exported symbols by mingw gcc
    +;   [mar'11] 399 symbols [jan'15] added 3 symbols
    +;   Annotate as DATA qh_last_random qh_qh qh_qhstat qhmem rbox rbox_inuse
    +;   Annotate as __declspec for outside access in win32 -- qh_qh qh_qhstat
    +;
    +; $Id: //main/2011/qhull/src/libqhull/qhull-exports.def#2 $$Change: 1368 $
    +; $DateTime: 2011/04/16 08:12:32 $$Author: bbarber $
    +;
    +; Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, and qhull-warn.pri
    +VERSION 7.0
    +EXPORTS
    +qh_addhash
    +qh_addpoint
    +qh_all_merges
    +qh_allstatA
    +qh_allstatB
    +qh_allstatC
    +qh_allstatD
    +qh_allstatE
    +qh_allstatE2
    +qh_allstatF
    +qh_allstatG
    +qh_allstatH
    +qh_allstatI
    +qh_allstatistics
    +qh_appendfacet
    +qh_appendmergeset
    +qh_appendprint
    +qh_appendvertex
    +qh_argv_to_command
    +qh_argv_to_command_size
    +qh_attachnewfacets
    +qh_backnormal
    +qh_basevertices
    +qh_build_withrestart
    +qh_buildhull
    +qh_buildtracing
    +qh_check_bestdist
    +qh_check_dupridge
    +qh_check_maxout
    +qh_check_output
    +qh_check_point
    +qh_check_points
    +qh_checkconnect
    +qh_checkconvex
    +qh_checkfacet
    +qh_checkflags
    +qh_checkflipped
    +qh_checkflipped_all
    +qh_checkpolygon
    +qh_checkvertex
    +qh_checkzero
    +qh_clear_outputflags
    +qh_clearcenters
    +qh_clock
    +qh_collectstatistics
    +qh_compare_facetarea
    +qh_compare_facetmerge
    +qh_compare_facetvisit
    +qh_compare_vertexpoint
    +qh_compareangle
    +qh_comparemerge
    +qh_comparevisit
    +qh_copyfilename
    +qh_copynonconvex
    +qh_copypoints
    +qh_countfacets
    +qh_createsimplex
    +qh_crossproduct
    +qh_degen_redundant_facet
    +qh_degen_redundant_neighbors
    +qh_deletevisible
    +qh_delfacet
    +qh_delridge
    +qh_delvertex
    +qh_determinant
    +qh_detjoggle
    +qh_detroundoff
    +qh_detsimplex
    +qh_detvnorm
    +qh_detvridge
    +qh_detvridge3
    +qh_dfacet
    +qh_distnorm
    +qh_distplane
    +qh_distround
    +qh_divzero
    +qh_dvertex
    +qh_eachvoronoi
    +qh_eachvoronoi_all
    +qh_errexit
    +qh_errexit2
    +qh_errexit_rbox
    +qh_errprint
    +qh_exit
    +qh_facet2point
    +qh_facet3vertex
    +qh_facetarea
    +qh_facetarea_simplex
    +qh_facetcenter
    +qh_facetintersect
    +qh_facetvertices
    +qh_find_newvertex
    +qh_findbest
    +qh_findbest_test
    +qh_findbestfacet
    +qh_findbesthorizon
    +qh_findbestlower
    +qh_findbestneighbor
    +qh_findbestnew
    +qh_findfacet_all
    +qh_findgood
    +qh_findgood_all
    +qh_findgooddist
    +qh_findhorizon
    +qh_flippedmerges
    +qh_forcedmerges
    +qh_fprintf
    +qh_fprintf_rbox
    +qh_fprintf_stderr
    +qh_free
    +qh_freebuffers
    +qh_freebuild
    +qh_freeqhull
    +qh_freeqhull2
    +qh_freestatistics
    +qh_furthestnext
    +qh_furthestout
    +qh_gausselim
    +qh_geomplanes
    +qh_getangle
    +qh_getarea
    +qh_getcenter
    +qh_getcentrum
    +qh_getdistance
    +qh_gethash
    +qh_getmergeset
    +qh_getmergeset_initial
    +qh_gram_schmidt
    +qh_hashridge
    +qh_hashridge_find
    +qh_infiniteloop
    +qh_init_A
    +qh_init_B
    +qh_init_qhull_command
    +qh_initbuild
    +qh_initflags
    +qh_initialhull
    +qh_initialvertices
    +qh_initqhull_buffers
    +qh_initqhull_globals
    +qh_initqhull_mem
    +qh_initqhull_outputflags
    +qh_initqhull_start
    +qh_initqhull_start2
    +qh_initstatistics
    +qh_initthresholds
    +qh_inthresholds
    +qh_isvertex
    +qh_joggleinput
    +; Mark as DATA, otherwise links a separate qh_last_random.  No __declspec.
    +qh_last_random      DATA
    +qh_lib_check
    +qh_makenew_nonsimplicial
    +qh_makenew_simplicial
    +qh_makenewfacet
    +qh_makenewfacets
    +qh_makenewplanes
    +qh_makeridges
    +qh_malloc
    +qh_mark_dupridges
    +qh_markkeep
    +qh_markvoronoi
    +qh_matchduplicates
    +qh_matchneighbor
    +qh_matchnewfacets
    +qh_matchvertices
    +qh_maxabsval
    +qh_maxmin
    +qh_maxouter
    +qh_maxsimplex
    +qh_maydropneighbor
    +qh_memalloc
    +qh_memfree
    +qh_memfreeshort
    +qh_meminit
    +qh_meminitbuffers
    +qh_memsetup
    +qh_memsize
    +qh_memstatistics
    +qh_memtotal
    +qh_merge_degenredundant
    +qh_merge_nonconvex
    +qh_mergecycle
    +qh_mergecycle_all
    +qh_mergecycle_facets
    +qh_mergecycle_neighbors
    +qh_mergecycle_ridges
    +qh_mergecycle_vneighbors
    +qh_mergefacet
    +qh_mergefacet2d
    +qh_mergeneighbors
    +qh_mergeridges
    +qh_mergesimplex
    +qh_mergevertex_del
    +qh_mergevertex_neighbors
    +qh_mergevertices
    +qh_minabsval
    +qh_mindiff
    +qh_nearcoplanar
    +qh_nearvertex
    +qh_neighbor_intersections
    +qh_new_qhull
    +qh_newfacet
    +qh_newhashtable
    +qh_newridge
    +qh_newstats
    +qh_newvertex
    +qh_newvertices
    +qh_nextfurthest
    +qh_nextridge3d
    +qh_normalize
    +qh_normalize2
    +qh_nostatistic
    +qh_option
    +qh_order_vertexneighbors
    +qh_orientoutside
    +qh_out1
    +qh_out2n
    +qh_out3n
    +qh_outcoplanar
    +qh_outerinner
    +qh_partitionall
    +qh_partitioncoplanar
    +qh_partitionpoint
    +qh_partitionvisible
    +qh_point
    +qh_point_add
    +qh_pointdist
    +qh_pointfacet
    +qh_pointid
    +qh_pointvertex
    +qh_postmerge
    +qh_precision
    +qh_premerge
    +qh_prepare_output
    +qh_prependfacet
    +qh_printafacet
    +qh_printallstatistics
    +qh_printbegin
    +qh_printcenter
    +qh_printcentrum
    +qh_printend
    +qh_printend4geom
    +qh_printextremes
    +qh_printextremes_2d
    +qh_printextremes_d
    +qh_printfacet
    +qh_printfacet2geom
    +qh_printfacet2geom_points
    +qh_printfacet2math
    +qh_printfacet3geom_nonsimplicial
    +qh_printfacet3geom_points
    +qh_printfacet3geom_simplicial
    +qh_printfacet3math
    +qh_printfacet3vertex
    +qh_printfacet4geom_nonsimplicial
    +qh_printfacet4geom_simplicial
    +qh_printfacetNvertex_nonsimplicial
    +qh_printfacetNvertex_simplicial
    +qh_printfacetheader
    +qh_printfacetlist
    +qh_printfacetridges
    +qh_printfacets
    +qh_printhashtable
    +qh_printhelp_degenerate
    +qh_printhelp_narrowhull
    +qh_printhelp_singular
    +qh_printhyperplaneintersection
    +qh_printline3geom
    +qh_printlists
    +qh_printmatrix
    +qh_printneighborhood
    +qh_printpoint
    +qh_printpoint3
    +qh_printpointid
    +qh_printpoints
    +qh_printpoints_out
    +qh_printpointvect
    +qh_printpointvect2
    +qh_printridge
    +qh_printspheres
    +qh_printstatistics
    +qh_printstatlevel
    +qh_printstats
    +qh_printsummary
    +qh_printvdiagram
    +qh_printvdiagram2
    +qh_printvertex
    +qh_printvertexlist
    +qh_printvertices
    +qh_printvneighbors
    +qh_printvnorm
    +qh_printvoronoi
    +qh_printvridge
    +qh_produce_output
    +qh_produce_output2
    +qh_projectdim3
    +qh_projectinput
    +qh_projectpoint
    +qh_projectpoints
    +; Mark as DATA, otherwise links a separate qh_qh.  qh_qh and qh_qhstat requires __declspec
    +qh_qh               DATA
    +qh_qhstat           DATA
    +qh_qhull
    +qh_rand
    +qh_randomfactor
    +qh_randommatrix
    +qh_rboxpoints
    +qh_readfeasible
    +qh_readpoints
    +qh_reducevertices
    +qh_redundant_vertex
    +qh_remove_extravertices
    +qh_removefacet
    +qh_removevertex
    +qh_rename_sharedvertex
    +qh_renameridgevertex
    +qh_renamevertex
    +qh_resetlists
    +qh_restore_qhull
    +qh_rotateinput
    +qh_rotatepoints
    +qh_roundi
    +qh_save_qhull
    +qh_scaleinput
    +qh_scalelast
    +qh_scalepoints
    +qh_setaddnth
    +qh_setaddsorted
    +qh_setappend
    +qh_setappend2ndlast
    +qh_setappend_set
    +qh_setcheck
    +qh_setcompact
    +qh_setcopy
    +qh_setdel
    +qh_setdelaunay
    +qh_setdellast
    +qh_setdelnth
    +qh_setdelnthsorted
    +qh_setdelsorted
    +qh_setduplicate
    +qh_setequal
    +qh_setequal_except
    +qh_setequal_skip
    +qh_setfacetplane
    +qh_setfeasible
    +qh_setfree
    +qh_setfree2
    +qh_setfreelong
    +qh_sethalfspace
    +qh_sethalfspace_all
    +qh_sethyperplane_det
    +qh_sethyperplane_gauss
    +qh_setin
    +qh_setindex
    +qh_setlarger
    +qh_setlast
    +qh_setnew
    +qh_setnew_delnthsorted
    +qh_setprint
    +qh_setreplace
    +qh_setsize
    +qh_settemp
    +qh_settempfree
    +qh_settempfree_all
    +qh_settemppop
    +qh_settemppush
    +qh_settruncate
    +qh_setunique
    +qh_setvoronoi_all
    +qh_setzero
    +qh_sharpnewfacets
    +qh_skipfacet
    +qh_skipfilename
    +qh_srand
    +qh_stddev
    +qh_strtod
    +qh_strtol
    +qh_test_appendmerge
    +qh_test_vneighbors
    +qh_tracemerge
    +qh_tracemerging
    +qh_triangulate
    +qh_triangulate_facet
    +qh_triangulate_link
    +qh_triangulate_mirror
    +qh_triangulate_null
    +qh_updatetested
    +qh_updatevertices
    +qh_user_memsizes
    +qh_version
    +qh_version2
    +qh_vertexintersect
    +qh_vertexintersect_new
    +qh_vertexneighbors
    +qh_vertexridges
    +qh_vertexridges_facet
    +qh_vertexsubset
    +qh_voronoi_center
    +qh_willdelete
    +; Mark as DATA, otherwise links a separate qhmem.  No __declspec
    +qhmem                   DATA
    +rbox			DATA
    +rbox_inuse              DATA
    diff --git a/xs/src/qhull/src/libqhull/qset.c b/xs/src/qhull/src/libqhull/qset.c
    new file mode 100644
    index 0000000000..a969252a75
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/qset.c
    @@ -0,0 +1,1340 @@
    +/*
      ---------------------------------
    +
    +   qset.c
    +   implements set manipulations needed for quickhull
    +
    +   see qh-set.htm and qset.h
    +
    +   Be careful of strict aliasing (two pointers of different types
    +   that reference the same location).  The last slot of a set is
    +   either the actual size of the set plus 1, or the NULL terminator
    +   of the set (i.e., setelemT).
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/qset.c#3 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "user.h" /* for QHULL_CRTDBG */
    +#include "qset.h"
    +#include "mem.h"
    +#include 
    +#include 
    +/*** uncomment here and qhull_a.h
    +     if string.h does not define memcpy()
    +#include 
    +*/
    +
    +#ifndef qhDEFlibqhull
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +void    qh_errexit(int exitcode, facetT *, ridgeT *);
    +void    qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... );
    +#  ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#  pragma warning( disable : 4127)  /* conditional expression is constant */
    +#  pragma warning( disable : 4706)  /* assignment within conditional function */
    +#  endif
    +#endif
    +
    +/*=============== internal macros ===========================*/
    +
    +/*============ functions in alphabetical order ===================*/
    +
    +/*----------------------------------
    +
    +  qh_setaddnth( setp, nth, newelem)
    +    adds newelem as n'th element of sorted or unsorted *setp
    +
    +  notes:
    +    *setp and newelem must be defined
    +    *setp may be a temp set
    +    nth=0 is first element
    +    errors if nth is out of bounds
    +
    +  design:
    +    expand *setp if empty or full
    +    move tail of *setp up one
    +    insert newelem
    +*/
    +void qh_setaddnth(setT **setp, int nth, void *newelem) {
    +  int oldsize, i;
    +  setelemT *sizep;          /* avoid strict aliasing */
    +  setelemT *oldp, *newp;
    +
    +  if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +    qh_setlarger(setp);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  oldsize= sizep->i - 1;
    +  if (nth < 0 || nth > oldsize) {
    +    qh_fprintf(qhmem.ferr, 6171, "qhull internal error (qh_setaddnth): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qhmem.ferr, "", *setp);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  sizep->i++;
    +  oldp= (setelemT *)SETelemaddr_(*setp, oldsize, void);   /* NULL */
    +  newp= oldp+1;
    +  for (i=oldsize-nth+1; i--; )  /* move at least NULL  */
    +    (newp--)->p= (oldp--)->p;       /* may overwrite *sizep */
    +  newp->p= newelem;
    +} /* setaddnth */
    +
    +
    +/*----------------------------------
    +
    +  setaddsorted( setp, newelem )
    +    adds an newelem into sorted *setp
    +
    +  notes:
    +    *setp and newelem must be defined
    +    *setp may be a temp set
    +    nop if newelem already in set
    +
    +  design:
    +    find newelem's position in *setp
    +    insert newelem
    +*/
    +void qh_setaddsorted(setT **setp, void *newelem) {
    +  int newindex=0;
    +  void *elem, **elemp;
    +
    +  FOREACHelem_(*setp) {          /* could use binary search instead */
    +    if (elem < newelem)
    +      newindex++;
    +    else if (elem == newelem)
    +      return;
    +    else
    +      break;
    +  }
    +  qh_setaddnth(setp, newindex, newelem);
    +} /* setaddsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setappend( setp, newelem)
    +    append newelem to *setp
    +
    +  notes:
    +    *setp may be a temp set
    +    *setp and newelem may be NULL
    +
    +  design:
    +    expand *setp if empty or full
    +    append newelem to *setp
    +
    +*/
    +void qh_setappend(setT **setp, void *newelem) {
    +  setelemT *sizep;  /* Avoid strict aliasing.  Writing to *endp may overwrite *sizep */
    +  setelemT *endp;
    +  int count;
    +
    +  if (!newelem)
    +    return;
    +  if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +    qh_setlarger(setp);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  count= (sizep->i)++ - 1;
    +  endp= (setelemT *)SETelemaddr_(*setp, count, void);
    +  (endp++)->p= newelem;
    +  endp->p= NULL;
    +} /* setappend */
    +
    +/*---------------------------------
    +
    +  qh_setappend_set( setp, setA)
    +    appends setA to *setp
    +
    +  notes:
    +    *setp can not be a temp set
    +    *setp and setA may be NULL
    +
    +  design:
    +    setup for copy
    +    expand *setp if it is too small
    +    append all elements of setA to *setp
    +*/
    +void qh_setappend_set(setT **setp, setT *setA) {
    +  int sizeA, size;
    +  setT *oldset;
    +  setelemT *sizep;
    +
    +  if (!setA)
    +    return;
    +  SETreturnsize_(setA, sizeA);
    +  if (!*setp)
    +    *setp= qh_setnew(sizeA);
    +  sizep= SETsizeaddr_(*setp);
    +  if (!(size= sizep->i))
    +    size= (*setp)->maxsize;
    +  else
    +    size--;
    +  if (size + sizeA > (*setp)->maxsize) {
    +    oldset= *setp;
    +    *setp= qh_setcopy(oldset, sizeA);
    +    qh_setfree(&oldset);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  if (sizeA > 0) {
    +    sizep->i= size+sizeA+1;   /* memcpy may overwrite */
    +    memcpy((char *)&((*setp)->e[size].p), (char *)&(setA->e[0].p), (size_t)(sizeA+1) * SETelemsize);
    +  }
    +} /* setappend_set */
    +
    +
    +/*---------------------------------
    +
    +  qh_setappend2ndlast( setp, newelem )
    +    makes newelem the next to the last element in *setp
    +
    +  notes:
    +    *setp must have at least one element
    +    newelem must be defined
    +    *setp may be a temp set
    +
    +  design:
    +    expand *setp if empty or full
    +    move last element of *setp up one
    +    insert newelem
    +*/
    +void qh_setappend2ndlast(setT **setp, void *newelem) {
    +    setelemT *sizep;  /* Avoid strict aliasing.  Writing to *endp may overwrite *sizep */
    +    setelemT *endp, *lastp;
    +    int count;
    +
    +    if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +        qh_setlarger(setp);
    +        sizep= SETsizeaddr_(*setp);
    +    }
    +    count= (sizep->i)++ - 1;
    +    endp= (setelemT *)SETelemaddr_(*setp, count, void); /* NULL */
    +    lastp= endp-1;
    +    *(endp++)= *lastp;
    +    endp->p= NULL;    /* may overwrite *sizep */
    +    lastp->p= newelem;
    +} /* setappend2ndlast */
    +
    +/*---------------------------------
    +
    +  qh_setcheck( set, typename, id )
    +    check set for validity
    +    report errors with typename and id
    +
    +  design:
    +    checks that maxsize, actual size, and NULL terminator agree
    +*/
    +void qh_setcheck(setT *set, const char *tname, unsigned id) {
    +  int maxsize, size;
    +  int waserr= 0;
    +
    +  if (!set)
    +    return;
    +  SETreturnsize_(set, size);
    +  maxsize= set->maxsize;
    +  if (size > maxsize || !maxsize) {
    +    qh_fprintf(qhmem.ferr, 6172, "qhull internal error (qh_setcheck): actual size %d of %s%d is greater than max size %d\n",
    +             size, tname, id, maxsize);
    +    waserr= 1;
    +  }else if (set->e[size].p) {
    +    qh_fprintf(qhmem.ferr, 6173, "qhull internal error (qh_setcheck): %s%d(size %d max %d) is not null terminated.\n",
    +             tname, id, size-1, maxsize);
    +    waserr= 1;
    +  }
    +  if (waserr) {
    +    qh_setprint(qhmem.ferr, "ERRONEOUS", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +} /* setcheck */
    +
    +
    +/*---------------------------------
    +
    +  qh_setcompact( set )
    +    remove internal NULLs from an unsorted set
    +
    +  returns:
    +    updated set
    +
    +  notes:
    +    set may be NULL
    +    it would be faster to swap tail of set into holes, like qh_setdel
    +
    +  design:
    +    setup pointers into set
    +    skip NULLs while copying elements to start of set
    +    update the actual size
    +*/
    +void qh_setcompact(setT *set) {
    +  int size;
    +  void **destp, **elemp, **endp, **firstp;
    +
    +  if (!set)
    +    return;
    +  SETreturnsize_(set, size);
    +  destp= elemp= firstp= SETaddr_(set, void);
    +  endp= destp + size;
    +  while (1) {
    +    if (!(*destp++ = *elemp++)) {
    +      destp--;
    +      if (elemp > endp)
    +        break;
    +    }
    +  }
    +  qh_settruncate(set, (int)(destp-firstp));   /* WARN64 */
    +} /* setcompact */
    +
    +
    +/*---------------------------------
    +
    +  qh_setcopy( set, extra )
    +    make a copy of a sorted or unsorted set with extra slots
    +
    +  returns:
    +    new set
    +
    +  design:
    +    create a newset with extra slots
    +    copy the elements to the newset
    +
    +*/
    +setT *qh_setcopy(setT *set, int extra) {
    +  setT *newset;
    +  int size;
    +
    +  if (extra < 0)
    +    extra= 0;
    +  SETreturnsize_(set, size);
    +  newset= qh_setnew(size+extra);
    +  SETsizeaddr_(newset)->i= size+1;    /* memcpy may overwrite */
    +  memcpy((char *)&(newset->e[0].p), (char *)&(set->e[0].p), (size_t)(size+1) * SETelemsize);
    +  return(newset);
    +} /* setcopy */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdel( set, oldelem )
    +    delete oldelem from an unsorted set
    +
    +  returns:
    +    returns oldelem if found
    +    returns NULL otherwise
    +
    +  notes:
    +    set may be NULL
    +    oldelem must not be NULL;
    +    only deletes one copy of oldelem in set
    +
    +  design:
    +    locate oldelem
    +    update actual size if it was full
    +    move the last element to the oldelem's location
    +*/
    +void *qh_setdel(setT *set, void *oldelem) {
    +  setelemT *sizep;
    +  setelemT *elemp;
    +  setelemT *lastp;
    +
    +  if (!set)
    +    return NULL;
    +  elemp= (setelemT *)SETaddr_(set, void);
    +  while (elemp->p != oldelem && elemp->p)
    +    elemp++;
    +  if (elemp->p) {
    +    sizep= SETsizeaddr_(set);
    +    if (!(sizep->i)--)         /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (maxsize-1)+ 1 */
    +    lastp= (setelemT *)SETelemaddr_(set, sizep->i-1, void);
    +    elemp->p= lastp->p;      /* may overwrite itself */
    +    lastp->p= NULL;
    +    return oldelem;
    +  }
    +  return NULL;
    +} /* setdel */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdellast( set)
    +    return last element of set or NULL
    +
    +  notes:
    +    deletes element from set
    +    set may be NULL
    +
    +  design:
    +    return NULL if empty
    +    if full set
    +      delete last element and set actual size
    +    else
    +      delete last element and update actual size
    +*/
    +void *qh_setdellast(setT *set) {
    +  int setsize;  /* actually, actual_size + 1 */
    +  int maxsize;
    +  setelemT *sizep;
    +  void *returnvalue;
    +
    +  if (!set || !(set->e[0].p))
    +    return NULL;
    +  sizep= SETsizeaddr_(set);
    +  if ((setsize= sizep->i)) {
    +    returnvalue= set->e[setsize - 2].p;
    +    set->e[setsize - 2].p= NULL;
    +    sizep->i--;
    +  }else {
    +    maxsize= set->maxsize;
    +    returnvalue= set->e[maxsize - 1].p;
    +    set->e[maxsize - 1].p= NULL;
    +    sizep->i= maxsize;
    +  }
    +  return returnvalue;
    +} /* setdellast */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelnth( set, nth )
    +    deletes nth element from unsorted set
    +    0 is first element
    +
    +  returns:
    +    returns the element (needs type conversion)
    +
    +  notes:
    +    errors if nth invalid
    +
    +  design:
    +    setup points and check nth
    +    delete nth element and overwrite with last element
    +*/
    +void *qh_setdelnth(setT *set, int nth) {
    +  void *elem;
    +  setelemT *sizep;
    +  setelemT *elemp, *lastp;
    +
    +  sizep= SETsizeaddr_(set);
    +  if ((sizep->i--)==0)         /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (maxsize-1)+ 1 */
    +  if (nth < 0 || nth >= sizep->i) {
    +    qh_fprintf(qhmem.ferr, 6174, "qhull internal error (qh_setdelnth): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  elemp= (setelemT *)SETelemaddr_(set, nth, void); /* nth valid by QH6174 */
    +  lastp= (setelemT *)SETelemaddr_(set, sizep->i-1, void);
    +  elem= elemp->p;
    +  elemp->p= lastp->p;      /* may overwrite itself */
    +  lastp->p= NULL;
    +  return elem;
    +} /* setdelnth */
    +
    +/*---------------------------------
    +
    +  qh_setdelnthsorted( set, nth )
    +    deletes nth element from sorted set
    +
    +  returns:
    +    returns the element (use type conversion)
    +
    +  notes:
    +    errors if nth invalid
    +
    +  see also:
    +    setnew_delnthsorted
    +
    +  design:
    +    setup points and check nth
    +    copy remaining elements down one
    +    update actual size
    +*/
    +void *qh_setdelnthsorted(setT *set, int nth) {
    +  void *elem;
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  sizep= SETsizeaddr_(set);
    +  if (nth < 0 || (sizep->i && nth >= sizep->i-1) || nth >= set->maxsize) {
    +    qh_fprintf(qhmem.ferr, 6175, "qhull internal error (qh_setdelnthsorted): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  newp= (setelemT *)SETelemaddr_(set, nth, void);
    +  elem= newp->p;
    +  oldp= newp+1;
    +  while (((newp++)->p= (oldp++)->p))
    +    ; /* copy remaining elements and NULL */
    +  if ((sizep->i--)==0)         /*  if was a full set */
    +    sizep->i= set->maxsize;  /*     *sizep= (max size-1)+ 1 */
    +  return elem;
    +} /* setdelnthsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelsorted( set, oldelem )
    +    deletes oldelem from sorted set
    +
    +  returns:
    +    returns oldelem if it was deleted
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    locate oldelem in set
    +    copy remaining elements down one
    +    update actual size
    +*/
    +void *qh_setdelsorted(setT *set, void *oldelem) {
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  if (!set)
    +    return NULL;
    +  newp= (setelemT *)SETaddr_(set, void);
    +  while(newp->p != oldelem && newp->p)
    +    newp++;
    +  if (newp->p) {
    +    oldp= newp+1;
    +    while (((newp++)->p= (oldp++)->p))
    +      ; /* copy remaining elements */
    +    sizep= SETsizeaddr_(set);
    +    if ((sizep->i--)==0)    /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (max size-1)+ 1 */
    +    return oldelem;
    +  }
    +  return NULL;
    +} /* setdelsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setduplicate( set, elemsize )
    +    duplicate a set of elemsize elements
    +
    +  notes:
    +    use setcopy if retaining old elements
    +
    +  design:
    +    create a new set
    +    for each elem of the old set
    +      create a newelem
    +      append newelem to newset
    +*/
    +setT *qh_setduplicate(setT *set, int elemsize) {
    +  void          *elem, **elemp, *newElem;
    +  setT          *newSet;
    +  int           size;
    +
    +  if (!(size= qh_setsize(set)))
    +    return NULL;
    +  newSet= qh_setnew(size);
    +  FOREACHelem_(set) {
    +    newElem= qh_memalloc(elemsize);
    +    memcpy(newElem, elem, (size_t)elemsize);
    +    qh_setappend(&newSet, newElem);
    +  }
    +  return newSet;
    +} /* setduplicate */
    +
    +
    +/*---------------------------------
    +
    +  qh_setendpointer( set )
    +    Returns pointer to NULL terminator of a set's elements
    +    set can not be NULL
    +
    +*/
    +void **qh_setendpointer(setT *set) {
    +
    +  setelemT *sizep= SETsizeaddr_(set);
    +  int n= sizep->i;
    +  return (n ? &set->e[n-1].p : &sizep->p);
    +} /* qh_setendpointer */
    +
    +/*---------------------------------
    +
    +  qh_setequal( setA, setB )
    +    returns 1 if two sorted sets are equal, otherwise returns 0
    +
    +  notes:
    +    either set may be NULL
    +
    +  design:
    +    check size of each set
    +    setup pointers
    +    compare elements of each set
    +*/
    +int qh_setequal(setT *setA, setT *setB) {
    +  void **elemAp, **elemBp;
    +  int sizeA= 0, sizeB= 0;
    +
    +  if (setA) {
    +    SETreturnsize_(setA, sizeA);
    +  }
    +  if (setB) {
    +    SETreturnsize_(setB, sizeB);
    +  }
    +  if (sizeA != sizeB)
    +    return 0;
    +  if (!sizeA)
    +    return 1;
    +  elemAp= SETaddr_(setA, void);
    +  elemBp= SETaddr_(setB, void);
    +  if (!memcmp((char *)elemAp, (char *)elemBp, sizeA*SETelemsize))
    +    return 1;
    +  return 0;
    +} /* setequal */
    +
    +
    +/*---------------------------------
    +
    +  qh_setequal_except( setA, skipelemA, setB, skipelemB )
    +    returns 1 if sorted setA and setB are equal except for skipelemA & B
    +
    +  returns:
    +    false if either skipelemA or skipelemB are missing
    +
    +  notes:
    +    neither set may be NULL
    +
    +    if skipelemB is NULL,
    +      can skip any one element of setB
    +
    +  design:
    +    setup pointers
    +    search for skipelemA, skipelemB, and mismatches
    +    check results
    +*/
    +int qh_setequal_except(setT *setA, void *skipelemA, setT *setB, void *skipelemB) {
    +  void **elemA, **elemB;
    +  int skip=0;
    +
    +  elemA= SETaddr_(setA, void);
    +  elemB= SETaddr_(setB, void);
    +  while (1) {
    +    if (*elemA == skipelemA) {
    +      skip++;
    +      elemA++;
    +    }
    +    if (skipelemB) {
    +      if (*elemB == skipelemB) {
    +        skip++;
    +        elemB++;
    +      }
    +    }else if (*elemA != *elemB) {
    +      skip++;
    +      if (!(skipelemB= *elemB++))
    +        return 0;
    +    }
    +    if (!*elemA)
    +      break;
    +    if (*elemA++ != *elemB++)
    +      return 0;
    +  }
    +  if (skip != 2 || *elemB)
    +    return 0;
    +  return 1;
    +} /* setequal_except */
    +
    +
    +/*---------------------------------
    +
    +  qh_setequal_skip( setA, skipA, setB, skipB )
    +    returns 1 if sorted setA and setB are equal except for elements skipA & B
    +
    +  returns:
    +    false if different size
    +
    +  notes:
    +    neither set may be NULL
    +
    +  design:
    +    setup pointers
    +    search for mismatches while skipping skipA and skipB
    +*/
    +int qh_setequal_skip(setT *setA, int skipA, setT *setB, int skipB) {
    +  void **elemA, **elemB, **skipAp, **skipBp;
    +
    +  elemA= SETaddr_(setA, void);
    +  elemB= SETaddr_(setB, void);
    +  skipAp= SETelemaddr_(setA, skipA, void);
    +  skipBp= SETelemaddr_(setB, skipB, void);
    +  while (1) {
    +    if (elemA == skipAp)
    +      elemA++;
    +    if (elemB == skipBp)
    +      elemB++;
    +    if (!*elemA)
    +      break;
    +    if (*elemA++ != *elemB++)
    +      return 0;
    +  }
    +  if (*elemB)
    +    return 0;
    +  return 1;
    +} /* setequal_skip */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfree( setp )
    +    frees the space occupied by a sorted or unsorted set
    +
    +  returns:
    +    sets setp to NULL
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    free array
    +    free set
    +*/
    +void qh_setfree(setT **setp) {
    +  int size;
    +  void **freelistp;  /* used if !qh_NOmem by qh_memfree_() */
    +
    +  if (*setp) {
    +    size= sizeof(setT) + ((*setp)->maxsize)*SETelemsize;
    +    if (size <= qhmem.LASTsize) {
    +      qh_memfree_(*setp, size, freelistp);
    +    }else
    +      qh_memfree(*setp, size);
    +    *setp= NULL;
    +  }
    +} /* setfree */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfree2( setp, elemsize )
    +    frees the space occupied by a set and its elements
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    free each element
    +    free set
    +*/
    +void qh_setfree2(setT **setp, int elemsize) {
    +  void          *elem, **elemp;
    +
    +  FOREACHelem_(*setp)
    +    qh_memfree(elem, elemsize);
    +  qh_setfree(setp);
    +} /* setfree2 */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_setfreelong( setp )
    +    frees a set only if it's in long memory
    +
    +  returns:
    +    sets setp to NULL if it is freed
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    if set is large
    +      free it
    +*/
    +void qh_setfreelong(setT **setp) {
    +  int size;
    +
    +  if (*setp) {
    +    size= sizeof(setT) + ((*setp)->maxsize)*SETelemsize;
    +    if (size > qhmem.LASTsize) {
    +      qh_memfree(*setp, size);
    +      *setp= NULL;
    +    }
    +  }
    +} /* setfreelong */
    +
    +
    +/*---------------------------------
    +
    +  qh_setin( set, setelem )
    +    returns 1 if setelem is in a set, 0 otherwise
    +
    +  notes:
    +    set may be NULL or unsorted
    +
    +  design:
    +    scans set for setelem
    +*/
    +int qh_setin(setT *set, void *setelem) {
    +  void *elem, **elemp;
    +
    +  FOREACHelem_(set) {
    +    if (elem == setelem)
    +      return 1;
    +  }
    +  return 0;
    +} /* setin */
    +
    +
    +/*---------------------------------
    +
    +  qh_setindex( set, atelem )
    +    returns the index of atelem in set.
    +    returns -1, if not in set or maxsize wrong
    +
    +  notes:
    +    set may be NULL and may contain nulls.
    +    NOerrors returned (qh_pointid, QhullPoint::id)
    +
    +  design:
    +    checks maxsize
    +    scans set for atelem
    +*/
    +int qh_setindex(setT *set, void *atelem) {
    +  void **elem;
    +  int size, i;
    +
    +  if (!set)
    +    return -1;
    +  SETreturnsize_(set, size);
    +  if (size > set->maxsize)
    +    return -1;
    +  elem= SETaddr_(set, void);
    +  for (i=0; i < size; i++) {
    +    if (*elem++ == atelem)
    +      return i;
    +  }
    +  return -1;
    +} /* setindex */
    +
    +
    +/*---------------------------------
    +
    +  qh_setlarger( oldsetp )
    +    returns a larger set that contains all elements of *oldsetp
    +
    +  notes:
    +    the set is at least twice as large
    +    if temp set, updates qhmem.tempstack
    +
    +  design:
    +    creates a new set
    +    copies the old set to the new set
    +    updates pointers in tempstack
    +    deletes the old set
    +*/
    +void qh_setlarger(setT **oldsetp) {
    +  int size= 1;
    +  setT *newset, *set, **setp, *oldset;
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  if (*oldsetp) {
    +    oldset= *oldsetp;
    +    SETreturnsize_(oldset, size);
    +    qhmem.cntlarger++;
    +    qhmem.totlarger += size+1;
    +    newset= qh_setnew(2 * size);
    +    oldp= (setelemT *)SETaddr_(oldset, void);
    +    newp= (setelemT *)SETaddr_(newset, void);
    +    memcpy((char *)newp, (char *)oldp, (size_t)(size+1) * SETelemsize);
    +    sizep= SETsizeaddr_(newset);
    +    sizep->i= size+1;
    +    FOREACHset_((setT *)qhmem.tempstack) {
    +      if (set == oldset)
    +        *(setp-1)= newset;
    +    }
    +    qh_setfree(oldsetp);
    +  }else
    +    newset= qh_setnew(3);
    +  *oldsetp= newset;
    +} /* setlarger */
    +
    +
    +/*---------------------------------
    +
    +  qh_setlast( set )
    +    return last element of set or NULL (use type conversion)
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    return last element
    +*/
    +void *qh_setlast(setT *set) {
    +  int size;
    +
    +  if (set) {
    +    size= SETsizeaddr_(set)->i;
    +    if (!size)
    +      return SETelem_(set, set->maxsize - 1);
    +    else if (size > 1)
    +      return SETelem_(set, size - 2);
    +  }
    +  return NULL;
    +} /* setlast */
    +
    +
    +/*---------------------------------
    +
    +  qh_setnew( setsize )
    +    creates and allocates space for a set
    +
    +  notes:
    +    setsize means the number of elements (!including the NULL terminator)
    +    use qh_settemp/qh_setfreetemp if set is temporary
    +
    +  design:
    +    allocate memory for set
    +    roundup memory if small set
    +    initialize as empty set
    +*/
    +setT *qh_setnew(int setsize) {
    +  setT *set;
    +  int sizereceived; /* used if !qh_NOmem */
    +  int size;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  if (!setsize)
    +    setsize++;
    +  size= sizeof(setT) + setsize * SETelemsize;
    +  if (size>0 && size <= qhmem.LASTsize) {
    +    qh_memalloc_(size, freelistp, set, setT);
    +#ifndef qh_NOmem
    +    sizereceived= qhmem.sizetable[ qhmem.indextable[size]];
    +    if (sizereceived > size)
    +      setsize += (sizereceived - size)/SETelemsize;
    +#endif
    +  }else
    +    set= (setT*)qh_memalloc(size);
    +  set->maxsize= setsize;
    +  set->e[setsize].i= 1;
    +  set->e[0].p= NULL;
    +  return(set);
    +} /* setnew */
    +
    +
    +/*---------------------------------
    +
    +  qh_setnew_delnthsorted( set, size, nth, prepend )
    +    creates a sorted set not containing nth element
    +    if prepend, the first prepend elements are undefined
    +
    +  notes:
    +    set must be defined
    +    checks nth
    +    see also: setdelnthsorted
    +
    +  design:
    +    create new set
    +    setup pointers and allocate room for prepend'ed entries
    +    append head of old set to new set
    +    append tail of old set to new set
    +*/
    +setT *qh_setnew_delnthsorted(setT *set, int size, int nth, int prepend) {
    +  setT *newset;
    +  void **oldp, **newp;
    +  int tailsize= size - nth -1, newsize;
    +
    +  if (tailsize < 0) {
    +    qh_fprintf(qhmem.ferr, 6176, "qhull internal error (qh_setnew_delnthsorted): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  newsize= size-1 + prepend;
    +  newset= qh_setnew(newsize);
    +  newset->e[newset->maxsize].i= newsize+1;  /* may be overwritten */
    +  oldp= SETaddr_(set, void);
    +  newp= SETaddr_(newset, void) + prepend;
    +  switch (nth) {
    +  case 0:
    +    break;
    +  case 1:
    +    *(newp++)= *oldp++;
    +    break;
    +  case 2:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 3:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 4:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  default:
    +    memcpy((char *)newp, (char *)oldp, (size_t)nth * SETelemsize);
    +    newp += nth;
    +    oldp += nth;
    +    break;
    +  }
    +  oldp++;
    +  switch (tailsize) {
    +  case 0:
    +    break;
    +  case 1:
    +    *(newp++)= *oldp++;
    +    break;
    +  case 2:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 3:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 4:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  default:
    +    memcpy((char *)newp, (char *)oldp, (size_t)tailsize * SETelemsize);
    +    newp += tailsize;
    +  }
    +  *newp= NULL;
    +  return(newset);
    +} /* setnew_delnthsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setprint( fp, string, set )
    +    print set elements to fp with identifying string
    +
    +  notes:
    +    never errors
    +*/
    +void qh_setprint(FILE *fp, const char* string, setT *set) {
    +  int size, k;
    +
    +  if (!set)
    +    qh_fprintf(fp, 9346, "%s set is null\n", string);
    +  else {
    +    SETreturnsize_(set, size);
    +    qh_fprintf(fp, 9347, "%s set=%p maxsize=%d size=%d elems=",
    +             string, set, set->maxsize, size);
    +    if (size > set->maxsize)
    +      size= set->maxsize+1;
    +    for (k=0; k < size; k++)
    +      qh_fprintf(fp, 9348, " %p", set->e[k].p);
    +    qh_fprintf(fp, 9349, "\n");
    +  }
    +} /* setprint */
    +
    +/*---------------------------------
    +
    +  qh_setreplace( set, oldelem, newelem )
    +    replaces oldelem in set with newelem
    +
    +  notes:
    +    errors if oldelem not in the set
    +    newelem may be NULL, but it turns the set into an indexed set (no FOREACH)
    +
    +  design:
    +    find oldelem
    +    replace with newelem
    +*/
    +void qh_setreplace(setT *set, void *oldelem, void *newelem) {
    +  void **elemp;
    +
    +  elemp= SETaddr_(set, void);
    +  while (*elemp != oldelem && *elemp)
    +    elemp++;
    +  if (*elemp)
    +    *elemp= newelem;
    +  else {
    +    qh_fprintf(qhmem.ferr, 6177, "qhull internal error (qh_setreplace): elem %p not found in set\n",
    +       oldelem);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +} /* setreplace */
    +
    +
    +/*---------------------------------
    +
    +  qh_setsize( set )
    +    returns the size of a set
    +
    +  notes:
    +    errors if set's maxsize is incorrect
    +    same as SETreturnsize_(set)
    +    same code for qh_setsize [qset.c] and QhullSetBase::count
    +
    +  design:
    +    determine actual size of set from maxsize
    +*/
    +int qh_setsize(setT *set) {
    +  int size;
    +  setelemT *sizep;
    +
    +  if (!set)
    +    return(0);
    +  sizep= SETsizeaddr_(set);
    +  if ((size= sizep->i)) {
    +    size--;
    +    if (size > set->maxsize) {
    +      qh_fprintf(qhmem.ferr, 6178, "qhull internal error (qh_setsize): current set size %d is greater than maximum size %d\n",
    +               size, set->maxsize);
    +      qh_setprint(qhmem.ferr, "set: ", set);
    +      qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +    }
    +  }else
    +    size= set->maxsize;
    +  return size;
    +} /* setsize */
    +
    +/*---------------------------------
    +
    +  qh_settemp( setsize )
    +    return a stacked, temporary set of upto setsize elements
    +
    +  notes:
    +    use settempfree or settempfree_all to release from qhmem.tempstack
    +    see also qh_setnew
    +
    +  design:
    +    allocate set
    +    append to qhmem.tempstack
    +
    +*/
    +setT *qh_settemp(int setsize) {
    +  setT *newset;
    +
    +  newset= qh_setnew(setsize);
    +  qh_setappend(&qhmem.tempstack, newset);
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8123, "qh_settemp: temp set %p of %d elements, depth %d\n",
    +       newset, newset->maxsize, qh_setsize(qhmem.tempstack));
    +  return newset;
    +} /* settemp */
    +
    +/*---------------------------------
    +
    +  qh_settempfree( set )
    +    free temporary set at top of qhmem.tempstack
    +
    +  notes:
    +    nop if set is NULL
    +    errors if set not from previous   qh_settemp
    +
    +  to locate errors:
    +    use 'T2' to find source and then find mis-matching qh_settemp
    +
    +  design:
    +    check top of qhmem.tempstack
    +    free it
    +*/
    +void qh_settempfree(setT **set) {
    +  setT *stackedset;
    +
    +  if (!*set)
    +    return;
    +  stackedset= qh_settemppop();
    +  if (stackedset != *set) {
    +    qh_settemppush(stackedset);
    +    qh_fprintf(qhmem.ferr, 6179, "qhull internal error (qh_settempfree): set %p(size %d) was not last temporary allocated(depth %d, set %p, size %d)\n",
    +             *set, qh_setsize(*set), qh_setsize(qhmem.tempstack)+1,
    +             stackedset, qh_setsize(stackedset));
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh_setfree(set);
    +} /* settempfree */
    +
    +/*---------------------------------
    +
    +  qh_settempfree_all(  )
    +    free all temporary sets in qhmem.tempstack
    +
    +  design:
    +    for each set in tempstack
    +      free set
    +    free qhmem.tempstack
    +*/
    +void qh_settempfree_all(void) {
    +  setT *set, **setp;
    +
    +  FOREACHset_(qhmem.tempstack)
    +    qh_setfree(&set);
    +  qh_setfree(&qhmem.tempstack);
    +} /* settempfree_all */
    +
    +/*---------------------------------
    +
    +  qh_settemppop(  )
    +    pop and return temporary set from qhmem.tempstack
    +
    +  notes:
    +    the returned set is permanent
    +
    +  design:
    +    pop and check top of qhmem.tempstack
    +*/
    +setT *qh_settemppop(void) {
    +  setT *stackedset;
    +
    +  stackedset= (setT*)qh_setdellast(qhmem.tempstack);
    +  if (!stackedset) {
    +    qh_fprintf(qhmem.ferr, 6180, "qhull internal error (qh_settemppop): pop from empty temporary stack\n");
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8124, "qh_settemppop: depth %d temp set %p of %d elements\n",
    +       qh_setsize(qhmem.tempstack)+1, stackedset, qh_setsize(stackedset));
    +  return stackedset;
    +} /* settemppop */
    +
    +/*---------------------------------
    +
    +  qh_settemppush( set )
    +    push temporary set unto qhmem.tempstack (makes it temporary)
    +
    +  notes:
    +    duplicates settemp() for tracing
    +
    +  design:
    +    append set to tempstack
    +*/
    +void qh_settemppush(setT *set) {
    +  if (!set) {
    +    qh_fprintf(qhmem.ferr, 6267, "qhull error (qh_settemppush): can not push a NULL temp\n");
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh_setappend(&qhmem.tempstack, set);
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8125, "qh_settemppush: depth %d temp set %p of %d elements\n",
    +      qh_setsize(qhmem.tempstack), set, qh_setsize(set));
    +} /* settemppush */
    +
    +
    +/*---------------------------------
    +
    +  qh_settruncate( set, size )
    +    truncate set to size elements
    +
    +  notes:
    +    set must be defined
    +
    +  see:
    +    SETtruncate_
    +
    +  design:
    +    check size
    +    update actual size of set
    +*/
    +void qh_settruncate(setT *set, int size) {
    +
    +  if (size < 0 || size > set->maxsize) {
    +    qh_fprintf(qhmem.ferr, 6181, "qhull internal error (qh_settruncate): size %d out of bounds for set:\n", size);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  set->e[set->maxsize].i= size+1;   /* maybe overwritten */
    +  set->e[size].p= NULL;
    +} /* settruncate */
    +
    +/*---------------------------------
    +
    +  qh_setunique( set, elem )
    +    add elem to unsorted set unless it is already in set
    +
    +  notes:
    +    returns 1 if it is appended
    +
    +  design:
    +    if elem not in set
    +      append elem to set
    +*/
    +int qh_setunique(setT **set, void *elem) {
    +
    +  if (!qh_setin(*set, elem)) {
    +    qh_setappend(set, elem);
    +    return 1;
    +  }
    +  return 0;
    +} /* setunique */
    +
    +/*---------------------------------
    +
    +  qh_setzero( set, index, size )
    +    zero elements from index on
    +    set actual size of set to size
    +
    +  notes:
    +    set must be defined
    +    the set becomes an indexed set (can not use FOREACH...)
    +
    +  see also:
    +    qh_settruncate
    +
    +  design:
    +    check index and size
    +    update actual size
    +    zero elements starting at e[index]
    +*/
    +void qh_setzero(setT *set, int idx, int size) {
    +  int count;
    +
    +  if (idx < 0 || idx >= size || size > set->maxsize) {
    +    qh_fprintf(qhmem.ferr, 6182, "qhull internal error (qh_setzero): index %d or size %d out of bounds for set:\n", idx, size);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  set->e[set->maxsize].i=  size+1;  /* may be overwritten */
    +  count= size - idx + 1;   /* +1 for NULL terminator */
    +  memset((char *)SETelemaddr_(set, idx, void), 0, (size_t)count * SETelemsize);
    +} /* setzero */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/qset.h b/xs/src/qhull/src/libqhull/qset.h
    new file mode 100644
    index 0000000000..7e4e7d14f6
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/qset.h
    @@ -0,0 +1,490 @@
    +/*
      ---------------------------------
    +
    +   qset.h
    +     header file for qset.c that implements set
    +
    +   see qh-set.htm and qset.c
    +
    +   only uses mem.c, malloc/free
    +
    +   for error handling, writes message and calls
    +      qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +
    +   set operations satisfy the following properties:
    +    - sets have a max size, the actual size (if different) is stored at the end
    +    - every set is NULL terminated
    +    - sets may be sorted or unsorted, the caller must distinguish this
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/qset.h#2 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFset
    +#define qhDEFset 1
    +
    +#include 
    +
    +/*================= -structures- ===============*/
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;   /* a set is a sorted or unsorted array of pointers */
    +#endif
    +
    +/* [jan'15] Decided not to use countT.  Most sets are small.  The code uses signed tests */
    +
    +/*------------------------------------------
    +
    +setT
    +  a set or list of pointers with maximum size and actual size.
    +
    +variations:
    +  unsorted, unique   -- a list of unique pointers with NULL terminator
    +                           user guarantees uniqueness
    +  sorted             -- a sorted list of unique pointers with NULL terminator
    +                           qset.c guarantees uniqueness
    +  unsorted           -- a list of pointers terminated with NULL
    +  indexed            -- an array of pointers with NULL elements
    +
    +structure for set of n elements:
    +
    +        --------------
    +        |  maxsize
    +        --------------
    +        |  e[0] - a pointer, may be NULL for indexed sets
    +        --------------
    +        |  e[1]
    +
    +        --------------
    +        |  ...
    +        --------------
    +        |  e[n-1]
    +        --------------
    +        |  e[n] = NULL
    +        --------------
    +        |  ...
    +        --------------
    +        |  e[maxsize] - n+1 or NULL (determines actual size of set)
    +        --------------
    +
    +*/
    +
    +/*-- setelemT -- internal type to allow both pointers and indices
    +*/
    +typedef union setelemT setelemT;
    +union setelemT {
    +  void    *p;
    +  int      i;         /* integer used for e[maxSize] */
    +};
    +
    +struct setT {
    +  int maxsize;          /* maximum number of elements (except NULL) */
    +  setelemT e[1];        /* array of pointers, tail is NULL */
    +                        /* last slot (unless NULL) is actual size+1
    +                           e[maxsize]==NULL or e[e[maxsize]-1]==NULL */
    +                        /* this may generate a warning since e[] contains
    +                           maxsize elements */
    +};
    +
    +/*=========== -constants- =========================*/
    +
    +/*-------------------------------------
    +
    +  SETelemsize
    +    size of a set element in bytes
    +*/
    +#define SETelemsize ((int)sizeof(setelemT))
    +
    +
    +/*=========== -macros- =========================*/
    +
    +/*-------------------------------------
    +
    +   FOREACHsetelement_(type, set, variable)
    +     define FOREACH iterator
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +     no space in "variable)" [DEC Alpha cc compiler]
    +
    +   each iteration:
    +     variable is set element
    +     variablep is one beyond variable.
    +
    +   to repeat an element:
    +     variablep--; / *repeat* /
    +
    +   at exit:
    +     variable is NULL at end of loop
    +
    +   example:
    +     #define FOREACHfacet_( facets ) FOREACHsetelement_( facetT, facets, facet )
    +
    +   notes:
    +     use FOREACHsetelement_i_() if need index or include NULLs
    +
    +   WARNING:
    +     nested loops can't use the same variable (define another FOREACH)
    +
    +     needs braces if nested inside another FOREACH
    +     this includes intervening blocks, e.g. FOREACH...{ if () FOREACH...} )
    +*/
    +#define FOREACHsetelement_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##p= (type **)&((set)->e[0].p); \
    +          (variable= *variable##p++);)
    +
    +/*------------------------------------------
    +
    +   FOREACHsetelement_i_(type, set, variable)
    +     define indexed FOREACH iterator
    +
    +   declare:
    +     type *variable, variable_n, variable_i;
    +
    +   each iteration:
    +     variable is set element, may be NULL
    +     variable_i is index, variable_n is qh_setsize()
    +
    +   to repeat an element:
    +     variable_i--; variable_n-- repeats for deleted element
    +
    +   at exit:
    +     variable==NULL and variable_i==variable_n
    +
    +   example:
    +     #define FOREACHfacet_i_( facets ) FOREACHsetelement_i_( facetT, facets, facet )
    +
    +   WARNING:
    +     nested loops can't use the same variable (define another FOREACH)
    +
    +     needs braces if nested inside another FOREACH
    +     this includes intervening blocks, e.g. FOREACH...{ if () FOREACH...} )
    +*/
    +#define FOREACHsetelement_i_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##_i= 0, variable= (type *)((set)->e[0].p), \
    +                   variable##_n= qh_setsize(set);\
    +          variable##_i < variable##_n;\
    +          variable= (type *)((set)->e[++variable##_i].p) )
    +
    +/*----------------------------------------
    +
    +   FOREACHsetelementreverse_(type, set, variable)-
    +     define FOREACH iterator in reverse order
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +     also declare 'int variabletemp'
    +
    +   each iteration:
    +     variable is set element
    +
    +   to repeat an element:
    +     variabletemp++; / *repeat* /
    +
    +   at exit:
    +     variable is NULL
    +
    +   example:
    +     #define FOREACHvertexreverse_( vertices ) FOREACHsetelementreverse_( vertexT, vertices, vertex )
    +
    +   notes:
    +     use FOREACHsetelementreverse12_() to reverse first two elements
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHsetelementreverse_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +           variable##temp= qh_setsize(set)-1, variable= qh_setlast(set);\
    +           variable; variable= \
    +           ((--variable##temp >= 0) ? SETelemt_(set, variable##temp, type) : NULL))
    +
    +/*-------------------------------------
    +
    +   FOREACHsetelementreverse12_(type, set, variable)-
    +     define FOREACH iterator with e[1] and e[0] reversed
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +
    +   each iteration:
    +     variable is set element
    +     variablep is one after variable.
    +
    +   to repeat an element:
    +     variablep--; / *repeat* /
    +
    +   at exit:
    +     variable is NULL at end of loop
    +
    +   example
    +     #define FOREACHvertexreverse12_( vertices ) FOREACHsetelementreverse12_( vertexT, vertices, vertex )
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHsetelementreverse12_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##p= (type **)&((set)->e[1].p); \
    +          (variable= *variable##p); \
    +          variable##p == ((type **)&((set)->e[0].p))?variable##p += 2: \
    +              (variable##p == ((type **)&((set)->e[1].p))?variable##p--:variable##p++))
    +
    +/*-------------------------------------
    +
    +   FOREACHelem_( set )-
    +     iterate elements in a set
    +
    +   declare:
    +     void *elem, *elemp;
    +
    +   each iteration:
    +     elem is set element
    +     elemp is one beyond
    +
    +   to repeat an element:
    +     elemp--; / *repeat* /
    +
    +   at exit:
    +     elem == NULL at end of loop
    +
    +   example:
    +     FOREACHelem_(set) {
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHelem_(set) FOREACHsetelement_(void, set, elem)
    +
    +/*-------------------------------------
    +
    +   FOREACHset_( set )-
    +     iterate a set of sets
    +
    +   declare:
    +     setT *set, **setp;
    +
    +   each iteration:
    +     set is set element
    +     setp is one beyond
    +
    +   to repeat an element:
    +     setp--; / *repeat* /
    +
    +   at exit:
    +     set == NULL at end of loop
    +
    +   example
    +     FOREACHset_(sets) {
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHset_(sets) FOREACHsetelement_(setT, sets, set)
    +
    +/*-------------------------------------------
    +
    +   SETindex_( set, elem )
    +     return index of elem in set
    +
    +   notes:
    +     for use with FOREACH iteration
    +     WARN64 -- Maximum set size is 2G
    +
    +   example:
    +     i= SETindex_(ridges, ridge)
    +*/
    +#define SETindex_(set, elem) ((int)((void **)elem##p - (void **)&(set)->e[1].p))
    +
    +/*-----------------------------------------
    +
    +   SETref_( elem )
    +     l.h.s. for modifying the current element in a FOREACH iteration
    +
    +   example:
    +     SETref_(ridge)= anotherridge;
    +*/
    +#define SETref_(elem) (elem##p[-1])
    +
    +/*-----------------------------------------
    +
    +   SETelem_(set, n)
    +     return the n'th element of set
    +
    +   notes:
    +      assumes that n is valid [0..size] and that set is defined
    +      use SETelemt_() for type cast
    +*/
    +#define SETelem_(set, n)           ((set)->e[n].p)
    +
    +/*-----------------------------------------
    +
    +   SETelemt_(set, n, type)
    +     return the n'th element of set as a type
    +
    +   notes:
    +      assumes that n is valid [0..size] and that set is defined
    +*/
    +#define SETelemt_(set, n, type)    ((type*)((set)->e[n].p))
    +
    +/*-----------------------------------------
    +
    +   SETelemaddr_(set, n, type)
    +     return address of the n'th element of a set
    +
    +   notes:
    +      assumes that n is valid [0..size] and set is defined
    +*/
    +#define SETelemaddr_(set, n, type) ((type **)(&((set)->e[n].p)))
    +
    +/*-----------------------------------------
    +
    +   SETfirst_(set)
    +     return first element of set
    +
    +*/
    +#define SETfirst_(set)             ((set)->e[0].p)
    +
    +/*-----------------------------------------
    +
    +   SETfirstt_(set, type)
    +     return first element of set as a type
    +
    +*/
    +#define SETfirstt_(set, type)      ((type*)((set)->e[0].p))
    +
    +/*-----------------------------------------
    +
    +   SETsecond_(set)
    +     return second element of set
    +
    +*/
    +#define SETsecond_(set)            ((set)->e[1].p)
    +
    +/*-----------------------------------------
    +
    +   SETsecondt_(set, type)
    +     return second element of set as a type
    +*/
    +#define SETsecondt_(set, type)     ((type*)((set)->e[1].p))
    +
    +/*-----------------------------------------
    +
    +   SETaddr_(set, type)
    +       return address of set's elements
    +*/
    +#define SETaddr_(set,type)         ((type **)(&((set)->e[0].p)))
    +
    +/*-----------------------------------------
    +
    +   SETreturnsize_(set, size)
    +     return size of a set
    +
    +   notes:
    +      set must be defined
    +      use qh_setsize(set) unless speed is critical
    +*/
    +#define SETreturnsize_(set, size) (((size)= ((set)->e[(set)->maxsize].i))?(--(size)):((size)= (set)->maxsize))
    +
    +/*-----------------------------------------
    +
    +   SETempty_(set)
    +     return true(1) if set is empty
    +
    +   notes:
    +      set may be NULL
    +*/
    +#define SETempty_(set)            (!set || (SETfirst_(set) ? 0 : 1))
    +
    +/*---------------------------------
    +
    +  SETsizeaddr_(set)
    +    return pointer to 'actual size+1' of set (set CANNOT be NULL!!)
    +    Its type is setelemT* for strict aliasing
    +    All SETelemaddr_ must be cast to setelemT
    +
    +
    +  notes:
    +    *SETsizeaddr==NULL or e[*SETsizeaddr-1].p==NULL
    +*/
    +#define SETsizeaddr_(set) (&((set)->e[(set)->maxsize]))
    +
    +/*-----------------------------------------
    +
    +   SETtruncate_(set, size)
    +     truncate set to size
    +
    +   see:
    +     qh_settruncate()
    +
    +*/
    +#define SETtruncate_(set, size) {set->e[set->maxsize].i= size+1; /* maybe overwritten */ \
    +      set->e[size].p= NULL;}
    +
    +/*======= prototypes in alphabetical order ============*/
    +
    +void  qh_setaddsorted(setT **setp, void *elem);
    +void  qh_setaddnth(setT **setp, int nth, void *newelem);
    +void  qh_setappend(setT **setp, void *elem);
    +void  qh_setappend_set(setT **setp, setT *setA);
    +void  qh_setappend2ndlast(setT **setp, void *elem);
    +void  qh_setcheck(setT *set, const char *tname, unsigned id);
    +void  qh_setcompact(setT *set);
    +setT *qh_setcopy(setT *set, int extra);
    +void *qh_setdel(setT *set, void *elem);
    +void *qh_setdellast(setT *set);
    +void *qh_setdelnth(setT *set, int nth);
    +void *qh_setdelnthsorted(setT *set, int nth);
    +void *qh_setdelsorted(setT *set, void *newelem);
    +setT *qh_setduplicate( setT *set, int elemsize);
    +void **qh_setendpointer(setT *set);
    +int   qh_setequal(setT *setA, setT *setB);
    +int   qh_setequal_except(setT *setA, void *skipelemA, setT *setB, void *skipelemB);
    +int   qh_setequal_skip(setT *setA, int skipA, setT *setB, int skipB);
    +void  qh_setfree(setT **set);
    +void  qh_setfree2( setT **setp, int elemsize);
    +void  qh_setfreelong(setT **set);
    +int   qh_setin(setT *set, void *setelem);
    +int   qh_setindex(setT *set, void *setelem);
    +void  qh_setlarger(setT **setp);
    +void *qh_setlast(setT *set);
    +setT *qh_setnew(int size);
    +setT *qh_setnew_delnthsorted(setT *set, int size, int nth, int prepend);
    +void  qh_setprint(FILE *fp, const char* string, setT *set);
    +void  qh_setreplace(setT *set, void *oldelem, void *newelem);
    +int   qh_setsize(setT *set);
    +setT *qh_settemp(int setsize);
    +void  qh_settempfree(setT **set);
    +void  qh_settempfree_all(void);
    +setT *qh_settemppop(void);
    +void  qh_settemppush(setT *set);
    +void  qh_settruncate(setT *set, int size);
    +int   qh_setunique(setT **set, void *elem);
    +void  qh_setzero(setT *set, int idx, int size);
    +
    +
    +#endif /* qhDEFset */
    diff --git a/xs/src/qhull/src/libqhull/random.c b/xs/src/qhull/src/libqhull/random.c
    new file mode 100644
    index 0000000000..176d697aeb
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/random.c
    @@ -0,0 +1,245 @@
    +/*
      ---------------------------------
    +
    +   random.c and utilities
    +     Park & Miller's minimimal standard random number generator
    +     argc/argv conversion
    +
    +     Used by rbox.  Do not use 'qh' 
    +*/
    +
    +#include "libqhull.h"
    +#include "random.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#pragma warning( disable : 4996)  /* function was declared deprecated(strcpy, localtime, etc.) */
    +#endif
    +
    +/*---------------------------------
    +
    + qh_argv_to_command( argc, argv, command, max_size )
    +
    +    build command from argc/argv
    +    max_size is at least
    +
    + returns:
    +    a space-delimited string of options (just as typed)
    +    returns false if max_size is too short
    +
    + notes:
    +    silently removes
    +    makes option string easy to input and output
    +    matches qh_argv_to_command_size()
    +
    +    argc may be 0
    +*/
    +int qh_argv_to_command(int argc, char *argv[], char* command, int max_size) {
    +  int i, remaining;
    +  char *s;
    +  *command= '\0';  /* max_size > 0 */
    +
    +  if (argc) {
    +    if ((s= strrchr( argv[0], '\\')) /* get filename w/o .exe extension */
    +    || (s= strrchr( argv[0], '/')))
    +        s++;
    +    else
    +        s= argv[0];
    +    if ((int)strlen(s) < max_size)   /* WARN64 */
    +        strcpy(command, s);
    +    else
    +        goto error_argv;
    +    if ((s= strstr(command, ".EXE"))
    +    ||  (s= strstr(command, ".exe")))
    +        *s= '\0';
    +  }
    +  for (i=1; i < argc; i++) {
    +    s= argv[i];
    +    remaining= max_size - (int)strlen(command) - (int)strlen(s) - 2;   /* WARN64 */
    +    if (!*s || strchr(s, ' ')) {
    +      char *t= command + strlen(command);
    +      remaining -= 2;
    +      if (remaining < 0) {
    +        goto error_argv;
    +      }
    +      *t++= ' ';
    +      *t++= '"';
    +      while (*s) {
    +        if (*s == '"') {
    +          if (--remaining < 0)
    +            goto error_argv;
    +          *t++= '\\';
    +        }
    +        *t++= *s++;
    +      }
    +      *t++= '"';
    +      *t= '\0';
    +    }else if (remaining < 0) {
    +      goto error_argv;
    +    }else
    +      strcat(command, " ");
    +      strcat(command, s);
    +  }
    +  return 1;
    +
    +error_argv:
    +  return 0;
    +} /* argv_to_command */
    +
    +/*---------------------------------
    +
    +qh_argv_to_command_size( argc, argv )
    +
    +    return size to allocate for qh_argv_to_command()
    +
    +notes:
    +    argc may be 0
    +    actual size is usually shorter
    +*/
    +int qh_argv_to_command_size(int argc, char *argv[]) {
    +    unsigned int count= 1; /* null-terminator if argc==0 */
    +    int i;
    +    char *s;
    +
    +    for (i=0; i0 && strchr(argv[i], ' ')) {
    +        count += 2;  /* quote delimiters */
    +        for (s=argv[i]; *s; s++) {
    +          if (*s == '"') {
    +            count++;
    +          }
    +        }
    +      }
    +    }
    +    return count;
    +} /* argv_to_command_size */
    +
    +/*---------------------------------
    +
    +  qh_rand()
    +  qh_srand( seed )
    +    generate pseudo-random number between 1 and 2^31 -2
    +
    +  notes:
    +    For qhull and rbox, called from qh_RANDOMint(),etc. [user.h]
    +
    +    From Park & Miller's minimal standard random number generator
    +      Communications of the ACM, 31:1192-1201, 1988.
    +    Does not use 0 or 2^31 -1
    +      this is silently enforced by qh_srand()
    +    Can make 'Rn' much faster by moving qh_rand to qh_distplane
    +*/
    +
    +/* Global variables and constants */
    +
    +int qh_last_random= 1;  /* define as global variable instead of using qh */
    +
    +#define qh_rand_a 16807
    +#define qh_rand_m 2147483647
    +#define qh_rand_q 127773  /* m div a */
    +#define qh_rand_r 2836    /* m mod a */
    +
    +int qh_rand( void) {
    +    int lo, hi, test;
    +    int seed = qh_last_random;
    +
    +    hi = seed / qh_rand_q;  /* seed div q */
    +    lo = seed % qh_rand_q;  /* seed mod q */
    +    test = qh_rand_a * lo - qh_rand_r * hi;
    +    if (test > 0)
    +        seed= test;
    +    else
    +        seed= test + qh_rand_m;
    +    qh_last_random= seed;
    +    /* seed = seed < qh_RANDOMmax/2 ? 0 : qh_RANDOMmax;  for testing */
    +    /* seed = qh_RANDOMmax;  for testing */
    +    return seed;
    +} /* rand */
    +
    +void qh_srand( int seed) {
    +    if (seed < 1)
    +        qh_last_random= 1;
    +    else if (seed >= qh_rand_m)
    +        qh_last_random= qh_rand_m - 1;
    +    else
    +        qh_last_random= seed;
    +} /* qh_srand */
    +
    +/*---------------------------------
    +
    +qh_randomfactor( scale, offset )
    +  return a random factor r * scale + offset
    +
    +notes:
    +  qh.RANDOMa/b are defined in global.c
    +*/
    +realT qh_randomfactor(realT scale, realT offset) {
    +    realT randr;
    +
    +    randr= qh_RANDOMint;
    +    return randr * scale + offset;
    +} /* randomfactor */
    +
    +/*---------------------------------
    +
    +qh_randommatrix( buffer, dim, rows )
    +  generate a random dim X dim matrix in range [-1,1]
    +  assumes buffer is [dim+1, dim]
    +
    +returns:
    +  sets buffer to random numbers
    +  sets rows to rows of buffer
    +  sets row[dim] as scratch row
    +*/
    +void qh_randommatrix(realT *buffer, int dim, realT **rows) {
    +    int i, k;
    +    realT **rowi, *coord, realr;
    +
    +    coord= buffer;
    +    rowi= rows;
    +    for (i=0; i < dim; i++) {
    +        *(rowi++)= coord;
    +        for (k=0; k < dim; k++) {
    +            realr= qh_RANDOMint;
    +            *(coord++)= 2.0 * realr/(qh_RANDOMmax+1) - 1.0;
    +        }
    +    }
    +    *rowi= coord;
    +} /* randommatrix */
    +
    +/*---------------------------------
    +
    +  qh_strtol( s, endp) qh_strtod( s, endp)
    +    internal versions of strtol() and strtod()
    +    does not skip trailing spaces
    +  notes:
    +    some implementations of strtol()/strtod() skip trailing spaces
    +*/
    +double qh_strtod(const char *s, char **endp) {
    +  double result;
    +
    +  result= strtod(s, endp);
    +  if (s < (*endp) && (*endp)[-1] == ' ')
    +    (*endp)--;
    +  return result;
    +} /* strtod */
    +
    +int qh_strtol(const char *s, char **endp) {
    +  int result;
    +
    +  result= (int) strtol(s, endp, 10);     /* WARN64 */
    +  if (s< (*endp) && (*endp)[-1] == ' ')
    +    (*endp)--;
    +  return result;
    +} /* strtol */
    diff --git a/xs/src/qhull/src/libqhull/random.h b/xs/src/qhull/src/libqhull/random.h
    new file mode 100644
    index 0000000000..0c6896b765
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/random.h
    @@ -0,0 +1,34 @@
    +/*
      ---------------------------------
    +
    +  random.h
    +    header file for random and utility routines
    +
    +   see qh-geom.htm and random.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/random.h#2 $$Change: 2026 $
    +   $DateTime: 2015/11/07 22:44:39 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFrandom
    +#define qhDEFrandom 1
    +
    +#include "libqhull.h"
    +
    +/*============= prototypes in alphabetical order ======= */
    +
    +
    +int     qh_argv_to_command(int argc, char *argv[], char* command, int max_size);
    +int     qh_argv_to_command_size(int argc, char *argv[]);
    +int     qh_rand( void);
    +void    qh_srand( int seed);
    +realT   qh_randomfactor(realT scale, realT offset);
    +void    qh_randommatrix(realT *buffer, int dim, realT **row);
    +int     qh_strtol(const char *s, char **endp);
    +double  qh_strtod(const char *s, char **endp);
    +
    +#endif /* qhDEFrandom */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/rboxlib.c b/xs/src/qhull/src/libqhull/rboxlib.c
    new file mode 100644
    index 0000000000..f945133fa0
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/rboxlib.c
    @@ -0,0 +1,870 @@
    +/*
      ---------------------------------
    +
    +   rboxlib.c
    +     Generate input points
    +
    +   notes:
    +     For documentation, see prompt[] of rbox.c
    +     50 points generated for 'rbox D4'
    +
    +   WARNING:
    +     incorrect range if qh_RANDOMmax is defined wrong (user.h)
    +*/
    +
    +#include "libqhull.h"  /* First for user.h */
    +#include "random.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ */
    +#pragma warning( disable : 4706)  /* assignment within conditional expression. */
    +#pragma warning( disable : 4996)  /* this function (strncat,sprintf,strcpy) or variable may be unsafe. */
    +#endif
    +
    +#define MAXdim 200
    +#define PI 3.1415926535897932384
    +
    +/* ------------------------------ prototypes ----------------*/
    +int qh_roundi( double a);
    +void qh_out1( double a);
    +void qh_out2n( double a, double b);
    +void qh_out3n( double a, double b, double c);
    +void qh_outcoord(int iscdd, double *coord, int dim);
    +void qh_outcoincident(int coincidentpoints, double radius, int iscdd, double *coord, int dim);
    +
    +void    qh_fprintf_rbox(FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +int     qh_rand( void);
    +void    qh_srand( int seed);
    +
    +
    +/* ------------------------------ globals -------------------*/
    +
    +/* No state is carried between rbox requests */
    +typedef struct rboxT rboxT;
    +struct rboxT {
    +  FILE *fout;
    +  FILE *ferr;
    +  int isinteger;
    +  double out_offset;
    +  jmp_buf errexit;        /* exit label for rboxpoints, defined by setjmp(), called by qh_errexit_rbox() */
    +  char  jmpXtra[40];      /* extra bytes in case jmp_buf is defined wrong by compiler */
    +};
    +
    +
    +int rbox_inuse= 0;
    +rboxT rbox;
    +
    +/*---------------------------------
    +
    +  qh_rboxpoints( fout, ferr, rbox_command )
    +    Generate points to fout according to rbox options
    +    Report errors on ferr
    +
    +  returns:
    +    0 (qh_ERRnone) on success
    +    1 (qh_ERRinput) on input error
    +    4 (qh_ERRmem) on memory error
    +    5 (qh_ERRqhull) on internal error
    +
    +  notes:
    +    To avoid using stdio, redefine qh_malloc, qh_free, and qh_fprintf_rbox (user.c)
    +
    +  design:
    +    Straight line code (consider defining a struct and functions):
    +
    +    Parse arguments into variables
    +    Determine the number of points
    +    Generate the points
    +*/
    +int qh_rboxpoints(FILE* fout, FILE* ferr, char* rbox_command) {
    +  int i,j,k;
    +  int gendim;
    +  int coincidentcount=0, coincidenttotal=0, coincidentpoints=0;
    +  int cubesize, diamondsize, seed=0, count, apex;
    +  int dim=3, numpoints=0, totpoints, addpoints=0;
    +  int issphere=0, isaxis=0,  iscdd=0, islens=0, isregular=0, iswidth=0, addcube=0;
    +  int isgap=0, isspiral=0, NOcommand=0, adddiamond=0;
    +  int israndom=0, istime=0;
    +  int isbox=0, issimplex=0, issimplex2=0, ismesh=0;
    +  double width=0.0, gap=0.0, radius=0.0, coincidentradius=0.0;
    +  double coord[MAXdim], offset, meshm=3.0, meshn=4.0, meshr=5.0;
    +  double *coordp, *simplex= NULL, *simplexp;
    +  int nthroot, mult[MAXdim];
    +  double norm, factor, randr, rangap, lensangle=0, lensbase=1;
    +  double anglediff, angle, x, y, cube=0.0, diamond=0.0;
    +  double box= qh_DEFAULTbox; /* scale all numbers before output */
    +  double randmax= qh_RANDOMmax;
    +  char command[200], seedbuf[200];
    +  char *s= command, *t, *first_point= NULL;
    +  time_t timedata;
    +  int exitcode;
    +
    +  if (rbox_inuse) {
    +    qh_fprintf_rbox(rbox.ferr, 6188, "rbox error: rbox in use by another process.  Please lock calls to rbox.\n");
    +    return qh_ERRqhull;
    +  }
    +  rbox_inuse= True;
    +  rbox.ferr= ferr;
    +  rbox.fout= fout;
    +
    +  exitcode= setjmp(rbox.errexit);
    +  if (exitcode) {
    +    /* same code for error exit and normal return.  qh.NOerrexit is set */
    +    if (simplex)
    +        qh_free(simplex);
    +    rbox_inuse= False;
    +    return exitcode;
    +  }
    +
    +  *command= '\0';
    +  strncat(command, rbox_command, sizeof(command)-strlen(command)-1);
    +
    +  while (*s && !isspace(*s))  /* skip program name */
    +    s++;
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    if (isdigit(*s)) {
    +      numpoints= qh_strtol(s, &s);
    +      continue;
    +    }
    +    /* ============= read flags =============== */
    +    switch (*s++) {
    +    case 'c':
    +      addcube= 1;
    +      t= s;
    +      while (isspace(*t))
    +        t++;
    +      if (*t == 'G')
    +        cube= qh_strtod(++t, &s);
    +      break;
    +    case 'd':
    +      adddiamond= 1;
    +      t= s;
    +      while (isspace(*t))
    +        t++;
    +      if (*t == 'G')
    +        diamond= qh_strtod(++t, &s);
    +      break;
    +    case 'h':
    +      iscdd= 1;
    +      break;
    +    case 'l':
    +      isspiral= 1;
    +      break;
    +    case 'n':
    +      NOcommand= 1;
    +      break;
    +    case 'r':
    +      isregular= 1;
    +      break;
    +    case 's':
    +      issphere= 1;
    +      break;
    +    case 't':
    +      istime= 1;
    +      if (isdigit(*s)) {
    +        seed= qh_strtol(s, &s);
    +        israndom= 0;
    +      }else
    +        israndom= 1;
    +      break;
    +    case 'x':
    +      issimplex= 1;
    +      break;
    +    case 'y':
    +      issimplex2= 1;
    +      break;
    +    case 'z':
    +      rbox.isinteger= 1;
    +      break;
    +    case 'B':
    +      box= qh_strtod(s, &s);
    +      isbox= 1;
    +      break;
    +    case 'C':
    +      if (*s)
    +        coincidentpoints=  qh_strtol(s, &s);
    +      if (*s == ',') {
    +        ++s;
    +        coincidentradius=  qh_strtod(s, &s);
    +      }
    +      if (*s == ',') {
    +        ++s;
    +        coincidenttotal=  qh_strtol(s, &s);
    +      }
    +      if (*s && !isspace(*s)) {
    +        qh_fprintf_rbox(rbox.ferr, 7080, "rbox error: arguments for 'Cn,r,m' are not 'int', 'float', and 'int'.  Remaining string is '%s'\n", s);
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      if (coincidentpoints==0){
    +        qh_fprintf_rbox(rbox.ferr, 6268, "rbox error: missing arguments for 'Cn,r,m' where n is the number of coincident points, r is the radius (default 0.0), and m is the number of points\n");
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      if (coincidentpoints<0 || coincidenttotal<0 || coincidentradius<0.0){
    +        qh_fprintf_rbox(rbox.ferr, 6269, "rbox error: negative arguments for 'Cn,m,r' where n (%d) is the number of coincident points, m (%d) is the number of points, and r (%.2g) is the radius (default 0.0)\n", coincidentpoints, coincidenttotal, coincidentradius);
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      break;
    +    case 'D':
    +      dim= qh_strtol(s, &s);
    +      if (dim < 1
    +      || dim > MAXdim) {
    +        qh_fprintf_rbox(rbox.ferr, 6189, "rbox error: dimension, D%d, out of bounds (>=%d or <=0)", dim, MAXdim);
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      break;
    +    case 'G':
    +      if (isdigit(*s))
    +        gap= qh_strtod(s, &s);
    +      else
    +        gap= 0.5;
    +      isgap= 1;
    +      break;
    +    case 'L':
    +      if (isdigit(*s))
    +        radius= qh_strtod(s, &s);
    +      else
    +        radius= 10;
    +      islens= 1;
    +      break;
    +    case 'M':
    +      ismesh= 1;
    +      if (*s)
    +        meshn= qh_strtod(s, &s);
    +      if (*s == ',') {
    +        ++s;
    +        meshm= qh_strtod(s, &s);
    +      }else
    +        meshm= 0.0;
    +      if (*s == ',') {
    +        ++s;
    +        meshr= qh_strtod(s, &s);
    +      }else
    +        meshr= sqrt(meshn*meshn + meshm*meshm);
    +      if (*s && !isspace(*s)) {
    +        qh_fprintf_rbox(rbox.ferr, 7069, "rbox warning: assuming 'M3,4,5' since mesh args are not integers or reals\n");
    +        meshn= 3.0, meshm=4.0, meshr=5.0;
    +      }
    +      break;
    +    case 'O':
    +      rbox.out_offset= qh_strtod(s, &s);
    +      break;
    +    case 'P':
    +      if (!first_point)
    +        first_point= s-1;
    +      addpoints++;
    +      while (*s && !isspace(*s))   /* read points later */
    +        s++;
    +      break;
    +    case 'W':
    +      width= qh_strtod(s, &s);
    +      iswidth= 1;
    +      break;
    +    case 'Z':
    +      if (isdigit(*s))
    +        radius= qh_strtod(s, &s);
    +      else
    +        radius= 1.0;
    +      isaxis= 1;
    +      break;
    +    default:
    +      qh_fprintf_rbox(rbox.ferr, 7070, "rbox error: unknown flag at %s.\nExecute 'rbox' without arguments for documentation.\n", s);
    +      qh_errexit_rbox(qh_ERRinput);
    +    }
    +    if (*s && !isspace(*s)) {
    +      qh_fprintf_rbox(rbox.ferr, 7071, "rbox error: missing space between flags at %s.\n", s);
    +      qh_errexit_rbox(qh_ERRinput);
    +    }
    +  }
    +
    +  /* ============= defaults, constants, and sizes =============== */
    +  if (rbox.isinteger && !isbox)
    +    box= qh_DEFAULTzbox;
    +  if (addcube) {
    +    cubesize= (int)floor(ldexp(1.0,dim)+0.5);
    +    if (cube == 0.0)
    +      cube= box;
    +  }else
    +    cubesize= 0;
    +  if (adddiamond) {
    +    diamondsize= 2*dim;
    +    if (diamond == 0.0)
    +      diamond= box;
    +  }else
    +    diamondsize= 0;
    +  if (islens) {
    +    if (isaxis) {
    +        qh_fprintf_rbox(rbox.ferr, 6190, "rbox error: can not combine 'Ln' with 'Zn'\n");
    +        qh_errexit_rbox(qh_ERRinput);
    +    }
    +    if (radius <= 1.0) {
    +        qh_fprintf_rbox(rbox.ferr, 6191, "rbox error: lens radius %.2g should be greater than 1.0\n",
    +               radius);
    +        qh_errexit_rbox(qh_ERRinput);
    +    }
    +    lensangle= asin(1.0/radius);
    +    lensbase= radius * cos(lensangle);
    +  }
    +
    +  if (!numpoints) {
    +    if (issimplex2)
    +        ; /* ok */
    +    else if (isregular + issimplex + islens + issphere + isaxis + isspiral + iswidth + ismesh) {
    +        qh_fprintf_rbox(rbox.ferr, 6192, "rbox error: missing count\n");
    +        qh_errexit_rbox(qh_ERRinput);
    +    }else if (adddiamond + addcube + addpoints)
    +        ; /* ok */
    +    else {
    +        numpoints= 50;  /* ./rbox D4 is the test case */
    +        issphere= 1;
    +    }
    +  }
    +  if ((issimplex + islens + isspiral + ismesh > 1)
    +  || (issimplex + issphere + isspiral + ismesh > 1)) {
    +    qh_fprintf_rbox(rbox.ferr, 6193, "rbox error: can only specify one of 'l', 's', 'x', 'Ln', or 'Mn,m,r' ('Ln s' is ok).\n");
    +    qh_errexit_rbox(qh_ERRinput);
    +  }
    +  if (coincidentpoints>0 && (numpoints == 0 || coincidenttotal > numpoints)) {
    +    qh_fprintf_rbox(rbox.ferr, 6270, "rbox error: 'Cn,r,m' requested n coincident points for each of m points.  Either there is no points or m (%d) is greater than the number of points (%d).\n", coincidenttotal, numpoints);
    +    qh_errexit_rbox(qh_ERRinput);
    +  }
    +  if (coincidenttotal == 0)
    +    coincidenttotal= numpoints;
    +
    +  /* ============= print header with total points =============== */
    +  if (issimplex || ismesh)
    +    totpoints= numpoints;
    +  else if (issimplex2)
    +    totpoints= numpoints+dim+1;
    +  else if (isregular) {
    +    totpoints= numpoints;
    +    if (dim == 2) {
    +        if (islens)
    +          totpoints += numpoints - 2;
    +    }else if (dim == 3) {
    +        if (islens)
    +          totpoints += 2 * numpoints;
    +      else if (isgap)
    +        totpoints += 1 + numpoints;
    +      else
    +        totpoints += 2;
    +    }
    +  }else
    +    totpoints= numpoints + isaxis;
    +  totpoints += cubesize + diamondsize + addpoints;
    +  totpoints += coincidentpoints*coincidenttotal;
    +
    +  /* ============= seed randoms =============== */
    +  if (istime == 0) {
    +    for (s=command; *s; s++) {
    +      if (issimplex2 && *s == 'y') /* make 'y' same seed as 'x' */
    +        i= 'x';
    +      else
    +        i= *s;
    +      seed= 11*seed + i;
    +    }
    +  }else if (israndom) {
    +    seed= (int)time(&timedata);
    +    sprintf(seedbuf, " t%d", seed);  /* appends an extra t, not worth removing */
    +    strncat(command, seedbuf, sizeof(command)-strlen(command)-1);
    +    t= strstr(command, " t ");
    +    if (t)
    +      strcpy(t+1, t+3); /* remove " t " */
    +  } /* else, seed explicitly set to n */
    +  qh_RANDOMseed_(seed);
    +
    +  /* ============= print header =============== */
    +
    +  if (iscdd)
    +      qh_fprintf_rbox(rbox.fout, 9391, "%s\nbegin\n        %d %d %s\n",
    +      NOcommand ? "" : command,
    +      totpoints, dim+1,
    +      rbox.isinteger ? "integer" : "real");
    +  else if (NOcommand)
    +      qh_fprintf_rbox(rbox.fout, 9392, "%d\n%d\n", dim, totpoints);
    +  else
    +      /* qh_fprintf_rbox special cases 9393 to append 'command' to the RboxPoints.comment() */
    +      qh_fprintf_rbox(rbox.fout, 9393, "%d %s\n%d\n", dim, command, totpoints);
    +
    +  /* ============= explicit points =============== */
    +  if ((s= first_point)) {
    +    while (s && *s) { /* 'P' */
    +      count= 0;
    +      if (iscdd)
    +        qh_out1( 1.0);
    +      while (*++s) {
    +        qh_out1( qh_strtod(s, &s));
    +        count++;
    +        if (isspace(*s) || !*s)
    +          break;
    +        if (*s != ',') {
    +          qh_fprintf_rbox(rbox.ferr, 6194, "rbox error: missing comma after coordinate in %s\n\n", s);
    +          qh_errexit_rbox(qh_ERRinput);
    +        }
    +      }
    +      if (count < dim) {
    +        for (k=dim-count; k--; )
    +          qh_out1( 0.0);
    +      }else if (count > dim) {
    +        qh_fprintf_rbox(rbox.ferr, 6195, "rbox error: %d coordinates instead of %d coordinates in %s\n\n",
    +                  count, dim, s);
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      qh_fprintf_rbox(rbox.fout, 9394, "\n");
    +      while ((s= strchr(s, 'P'))) {
    +        if (isspace(s[-1]))
    +          break;
    +      }
    +    }
    +  }
    +
    +  /* ============= simplex distribution =============== */
    +  if (issimplex+issimplex2) {
    +    if (!(simplex= (double*)qh_malloc( dim * (dim+1) * sizeof(double)))) {
    +      qh_fprintf_rbox(rbox.ferr, 6196, "rbox error: insufficient memory for simplex\n");
    +      qh_errexit_rbox(qh_ERRmem); /* qh_ERRmem */
    +    }
    +    simplexp= simplex;
    +    if (isregular) {
    +      for (i=0; i randmax/2)
    +          coord[dim-1]= -coord[dim-1];
    +      /* ============= project 'Wn' point toward boundary =============== */
    +      }else if (iswidth && !issphere) {
    +        j= qh_RANDOMint % gendim;
    +        if (coord[j] < 0)
    +          coord[j]= -1.0 - coord[j] * width;
    +        else
    +          coord[j]= 1.0 - coord[j] * width;
    +      }
    +      /* ============= scale point to box =============== */
    +      for (k=0; k=0; k--) {
    +        if (j & ( 1 << k))
    +          qh_out1( cube);
    +        else
    +          qh_out1( -cube);
    +      }
    +      qh_fprintf_rbox(rbox.fout, 9400, "\n");
    +    }
    +  }
    +
    +  /* ============= write diamond vertices =============== */
    +  if (adddiamond) {
    +    for (j=0; j=0; k--) {
    +        if (j/2 != k)
    +          qh_out1( 0.0);
    +        else if (j & 0x1)
    +          qh_out1( diamond);
    +        else
    +          qh_out1( -diamond);
    +      }
    +      qh_fprintf_rbox(rbox.fout, 9401, "\n");
    +    }
    +  }
    +
    +  if (iscdd)
    +    qh_fprintf_rbox(rbox.fout, 9402, "end\nhull\n");
    +
    +  /* same code for error exit and normal return */
    +  qh_free(simplex);
    +  rbox_inuse= False;
    +  return qh_ERRnone;
    +} /* rboxpoints */
    +
    +/*------------------------------------------------
    +outxxx - output functions for qh_rboxpoints
    +*/
    +int qh_roundi( double a) {
    +  if (a < 0.0) {
    +    if (a - 0.5 < INT_MIN) {
    +      qh_fprintf_rbox(rbox.ferr, 6200, "rbox input error: negative coordinate %2.2g is too large.  Reduce 'Bn'\n", a);
    +      qh_errexit_rbox(qh_ERRinput);
    +    }
    +    return (int)(a - 0.5);
    +  }else {
    +    if (a + 0.5 > INT_MAX) {
    +      qh_fprintf_rbox(rbox.ferr, 6201, "rbox input error: coordinate %2.2g is too large.  Reduce 'Bn'\n", a);
    +      qh_errexit_rbox(qh_ERRinput);
    +    }
    +    return (int)(a + 0.5);
    +  }
    +} /* qh_roundi */
    +
    +void qh_out1(double a) {
    +
    +  if (rbox.isinteger)
    +    qh_fprintf_rbox(rbox.fout, 9403, "%d ", qh_roundi( a+rbox.out_offset));
    +  else
    +    qh_fprintf_rbox(rbox.fout, 9404, qh_REAL_1, a+rbox.out_offset);
    +} /* qh_out1 */
    +
    +void qh_out2n( double a, double b) {
    +
    +  if (rbox.isinteger)
    +    qh_fprintf_rbox(rbox.fout, 9405, "%d %d\n", qh_roundi(a+rbox.out_offset), qh_roundi(b+rbox.out_offset));
    +  else
    +    qh_fprintf_rbox(rbox.fout, 9406, qh_REAL_2n, a+rbox.out_offset, b+rbox.out_offset);
    +} /* qh_out2n */
    +
    +void qh_out3n( double a, double b, double c) {
    +
    +  if (rbox.isinteger)
    +    qh_fprintf_rbox(rbox.fout, 9407, "%d %d %d\n", qh_roundi(a+rbox.out_offset), qh_roundi(b+rbox.out_offset), qh_roundi(c+rbox.out_offset));
    +  else
    +    qh_fprintf_rbox(rbox.fout, 9408, qh_REAL_3n, a+rbox.out_offset, b+rbox.out_offset, c+rbox.out_offset);
    +} /* qh_out3n */
    +
    +void qh_outcoord(int iscdd, double *coord, int dim) {
    +    double *p= coord;
    +    int k;
    +
    +    if (iscdd)
    +      qh_out1( 1.0);
    +    for (k=0; k < dim; k++)
    +      qh_out1(*(p++));
    +    qh_fprintf_rbox(rbox.fout, 9396, "\n");
    +} /* qh_outcoord */
    +
    +void qh_outcoincident(int coincidentpoints, double radius, int iscdd, double *coord, int dim) {
    +  double *p;
    +  double randr, delta;
    +  int i,k;
    +  double randmax= qh_RANDOMmax;
    +
    +  for (i= 0; i
      ---------------------------------
    +
    +   stat.c
    +   contains all statistics that are collected for qhull
    +
    +   see qh-stat.htm and stat.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/stat.c#5 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*============ global data structure ==========*/
    +
    +#if qh_QHpointer
    +qhstatT *qh_qhstat=NULL;  /* global data structure */
    +#else
    +qhstatT qh_qhstat;   /* add "={0}" if this causes a compiler error */
    +#endif
    +
    +/*========== functions in alphabetic order ================*/
    +
    +/*---------------------------------
    +
    +  qh_allstatA()
    +    define statistics in groups of 20
    +
    +  notes:
    +    (otherwise, 'gcc -O2' uses too much memory)
    +    uses qhstat.next
    +*/
    +void qh_allstatA(void) {
    +
    +   /* zdef_(type,name,doc,average) */
    +  zzdef_(zdoc, Zdoc2, "precision statistics", -1);
    +  zdef_(zinc, Znewvertex, NULL, -1);
    +  zdef_(wadd, Wnewvertex, "ave. distance of a new vertex to a facet(!0s)", Znewvertex);
    +  zzdef_(wmax, Wnewvertexmax, "max. distance of a new vertex to a facet", -1);
    +  zdef_(wmax, Wvertexmax, "max. distance of an output vertex to a facet", -1);
    +  zdef_(wmin, Wvertexmin, "min. distance of an output vertex to a facet", -1);
    +  zdef_(wmin, Wmindenom, "min. denominator in hyperplane computation", -1);
    +
    +  qhstat precision= qhstat next;  /* call qh_precision for each of these */
    +  zzdef_(zdoc, Zdoc3, "precision problems (corrected unless 'Q0' or an error)", -1);
    +  zzdef_(zinc, Zcoplanarridges, "coplanar half ridges in output", -1);
    +  zzdef_(zinc, Zconcaveridges, "concave half ridges in output", -1);
    +  zzdef_(zinc, Zflippedfacets, "flipped facets", -1);
    +  zzdef_(zinc, Zcoplanarhorizon, "coplanar horizon facets for new vertices", -1);
    +  zzdef_(zinc, Zcoplanarpart, "coplanar points during partitioning", -1);
    +  zzdef_(zinc, Zminnorm, "degenerate hyperplanes recomputed with gaussian elimination", -1);
    +  zzdef_(zinc, Znearlysingular, "nearly singular or axis-parallel hyperplanes", -1);
    +  zzdef_(zinc, Zback0, "zero divisors during back substitute", -1);
    +  zzdef_(zinc, Zgauss0, "zero divisors during gaussian elimination", -1);
    +  zzdef_(zinc, Zmultiridge, "ridges with multiple neighbors", -1);
    +}
    +void qh_allstatB(void) {
    +  zzdef_(zdoc, Zdoc1, "summary information", -1);
    +  zdef_(zinc, Zvertices, "number of vertices in output", -1);
    +  zdef_(zinc, Znumfacets, "number of facets in output", -1);
    +  zdef_(zinc, Znonsimplicial, "number of non-simplicial facets in output", -1);
    +  zdef_(zinc, Znowsimplicial, "number of simplicial facets that were merged", -1);
    +  zdef_(zinc, Znumridges, "number of ridges in output", -1);
    +  zdef_(zadd, Znumridges, "average number of ridges per facet", Znumfacets);
    +  zdef_(zmax, Zmaxridges, "maximum number of ridges", -1);
    +  zdef_(zadd, Znumneighbors, "average number of neighbors per facet", Znumfacets);
    +  zdef_(zmax, Zmaxneighbors, "maximum number of neighbors", -1);
    +  zdef_(zadd, Znumvertices, "average number of vertices per facet", Znumfacets);
    +  zdef_(zmax, Zmaxvertices, "maximum number of vertices", -1);
    +  zdef_(zadd, Znumvneighbors, "average number of neighbors per vertex", Zvertices);
    +  zdef_(zmax, Zmaxvneighbors, "maximum number of neighbors", -1);
    +  zdef_(wadd, Wcpu, "cpu seconds for qhull after input", -1);
    +  zdef_(zinc, Ztotvertices, "vertices created altogether", -1);
    +  zzdef_(zinc, Zsetplane, "facets created altogether", -1);
    +  zdef_(zinc, Ztotridges, "ridges created altogether", -1);
    +  zdef_(zinc, Zpostfacets, "facets before post merge", -1);
    +  zdef_(zadd, Znummergetot, "average merges per facet(at most 511)", Znumfacets);
    +  zdef_(zmax, Znummergemax, "  maximum merges for a facet(at most 511)", -1);
    +  zdef_(zinc, Zangle, NULL, -1);
    +  zdef_(wadd, Wangle, "average angle(cosine) of facet normals for all ridges", Zangle);
    +  zdef_(wmax, Wanglemax, "  maximum angle(cosine) of facet normals across a ridge", -1);
    +  zdef_(wmin, Wanglemin, "  minimum angle(cosine) of facet normals across a ridge", -1);
    +  zdef_(wadd, Wareatot, "total area of facets", -1);
    +  zdef_(wmax, Wareamax, "  maximum facet area", -1);
    +  zdef_(wmin, Wareamin, "  minimum facet area", -1);
    +}
    +void qh_allstatC(void) {
    +  zdef_(zdoc, Zdoc9, "build hull statistics", -1);
    +  zzdef_(zinc, Zprocessed, "points processed", -1);
    +  zzdef_(zinc, Zretry, "retries due to precision problems", -1);
    +  zdef_(wmax, Wretrymax, "  max. random joggle", -1);
    +  zdef_(zmax, Zmaxvertex, "max. vertices at any one time", -1);
    +  zdef_(zinc, Ztotvisible, "ave. visible facets per iteration", Zprocessed);
    +  zdef_(zinc, Zinsidevisible, "  ave. visible facets without an horizon neighbor", Zprocessed);
    +  zdef_(zadd, Zvisfacettot,  "  ave. facets deleted per iteration", Zprocessed);
    +  zdef_(zmax, Zvisfacetmax,  "    maximum", -1);
    +  zdef_(zadd, Zvisvertextot, "ave. visible vertices per iteration", Zprocessed);
    +  zdef_(zmax, Zvisvertexmax, "    maximum", -1);
    +  zdef_(zinc, Ztothorizon, "ave. horizon facets per iteration", Zprocessed);
    +  zdef_(zadd, Znewfacettot,  "ave. new or merged facets per iteration", Zprocessed);
    +  zdef_(zmax, Znewfacetmax,  "    maximum(includes initial simplex)", -1);
    +  zdef_(wadd, Wnewbalance, "average new facet balance", Zprocessed);
    +  zdef_(wadd, Wnewbalance2, "  standard deviation", -1);
    +  zdef_(wadd, Wpbalance, "average partition balance", Zpbalance);
    +  zdef_(wadd, Wpbalance2, "  standard deviation", -1);
    +  zdef_(zinc, Zpbalance, "  number of trials", -1);
    +  zdef_(zinc, Zsearchpoints, "searches of all points for initial simplex", -1);
    +  zdef_(zinc, Zdetsimplex, "determinants computed(area & initial hull)", -1);
    +  zdef_(zinc, Znoarea, "determinants not computed because vertex too low", -1);
    +  zdef_(zinc, Znotmax, "points ignored(!above max_outside)", -1);
    +  zdef_(zinc, Znotgood, "points ignored(!above a good facet)", -1);
    +  zdef_(zinc, Znotgoodnew, "points ignored(didn't create a good new facet)", -1);
    +  zdef_(zinc, Zgoodfacet, "good facets found", -1);
    +  zzdef_(zinc, Znumvisibility, "distance tests for facet visibility", -1);
    +  zdef_(zinc, Zdistvertex, "distance tests to report minimum vertex", -1);
    +  zzdef_(zinc, Ztotcheck, "points checked for facets' outer planes", -1);
    +  zzdef_(zinc, Zcheckpart, "  ave. distance tests per check", Ztotcheck);
    +}
    +void qh_allstatD(void) {
    +  zdef_(zinc, Zvisit, "resets of visit_id", -1);
    +  zdef_(zinc, Zvvisit, "  resets of vertex_visit", -1);
    +  zdef_(zmax, Zvisit2max, "  max visit_id/2", -1);
    +  zdef_(zmax, Zvvisit2max, "  max vertex_visit/2", -1);
    +
    +  zdef_(zdoc, Zdoc4, "partitioning statistics(see previous for outer planes)", -1);
    +  zzdef_(zadd, Zdelvertextot, "total vertices deleted", -1);
    +  zdef_(zmax, Zdelvertexmax, "    maximum vertices deleted per iteration", -1);
    +  zdef_(zinc, Zfindbest, "calls to findbest", -1);
    +  zdef_(zadd, Zfindbesttot, " ave. facets tested", Zfindbest);
    +  zdef_(zmax, Zfindbestmax, " max. facets tested", -1);
    +  zdef_(zadd, Zfindcoplanar, " ave. coplanar search", Zfindbest);
    +  zdef_(zinc, Zfindnew, "calls to findbestnew", -1);
    +  zdef_(zadd, Zfindnewtot, " ave. facets tested", Zfindnew);
    +  zdef_(zmax, Zfindnewmax, " max. facets tested", -1);
    +  zdef_(zinc, Zfindnewjump, " ave. clearly better", Zfindnew);
    +  zdef_(zinc, Zfindnewsharp, " calls due to qh_sharpnewfacets", -1);
    +  zdef_(zinc, Zfindhorizon, "calls to findhorizon", -1);
    +  zdef_(zadd, Zfindhorizontot, " ave. facets tested", Zfindhorizon);
    +  zdef_(zmax, Zfindhorizonmax, " max. facets tested", -1);
    +  zdef_(zinc, Zfindjump,       " ave. clearly better", Zfindhorizon);
    +  zdef_(zinc, Zparthorizon, " horizon facets better than bestfacet", -1);
    +  zdef_(zinc, Zpartangle, "angle tests for repartitioned coplanar points", -1);
    +  zdef_(zinc, Zpartflip, "  repartitioned coplanar points for flipped orientation", -1);
    +}
    +void qh_allstatE(void) {
    +  zdef_(zinc, Zpartinside, "inside points", -1);
    +  zdef_(zinc, Zpartnear, "  inside points kept with a facet", -1);
    +  zdef_(zinc, Zcoplanarinside, "  inside points that were coplanar with a facet", -1);
    +  zdef_(zinc, Zbestlower, "calls to findbestlower", -1);
    +  zdef_(zinc, Zbestlowerv, "  with search of vertex neighbors", -1);
    +  zdef_(zinc, Zbestlowerall, "  with rare search of all facets", -1);
    +  zdef_(zmax, Zbestloweralln, "  facets per search of all facets", -1);
    +  zdef_(wadd, Wmaxout, "difference in max_outside at final check", -1);
    +  zzdef_(zinc, Zpartitionall, "distance tests for initial partition", -1);
    +  zdef_(zinc, Ztotpartition, "partitions of a point", -1);
    +  zzdef_(zinc, Zpartition, "distance tests for partitioning", -1);
    +  zzdef_(zinc, Zdistcheck, "distance tests for checking flipped facets", -1);
    +  zzdef_(zinc, Zdistconvex, "distance tests for checking convexity", -1);
    +  zdef_(zinc, Zdistgood, "distance tests for checking good point", -1);
    +  zdef_(zinc, Zdistio, "distance tests for output", -1);
    +  zdef_(zinc, Zdiststat, "distance tests for statistics", -1);
    +  zdef_(zinc, Zdistplane, "total number of distance tests", -1);
    +  zdef_(zinc, Ztotpartcoplanar, "partitions of coplanar points or deleted vertices", -1);
    +  zzdef_(zinc, Zpartcoplanar, "   distance tests for these partitions", -1);
    +  zdef_(zinc, Zcomputefurthest, "distance tests for computing furthest", -1);
    +}
    +void qh_allstatE2(void) {
    +  zdef_(zdoc, Zdoc5, "statistics for matching ridges", -1);
    +  zdef_(zinc, Zhashlookup, "total lookups for matching ridges of new facets", -1);
    +  zdef_(zinc, Zhashtests, "average number of tests to match a ridge", Zhashlookup);
    +  zdef_(zinc, Zhashridge, "total lookups of subridges(duplicates and boundary)", -1);
    +  zdef_(zinc, Zhashridgetest, "average number of tests per subridge", Zhashridge);
    +  zdef_(zinc, Zdupsame, "duplicated ridges in same merge cycle", -1);
    +  zdef_(zinc, Zdupflip, "duplicated ridges with flipped facets", -1);
    +
    +  zdef_(zdoc, Zdoc6, "statistics for determining merges", -1);
    +  zdef_(zinc, Zangletests, "angles computed for ridge convexity", -1);
    +  zdef_(zinc, Zbestcentrum, "best merges used centrum instead of vertices",-1);
    +  zzdef_(zinc, Zbestdist, "distance tests for best merge", -1);
    +  zzdef_(zinc, Zcentrumtests, "distance tests for centrum convexity", -1);
    +  zzdef_(zinc, Zdistzero, "distance tests for checking simplicial convexity", -1);
    +  zdef_(zinc, Zcoplanarangle, "coplanar angles in getmergeset", -1);
    +  zdef_(zinc, Zcoplanarcentrum, "coplanar centrums in getmergeset", -1);
    +  zdef_(zinc, Zconcaveridge, "concave ridges in getmergeset", -1);
    +}
    +void qh_allstatF(void) {
    +  zdef_(zdoc, Zdoc7, "statistics for merging", -1);
    +  zdef_(zinc, Zpremergetot, "merge iterations", -1);
    +  zdef_(zadd, Zmergeinittot, "ave. initial non-convex ridges per iteration", Zpremergetot);
    +  zdef_(zadd, Zmergeinitmax, "  maximum", -1);
    +  zdef_(zadd, Zmergesettot, "  ave. additional non-convex ridges per iteration", Zpremergetot);
    +  zdef_(zadd, Zmergesetmax, "  maximum additional in one pass", -1);
    +  zdef_(zadd, Zmergeinittot2, "initial non-convex ridges for post merging", -1);
    +  zdef_(zadd, Zmergesettot2, "  additional non-convex ridges", -1);
    +  zdef_(wmax, Wmaxoutside, "max distance of vertex or coplanar point above facet(w/roundoff)", -1);
    +  zdef_(wmin, Wminvertex, "max distance of merged vertex below facet(or roundoff)", -1);
    +  zdef_(zinc, Zwidefacet, "centrums frozen due to a wide merge", -1);
    +  zdef_(zinc, Zwidevertices, "centrums frozen due to extra vertices", -1);
    +  zzdef_(zinc, Ztotmerge, "total number of facets or cycles of facets merged", -1);
    +  zdef_(zinc, Zmergesimplex, "merged a simplex", -1);
    +  zdef_(zinc, Zonehorizon, "simplices merged into coplanar horizon", -1);
    +  zzdef_(zinc, Zcyclehorizon, "cycles of facets merged into coplanar horizon", -1);
    +  zzdef_(zadd, Zcyclefacettot, "  ave. facets per cycle", Zcyclehorizon);
    +  zdef_(zmax, Zcyclefacetmax, "  max. facets", -1);
    +  zdef_(zinc, Zmergeintohorizon, "new facets merged into horizon", -1);
    +  zdef_(zinc, Zmergenew, "new facets merged", -1);
    +  zdef_(zinc, Zmergehorizon, "horizon facets merged into new facets", -1);
    +  zdef_(zinc, Zmergevertex, "vertices deleted by merging", -1);
    +  zdef_(zinc, Zcyclevertex, "vertices deleted by merging into coplanar horizon", -1);
    +  zdef_(zinc, Zdegenvertex, "vertices deleted by degenerate facet", -1);
    +  zdef_(zinc, Zmergeflipdup, "merges due to flipped facets in duplicated ridge", -1);
    +  zdef_(zinc, Zneighbor, "merges due to redundant neighbors", -1);
    +  zdef_(zadd, Ztestvneighbor, "non-convex vertex neighbors", -1);
    +}
    +void qh_allstatG(void) {
    +  zdef_(zinc, Zacoplanar, "merges due to angle coplanar facets", -1);
    +  zdef_(wadd, Wacoplanartot, "  average merge distance", Zacoplanar);
    +  zdef_(wmax, Wacoplanarmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zcoplanar, "merges due to coplanar facets", -1);
    +  zdef_(wadd, Wcoplanartot, "  average merge distance", Zcoplanar);
    +  zdef_(wmax, Wcoplanarmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zconcave, "merges due to concave facets", -1);
    +  zdef_(wadd, Wconcavetot, "  average merge distance", Zconcave);
    +  zdef_(wmax, Wconcavemax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zavoidold, "coplanar/concave merges due to avoiding old merge", -1);
    +  zdef_(wadd, Wavoidoldtot, "  average merge distance", Zavoidold);
    +  zdef_(wmax, Wavoidoldmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zdegen, "merges due to degenerate facets", -1);
    +  zdef_(wadd, Wdegentot, "  average merge distance", Zdegen);
    +  zdef_(wmax, Wdegenmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zflipped, "merges due to removing flipped facets", -1);
    +  zdef_(wadd, Wflippedtot, "  average merge distance", Zflipped);
    +  zdef_(wmax, Wflippedmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zduplicate, "merges due to duplicated ridges", -1);
    +  zdef_(wadd, Wduplicatetot, "  average merge distance", Zduplicate);
    +  zdef_(wmax, Wduplicatemax, "  maximum merge distance", -1);
    +}
    +void qh_allstatH(void) {
    +  zdef_(zdoc, Zdoc8, "renamed vertex statistics", -1);
    +  zdef_(zinc, Zrenameshare, "renamed vertices shared by two facets", -1);
    +  zdef_(zinc, Zrenamepinch, "renamed vertices in a pinched facet", -1);
    +  zdef_(zinc, Zrenameall, "renamed vertices shared by multiple facets", -1);
    +  zdef_(zinc, Zfindfail, "rename failures due to duplicated ridges", -1);
    +  zdef_(zinc, Zdupridge, "  duplicate ridges detected", -1);
    +  zdef_(zinc, Zdelridge, "deleted ridges due to renamed vertices", -1);
    +  zdef_(zinc, Zdropneighbor, "dropped neighbors due to renamed vertices", -1);
    +  zdef_(zinc, Zdropdegen, "degenerate facets due to dropped neighbors", -1);
    +  zdef_(zinc, Zdelfacetdup, "  facets deleted because of no neighbors", -1);
    +  zdef_(zinc, Zremvertex, "vertices removed from facets due to no ridges", -1);
    +  zdef_(zinc, Zremvertexdel, "  deleted", -1);
    +  zdef_(zinc, Zintersectnum, "vertex intersections for locating redundant vertices", -1);
    +  zdef_(zinc, Zintersectfail, "intersections failed to find a redundant vertex", -1);
    +  zdef_(zinc, Zintersect, "intersections found redundant vertices", -1);
    +  zdef_(zadd, Zintersecttot, "   ave. number found per vertex", Zintersect);
    +  zdef_(zmax, Zintersectmax, "   max. found for a vertex", -1);
    +  zdef_(zinc, Zvertexridge, NULL, -1);
    +  zdef_(zadd, Zvertexridgetot, "  ave. number of ridges per tested vertex", Zvertexridge);
    +  zdef_(zmax, Zvertexridgemax, "  max. number of ridges per tested vertex", -1);
    +
    +  zdef_(zdoc, Zdoc10, "memory usage statistics(in bytes)", -1);
    +  zdef_(zadd, Zmemfacets, "for facets and their normals, neighbor and vertex sets", -1);
    +  zdef_(zadd, Zmemvertices, "for vertices and their neighbor sets", -1);
    +  zdef_(zadd, Zmempoints, "for input points and outside and coplanar sets",-1);
    +  zdef_(zadd, Zmemridges, "for ridges and their vertex sets", -1);
    +} /* allstat */
    +
    +void qh_allstatI(void) {
    +  qhstat vridges= qhstat next;
    +  zzdef_(zdoc, Zdoc11, "Voronoi ridge statistics", -1);
    +  zzdef_(zinc, Zridge, "non-simplicial Voronoi vertices for all ridges", -1);
    +  zzdef_(wadd, Wridge, "  ave. distance to ridge", Zridge);
    +  zzdef_(wmax, Wridgemax, "  max. distance to ridge", -1);
    +  zzdef_(zinc, Zridgemid, "bounded ridges", -1);
    +  zzdef_(wadd, Wridgemid, "  ave. distance of midpoint to ridge", Zridgemid);
    +  zzdef_(wmax, Wridgemidmax, "  max. distance of midpoint to ridge", -1);
    +  zzdef_(zinc, Zridgeok, "bounded ridges with ok normal", -1);
    +  zzdef_(wadd, Wridgeok, "  ave. angle to ridge", Zridgeok);
    +  zzdef_(wmax, Wridgeokmax, "  max. angle to ridge", -1);
    +  zzdef_(zinc, Zridge0, "bounded ridges with near-zero normal", -1);
    +  zzdef_(wadd, Wridge0, "  ave. angle to ridge", Zridge0);
    +  zzdef_(wmax, Wridge0max, "  max. angle to ridge", -1);
    +
    +  zdef_(zdoc, Zdoc12, "Triangulation statistics(Qt)", -1);
    +  zdef_(zinc, Ztricoplanar, "non-simplicial facets triangulated", -1);
    +  zdef_(zadd, Ztricoplanartot, "  ave. new facets created(may be deleted)", Ztricoplanar);
    +  zdef_(zmax, Ztricoplanarmax, "  max. new facets created", -1);
    +  zdef_(zinc, Ztrinull, "null new facets deleted(duplicated vertex)", -1);
    +  zdef_(zinc, Ztrimirror, "mirrored pairs of new facets deleted(same vertices)", -1);
    +  zdef_(zinc, Ztridegen, "degenerate new facets in output(same ridge)", -1);
    +} /* allstat */
    +
    +/*---------------------------------
    +
    +  qh_allstatistics()
    +    reset printed flag for all statistics
    +*/
    +void qh_allstatistics(void) {
    +  int i;
    +
    +  for(i=ZEND; i--; )
    +    qhstat printed[i]= False;
    +} /* allstatistics */
    +
    +#if qh_KEEPstatistics
    +/*---------------------------------
    +
    +  qh_collectstatistics()
    +    collect statistics for qh.facet_list
    +
    +*/
    +void qh_collectstatistics(void) {
    +  facetT *facet, *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  realT dotproduct, dist;
    +  int sizneighbors, sizridges, sizvertices, i;
    +
    +  qh old_randomdist= qh RANDOMdist;
    +  qh RANDOMdist= False;
    +  zval_(Zmempoints)= qh num_points * qh normal_size +
    +                             sizeof(qhT) + sizeof(qhstatT);
    +  zval_(Zmemfacets)= 0;
    +  zval_(Zmemridges)= 0;
    +  zval_(Zmemvertices)= 0;
    +  zval_(Zangle)= 0;
    +  wval_(Wangle)= 0.0;
    +  zval_(Znumridges)= 0;
    +  zval_(Znumfacets)= 0;
    +  zval_(Znumneighbors)= 0;
    +  zval_(Znumvertices)= 0;
    +  zval_(Znumvneighbors)= 0;
    +  zval_(Znummergetot)= 0;
    +  zval_(Znummergemax)= 0;
    +  zval_(Zvertices)= qh num_vertices - qh_setsize(qh del_vertices);
    +  if (qh MERGING || qh APPROXhull || qh JOGGLEmax < REALmax/2)
    +    wmax_(Wmaxoutside, qh max_outside);
    +  if (qh MERGING)
    +    wmin_(Wminvertex, qh min_vertex);
    +  FORALLfacets
    +    facet->seen= False;
    +  if (qh DELAUNAY) {
    +    FORALLfacets {
    +      if (facet->upperdelaunay != qh UPPERdelaunay)
    +        facet->seen= True; /* remove from angle statistics */
    +    }
    +  }
    +  FORALLfacets {
    +    if (facet->visible && qh NEWfacets)
    +      continue;
    +    sizvertices= qh_setsize(facet->vertices);
    +    sizneighbors= qh_setsize(facet->neighbors);
    +    sizridges= qh_setsize(facet->ridges);
    +    zinc_(Znumfacets);
    +    zadd_(Znumvertices, sizvertices);
    +    zmax_(Zmaxvertices, sizvertices);
    +    zadd_(Znumneighbors, sizneighbors);
    +    zmax_(Zmaxneighbors, sizneighbors);
    +    zadd_(Znummergetot, facet->nummerge);
    +    i= facet->nummerge; /* avoid warnings */
    +    zmax_(Znummergemax, i);
    +    if (!facet->simplicial) {
    +      if (sizvertices == qh hull_dim) {
    +        zinc_(Znowsimplicial);
    +      }else {
    +        zinc_(Znonsimplicial);
    +      }
    +    }
    +    if (sizridges) {
    +      zadd_(Znumridges, sizridges);
    +      zmax_(Zmaxridges, sizridges);
    +    }
    +    zadd_(Zmemfacets, sizeof(facetT) + qh normal_size + 2*sizeof(setT)
    +       + SETelemsize * (sizneighbors + sizvertices));
    +    if (facet->ridges) {
    +      zadd_(Zmemridges,
    +         sizeof(setT) + SETelemsize * sizridges + sizridges *
    +         (sizeof(ridgeT) + sizeof(setT) + SETelemsize * (qh hull_dim-1))/2);
    +    }
    +    if (facet->outsideset)
    +      zadd_(Zmempoints, sizeof(setT) + SETelemsize * qh_setsize(facet->outsideset));
    +    if (facet->coplanarset)
    +      zadd_(Zmempoints, sizeof(setT) + SETelemsize * qh_setsize(facet->coplanarset));
    +    if (facet->seen) /* Delaunay upper envelope */
    +      continue;
    +    facet->seen= True;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor == qh_DUPLICATEridge || neighbor == qh_MERGEridge
    +          || neighbor->seen || !facet->normal || !neighbor->normal)
    +        continue;
    +      dotproduct= qh_getangle(facet->normal, neighbor->normal);
    +      zinc_(Zangle);
    +      wadd_(Wangle, dotproduct);
    +      wmax_(Wanglemax, dotproduct)
    +      wmin_(Wanglemin, dotproduct)
    +    }
    +    if (facet->normal) {
    +      FOREACHvertex_(facet->vertices) {
    +        zinc_(Zdiststat);
    +        qh_distplane(vertex->point, facet, &dist);
    +        wmax_(Wvertexmax, dist);
    +        wmin_(Wvertexmin, dist);
    +      }
    +    }
    +  }
    +  FORALLvertices {
    +    if (vertex->deleted)
    +      continue;
    +    zadd_(Zmemvertices, sizeof(vertexT));
    +    if (vertex->neighbors) {
    +      sizneighbors= qh_setsize(vertex->neighbors);
    +      zadd_(Znumvneighbors, sizneighbors);
    +      zmax_(Zmaxvneighbors, sizneighbors);
    +      zadd_(Zmemvertices, sizeof(vertexT) + SETelemsize * sizneighbors);
    +    }
    +  }
    +  qh RANDOMdist= qh old_randomdist;
    +} /* collectstatistics */
    +#endif /* qh_KEEPstatistics */
    +
    +/*---------------------------------
    +
    +  qh_freestatistics(  )
    +    free memory used for statistics
    +*/
    +void qh_freestatistics(void) {
    +
    +#if qh_QHpointer
    +  qh_free(qh_qhstat);
    +  qh_qhstat= NULL;
    +#endif
    +} /* freestatistics */
    +
    +/*---------------------------------
    +
    +  qh_initstatistics(  )
    +    allocate and initialize statistics
    +
    +  notes:
    +    uses qh_malloc() instead of qh_memalloc() since mem.c not set up yet
    +    NOerrors -- qh_initstatistics can not use qh_errexit(), qh_fprintf, or qh.ferr
    +    On first call, only qhmem.ferr is defined.  qh_memalloc is not setup.
    +    Also invoked by QhullQh().
    +*/
    +void qh_initstatistics(void) {
    +  int i;
    +  realT realx;
    +  int intx;
    +
    +#if qh_QHpointer
    +  if(qh_qhstat){  /* qh_initstatistics may be called from Qhull::resetStatistics() */
    +      qh_free(qh_qhstat);
    +      qh_qhstat= 0;
    +  }
    +  if (!(qh_qhstat= (qhstatT *)qh_malloc(sizeof(qhstatT)))) {
    +    qh_fprintf_stderr(6183, "qhull error (qh_initstatistics): insufficient memory\n");
    +    qh_exit(qh_ERRmem);  /* can not use qh_errexit() */
    +  }
    +#endif
    +
    +  qhstat next= 0;
    +  qh_allstatA();
    +  qh_allstatB();
    +  qh_allstatC();
    +  qh_allstatD();
    +  qh_allstatE();
    +  qh_allstatE2();
    +  qh_allstatF();
    +  qh_allstatG();
    +  qh_allstatH();
    +  qh_allstatI();
    +  if (qhstat next > (int)sizeof(qhstat id)) {
    +    qh_fprintf(qhmem.ferr, 6184, "qhull error (qh_initstatistics): increase size of qhstat.id[].\n\
    +      qhstat.next %d should be <= sizeof(qhstat id) %d\n", qhstat next, (int)sizeof(qhstat id));
    +#if 0 /* for locating error, Znumridges should be duplicated */
    +    for(i=0; i < ZEND; i++) {
    +      int j;
    +      for(j=i+1; j < ZEND; j++) {
    +        if (qhstat id[i] == qhstat id[j]) {
    +          qh_fprintf(qhmem.ferr, 6185, "qhull error (qh_initstatistics): duplicated statistic %d at indices %d and %d\n",
    +              qhstat id[i], i, j);
    +        }
    +      }
    +    }
    +#endif
    +    qh_exit(qh_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  qhstat init[zinc].i= 0;
    +  qhstat init[zadd].i= 0;
    +  qhstat init[zmin].i= INT_MAX;
    +  qhstat init[zmax].i= INT_MIN;
    +  qhstat init[wadd].r= 0;
    +  qhstat init[wmin].r= REALmax;
    +  qhstat init[wmax].r= -REALmax;
    +  for(i=0; i < ZEND; i++) {
    +    if (qhstat type[i] > ZTYPEreal) {
    +      realx= qhstat init[(unsigned char)(qhstat type[i])].r;
    +      qhstat stats[i].r= realx;
    +    }else if (qhstat type[i] != zdoc) {
    +      intx= qhstat init[(unsigned char)(qhstat type[i])].i;
    +      qhstat stats[i].i= intx;
    +    }
    +  }
    +} /* initstatistics */
    +
    +/*---------------------------------
    +
    +  qh_newstats(  )
    +    returns True if statistics for zdoc
    +
    +  returns:
    +    next zdoc
    +*/
    +boolT qh_newstats(int idx, int *nextindex) {
    +  boolT isnew= False;
    +  int start, i;
    +
    +  if (qhstat type[qhstat id[idx]] == zdoc)
    +    start= idx+1;
    +  else
    +    start= idx;
    +  for(i= start; i < qhstat next && qhstat type[qhstat id[i]] != zdoc; i++) {
    +    if (!qh_nostatistic(qhstat id[i]) && !qhstat printed[qhstat id[i]])
    +        isnew= True;
    +  }
    +  *nextindex= i;
    +  return isnew;
    +} /* newstats */
    +
    +/*---------------------------------
    +
    +  qh_nostatistic( index )
    +    true if no statistic to print
    +*/
    +boolT qh_nostatistic(int i) {
    +
    +  if ((qhstat type[i] > ZTYPEreal
    +       &&qhstat stats[i].r == qhstat init[(unsigned char)(qhstat type[i])].r)
    +      || (qhstat type[i] < ZTYPEreal
    +          &&qhstat stats[i].i == qhstat init[(unsigned char)(qhstat type[i])].i))
    +    return True;
    +  return False;
    +} /* nostatistic */
    +
    +#if qh_KEEPstatistics
    +/*---------------------------------
    +
    +  qh_printallstatistics( fp, string )
    +    print all statistics with header 'string'
    +*/
    +void qh_printallstatistics(FILE *fp, const char *string) {
    +
    +  qh_allstatistics();
    +  qh_collectstatistics();
    +  qh_printstatistics(fp, string);
    +  qh_memstatistics(fp);
    +}
    +
    +
    +/*---------------------------------
    +
    +  qh_printstatistics( fp, string )
    +    print statistics to a file with header 'string'
    +    skips statistics with qhstat.printed[] (reset with qh_allstatistics)
    +
    +  see:
    +    qh_printallstatistics()
    +*/
    +void qh_printstatistics(FILE *fp, const char *string) {
    +  int i, k;
    +  realT ave;
    +
    +  if (qh num_points != qh num_vertices) {
    +    wval_(Wpbalance)= 0;
    +    wval_(Wpbalance2)= 0;
    +  }else
    +    wval_(Wpbalance2)= qh_stddev(zval_(Zpbalance), wval_(Wpbalance),
    +                                 wval_(Wpbalance2), &ave);
    +  wval_(Wnewbalance2)= qh_stddev(zval_(Zprocessed), wval_(Wnewbalance),
    +                                 wval_(Wnewbalance2), &ave);
    +  qh_fprintf(fp, 9350, "\n\
    +%s\n\
    + qhull invoked by: %s | %s\n%s with options:\n%s\n", string, qh rbox_command,
    +     qh qhull_command, qh_version, qh qhull_options);
    +  qh_fprintf(fp, 9351, "\nprecision constants:\n\
    + %6.2g max. abs. coordinate in the (transformed) input('Qbd:n')\n\
    + %6.2g max. roundoff error for distance computation('En')\n\
    + %6.2g max. roundoff error for angle computations\n\
    + %6.2g min. distance for outside points ('Wn')\n\
    + %6.2g min. distance for visible facets ('Vn')\n\
    + %6.2g max. distance for coplanar facets ('Un')\n\
    + %6.2g max. facet width for recomputing centrum and area\n\
    +",
    +  qh MAXabs_coord, qh DISTround, qh ANGLEround, qh MINoutside,
    +        qh MINvisible, qh MAXcoplanar, qh WIDEfacet);
    +  if (qh KEEPnearinside)
    +    qh_fprintf(fp, 9352, "\
    + %6.2g max. distance for near-inside points\n", qh NEARinside);
    +  if (qh premerge_cos < REALmax/2) qh_fprintf(fp, 9353, "\
    + %6.2g max. cosine for pre-merge angle\n", qh premerge_cos);
    +  if (qh PREmerge) qh_fprintf(fp, 9354, "\
    + %6.2g radius of pre-merge centrum\n", qh premerge_centrum);
    +  if (qh postmerge_cos < REALmax/2) qh_fprintf(fp, 9355, "\
    + %6.2g max. cosine for post-merge angle\n", qh postmerge_cos);
    +  if (qh POSTmerge) qh_fprintf(fp, 9356, "\
    + %6.2g radius of post-merge centrum\n", qh postmerge_centrum);
    +  qh_fprintf(fp, 9357, "\
    + %6.2g max. distance for merging two simplicial facets\n\
    + %6.2g max. roundoff error for arithmetic operations\n\
    + %6.2g min. denominator for divisions\n\
    +  zero diagonal for Gauss: ", qh ONEmerge, REALepsilon, qh MINdenom);
    +  for(k=0; k < qh hull_dim; k++)
    +    qh_fprintf(fp, 9358, "%6.2e ", qh NEARzero[k]);
    +  qh_fprintf(fp, 9359, "\n\n");
    +  for(i=0 ; i < qhstat next; )
    +    qh_printstats(fp, i, &i);
    +} /* printstatistics */
    +#endif /* qh_KEEPstatistics */
    +
    +/*---------------------------------
    +
    +  qh_printstatlevel( fp, id )
    +    print level information for a statistic
    +
    +  notes:
    +    nop if id >= ZEND, printed, or same as initial value
    +*/
    +void qh_printstatlevel(FILE *fp, int id) {
    +#define NULLfield "       "
    +
    +  if (id >= ZEND || qhstat printed[id])
    +    return;
    +  if (qhstat type[id] == zdoc) {
    +    qh_fprintf(fp, 9360, "%s\n", qhstat doc[id]);
    +    return;
    +  }
    +  if (qh_nostatistic(id) || !qhstat doc[id])
    +    return;
    +  qhstat printed[id]= True;
    +  if (qhstat count[id] != -1
    +      && qhstat stats[(unsigned char)(qhstat count[id])].i == 0)
    +    qh_fprintf(fp, 9361, " *0 cnt*");
    +  else if (qhstat type[id] >= ZTYPEreal && qhstat count[id] == -1)
    +    qh_fprintf(fp, 9362, "%7.2g", qhstat stats[id].r);
    +  else if (qhstat type[id] >= ZTYPEreal && qhstat count[id] != -1)
    +    qh_fprintf(fp, 9363, "%7.2g", qhstat stats[id].r/ qhstat stats[(unsigned char)(qhstat count[id])].i);
    +  else if (qhstat type[id] < ZTYPEreal && qhstat count[id] == -1)
    +    qh_fprintf(fp, 9364, "%7d", qhstat stats[id].i);
    +  else if (qhstat type[id] < ZTYPEreal && qhstat count[id] != -1)
    +    qh_fprintf(fp, 9365, "%7.3g", (realT) qhstat stats[id].i / qhstat stats[(unsigned char)(qhstat count[id])].i);
    +  qh_fprintf(fp, 9366, " %s\n", qhstat doc[id]);
    +} /* printstatlevel */
    +
    +
    +/*---------------------------------
    +
    +  qh_printstats( fp, index, nextindex )
    +    print statistics for a zdoc group
    +
    +  returns:
    +    next zdoc if non-null
    +*/
    +void qh_printstats(FILE *fp, int idx, int *nextindex) {
    +  int j, nexti;
    +
    +  if (qh_newstats(idx, &nexti)) {
    +    qh_fprintf(fp, 9367, "\n");
    +    for (j=idx; j--------------------------------
    +
    +  qh_stddev( num, tot, tot2, ave )
    +    compute the standard deviation and average from statistics
    +
    +    tot2 is the sum of the squares
    +  notes:
    +    computes r.m.s.:
    +      (x-ave)^2
    +      == x^2 - 2x tot/num +   (tot/num)^2
    +      == tot2 - 2 tot tot/num + tot tot/num
    +      == tot2 - tot ave
    +*/
    +realT qh_stddev(int num, realT tot, realT tot2, realT *ave) {
    +  realT stddev;
    +
    +  *ave= tot/num;
    +  stddev= sqrt(tot2/num - *ave * *ave);
    +  return stddev;
    +} /* stddev */
    +
    +#endif /* qh_KEEPstatistics */
    +
    +#if !qh_KEEPstatistics
    +void    qh_collectstatistics(void) {}
    +void    qh_printallstatistics(FILE *fp, char *string) {};
    +void    qh_printstatistics(FILE *fp, char *string) {}
    +#endif
    +
    diff --git a/xs/src/qhull/src/libqhull/stat.h b/xs/src/qhull/src/libqhull/stat.h
    new file mode 100644
    index 0000000000..d86fc0a87a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/stat.h
    @@ -0,0 +1,543 @@
    +/*
      ---------------------------------
    +
    +   stat.h
    +     contains all statistics that are collected for qhull
    +
    +   see qh-stat.htm and stat.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/stat.h#4 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +
    +   recompile qhull if you change this file
    +
    +   Integer statistics are Z* while real statistics are W*.
    +
    +   define maydebugx to call a routine at every statistic event
    +
    +*/
    +
    +#ifndef qhDEFstat
    +#define qhDEFstat 1
    +
    +#include "libqhull.h"
    +
    +/*---------------------------------
    +
    +  qh_KEEPstatistics
    +    0 turns off statistic gathering (except zzdef/zzinc/zzadd/zzval/wwval)
    +*/
    +#ifndef qh_KEEPstatistics
    +#define qh_KEEPstatistics 1
    +#endif
    +
    +/*---------------------------------
    +
    +  Zxxx for integers, Wxxx for reals
    +
    +  notes:
    +    be sure that all statistics are defined in stat.c
    +      otherwise initialization may core dump
    +    can pick up all statistics by:
    +      grep '[zw].*_[(][ZW]' *.c >z.x
    +    remove trailers with query">-
    +    remove leaders with  query-replace-regexp [ ^I]+  (
    +*/
    +#if qh_KEEPstatistics
    +enum qh_statistics {     /* alphabetical after Z/W */
    +    Zacoplanar,
    +    Wacoplanarmax,
    +    Wacoplanartot,
    +    Zangle,
    +    Wangle,
    +    Wanglemax,
    +    Wanglemin,
    +    Zangletests,
    +    Wareatot,
    +    Wareamax,
    +    Wareamin,
    +    Zavoidold,
    +    Wavoidoldmax,
    +    Wavoidoldtot,
    +    Zback0,
    +    Zbestcentrum,
    +    Zbestdist,
    +    Zbestlower,
    +    Zbestlowerall,
    +    Zbestloweralln,
    +    Zbestlowerv,
    +    Zcentrumtests,
    +    Zcheckpart,
    +    Zcomputefurthest,
    +    Zconcave,
    +    Wconcavemax,
    +    Wconcavetot,
    +    Zconcaveridges,
    +    Zconcaveridge,
    +    Zcoplanar,
    +    Wcoplanarmax,
    +    Wcoplanartot,
    +    Zcoplanarangle,
    +    Zcoplanarcentrum,
    +    Zcoplanarhorizon,
    +    Zcoplanarinside,
    +    Zcoplanarpart,
    +    Zcoplanarridges,
    +    Wcpu,
    +    Zcyclefacetmax,
    +    Zcyclefacettot,
    +    Zcyclehorizon,
    +    Zcyclevertex,
    +    Zdegen,
    +    Wdegenmax,
    +    Wdegentot,
    +    Zdegenvertex,
    +    Zdelfacetdup,
    +    Zdelridge,
    +    Zdelvertextot,
    +    Zdelvertexmax,
    +    Zdetsimplex,
    +    Zdistcheck,
    +    Zdistconvex,
    +    Zdistgood,
    +    Zdistio,
    +    Zdistplane,
    +    Zdiststat,
    +    Zdistvertex,
    +    Zdistzero,
    +    Zdoc1,
    +    Zdoc2,
    +    Zdoc3,
    +    Zdoc4,
    +    Zdoc5,
    +    Zdoc6,
    +    Zdoc7,
    +    Zdoc8,
    +    Zdoc9,
    +    Zdoc10,
    +    Zdoc11,
    +    Zdoc12,
    +    Zdropdegen,
    +    Zdropneighbor,
    +    Zdupflip,
    +    Zduplicate,
    +    Wduplicatemax,
    +    Wduplicatetot,
    +    Zdupridge,
    +    Zdupsame,
    +    Zflipped,
    +    Wflippedmax,
    +    Wflippedtot,
    +    Zflippedfacets,
    +    Zfindbest,
    +    Zfindbestmax,
    +    Zfindbesttot,
    +    Zfindcoplanar,
    +    Zfindfail,
    +    Zfindhorizon,
    +    Zfindhorizonmax,
    +    Zfindhorizontot,
    +    Zfindjump,
    +    Zfindnew,
    +    Zfindnewmax,
    +    Zfindnewtot,
    +    Zfindnewjump,
    +    Zfindnewsharp,
    +    Zgauss0,
    +    Zgoodfacet,
    +    Zhashlookup,
    +    Zhashridge,
    +    Zhashridgetest,
    +    Zhashtests,
    +    Zinsidevisible,
    +    Zintersect,
    +    Zintersectfail,
    +    Zintersectmax,
    +    Zintersectnum,
    +    Zintersecttot,
    +    Zmaxneighbors,
    +    Wmaxout,
    +    Wmaxoutside,
    +    Zmaxridges,
    +    Zmaxvertex,
    +    Zmaxvertices,
    +    Zmaxvneighbors,
    +    Zmemfacets,
    +    Zmempoints,
    +    Zmemridges,
    +    Zmemvertices,
    +    Zmergeflipdup,
    +    Zmergehorizon,
    +    Zmergeinittot,
    +    Zmergeinitmax,
    +    Zmergeinittot2,
    +    Zmergeintohorizon,
    +    Zmergenew,
    +    Zmergesettot,
    +    Zmergesetmax,
    +    Zmergesettot2,
    +    Zmergesimplex,
    +    Zmergevertex,
    +    Wmindenom,
    +    Wminvertex,
    +    Zminnorm,
    +    Zmultiridge,
    +    Znearlysingular,
    +    Zneighbor,
    +    Wnewbalance,
    +    Wnewbalance2,
    +    Znewfacettot,
    +    Znewfacetmax,
    +    Znewvertex,
    +    Wnewvertex,
    +    Wnewvertexmax,
    +    Znoarea,
    +    Znonsimplicial,
    +    Znowsimplicial,
    +    Znotgood,
    +    Znotgoodnew,
    +    Znotmax,
    +    Znumfacets,
    +    Znummergemax,
    +    Znummergetot,
    +    Znumneighbors,
    +    Znumridges,
    +    Znumvertices,
    +    Znumvisibility,
    +    Znumvneighbors,
    +    Zonehorizon,
    +    Zpartangle,
    +    Zpartcoplanar,
    +    Zpartflip,
    +    Zparthorizon,
    +    Zpartinside,
    +    Zpartition,
    +    Zpartitionall,
    +    Zpartnear,
    +    Zpbalance,
    +    Wpbalance,
    +    Wpbalance2,
    +    Zpostfacets,
    +    Zpremergetot,
    +    Zprocessed,
    +    Zremvertex,
    +    Zremvertexdel,
    +    Zrenameall,
    +    Zrenamepinch,
    +    Zrenameshare,
    +    Zretry,
    +    Wretrymax,
    +    Zridge,
    +    Wridge,
    +    Wridgemax,
    +    Zridge0,
    +    Wridge0,
    +    Wridge0max,
    +    Zridgemid,
    +    Wridgemid,
    +    Wridgemidmax,
    +    Zridgeok,
    +    Wridgeok,
    +    Wridgeokmax,
    +    Zsearchpoints,
    +    Zsetplane,
    +    Ztestvneighbor,
    +    Ztotcheck,
    +    Ztothorizon,
    +    Ztotmerge,
    +    Ztotpartcoplanar,
    +    Ztotpartition,
    +    Ztotridges,
    +    Ztotvertices,
    +    Ztotvisible,
    +    Ztricoplanar,
    +    Ztricoplanarmax,
    +    Ztricoplanartot,
    +    Ztridegen,
    +    Ztrimirror,
    +    Ztrinull,
    +    Wvertexmax,
    +    Wvertexmin,
    +    Zvertexridge,
    +    Zvertexridgetot,
    +    Zvertexridgemax,
    +    Zvertices,
    +    Zvisfacettot,
    +    Zvisfacetmax,
    +    Zvisit,
    +    Zvisit2max,
    +    Zvisvertextot,
    +    Zvisvertexmax,
    +    Zvvisit,
    +    Zvvisit2max,
    +    Zwidefacet,
    +    Zwidevertices,
    +    ZEND};
    +
    +/*---------------------------------
    +
    +  Zxxx/Wxxx statistics that remain defined if qh_KEEPstatistics=0
    +
    +  notes:
    +    be sure to use zzdef, zzinc, etc. with these statistics (no double checking!)
    +*/
    +#else
    +enum qh_statistics {     /* for zzdef etc. macros */
    +  Zback0,
    +  Zbestdist,
    +  Zcentrumtests,
    +  Zcheckpart,
    +  Zconcaveridges,
    +  Zcoplanarhorizon,
    +  Zcoplanarpart,
    +  Zcoplanarridges,
    +  Zcyclefacettot,
    +  Zcyclehorizon,
    +  Zdelvertextot,
    +  Zdistcheck,
    +  Zdistconvex,
    +  Zdistzero,
    +  Zdoc1,
    +  Zdoc2,
    +  Zdoc3,
    +  Zdoc11,
    +  Zflippedfacets,
    +  Zgauss0,
    +  Zminnorm,
    +  Zmultiridge,
    +  Znearlysingular,
    +  Wnewvertexmax,
    +  Znumvisibility,
    +  Zpartcoplanar,
    +  Zpartition,
    +  Zpartitionall,
    +  Zprocessed,
    +  Zretry,
    +  Zridge,
    +  Wridge,
    +  Wridgemax,
    +  Zridge0,
    +  Wridge0,
    +  Wridge0max,
    +  Zridgemid,
    +  Wridgemid,
    +  Wridgemidmax,
    +  Zridgeok,
    +  Wridgeok,
    +  Wridgeokmax,
    +  Zsetplane,
    +  Ztotcheck,
    +  Ztotmerge,
    +    ZEND};
    +#endif
    +
    +/*---------------------------------
    +
    +  ztype
    +    the type of a statistic sets its initial value.
    +
    +  notes:
    +    The type should be the same as the macro for collecting the statistic
    +*/
    +enum ztypes {zdoc,zinc,zadd,zmax,zmin,ZTYPEreal,wadd,wmax,wmin,ZTYPEend};
    +
    +/*========== macros and constants =============*/
    +
    +/*----------------------------------
    +
    +  MAYdebugx
    +    define as maydebug() to be called frequently for error trapping
    +*/
    +#define MAYdebugx
    +
    +/*----------------------------------
    +
    +  zzdef_, zdef_( type, name, doc, -1)
    +    define a statistic (assumes 'qhstat.next= 0;')
    +
    +  zdef_( type, name, doc, count)
    +    define an averaged statistic
    +    printed as name/count
    +*/
    +#define zzdef_(stype,name,string,cnt) qhstat id[qhstat next++]=name; \
    +   qhstat doc[name]= string; qhstat count[name]= cnt; qhstat type[name]= stype
    +#if qh_KEEPstatistics
    +#define zdef_(stype,name,string,cnt) qhstat id[qhstat next++]=name; \
    +   qhstat doc[name]= string; qhstat count[name]= cnt; qhstat type[name]= stype
    +#else
    +#define zdef_(type,name,doc,count)
    +#endif
    +
    +/*----------------------------------
    +
    +  zzinc_( name ), zinc_( name)
    +    increment an integer statistic
    +*/
    +#define zzinc_(id) {MAYdebugx; qhstat stats[id].i++;}
    +#if qh_KEEPstatistics
    +#define zinc_(id) {MAYdebugx; qhstat stats[id].i++;}
    +#else
    +#define zinc_(id) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zzadd_( name, value ), zadd_( name, value ), wadd_( name, value )
    +    add value to an integer or real statistic
    +*/
    +#define zzadd_(id, val) {MAYdebugx; qhstat stats[id].i += (val);}
    +#define wwadd_(id, val) {MAYdebugx; qhstat stats[id].r += (val);}
    +#if qh_KEEPstatistics
    +#define zadd_(id, val) {MAYdebugx; qhstat stats[id].i += (val);}
    +#define wadd_(id, val) {MAYdebugx; qhstat stats[id].r += (val);}
    +#else
    +#define zadd_(id, val) {}
    +#define wadd_(id, val) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zzval_( name ), zval_( name ), wwval_( name )
    +    set or return value of a statistic
    +*/
    +#define zzval_(id) ((qhstat stats[id]).i)
    +#define wwval_(id) ((qhstat stats[id]).r)
    +#if qh_KEEPstatistics
    +#define zval_(id) ((qhstat stats[id]).i)
    +#define wval_(id) ((qhstat stats[id]).r)
    +#else
    +#define zval_(id) qhstat tempi
    +#define wval_(id) qhstat tempr
    +#endif
    +
    +/*----------------------------------
    +
    +  zmax_( id, val ), wmax_( id, value )
    +    maximize id with val
    +*/
    +#define wwmax_(id, val) {MAYdebugx; maximize_(qhstat stats[id].r,(val));}
    +#if qh_KEEPstatistics
    +#define zmax_(id, val) {MAYdebugx; maximize_(qhstat stats[id].i,(val));}
    +#define wmax_(id, val) {MAYdebugx; maximize_(qhstat stats[id].r,(val));}
    +#else
    +#define zmax_(id, val) {}
    +#define wmax_(id, val) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zmin_( id, val ), wmin_( id, value )
    +    minimize id with val
    +*/
    +#if qh_KEEPstatistics
    +#define zmin_(id, val) {MAYdebugx; minimize_(qhstat stats[id].i,(val));}
    +#define wmin_(id, val) {MAYdebugx; minimize_(qhstat stats[id].r,(val));}
    +#else
    +#define zmin_(id, val) {}
    +#define wmin_(id, val) {}
    +#endif
    +
    +/*================== stat.h types ==============*/
    +
    +
    +/*----------------------------------
    +
    +  intrealT
    +    union of integer and real, used for statistics
    +*/
    +typedef union intrealT intrealT;    /* union of int and realT */
    +union intrealT {
    +    int i;
    +    realT r;
    +};
    +
    +/*----------------------------------
    +
    +  qhstat
    +    global data structure for statistics, similar to qh and qhrbox
    +
    +  notes:
    +   access to qh_qhstat is via the "qhstat" macro.  There are two choices
    +   qh_QHpointer = 1     access globals via a pointer
    +                        enables qh_saveqhull() and qh_restoreqhull()
    +                = 0     qh_qhstat is a static data structure
    +                        only one instance of qhull() can be active at a time
    +                        default value
    +   qh_QHpointer is defined in libqhull.h
    +   qh_QHpointer_dllimport and qh_dllimport define qh_qh as __declspec(dllimport) [libqhull.h]
    +
    +   allocated in stat.c using qh_malloc()
    +*/
    +#ifndef DEFqhstatT
    +#define DEFqhstatT 1
    +typedef struct qhstatT qhstatT;
    +#endif
    +
    +#if qh_QHpointer_dllimport
    +#define qhstat qh_qhstat->
    +__declspec(dllimport) extern qhstatT *qh_qhstat;
    +#elif qh_QHpointer
    +#define qhstat qh_qhstat->
    +extern qhstatT *qh_qhstat;
    +#elif qh_dllimport
    +#define qhstat qh_qhstat.
    +__declspec(dllimport) extern qhstatT qh_qhstat;
    +#else
    +#define qhstat qh_qhstat.
    +extern qhstatT qh_qhstat;
    +#endif
    +struct qhstatT {
    +  intrealT   stats[ZEND];     /* integer and real statistics */
    +  unsigned   char id[ZEND+10]; /* id's in print order */
    +  const char *doc[ZEND];       /* array of documentation strings */
    +  short int  count[ZEND];     /* -1 if none, else index of count to use */
    +  char       type[ZEND];      /* type, see ztypes above */
    +  char       printed[ZEND];   /* true, if statistic has been printed */
    +  intrealT   init[ZTYPEend];  /* initial values by types, set initstatistics */
    +
    +  int        next;            /* next index for zdef_ */
    +  int        precision;       /* index for precision problems */
    +  int        vridges;         /* index for Voronoi ridges */
    +  int        tempi;
    +  realT      tempr;
    +};
    +
    +/*========== function prototypes ===========*/
    +
    +void    qh_allstatA(void);
    +void    qh_allstatB(void);
    +void    qh_allstatC(void);
    +void    qh_allstatD(void);
    +void    qh_allstatE(void);
    +void    qh_allstatE2(void);
    +void    qh_allstatF(void);
    +void    qh_allstatG(void);
    +void    qh_allstatH(void);
    +void    qh_allstatI(void);
    +void    qh_allstatistics(void);
    +void    qh_collectstatistics(void);
    +void    qh_freestatistics(void);
    +void    qh_initstatistics(void);
    +boolT   qh_newstats(int idx, int *nextindex);
    +boolT   qh_nostatistic(int i);
    +void    qh_printallstatistics(FILE *fp, const char *string);
    +void    qh_printstatistics(FILE *fp, const char *string);
    +void    qh_printstatlevel(FILE *fp, int id);
    +void    qh_printstats(FILE *fp, int idx, int *nextindex);
    +realT   qh_stddev(int num, realT tot, realT tot2, realT *ave);
    +
    +#endif   /* qhDEFstat */
    diff --git a/xs/src/qhull/src/libqhull/user.c b/xs/src/qhull/src/libqhull/user.c
    new file mode 100644
    index 0000000000..d4726eaa31
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/user.c
    @@ -0,0 +1,538 @@
    +/*
      ---------------------------------
    +
    +   user.c
    +   user redefinable functions
    +
    +   see user2.c for qh_fprintf, qh_malloc, qh_free
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +
    +   see user_eg.c, user_eg2.c, and unix.c for examples.
    +
    +   see user.h for user-definable constants
    +
    +      use qh_NOmem in mem.h to turn off memory management
    +      use qh_NOmerge in user.h to turn off facet merging
    +      set qh_KEEPstatistics in user.h to 0 to turn off statistics
    +
    +   This is unsupported software.  You're welcome to make changes,
    +   but you're on your own if something goes wrong.  Use 'Tc' to
    +   check frequently.  Usually qhull will report an error if
    +   a data structure becomes inconsistent.  If so, it also reports
    +   the last point added to the hull, e.g., 102.  You can then trace
    +   the execution of qhull with "T4P102".
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +
    +   Qhull-template is a template for calling qhull from within your application
    +
    +   if you recompile and load this module, then user.o will not be loaded
    +   from qhull.a
    +
    +   you can add additional quick allocation sizes in qh_user_memsizes
    +
    +   if the other functions here are redefined to not use qh_print...,
    +   then io.o will not be loaded from qhull.a.  See user_eg.c for an
    +   example.  We recommend keeping io.o for the extra debugging
    +   information it supplies.
    +*/
    +
    +#include "qhull_a.h"
    +
    +#include 
    +
    +/*---------------------------------
    +
    +  Qhull-template
    +    Template for calling qhull from inside your program
    +
    +  returns:
    +    exit code(see qh_ERR... in libqhull.h)
    +    all memory freed
    +
    +  notes:
    +    This can be called any number of times.
    +*/
    +#if 0
    +{
    +  int dim;                  /* dimension of points */
    +  int numpoints;            /* number of points */
    +  coordT *points;           /* array of coordinates for each point */
    +  boolT ismalloc;           /* True if qhull should free points in qh_freeqhull() or reallocation */
    +  char flags[]= "qhull Tv"; /* option flags for qhull, see qh_opt.htm */
    +  FILE *outfile= stdout;    /* output from qh_produce_output()
    +                               use NULL to skip qh_produce_output() */
    +  FILE *errfile= stderr;    /* error messages from qhull code */
    +  int exitcode;             /* 0 if no error from qhull */
    +  facetT *facet;            /* set by FORALLfacets */
    +  int curlong, totlong;     /* memory remaining after qh_memfreeshort */
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +#if qh_QHpointer  /* see user.h */
    +  if (qh_qh){ /* should be NULL */
    +      qh_printf_stderr(6238, "Qhull link error.  The global variable qh_qh was not initialized\n\
    +              to NULL by global.c.  Please compile this program with -Dqh_QHpointer_dllimport\n\
    +              as well as -Dqh_QHpointer, or use libqhullstatic, or use a different tool chain.\n\n");
    +      exit(1);
    +  }
    +#endif
    +
    +  /* initialize dim, numpoints, points[], ismalloc here */
    +  exitcode= qh_new_qhull(dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh facet_list' contains the convex hull */
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +  }
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf(errfile, 7068, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n", totlong, curlong);
    +}
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_new_qhull( dim, numpoints, points, ismalloc, qhull_cmd, outfile, errfile )
    +    build new qhull data structure and return exitcode (0 if no errors)
    +    if numpoints=0 and points=NULL, initializes qhull
    +
    +  notes:
    +    do not modify points until finished with results.
    +      The qhull data structure contains pointers into the points array.
    +    do not call qhull functions before qh_new_qhull().
    +      The qhull data structure is not initialized until qh_new_qhull().
    +
    +    Default errfile is stderr, outfile may be null
    +    qhull_cmd must start with "qhull "
    +    projects points to a new point array for Delaunay triangulations ('d' and 'v')
    +    transforms points into a new point array for halfspace intersection ('H')
    +
    +
    +  To allow multiple, concurrent calls to qhull()
    +    - set qh_QHpointer in user.h
    +    - use qh_save_qhull and qh_restore_qhull to swap the global data structure between calls.
    +    - use qh_freeqhull(qh_ALL) to free intermediate convex hulls
    +
    +  see:
    +      Qhull-template at the beginning of this file.
    +      An example of using qh_new_qhull is user_eg.c
    +*/
    +int qh_new_qhull(int dim, int numpoints, coordT *points, boolT ismalloc,
    +                char *qhull_cmd, FILE *outfile, FILE *errfile) {
    +  /* gcc may issue a "might be clobbered" warning for dim, points, and ismalloc [-Wclobbered].
    +     These parameters are not referenced after a longjmp() and hence not clobbered.
    +     See http://stackoverflow.com/questions/7721854/what-sense-do-these-clobbered-variable-warnings-make */
    +  int exitcode, hulldim;
    +  boolT new_ismalloc;
    +  static boolT firstcall = True;
    +  coordT *new_points;
    +  if(!errfile){
    +      errfile= stderr;
    +  }
    +  if (firstcall) {
    +    qh_meminit(errfile);
    +    firstcall= False;
    +  } else {
    +    qh_memcheck();
    +  }
    +  if (strncmp(qhull_cmd, "qhull ", (size_t)6)) {
    +    qh_fprintf(errfile, 6186, "qhull error (qh_new_qhull): start qhull_cmd argument with \"qhull \"\n");
    +    return qh_ERRinput;
    +  }
    +  qh_initqhull_start(NULL, outfile, errfile);
    +  if(numpoints==0 && points==NULL){
    +      trace1((qh ferr, 1047, "qh_new_qhull: initialize Qhull\n"));
    +      return 0;
    +  }
    +  trace1((qh ferr, 1044, "qh_new_qhull: build new Qhull for %d %d-d points with %s\n", numpoints, dim, qhull_cmd));
    +  exitcode = setjmp(qh errexit);
    +  if (!exitcode)
    +  {
    +    qh NOerrexit = False;
    +    qh_initflags(qhull_cmd);
    +    if (qh DELAUNAY)
    +      qh PROJECTdelaunay= True;
    +    if (qh HALFspace) {
    +      /* points is an array of halfspaces,
    +         the last coordinate of each halfspace is its offset */
    +      hulldim= dim-1;
    +      qh_setfeasible(hulldim);
    +      new_points= qh_sethalfspace_all(dim, numpoints, points, qh feasible_point);
    +      new_ismalloc= True;
    +      if (ismalloc)
    +        qh_free(points);
    +    }else {
    +      hulldim= dim;
    +      new_points= points;
    +      new_ismalloc= ismalloc;
    +    }
    +    qh_init_B(new_points, numpoints, hulldim, new_ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    if (outfile) {
    +      qh_produce_output();
    +    }else {
    +      qh_prepare_output();
    +    }
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +  }
    +  qh NOerrexit = True;
    +  return exitcode;
    +} /* new_qhull */
    +
    +/*---------------------------------
    +
    +  qh_errexit( exitcode, facet, ridge )
    +    report and exit from an error
    +    report facet and ridge if non-NULL
    +    reports useful information such as last point processed
    +    set qh.FORCEoutput to print neighborhood of facet
    +
    +  see:
    +    qh_errexit2() in libqhull.c for printing 2 facets
    +
    +  design:
    +    check for error within error processing
    +    compute qh.hulltime
    +    print facet and ridge (if any)
    +    report commandString, options, qh.furthest_id
    +    print summary and statistics (including precision statistics)
    +    if qh_ERRsingular
    +      print help text for singular data set
    +    exit program via long jump (if defined) or exit()
    +*/
    +void qh_errexit(int exitcode, facetT *facet, ridgeT *ridge) {
    +
    +  if (qh ERREXITcalled) {
    +    qh_fprintf(qh ferr, 8126, "\nqhull error while processing previous error.  Exit program\n");
    +    qh_exit(qh_ERRqhull);
    +  }
    +  qh ERREXITcalled= True;
    +  if (!qh QHULLfinished)
    +    qh hulltime= qh_CPUclock - qh hulltime;
    +  qh_errprint("ERRONEOUS", facet, NULL, ridge, NULL);
    +  qh_fprintf(qh ferr, 8127, "\nWhile executing: %s | %s\n", qh rbox_command, qh qhull_command);
    +  qh_fprintf(qh ferr, 8128, "Options selected for Qhull %s:\n%s\n", qh_version, qh qhull_options);
    +  if (qh furthest_id >= 0) {
    +    qh_fprintf(qh ferr, 8129, "Last point added to hull was p%d.", qh furthest_id);
    +    if (zzval_(Ztotmerge))
    +      qh_fprintf(qh ferr, 8130, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    if (qh QHULLfinished)
    +      qh_fprintf(qh ferr, 8131, "\nQhull has finished constructing the hull.");
    +    else if (qh POSTmerging)
    +      qh_fprintf(qh ferr, 8132, "\nQhull has started post-merging.");
    +    qh_fprintf(qh ferr, 8133, "\n");
    +  }
    +  if (qh FORCEoutput && (qh QHULLfinished || (!facet && !ridge)))
    +    qh_produce_output();
    +  else if (exitcode != qh_ERRinput) {
    +    if (exitcode != qh_ERRsingular && zzval_(Zsetplane) > qh hull_dim+1) {
    +      qh_fprintf(qh ferr, 8134, "\nAt error exit:\n");
    +      qh_printsummary(qh ferr);
    +      if (qh PRINTstatistics) {
    +        qh_collectstatistics();
    +        qh_printstatistics(qh ferr, "at error exit");
    +        qh_memstatistics(qh ferr);
    +      }
    +    }
    +    if (qh PRINTprecision)
    +      qh_printstats(qh ferr, qhstat precision, NULL);
    +  }
    +  if (!exitcode)
    +    exitcode= qh_ERRqhull;
    +  else if (exitcode == qh_ERRsingular)
    +    qh_printhelp_singular(qh ferr);
    +  else if (exitcode == qh_ERRprec && !qh PREmerge)
    +    qh_printhelp_degenerate(qh ferr);
    +  if (qh NOerrexit) {
    +    qh_fprintf(qh ferr, 6187, "qhull error while ending program, or qh->NOerrexit not cleared after setjmp(). Exit program with error.\n");
    +    qh_exit(qh_ERRqhull);
    +  }
    +  qh ERREXITcalled= False;
    +  qh NOerrexit= True;
    +  qh ALLOWrestart= False;  /* longjmp will undo qh_build_withrestart */
    +  longjmp(qh errexit, exitcode);
    +} /* errexit */
    +
    +
    +/*---------------------------------
    +
    +  qh_errprint( fp, string, atfacet, otherfacet, atridge, atvertex )
    +    prints out the information of facets and ridges to fp
    +    also prints neighbors and geomview output
    +
    +  notes:
    +    except for string, any parameter may be NULL
    +*/
    +void qh_errprint(const char *string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex) {
    +  int i;
    +
    +  if (atfacet) {
    +    qh_fprintf(qh ferr, 8135, "%s FACET:\n", string);
    +    qh_printfacet(qh ferr, atfacet);
    +  }
    +  if (otherfacet) {
    +    qh_fprintf(qh ferr, 8136, "%s OTHER FACET:\n", string);
    +    qh_printfacet(qh ferr, otherfacet);
    +  }
    +  if (atridge) {
    +    qh_fprintf(qh ferr, 8137, "%s RIDGE:\n", string);
    +    qh_printridge(qh ferr, atridge);
    +    if (atridge->top && atridge->top != atfacet && atridge->top != otherfacet)
    +      qh_printfacet(qh ferr, atridge->top);
    +    if (atridge->bottom
    +        && atridge->bottom != atfacet && atridge->bottom != otherfacet)
    +      qh_printfacet(qh ferr, atridge->bottom);
    +    if (!atfacet)
    +      atfacet= atridge->top;
    +    if (!otherfacet)
    +      otherfacet= otherfacet_(atridge, atfacet);
    +  }
    +  if (atvertex) {
    +    qh_fprintf(qh ferr, 8138, "%s VERTEX:\n", string);
    +    qh_printvertex(qh ferr, atvertex);
    +  }
    +  if (qh fout && qh FORCEoutput && atfacet && !qh QHULLfinished && !qh IStracing) {
    +    qh_fprintf(qh ferr, 8139, "ERRONEOUS and NEIGHBORING FACETS to output\n");
    +    for (i=0; i < qh_PRINTEND; i++)  /* use fout for geomview output */
    +      qh_printneighborhood(qh fout, qh PRINTout[i], atfacet, otherfacet,
    +                            !qh_ALL);
    +  }
    +} /* errprint */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetlist( fp, facetlist, facets, printall )
    +    print all fields for a facet list and/or set of facets to fp
    +    if !printall,
    +      only prints good facets
    +
    +  notes:
    +    also prints all vertices
    +*/
    +void qh_printfacetlist(facetT *facetlist, setT *facets, boolT printall) {
    +  facetT *facet, **facetp;
    +
    +  qh_printbegin(qh ferr, qh_PRINTfacets, facetlist, facets, printall);
    +  FORALLfacet_(facetlist)
    +    qh_printafacet(qh ferr, qh_PRINTfacets, facet, printall);
    +  FOREACHfacet_(facets)
    +    qh_printafacet(qh ferr, qh_PRINTfacets, facet, printall);
    +  qh_printend(qh ferr, qh_PRINTfacets, facetlist, facets, printall);
    +} /* printfacetlist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhelp_degenerate( fp )
    +    prints descriptive message for precision error
    +
    +  notes:
    +    no message if qh_QUICKhelp
    +*/
    +void qh_printhelp_degenerate(FILE *fp) {
    +
    +  if (qh MERGEexact || qh PREmerge || qh JOGGLEmax < REALmax/2)
    +    qh_fprintf(fp, 9368, "\n\
    +A Qhull error has occurred.  Qhull should have corrected the above\n\
    +precision error.  Please send the input and all of the output to\n\
    +qhull_bug@qhull.org\n");
    +  else if (!qh_QUICKhelp) {
    +    qh_fprintf(fp, 9369, "\n\
    +Precision problems were detected during construction of the convex hull.\n\
    +This occurs because convex hull algorithms assume that calculations are\n\
    +exact, but floating-point arithmetic has roundoff errors.\n\
    +\n\
    +To correct for precision problems, do not use 'Q0'.  By default, Qhull\n\
    +selects 'C-0' or 'Qx' and merges non-convex facets.  With option 'QJ',\n\
    +Qhull joggles the input to prevent precision problems.  See \"Imprecision\n\
    +in Qhull\" (qh-impre.htm).\n\
    +\n\
    +If you use 'Q0', the output may include\n\
    +coplanar ridges, concave ridges, and flipped facets.  In 4-d and higher,\n\
    +Qhull may produce a ridge with four neighbors or two facets with the same \n\
    +vertices.  Qhull reports these events when they occur.  It stops when a\n\
    +concave ridge, flipped facet, or duplicate facet occurs.\n");
    +#if REALfloat
    +    qh_fprintf(fp, 9370, "\
    +\n\
    +Qhull is currently using single precision arithmetic.  The following\n\
    +will probably remove the precision problems:\n\
    +  - recompile qhull for realT precision(#define REALfloat 0 in user.h).\n");
    +#endif
    +    if (qh DELAUNAY && !qh SCALElast && qh MAXabs_coord > 1e4)
    +      qh_fprintf(fp, 9371, "\
    +\n\
    +When computing the Delaunay triangulation of coordinates > 1.0,\n\
    +  - use 'Qbb' to scale the last coordinate to [0,m] (max previous coordinate)\n");
    +    if (qh DELAUNAY && !qh ATinfinity)
    +      qh_fprintf(fp, 9372, "\
    +When computing the Delaunay triangulation:\n\
    +  - use 'Qz' to add a point at-infinity.  This reduces precision problems.\n");
    +
    +    qh_fprintf(fp, 9373, "\
    +\n\
    +If you need triangular output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft'.  It triangulates non-simplicial facets with added points.\n\
    +\n\
    +If you must use 'Q0',\n\
    +try one or more of the following options.  They can not guarantee an output.\n\
    +  - use 'QbB' to scale the input to a cube.\n\
    +  - use 'Po' to produce output and prevent partitioning for flipped facets\n\
    +  - use 'V0' to set min. distance to visible facet as 0 instead of roundoff\n\
    +  - use 'En' to specify a maximum roundoff error less than %2.2g.\n\
    +  - options 'Qf', 'Qbb', and 'QR0' may also help\n",
    +               qh DISTround);
    +    qh_fprintf(fp, 9374, "\
    +\n\
    +To guarantee simplicial output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft' to triangulate the output by adding points\n\
    +  - use exact arithmetic (see \"Imprecision in Qhull\", qh-impre.htm)\n\
    +");
    +  }
    +} /* printhelp_degenerate */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhelp_narrowhull( minangle )
    +    Warn about a narrow hull
    +
    +  notes:
    +    Alternatively, reduce qh_WARNnarrow in user.h
    +
    +*/
    +void qh_printhelp_narrowhull(FILE *fp, realT minangle) {
    +
    +    qh_fprintf(fp, 9375, "qhull precision warning: \n\
    +The initial hull is narrow (cosine of min. angle is %.16f).\n\
    +Is the input lower dimensional (e.g., on a plane in 3-d)?  Qhull may\n\
    +produce a wide facet.  Options 'QbB' (scale to unit box) or 'Qbb' (scale\n\
    +last coordinate) may remove this warning.  Use 'Pp' to skip this warning.\n\
    +See 'Limitations' in qh-impre.htm.\n",
    +          -minangle);   /* convert from angle between normals to angle between facets */
    +} /* printhelp_narrowhull */
    +
    +/*---------------------------------
    +
    +  qh_printhelp_singular( fp )
    +    prints descriptive message for singular input
    +*/
    +void qh_printhelp_singular(FILE *fp) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  realT min, max, *coord, dist;
    +  int i,k;
    +
    +  qh_fprintf(fp, 9376, "\n\
    +The input to qhull appears to be less than %d dimensional, or a\n\
    +computation has overflowed.\n\n\
    +Qhull could not construct a clearly convex simplex from points:\n",
    +           qh hull_dim);
    +  qh_printvertexlist(fp, "", qh facet_list, NULL, qh_ALL);
    +  if (!qh_QUICKhelp)
    +    qh_fprintf(fp, 9377, "\n\
    +The center point is coplanar with a facet, or a vertex is coplanar\n\
    +with a neighboring facet.  The maximum round off error for\n\
    +computing distances is %2.2g.  The center point, facets and distances\n\
    +to the center point are as follows:\n\n", qh DISTround);
    +  qh_printpointid(fp, "center point", qh hull_dim, qh interior_point, qh_IDunknown);
    +  qh_fprintf(fp, 9378, "\n");
    +  FORALLfacets {
    +    qh_fprintf(fp, 9379, "facet");
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(fp, 9380, " p%d", qh_pointid(vertex->point));
    +    zinc_(Zdistio);
    +    qh_distplane(qh interior_point, facet, &dist);
    +    qh_fprintf(fp, 9381, " distance= %4.2g\n", dist);
    +  }
    +  if (!qh_QUICKhelp) {
    +    if (qh HALFspace)
    +      qh_fprintf(fp, 9382, "\n\
    +These points are the dual of the given halfspaces.  They indicate that\n\
    +the intersection is degenerate.\n");
    +    qh_fprintf(fp, 9383,"\n\
    +These points either have a maximum or minimum x-coordinate, or\n\
    +they maximize the determinant for k coordinates.  Trial points\n\
    +are first selected from points that maximize a coordinate.\n");
    +    if (qh hull_dim >= qh_INITIALmax)
    +      qh_fprintf(fp, 9384, "\n\
    +Because of the high dimension, the min x-coordinate and max-coordinate\n\
    +points are used if the determinant is non-zero.  Option 'Qs' will\n\
    +do a better, though much slower, job.  Instead of 'Qs', you can change\n\
    +the points by randomly rotating the input with 'QR0'.\n");
    +  }
    +  qh_fprintf(fp, 9385, "\nThe min and max coordinates for each dimension are:\n");
    +  for (k=0; k < qh hull_dim; k++) {
    +    min= REALmax;
    +    max= -REALmin;
    +    for (i=qh num_points, coord= qh first_point+k; i--; coord += qh hull_dim) {
    +      maximize_(max, *coord);
    +      minimize_(min, *coord);
    +    }
    +    qh_fprintf(fp, 9386, "  %d:  %8.4g  %8.4g  difference= %4.4g\n", k, min, max, max-min);
    +  }
    +  if (!qh_QUICKhelp) {
    +    qh_fprintf(fp, 9387, "\n\
    +If the input should be full dimensional, you have several options that\n\
    +may determine an initial simplex:\n\
    +  - use 'QJ'  to joggle the input and make it full dimensional\n\
    +  - use 'QbB' to scale the points to the unit cube\n\
    +  - use 'QR0' to randomly rotate the input for different maximum points\n\
    +  - use 'Qs'  to search all points for the initial simplex\n\
    +  - use 'En'  to specify a maximum roundoff error less than %2.2g.\n\
    +  - trace execution with 'T3' to see the determinant for each point.\n",
    +                     qh DISTround);
    +#if REALfloat
    +    qh_fprintf(fp, 9388, "\
    +  - recompile qhull for realT precision(#define REALfloat 0 in libqhull.h).\n");
    +#endif
    +    qh_fprintf(fp, 9389, "\n\
    +If the input is lower dimensional:\n\
    +  - use 'QJ' to joggle the input and make it full dimensional\n\
    +  - use 'Qbk:0Bk:0' to delete coordinate k from the input.  You should\n\
    +    pick the coordinate with the least range.  The hull will have the\n\
    +    correct topology.\n\
    +  - determine the flat containing the points, rotate the points\n\
    +    into a coordinate plane, and delete the other coordinates.\n\
    +  - add one or more points to make the input full dimensional.\n\
    +");
    +  }
    +} /* printhelp_singular */
    +
    +/*---------------------------------
    +
    +  qh_user_memsizes()
    +    allocate up to 10 additional, quick allocation sizes
    +
    +  notes:
    +    increase maximum number of allocations in qh_initqhull_mem()
    +*/
    +void qh_user_memsizes(void) {
    +
    +  /* qh_memsize(size); */
    +} /* user_memsizes */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/user.h b/xs/src/qhull/src/libqhull/user.h
    new file mode 100644
    index 0000000000..523aa7b4e8
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/user.h
    @@ -0,0 +1,909 @@
    +/*
      ---------------------------------
    +
    +   user.h
    +   user redefinable constants
    +
    +   for each source file, user.h is included first
    +   see qh-user.htm.  see COPYING for copyright information.
    +
    +   See user.c for sample code.
    +
    +   before reading any code, review libqhull.h for data structure definitions and
    +   the "qh" macro.
    +
    +Sections:
    +   ============= qhull library constants ======================
    +   ============= data types and configuration macros ==========
    +   ============= performance related constants ================
    +   ============= memory constants =============================
    +   ============= joggle constants =============================
    +   ============= conditional compilation ======================
    +   ============= -merge constants- ============================
    +
    +Code flags --
    +  NOerrors -- the code does not call qh_errexit()
    +  WARN64 -- the code may be incompatible with 64-bit pointers
    +
    +*/
    +
    +#include 
    +
    +#ifndef qhDEFuser
    +#define qhDEFuser 1
    +
    +/* Derived from Qt's corelib/global/qglobal.h */
    +#if !defined(SAG_COM) && !defined(__CYGWIN__) && (defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__))
    +#   define QHULL_OS_WIN
    +#elif defined(__MWERKS__) && defined(__INTEL__) /* Metrowerks discontinued before the release of Intel Macs */
    +#   define QHULL_OS_WIN
    +#endif
    +/*============================================================*/
    +/*============= qhull library constants ======================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  FILENAMElen -- max length for TI and TO filenames
    +
    +*/
    +
    +#define qh_FILENAMElen 500
    +
    +/*----------------------------------
    +
    +  msgcode -- Unique message codes for qh_fprintf
    +
    +  If add new messages, assign these values and increment in user.h and user_r.h
    +  See QhullError.h for 10000 errors.
    +
    +  def counters =  [27, 1048, 2059, 3026, 4068, 5003,
    +     6273, 7081, 8147, 9411, 10000, 11029]
    +
    +  See: qh_ERR* [libqhull.h]
    +*/
    +
    +#define MSG_TRACE0 0
    +#define MSG_TRACE1 1000
    +#define MSG_TRACE2 2000
    +#define MSG_TRACE3 3000
    +#define MSG_TRACE4 4000
    +#define MSG_TRACE5 5000
    +#define MSG_ERROR  6000   /* errors written to qh.ferr */
    +#define MSG_WARNING 7000
    +#define MSG_STDERR  8000  /* log messages Written to qh.ferr */
    +#define MSG_OUTPUT  9000
    +#define MSG_QHULL_ERROR 10000 /* errors thrown by QhullError.cpp (QHULLlastError is in QhullError.h) */
    +#define MSG_FIXUP  11000  /* FIXUP QH11... */
    +#define MSG_MAXLEN  3000 /* qh_printhelp_degenerate() in user.c */
    +
    +
    +/*----------------------------------
    +
    +  qh_OPTIONline -- max length of an option line 'FO'
    +*/
    +#define qh_OPTIONline 80
    +
    +/*============================================================*/
    +/*============= data types and configuration macros ==========*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  realT
    +    set the size of floating point numbers
    +
    +  qh_REALdigits
    +    maximimum number of significant digits
    +
    +  qh_REAL_1, qh_REAL_2n, qh_REAL_3n
    +    format strings for printf
    +
    +  qh_REALmax, qh_REALmin
    +    maximum and minimum (near zero) values
    +
    +  qh_REALepsilon
    +    machine roundoff.  Maximum roundoff error for addition and multiplication.
    +
    +  notes:
    +   Select whether to store floating point numbers in single precision (float)
    +   or double precision (double).
    +
    +   Use 'float' to save about 8% in time and 25% in space.  This is particularly
    +   helpful if high-d where convex hulls are space limited.  Using 'float' also
    +   reduces the printed size of Qhull's output since numbers have 8 digits of
    +   precision.
    +
    +   Use 'double' when greater arithmetic precision is needed.  This is needed
    +   for Delaunay triangulations and Voronoi diagrams when you are not merging
    +   facets.
    +
    +   If 'double' gives insufficient precision, your data probably includes
    +   degeneracies.  If so you should use facet merging (done by default)
    +   or exact arithmetic (see imprecision section of manual, qh-impre.htm).
    +   You may also use option 'Po' to force output despite precision errors.
    +
    +   You may use 'long double', but many format statements need to be changed
    +   and you may need a 'long double' square root routine.  S. Grundmann
    +   (sg@eeiwzb.et.tu-dresden.de) has done this.  He reports that the code runs
    +   much slower with little gain in precision.
    +
    +   WARNING: on some machines,    int f(){realT a= REALmax;return (a == REALmax);}
    +      returns False.  Use (a > REALmax/2) instead of (a == REALmax).
    +
    +   REALfloat =   1      all numbers are 'float' type
    +             =   0      all numbers are 'double' type
    +*/
    +#define REALfloat 0
    +
    +#if (REALfloat == 1)
    +#define realT float
    +#define REALmax FLT_MAX
    +#define REALmin FLT_MIN
    +#define REALepsilon FLT_EPSILON
    +#define qh_REALdigits 8   /* maximum number of significant digits */
    +#define qh_REAL_1 "%6.8g "
    +#define qh_REAL_2n "%6.8g %6.8g\n"
    +#define qh_REAL_3n "%6.8g %6.8g %6.8g\n"
    +
    +#elif (REALfloat == 0)
    +#define realT double
    +#define REALmax DBL_MAX
    +#define REALmin DBL_MIN
    +#define REALepsilon DBL_EPSILON
    +#define qh_REALdigits 16    /* maximum number of significant digits */
    +#define qh_REAL_1 "%6.16g "
    +#define qh_REAL_2n "%6.16g %6.16g\n"
    +#define qh_REAL_3n "%6.16g %6.16g %6.16g\n"
    +
    +#else
    +#error unknown float option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_CPUclock
    +    define the clock() function for reporting the total time spent by Qhull
    +    returns CPU ticks as a 'long int'
    +    qh_CPUclock is only used for reporting the total time spent by Qhull
    +
    +  qh_SECticks
    +    the number of clock ticks per second
    +
    +  notes:
    +    looks for CLOCKS_PER_SEC, CLOCKS_PER_SECOND, or assumes microseconds
    +    to define a custom clock, set qh_CLOCKtype to 0
    +
    +    if your system does not use clock() to return CPU ticks, replace
    +    qh_CPUclock with the corresponding function.  It is converted
    +    to 'unsigned long' to prevent wrap-around during long runs.  By default,
    +     defines clock_t as 'long'
    +
    +   Set qh_CLOCKtype to
    +
    +     1          for CLOCKS_PER_SEC, CLOCKS_PER_SECOND, or microsecond
    +                Note:  may fail if more than 1 hour elapsed time
    +
    +     2          use qh_clock() with POSIX times() (see global.c)
    +*/
    +#define qh_CLOCKtype 1  /* change to the desired number */
    +
    +#if (qh_CLOCKtype == 1)
    +
    +#if defined(CLOCKS_PER_SECOND)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLOCKS_PER_SECOND
    +
    +#elif defined(CLOCKS_PER_SEC)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLOCKS_PER_SEC
    +
    +#elif defined(CLK_TCK)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLK_TCK
    +
    +#else
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks 1E6
    +#endif
    +
    +#elif (qh_CLOCKtype == 2)
    +#define qh_CPUclock    qh_clock()  /* return CPU clock */
    +#define qh_SECticks 100
    +
    +#else /* qh_CLOCKtype == ? */
    +#error unknown clock option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_RANDOMtype, qh_RANDOMmax, qh_RANDOMseed
    +    define random number generator
    +
    +    qh_RANDOMint generates a random integer between 0 and qh_RANDOMmax.
    +    qh_RANDOMseed sets the random number seed for qh_RANDOMint
    +
    +  Set qh_RANDOMtype (default 5) to:
    +    1       for random() with 31 bits (UCB)
    +    2       for rand() with RAND_MAX or 15 bits (system 5)
    +    3       for rand() with 31 bits (Sun)
    +    4       for lrand48() with 31 bits (Solaris)
    +    5       for qh_rand() with 31 bits (included with Qhull)
    +
    +  notes:
    +    Random numbers are used by rbox to generate point sets.  Random
    +    numbers are used by Qhull to rotate the input ('QRn' option),
    +    simulate a randomized algorithm ('Qr' option), and to simulate
    +    roundoff errors ('Rn' option).
    +
    +    Random number generators differ between systems.  Most systems provide
    +    rand() but the period varies.  The period of rand() is not critical
    +    since qhull does not normally use random numbers.
    +
    +    The default generator is Park & Miller's minimal standard random
    +    number generator [CACM 31:1195 '88].  It is included with Qhull.
    +
    +    If qh_RANDOMmax is wrong, qhull will report a warning and Geomview
    +    output will likely be invisible.
    +*/
    +#define qh_RANDOMtype 5   /* *** change to the desired number *** */
    +
    +#if (qh_RANDOMtype == 1)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, random()/MAX */
    +#define qh_RANDOMint random()
    +#define qh_RANDOMseed_(seed) srandom(seed);
    +
    +#elif (qh_RANDOMtype == 2)
    +#ifdef RAND_MAX
    +#define qh_RANDOMmax ((realT)RAND_MAX)
    +#else
    +#define qh_RANDOMmax ((realT)32767)   /* 15 bits (System 5) */
    +#endif
    +#define qh_RANDOMint  rand()
    +#define qh_RANDOMseed_(seed) srand((unsigned)seed);
    +
    +#elif (qh_RANDOMtype == 3)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, Sun */
    +#define qh_RANDOMint  rand()
    +#define qh_RANDOMseed_(seed) srand((unsigned)seed);
    +
    +#elif (qh_RANDOMtype == 4)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, lrand38()/MAX */
    +#define qh_RANDOMint lrand48()
    +#define qh_RANDOMseed_(seed) srand48(seed);
    +
    +#elif (qh_RANDOMtype == 5)
    +#define qh_RANDOMmax ((realT)2147483646UL)  /* 31 bits, qh_rand/MAX */
    +#define qh_RANDOMint qh_rand()
    +#define qh_RANDOMseed_(seed) qh_srand(seed);
    +/* unlike rand(), never returns 0 */
    +
    +#else
    +#error: unknown random option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_ORIENTclock
    +    0 for inward pointing normals by Geomview convention
    +*/
    +#define qh_ORIENTclock 0
    +
    +
    +/*============================================================*/
    +/*============= joggle constants =============================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +qh_JOGGLEdefault
    +default qh.JOGGLEmax is qh.DISTround * qh_JOGGLEdefault
    +
    +notes:
    +rbox s r 100 | qhull QJ1e-15 QR0 generates 90% faults at distround 7e-16
    +rbox s r 100 | qhull QJ1e-14 QR0 generates 70% faults
    +rbox s r 100 | qhull QJ1e-13 QR0 generates 35% faults
    +rbox s r 100 | qhull QJ1e-12 QR0 generates 8% faults
    +rbox s r 100 | qhull QJ1e-11 QR0 generates 1% faults
    +rbox s r 100 | qhull QJ1e-10 QR0 generates 0% faults
    +rbox 1000 W0 | qhull QJ1e-12 QR0 generates 86% faults
    +rbox 1000 W0 | qhull QJ1e-11 QR0 generates 20% faults
    +rbox 1000 W0 | qhull QJ1e-10 QR0 generates 2% faults
    +the later have about 20 points per facet, each of which may interfere
    +
    +pick a value large enough to avoid retries on most inputs
    +*/
    +#define qh_JOGGLEdefault 30000.0
    +
    +/*----------------------------------
    +
    +qh_JOGGLEincrease
    +factor to increase qh.JOGGLEmax on qh_JOGGLEretry or qh_JOGGLEagain
    +*/
    +#define qh_JOGGLEincrease 10.0
    +
    +/*----------------------------------
    +
    +qh_JOGGLEretry
    +if ZZretry = qh_JOGGLEretry, increase qh.JOGGLEmax
    +
    +notes:
    +try twice at the original value in case of bad luck the first time
    +*/
    +#define qh_JOGGLEretry 2
    +
    +/*----------------------------------
    +
    +qh_JOGGLEagain
    +every following qh_JOGGLEagain, increase qh.JOGGLEmax
    +
    +notes:
    +1 is OK since it's already failed qh_JOGGLEretry times
    +*/
    +#define qh_JOGGLEagain 1
    +
    +/*----------------------------------
    +
    +qh_JOGGLEmaxincrease
    +maximum qh.JOGGLEmax due to qh_JOGGLEincrease
    +relative to qh.MAXwidth
    +
    +notes:
    +qh.joggleinput will retry at this value until qh_JOGGLEmaxretry
    +*/
    +#define qh_JOGGLEmaxincrease 1e-2
    +
    +/*----------------------------------
    +
    +qh_JOGGLEmaxretry
    +stop after qh_JOGGLEmaxretry attempts
    +*/
    +#define qh_JOGGLEmaxretry 100
    +
    +/*============================================================*/
    +/*============= performance related constants ================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  qh_HASHfactor
    +    total hash slots / used hash slots.  Must be at least 1.1.
    +
    +  notes:
    +    =2 for at worst 50% occupancy for qh.hash_table and normally 25% occupancy
    +*/
    +#define qh_HASHfactor 2
    +
    +/*----------------------------------
    +
    +  qh_VERIFYdirect
    +    with 'Tv' verify all points against all facets if op count is smaller
    +
    +  notes:
    +    if greater, calls qh_check_bestdist() instead
    +*/
    +#define qh_VERIFYdirect 1000000
    +
    +/*----------------------------------
    +
    +  qh_INITIALsearch
    +     if qh_INITIALmax, search points up to this dimension
    +*/
    +#define qh_INITIALsearch 6
    +
    +/*----------------------------------
    +
    +  qh_INITIALmax
    +    if dim >= qh_INITIALmax, use min/max coordinate points for initial simplex
    +
    +  notes:
    +    from points with non-zero determinants
    +    use option 'Qs' to override (much slower)
    +*/
    +#define qh_INITIALmax 8
    +
    +/*============================================================*/
    +/*============= memory constants =============================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  qh_MEMalign
    +    memory alignment for qh_meminitbuffers() in global.c
    +
    +  notes:
    +    to avoid bus errors, memory allocation must consider alignment requirements.
    +    malloc() automatically takes care of alignment.   Since mem.c manages
    +    its own memory, we need to explicitly specify alignment in
    +    qh_meminitbuffers().
    +
    +    A safe choice is sizeof(double).  sizeof(float) may be used if doubles
    +    do not occur in data structures and pointers are the same size.  Be careful
    +    of machines (e.g., DEC Alpha) with large pointers.
    +
    +    If using gcc, best alignment is  [fmax_() is defined in geom_r.h]
    +              #define qh_MEMalign fmax_(__alignof__(realT),__alignof__(void *))
    +*/
    +#define qh_MEMalign ((int)(fmax_(sizeof(realT), sizeof(void *))))
    +
    +/*----------------------------------
    +
    +  qh_MEMbufsize
    +    size of additional memory buffers
    +
    +  notes:
    +    used for qh_meminitbuffers() in global.c
    +*/
    +#define qh_MEMbufsize 0x10000       /* allocate 64K memory buffers */
    +
    +/*----------------------------------
    +
    +  qh_MEMinitbuf
    +    size of initial memory buffer
    +
    +  notes:
    +    use for qh_meminitbuffers() in global.c
    +*/
    +#define qh_MEMinitbuf 0x20000      /* initially allocate 128K buffer */
    +
    +/*----------------------------------
    +
    +  qh_INFINITE
    +    on output, indicates Voronoi center at infinity
    +*/
    +#define qh_INFINITE  -10.101
    +
    +/*----------------------------------
    +
    +  qh_DEFAULTbox
    +    default box size (Geomview expects 0.5)
    +
    +  qh_DEFAULTbox
    +    default box size for integer coorindate (rbox only)
    +*/
    +#define qh_DEFAULTbox 0.5
    +#define qh_DEFAULTzbox 1e6
    +
    +/*============================================================*/
    +/*============= conditional compilation ======================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  __cplusplus
    +    defined by C++ compilers
    +
    +  __MSC_VER
    +    defined by Microsoft Visual C++
    +
    +  __MWERKS__ && __INTEL__
    +    defined by Metrowerks when compiling for Windows (not Intel-based Macintosh)
    +
    +  __MWERKS__ && __POWERPC__
    +    defined by Metrowerks when compiling for PowerPC-based Macintosh
    +  __STDC__
    +    defined for strict ANSI C
    +*/
    +
    +/*----------------------------------
    +
    +  qh_COMPUTEfurthest
    +    compute furthest distance to an outside point instead of storing it with the facet
    +    =1 to compute furthest
    +
    +  notes:
    +    computing furthest saves memory but costs time
    +      about 40% more distance tests for partitioning
    +      removes facet->furthestdist
    +*/
    +#define qh_COMPUTEfurthest 0
    +
    +/*----------------------------------
    +
    +  qh_KEEPstatistics
    +    =0 removes most of statistic gathering and reporting
    +
    +  notes:
    +    if 0, code size is reduced by about 4%.
    +*/
    +#define qh_KEEPstatistics 1
    +
    +/*----------------------------------
    +
    +  qh_MAXoutside
    +    record outer plane for each facet
    +    =1 to record facet->maxoutside
    +
    +  notes:
    +    this takes a realT per facet and slightly slows down qhull
    +    it produces better outer planes for geomview output
    +*/
    +#define qh_MAXoutside 1
    +
    +/*----------------------------------
    +
    +  qh_NOmerge
    +    disables facet merging if defined
    +
    +  notes:
    +    This saves about 10% space.
    +
    +    Unless 'Q0'
    +      qh_NOmerge sets 'QJ' to avoid precision errors
    +
    +    #define qh_NOmerge
    +
    +  see:
    +    qh_NOmem in mem.c
    +
    +    see user.c/user_eg.c for removing io.o
    +*/
    +
    +/*----------------------------------
    +
    +  qh_NOtrace
    +    no tracing if defined
    +
    +  notes:
    +    This saves about 5% space.
    +
    +    #define qh_NOtrace
    +*/
    +
    +/*----------------------------------
    +
    +  qh_QHpointer
    +    access global data with pointer or static structure
    +
    +  qh_QHpointer  = 1     access globals via a pointer to allocated memory
    +                        enables qh_saveqhull() and qh_restoreqhull()
    +                        [2010, gcc] costs about 4% in time and 4% in space
    +                        [2003, msvc] costs about 8% in time and 2% in space
    +
    +                = 0     qh_qh and qh_qhstat are static data structures
    +                        only one instance of qhull() can be active at a time
    +                        default value
    +
    +  qh_QHpointer_dllimport and qh_dllimport define qh_qh as __declspec(dllimport) [libqhull.h]
    +  It is required for msvc-2005.  It is not needed for gcc.
    +
    +  notes:
    +    [jan'16] qh_QHpointer is deprecated for Qhull.  Use libqhull_r instead.
    +    all global variables for qhull are in qh, qhmem, and qhstat
    +    qh is defined in libqhull.h
    +    qhmem is defined in mem.h
    +    qhstat is defined in stat.h
    +
    +*/
    +#ifdef qh_QHpointer
    +#if qh_dllimport
    +#error QH6207 Qhull error: Use qh_QHpointer_dllimport instead of qh_dllimport with qh_QHpointer
    +#endif
    +#else
    +#define qh_QHpointer 0
    +#if qh_QHpointer_dllimport
    +#error QH6234 Qhull error: Use qh_dllimport instead of qh_QHpointer_dllimport when qh_QHpointer is not defined
    +#endif
    +#endif
    +#if 0  /* sample code */
    +    qhT *oldqhA, *oldqhB;
    +
    +    exitcode= qh_new_qhull(dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +    /* use results from first call to qh_new_qhull */
    +    oldqhA= qh_save_qhull();
    +    exitcode= qh_new_qhull(dimB, numpointsB, pointsB, ismalloc,
    +                      flags, outfile, errfile);
    +    /* use results from second call to qh_new_qhull */
    +    oldqhB= qh_save_qhull();
    +    qh_restore_qhull(&oldqhA);
    +    /* use results from first call to qh_new_qhull */
    +    qh_freeqhull(qh_ALL);  /* frees all memory used by first call */
    +    qh_restore_qhull(&oldqhB);
    +    /* use results from second call to qh_new_qhull */
    +    qh_freeqhull(!qh_ALL); /* frees long memory used by second call */
    +    qh_memfreeshort(&curlong, &totlong);  /* frees short memory and memory allocator */
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_QUICKhelp
    +    =1 to use abbreviated help messages, e.g., for degenerate inputs
    +*/
    +#define qh_QUICKhelp    0
    +
    +/*============================================================*/
    +/*============= -merge constants- ============================*/
    +/*============================================================*/
    +/*
    +   These constants effect facet merging.  You probably will not need
    +   to modify them.  They effect the performance of facet merging.
    +*/
    +
    +/*----------------------------------
    +
    +  qh_DIMmergeVertex
    +    max dimension for vertex merging (it is not effective in high-d)
    +*/
    +#define qh_DIMmergeVertex 6
    +
    +/*----------------------------------
    +
    +  qh_DIMreduceBuild
    +     max dimension for vertex reduction during build (slow in high-d)
    +*/
    +#define qh_DIMreduceBuild 5
    +
    +/*----------------------------------
    +
    +  qh_BESTcentrum
    +     if > 2*dim+n vertices, qh_findbestneighbor() tests centrums (faster)
    +     else, qh_findbestneighbor() tests all vertices (much better merges)
    +
    +  qh_BESTcentrum2
    +     if qh_BESTcentrum2 * DIM3 + BESTcentrum < #vertices tests centrums
    +*/
    +#define qh_BESTcentrum 20
    +#define qh_BESTcentrum2 2
    +
    +/*----------------------------------
    +
    +  qh_BESTnonconvex
    +    if > dim+n neighbors, qh_findbestneighbor() tests nonconvex ridges.
    +
    +  notes:
    +    It is needed because qh_findbestneighbor is slow for large facets
    +*/
    +#define qh_BESTnonconvex 15
    +
    +/*----------------------------------
    +
    +  qh_MAXnewmerges
    +    if >n newmerges, qh_merge_nonconvex() calls qh_reducevertices_centrums.
    +
    +  notes:
    +    It is needed because postmerge can merge many facets at once
    +*/
    +#define qh_MAXnewmerges 2
    +
    +/*----------------------------------
    +
    +  qh_MAXnewcentrum
    +    if <= dim+n vertices (n approximates the number of merges),
    +      reset the centrum in qh_updatetested() and qh_mergecycle_facets()
    +
    +  notes:
    +    needed to reduce cost and because centrums may move too much if
    +    many vertices in high-d
    +*/
    +#define qh_MAXnewcentrum 5
    +
    +/*----------------------------------
    +
    +  qh_COPLANARratio
    +    for 3-d+ merging, qh.MINvisible is n*premerge_centrum
    +
    +  notes:
    +    for non-merging, it's DISTround
    +*/
    +#define qh_COPLANARratio 3
    +
    +/*----------------------------------
    +
    +  qh_DISToutside
    +    When is a point clearly outside of a facet?
    +    Stops search in qh_findbestnew or qh_partitionall
    +    qh_findbest uses qh.MINoutside since since it is only called if no merges.
    +
    +  notes:
    +    'Qf' always searches for best facet
    +    if !qh.MERGING, same as qh.MINoutside.
    +    if qh_USEfindbestnew, increase value since neighboring facets may be ill-behaved
    +      [Note: Zdelvertextot occurs normally with interior points]
    +            RBOX 1000 s Z1 G1e-13 t1001188774 | QHULL Tv
    +    When there is a sharp edge, need to move points to a
    +    clearly good facet; otherwise may be lost in another partitioning.
    +    if too big then O(n^2) behavior for partitioning in cone
    +    if very small then important points not processed
    +    Needed in qh_partitionall for
    +      RBOX 1000 s Z1 G1e-13 t1001032651 | QHULL Tv
    +    Needed in qh_findbestnew for many instances of
    +      RBOX 1000 s Z1 G1e-13 t | QHULL Tv
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_DISToutside ((qh_USEfindbestnew ? 2 : 1) * \
    +     fmax_((qh MERGING ? 2 : 1)*qh MINoutside, qh max_outside))
    +
    +/*----------------------------------
    +
    +  qh_RATIOnearinside
    +    ratio of qh.NEARinside to qh.ONEmerge for retaining inside points for
    +    qh_check_maxout().
    +
    +  notes:
    +    This is overkill since do not know the correct value.
    +    It effects whether 'Qc' reports all coplanar points
    +    Not used for 'd' since non-extreme points are coplanar
    +*/
    +#define qh_RATIOnearinside 5
    +
    +/*----------------------------------
    +
    +  qh_SEARCHdist
    +    When is a facet coplanar with the best facet?
    +    qh_findbesthorizon: all coplanar facets of the best facet need to be searched.
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_SEARCHdist ((qh_USEfindbestnew ? 2 : 1) * \
    +      (qh max_outside + 2 * qh DISTround + fmax_( qh MINvisible, qh MAXcoplanar)));
    +
    +/*----------------------------------
    +
    +  qh_USEfindbestnew
    +     Always use qh_findbestnew for qh_partitionpoint, otherwise use
    +     qh_findbestnew if merged new facet or sharpnewfacets.
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_USEfindbestnew (zzval_(Ztotmerge) > 50)
    +
    +/*----------------------------------
    +
    +  qh_WIDEcoplanar
    +    n*MAXcoplanar or n*MINvisible for a WIDEfacet
    +
    +    if vertex is further than qh.WIDEfacet from the hyperplane
    +    then its ridges are not counted in computing the area, and
    +    the facet's centrum is frozen.
    +
    +  notes:
    +   qh.WIDEfacet= max(qh.MAXoutside,qh_WIDEcoplanar*qh.MAXcoplanar,
    +      qh_WIDEcoplanar * qh.MINvisible);
    +*/
    +#define qh_WIDEcoplanar 6
    +
    +/*----------------------------------
    +
    +  qh_WIDEduplicate
    +    Merge ratio for errexit from qh_forcedmerges due to duplicate ridge
    +    Override with option Q12 no-wide-duplicate
    +
    +    Notes:
    +      Merging a duplicate ridge can lead to very wide facets.
    +      A future release of qhull will avoid duplicate ridges by removing duplicate sub-ridges from the horizon
    +*/
    +#define qh_WIDEduplicate 100
    +
    +/*----------------------------------
    +
    +  qh_MAXnarrow
    +    max. cosine in initial hull that sets qh.NARROWhull
    +
    +  notes:
    +    If qh.NARROWhull, the initial partition does not make
    +    coplanar points.  If narrow, a coplanar point can be
    +    coplanar to two facets of opposite orientations and
    +    distant from the exact convex hull.
    +
    +    Conservative estimate.  Don't actually see problems until it is -1.0
    +*/
    +#define qh_MAXnarrow -0.99999999
    +
    +/*----------------------------------
    +
    +  qh_WARNnarrow
    +    max. cosine in initial hull to warn about qh.NARROWhull
    +
    +  notes:
    +    this is a conservative estimate.
    +    Don't actually see problems until it is -1.0.  See qh-impre.htm
    +*/
    +#define qh_WARNnarrow -0.999999999999999
    +
    +/*----------------------------------
    +
    +  qh_ZEROdelaunay
    +    a zero Delaunay facet occurs for input sites coplanar with their convex hull
    +    the last normal coefficient of a zero Delaunay facet is within
    +        qh_ZEROdelaunay * qh.ANGLEround of 0
    +
    +  notes:
    +    qh_ZEROdelaunay does not allow for joggled input ('QJ').
    +
    +    You can avoid zero Delaunay facets by surrounding the input with a box.
    +
    +    Use option 'PDk:-n' to explicitly define zero Delaunay facets
    +      k= dimension of input sites (e.g., 3 for 3-d Delaunay triangulation)
    +      n= the cutoff for zero Delaunay facets (e.g., 'PD3:-1e-12')
    +*/
    +#define qh_ZEROdelaunay 2
    +
    +/*============================================================*/
    +/*============= Microsoft DevStudio ==========================*/
    +/*============================================================*/
    +
    +/*
    +   Finding Memory Leaks Using the CRT Library
    +   https://msdn.microsoft.com/en-us/library/x98tx3cf(v=vs.100).aspx
    +
    +   Reports enabled in qh_lib_check for Debug window and stderr
    +
    +   From 2005=>msvcr80d, 2010=>msvcr100d, 2012=>msvcr110d
    +
    +   Watch: {,,msvcr80d.dll}_crtBreakAlloc  Value from {n} in the leak report
    +   _CrtSetBreakAlloc(689); // qh_lib_check() [global_r.c]
    +
    +   Examples
    +     http://free-cad.sourceforge.net/SrcDocu/d2/d7f/MemDebug_8cpp_source.html
    +     https://github.com/illlust/Game/blob/master/library/MemoryLeak.cpp
    +*/
    +#if 0   /* off (0) by default for QHULL_CRTDBG */
    +#define QHULL_CRTDBG
    +#endif
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)
    +#define _CRTDBG_MAP_ALLOC
    +#include 
    +#include 
    +#endif
    +#endif /* qh_DEFuser */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/usermem.c b/xs/src/qhull/src/libqhull/usermem.c
    new file mode 100644
    index 0000000000..0e99e8f66c
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/usermem.c
    @@ -0,0 +1,94 @@
    +/*
      ---------------------------------
    +
    +   usermem.c
    +   qh_exit(), qh_free(), and qh_malloc()
    +
    +   See README.txt.
    +
    +   If you redefine one of these functions you must redefine all of them.
    +   If you recompile and load this file, then usermem.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull.h for data structures, macros, and user-callable functions.
    +   See user.c for qhull-related, redefinable functions
    +   see user.h for user-definable constants
    +   See userprintf.c for qh_fprintf and userprintf_rbox.c for qh_fprintf_rbox
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull.h"
    +
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +  qh_exit( exitcode )
    +    exit program
    +
    +  notes:
    +    qh_exit() is called when qh_errexit() and longjmp() are not available.
    +
    +    This is the only use of exit() in Qhull
    +    To replace qh_exit with 'throw', see libqhullcpp/usermem_r-cpp.cpp
    +*/
    +void qh_exit(int exitcode) {
    +    exit(exitcode);
    +} /* exit */
    +
    +/*---------------------------------
    +
    +  qh_fprintf_stderr( msgcode, format, list of args )
    +    fprintf to stderr with msgcode (non-zero)
    +
    +  notes:
    +    qh_fprintf_stderr() is called when qh.ferr is not defined, usually due to an initialization error
    +    
    +    It is typically followed by qh_errexit().
    +
    +    Redefine this function to avoid using stderr
    +
    +    Use qh_fprintf [userprintf.c] for normal printing
    +*/
    +void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    va_start(args, fmt);
    +    if(msgcode)
    +      fprintf(stderr, "QH%.4d ", msgcode);
    +    vfprintf(stderr, fmt, args);
    +    va_end(args);
    +} /* fprintf_stderr */
    +
    +/*---------------------------------
    +
    +  qh_free( mem )
    +    free memory
    +
    +  notes:
    +    same as free()
    +    No calls to qh_errexit() 
    +*/
    +void qh_free(void *mem) {
    +    free(mem);
    +} /* free */
    +
    +/*---------------------------------
    +
    +    qh_malloc( mem )
    +      allocate memory
    +
    +    notes:
    +      same as malloc()
    +*/
    +void *qh_malloc(size_t size) {
    +    return malloc(size);
    +} /* malloc */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/userprintf.c b/xs/src/qhull/src/libqhull/userprintf.c
    new file mode 100644
    index 0000000000..190d7cd79b
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/userprintf.c
    @@ -0,0 +1,66 @@
    +/*
      ---------------------------------
    +
    +   userprintf.c
    +   qh_fprintf()
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   If you recompile and load this file, then userprintf.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull.h for data structures, macros, and user-callable functions.
    +   See user.c for qhull-related, redefinable functions
    +   see user.h for user-definable constants
    +   See usermem.c for qh_exit(), qh_free(), and qh_malloc()
    +   see Qhull.cpp and RboxPoints.cpp for examples.
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull.h"
    +#include "mem.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +   qh_fprintf(fp, msgcode, format, list of args )
    +     print arguments to *fp according to format
    +     Use qh_fprintf_rbox() for rboxlib.c
    +
    +   notes:
    +     same as fprintf()
    +     fgets() is not trapped like fprintf()
    +     exit qh_fprintf via qh_errexit()
    +     may be called for errors in qh_initstatistics and qh_meminit
    +*/
    +
    +void qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    if (!fp) {
    +        /* could use qhmem.ferr, but probably better to be cautious */
    +        qh_fprintf_stderr(6232, "Qhull internal error (userprintf.c): fp is 0.  Wrong qh_fprintf called.\n");
    +        qh_errexit(6232, NULL, NULL);
    +    }
    +    va_start(args, fmt);
    +#if qh_QHpointer
    +    if (qh_qh && qh ANNOTATEoutput) {
    +#else
    +    if (qh ANNOTATEoutput) {
    +#endif
    +      fprintf(fp, "[QH%.4d]", msgcode);
    +    }else if (msgcode >= MSG_ERROR && msgcode < MSG_STDERR ) {
    +      fprintf(fp, "QH%.4d ", msgcode);
    +    }
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +
    +    /* Place debugging traps here. Use with option 'Tn' */
    +
    +} /* qh_fprintf */
    +
    diff --git a/xs/src/qhull/src/libqhull/userprintf_rbox.c b/xs/src/qhull/src/libqhull/userprintf_rbox.c
    new file mode 100644
    index 0000000000..8edd2001aa
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/userprintf_rbox.c
    @@ -0,0 +1,53 @@
    +/*
      ---------------------------------
    +
    +   userprintf_rbox.c
    +   qh_fprintf_rbox()
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   If you recompile and load this file, then userprintf_rbox.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull.h for data structures, macros, and user-callable functions.
    +   See user.c for qhull-related, redefinable functions
    +   see user.h for user-definable constants
    +   See usermem.c for qh_exit(), qh_free(), and qh_malloc()
    +   see Qhull.cpp and RboxPoints.cpp for examples.
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +   qh_fprintf_rbox(fp, msgcode, format, list of args )
    +     print arguments to *fp according to format
    +     Use qh_fprintf_rbox() for rboxlib.c
    +
    +   notes:
    +     same as fprintf()
    +     fgets() is not trapped like fprintf()
    +     exit qh_fprintf_rbox via qh_errexit_rbox()
    +*/
    +
    +void qh_fprintf_rbox(FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    if (!fp) {
    +        qh_fprintf_stderr(6231, "Qhull internal error (userprintf_rbox.c): fp is 0.  Wrong qh_fprintf_rbox called.\n");
    +        qh_errexit_rbox(6231);
    +    }
    +    if (msgcode >= MSG_ERROR && msgcode < MSG_STDERR)
    +      fprintf(fp, "QH%.4d ", msgcode);
    +    va_start(args, fmt);
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +} /* qh_fprintf_rbox */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/Makefile b/xs/src/qhull/src/libqhull_r/Makefile
    new file mode 100644
    index 0000000000..5c40969e01
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/Makefile
    @@ -0,0 +1,240 @@
    +# Simple gcc Makefile for reentrant qhull and rbox (default gcc/g++)
    +#
    +#   make help
    +#   See README.txt and ../../Makefile
    +#       
    +# Variables
    +#   BINDIR         directory where to copy executables
    +#   DESTDIR        destination directory for 'make install'
    +#   DOCDIR         directory where to copy html documentation
    +#   INCDIR         directory where to copy headers
    +#   LIBDIR         directory where to copy libraries
    +#   MANDIR         directory where to copy manual pages
    +#   PRINTMAN       command for printing manual pages
    +#   PRINTC         command for printing C files
    +#   CC             ANSI C or C++ compiler
    +#   CC_OPTS1       options used to compile .c files
    +#   CC_OPTS2       options used to link .o files
    +#   CC_OPTS3       options to build shared libraries
    +#
    +#   LIBQHULL_OBJS  .o files for linking
    +#   LIBQHULL_HDRS  .h files for printing
    +#   CFILES         .c files for printing
    +#   DOCFILES       documentation files
    +#   FILES          miscellaneous files for printing
    +#   TFILES         .txt versions of html files
    +#   FILES          all other files
    +#   LIBQHULL_OBJS  specifies the object files of libqhullstatic_r.a
    +#
    +# Results
    +#   rbox           Generates points sets for qhull, qconvex, etc.
    +#   qhull          Computes convex hulls and related structures
    +#   qconvex, qdelaunay, qhalf, qvoronoi
    +#                  Specializations of qhull for each geometric structure
    +#   libqhullstatic_r.a Static library for reentrant qhull
    +#   testqset_r     Standalone test of reentrant qset_r.c with mem_r.c
    +#   user_eg        An example of using qhull (reentrant)
    +#   user_eg2       An example of using qhull (reentrant)
    +#
    +# Make targets
    +#   make           Build results using gcc or another compiler
    +#   make clean     Remove object files
    +#   make cleanall  Remove generated files
    +#   make doc       Print documentation
    +#   make help
    +#   make install   Copy qhull, rbox, qhull.1, rbox.1 to BINDIR, MANDIR
    +#   make new       Rebuild qhull and rbox from source
    +#   make printall  Print all files
    +#   make qtest     Quick test of qset, rbox, and qhull
    +#   make test      Quck test of qhull, qconvex, etc.
    +#
    +# Do not replace tabs with spaces.  Needed for build rules
    +# Unix line endings (\n)
    +# $Id: //main/2015/qhull/src/libqhull_r/Makefile#6 $
    +
    +DESTDIR = /usr/local
    +BINDIR	= $(DESTDIR)/bin
    +INCDIR	= $(DESTDIR)/include
    +LIBDIR	= $(DESTDIR)/lib
    +DOCDIR	= $(DESTDIR)/share/doc/qhull
    +MANDIR	= $(DESTDIR)/share/man/man1
    +
    +# if you do not have enscript, try a2ps or just use lpr.  The files are text.
    +PRINTMAN = enscript -2rl
    +PRINTC = enscript -2r
    +# PRINTMAN = lpr
    +# PRINTC = lpr
    +
    +#for Gnu's gcc compiler, -O3 for optimization, -g for debugging, -pg for profiling
    +# -fpic  needed for gcc x86_64-linux-gnu.  Not needed for mingw
    +CC        = gcc
    +CC_OPTS1  = -O3 -ansi -I../../src -fpic $(CC_WARNINGS)
    +
    +# for Sun's cc compiler, -fast or O2 for optimization, -g for debugging, -Xc for ANSI
    +#CC       = cc
    +#CC_OPTS1 = -Xc -v -fast -I../../src 
    +
    +# for Silicon Graphics cc compiler, -O2 for optimization, -g for debugging
    +#CC       = cc
    +#CC_OPTS1 = -ansi -O2 -I../../src 
    +
    +# for Next cc compiler with fat executable
    +#CC       = cc
    +#CC_OPTS1 = -ansi -O2 -I../../src -arch m68k -arch i386 -arch hppa
    +
    +# For loader, ld, 
    +CC_OPTS2 = $(CC_OPTS1)
    +
    +# Default targets for make
    +
    +all: qhull_links qhull_all qtest
    +
    +help:
    +	head -n 50 Makefile
    +
    +clean:
    +	rm -f *.o 
    +	# Delete linked files from other directories [qhull_links]
    +	rm -f qconvex_r.c unix_r.c qdelaun_r.c qhalf_r.c qvoronoi_r.c rbox_r.c
    +	rm -f user_eg_r.c user_eg2_r.c testqset_r.c
    +	
    +cleanall: clean
    +	rm -f qconvex qdelaunay qhalf qvoronoi qhull *.exe
    +	rm -f core user_eg_r user_eg2_r testqset_r libqhullstatic_r.a
    +
    +doc: 
    +	$(PRINTMAN) $(TXTFILES) $(DOCFILES)
    +
    +install:
    +	mkdir -p $(BINDIR)
    +	mkdir -p $(DOCDIR)
    +	mkdir -p $(INCDIR)/libqhull
    +	mkdir -p $(MANDIR)
    +	cp -p qconvex qdelaunay qhalf qhull qvoronoi rbox $(BINDIR)
    +	cp -p libqhullstatic_r.a $(LIBDIR)
    +	cp -p ../../html/qhull.man $(MANDIR)/qhull.1
    +	cp -p ../../html/rbox.man $(MANDIR)/rbox.1
    +	cp -p ../../html/* $(DOCDIR)
    +	cp *.h $(INCDIR)/libqhull_r
    +
    +new:	cleanall all
    +
    +printall: doc printh printc printf
    +
    +printh:
    +	$(PRINTC) $(LIBQHULL_HDRS)
    +
    +printc:
    +	$(PRINTC) $(CFILES)
    +
    +# LIBQHULL_OBJS_1 ordered by frequency of execution with small files at end.  Better locality.
    +# Same definitions as ../../Makefile
    +
    +LIBQHULLS_OBJS_1= global_r.o stat_r.o geom2_r.o poly2_r.o merge_r.o \
    +        libqhull_r.o geom_r.o poly_r.o qset_r.o mem_r.o random_r.o 
    +
    +LIBQHULLS_OBJS_2= $(LIBQHULLS_OBJS_1) usermem_r.o userprintf_r.o io_r.o user_r.o
    +
    +LIBQHULLS_OBJS= $(LIBQHULLS_OBJS_2)  rboxlib_r.o userprintf_rbox_r.o
    +
    +LIBQHULL_HDRS= user_r.h libqhull_r.h qhull_ra.h geom_r.h \
    +        io_r.h mem_r.h merge_r.h poly_r.h random_r.h \
    +        qset_r.h stat_r.h
    +
    +# CFILES ordered alphabetically after libqhull.c 
    +CFILES= ../qhull/unix_r.c libqhull_r.c geom_r.c geom2_r.c global_r.c io_r.c \
    +	mem_r.c merge_r.c poly_r.c poly2_r.c random_r.c rboxlib_r.c \
    +	qset_r.c stat_r.c user_r.c usermem_r.c userprintf_r.c \
    +	../qconvex/qconvex.c ../qdelaunay/qdelaun.c ../qhalf/qhalf.c ../qvoronoi/qvoronoi.c
    +
    +TXTFILES= ../../Announce.txt ../../REGISTER.txt ../../COPYING.txt ../../README.txt ../Changes.txt
    +DOCFILES= ../../html/rbox.txt ../../html/qhull.txt
    +
    +.c.o:
    +	$(CC) -c $(CC_OPTS1) -o $@ $<
    +
    +# Work around problems with ../ in Red Hat Linux
    +qhull_links:
    +	# On MINSYS, 'ln -s' may create a copy instead of a symbolic link
    +	[ -f qconvex_r.c ]  || ln -s ../qconvex/qconvex_r.c
    +	[ -f qdelaun_r.c ]  || ln -s ../qdelaunay/qdelaun_r.c
    +	[ -f qhalf_r.c ]    || ln -s ../qhalf/qhalf_r.c
    +	[ -f qvoronoi_r.c ] || ln -s ../qvoronoi/qvoronoi_r.c
    +	[ -f rbox_r.c ]     || ln -s ../rbox/rbox_r.c
    +	[ -f testqset_r.c ] || ln -s ../testqset_r/testqset_r.c
    +	[ -f unix_r.c ]     || ln -s ../qhull/unix_r.c
    +	[ -f user_eg_r.c ]  || ln -s ../user_eg/user_eg_r.c
    +	[ -f user_eg2_r.c ] || ln -s ../user_eg2/user_eg2_r.c
    +
    +# compile qhull without using bin/libqhullstatic_r.a
    +qhull_all: qconvex_r.o qdelaun_r.o qhalf_r.o qvoronoi_r.o unix_r.o user_eg_r.o user_eg2_r.o rbox_r.o testqset_r.o $(LIBQHULLS_OBJS)
    +	$(CC) -o qconvex $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) qconvex_r.o
    +	$(CC) -o qdelaunay $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) qdelaun_r.o
    +	$(CC) -o qhalf $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) qhalf_r.o
    +	$(CC) -o qvoronoi $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) qvoronoi_r.o
    +	$(CC) -o qhull $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) unix_r.o 
    +	$(CC) -o rbox $(CC_OPTS2) -lm $(LIBQHULLS_OBJS) rbox_r.o
    +	$(CC) -o user_eg $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) user_eg_r.o 
    +	$(CC) -o user_eg2 $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_1) user_eg2_r.o  usermem_r.o userprintf_r.o io_r.o
    +	$(CC) -o testqset_r $(CC_OPTS2) -lm mem_r.o qset_r.o usermem_r.o testqset_r.o
    +	-ar -rs libqhullstatic_r.a $(LIBQHULLS_OBJS)
    +	#libqhullstatic_r.a is not needed for qhull
    +	#If 'ar -rs' fails try using 'ar -s' with 'ranlib'
    +	#ranlib libqhullstatic_r.a
    +
    +qtest:
    +	@echo ============================================
    +	@echo == make qtest ==============================
    +	@echo ============================================
    +	@echo -n "== "
    +	@date
    +	@echo
    +	@echo Testing qset.c and mem.c with testqset
    +	./testqset_r 10000
    +	@echo Run the qhull smoketest
    +	./rbox D4 | ./qhull
    +	@echo ============================================
    +	@echo == To smoketest qhull programs
    +	@echo '==     make test'
    +	@echo ============================================
    +	@echo
    +	@echo ============================================
    +	@echo == For all make targets
    +	@echo '==     make help'
    +	@echo ============================================
    +	@echo
    +
    +test: qtest
    +	@echo ==============================
    +	@echo ========= qconvex ============
    +	@echo ==============================
    +	-./rbox 10 | ./qconvex Tv 
    +	@echo
    +	@echo ==============================
    +	@echo ========= qdelaunay ==========
    +	@echo ==============================
    +	-./rbox 10 | ./qdelaunay Tv 
    +	@echo
    +	@echo ==============================
    +	@echo ========= qhalf ==============
    +	@echo ==============================
    +	-./rbox 10 | ./qconvex FQ FV n Tv | ./qhalf Tv
    +	@echo
    +	@echo ==============================
    +	@echo ========= qvoronoi ===========
    +	@echo ==============================
    +	-./rbox 10 | ./qvoronoi Tv
    +	@echo
    +	@echo ==============================
    +	@echo ========= user_eg ============
    +	@echo == w/o shared library ========
    +	@echo ==============================
    +	-./user_eg
    +	@echo
    +	@echo ==============================
    +	@echo ========= user_eg2 ===========
    +	@echo ==============================
    +	-./user_eg2
    +	@echo
    +
    +# end of Makefile
    diff --git a/xs/src/qhull/src/libqhull_r/geom2_r.c b/xs/src/qhull/src/libqhull_r/geom2_r.c
    new file mode 100644
    index 0000000000..48addba1cf
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/geom2_r.c
    @@ -0,0 +1,2096 @@
    +/*
      ---------------------------------
    +
    +
    +   geom2_r.c
    +   infrequently used geometric routines of qhull
    +
    +   see qh-geom_r.htm and geom_r.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/geom2_r.c#6 $$Change: 2065 $
    +   $DateTime: 2016/01/18 13:51:04 $$Author: bbarber $
    +
    +   frequently used code goes into geom_r.c
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*================== functions in alphabetic order ============*/
    +
    +/*---------------------------------
    +
    +  qh_copypoints(qh, points, numpoints, dimension)
    +    return qh_malloc'd copy of points
    +  
    +  notes:
    +    qh_free the returned points to avoid a memory leak
    +*/
    +coordT *qh_copypoints(qhT *qh, coordT *points, int numpoints, int dimension) {
    +  int size;
    +  coordT *newpoints;
    +
    +  size= numpoints * dimension * (int)sizeof(coordT);
    +  if (!(newpoints= (coordT*)qh_malloc((size_t)size))) {
    +    qh_fprintf(qh, qh->ferr, 6004, "qhull error: insufficient memory to copy %d points\n",
    +        numpoints);
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  memcpy((char *)newpoints, (char *)points, (size_t)size); /* newpoints!=0 by QH6004 */
    +  return newpoints;
    +} /* copypoints */
    +
    +/*---------------------------------
    +
    +  qh_crossproduct( dim, vecA, vecB, vecC )
    +    crossproduct of 2 dim vectors
    +    C= A x B
    +
    +  notes:
    +    from Glasner, Graphics Gems I, p. 639
    +    only defined for dim==3
    +*/
    +void qh_crossproduct(int dim, realT vecA[3], realT vecB[3], realT vecC[3]){
    +
    +  if (dim == 3) {
    +    vecC[0]=   det2_(vecA[1], vecA[2],
    +                     vecB[1], vecB[2]);
    +    vecC[1]= - det2_(vecA[0], vecA[2],
    +                     vecB[0], vecB[2]);
    +    vecC[2]=   det2_(vecA[0], vecA[1],
    +                     vecB[0], vecB[1]);
    +  }
    +} /* vcross */
    +
    +/*---------------------------------
    +
    +  qh_determinant(qh, rows, dim, nearzero )
    +    compute signed determinant of a square matrix
    +    uses qh.NEARzero to test for degenerate matrices
    +
    +  returns:
    +    determinant
    +    overwrites rows and the matrix
    +    if dim == 2 or 3
    +      nearzero iff determinant < qh->NEARzero[dim-1]
    +      (!quite correct, not critical)
    +    if dim >= 4
    +      nearzero iff diagonal[k] < qh->NEARzero[k]
    +*/
    +realT qh_determinant(qhT *qh, realT **rows, int dim, boolT *nearzero) {
    +  realT det=0;
    +  int i;
    +  boolT sign= False;
    +
    +  *nearzero= False;
    +  if (dim < 2) {
    +    qh_fprintf(qh, qh->ferr, 6005, "qhull internal error (qh_determinate): only implemented for dimension >= 2\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }else if (dim == 2) {
    +    det= det2_(rows[0][0], rows[0][1],
    +                 rows[1][0], rows[1][1]);
    +    if (fabs_(det) < 10*qh->NEARzero[1])  /* not really correct, what should this be? */
    +      *nearzero= True;
    +  }else if (dim == 3) {
    +    det= det3_(rows[0][0], rows[0][1], rows[0][2],
    +                 rows[1][0], rows[1][1], rows[1][2],
    +                 rows[2][0], rows[2][1], rows[2][2]);
    +    if (fabs_(det) < 10*qh->NEARzero[2])  /* what should this be?  det 5.5e-12 was flat for qh_maxsimplex of qdelaunay 0,0 27,27 -36,36 -9,63 */
    +      *nearzero= True;
    +  }else {
    +    qh_gausselim(qh, rows, dim, dim, &sign, nearzero);  /* if nearzero, diagonal still ok*/
    +    det= 1.0;
    +    for (i=dim; i--; )
    +      det *= (rows[i])[i];
    +    if (sign)
    +      det= -det;
    +  }
    +  return det;
    +} /* determinant */
    +
    +/*---------------------------------
    +
    +  qh_detjoggle(qh, points, numpoints, dimension )
    +    determine default max joggle for point array
    +      as qh_distround * qh_JOGGLEdefault
    +
    +  returns:
    +    initial value for JOGGLEmax from points and REALepsilon
    +
    +  notes:
    +    computes DISTround since qh_maxmin not called yet
    +    if qh->SCALElast, last dimension will be scaled later to MAXwidth
    +
    +    loop duplicated from qh_maxmin
    +*/
    +realT qh_detjoggle(qhT *qh, pointT *points, int numpoints, int dimension) {
    +  realT abscoord, distround, joggle, maxcoord, mincoord;
    +  pointT *point, *pointtemp;
    +  realT maxabs= -REALmax;
    +  realT sumabs= 0;
    +  realT maxwidth= 0;
    +  int k;
    +
    +  for (k=0; k < dimension; k++) {
    +    if (qh->SCALElast && k == dimension-1)
    +      abscoord= maxwidth;
    +    else if (qh->DELAUNAY && k == dimension-1) /* will qh_setdelaunay() */
    +      abscoord= 2 * maxabs * maxabs;  /* may be low by qh->hull_dim/2 */
    +    else {
    +      maxcoord= -REALmax;
    +      mincoord= REALmax;
    +      FORALLpoint_(qh, points, numpoints) {
    +        maximize_(maxcoord, point[k]);
    +        minimize_(mincoord, point[k]);
    +      }
    +      maximize_(maxwidth, maxcoord-mincoord);
    +      abscoord= fmax_(maxcoord, -mincoord);
    +    }
    +    sumabs += abscoord;
    +    maximize_(maxabs, abscoord);
    +  } /* for k */
    +  distround= qh_distround(qh, qh->hull_dim, maxabs, sumabs);
    +  joggle= distround * qh_JOGGLEdefault;
    +  maximize_(joggle, REALepsilon * qh_JOGGLEdefault);
    +  trace2((qh, qh->ferr, 2001, "qh_detjoggle: joggle=%2.2g maxwidth=%2.2g\n", joggle, maxwidth));
    +  return joggle;
    +} /* detjoggle */
    +
    +/*---------------------------------
    +
    +  qh_detroundoff(qh)
    +    determine maximum roundoff errors from
    +      REALepsilon, REALmax, REALmin, qh.hull_dim, qh.MAXabs_coord,
    +      qh.MAXsumcoord, qh.MAXwidth, qh.MINdenom_1
    +
    +    accounts for qh.SETroundoff, qh.RANDOMdist, qh->MERGEexact
    +      qh.premerge_cos, qh.postmerge_cos, qh.premerge_centrum,
    +      qh.postmerge_centrum, qh.MINoutside,
    +      qh_RATIOnearinside, qh_COPLANARratio, qh_WIDEcoplanar
    +
    +  returns:
    +    sets qh.DISTround, etc. (see below)
    +    appends precision constants to qh.qhull_options
    +
    +  see:
    +    qh_maxmin() for qh.NEARzero
    +
    +  design:
    +    determine qh.DISTround for distance computations
    +    determine minimum denominators for qh_divzero
    +    determine qh.ANGLEround for angle computations
    +    adjust qh.premerge_cos,... for roundoff error
    +    determine qh.ONEmerge for maximum error due to a single merge
    +    determine qh.NEARinside, qh.MAXcoplanar, qh.MINvisible,
    +      qh.MINoutside, qh.WIDEfacet
    +    initialize qh.max_vertex and qh.minvertex
    +*/
    +void qh_detroundoff(qhT *qh) {
    +
    +  qh_option(qh, "_max-width", NULL, &qh->MAXwidth);
    +  if (!qh->SETroundoff) {
    +    qh->DISTround= qh_distround(qh, qh->hull_dim, qh->MAXabs_coord, qh->MAXsumcoord);
    +    if (qh->RANDOMdist)
    +      qh->DISTround += qh->RANDOMfactor * qh->MAXabs_coord;
    +    qh_option(qh, "Error-roundoff", NULL, &qh->DISTround);
    +  }
    +  qh->MINdenom= qh->MINdenom_1 * qh->MAXabs_coord;
    +  qh->MINdenom_1_2= sqrt(qh->MINdenom_1 * qh->hull_dim) ;  /* if will be normalized */
    +  qh->MINdenom_2= qh->MINdenom_1_2 * qh->MAXabs_coord;
    +                                              /* for inner product */
    +  qh->ANGLEround= 1.01 * qh->hull_dim * REALepsilon;
    +  if (qh->RANDOMdist)
    +    qh->ANGLEround += qh->RANDOMfactor;
    +  if (qh->premerge_cos < REALmax/2) {
    +    qh->premerge_cos -= qh->ANGLEround;
    +    if (qh->RANDOMdist)
    +      qh_option(qh, "Angle-premerge-with-random", NULL, &qh->premerge_cos);
    +  }
    +  if (qh->postmerge_cos < REALmax/2) {
    +    qh->postmerge_cos -= qh->ANGLEround;
    +    if (qh->RANDOMdist)
    +      qh_option(qh, "Angle-postmerge-with-random", NULL, &qh->postmerge_cos);
    +  }
    +  qh->premerge_centrum += 2 * qh->DISTround;    /*2 for centrum and distplane()*/
    +  qh->postmerge_centrum += 2 * qh->DISTround;
    +  if (qh->RANDOMdist && (qh->MERGEexact || qh->PREmerge))
    +    qh_option(qh, "Centrum-premerge-with-random", NULL, &qh->premerge_centrum);
    +  if (qh->RANDOMdist && qh->POSTmerge)
    +    qh_option(qh, "Centrum-postmerge-with-random", NULL, &qh->postmerge_centrum);
    +  { /* compute ONEmerge, max vertex offset for merging simplicial facets */
    +    realT maxangle= 1.0, maxrho;
    +
    +    minimize_(maxangle, qh->premerge_cos);
    +    minimize_(maxangle, qh->postmerge_cos);
    +    /* max diameter * sin theta + DISTround for vertex to its hyperplane */
    +    qh->ONEmerge= sqrt((realT)qh->hull_dim) * qh->MAXwidth *
    +      sqrt(1.0 - maxangle * maxangle) + qh->DISTround;
    +    maxrho= qh->hull_dim * qh->premerge_centrum + qh->DISTround;
    +    maximize_(qh->ONEmerge, maxrho);
    +    maxrho= qh->hull_dim * qh->postmerge_centrum + qh->DISTround;
    +    maximize_(qh->ONEmerge, maxrho);
    +    if (qh->MERGING)
    +      qh_option(qh, "_one-merge", NULL, &qh->ONEmerge);
    +  }
    +  qh->NEARinside= qh->ONEmerge * qh_RATIOnearinside; /* only used if qh->KEEPnearinside */
    +  if (qh->JOGGLEmax < REALmax/2 && (qh->KEEPcoplanar || qh->KEEPinside)) {
    +    realT maxdist;             /* adjust qh.NEARinside for joggle */
    +    qh->KEEPnearinside= True;
    +    maxdist= sqrt((realT)qh->hull_dim) * qh->JOGGLEmax + qh->DISTround;
    +    maxdist= 2*maxdist;        /* vertex and coplanar point can joggle in opposite directions */
    +    maximize_(qh->NEARinside, maxdist);  /* must agree with qh_nearcoplanar() */
    +  }
    +  if (qh->KEEPnearinside)
    +    qh_option(qh, "_near-inside", NULL, &qh->NEARinside);
    +  if (qh->JOGGLEmax < qh->DISTround) {
    +    qh_fprintf(qh, qh->ferr, 6006, "qhull error: the joggle for 'QJn', %.2g, is below roundoff for distance computations, %.2g\n",
    +         qh->JOGGLEmax, qh->DISTround);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh->MINvisible > REALmax/2) {
    +    if (!qh->MERGING)
    +      qh->MINvisible= qh->DISTround;
    +    else if (qh->hull_dim <= 3)
    +      qh->MINvisible= qh->premerge_centrum;
    +    else
    +      qh->MINvisible= qh_COPLANARratio * qh->premerge_centrum;
    +    if (qh->APPROXhull && qh->MINvisible > qh->MINoutside)
    +      qh->MINvisible= qh->MINoutside;
    +    qh_option(qh, "Visible-distance", NULL, &qh->MINvisible);
    +  }
    +  if (qh->MAXcoplanar > REALmax/2) {
    +    qh->MAXcoplanar= qh->MINvisible;
    +    qh_option(qh, "U-coplanar-distance", NULL, &qh->MAXcoplanar);
    +  }
    +  if (!qh->APPROXhull) {             /* user may specify qh->MINoutside */
    +    qh->MINoutside= 2 * qh->MINvisible;
    +    if (qh->premerge_cos < REALmax/2)
    +      maximize_(qh->MINoutside, (1- qh->premerge_cos) * qh->MAXabs_coord);
    +    qh_option(qh, "Width-outside", NULL, &qh->MINoutside);
    +  }
    +  qh->WIDEfacet= qh->MINoutside;
    +  maximize_(qh->WIDEfacet, qh_WIDEcoplanar * qh->MAXcoplanar);
    +  maximize_(qh->WIDEfacet, qh_WIDEcoplanar * qh->MINvisible);
    +  qh_option(qh, "_wide-facet", NULL, &qh->WIDEfacet);
    +  if (qh->MINvisible > qh->MINoutside + 3 * REALepsilon
    +  && !qh->BESToutside && !qh->FORCEoutput)
    +    qh_fprintf(qh, qh->ferr, 7001, "qhull input warning: minimum visibility V%.2g is greater than \nminimum outside W%.2g.  Flipped facets are likely.\n",
    +             qh->MINvisible, qh->MINoutside);
    +  qh->max_vertex= qh->DISTround;
    +  qh->min_vertex= -qh->DISTround;
    +  /* numeric constants reported in printsummary */
    +} /* detroundoff */
    +
    +/*---------------------------------
    +
    +  qh_detsimplex(qh, apex, points, dim, nearzero )
    +    compute determinant of a simplex with point apex and base points
    +
    +  returns:
    +     signed determinant and nearzero from qh_determinant
    +
    +  notes:
    +     uses qh.gm_matrix/qh.gm_row (assumes they're big enough)
    +
    +  design:
    +    construct qm_matrix by subtracting apex from points
    +    compute determinate
    +*/
    +realT qh_detsimplex(qhT *qh, pointT *apex, setT *points, int dim, boolT *nearzero) {
    +  pointT *coorda, *coordp, *gmcoord, *point, **pointp;
    +  coordT **rows;
    +  int k,  i=0;
    +  realT det;
    +
    +  zinc_(Zdetsimplex);
    +  gmcoord= qh->gm_matrix;
    +  rows= qh->gm_row;
    +  FOREACHpoint_(points) {
    +    if (i == dim)
    +      break;
    +    rows[i++]= gmcoord;
    +    coordp= point;
    +    coorda= apex;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= *coordp++ - *coorda++;
    +  }
    +  if (i < dim) {
    +    qh_fprintf(qh, qh->ferr, 6007, "qhull internal error (qh_detsimplex): #points %d < dimension %d\n",
    +               i, dim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  det= qh_determinant(qh, rows, dim, nearzero);
    +  trace2((qh, qh->ferr, 2002, "qh_detsimplex: det=%2.2g for point p%d, dim %d, nearzero? %d\n",
    +          det, qh_pointid(qh, apex), dim, *nearzero));
    +  return det;
    +} /* detsimplex */
    +
    +/*---------------------------------
    +
    +  qh_distnorm( dim, point, normal, offset )
    +    return distance from point to hyperplane at normal/offset
    +
    +  returns:
    +    dist
    +
    +  notes:
    +    dist > 0 if point is outside of hyperplane
    +
    +  see:
    +    qh_distplane in geom_r.c
    +*/
    +realT qh_distnorm(int dim, pointT *point, pointT *normal, realT *offsetp) {
    +  coordT *normalp= normal, *coordp= point;
    +  realT dist;
    +  int k;
    +
    +  dist= *offsetp;
    +  for (k=dim; k--; )
    +    dist += *(coordp++) * *(normalp++);
    +  return dist;
    +} /* distnorm */
    +
    +/*---------------------------------
    +
    +  qh_distround(qh, dimension, maxabs, maxsumabs )
    +    compute maximum round-off error for a distance computation
    +      to a normalized hyperplane
    +    maxabs is the maximum absolute value of a coordinate
    +    maxsumabs is the maximum possible sum of absolute coordinate values
    +
    +  returns:
    +    max dist round for REALepsilon
    +
    +  notes:
    +    calculate roundoff error according to Golub & van Loan, 1983, Lemma 3.2-1, "Rounding Errors"
    +    use sqrt(dim) since one vector is normalized
    +      or use maxsumabs since one vector is < 1
    +*/
    +realT qh_distround(qhT *qh, int dimension, realT maxabs, realT maxsumabs) {
    +  realT maxdistsum, maxround;
    +
    +  maxdistsum= sqrt((realT)dimension) * maxabs;
    +  minimize_( maxdistsum, maxsumabs);
    +  maxround= REALepsilon * (dimension * maxdistsum * 1.01 + maxabs);
    +              /* adds maxabs for offset */
    +  trace4((qh, qh->ferr, 4008, "qh_distround: %2.2g maxabs %2.2g maxsumabs %2.2g maxdistsum %2.2g\n",
    +                 maxround, maxabs, maxsumabs, maxdistsum));
    +  return maxround;
    +} /* distround */
    +
    +/*---------------------------------
    +
    +  qh_divzero( numer, denom, mindenom1, zerodiv )
    +    divide by a number that's nearly zero
    +    mindenom1= minimum denominator for dividing into 1.0
    +
    +  returns:
    +    quotient
    +    sets zerodiv and returns 0.0 if it would overflow
    +
    +  design:
    +    if numer is nearly zero and abs(numer) < abs(denom)
    +      return numer/denom
    +    else if numer is nearly zero
    +      return 0 and zerodiv
    +    else if denom/numer non-zero
    +      return numer/denom
    +    else
    +      return 0 and zerodiv
    +*/
    +realT qh_divzero(realT numer, realT denom, realT mindenom1, boolT *zerodiv) {
    +  realT temp, numerx, denomx;
    +
    +
    +  if (numer < mindenom1 && numer > -mindenom1) {
    +    numerx= fabs_(numer);
    +    denomx= fabs_(denom);
    +    if (numerx < denomx) {
    +      *zerodiv= False;
    +      return numer/denom;
    +    }else {
    +      *zerodiv= True;
    +      return 0.0;
    +    }
    +  }
    +  temp= denom/numer;
    +  if (temp > mindenom1 || temp < -mindenom1) {
    +    *zerodiv= False;
    +    return numer/denom;
    +  }else {
    +    *zerodiv= True;
    +    return 0.0;
    +  }
    +} /* divzero */
    +
    +
    +/*---------------------------------
    +
    +  qh_facetarea(qh, facet )
    +    return area for a facet
    +
    +  notes:
    +    if non-simplicial,
    +      uses centrum to triangulate facet and sums the projected areas.
    +    if (qh->DELAUNAY),
    +      computes projected area instead for last coordinate
    +    assumes facet->normal exists
    +    projecting tricoplanar facets to the hyperplane does not appear to make a difference
    +
    +  design:
    +    if simplicial
    +      compute area
    +    else
    +      for each ridge
    +        compute area from centrum to ridge
    +    negate area if upper Delaunay facet
    +*/
    +realT qh_facetarea(qhT *qh, facetT *facet) {
    +  vertexT *apex;
    +  pointT *centrum;
    +  realT area= 0.0;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (facet->simplicial) {
    +    apex= SETfirstt_(facet->vertices, vertexT);
    +    area= qh_facetarea_simplex(qh, qh->hull_dim, apex->point, facet->vertices,
    +                    apex, facet->toporient, facet->normal, &facet->offset);
    +  }else {
    +    if (qh->CENTERtype == qh_AScentrum)
    +      centrum= facet->center;
    +    else
    +      centrum= qh_getcentrum(qh, facet);
    +    FOREACHridge_(facet->ridges)
    +      area += qh_facetarea_simplex(qh, qh->hull_dim, centrum, ridge->vertices,
    +                 NULL, (boolT)(ridge->top == facet),  facet->normal, &facet->offset);
    +    if (qh->CENTERtype != qh_AScentrum)
    +      qh_memfree(qh, centrum, qh->normal_size);
    +  }
    +  if (facet->upperdelaunay && qh->DELAUNAY)
    +    area= -area;  /* the normal should be [0,...,1] */
    +  trace4((qh, qh->ferr, 4009, "qh_facetarea: f%d area %2.2g\n", facet->id, area));
    +  return area;
    +} /* facetarea */
    +
    +/*---------------------------------
    +
    +  qh_facetarea_simplex(qh, dim, apex, vertices, notvertex, toporient, normal, offset )
    +    return area for a simplex defined by
    +      an apex, a base of vertices, an orientation, and a unit normal
    +    if simplicial or tricoplanar facet,
    +      notvertex is defined and it is skipped in vertices
    +
    +  returns:
    +    computes area of simplex projected to plane [normal,offset]
    +    returns 0 if vertex too far below plane (qh->WIDEfacet)
    +      vertex can't be apex of tricoplanar facet
    +
    +  notes:
    +    if (qh->DELAUNAY),
    +      computes projected area instead for last coordinate
    +    uses qh->gm_matrix/gm_row and qh->hull_dim
    +    helper function for qh_facetarea
    +
    +  design:
    +    if Notvertex
    +      translate simplex to apex
    +    else
    +      project simplex to normal/offset
    +      translate simplex to apex
    +    if Delaunay
    +      set last row/column to 0 with -1 on diagonal
    +    else
    +      set last row to Normal
    +    compute determinate
    +    scale and flip sign for area
    +*/
    +realT qh_facetarea_simplex(qhT *qh, int dim, coordT *apex, setT *vertices,
    +        vertexT *notvertex,  boolT toporient, coordT *normal, realT *offset) {
    +  pointT *coorda, *coordp, *gmcoord;
    +  coordT **rows, *normalp;
    +  int k,  i=0;
    +  realT area, dist;
    +  vertexT *vertex, **vertexp;
    +  boolT nearzero;
    +
    +  gmcoord= qh->gm_matrix;
    +  rows= qh->gm_row;
    +  FOREACHvertex_(vertices) {
    +    if (vertex == notvertex)
    +      continue;
    +    rows[i++]= gmcoord;
    +    coorda= apex;
    +    coordp= vertex->point;
    +    normalp= normal;
    +    if (notvertex) {
    +      for (k=dim; k--; )
    +        *(gmcoord++)= *coordp++ - *coorda++;
    +    }else {
    +      dist= *offset;
    +      for (k=dim; k--; )
    +        dist += *coordp++ * *normalp++;
    +      if (dist < -qh->WIDEfacet) {
    +        zinc_(Znoarea);
    +        return 0.0;
    +      }
    +      coordp= vertex->point;
    +      normalp= normal;
    +      for (k=dim; k--; )
    +        *(gmcoord++)= (*coordp++ - dist * *normalp++) - *coorda++;
    +    }
    +  }
    +  if (i != dim-1) {
    +    qh_fprintf(qh, qh->ferr, 6008, "qhull internal error (qh_facetarea_simplex): #points %d != dim %d -1\n",
    +               i, dim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  rows[i]= gmcoord;
    +  if (qh->DELAUNAY) {
    +    for (i=0; i < dim-1; i++)
    +      rows[i][dim-1]= 0.0;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= 0.0;
    +    rows[dim-1][dim-1]= -1.0;
    +  }else {
    +    normalp= normal;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= *normalp++;
    +  }
    +  zinc_(Zdetsimplex);
    +  area= qh_determinant(qh, rows, dim, &nearzero);
    +  if (toporient)
    +    area= -area;
    +  area *= qh->AREAfactor;
    +  trace4((qh, qh->ferr, 4010, "qh_facetarea_simplex: area=%2.2g for point p%d, toporient %d, nearzero? %d\n",
    +          area, qh_pointid(qh, apex), toporient, nearzero));
    +  return area;
    +} /* facetarea_simplex */
    +
    +/*---------------------------------
    +
    +  qh_facetcenter(qh, vertices )
    +    return Voronoi center (Voronoi vertex) for a facet's vertices
    +
    +  returns:
    +    return temporary point equal to the center
    +
    +  see:
    +    qh_voronoi_center()
    +*/
    +pointT *qh_facetcenter(qhT *qh, setT *vertices) {
    +  setT *points= qh_settemp(qh, qh_setsize(qh, vertices));
    +  vertexT *vertex, **vertexp;
    +  pointT *center;
    +
    +  FOREACHvertex_(vertices)
    +    qh_setappend(qh, &points, vertex->point);
    +  center= qh_voronoi_center(qh, qh->hull_dim-1, points);
    +  qh_settempfree(qh, &points);
    +  return center;
    +} /* facetcenter */
    +
    +/*---------------------------------
    +
    +  qh_findgooddist(qh, point, facetA, dist, facetlist )
    +    find best good facet visible for point from facetA
    +    assumes facetA is visible from point
    +
    +  returns:
    +    best facet, i.e., good facet that is furthest from point
    +      distance to best facet
    +      NULL if none
    +
    +    moves good, visible facets (and some other visible facets)
    +      to end of qh->facet_list
    +
    +  notes:
    +    uses qh->visit_id
    +
    +  design:
    +    initialize bestfacet if facetA is good
    +    move facetA to end of facetlist
    +    for each facet on facetlist
    +      for each unvisited neighbor of facet
    +        move visible neighbors to end of facetlist
    +        update best good neighbor
    +        if no good neighbors, update best facet
    +*/
    +facetT *qh_findgooddist(qhT *qh, pointT *point, facetT *facetA, realT *distp,
    +               facetT **facetlist) {
    +  realT bestdist= -REALmax, dist;
    +  facetT *neighbor, **neighborp, *bestfacet=NULL, *facet;
    +  boolT goodseen= False;
    +
    +  if (facetA->good) {
    +    zzinc_(Zcheckpart);  /* calls from check_bestdist occur after print stats */
    +    qh_distplane(qh, point, facetA, &bestdist);
    +    bestfacet= facetA;
    +    goodseen= True;
    +  }
    +  qh_removefacet(qh, facetA);
    +  qh_appendfacet(qh, facetA);
    +  *facetlist= facetA;
    +  facetA->visitid= ++qh->visit_id;
    +  FORALLfacet_(*facetlist) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == qh->visit_id)
    +        continue;
    +      neighbor->visitid= qh->visit_id;
    +      if (goodseen && !neighbor->good)
    +        continue;
    +      zzinc_(Zcheckpart);
    +      qh_distplane(qh, point, neighbor, &dist);
    +      if (dist > 0) {
    +        qh_removefacet(qh, neighbor);
    +        qh_appendfacet(qh, neighbor);
    +        if (neighbor->good) {
    +          goodseen= True;
    +          if (dist > bestdist) {
    +            bestdist= dist;
    +            bestfacet= neighbor;
    +          }
    +        }
    +      }
    +    }
    +  }
    +  if (bestfacet) {
    +    *distp= bestdist;
    +    trace2((qh, qh->ferr, 2003, "qh_findgooddist: p%d is %2.2g above good facet f%d\n",
    +      qh_pointid(qh, point), bestdist, bestfacet->id));
    +    return bestfacet;
    +  }
    +  trace4((qh, qh->ferr, 4011, "qh_findgooddist: no good facet for p%d above f%d\n",
    +      qh_pointid(qh, point), facetA->id));
    +  return NULL;
    +}  /* findgooddist */
    +
    +/*---------------------------------
    +
    +  qh_getarea(qh, facetlist )
    +    set area of all facets in facetlist
    +    collect statistics
    +    nop if hasAreaVolume
    +
    +  returns:
    +    sets qh->totarea/totvol to total area and volume of convex hull
    +    for Delaunay triangulation, computes projected area of the lower or upper hull
    +      ignores upper hull if qh->ATinfinity
    +
    +  notes:
    +    could compute outer volume by expanding facet area by rays from interior
    +    the following attempt at perpendicular projection underestimated badly:
    +      qh.totoutvol += (-dist + facet->maxoutside + qh->DISTround)
    +                            * area/ qh->hull_dim;
    +  design:
    +    for each facet on facetlist
    +      compute facet->area
    +      update qh.totarea and qh.totvol
    +*/
    +void qh_getarea(qhT *qh, facetT *facetlist) {
    +  realT area;
    +  realT dist;
    +  facetT *facet;
    +
    +  if (qh->hasAreaVolume)
    +    return;
    +  if (qh->REPORTfreq)
    +    qh_fprintf(qh, qh->ferr, 8020, "computing area of each facet and volume of the convex hull\n");
    +  else
    +    trace1((qh, qh->ferr, 1001, "qh_getarea: computing volume and area for each facet\n"));
    +  qh->totarea= qh->totvol= 0.0;
    +  FORALLfacet_(facetlist) {
    +    if (!facet->normal)
    +      continue;
    +    if (facet->upperdelaunay && qh->ATinfinity)
    +      continue;
    +    if (!facet->isarea) {
    +      facet->f.area= qh_facetarea(qh, facet);
    +      facet->isarea= True;
    +    }
    +    area= facet->f.area;
    +    if (qh->DELAUNAY) {
    +      if (facet->upperdelaunay == qh->UPPERdelaunay)
    +        qh->totarea += area;
    +    }else {
    +      qh->totarea += area;
    +      qh_distplane(qh, qh->interior_point, facet, &dist);
    +      qh->totvol += -dist * area/ qh->hull_dim;
    +    }
    +    if (qh->PRINTstatistics) {
    +      wadd_(Wareatot, area);
    +      wmax_(Wareamax, area);
    +      wmin_(Wareamin, area);
    +    }
    +  }
    +  qh->hasAreaVolume= True;
    +} /* getarea */
    +
    +/*---------------------------------
    +
    +  qh_gram_schmidt(qh, dim, row )
    +    implements Gram-Schmidt orthogonalization by rows
    +
    +  returns:
    +    false if zero norm
    +    overwrites rows[dim][dim]
    +
    +  notes:
    +    see Golub & van Loan, 1983, Algorithm 6.2-2, "Modified Gram-Schmidt"
    +    overflow due to small divisors not handled
    +
    +  design:
    +    for each row
    +      compute norm for row
    +      if non-zero, normalize row
    +      for each remaining rowA
    +        compute inner product of row and rowA
    +        reduce rowA by row * inner product
    +*/
    +boolT qh_gram_schmidt(qhT *qh, int dim, realT **row) {
    +  realT *rowi, *rowj, norm;
    +  int i, j, k;
    +
    +  for (i=0; i < dim; i++) {
    +    rowi= row[i];
    +    for (norm= 0.0, k= dim; k--; rowi++)
    +      norm += *rowi * *rowi;
    +    norm= sqrt(norm);
    +    wmin_(Wmindenom, norm);
    +    if (norm == 0.0)  /* either 0 or overflow due to sqrt */
    +      return False;
    +    for (k=dim; k--; )
    +      *(--rowi) /= norm;
    +    for (j=i+1; j < dim; j++) {
    +      rowj= row[j];
    +      for (norm= 0.0, k=dim; k--; )
    +        norm += *rowi++ * *rowj++;
    +      for (k=dim; k--; )
    +        *(--rowj) -= *(--rowi) * norm;
    +    }
    +  }
    +  return True;
    +} /* gram_schmidt */
    +
    +
    +/*---------------------------------
    +
    +  qh_inthresholds(qh, normal, angle )
    +    return True if normal within qh.lower_/upper_threshold
    +
    +  returns:
    +    estimate of angle by summing of threshold diffs
    +      angle may be NULL
    +      smaller "angle" is better
    +
    +  notes:
    +    invalid if qh.SPLITthresholds
    +
    +  see:
    +    qh.lower_threshold in qh_initbuild()
    +    qh_initthresholds()
    +
    +  design:
    +    for each dimension
    +      test threshold
    +*/
    +boolT qh_inthresholds(qhT *qh, coordT *normal, realT *angle) {
    +  boolT within= True;
    +  int k;
    +  realT threshold;
    +
    +  if (angle)
    +    *angle= 0.0;
    +  for (k=0; k < qh->hull_dim; k++) {
    +    threshold= qh->lower_threshold[k];
    +    if (threshold > -REALmax/2) {
    +      if (normal[k] < threshold)
    +        within= False;
    +      if (angle) {
    +        threshold -= normal[k];
    +        *angle += fabs_(threshold);
    +      }
    +    }
    +    if (qh->upper_threshold[k] < REALmax/2) {
    +      threshold= qh->upper_threshold[k];
    +      if (normal[k] > threshold)
    +        within= False;
    +      if (angle) {
    +        threshold -= normal[k];
    +        *angle += fabs_(threshold);
    +      }
    +    }
    +  }
    +  return within;
    +} /* inthresholds */
    +
    +
    +/*---------------------------------
    +
    +  qh_joggleinput(qh)
    +    randomly joggle input to Qhull by qh.JOGGLEmax
    +    initial input is qh.first_point/qh.num_points of qh.hull_dim
    +      repeated calls use qh.input_points/qh.num_points
    +
    +  returns:
    +    joggles points at qh.first_point/qh.num_points
    +    copies data to qh.input_points/qh.input_malloc if first time
    +    determines qh.JOGGLEmax if it was zero
    +    if qh.DELAUNAY
    +      computes the Delaunay projection of the joggled points
    +
    +  notes:
    +    if qh.DELAUNAY, unnecessarily joggles the last coordinate
    +    the initial 'QJn' may be set larger than qh_JOGGLEmaxincrease
    +
    +  design:
    +    if qh.DELAUNAY
    +      set qh.SCALElast for reduced precision errors
    +    if first call
    +      initialize qh.input_points to the original input points
    +      if qh.JOGGLEmax == 0
    +        determine default qh.JOGGLEmax
    +    else
    +      increase qh.JOGGLEmax according to qh.build_cnt
    +    joggle the input by adding a random number in [-qh.JOGGLEmax,qh.JOGGLEmax]
    +    if qh.DELAUNAY
    +      sets the Delaunay projection
    +*/
    +void qh_joggleinput(qhT *qh) {
    +  int i, seed, size;
    +  coordT *coordp, *inputp;
    +  realT randr, randa, randb;
    +
    +  if (!qh->input_points) { /* first call */
    +    qh->input_points= qh->first_point;
    +    qh->input_malloc= qh->POINTSmalloc;
    +    size= qh->num_points * qh->hull_dim * sizeof(coordT);
    +    if (!(qh->first_point=(coordT*)qh_malloc((size_t)size))) {
    +      qh_fprintf(qh, qh->ferr, 6009, "qhull error: insufficient memory to joggle %d points\n",
    +          qh->num_points);
    +      qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +    }
    +    qh->POINTSmalloc= True;
    +    if (qh->JOGGLEmax == 0.0) {
    +      qh->JOGGLEmax= qh_detjoggle(qh, qh->input_points, qh->num_points, qh->hull_dim);
    +      qh_option(qh, "QJoggle", NULL, &qh->JOGGLEmax);
    +    }
    +  }else {                 /* repeated call */
    +    if (!qh->RERUN && qh->build_cnt > qh_JOGGLEretry) {
    +      if (((qh->build_cnt-qh_JOGGLEretry-1) % qh_JOGGLEagain) == 0) {
    +        realT maxjoggle= qh->MAXwidth * qh_JOGGLEmaxincrease;
    +        if (qh->JOGGLEmax < maxjoggle) {
    +          qh->JOGGLEmax *= qh_JOGGLEincrease;
    +          minimize_(qh->JOGGLEmax, maxjoggle);
    +        }
    +      }
    +    }
    +    qh_option(qh, "QJoggle", NULL, &qh->JOGGLEmax);
    +  }
    +  if (qh->build_cnt > 1 && qh->JOGGLEmax > fmax_(qh->MAXwidth/4, 0.1)) {
    +      qh_fprintf(qh, qh->ferr, 6010, "qhull error: the current joggle for 'QJn', %.2g, is too large for the width\nof the input.  If possible, recompile Qhull with higher-precision reals.\n",
    +                qh->JOGGLEmax);
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  /* for some reason, using qh->ROTATErandom and qh_RANDOMseed does not repeat the run. Use 'TRn' instead */
    +  seed= qh_RANDOMint;
    +  qh_option(qh, "_joggle-seed", &seed, NULL);
    +  trace0((qh, qh->ferr, 6, "qh_joggleinput: joggle input by %2.2g with seed %d\n",
    +    qh->JOGGLEmax, seed));
    +  inputp= qh->input_points;
    +  coordp= qh->first_point;
    +  randa= 2.0 * qh->JOGGLEmax/qh_RANDOMmax;
    +  randb= -qh->JOGGLEmax;
    +  size= qh->num_points * qh->hull_dim;
    +  for (i=size; i--; ) {
    +    randr= qh_RANDOMint;
    +    *(coordp++)= *(inputp++) + (randr * randa + randb);
    +  }
    +  if (qh->DELAUNAY) {
    +    qh->last_low= qh->last_high= qh->last_newhigh= REALmax;
    +    qh_setdelaunay(qh, qh->hull_dim, qh->num_points, qh->first_point);
    +  }
    +} /* joggleinput */
    +
    +/*---------------------------------
    +
    +  qh_maxabsval( normal, dim )
    +    return pointer to maximum absolute value of a dim vector
    +    returns NULL if dim=0
    +*/
    +realT *qh_maxabsval(realT *normal, int dim) {
    +  realT maxval= -REALmax;
    +  realT *maxp= NULL, *colp, absval;
    +  int k;
    +
    +  for (k=dim, colp= normal; k--; colp++) {
    +    absval= fabs_(*colp);
    +    if (absval > maxval) {
    +      maxval= absval;
    +      maxp= colp;
    +    }
    +  }
    +  return maxp;
    +} /* maxabsval */
    +
    +
    +/*---------------------------------
    +
    +  qh_maxmin(qh, points, numpoints, dimension )
    +    return max/min points for each dimension
    +    determine max and min coordinates
    +
    +  returns:
    +    returns a temporary set of max and min points
    +      may include duplicate points. Does not include qh.GOODpoint
    +    sets qh.NEARzero, qh.MAXabs_coord, qh.MAXsumcoord, qh.MAXwidth
    +         qh.MAXlastcoord, qh.MINlastcoord
    +    initializes qh.max_outside, qh.min_vertex, qh.WAScoplanar, qh.ZEROall_ok
    +
    +  notes:
    +    loop duplicated in qh_detjoggle()
    +
    +  design:
    +    initialize global precision variables
    +    checks definition of REAL...
    +    for each dimension
    +      for each point
    +        collect maximum and minimum point
    +      collect maximum of maximums and minimum of minimums
    +      determine qh.NEARzero for Gaussian Elimination
    +*/
    +setT *qh_maxmin(qhT *qh, pointT *points, int numpoints, int dimension) {
    +  int k;
    +  realT maxcoord, temp;
    +  pointT *minimum, *maximum, *point, *pointtemp;
    +  setT *set;
    +
    +  qh->max_outside= 0.0;
    +  qh->MAXabs_coord= 0.0;
    +  qh->MAXwidth= -REALmax;
    +  qh->MAXsumcoord= 0.0;
    +  qh->min_vertex= 0.0;
    +  qh->WAScoplanar= False;
    +  if (qh->ZEROcentrum)
    +    qh->ZEROall_ok= True;
    +  if (REALmin < REALepsilon && REALmin < REALmax && REALmin > -REALmax
    +  && REALmax > 0.0 && -REALmax < 0.0)
    +    ; /* all ok */
    +  else {
    +    qh_fprintf(qh, qh->ferr, 6011, "qhull error: floating point constants in user.h are wrong\n\
    +REALepsilon %g REALmin %g REALmax %g -REALmax %g\n",
    +             REALepsilon, REALmin, REALmax, -REALmax);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  set= qh_settemp(qh, 2*dimension);
    +  for (k=0; k < dimension; k++) {
    +    if (points == qh->GOODpointp)
    +      minimum= maximum= points + dimension;
    +    else
    +      minimum= maximum= points;
    +    FORALLpoint_(qh, points, numpoints) {
    +      if (point == qh->GOODpointp)
    +        continue;
    +      if (maximum[k] < point[k])
    +        maximum= point;
    +      else if (minimum[k] > point[k])
    +        minimum= point;
    +    }
    +    if (k == dimension-1) {
    +      qh->MINlastcoord= minimum[k];
    +      qh->MAXlastcoord= maximum[k];
    +    }
    +    if (qh->SCALElast && k == dimension-1)
    +      maxcoord= qh->MAXwidth;
    +    else {
    +      maxcoord= fmax_(maximum[k], -minimum[k]);
    +      if (qh->GOODpointp) {
    +        temp= fmax_(qh->GOODpointp[k], -qh->GOODpointp[k]);
    +        maximize_(maxcoord, temp);
    +      }
    +      temp= maximum[k] - minimum[k];
    +      maximize_(qh->MAXwidth, temp);
    +    }
    +    maximize_(qh->MAXabs_coord, maxcoord);
    +    qh->MAXsumcoord += maxcoord;
    +    qh_setappend(qh, &set, maximum);
    +    qh_setappend(qh, &set, minimum);
    +    /* calculation of qh NEARzero is based on Golub & van Loan, 1983,
    +       Eq. 4.4-13 for "Gaussian elimination with complete pivoting".
    +       Golub & van Loan say that n^3 can be ignored and 10 be used in
    +       place of rho */
    +    qh->NEARzero[k]= 80 * qh->MAXsumcoord * REALepsilon;
    +  }
    +  if (qh->IStracing >=1)
    +    qh_printpoints(qh, qh->ferr, "qh_maxmin: found the max and min points(by dim):", set);
    +  return(set);
    +} /* maxmin */
    +
    +/*---------------------------------
    +
    +  qh_maxouter(qh)
    +    return maximum distance from facet to outer plane
    +    normally this is qh.max_outside+qh.DISTround
    +    does not include qh.JOGGLEmax
    +
    +  see:
    +    qh_outerinner()
    +
    +  notes:
    +    need to add another qh.DISTround if testing actual point with computation
    +
    +  for joggle:
    +    qh_setfacetplane() updated qh.max_outer for Wnewvertexmax (max distance to vertex)
    +    need to use Wnewvertexmax since could have a coplanar point for a high
    +      facet that is replaced by a low facet
    +    need to add qh.JOGGLEmax if testing input points
    +*/
    +realT qh_maxouter(qhT *qh) {
    +  realT dist;
    +
    +  dist= fmax_(qh->max_outside, qh->DISTround);
    +  dist += qh->DISTround;
    +  trace4((qh, qh->ferr, 4012, "qh_maxouter: max distance from facet to outer plane is %2.2g max_outside is %2.2g\n", dist, qh->max_outside));
    +  return dist;
    +} /* maxouter */
    +
    +/*---------------------------------
    +
    +  qh_maxsimplex(qh, dim, maxpoints, points, numpoints, simplex )
    +    determines maximum simplex for a set of points
    +    starts from points already in simplex
    +    skips qh.GOODpointp (assumes that it isn't in maxpoints)
    +
    +  returns:
    +    simplex with dim+1 points
    +
    +  notes:
    +    assumes at least pointsneeded points in points
    +    maximizes determinate for x,y,z,w, etc.
    +    uses maxpoints as long as determinate is clearly non-zero
    +
    +  design:
    +    initialize simplex with at least two points
    +      (find points with max or min x coordinate)
    +    for each remaining dimension
    +      add point that maximizes the determinate
    +        (use points from maxpoints first)
    +*/
    +void qh_maxsimplex(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints, setT **simplex) {
    +  pointT *point, **pointp, *pointtemp, *maxpoint, *minx=NULL, *maxx=NULL;
    +  boolT nearzero, maxnearzero= False;
    +  int k, sizinit;
    +  realT maxdet= -REALmax, det, mincoord= REALmax, maxcoord= -REALmax;
    +
    +  sizinit= qh_setsize(qh, *simplex);
    +  if (sizinit < 2) {
    +    if (qh_setsize(qh, maxpoints) >= 2) {
    +      FOREACHpoint_(maxpoints) {
    +        if (maxcoord < point[0]) {
    +          maxcoord= point[0];
    +          maxx= point;
    +        }
    +        if (mincoord > point[0]) {
    +          mincoord= point[0];
    +          minx= point;
    +        }
    +      }
    +    }else {
    +      FORALLpoint_(qh, points, numpoints) {
    +        if (point == qh->GOODpointp)
    +          continue;
    +        if (maxcoord < point[0]) {
    +          maxcoord= point[0];
    +          maxx= point;
    +        }
    +        if (mincoord > point[0]) {
    +          mincoord= point[0];
    +          minx= point;
    +        }
    +      }
    +    }
    +    qh_setunique(qh, simplex, minx);
    +    if (qh_setsize(qh, *simplex) < 2)
    +      qh_setunique(qh, simplex, maxx);
    +    sizinit= qh_setsize(qh, *simplex);
    +    if (sizinit < 2) {
    +      qh_precision(qh, "input has same x coordinate");
    +      if (zzval_(Zsetplane) > qh->hull_dim+1) {
    +        qh_fprintf(qh, qh->ferr, 6012, "qhull precision error (qh_maxsimplex for voronoi_center):\n%d points with the same x coordinate.\n",
    +                 qh_setsize(qh, maxpoints)+numpoints);
    +        qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +      }else {
    +        qh_fprintf(qh, qh->ferr, 6013, "qhull input error: input is less than %d-dimensional since it has the same x coordinate\n", qh->hull_dim);
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +    }
    +  }
    +  for (k=sizinit; k < dim+1; k++) {
    +    maxpoint= NULL;
    +    maxdet= -REALmax;
    +    FOREACHpoint_(maxpoints) {
    +      if (!qh_setin(*simplex, point)) {
    +        det= qh_detsimplex(qh, point, *simplex, k, &nearzero);
    +        if ((det= fabs_(det)) > maxdet) {
    +          maxdet= det;
    +          maxpoint= point;
    +          maxnearzero= nearzero;
    +        }
    +      }
    +    }
    +    if (!maxpoint || maxnearzero) {
    +      zinc_(Zsearchpoints);
    +      if (!maxpoint) {
    +        trace0((qh, qh->ferr, 7, "qh_maxsimplex: searching all points for %d-th initial vertex.\n", k+1));
    +      }else {
    +        trace0((qh, qh->ferr, 8, "qh_maxsimplex: searching all points for %d-th initial vertex, better than p%d det %2.2g\n",
    +                k+1, qh_pointid(qh, maxpoint), maxdet));
    +      }
    +      FORALLpoint_(qh, points, numpoints) {
    +        if (point == qh->GOODpointp)
    +          continue;
    +        if (!qh_setin(*simplex, point)) {
    +          det= qh_detsimplex(qh, point, *simplex, k, &nearzero);
    +          if ((det= fabs_(det)) > maxdet) {
    +            maxdet= det;
    +            maxpoint= point;
    +            maxnearzero= nearzero;
    +          }
    +        }
    +      }
    +    } /* !maxpoint */
    +    if (!maxpoint) {
    +      qh_fprintf(qh, qh->ferr, 6014, "qhull internal error (qh_maxsimplex): not enough points available\n");
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +    qh_setappend(qh, simplex, maxpoint);
    +    trace1((qh, qh->ferr, 1002, "qh_maxsimplex: selected point p%d for %d`th initial vertex, det=%2.2g\n",
    +            qh_pointid(qh, maxpoint), k+1, maxdet));
    +  } /* k */
    +} /* maxsimplex */
    +
    +/*---------------------------------
    +
    +  qh_minabsval( normal, dim )
    +    return minimum absolute value of a dim vector
    +*/
    +realT qh_minabsval(realT *normal, int dim) {
    +  realT minval= 0;
    +  realT maxval= 0;
    +  realT *colp;
    +  int k;
    +
    +  for (k=dim, colp=normal; k--; colp++) {
    +    maximize_(maxval, *colp);
    +    minimize_(minval, *colp);
    +  }
    +  return fmax_(maxval, -minval);
    +} /* minabsval */
    +
    +
    +/*---------------------------------
    +
    +  qh_mindif(qh, vecA, vecB, dim )
    +    return index of min abs. difference of two vectors
    +*/
    +int qh_mindiff(realT *vecA, realT *vecB, int dim) {
    +  realT mindiff= REALmax, diff;
    +  realT *vecAp= vecA, *vecBp= vecB;
    +  int k, mink= 0;
    +
    +  for (k=0; k < dim; k++) {
    +    diff= *vecAp++ - *vecBp++;
    +    diff= fabs_(diff);
    +    if (diff < mindiff) {
    +      mindiff= diff;
    +      mink= k;
    +    }
    +  }
    +  return mink;
    +} /* mindiff */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_orientoutside(qh, facet  )
    +    make facet outside oriented via qh.interior_point
    +
    +  returns:
    +    True if facet reversed orientation.
    +*/
    +boolT qh_orientoutside(qhT *qh, facetT *facet) {
    +  int k;
    +  realT dist;
    +
    +  qh_distplane(qh, qh->interior_point, facet, &dist);
    +  if (dist > 0) {
    +    for (k=qh->hull_dim; k--; )
    +      facet->normal[k]= -facet->normal[k];
    +    facet->offset= -facet->offset;
    +    return True;
    +  }
    +  return False;
    +} /* orientoutside */
    +
    +/*---------------------------------
    +
    +  qh_outerinner(qh, facet, outerplane, innerplane  )
    +    if facet and qh.maxoutdone (i.e., qh_check_maxout)
    +      returns outer and inner plane for facet
    +    else
    +      returns maximum outer and inner plane
    +    accounts for qh.JOGGLEmax
    +
    +  see:
    +    qh_maxouter(qh), qh_check_bestdist(), qh_check_points()
    +
    +  notes:
    +    outerplaner or innerplane may be NULL
    +    facet is const
    +    Does not error (QhullFacet)
    +
    +    includes qh.DISTround for actual points
    +    adds another qh.DISTround if testing with floating point arithmetic
    +*/
    +void qh_outerinner(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane) {
    +  realT dist, mindist;
    +  vertexT *vertex, **vertexp;
    +
    +  if (outerplane) {
    +    if (!qh_MAXoutside || !facet || !qh->maxoutdone) {
    +      *outerplane= qh_maxouter(qh);       /* includes qh.DISTround */
    +    }else { /* qh_MAXoutside ... */
    +#if qh_MAXoutside
    +      *outerplane= facet->maxoutside + qh->DISTround;
    +#endif
    +
    +    }
    +    if (qh->JOGGLEmax < REALmax/2)
    +      *outerplane += qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
    +  }
    +  if (innerplane) {
    +    if (facet) {
    +      mindist= REALmax;
    +      FOREACHvertex_(facet->vertices) {
    +        zinc_(Zdistio);
    +        qh_distplane(qh, vertex->point, facet, &dist);
    +        minimize_(mindist, dist);
    +      }
    +      *innerplane= mindist - qh->DISTround;
    +    }else
    +      *innerplane= qh->min_vertex - qh->DISTround;
    +    if (qh->JOGGLEmax < REALmax/2)
    +      *innerplane -= qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
    +  }
    +} /* outerinner */
    +
    +/*---------------------------------
    +
    +  qh_pointdist( point1, point2, dim )
    +    return distance between two points
    +
    +  notes:
    +    returns distance squared if 'dim' is negative
    +*/
    +coordT qh_pointdist(pointT *point1, pointT *point2, int dim) {
    +  coordT dist, diff;
    +  int k;
    +
    +  dist= 0.0;
    +  for (k= (dim > 0 ? dim : -dim); k--; ) {
    +    diff= *point1++ - *point2++;
    +    dist += diff * diff;
    +  }
    +  if (dim > 0)
    +    return(sqrt(dist));
    +  return dist;
    +} /* pointdist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printmatrix(qh, fp, string, rows, numrow, numcol )
    +    print matrix to fp given by row vectors
    +    print string as header
    +    qh may be NULL if fp is defined
    +
    +  notes:
    +    print a vector by qh_printmatrix(qh, fp, "", &vect, 1, len)
    +*/
    +void qh_printmatrix(qhT *qh, FILE *fp, const char *string, realT **rows, int numrow, int numcol) {
    +  realT *rowp;
    +  realT r; /*bug fix*/
    +  int i,k;
    +
    +  qh_fprintf(qh, fp, 9001, "%s\n", string);
    +  for (i=0; i < numrow; i++) {
    +    rowp= rows[i];
    +    for (k=0; k < numcol; k++) {
    +      r= *rowp++;
    +      qh_fprintf(qh, fp, 9002, "%6.3g ", r);
    +    }
    +    qh_fprintf(qh, fp, 9003, "\n");
    +  }
    +} /* printmatrix */
    +
    +
    +/*---------------------------------
    +
    +  qh_printpoints(qh, fp, string, points )
    +    print pointids to fp for a set of points
    +    if string, prints string and 'p' point ids
    +*/
    +void qh_printpoints(qhT *qh, FILE *fp, const char *string, setT *points) {
    +  pointT *point, **pointp;
    +
    +  if (string) {
    +    qh_fprintf(qh, fp, 9004, "%s", string);
    +    FOREACHpoint_(points)
    +      qh_fprintf(qh, fp, 9005, " p%d", qh_pointid(qh, point));
    +    qh_fprintf(qh, fp, 9006, "\n");
    +  }else {
    +    FOREACHpoint_(points)
    +      qh_fprintf(qh, fp, 9007, " %d", qh_pointid(qh, point));
    +    qh_fprintf(qh, fp, 9008, "\n");
    +  }
    +} /* printpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectinput(qh)
    +    project input points using qh.lower_bound/upper_bound and qh->DELAUNAY
    +    if qh.lower_bound[k]=qh.upper_bound[k]= 0,
    +      removes dimension k
    +    if halfspace intersection
    +      removes dimension k from qh.feasible_point
    +    input points in qh->first_point, num_points, input_dim
    +
    +  returns:
    +    new point array in qh->first_point of qh->hull_dim coordinates
    +    sets qh->POINTSmalloc
    +    if qh->DELAUNAY
    +      projects points to paraboloid
    +      lowbound/highbound is also projected
    +    if qh->ATinfinity
    +      adds point "at-infinity"
    +    if qh->POINTSmalloc
    +      frees old point array
    +
    +  notes:
    +    checks that qh.hull_dim agrees with qh.input_dim, PROJECTinput, and DELAUNAY
    +
    +
    +  design:
    +    sets project[k] to -1 (delete), 0 (keep), 1 (add for Delaunay)
    +    determines newdim and newnum for qh->hull_dim and qh->num_points
    +    projects points to newpoints
    +    projects qh.lower_bound to itself
    +    projects qh.upper_bound to itself
    +    if qh->DELAUNAY
    +      if qh->ATINFINITY
    +        projects points to paraboloid
    +        computes "infinity" point as vertex average and 10% above all points
    +      else
    +        uses qh_setdelaunay to project points to paraboloid
    +*/
    +void qh_projectinput(qhT *qh) {
    +  int k,i;
    +  int newdim= qh->input_dim, newnum= qh->num_points;
    +  signed char *project;
    +  int projectsize= (qh->input_dim+1)*sizeof(*project);
    +  pointT *newpoints, *coord, *infinity;
    +  realT paraboloid, maxboloid= 0;
    +
    +  project= (signed char*)qh_memalloc(qh, projectsize);
    +  memset((char*)project, 0, (size_t)projectsize);
    +  for (k=0; k < qh->input_dim; k++) {   /* skip Delaunay bound */
    +    if (qh->lower_bound[k] == 0 && qh->upper_bound[k] == 0) {
    +      project[k]= -1;
    +      newdim--;
    +    }
    +  }
    +  if (qh->DELAUNAY) {
    +    project[k]= 1;
    +    newdim++;
    +    if (qh->ATinfinity)
    +      newnum++;
    +  }
    +  if (newdim != qh->hull_dim) {
    +    qh_memfree(qh, project, projectsize);
    +    qh_fprintf(qh, qh->ferr, 6015, "qhull internal error (qh_projectinput): dimension after projection %d != hull_dim %d\n", newdim, qh->hull_dim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  if (!(newpoints= qh->temp_malloc= (coordT*)qh_malloc(newnum*newdim*sizeof(coordT)))){
    +    qh_memfree(qh, project, projectsize);
    +    qh_fprintf(qh, qh->ferr, 6016, "qhull error: insufficient memory to project %d points\n",
    +           qh->num_points);
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  /* qh_projectpoints throws error if mismatched dimensions */
    +  qh_projectpoints(qh, project, qh->input_dim+1, qh->first_point,
    +                    qh->num_points, qh->input_dim, newpoints, newdim);
    +  trace1((qh, qh->ferr, 1003, "qh_projectinput: updating lower and upper_bound\n"));
    +  qh_projectpoints(qh, project, qh->input_dim+1, qh->lower_bound,
    +                    1, qh->input_dim+1, qh->lower_bound, newdim+1);
    +  qh_projectpoints(qh, project, qh->input_dim+1, qh->upper_bound,
    +                    1, qh->input_dim+1, qh->upper_bound, newdim+1);
    +  if (qh->HALFspace) {
    +    if (!qh->feasible_point) {
    +      qh_memfree(qh, project, projectsize);
    +      qh_fprintf(qh, qh->ferr, 6017, "qhull internal error (qh_projectinput): HALFspace defined without qh.feasible_point\n");
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +    qh_projectpoints(qh, project, qh->input_dim, qh->feasible_point,
    +                      1, qh->input_dim, qh->feasible_point, newdim);
    +  }
    +  qh_memfree(qh, project, projectsize);
    +  if (qh->POINTSmalloc)
    +    qh_free(qh->first_point);
    +  qh->first_point= newpoints;
    +  qh->POINTSmalloc= True;
    +  qh->temp_malloc= NULL;
    +  if (qh->DELAUNAY && qh->ATinfinity) {
    +    coord= qh->first_point;
    +    infinity= qh->first_point + qh->hull_dim * qh->num_points;
    +    for (k=qh->hull_dim-1; k--; )
    +      infinity[k]= 0.0;
    +    for (i=qh->num_points; i--; ) {
    +      paraboloid= 0.0;
    +      for (k=0; k < qh->hull_dim-1; k++) {
    +        paraboloid += *coord * *coord;
    +        infinity[k] += *coord;
    +        coord++;
    +      }
    +      *(coord++)= paraboloid;
    +      maximize_(maxboloid, paraboloid);
    +    }
    +    /* coord == infinity */
    +    for (k=qh->hull_dim-1; k--; )
    +      *(coord++) /= qh->num_points;
    +    *(coord++)= maxboloid * 1.1;
    +    qh->num_points++;
    +    trace0((qh, qh->ferr, 9, "qh_projectinput: projected points to paraboloid for Delaunay\n"));
    +  }else if (qh->DELAUNAY)  /* !qh->ATinfinity */
    +    qh_setdelaunay(qh, qh->hull_dim, qh->num_points, qh->first_point);
    +} /* projectinput */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectpoints(qh, project, n, points, numpoints, dim, newpoints, newdim )
    +    project points/numpoints/dim to newpoints/newdim
    +    if project[k] == -1
    +      delete dimension k
    +    if project[k] == 1
    +      add dimension k by duplicating previous column
    +    n is size of project
    +
    +  notes:
    +    newpoints may be points if only adding dimension at end
    +
    +  design:
    +    check that 'project' and 'newdim' agree
    +    for each dimension
    +      if project == -1
    +        skip dimension
    +      else
    +        determine start of column in newpoints
    +        determine start of column in points
    +          if project == +1, duplicate previous column
    +        copy dimension (column) from points to newpoints
    +*/
    +void qh_projectpoints(qhT *qh, signed char *project, int n, realT *points,
    +        int numpoints, int dim, realT *newpoints, int newdim) {
    +  int testdim= dim, oldk=0, newk=0, i,j=0,k;
    +  realT *newp, *oldp;
    +
    +  for (k=0; k < n; k++)
    +    testdim += project[k];
    +  if (testdim != newdim) {
    +    qh_fprintf(qh, qh->ferr, 6018, "qhull internal error (qh_projectpoints): newdim %d should be %d after projection\n",
    +      newdim, testdim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  for (j=0; j= dim)
    +          continue;
    +        oldp= points+oldk;
    +      }else
    +        oldp= points+oldk++;
    +      for (i=numpoints; i--; ) {
    +        *newp= *oldp;
    +        newp += newdim;
    +        oldp += dim;
    +      }
    +    }
    +    if (oldk >= dim)
    +      break;
    +  }
    +  trace1((qh, qh->ferr, 1004, "qh_projectpoints: projected %d points from dim %d to dim %d\n",
    +    numpoints, dim, newdim));
    +} /* projectpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_rotateinput(qh, rows )
    +    rotate input using row matrix
    +    input points given by qh->first_point, num_points, hull_dim
    +    assumes rows[dim] is a scratch buffer
    +    if qh->POINTSmalloc, overwrites input points, else mallocs a new array
    +
    +  returns:
    +    rotated input
    +    sets qh->POINTSmalloc
    +
    +  design:
    +    see qh_rotatepoints
    +*/
    +void qh_rotateinput(qhT *qh, realT **rows) {
    +
    +  if (!qh->POINTSmalloc) {
    +    qh->first_point= qh_copypoints(qh, qh->first_point, qh->num_points, qh->hull_dim);
    +    qh->POINTSmalloc= True;
    +  }
    +  qh_rotatepoints(qh, qh->first_point, qh->num_points, qh->hull_dim, rows);
    +}  /* rotateinput */
    +
    +/*---------------------------------
    +
    +  qh_rotatepoints(qh, points, numpoints, dim, row )
    +    rotate numpoints points by a d-dim row matrix
    +    assumes rows[dim] is a scratch buffer
    +
    +  returns:
    +    rotated points in place
    +
    +  design:
    +    for each point
    +      for each coordinate
    +        use row[dim] to compute partial inner product
    +      for each coordinate
    +        rotate by partial inner product
    +*/
    +void qh_rotatepoints(qhT *qh, realT *points, int numpoints, int dim, realT **row) {
    +  realT *point, *rowi, *coord= NULL, sum, *newval;
    +  int i,j,k;
    +
    +  if (qh->IStracing >= 1)
    +    qh_printmatrix(qh, qh->ferr, "qh_rotatepoints: rotate points by", row, dim, dim);
    +  for (point= points, j= numpoints; j--; point += dim) {
    +    newval= row[dim];
    +    for (i=0; i < dim; i++) {
    +      rowi= row[i];
    +      coord= point;
    +      for (sum= 0.0, k= dim; k--; )
    +        sum += *rowi++ * *coord++;
    +      *(newval++)= sum;
    +    }
    +    for (k=dim; k--; )
    +      *(--coord)= *(--newval);
    +  }
    +} /* rotatepoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_scaleinput(qh)
    +    scale input points using qh->low_bound/high_bound
    +    input points given by qh->first_point, num_points, hull_dim
    +    if qh->POINTSmalloc, overwrites input points, else mallocs a new array
    +
    +  returns:
    +    scales coordinates of points to low_bound[k], high_bound[k]
    +    sets qh->POINTSmalloc
    +
    +  design:
    +    see qh_scalepoints
    +*/
    +void qh_scaleinput(qhT *qh) {
    +
    +  if (!qh->POINTSmalloc) {
    +    qh->first_point= qh_copypoints(qh, qh->first_point, qh->num_points, qh->hull_dim);
    +    qh->POINTSmalloc= True;
    +  }
    +  qh_scalepoints(qh, qh->first_point, qh->num_points, qh->hull_dim,
    +       qh->lower_bound, qh->upper_bound);
    +}  /* scaleinput */
    +
    +/*---------------------------------
    +
    +  qh_scalelast(qh, points, numpoints, dim, low, high, newhigh )
    +    scale last coordinate to [0,m] for Delaunay triangulations
    +    input points given by points, numpoints, dim
    +
    +  returns:
    +    changes scale of last coordinate from [low, high] to [0, newhigh]
    +    overwrites last coordinate of each point
    +    saves low/high/newhigh in qh.last_low, etc. for qh_setdelaunay()
    +
    +  notes:
    +    when called by qh_setdelaunay, low/high may not match actual data
    +
    +  design:
    +    compute scale and shift factors
    +    apply to last coordinate of each point
    +*/
    +void qh_scalelast(qhT *qh, coordT *points, int numpoints, int dim, coordT low,
    +                   coordT high, coordT newhigh) {
    +  realT scale, shift;
    +  coordT *coord;
    +  int i;
    +  boolT nearzero= False;
    +
    +  trace4((qh, qh->ferr, 4013, "qh_scalelast: scale last coordinate from [%2.2g, %2.2g] to [0,%2.2g]\n",
    +    low, high, newhigh));
    +  qh->last_low= low;
    +  qh->last_high= high;
    +  qh->last_newhigh= newhigh;
    +  scale= qh_divzero(newhigh, high - low,
    +                  qh->MINdenom_1, &nearzero);
    +  if (nearzero) {
    +    if (qh->DELAUNAY)
    +      qh_fprintf(qh, qh->ferr, 6019, "qhull input error: can not scale last coordinate.  Input is cocircular\n   or cospherical.   Use option 'Qz' to add a point at infinity.\n");
    +    else
    +      qh_fprintf(qh, qh->ferr, 6020, "qhull input error: can not scale last coordinate.  New bounds [0, %2.2g] are too wide for\nexisting bounds [%2.2g, %2.2g] (width %2.2g)\n",
    +                newhigh, low, high, high-low);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  shift= - low * newhigh / (high-low);
    +  coord= points + dim - 1;
    +  for (i=numpoints; i--; coord += dim)
    +    *coord= *coord * scale + shift;
    +} /* scalelast */
    +
    +/*---------------------------------
    +
    +  qh_scalepoints(qh, points, numpoints, dim, newlows, newhighs )
    +    scale points to new lowbound and highbound
    +    retains old bound when newlow= -REALmax or newhigh= +REALmax
    +
    +  returns:
    +    scaled points
    +    overwrites old points
    +
    +  design:
    +    for each coordinate
    +      compute current low and high bound
    +      compute scale and shift factors
    +      scale all points
    +      enforce new low and high bound for all points
    +*/
    +void qh_scalepoints(qhT *qh, pointT *points, int numpoints, int dim,
    +        realT *newlows, realT *newhighs) {
    +  int i,k;
    +  realT shift, scale, *coord, low, high, newlow, newhigh, mincoord, maxcoord;
    +  boolT nearzero= False;
    +
    +  for (k=0; k < dim; k++) {
    +    newhigh= newhighs[k];
    +    newlow= newlows[k];
    +    if (newhigh > REALmax/2 && newlow < -REALmax/2)
    +      continue;
    +    low= REALmax;
    +    high= -REALmax;
    +    for (i=numpoints, coord=points+k; i--; coord += dim) {
    +      minimize_(low, *coord);
    +      maximize_(high, *coord);
    +    }
    +    if (newhigh > REALmax/2)
    +      newhigh= high;
    +    if (newlow < -REALmax/2)
    +      newlow= low;
    +    if (qh->DELAUNAY && k == dim-1 && newhigh < newlow) {
    +      qh_fprintf(qh, qh->ferr, 6021, "qhull input error: 'Qb%d' or 'QB%d' inverts paraboloid since high bound %.2g < low bound %.2g\n",
    +               k, k, newhigh, newlow);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    scale= qh_divzero(newhigh - newlow, high - low,
    +                  qh->MINdenom_1, &nearzero);
    +    if (nearzero) {
    +      qh_fprintf(qh, qh->ferr, 6022, "qhull input error: %d'th dimension's new bounds [%2.2g, %2.2g] too wide for\nexisting bounds [%2.2g, %2.2g]\n",
    +              k, newlow, newhigh, low, high);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    shift= (newlow * high - low * newhigh)/(high-low);
    +    coord= points+k;
    +    for (i=numpoints; i--; coord += dim)
    +      *coord= *coord * scale + shift;
    +    coord= points+k;
    +    if (newlow < newhigh) {
    +      mincoord= newlow;
    +      maxcoord= newhigh;
    +    }else {
    +      mincoord= newhigh;
    +      maxcoord= newlow;
    +    }
    +    for (i=numpoints; i--; coord += dim) {
    +      minimize_(*coord, maxcoord);  /* because of roundoff error */
    +      maximize_(*coord, mincoord);
    +    }
    +    trace0((qh, qh->ferr, 10, "qh_scalepoints: scaled %d'th coordinate [%2.2g, %2.2g] to [%.2g, %.2g] for %d points by %2.2g and shifted %2.2g\n",
    +      k, low, high, newlow, newhigh, numpoints, scale, shift));
    +  }
    +} /* scalepoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelaunay(qh, dim, count, points )
    +    project count points to dim-d paraboloid for Delaunay triangulation
    +
    +    dim is one more than the dimension of the input set
    +    assumes dim is at least 3 (i.e., at least a 2-d Delaunay triangulation)
    +
    +    points is a dim*count realT array.  The first dim-1 coordinates
    +    are the coordinates of the first input point.  array[dim] is
    +    the first coordinate of the second input point.  array[2*dim] is
    +    the first coordinate of the third input point.
    +
    +    if qh.last_low defined (i.e., 'Qbb' called qh_scalelast)
    +      calls qh_scalelast to scale the last coordinate the same as the other points
    +
    +  returns:
    +    for each point
    +      sets point[dim-1] to sum of squares of coordinates
    +    scale points to 'Qbb' if needed
    +
    +  notes:
    +    to project one point, use
    +      qh_setdelaunay(qh, qh->hull_dim, 1, point)
    +
    +    Do not use options 'Qbk', 'QBk', or 'QbB' since they scale
    +    the coordinates after the original projection.
    +
    +*/
    +void qh_setdelaunay(qhT *qh, int dim, int count, pointT *points) {
    +  int i, k;
    +  coordT *coordp, coord;
    +  realT paraboloid;
    +
    +  trace0((qh, qh->ferr, 11, "qh_setdelaunay: project %d points to paraboloid for Delaunay triangulation\n", count));
    +  coordp= points;
    +  for (i=0; i < count; i++) {
    +    coord= *coordp++;
    +    paraboloid= coord*coord;
    +    for (k=dim-2; k--; ) {
    +      coord= *coordp++;
    +      paraboloid += coord*coord;
    +    }
    +    *coordp++ = paraboloid;
    +  }
    +  if (qh->last_low < REALmax/2)
    +    qh_scalelast(qh, points, count, dim, qh->last_low, qh->last_high, qh->last_newhigh);
    +} /* setdelaunay */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethalfspace(qh, dim, coords, nextp, normal, offset, feasible )
    +    set point to dual of halfspace relative to feasible point
    +    halfspace is normal coefficients and offset.
    +
    +  returns:
    +    false and prints error if feasible point is outside of hull
    +    overwrites coordinates for point at dim coords
    +    nextp= next point (coords)
    +    does not call qh_errexit
    +
    +  design:
    +    compute distance from feasible point to halfspace
    +    divide each normal coefficient by -dist
    +*/
    +boolT qh_sethalfspace(qhT *qh, int dim, coordT *coords, coordT **nextp,
    +         coordT *normal, coordT *offset, coordT *feasible) {
    +  coordT *normp= normal, *feasiblep= feasible, *coordp= coords;
    +  realT dist;
    +  realT r; /*bug fix*/
    +  int k;
    +  boolT zerodiv;
    +
    +  dist= *offset;
    +  for (k=dim; k--; )
    +    dist += *(normp++) * *(feasiblep++);
    +  if (dist > 0)
    +    goto LABELerroroutside;
    +  normp= normal;
    +  if (dist < -qh->MINdenom) {
    +    for (k=dim; k--; )
    +      *(coordp++)= *(normp++) / -dist;
    +  }else {
    +    for (k=dim; k--; ) {
    +      *(coordp++)= qh_divzero(*(normp++), -dist, qh->MINdenom_1, &zerodiv);
    +      if (zerodiv)
    +        goto LABELerroroutside;
    +    }
    +  }
    +  *nextp= coordp;
    +  if (qh->IStracing >= 4) {
    +    qh_fprintf(qh, qh->ferr, 8021, "qh_sethalfspace: halfspace at offset %6.2g to point: ", *offset);
    +    for (k=dim, coordp=coords; k--; ) {
    +      r= *coordp++;
    +      qh_fprintf(qh, qh->ferr, 8022, " %6.2g", r);
    +    }
    +    qh_fprintf(qh, qh->ferr, 8023, "\n");
    +  }
    +  return True;
    +LABELerroroutside:
    +  feasiblep= feasible;
    +  normp= normal;
    +  qh_fprintf(qh, qh->ferr, 6023, "qhull input error: feasible point is not clearly inside halfspace\nfeasible point: ");
    +  for (k=dim; k--; )
    +    qh_fprintf(qh, qh->ferr, 8024, qh_REAL_1, r=*(feasiblep++));
    +  qh_fprintf(qh, qh->ferr, 8025, "\n     halfspace: ");
    +  for (k=dim; k--; )
    +    qh_fprintf(qh, qh->ferr, 8026, qh_REAL_1, r=*(normp++));
    +  qh_fprintf(qh, qh->ferr, 8027, "\n     at offset: ");
    +  qh_fprintf(qh, qh->ferr, 8028, qh_REAL_1, *offset);
    +  qh_fprintf(qh, qh->ferr, 8029, " and distance: ");
    +  qh_fprintf(qh, qh->ferr, 8030, qh_REAL_1, dist);
    +  qh_fprintf(qh, qh->ferr, 8031, "\n");
    +  return False;
    +} /* sethalfspace */
    +
    +/*---------------------------------
    +
    +  qh_sethalfspace_all(qh, dim, count, halfspaces, feasible )
    +    generate dual for halfspace intersection with feasible point
    +    array of count halfspaces
    +      each halfspace is normal coefficients followed by offset
    +      the origin is inside the halfspace if the offset is negative
    +    feasible is a point inside all halfspaces (http://www.qhull.org/html/qhalf.htm#notes)
    +
    +  returns:
    +    malloc'd array of count X dim-1 points
    +
    +  notes:
    +    call before qh_init_B or qh_initqhull_globals
    +    free memory when done
    +    unused/untested code: please email bradb@shore.net if this works ok for you
    +    if using option 'Fp', qh->feasible_point must be set (e.g., to 'feasible')
    +    qh->feasible_point is a malloc'd array that is freed by qh_freebuffers.
    +
    +  design:
    +    see qh_sethalfspace
    +*/
    +coordT *qh_sethalfspace_all(qhT *qh, int dim, int count, coordT *halfspaces, pointT *feasible) {
    +  int i, newdim;
    +  pointT *newpoints;
    +  coordT *coordp, *normalp, *offsetp;
    +
    +  trace0((qh, qh->ferr, 12, "qh_sethalfspace_all: compute dual for halfspace intersection\n"));
    +  newdim= dim - 1;
    +  if (!(newpoints=(coordT*)qh_malloc(count*newdim*sizeof(coordT)))){
    +    qh_fprintf(qh, qh->ferr, 6024, "qhull error: insufficient memory to compute dual of %d halfspaces\n",
    +          count);
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  coordp= newpoints;
    +  normalp= halfspaces;
    +  for (i=0; i < count; i++) {
    +    offsetp= normalp + newdim;
    +    if (!qh_sethalfspace(qh, newdim, coordp, &coordp, normalp, offsetp, feasible)) {
    +      qh_free(newpoints);  /* feasible is not inside halfspace as reported by qh_sethalfspace */
    +      qh_fprintf(qh, qh->ferr, 8032, "The halfspace was at index %d\n", i);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    normalp= offsetp + 1;
    +  }
    +  return newpoints;
    +} /* sethalfspace_all */
    +
    +
    +/*---------------------------------
    +
    +  qh_sharpnewfacets(qh)
    +
    +  returns:
    +    true if could be an acute angle (facets in different quadrants)
    +
    +  notes:
    +    for qh_findbest
    +
    +  design:
    +    for all facets on qh.newfacet_list
    +      if two facets are in different quadrants
    +        set issharp
    +*/
    +boolT qh_sharpnewfacets(qhT *qh) {
    +  facetT *facet;
    +  boolT issharp = False;
    +  int *quadrant, k;
    +
    +  quadrant= (int*)qh_memalloc(qh, qh->hull_dim * sizeof(int));
    +  FORALLfacet_(qh->newfacet_list) {
    +    if (facet == qh->newfacet_list) {
    +      for (k=qh->hull_dim; k--; )
    +        quadrant[ k]= (facet->normal[ k] > 0);
    +    }else {
    +      for (k=qh->hull_dim; k--; ) {
    +        if (quadrant[ k] != (facet->normal[ k] > 0)) {
    +          issharp= True;
    +          break;
    +        }
    +      }
    +    }
    +    if (issharp)
    +      break;
    +  }
    +  qh_memfree(qh, quadrant, qh->hull_dim * sizeof(int));
    +  trace3((qh, qh->ferr, 3001, "qh_sharpnewfacets: %d\n", issharp));
    +  return issharp;
    +} /* sharpnewfacets */
    +
    +/*---------------------------------
    +
    +  qh_voronoi_center(qh, dim, points )
    +    return Voronoi center for a set of points
    +    dim is the orginal dimension of the points
    +    gh.gm_matrix/qh.gm_row are scratch buffers
    +
    +  returns:
    +    center as a temporary point (qh_memalloc)
    +    if non-simplicial,
    +      returns center for max simplex of points
    +
    +  notes:
    +    only called by qh_facetcenter
    +    from Bowyer & Woodwark, A Programmer's Geometry, 1983, p. 65
    +
    +  design:
    +    if non-simplicial
    +      determine max simplex for points
    +    translate point0 of simplex to origin
    +    compute sum of squares of diagonal
    +    compute determinate
    +    compute Voronoi center (see Bowyer & Woodwark)
    +*/
    +pointT *qh_voronoi_center(qhT *qh, int dim, setT *points) {
    +  pointT *point, **pointp, *point0;
    +  pointT *center= (pointT*)qh_memalloc(qh, qh->center_size);
    +  setT *simplex;
    +  int i, j, k, size= qh_setsize(qh, points);
    +  coordT *gmcoord;
    +  realT *diffp, sum2, *sum2row, *sum2p, det, factor;
    +  boolT nearzero, infinite;
    +
    +  if (size == dim+1)
    +    simplex= points;
    +  else if (size < dim+1) {
    +    qh_memfree(qh, center, qh->center_size);
    +    qh_fprintf(qh, qh->ferr, 6025, "qhull internal error (qh_voronoi_center):\n  need at least %d points to construct a Voronoi center\n",
    +             dim+1);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    simplex= points;  /* never executed -- avoids warning */
    +  }else {
    +    simplex= qh_settemp(qh, dim+1);
    +    qh_maxsimplex(qh, dim, points, NULL, 0, &simplex);
    +  }
    +  point0= SETfirstt_(simplex, pointT);
    +  gmcoord= qh->gm_matrix;
    +  for (k=0; k < dim; k++) {
    +    qh->gm_row[k]= gmcoord;
    +    FOREACHpoint_(simplex) {
    +      if (point != point0)
    +        *(gmcoord++)= point[k] - point0[k];
    +    }
    +  }
    +  sum2row= gmcoord;
    +  for (i=0; i < dim; i++) {
    +    sum2= 0.0;
    +    for (k=0; k < dim; k++) {
    +      diffp= qh->gm_row[k] + i;
    +      sum2 += *diffp * *diffp;
    +    }
    +    *(gmcoord++)= sum2;
    +  }
    +  det= qh_determinant(qh, qh->gm_row, dim, &nearzero);
    +  factor= qh_divzero(0.5, det, qh->MINdenom, &infinite);
    +  if (infinite) {
    +    for (k=dim; k--; )
    +      center[k]= qh_INFINITE;
    +    if (qh->IStracing)
    +      qh_printpoints(qh, qh->ferr, "qh_voronoi_center: at infinity for ", simplex);
    +  }else {
    +    for (i=0; i < dim; i++) {
    +      gmcoord= qh->gm_matrix;
    +      sum2p= sum2row;
    +      for (k=0; k < dim; k++) {
    +        qh->gm_row[k]= gmcoord;
    +        if (k == i) {
    +          for (j=dim; j--; )
    +            *(gmcoord++)= *sum2p++;
    +        }else {
    +          FOREACHpoint_(simplex) {
    +            if (point != point0)
    +              *(gmcoord++)= point[k] - point0[k];
    +          }
    +        }
    +      }
    +      center[i]= qh_determinant(qh, qh->gm_row, dim, &nearzero)*factor + point0[i];
    +    }
    +#ifndef qh_NOtrace
    +    if (qh->IStracing >= 3) {
    +      qh_fprintf(qh, qh->ferr, 8033, "qh_voronoi_center: det %2.2g factor %2.2g ", det, factor);
    +      qh_printmatrix(qh, qh->ferr, "center:", ¢er, 1, dim);
    +      if (qh->IStracing >= 5) {
    +        qh_printpoints(qh, qh->ferr, "points", simplex);
    +        FOREACHpoint_(simplex)
    +          qh_fprintf(qh, qh->ferr, 8034, "p%d dist %.2g, ", qh_pointid(qh, point),
    +                   qh_pointdist(point, center, dim));
    +        qh_fprintf(qh, qh->ferr, 8035, "\n");
    +      }
    +    }
    +#endif
    +  }
    +  if (simplex != points)
    +    qh_settempfree(qh, &simplex);
    +  return center;
    +} /* voronoi_center */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/geom_r.c b/xs/src/qhull/src/libqhull_r/geom_r.c
    new file mode 100644
    index 0000000000..8104813cad
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/geom_r.c
    @@ -0,0 +1,1234 @@
    +/*
      ---------------------------------
    +
    +   geom_r.c
    +   geometric routines of qhull
    +
    +   see qh-geom_r.htm and geom_r.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/geom_r.c#2 $$Change: 1995 $
    +   $DateTime: 2015/10/13 21:59:42 $$Author: bbarber $
    +
    +   infrequent code goes into geom2_r.c
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*---------------------------------
    +
    +  qh_distplane(qh, point, facet, dist )
    +    return distance from point to facet
    +
    +  returns:
    +    dist
    +    if qh.RANDOMdist, joggles result
    +
    +  notes:
    +    dist > 0 if point is above facet (i.e., outside)
    +    does not error (for qh_sortfacets, qh_outerinner)
    +
    +  see:
    +    qh_distnorm in geom2_r.c
    +    qh_distplane [geom_r.c], QhullFacet::distance, and QhullHyperplane::distance are copies
    +*/
    +void qh_distplane(qhT *qh, pointT *point, facetT *facet, realT *dist) {
    +  coordT *normal= facet->normal, *coordp, randr;
    +  int k;
    +
    +  switch (qh->hull_dim){
    +  case 2:
    +    *dist= facet->offset + point[0] * normal[0] + point[1] * normal[1];
    +    break;
    +  case 3:
    +    *dist= facet->offset + point[0] * normal[0] + point[1] * normal[1] + point[2] * normal[2];
    +    break;
    +  case 4:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3];
    +    break;
    +  case 5:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4];
    +    break;
    +  case 6:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5];
    +    break;
    +  case 7:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6];
    +    break;
    +  case 8:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6]+point[7]*normal[7];
    +    break;
    +  default:
    +    *dist= facet->offset;
    +    coordp= point;
    +    for (k=qh->hull_dim; k--; )
    +      *dist += *coordp++ * *normal++;
    +    break;
    +  }
    +  zinc_(Zdistplane);
    +  if (!qh->RANDOMdist && qh->IStracing < 4)
    +    return;
    +  if (qh->RANDOMdist) {
    +    randr= qh_RANDOMint;
    +    *dist += (2.0 * randr / qh_RANDOMmax - 1.0) *
    +      qh->RANDOMfactor * qh->MAXabs_coord;
    +  }
    +  if (qh->IStracing >= 4) {
    +    qh_fprintf(qh, qh->ferr, 8001, "qh_distplane: ");
    +    qh_fprintf(qh, qh->ferr, 8002, qh_REAL_1, *dist);
    +    qh_fprintf(qh, qh->ferr, 8003, "from p%d to f%d\n", qh_pointid(qh, point), facet->id);
    +  }
    +  return;
    +} /* distplane */
    +
    +
    +/*---------------------------------
    +
    +  qh_findbest(qh, point, startfacet, bestoutside, qh_ISnewfacets, qh_NOupper, dist, isoutside, numpart )
    +    find facet that is furthest below a point
    +    for upperDelaunay facets
    +      returns facet only if !qh_NOupper and clearly above
    +
    +  input:
    +    starts search at 'startfacet' (can not be flipped)
    +    if !bestoutside(qh_ALL), stops at qh.MINoutside
    +
    +  returns:
    +    best facet (reports error if NULL)
    +    early out if isoutside defined and bestdist > qh.MINoutside
    +    dist is distance to facet
    +    isoutside is true if point is outside of facet
    +    numpart counts the number of distance tests
    +
    +  see also:
    +    qh_findbestnew()
    +
    +  notes:
    +    If merging (testhorizon), searches horizon facets of coplanar best facets because
    +    after qh_distplane, this and qh_partitionpoint are the most expensive in 3-d
    +      avoid calls to distplane, function calls, and real number operations.
    +    caller traces result
    +    Optimized for outside points.   Tried recording a search set for qh_findhorizon.
    +    Made code more complicated.
    +
    +  when called by qh_partitionvisible():
    +    indicated by qh_ISnewfacets
    +    qh.newfacet_list is list of simplicial, new facets
    +    qh_findbestnew set if qh_sharpnewfacets returns True (to use qh_findbestnew)
    +    qh.bestfacet_notsharp set if qh_sharpnewfacets returns False
    +
    +  when called by qh_findfacet(), qh_partitionpoint(), qh_partitioncoplanar(),
    +                 qh_check_bestdist(), qh_addpoint()
    +    indicated by !qh_ISnewfacets
    +    returns best facet in neighborhood of given facet
    +      this is best facet overall if dist > -   qh.MAXcoplanar
    +        or hull has at least a "spherical" curvature
    +
    +  design:
    +    initialize and test for early exit
    +    repeat while there are better facets
    +      for each neighbor of facet
    +        exit if outside facet found
    +        test for better facet
    +    if point is inside and partitioning
    +      test for new facets with a "sharp" intersection
    +      if so, future calls go to qh_findbestnew()
    +    test horizon facets
    +*/
    +facetT *qh_findbest(qhT *qh, pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT isnewfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart) {
    +  realT bestdist= -REALmax/2 /* avoid underflow */;
    +  facetT *facet, *neighbor, **neighborp;
    +  facetT *bestfacet= NULL, *lastfacet= NULL;
    +  int oldtrace= qh->IStracing;
    +  unsigned int visitid= ++qh->visit_id;
    +  int numpartnew=0;
    +  boolT testhorizon = True; /* needed if precise, e.g., rbox c D6 | qhull Q0 Tv */
    +
    +  zinc_(Zfindbest);
    +  if (qh->IStracing >= 3 || (qh->TRACElevel && qh->TRACEpoint >= 0 && qh->TRACEpoint == qh_pointid(qh, point))) {
    +    if (qh->TRACElevel > qh->IStracing)
    +      qh->IStracing= qh->TRACElevel;
    +    qh_fprintf(qh, qh->ferr, 8004, "qh_findbest: point p%d starting at f%d isnewfacets? %d, unless %d exit if > %2.2g\n",
    +             qh_pointid(qh, point), startfacet->id, isnewfacets, bestoutside, qh->MINoutside);
    +    qh_fprintf(qh, qh->ferr, 8005, "  testhorizon? %d noupper? %d", testhorizon, noupper);
    +    qh_fprintf(qh, qh->ferr, 8006, "  Last point added was p%d.", qh->furthest_id);
    +    qh_fprintf(qh, qh->ferr, 8007, "  Last merge was #%d.  max_outside %2.2g\n", zzval_(Ztotmerge), qh->max_outside);
    +  }
    +  if (isoutside)
    +    *isoutside= True;
    +  if (!startfacet->flipped) {  /* test startfacet */
    +    *numpart= 1;
    +    qh_distplane(qh, point, startfacet, dist);  /* this code is duplicated below */
    +    if (!bestoutside && *dist >= qh->MINoutside
    +    && (!startfacet->upperdelaunay || !noupper)) {
    +      bestfacet= startfacet;
    +      goto LABELreturn_best;
    +    }
    +    bestdist= *dist;
    +    if (!startfacet->upperdelaunay) {
    +      bestfacet= startfacet;
    +    }
    +  }else
    +    *numpart= 0;
    +  startfacet->visitid= visitid;
    +  facet= startfacet;
    +  while (facet) {
    +    trace4((qh, qh->ferr, 4001, "qh_findbest: neighbors of f%d, bestdist %2.2g f%d\n",
    +                facet->id, bestdist, getid_(bestfacet)));
    +    lastfacet= facet;
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->newfacet && isnewfacets)
    +        continue;
    +      if (neighbor->visitid == visitid)
    +        continue;
    +      neighbor->visitid= visitid;
    +      if (!neighbor->flipped) {  /* code duplicated above */
    +        (*numpart)++;
    +        qh_distplane(qh, point, neighbor, dist);
    +        if (*dist > bestdist) {
    +          if (!bestoutside && *dist >= qh->MINoutside
    +          && (!neighbor->upperdelaunay || !noupper)) {
    +            bestfacet= neighbor;
    +            goto LABELreturn_best;
    +          }
    +          if (!neighbor->upperdelaunay) {
    +            bestfacet= neighbor;
    +            bestdist= *dist;
    +            break; /* switch to neighbor */
    +          }else if (!bestfacet) {
    +            bestdist= *dist;
    +            break; /* switch to neighbor */
    +          }
    +        } /* end of *dist>bestdist */
    +      } /* end of !flipped */
    +    } /* end of FOREACHneighbor */
    +    facet= neighbor;  /* non-NULL only if *dist>bestdist */
    +  } /* end of while facet (directed search) */
    +  if (isnewfacets) {
    +    if (!bestfacet) {
    +      bestdist= -REALmax/2;
    +      bestfacet= qh_findbestnew(qh, point, startfacet->next, &bestdist, bestoutside, isoutside, &numpartnew);
    +      testhorizon= False; /* qh_findbestnew calls qh_findbesthorizon */
    +    }else if (!qh->findbest_notsharp && bestdist < - qh->DISTround) {
    +      if (qh_sharpnewfacets(qh)) {
    +        /* seldom used, qh_findbestnew will retest all facets */
    +        zinc_(Zfindnewsharp);
    +        bestfacet= qh_findbestnew(qh, point, bestfacet, &bestdist, bestoutside, isoutside, &numpartnew);
    +        testhorizon= False; /* qh_findbestnew calls qh_findbesthorizon */
    +        qh->findbestnew= True;
    +      }else
    +        qh->findbest_notsharp= True;
    +    }
    +  }
    +  if (!bestfacet)
    +    bestfacet= qh_findbestlower(qh, lastfacet, point, &bestdist, numpart);
    +  if (testhorizon)
    +    bestfacet= qh_findbesthorizon(qh, !qh_IScheckmax, point, bestfacet, noupper, &bestdist, &numpartnew);
    +  *dist= bestdist;
    +  if (isoutside && bestdist < qh->MINoutside)
    +    *isoutside= False;
    +LABELreturn_best:
    +  zadd_(Zfindbesttot, *numpart);
    +  zmax_(Zfindbestmax, *numpart);
    +  (*numpart) += numpartnew;
    +  qh->IStracing= oldtrace;
    +  return bestfacet;
    +}  /* findbest */
    +
    +
    +/*---------------------------------
    +
    +  qh_findbesthorizon(qh, qh_IScheckmax, point, startfacet, qh_NOupper, &bestdist, &numpart )
    +    search coplanar and better horizon facets from startfacet/bestdist
    +    ischeckmax turns off statistics and minsearch update
    +    all arguments must be initialized
    +  returns(ischeckmax):
    +    best facet
    +  returns(!ischeckmax):
    +    best facet that is not upperdelaunay
    +    allows upperdelaunay that is clearly outside
    +  returns:
    +    bestdist is distance to bestfacet
    +    numpart -- updates number of distance tests
    +
    +  notes:
    +    no early out -- use qh_findbest() or qh_findbestnew()
    +    Searches coplanar or better horizon facets
    +
    +  when called by qh_check_maxout() (qh_IScheckmax)
    +    startfacet must be closest to the point
    +      Otherwise, if point is beyond and below startfacet, startfacet may be a local minimum
    +      even though other facets are below the point.
    +    updates facet->maxoutside for good, visited facets
    +    may return NULL
    +
    +    searchdist is qh.max_outside + 2 * DISTround
    +      + max( MINvisible('Vn'), MAXcoplanar('Un'));
    +    This setting is a guess.  It must be at least max_outside + 2*DISTround
    +    because a facet may have a geometric neighbor across a vertex
    +
    +  design:
    +    for each horizon facet of coplanar best facets
    +      continue if clearly inside
    +      unless upperdelaunay or clearly outside
    +         update best facet
    +*/
    +facetT *qh_findbesthorizon(qhT *qh, boolT ischeckmax, pointT* point, facetT *startfacet, boolT noupper, realT *bestdist, int *numpart) {
    +  facetT *bestfacet= startfacet;
    +  realT dist;
    +  facetT *neighbor, **neighborp, *facet;
    +  facetT *nextfacet= NULL; /* optimize last facet of coplanarfacetset */
    +  int numpartinit= *numpart, coplanarfacetset_size;
    +  unsigned int visitid= ++qh->visit_id;
    +  boolT newbest= False; /* for tracing */
    +  realT minsearch, searchdist;  /* skip facets that are too far from point */
    +
    +  if (!ischeckmax) {
    +    zinc_(Zfindhorizon);
    +  }else {
    +#if qh_MAXoutside
    +    if ((!qh->ONLYgood || startfacet->good) && *bestdist > startfacet->maxoutside)
    +      startfacet->maxoutside= *bestdist;
    +#endif
    +  }
    +  searchdist= qh_SEARCHdist; /* multiple of qh.max_outside and precision constants */
    +  minsearch= *bestdist - searchdist;
    +  if (ischeckmax) {
    +    /* Always check coplanar facets.  Needed for RBOX 1000 s Z1 G1e-13 t996564279 | QHULL Tv */
    +    minimize_(minsearch, -searchdist);
    +  }
    +  coplanarfacetset_size= 0;
    +  facet= startfacet;
    +  while (True) {
    +    trace4((qh, qh->ferr, 4002, "qh_findbesthorizon: neighbors of f%d bestdist %2.2g f%d ischeckmax? %d noupper? %d minsearch %2.2g searchdist %2.2g\n",
    +                facet->id, *bestdist, getid_(bestfacet), ischeckmax, noupper,
    +                minsearch, searchdist));
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == visitid)
    +        continue;
    +      neighbor->visitid= visitid;
    +      if (!neighbor->flipped) {
    +        qh_distplane(qh, point, neighbor, &dist);
    +        (*numpart)++;
    +        if (dist > *bestdist) {
    +          if (!neighbor->upperdelaunay || ischeckmax || (!noupper && dist >= qh->MINoutside)) {
    +            bestfacet= neighbor;
    +            *bestdist= dist;
    +            newbest= True;
    +            if (!ischeckmax) {
    +              minsearch= dist - searchdist;
    +              if (dist > *bestdist + searchdist) {
    +                zinc_(Zfindjump);  /* everything in qh.coplanarfacetset at least searchdist below */
    +                coplanarfacetset_size= 0;
    +              }
    +            }
    +          }
    +        }else if (dist < minsearch)
    +          continue;  /* if ischeckmax, dist can't be positive */
    +#if qh_MAXoutside
    +        if (ischeckmax && dist > neighbor->maxoutside)
    +          neighbor->maxoutside= dist;
    +#endif
    +      } /* end of !flipped */
    +      if (nextfacet) {
    +        if (!coplanarfacetset_size++) {
    +          SETfirst_(qh->coplanarfacetset)= nextfacet;
    +          SETtruncate_(qh->coplanarfacetset, 1);
    +        }else
    +          qh_setappend(qh, &qh->coplanarfacetset, nextfacet); /* Was needed for RBOX 1000 s W1e-13 P0 t996547055 | QHULL d Qbb Qc Tv
    +                                                 and RBOX 1000 s Z1 G1e-13 t996564279 | qhull Tv  */
    +      }
    +      nextfacet= neighbor;
    +    } /* end of EACHneighbor */
    +    facet= nextfacet;
    +    if (facet)
    +      nextfacet= NULL;
    +    else if (!coplanarfacetset_size)
    +      break;
    +    else if (!--coplanarfacetset_size) {
    +      facet= SETfirstt_(qh->coplanarfacetset, facetT);
    +      SETtruncate_(qh->coplanarfacetset, 0);
    +    }else
    +      facet= (facetT*)qh_setdellast(qh->coplanarfacetset);
    +  } /* while True, for each facet in qh.coplanarfacetset */
    +  if (!ischeckmax) {
    +    zadd_(Zfindhorizontot, *numpart - numpartinit);
    +    zmax_(Zfindhorizonmax, *numpart - numpartinit);
    +    if (newbest)
    +      zinc_(Zparthorizon);
    +  }
    +  trace4((qh, qh->ferr, 4003, "qh_findbesthorizon: newbest? %d bestfacet f%d bestdist %2.2g\n", newbest, getid_(bestfacet), *bestdist));
    +  return bestfacet;
    +}  /* findbesthorizon */
    +
    +/*---------------------------------
    +
    +  qh_findbestnew(qh, point, startfacet, dist, isoutside, numpart )
    +    find best newfacet for point
    +    searches all of qh.newfacet_list starting at startfacet
    +    searches horizon facets of coplanar best newfacets
    +    searches all facets if startfacet == qh.facet_list
    +  returns:
    +    best new or horizon facet that is not upperdelaunay
    +    early out if isoutside and not 'Qf'
    +    dist is distance to facet
    +    isoutside is true if point is outside of facet
    +    numpart is number of distance tests
    +
    +  notes:
    +    Always used for merged new facets (see qh_USEfindbestnew)
    +    Avoids upperdelaunay facet unless (isoutside and outside)
    +
    +    Uses qh.visit_id, qh.coplanarfacetset.
    +    If share visit_id with qh_findbest, coplanarfacetset is incorrect.
    +
    +    If merging (testhorizon), searches horizon facets of coplanar best facets because
    +    a point maybe coplanar to the bestfacet, below its horizon facet,
    +    and above a horizon facet of a coplanar newfacet.  For example,
    +      rbox 1000 s Z1 G1e-13 | qhull
    +      rbox 1000 s W1e-13 P0 t992110337 | QHULL d Qbb Qc
    +
    +    qh_findbestnew() used if
    +       qh_sharpnewfacets -- newfacets contains a sharp angle
    +       if many merges, qh_premerge found a merge, or 'Qf' (qh.findbestnew)
    +
    +  see also:
    +    qh_partitionall() and qh_findbest()
    +
    +  design:
    +    for each new facet starting from startfacet
    +      test distance from point to facet
    +      return facet if clearly outside
    +      unless upperdelaunay and a lowerdelaunay exists
    +         update best facet
    +    test horizon facets
    +*/
    +facetT *qh_findbestnew(qhT *qh, pointT *point, facetT *startfacet,
    +           realT *dist, boolT bestoutside, boolT *isoutside, int *numpart) {
    +  realT bestdist= -REALmax/2;
    +  facetT *bestfacet= NULL, *facet;
    +  int oldtrace= qh->IStracing, i;
    +  unsigned int visitid= ++qh->visit_id;
    +  realT distoutside= 0.0;
    +  boolT isdistoutside; /* True if distoutside is defined */
    +  boolT testhorizon = True; /* needed if precise, e.g., rbox c D6 | qhull Q0 Tv */
    +
    +  if (!startfacet) {
    +    if (qh->MERGING)
    +      qh_fprintf(qh, qh->ferr, 6001, "qhull precision error (qh_findbestnew): merging has formed and deleted a cone of new facets.  Can not continue.\n");
    +    else
    +      qh_fprintf(qh, qh->ferr, 6002, "qhull internal error (qh_findbestnew): no new facets for point p%d\n",
    +              qh->furthest_id);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  zinc_(Zfindnew);
    +  if (qh->BESToutside || bestoutside)
    +    isdistoutside= False;
    +  else {
    +    isdistoutside= True;
    +    distoutside= qh_DISToutside; /* multiple of qh.MINoutside & qh.max_outside, see user.h */
    +  }
    +  if (isoutside)
    +    *isoutside= True;
    +  *numpart= 0;
    +  if (qh->IStracing >= 3 || (qh->TRACElevel && qh->TRACEpoint >= 0 && qh->TRACEpoint == qh_pointid(qh, point))) {
    +    if (qh->TRACElevel > qh->IStracing)
    +      qh->IStracing= qh->TRACElevel;
    +    qh_fprintf(qh, qh->ferr, 8008, "qh_findbestnew: point p%d facet f%d. Stop? %d if dist > %2.2g\n",
    +             qh_pointid(qh, point), startfacet->id, isdistoutside, distoutside);
    +    qh_fprintf(qh, qh->ferr, 8009, "  Last point added p%d visitid %d.",  qh->furthest_id, visitid);
    +    qh_fprintf(qh, qh->ferr, 8010, "  Last merge was #%d.\n", zzval_(Ztotmerge));
    +  }
    +  /* visit all new facets starting with startfacet, maybe qh->facet_list */
    +  for (i=0, facet=startfacet; i < 2; i++, facet= qh->newfacet_list) {
    +    FORALLfacet_(facet) {
    +      if (facet == startfacet && i)
    +        break;
    +      facet->visitid= visitid;
    +      if (!facet->flipped) {
    +        qh_distplane(qh, point, facet, dist);
    +        (*numpart)++;
    +        if (*dist > bestdist) {
    +          if (!facet->upperdelaunay || *dist >= qh->MINoutside) {
    +            bestfacet= facet;
    +            if (isdistoutside && *dist >= distoutside)
    +              goto LABELreturn_bestnew;
    +            bestdist= *dist;
    +          }
    +        }
    +      } /* end of !flipped */
    +    } /* FORALLfacet from startfacet or qh->newfacet_list */
    +  }
    +  if (testhorizon || !bestfacet) /* testhorizon is always True.  Keep the same code as qh_findbest */
    +    bestfacet= qh_findbesthorizon(qh, !qh_IScheckmax, point, bestfacet ? bestfacet : startfacet,
    +                                        !qh_NOupper, &bestdist, numpart);
    +  *dist= bestdist;
    +  if (isoutside && *dist < qh->MINoutside)
    +    *isoutside= False;
    +LABELreturn_bestnew:
    +  zadd_(Zfindnewtot, *numpart);
    +  zmax_(Zfindnewmax, *numpart);
    +  trace4((qh, qh->ferr, 4004, "qh_findbestnew: bestfacet f%d bestdist %2.2g\n", getid_(bestfacet), *dist));
    +  qh->IStracing= oldtrace;
    +  return bestfacet;
    +}  /* findbestnew */
    +
    +/* ============ hyperplane functions -- keep code together [?] ============ */
    +
    +/*---------------------------------
    +
    +  qh_backnormal(qh, rows, numrow, numcol, sign, normal, nearzero )
    +    given an upper-triangular rows array and a sign,
    +    solve for normal equation x using back substitution over rows U
    +
    +  returns:
    +     normal= x
    +
    +     if will not be able to divzero() when normalized(qh.MINdenom_2 and qh.MINdenom_1_2),
    +       if fails on last row
    +         this means that the hyperplane intersects [0,..,1]
    +         sets last coordinate of normal to sign
    +       otherwise
    +         sets tail of normal to [...,sign,0,...], i.e., solves for b= [0...0]
    +         sets nearzero
    +
    +  notes:
    +     assumes numrow == numcol-1
    +
    +     see Golub & van Loan, 1983, Eq. 4.4-9 for "Gaussian elimination with complete pivoting"
    +
    +     solves Ux=b where Ax=b and PA=LU
    +     b= [0,...,0,sign or 0]  (sign is either -1 or +1)
    +     last row of A= [0,...,0,1]
    +
    +     1) Ly=Pb == y=b since P only permutes the 0's of   b
    +
    +  design:
    +    for each row from end
    +      perform back substitution
    +      if near zero
    +        use qh_divzero for division
    +        if zero divide and not last row
    +          set tail of normal to 0
    +*/
    +void qh_backnormal(qhT *qh, realT **rows, int numrow, int numcol, boolT sign,
    +        coordT *normal, boolT *nearzero) {
    +  int i, j;
    +  coordT *normalp, *normal_tail, *ai, *ak;
    +  realT diagonal;
    +  boolT waszero;
    +  int zerocol= -1;
    +
    +  normalp= normal + numcol - 1;
    +  *normalp--= (sign ? -1.0 : 1.0);
    +  for (i=numrow; i--; ) {
    +    *normalp= 0.0;
    +    ai= rows[i] + i + 1;
    +    ak= normalp+1;
    +    for (j=i+1; j < numcol; j++)
    +      *normalp -= *ai++ * *ak++;
    +    diagonal= (rows[i])[i];
    +    if (fabs_(diagonal) > qh->MINdenom_2)
    +      *(normalp--) /= diagonal;
    +    else {
    +      waszero= False;
    +      *normalp= qh_divzero(*normalp, diagonal, qh->MINdenom_1_2, &waszero);
    +      if (waszero) {
    +        zerocol= i;
    +        *(normalp--)= (sign ? -1.0 : 1.0);
    +        for (normal_tail= normalp+2; normal_tail < normal + numcol; normal_tail++)
    +          *normal_tail= 0.0;
    +      }else
    +        normalp--;
    +    }
    +  }
    +  if (zerocol != -1) {
    +    zzinc_(Zback0);
    +    *nearzero= True;
    +    trace4((qh, qh->ferr, 4005, "qh_backnormal: zero diagonal at column %d.\n", i));
    +    qh_precision(qh, "zero diagonal on back substitution");
    +  }
    +} /* backnormal */
    +
    +/*---------------------------------
    +
    +  qh_gausselim(qh, rows, numrow, numcol, sign )
    +    Gaussian elimination with partial pivoting
    +
    +  returns:
    +    rows is upper triangular (includes row exchanges)
    +    flips sign for each row exchange
    +    sets nearzero if pivot[k] < qh.NEARzero[k], else clears it
    +
    +  notes:
    +    if nearzero, the determinant's sign may be incorrect.
    +    assumes numrow <= numcol
    +
    +  design:
    +    for each row
    +      determine pivot and exchange rows if necessary
    +      test for near zero
    +      perform gaussian elimination step
    +*/
    +void qh_gausselim(qhT *qh, realT **rows, int numrow, int numcol, boolT *sign, boolT *nearzero) {
    +  realT *ai, *ak, *rowp, *pivotrow;
    +  realT n, pivot, pivot_abs= 0.0, temp;
    +  int i, j, k, pivoti, flip=0;
    +
    +  *nearzero= False;
    +  for (k=0; k < numrow; k++) {
    +    pivot_abs= fabs_((rows[k])[k]);
    +    pivoti= k;
    +    for (i=k+1; i < numrow; i++) {
    +      if ((temp= fabs_((rows[i])[k])) > pivot_abs) {
    +        pivot_abs= temp;
    +        pivoti= i;
    +      }
    +    }
    +    if (pivoti != k) {
    +      rowp= rows[pivoti];
    +      rows[pivoti]= rows[k];
    +      rows[k]= rowp;
    +      *sign ^= 1;
    +      flip ^= 1;
    +    }
    +    if (pivot_abs <= qh->NEARzero[k]) {
    +      *nearzero= True;
    +      if (pivot_abs == 0.0) {   /* remainder of column == 0 */
    +        if (qh->IStracing >= 4) {
    +          qh_fprintf(qh, qh->ferr, 8011, "qh_gausselim: 0 pivot at column %d. (%2.2g < %2.2g)\n", k, pivot_abs, qh->DISTround);
    +          qh_printmatrix(qh, qh->ferr, "Matrix:", rows, numrow, numcol);
    +        }
    +        zzinc_(Zgauss0);
    +        qh_precision(qh, "zero pivot for Gaussian elimination");
    +        goto LABELnextcol;
    +      }
    +    }
    +    pivotrow= rows[k] + k;
    +    pivot= *pivotrow++;  /* signed value of pivot, and remainder of row */
    +    for (i=k+1; i < numrow; i++) {
    +      ai= rows[i] + k;
    +      ak= pivotrow;
    +      n= (*ai++)/pivot;   /* divzero() not needed since |pivot| >= |*ai| */
    +      for (j= numcol - (k+1); j--; )
    +        *ai++ -= n * *ak++;
    +    }
    +  LABELnextcol:
    +    ;
    +  }
    +  wmin_(Wmindenom, pivot_abs);  /* last pivot element */
    +  if (qh->IStracing >= 5)
    +    qh_printmatrix(qh, qh->ferr, "qh_gausselem: result", rows, numrow, numcol);
    +} /* gausselim */
    +
    +
    +/*---------------------------------
    +
    +  qh_getangle(qh, vect1, vect2 )
    +    returns the dot product of two vectors
    +    if qh.RANDOMdist, joggles result
    +
    +  notes:
    +    the angle may be > 1.0 or < -1.0 because of roundoff errors
    +
    +*/
    +realT qh_getangle(qhT *qh, pointT *vect1, pointT *vect2) {
    +  realT angle= 0, randr;
    +  int k;
    +
    +  for (k=qh->hull_dim; k--; )
    +    angle += *vect1++ * *vect2++;
    +  if (qh->RANDOMdist) {
    +    randr= qh_RANDOMint;
    +    angle += (2.0 * randr / qh_RANDOMmax - 1.0) *
    +      qh->RANDOMfactor;
    +  }
    +  trace4((qh, qh->ferr, 4006, "qh_getangle: %2.2g\n", angle));
    +  return(angle);
    +} /* getangle */
    +
    +
    +/*---------------------------------
    +
    +  qh_getcenter(qh, vertices )
    +    returns arithmetic center of a set of vertices as a new point
    +
    +  notes:
    +    allocates point array for center
    +*/
    +pointT *qh_getcenter(qhT *qh, setT *vertices) {
    +  int k;
    +  pointT *center, *coord;
    +  vertexT *vertex, **vertexp;
    +  int count= qh_setsize(qh, vertices);
    +
    +  if (count < 2) {
    +    qh_fprintf(qh, qh->ferr, 6003, "qhull internal error (qh_getcenter): not defined for %d points\n", count);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  center= (pointT *)qh_memalloc(qh, qh->normal_size);
    +  for (k=0; k < qh->hull_dim; k++) {
    +    coord= center+k;
    +    *coord= 0.0;
    +    FOREACHvertex_(vertices)
    +      *coord += vertex->point[k];
    +    *coord /= count;  /* count>=2 by QH6003 */
    +  }
    +  return(center);
    +} /* getcenter */
    +
    +
    +/*---------------------------------
    +
    +  qh_getcentrum(qh, facet )
    +    returns the centrum for a facet as a new point
    +
    +  notes:
    +    allocates the centrum
    +*/
    +pointT *qh_getcentrum(qhT *qh, facetT *facet) {
    +  realT dist;
    +  pointT *centrum, *point;
    +
    +  point= qh_getcenter(qh, facet->vertices);
    +  zzinc_(Zcentrumtests);
    +  qh_distplane(qh, point, facet, &dist);
    +  centrum= qh_projectpoint(qh, point, facet, dist);
    +  qh_memfree(qh, point, qh->normal_size);
    +  trace4((qh, qh->ferr, 4007, "qh_getcentrum: for f%d, %d vertices dist= %2.2g\n",
    +          facet->id, qh_setsize(qh, facet->vertices), dist));
    +  return centrum;
    +} /* getcentrum */
    +
    +
    +/*---------------------------------
    +
    +  qh_getdistance(qh, facet, neighbor, mindist, maxdist )
    +    returns the maxdist and mindist distance of any vertex from neighbor
    +
    +  returns:
    +    the max absolute value
    +
    +  design:
    +    for each vertex of facet that is not in neighbor
    +      test the distance from vertex to neighbor
    +*/
    +realT qh_getdistance(qhT *qh, facetT *facet, facetT *neighbor, realT *mindist, realT *maxdist) {
    +  vertexT *vertex, **vertexp;
    +  realT dist, maxd, mind;
    +
    +  FOREACHvertex_(facet->vertices)
    +    vertex->seen= False;
    +  FOREACHvertex_(neighbor->vertices)
    +    vertex->seen= True;
    +  mind= 0.0;
    +  maxd= 0.0;
    +  FOREACHvertex_(facet->vertices) {
    +    if (!vertex->seen) {
    +      zzinc_(Zbestdist);
    +      qh_distplane(qh, vertex->point, neighbor, &dist);
    +      if (dist < mind)
    +        mind= dist;
    +      else if (dist > maxd)
    +        maxd= dist;
    +    }
    +  }
    +  *mindist= mind;
    +  *maxdist= maxd;
    +  mind= -mind;
    +  if (maxd > mind)
    +    return maxd;
    +  else
    +    return mind;
    +} /* getdistance */
    +
    +
    +/*---------------------------------
    +
    +  qh_normalize(qh, normal, dim, toporient )
    +    normalize a vector and report if too small
    +    does not use min norm
    +
    +  see:
    +    qh_normalize2
    +*/
    +void qh_normalize(qhT *qh, coordT *normal, int dim, boolT toporient) {
    +  qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
    +} /* normalize */
    +
    +/*---------------------------------
    +
    +  qh_normalize2(qh, normal, dim, toporient, minnorm, ismin )
    +    normalize a vector and report if too small
    +    qh.MINdenom/MINdenom1 are the upper limits for divide overflow
    +
    +  returns:
    +    normalized vector
    +    flips sign if !toporient
    +    if minnorm non-NULL,
    +      sets ismin if normal < minnorm
    +
    +  notes:
    +    if zero norm
    +       sets all elements to sqrt(1.0/dim)
    +    if divide by zero (divzero())
    +       sets largest element to   +/-1
    +       bumps Znearlysingular
    +
    +  design:
    +    computes norm
    +    test for minnorm
    +    if not near zero
    +      normalizes normal
    +    else if zero norm
    +      sets normal to standard value
    +    else
    +      uses qh_divzero to normalize
    +      if nearzero
    +        sets norm to direction of maximum value
    +*/
    +void qh_normalize2(qhT *qh, coordT *normal, int dim, boolT toporient,
    +            realT *minnorm, boolT *ismin) {
    +  int k;
    +  realT *colp, *maxp, norm= 0, temp, *norm1, *norm2, *norm3;
    +  boolT zerodiv;
    +
    +  norm1= normal+1;
    +  norm2= normal+2;
    +  norm3= normal+3;
    +  if (dim == 2)
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1));
    +  else if (dim == 3)
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2));
    +  else if (dim == 4) {
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2)
    +               + (*norm3)*(*norm3));
    +  }else if (dim > 4) {
    +    norm= (*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2)
    +               + (*norm3)*(*norm3);
    +    for (k=dim-4, colp=normal+4; k--; colp++)
    +      norm += (*colp) * (*colp);
    +    norm= sqrt(norm);
    +  }
    +  if (minnorm) {
    +    if (norm < *minnorm)
    +      *ismin= True;
    +    else
    +      *ismin= False;
    +  }
    +  wmin_(Wmindenom, norm);
    +  if (norm > qh->MINdenom) {
    +    if (!toporient)
    +      norm= -norm;
    +    *normal /= norm;
    +    *norm1 /= norm;
    +    if (dim == 2)
    +      ; /* all done */
    +    else if (dim == 3)
    +      *norm2 /= norm;
    +    else if (dim == 4) {
    +      *norm2 /= norm;
    +      *norm3 /= norm;
    +    }else if (dim >4) {
    +      *norm2 /= norm;
    +      *norm3 /= norm;
    +      for (k=dim-4, colp=normal+4; k--; )
    +        *colp++ /= norm;
    +    }
    +  }else if (norm == 0.0) {
    +    temp= sqrt(1.0/dim);
    +    for (k=dim, colp=normal; k--; )
    +      *colp++ = temp;
    +  }else {
    +    if (!toporient)
    +      norm= -norm;
    +    for (k=dim, colp=normal; k--; colp++) { /* k used below */
    +      temp= qh_divzero(*colp, norm, qh->MINdenom_1, &zerodiv);
    +      if (!zerodiv)
    +        *colp= temp;
    +      else {
    +        maxp= qh_maxabsval(normal, dim);
    +        temp= ((*maxp * norm >= 0.0) ? 1.0 : -1.0);
    +        for (k=dim, colp=normal; k--; colp++)
    +          *colp= 0.0;
    +        *maxp= temp;
    +        zzinc_(Znearlysingular);
    +        trace0((qh, qh->ferr, 1, "qh_normalize: norm=%2.2g too small during p%d\n",
    +               norm, qh->furthest_id));
    +        return;
    +      }
    +    }
    +  }
    +} /* normalize */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectpoint(qh, point, facet, dist )
    +    project point onto a facet by dist
    +
    +  returns:
    +    returns a new point
    +
    +  notes:
    +    if dist= distplane(point,facet)
    +      this projects point to hyperplane
    +    assumes qh_memfree_() is valid for normal_size
    +*/
    +pointT *qh_projectpoint(qhT *qh, pointT *point, facetT *facet, realT dist) {
    +  pointT *newpoint, *np, *normal;
    +  int normsize= qh->normal_size;
    +  int k;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_(qh, normsize, freelistp, newpoint, pointT);
    +  np= newpoint;
    +  normal= facet->normal;
    +  for (k=qh->hull_dim; k--; )
    +    *(np++)= *point++ - dist * *normal++;
    +  return(newpoint);
    +} /* projectpoint */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfacetplane(qh, facet )
    +    sets the hyperplane for a facet
    +    if qh.RANDOMdist, joggles hyperplane
    +
    +  notes:
    +    uses global buffers qh.gm_matrix and qh.gm_row
    +    overwrites facet->normal if already defined
    +    updates Wnewvertex if PRINTstatistics
    +    sets facet->upperdelaunay if upper envelope of Delaunay triangulation
    +
    +  design:
    +    copy vertex coordinates to qh.gm_matrix/gm_row
    +    compute determinate
    +    if nearzero
    +      recompute determinate with gaussian elimination
    +      if nearzero
    +        force outside orientation by testing interior point
    +*/
    +void qh_setfacetplane(qhT *qh, facetT *facet) {
    +  pointT *point;
    +  vertexT *vertex, **vertexp;
    +  int normsize= qh->normal_size;
    +  int k,i, oldtrace= 0;
    +  realT dist;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +  coordT *coord, *gmcoord;
    +  pointT *point0= SETfirstt_(facet->vertices, vertexT)->point;
    +  boolT nearzero= False;
    +
    +  zzinc_(Zsetplane);
    +  if (!facet->normal)
    +    qh_memalloc_(qh, normsize, freelistp, facet->normal, coordT);
    +  if (facet == qh->tracefacet) {
    +    oldtrace= qh->IStracing;
    +    qh->IStracing= 5;
    +    qh_fprintf(qh, qh->ferr, 8012, "qh_setfacetplane: facet f%d created.\n", facet->id);
    +    qh_fprintf(qh, qh->ferr, 8013, "  Last point added to hull was p%d.", qh->furthest_id);
    +    if (zzval_(Ztotmerge))
    +      qh_fprintf(qh, qh->ferr, 8014, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    qh_fprintf(qh, qh->ferr, 8015, "\n\nCurrent summary is:\n");
    +      qh_printsummary(qh, qh->ferr);
    +  }
    +  if (qh->hull_dim <= 4) {
    +    i= 0;
    +    if (qh->RANDOMdist) {
    +      gmcoord= qh->gm_matrix;
    +      FOREACHvertex_(facet->vertices) {
    +        qh->gm_row[i++]= gmcoord;
    +        coord= vertex->point;
    +        for (k=qh->hull_dim; k--; )
    +          *(gmcoord++)= *coord++ * qh_randomfactor(qh, qh->RANDOMa, qh->RANDOMb);
    +      }
    +    }else {
    +      FOREACHvertex_(facet->vertices)
    +       qh->gm_row[i++]= vertex->point;
    +    }
    +    qh_sethyperplane_det(qh, qh->hull_dim, qh->gm_row, point0, facet->toporient,
    +                facet->normal, &facet->offset, &nearzero);
    +  }
    +  if (qh->hull_dim > 4 || nearzero) {
    +    i= 0;
    +    gmcoord= qh->gm_matrix;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->point != point0) {
    +        qh->gm_row[i++]= gmcoord;
    +        coord= vertex->point;
    +        point= point0;
    +        for (k=qh->hull_dim; k--; )
    +          *(gmcoord++)= *coord++ - *point++;
    +      }
    +    }
    +    qh->gm_row[i]= gmcoord;  /* for areasimplex */
    +    if (qh->RANDOMdist) {
    +      gmcoord= qh->gm_matrix;
    +      for (i=qh->hull_dim-1; i--; ) {
    +        for (k=qh->hull_dim; k--; )
    +          *(gmcoord++) *= qh_randomfactor(qh, qh->RANDOMa, qh->RANDOMb);
    +      }
    +    }
    +    qh_sethyperplane_gauss(qh, qh->hull_dim, qh->gm_row, point0, facet->toporient,
    +                facet->normal, &facet->offset, &nearzero);
    +    if (nearzero) {
    +      if (qh_orientoutside(qh, facet)) {
    +        trace0((qh, qh->ferr, 2, "qh_setfacetplane: flipped orientation after testing interior_point during p%d\n", qh->furthest_id));
    +      /* this is part of using Gaussian Elimination.  For example in 5-d
    +           1 1 1 1 0
    +           1 1 1 1 1
    +           0 0 0 1 0
    +           0 1 0 0 0
    +           1 0 0 0 0
    +           norm= 0.38 0.38 -0.76 0.38 0
    +         has a determinate of 1, but g.e. after subtracting pt. 0 has
    +         0's in the diagonal, even with full pivoting.  It does work
    +         if you subtract pt. 4 instead. */
    +      }
    +    }
    +  }
    +  facet->upperdelaunay= False;
    +  if (qh->DELAUNAY) {
    +    if (qh->UPPERdelaunay) {     /* matches qh_triangulate_facet and qh.lower_threshold in qh_initbuild */
    +      if (facet->normal[qh->hull_dim -1] >= qh->ANGLEround * qh_ZEROdelaunay)
    +        facet->upperdelaunay= True;
    +    }else {
    +      if (facet->normal[qh->hull_dim -1] > -qh->ANGLEround * qh_ZEROdelaunay)
    +        facet->upperdelaunay= True;
    +    }
    +  }
    +  if (qh->PRINTstatistics || qh->IStracing || qh->TRACElevel || qh->JOGGLEmax < REALmax) {
    +    qh->old_randomdist= qh->RANDOMdist;
    +    qh->RANDOMdist= False;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->point != point0) {
    +        boolT istrace= False;
    +        zinc_(Zdiststat);
    +        qh_distplane(qh, vertex->point, facet, &dist);
    +        dist= fabs_(dist);
    +        zinc_(Znewvertex);
    +        wadd_(Wnewvertex, dist);
    +        if (dist > wwval_(Wnewvertexmax)) {
    +          wwval_(Wnewvertexmax)= dist;
    +          if (dist > qh->max_outside) {
    +            qh->max_outside= dist;  /* used by qh_maxouter(qh) */
    +            if (dist > qh->TRACEdist)
    +              istrace= True;
    +          }
    +        }else if (-dist > qh->TRACEdist)
    +          istrace= True;
    +        if (istrace) {
    +          qh_fprintf(qh, qh->ferr, 8016, "qh_setfacetplane: ====== vertex p%d(v%d) increases max_outside to %2.2g for new facet f%d last p%d\n",
    +                qh_pointid(qh, vertex->point), vertex->id, dist, facet->id, qh->furthest_id);
    +          qh_errprint(qh, "DISTANT", facet, NULL, NULL, NULL);
    +        }
    +      }
    +    }
    +    qh->RANDOMdist= qh->old_randomdist;
    +  }
    +  if (qh->IStracing >= 3) {
    +    qh_fprintf(qh, qh->ferr, 8017, "qh_setfacetplane: f%d offset %2.2g normal: ",
    +             facet->id, facet->offset);
    +    for (k=0; k < qh->hull_dim; k++)
    +      qh_fprintf(qh, qh->ferr, 8018, "%2.2g ", facet->normal[k]);
    +    qh_fprintf(qh, qh->ferr, 8019, "\n");
    +  }
    +  if (facet == qh->tracefacet)
    +    qh->IStracing= oldtrace;
    +} /* setfacetplane */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethyperplane_det(qh, dim, rows, point0, toporient, normal, offset, nearzero )
    +    given dim X dim array indexed by rows[], one row per point,
    +        toporient(flips all signs),
    +        and point0 (any row)
    +    set normalized hyperplane equation from oriented simplex
    +
    +  returns:
    +    normal (normalized)
    +    offset (places point0 on the hyperplane)
    +    sets nearzero if hyperplane not through points
    +
    +  notes:
    +    only defined for dim == 2..4
    +    rows[] is not modified
    +    solves det(P-V_0, V_n-V_0, ..., V_1-V_0)=0, i.e. every point is on hyperplane
    +    see Bower & Woodworth, A programmer's geometry, Butterworths 1983.
    +
    +  derivation of 3-d minnorm
    +    Goal: all vertices V_i within qh.one_merge of hyperplane
    +    Plan: exactly translate the facet so that V_0 is the origin
    +          exactly rotate the facet so that V_1 is on the x-axis and y_2=0.
    +          exactly rotate the effective perturbation to only effect n_0
    +             this introduces a factor of sqrt(3)
    +    n_0 = ((y_2-y_0)*(z_1-z_0) - (z_2-z_0)*(y_1-y_0)) / norm
    +    Let M_d be the max coordinate difference
    +    Let M_a be the greater of M_d and the max abs. coordinate
    +    Let u be machine roundoff and distround be max error for distance computation
    +    The max error for n_0 is sqrt(3) u M_a M_d / norm.  n_1 is approx. 1 and n_2 is approx. 0
    +    The max error for distance of V_1 is sqrt(3) u M_a M_d M_d / norm.  Offset=0 at origin
    +    Then minnorm = 1.8 u M_a M_d M_d / qh.ONEmerge
    +    Note that qh.one_merge is approx. 45.5 u M_a and norm is usually about M_d M_d
    +
    +  derivation of 4-d minnorm
    +    same as above except rotate the facet so that V_1 on x-axis and w_2, y_3, w_3=0
    +     [if two vertices fixed on x-axis, can rotate the other two in yzw.]
    +    n_0 = det3_(...) = y_2 det2_(z_1, w_1, z_3, w_3) = - y_2 w_1 z_3
    +     [all other terms contain at least two factors nearly zero.]
    +    The max error for n_0 is sqrt(4) u M_a M_d M_d / norm
    +    Then minnorm = 2 u M_a M_d M_d M_d / qh.ONEmerge
    +    Note that qh.one_merge is approx. 82 u M_a and norm is usually about M_d M_d M_d
    +*/
    +void qh_sethyperplane_det(qhT *qh, int dim, coordT **rows, coordT *point0,
    +          boolT toporient, coordT *normal, realT *offset, boolT *nearzero) {
    +  realT maxround, dist;
    +  int i;
    +  pointT *point;
    +
    +
    +  if (dim == 2) {
    +    normal[0]= dY(1,0);
    +    normal[1]= dX(0,1);
    +    qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0]+point0[1]*normal[1]);
    +    *nearzero= False;  /* since nearzero norm => incident points */
    +  }else if (dim == 3) {
    +    normal[0]= det2_(dY(2,0), dZ(2,0),
    +                     dY(1,0), dZ(1,0));
    +    normal[1]= det2_(dX(1,0), dZ(1,0),
    +                     dX(2,0), dZ(2,0));
    +    normal[2]= det2_(dX(2,0), dY(2,0),
    +                     dX(1,0), dY(1,0));
    +    qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0] + point0[1]*normal[1]
    +               + point0[2]*normal[2]);
    +    maxround= qh->DISTround;
    +    for (i=dim; i--; ) {
    +      point= rows[i];
    +      if (point != point0) {
    +        dist= *offset + (point[0]*normal[0] + point[1]*normal[1]
    +               + point[2]*normal[2]);
    +        if (dist > maxround || dist < -maxround) {
    +          *nearzero= True;
    +          break;
    +        }
    +      }
    +    }
    +  }else if (dim == 4) {
    +    normal[0]= - det3_(dY(2,0), dZ(2,0), dW(2,0),
    +                        dY(1,0), dZ(1,0), dW(1,0),
    +                        dY(3,0), dZ(3,0), dW(3,0));
    +    normal[1]=   det3_(dX(2,0), dZ(2,0), dW(2,0),
    +                        dX(1,0), dZ(1,0), dW(1,0),
    +                        dX(3,0), dZ(3,0), dW(3,0));
    +    normal[2]= - det3_(dX(2,0), dY(2,0), dW(2,0),
    +                        dX(1,0), dY(1,0), dW(1,0),
    +                        dX(3,0), dY(3,0), dW(3,0));
    +    normal[3]=   det3_(dX(2,0), dY(2,0), dZ(2,0),
    +                        dX(1,0), dY(1,0), dZ(1,0),
    +                        dX(3,0), dY(3,0), dZ(3,0));
    +    qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0] + point0[1]*normal[1]
    +               + point0[2]*normal[2] + point0[3]*normal[3]);
    +    maxround= qh->DISTround;
    +    for (i=dim; i--; ) {
    +      point= rows[i];
    +      if (point != point0) {
    +        dist= *offset + (point[0]*normal[0] + point[1]*normal[1]
    +               + point[2]*normal[2] + point[3]*normal[3]);
    +        if (dist > maxround || dist < -maxround) {
    +          *nearzero= True;
    +          break;
    +        }
    +      }
    +    }
    +  }
    +  if (*nearzero) {
    +    zzinc_(Zminnorm);
    +    trace0((qh, qh->ferr, 3, "qh_sethyperplane_det: degenerate norm during p%d.\n", qh->furthest_id));
    +    zzinc_(Znearlysingular);
    +  }
    +} /* sethyperplane_det */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethyperplane_gauss(qh, dim, rows, point0, toporient, normal, offset, nearzero )
    +    given(dim-1) X dim array of rows[i]= V_{i+1} - V_0 (point0)
    +    set normalized hyperplane equation from oriented simplex
    +
    +  returns:
    +    normal (normalized)
    +    offset (places point0 on the hyperplane)
    +
    +  notes:
    +    if nearzero
    +      orientation may be incorrect because of incorrect sign flips in gausselim
    +    solves [V_n-V_0,...,V_1-V_0, 0 .. 0 1] * N == [0 .. 0 1]
    +        or [V_n-V_0,...,V_1-V_0, 0 .. 0 1] * N == [0]
    +    i.e., N is normal to the hyperplane, and the unnormalized
    +        distance to [0 .. 1] is either 1 or   0
    +
    +  design:
    +    perform gaussian elimination
    +    flip sign for negative values
    +    perform back substitution
    +    normalize result
    +    compute offset
    +*/
    +void qh_sethyperplane_gauss(qhT *qh, int dim, coordT **rows, pointT *point0,
    +                boolT toporient, coordT *normal, coordT *offset, boolT *nearzero) {
    +  coordT *pointcoord, *normalcoef;
    +  int k;
    +  boolT sign= toporient, nearzero2= False;
    +
    +  qh_gausselim(qh, rows, dim-1, dim, &sign, nearzero);
    +  for (k=dim-1; k--; ) {
    +    if ((rows[k])[k] < 0)
    +      sign ^= 1;
    +  }
    +  if (*nearzero) {
    +    zzinc_(Znearlysingular);
    +    trace0((qh, qh->ferr, 4, "qh_sethyperplane_gauss: nearly singular or axis parallel hyperplane during p%d.\n", qh->furthest_id));
    +    qh_backnormal(qh, rows, dim-1, dim, sign, normal, &nearzero2);
    +  }else {
    +    qh_backnormal(qh, rows, dim-1, dim, sign, normal, &nearzero2);
    +    if (nearzero2) {
    +      zzinc_(Znearlysingular);
    +      trace0((qh, qh->ferr, 5, "qh_sethyperplane_gauss: singular or axis parallel hyperplane at normalization during p%d.\n", qh->furthest_id));
    +    }
    +  }
    +  if (nearzero2)
    +    *nearzero= True;
    +  qh_normalize2(qh, normal, dim, True, NULL, NULL);
    +  pointcoord= point0;
    +  normalcoef= normal;
    +  *offset= -(*pointcoord++ * *normalcoef++);
    +  for (k=dim-1; k--; )
    +    *offset -= *pointcoord++ * *normalcoef++;
    +} /* sethyperplane_gauss */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/geom_r.h b/xs/src/qhull/src/libqhull_r/geom_r.h
    new file mode 100644
    index 0000000000..d73e953453
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/geom_r.h
    @@ -0,0 +1,184 @@
    +/*
      ---------------------------------
    +
    +  geom_r.h
    +    header file for geometric routines
    +
    +   see qh-geom_r.htm and geom_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/geom_r.h#3 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFgeom
    +#define qhDEFgeom 1
    +
    +#include "libqhull_r.h"
    +
    +/* ============ -macros- ======================== */
    +
    +/*----------------------------------
    +
    +  fabs_(a)
    +    returns the absolute value of a
    +*/
    +#define fabs_( a ) ((( a ) < 0 ) ? -( a ):( a ))
    +
    +/*----------------------------------
    +
    +  fmax_(a,b)
    +    returns the maximum value of a and b
    +*/
    +#define fmax_( a,b )  ( ( a ) < ( b ) ? ( b ) : ( a ) )
    +
    +/*----------------------------------
    +
    +  fmin_(a,b)
    +    returns the minimum value of a and b
    +*/
    +#define fmin_( a,b )  ( ( a ) > ( b ) ? ( b ) : ( a ) )
    +
    +/*----------------------------------
    +
    +  maximize_(maxval, val)
    +    set maxval to val if val is greater than maxval
    +*/
    +#define maximize_( maxval, val ) { if (( maxval ) < ( val )) ( maxval )= ( val ); }
    +
    +/*----------------------------------
    +
    +  minimize_(minval, val)
    +    set minval to val if val is less than minval
    +*/
    +#define minimize_( minval, val ) { if (( minval ) > ( val )) ( minval )= ( val ); }
    +
    +/*----------------------------------
    +
    +  det2_(a1, a2,
    +        b1, b2)
    +
    +    compute a 2-d determinate
    +*/
    +#define det2_( a1,a2,b1,b2 ) (( a1 )*( b2 ) - ( a2 )*( b1 ))
    +
    +/*----------------------------------
    +
    +  det3_(a1, a2, a3,
    +       b1, b2, b3,
    +       c1, c2, c3)
    +
    +    compute a 3-d determinate
    +*/
    +#define det3_( a1,a2,a3,b1,b2,b3,c1,c2,c3 ) ( ( a1 )*det2_( b2,b3,c2,c3 ) \
    +                - ( b1 )*det2_( a2,a3,c2,c3 ) + ( c1 )*det2_( a2,a3,b2,b3 ) )
    +
    +/*----------------------------------
    +
    +  dX( p1, p2 )
    +  dY( p1, p2 )
    +  dZ( p1, p2 )
    +
    +    given two indices into rows[],
    +
    +    compute the difference between X, Y, or Z coordinates
    +*/
    +#define dX( p1,p2 )  ( *( rows[p1] ) - *( rows[p2] ))
    +#define dY( p1,p2 )  ( *( rows[p1]+1 ) - *( rows[p2]+1 ))
    +#define dZ( p1,p2 )  ( *( rows[p1]+2 ) - *( rows[p2]+2 ))
    +#define dW( p1,p2 )  ( *( rows[p1]+3 ) - *( rows[p2]+3 ))
    +
    +/*============= prototypes in alphabetical order, infrequent at end ======= */
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_backnormal(qhT *qh, realT **rows, int numrow, int numcol, boolT sign, coordT *normal, boolT *nearzero);
    +void    qh_distplane(qhT *qh, pointT *point, facetT *facet, realT *dist);
    +facetT *qh_findbest(qhT *qh, pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT isnewfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart);
    +facetT *qh_findbesthorizon(qhT *qh, boolT ischeckmax, pointT *point,
    +                     facetT *startfacet, boolT noupper, realT *bestdist, int *numpart);
    +facetT *qh_findbestnew(qhT *qh, pointT *point, facetT *startfacet, realT *dist,
    +                     boolT bestoutside, boolT *isoutside, int *numpart);
    +void    qh_gausselim(qhT *qh, realT **rows, int numrow, int numcol, boolT *sign, boolT *nearzero);
    +realT   qh_getangle(qhT *qh, pointT *vect1, pointT *vect2);
    +pointT *qh_getcenter(qhT *qh, setT *vertices);
    +pointT *qh_getcentrum(qhT *qh, facetT *facet);
    +realT   qh_getdistance(qhT *qh, facetT *facet, facetT *neighbor, realT *mindist, realT *maxdist);
    +void    qh_normalize(qhT *qh, coordT *normal, int dim, boolT toporient);
    +void    qh_normalize2(qhT *qh, coordT *normal, int dim, boolT toporient,
    +            realT *minnorm, boolT *ismin);
    +pointT *qh_projectpoint(qhT *qh, pointT *point, facetT *facet, realT dist);
    +
    +void    qh_setfacetplane(qhT *qh, facetT *newfacets);
    +void    qh_sethyperplane_det(qhT *qh, int dim, coordT **rows, coordT *point0,
    +              boolT toporient, coordT *normal, realT *offset, boolT *nearzero);
    +void    qh_sethyperplane_gauss(qhT *qh, int dim, coordT **rows, pointT *point0,
    +             boolT toporient, coordT *normal, coordT *offset, boolT *nearzero);
    +boolT   qh_sharpnewfacets(qhT *qh);
    +
    +/*========= infrequently used code in geom2_r.c =============*/
    +
    +coordT *qh_copypoints(qhT *qh, coordT *points, int numpoints, int dimension);
    +void    qh_crossproduct(int dim, realT vecA[3], realT vecB[3], realT vecC[3]);
    +realT   qh_determinant(qhT *qh, realT **rows, int dim, boolT *nearzero);
    +realT   qh_detjoggle(qhT *qh, pointT *points, int numpoints, int dimension);
    +void    qh_detroundoff(qhT *qh);
    +realT   qh_detsimplex(qhT *qh, pointT *apex, setT *points, int dim, boolT *nearzero);
    +realT   qh_distnorm(int dim, pointT *point, pointT *normal, realT *offsetp);
    +realT   qh_distround(qhT *qh, int dimension, realT maxabs, realT maxsumabs);
    +realT   qh_divzero(realT numer, realT denom, realT mindenom1, boolT *zerodiv);
    +realT   qh_facetarea(qhT *qh, facetT *facet);
    +realT   qh_facetarea_simplex(qhT *qh, int dim, coordT *apex, setT *vertices,
    +          vertexT *notvertex,  boolT toporient, coordT *normal, realT *offset);
    +pointT *qh_facetcenter(qhT *qh, setT *vertices);
    +facetT *qh_findgooddist(qhT *qh, pointT *point, facetT *facetA, realT *distp, facetT **facetlist);
    +void    qh_getarea(qhT *qh, facetT *facetlist);
    +boolT   qh_gram_schmidt(qhT *qh, int dim, realT **rows);
    +boolT   qh_inthresholds(qhT *qh, coordT *normal, realT *angle);
    +void    qh_joggleinput(qhT *qh);
    +realT  *qh_maxabsval(realT *normal, int dim);
    +setT   *qh_maxmin(qhT *qh, pointT *points, int numpoints, int dimension);
    +realT   qh_maxouter(qhT *qh);
    +void    qh_maxsimplex(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints, setT **simplex);
    +realT   qh_minabsval(realT *normal, int dim);
    +int     qh_mindiff(realT *vecA, realT *vecB, int dim);
    +boolT   qh_orientoutside(qhT *qh, facetT *facet);
    +void    qh_outerinner(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane);
    +coordT  qh_pointdist(pointT *point1, pointT *point2, int dim);
    +void    qh_printmatrix(qhT *qh, FILE *fp, const char *string, realT **rows, int numrow, int numcol);
    +void    qh_printpoints(qhT *qh, FILE *fp, const char *string, setT *points);
    +void    qh_projectinput(qhT *qh);
    +void    qh_projectpoints(qhT *qh, signed char *project, int n, realT *points,
    +             int numpoints, int dim, realT *newpoints, int newdim);
    +void    qh_rotateinput(qhT *qh, realT **rows);
    +void    qh_rotatepoints(qhT *qh, realT *points, int numpoints, int dim, realT **rows);
    +void    qh_scaleinput(qhT *qh);
    +void    qh_scalelast(qhT *qh, coordT *points, int numpoints, int dim, coordT low,
    +                   coordT high, coordT newhigh);
    +void    qh_scalepoints(qhT *qh, pointT *points, int numpoints, int dim,
    +                realT *newlows, realT *newhighs);
    +boolT   qh_sethalfspace(qhT *qh, int dim, coordT *coords, coordT **nextp,
    +              coordT *normal, coordT *offset, coordT *feasible);
    +coordT *qh_sethalfspace_all(qhT *qh, int dim, int count, coordT *halfspaces, pointT *feasible);
    +pointT *qh_voronoi_center(qhT *qh, int dim, setT *points);
    +
    +#ifdef __cplusplus
    +} /* extern "C"*/
    +#endif
    +
    +#endif /* qhDEFgeom */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/global_r.c b/xs/src/qhull/src/libqhull_r/global_r.c
    new file mode 100644
    index 0000000000..eef465ca14
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/global_r.c
    @@ -0,0 +1,2100 @@
    +
    +/*
      ---------------------------------
    +
    +   global_r.c
    +   initializes all the globals of the qhull application
    +
    +   see README
    +
    +   see libqhull_r.h for qh.globals and function prototypes
    +
    +   see qhull_ra.h for internal functions
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/global_r.c#16 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    + */
    +
    +#include "qhull_ra.h"
    +
    +/*========= qh->definition -- globals defined in libqhull_r.h =======================*/
    +
    +/*----------------------------------
    +
    +  qh_version
    +    version string by year and date
    +    qh_version2 for Unix users and -V
    +
    +    the revision increases on code changes only
    +
    +  notes:
    +    change date:    Changes.txt, Announce.txt, index.htm, README.txt,
    +                    qhull-news.html, Eudora signatures, CMakeLists.txt
    +    change version: README.txt, qh-get.htm, File_id.diz, Makefile.txt, CMakeLists.txt
    +    check that CmakeLists @version is the same as qh_version2
    +    change year:    Copying.txt
    +    check download size
    +    recompile user_eg_r.c, rbox_r.c, libqhull_r.c, qconvex_r.c, qdelaun_r.c qvoronoi_r.c, qhalf_r.c, testqset_r.c
    +*/
    +
    +const char qh_version[]= "2015.2.r 2016/01/18";
    +const char qh_version2[]= "qhull_r 7.2.0 (2015.2.r 2016/01/18)";
    +
    +/*---------------------------------
    +
    +  qh_appendprint(qh, printFormat )
    +    append printFormat to qh.PRINTout unless already defined
    +*/
    +void qh_appendprint(qhT *qh, qh_PRINT format) {
    +  int i;
    +
    +  for (i=0; i < qh_PRINTEND; i++) {
    +    if (qh->PRINTout[i] == format && format != qh_PRINTqhull)
    +      break;
    +    if (!qh->PRINTout[i]) {
    +      qh->PRINTout[i]= format;
    +      break;
    +    }
    +  }
    +} /* appendprint */
    +
    +/*---------------------------------
    +
    +  qh_checkflags(qh, commandStr, hiddenFlags )
    +    errors if commandStr contains hiddenFlags
    +    hiddenFlags starts and ends with a space and is space delimited (checked)
    +
    +  notes:
    +    ignores first word (e.g., "qconvex i")
    +    use qh_strtol/strtod since strtol/strtod may or may not skip trailing spaces
    +
    +  see:
    +    qh_initflags() initializes Qhull according to commandStr
    +*/
    +void qh_checkflags(qhT *qh, char *command, char *hiddenflags) {
    +  char *s= command, *t, *chkerr; /* qh_skipfilename is non-const */
    +  char key, opt, prevopt;
    +  char chkkey[]= "   ";
    +  char chkopt[]=  "    ";
    +  char chkopt2[]= "     ";
    +  boolT waserr= False;
    +
    +  if (*hiddenflags != ' ' || hiddenflags[strlen(hiddenflags)-1] != ' ') {
    +    qh_fprintf(qh, qh->ferr, 6026, "qhull error (qh_checkflags): hiddenflags must start and end with a space: \"%s\"", hiddenflags);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (strpbrk(hiddenflags, ",\n\r\t")) {
    +    qh_fprintf(qh, qh->ferr, 6027, "qhull error (qh_checkflags): hiddenflags contains commas, newlines, or tabs: \"%s\"", hiddenflags);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  while (*s && !isspace(*s))  /* skip program name */
    +    s++;
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    key = *s++;
    +    chkerr = NULL;
    +    if (key == 'T' && (*s == 'I' || *s == 'O')) {  /* TI or TO 'file name' */
    +      s= qh_skipfilename(qh, ++s);
    +      continue;
    +    }
    +    chkkey[1]= key;
    +    if (strstr(hiddenflags, chkkey)) {
    +      chkerr= chkkey;
    +    }else if (isupper(key)) {
    +      opt= ' ';
    +      prevopt= ' ';
    +      chkopt[1]= key;
    +      chkopt2[1]= key;
    +      while (!chkerr && *s && !isspace(*s)) {
    +        opt= *s++;
    +        if (isalpha(opt)) {
    +          chkopt[2]= opt;
    +          if (strstr(hiddenflags, chkopt))
    +            chkerr= chkopt;
    +          if (prevopt != ' ') {
    +            chkopt2[2]= prevopt;
    +            chkopt2[3]= opt;
    +            if (strstr(hiddenflags, chkopt2))
    +              chkerr= chkopt2;
    +          }
    +        }else if (key == 'Q' && isdigit(opt) && prevopt != 'b'
    +              && (prevopt == ' ' || islower(prevopt))) {
    +            chkopt[2]= opt;
    +            if (strstr(hiddenflags, chkopt))
    +              chkerr= chkopt;
    +        }else {
    +          qh_strtod(s-1, &t);
    +          if (s < t)
    +            s= t;
    +        }
    +        prevopt= opt;
    +      }
    +    }
    +    if (chkerr) {
    +      *chkerr= '\'';
    +      chkerr[strlen(chkerr)-1]=  '\'';
    +      qh_fprintf(qh, qh->ferr, 6029, "qhull error: option %s is not used with this program.\n             It may be used with qhull.\n", chkerr);
    +      waserr= True;
    +    }
    +  }
    +  if (waserr)
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +} /* checkflags */
    +
    +/*---------------------------------
    +
    +  qh_clear_outputflags(qh)
    +    Clear output flags for QhullPoints
    +*/
    +void qh_clear_outputflags(qhT *qh) {
    +  int i,k;
    +
    +  qh->ANNOTATEoutput= False;
    +  qh->DOintersections= False;
    +  qh->DROPdim= -1;
    +  qh->FORCEoutput= False;
    +  qh->GETarea= False;
    +  qh->GOODpoint= 0;
    +  qh->GOODpointp= NULL;
    +  qh->GOODthreshold= False;
    +  qh->GOODvertex= 0;
    +  qh->GOODvertexp= NULL;
    +  qh->IStracing= 0;
    +  qh->KEEParea= False;
    +  qh->KEEPmerge= False;
    +  qh->KEEPminArea= REALmax;
    +  qh->PRINTcentrums= False;
    +  qh->PRINTcoplanar= False;
    +  qh->PRINTdots= False;
    +  qh->PRINTgood= False;
    +  qh->PRINTinner= False;
    +  qh->PRINTneighbors= False;
    +  qh->PRINTnoplanes= False;
    +  qh->PRINToptions1st= False;
    +  qh->PRINTouter= False;
    +  qh->PRINTprecision= True;
    +  qh->PRINTridges= False;
    +  qh->PRINTspheres= False;
    +  qh->PRINTstatistics= False;
    +  qh->PRINTsummary= False;
    +  qh->PRINTtransparent= False;
    +  qh->SPLITthresholds= False;
    +  qh->TRACElevel= 0;
    +  qh->TRInormals= False;
    +  qh->USEstdout= False;
    +  qh->VERIFYoutput= False;
    +  for (k=qh->input_dim+1; k--; ) {  /* duplicated in qh_initqhull_buffers and qh_clear_outputflags */
    +    qh->lower_threshold[k]= -REALmax;
    +    qh->upper_threshold[k]= REALmax;
    +    qh->lower_bound[k]= -REALmax;
    +    qh->upper_bound[k]= REALmax;
    +  }
    +
    +  for (i=0; i < qh_PRINTEND; i++) {
    +    qh->PRINTout[i]= qh_PRINTnone;
    +  }
    +
    +  if (!qh->qhull_commandsiz2)
    +      qh->qhull_commandsiz2= (int)strlen(qh->qhull_command); /* WARN64 */
    +  else {
    +      qh->qhull_command[qh->qhull_commandsiz2]= '\0';
    +  }
    +  if (!qh->qhull_optionsiz2)
    +    qh->qhull_optionsiz2= (int)strlen(qh->qhull_options);  /* WARN64 */
    +  else {
    +    qh->qhull_options[qh->qhull_optionsiz2]= '\0';
    +    qh->qhull_optionlen= qh_OPTIONline;  /* start a new line */
    +  }
    +} /* clear_outputflags */
    +
    +/*---------------------------------
    +
    +  qh_clock()
    +    return user CPU time in 100ths (qh_SECtick)
    +    only defined for qh_CLOCKtype == 2
    +
    +  notes:
    +    use first value to determine time 0
    +    from Stevens '92 8.15
    +*/
    +unsigned long qh_clock(qhT *qh) {
    +
    +#if (qh_CLOCKtype == 2)
    +  struct tms time;
    +  static long clktck;  /* initialized first call and never updated */
    +  double ratio, cpu;
    +  unsigned long ticks;
    +
    +  if (!clktck) {
    +    if ((clktck= sysconf(_SC_CLK_TCK)) < 0) {
    +      qh_fprintf(qh, qh->ferr, 6030, "qhull internal error (qh_clock): sysconf() failed.  Use qh_CLOCKtype 1 in user.h\n");
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +  }
    +  if (times(&time) == -1) {
    +    qh_fprintf(qh, qh->ferr, 6031, "qhull internal error (qh_clock): times() failed.  Use qh_CLOCKtype 1 in user.h\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  ratio= qh_SECticks / (double)clktck;
    +  ticks= time.tms_utime * ratio;
    +  return ticks;
    +#else
    +  qh_fprintf(qh, qh->ferr, 6032, "qhull internal error (qh_clock): use qh_CLOCKtype 2 in user.h\n");
    +  qh_errexit(qh, qh_ERRqhull, NULL, NULL); /* never returns */
    +  return 0;
    +#endif
    +} /* clock */
    +
    +/*---------------------------------
    +
    +  qh_freebuffers()
    +    free up global memory buffers
    +
    +  notes:
    +    must match qh_initbuffers()
    +*/
    +void qh_freebuffers(qhT *qh) {
    +
    +  trace5((qh, qh->ferr, 5001, "qh_freebuffers: freeing up global memory buffers\n"));
    +  /* allocated by qh_initqhull_buffers */
    +  qh_memfree(qh, qh->NEARzero, qh->hull_dim * sizeof(realT));
    +  qh_memfree(qh, qh->lower_threshold, (qh->input_dim+1) * sizeof(realT));
    +  qh_memfree(qh, qh->upper_threshold, (qh->input_dim+1) * sizeof(realT));
    +  qh_memfree(qh, qh->lower_bound, (qh->input_dim+1) * sizeof(realT));
    +  qh_memfree(qh, qh->upper_bound, (qh->input_dim+1) * sizeof(realT));
    +  qh_memfree(qh, qh->gm_matrix, (qh->hull_dim+1) * qh->hull_dim * sizeof(coordT));
    +  qh_memfree(qh, qh->gm_row, (qh->hull_dim+1) * sizeof(coordT *));
    +  qh->NEARzero= qh->lower_threshold= qh->upper_threshold= NULL;
    +  qh->lower_bound= qh->upper_bound= NULL;
    +  qh->gm_matrix= NULL;
    +  qh->gm_row= NULL;
    +  qh_setfree(qh, &qh->other_points);
    +  qh_setfree(qh, &qh->del_vertices);
    +  qh_setfree(qh, &qh->coplanarfacetset);
    +  if (qh->line)                /* allocated by qh_readinput, freed if no error */
    +    qh_free(qh->line);
    +  if (qh->half_space)
    +    qh_free(qh->half_space);
    +  if (qh->temp_malloc)
    +    qh_free(qh->temp_malloc);
    +  if (qh->feasible_point)      /* allocated by qh_readfeasible */
    +    qh_free(qh->feasible_point);
    +  if (qh->feasible_string)     /* allocated by qh_initflags */
    +    qh_free(qh->feasible_string);
    +  qh->line= qh->feasible_string= NULL;
    +  qh->half_space= qh->feasible_point= qh->temp_malloc= NULL;
    +  /* usually allocated by qh_readinput */
    +  if (qh->first_point && qh->POINTSmalloc) {
    +    qh_free(qh->first_point);
    +    qh->first_point= NULL;
    +  }
    +  if (qh->input_points && qh->input_malloc) { /* set by qh_joggleinput */
    +    qh_free(qh->input_points);
    +    qh->input_points= NULL;
    +  }
    +  trace5((qh, qh->ferr, 5002, "qh_freebuffers: finished\n"));
    +} /* freebuffers */
    +
    +
    +/*---------------------------------
    +
    +  qh_freebuild(qh, allmem )
    +    free global memory used by qh_initbuild and qh_buildhull
    +    if !allmem,
    +      does not free short memory (e.g., facetT, freed by qh_memfreeshort)
    +
    +  design:
    +    free centrums
    +    free each vertex
    +    mark unattached ridges
    +    for each facet
    +      free ridges
    +      free outside set, coplanar set, neighbor set, ridge set, vertex set
    +      free facet
    +    free hash table
    +    free interior point
    +    free merge set
    +    free temporary sets
    +*/
    +void qh_freebuild(qhT *qh, boolT allmem) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  ridgeT *ridge, **ridgep;
    +  mergeT *merge, **mergep;
    +
    +  trace1((qh, qh->ferr, 1005, "qh_freebuild: free memory from qh_inithull and qh_buildhull\n"));
    +  if (qh->del_vertices)
    +    qh_settruncate(qh, qh->del_vertices, 0);
    +  if (allmem) {
    +    while ((vertex= qh->vertex_list)) {
    +      if (vertex->next)
    +        qh_delvertex(qh, vertex);
    +      else {
    +        qh_memfree(qh, vertex, (int)sizeof(vertexT));
    +        qh->newvertex_list= qh->vertex_list= NULL;
    +      }
    +    }
    +  }else if (qh->VERTEXneighbors) {
    +    FORALLvertices
    +      qh_setfreelong(qh, &(vertex->neighbors));
    +  }
    +  qh->VERTEXneighbors= False;
    +  qh->GOODclosest= NULL;
    +  if (allmem) {
    +    FORALLfacets {
    +      FOREACHridge_(facet->ridges)
    +        ridge->seen= False;
    +    }
    +    FORALLfacets {
    +      if (facet->visible) {
    +        FOREACHridge_(facet->ridges) {
    +          if (!otherfacet_(ridge, facet)->visible)
    +            ridge->seen= True;  /* an unattached ridge */
    +        }
    +      }
    +    }
    +    while ((facet= qh->facet_list)) {
    +      FOREACHridge_(facet->ridges) {
    +        if (ridge->seen) {
    +          qh_setfree(qh, &(ridge->vertices));
    +          qh_memfree(qh, ridge, (int)sizeof(ridgeT));
    +        }else
    +          ridge->seen= True;
    +      }
    +      qh_setfree(qh, &(facet->outsideset));
    +      qh_setfree(qh, &(facet->coplanarset));
    +      qh_setfree(qh, &(facet->neighbors));
    +      qh_setfree(qh, &(facet->ridges));
    +      qh_setfree(qh, &(facet->vertices));
    +      if (facet->next)
    +        qh_delfacet(qh, facet);
    +      else {
    +        qh_memfree(qh, facet, (int)sizeof(facetT));
    +        qh->visible_list= qh->newfacet_list= qh->facet_list= NULL;
    +      }
    +    }
    +  }else {
    +    FORALLfacets {
    +      qh_setfreelong(qh, &(facet->outsideset));
    +      qh_setfreelong(qh, &(facet->coplanarset));
    +      if (!facet->simplicial) {
    +        qh_setfreelong(qh, &(facet->neighbors));
    +        qh_setfreelong(qh, &(facet->ridges));
    +        qh_setfreelong(qh, &(facet->vertices));
    +      }
    +    }
    +  }
    +  qh_setfree(qh, &(qh->hash_table));
    +  qh_memfree(qh, qh->interior_point, qh->normal_size);
    +  qh->interior_point= NULL;
    +  FOREACHmerge_(qh->facet_mergeset)  /* usually empty */
    +    qh_memfree(qh, merge, (int)sizeof(mergeT));
    +  qh->facet_mergeset= NULL;  /* temp set */
    +  qh->degen_mergeset= NULL;  /* temp set */
    +  qh_settempfree_all(qh);
    +} /* freebuild */
    +
    +/*---------------------------------
    +
    +  qh_freeqhull(qh, allmem )
    +
    +  free global memory and set qhT to 0
    +  if !allmem,
    +    does not free short memory (freed by qh_memfreeshort unless qh_NOmem)
    +
    +notes:
    +  sets qh.NOerrexit in case caller forgets to
    +  Does not throw errors
    +
    +see:
    +  see qh_initqhull_start2()
    +  For libqhull_r, qhstatT is part of qhT
    +
    +design:
    +  free global and temporary memory from qh_initbuild and qh_buildhull
    +  free buffers
    +*/
    +void qh_freeqhull(qhT *qh, boolT allmem) {
    +
    +  qh->NOerrexit= True;  /* no more setjmp since called at exit and ~QhullQh */
    +  trace1((qh, qh->ferr, 1006, "qh_freeqhull: free global memory\n"));
    +  qh_freebuild(qh, allmem);
    +  qh_freebuffers(qh);
    +  /* memset is the same in qh_freeqhull() and qh_initqhull_start2() */
    +  memset((char *)qh, 0, sizeof(qhT)-sizeof(qhmemT)-sizeof(qhstatT));
    +  qh->NOerrexit= True;
    +} /* freeqhull2 */
    +
    +/*---------------------------------
    +
    +  qh_init_A(qh, infile, outfile, errfile, argc, argv )
    +    initialize memory and stdio files
    +    convert input options to option string (qh.qhull_command)
    +
    +  notes:
    +    infile may be NULL if qh_readpoints() is not called
    +
    +    errfile should always be defined.  It is used for reporting
    +    errors.  outfile is used for output and format options.
    +
    +    argc/argv may be 0/NULL
    +
    +    called before error handling initialized
    +    qh_errexit() may not be used
    +*/
    +void qh_init_A(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile, int argc, char *argv[]) {
    +  qh_meminit(qh, errfile);
    +  qh_initqhull_start(qh, infile, outfile, errfile);
    +  qh_init_qhull_command(qh, argc, argv);
    +} /* init_A */
    +
    +/*---------------------------------
    +
    +  qh_init_B(qh, points, numpoints, dim, ismalloc )
    +    initialize globals for points array
    +
    +    points has numpoints dim-dimensional points
    +      points[0] is the first coordinate of the first point
    +      points[1] is the second coordinate of the first point
    +      points[dim] is the first coordinate of the second point
    +
    +    ismalloc=True
    +      Qhull will call qh_free(points) on exit or input transformation
    +    ismalloc=False
    +      Qhull will allocate a new point array if needed for input transformation
    +
    +    qh.qhull_command
    +      is the option string.
    +      It is defined by qh_init_B(), qh_qhull_command(), or qh_initflags
    +
    +  returns:
    +    if qh.PROJECTinput or (qh.DELAUNAY and qh.PROJECTdelaunay)
    +      projects the input to a new point array
    +
    +        if qh.DELAUNAY,
    +          qh.hull_dim is increased by one
    +        if qh.ATinfinity,
    +          qh_projectinput adds point-at-infinity for Delaunay tri.
    +
    +    if qh.SCALEinput
    +      changes the upper and lower bounds of the input, see qh_scaleinput(qh)
    +
    +    if qh.ROTATEinput
    +      rotates the input by a random rotation, see qh_rotateinput()
    +      if qh.DELAUNAY
    +        rotates about the last coordinate
    +
    +  notes:
    +    called after points are defined
    +    qh_errexit() may be used
    +*/
    +void qh_init_B(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc) {
    +  qh_initqhull_globals(qh, points, numpoints, dim, ismalloc);
    +  if (qh->qhmem.LASTsize == 0)
    +    qh_initqhull_mem(qh);
    +  /* mem_r.c and qset_r.c are initialized */
    +  qh_initqhull_buffers(qh);
    +  qh_initthresholds(qh, qh->qhull_command);
    +  if (qh->PROJECTinput || (qh->DELAUNAY && qh->PROJECTdelaunay))
    +    qh_projectinput(qh);
    +  if (qh->SCALEinput)
    +    qh_scaleinput(qh);
    +  if (qh->ROTATErandom >= 0) {
    +    qh_randommatrix(qh, qh->gm_matrix, qh->hull_dim, qh->gm_row);
    +    if (qh->DELAUNAY) {
    +      int k, lastk= qh->hull_dim-1;
    +      for (k=0; k < lastk; k++) {
    +        qh->gm_row[k][lastk]= 0.0;
    +        qh->gm_row[lastk][k]= 0.0;
    +      }
    +      qh->gm_row[lastk][lastk]= 1.0;
    +    }
    +    qh_gram_schmidt(qh, qh->hull_dim, qh->gm_row);
    +    qh_rotateinput(qh, qh->gm_row);
    +  }
    +} /* init_B */
    +
    +/*---------------------------------
    +
    +  qh_init_qhull_command(qh, argc, argv )
    +    build qh.qhull_command from argc/argv
    +    Calls qh_exit if qhull_command is too short
    +
    +  returns:
    +    a space-delimited string of options (just as typed)
    +
    +  notes:
    +    makes option string easy to input and output
    +
    +    argc/argv may be 0/NULL
    +*/
    +void qh_init_qhull_command(qhT *qh, int argc, char *argv[]) {
    +
    +  if (!qh_argv_to_command(argc, argv, qh->qhull_command, (int)sizeof(qh->qhull_command))){
    +    /* Assumes qh.ferr is defined. */
    +    qh_fprintf(qh, qh->ferr, 6033, "qhull input error: more than %d characters in command line.\n",
    +          (int)sizeof(qh->qhull_command));
    +    qh_exit(qh_ERRinput);  /* error reported, can not use qh_errexit */
    +  }
    +} /* init_qhull_command */
    +
    +/*---------------------------------
    +
    +  qh_initflags(qh, commandStr )
    +    set flags and initialized constants from commandStr
    +    calls qh_exit() if qh->NOerrexit
    +
    +  returns:
    +    sets qh.qhull_command to command if needed
    +
    +  notes:
    +    ignores first word (e.g., "qhull d")
    +    use qh_strtol/strtod since strtol/strtod may or may not skip trailing spaces
    +
    +  see:
    +    qh_initthresholds() continues processing of 'Pdn' and 'PDn'
    +    'prompt' in unix_r.c for documentation
    +
    +  design:
    +    for each space-delimited option group
    +      if top-level option
    +        check syntax
    +        append appropriate option to option string
    +        set appropriate global variable or append printFormat to print options
    +      else
    +        for each sub-option
    +          check syntax
    +          append appropriate option to option string
    +          set appropriate global variable or append printFormat to print options
    +*/
    +void qh_initflags(qhT *qh, char *command) {
    +  int k, i, lastproject;
    +  char *s= command, *t, *prev_s, *start, key;
    +  boolT isgeom= False, wasproject;
    +  realT r;
    +
    +  if(qh->NOerrexit){
    +    qh_fprintf(qh, qh->ferr, 6245, "qhull initflags error: qh.NOerrexit was not cleared before calling qh_initflags().  It should be cleared after setjmp().  Exit qhull.");
    +    qh_exit(6245);
    +  }
    +  if (command <= &qh->qhull_command[0] || command > &qh->qhull_command[0] + sizeof(qh->qhull_command)) {
    +    if (command != &qh->qhull_command[0]) {
    +      *qh->qhull_command= '\0';
    +      strncat(qh->qhull_command, command, sizeof(qh->qhull_command)-strlen(qh->qhull_command)-1);
    +    }
    +    while (*s && !isspace(*s))  /* skip program name */
    +      s++;
    +  }
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    prev_s= s;
    +    switch (*s++) {
    +    case 'd':
    +      qh_option(qh, "delaunay", NULL, NULL);
    +      qh->DELAUNAY= True;
    +      break;
    +    case 'f':
    +      qh_option(qh, "facets", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTfacets);
    +      break;
    +    case 'i':
    +      qh_option(qh, "incidence", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTincidences);
    +      break;
    +    case 'm':
    +      qh_option(qh, "mathematica", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTmathematica);
    +      break;
    +    case 'n':
    +      qh_option(qh, "normals", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTnormals);
    +      break;
    +    case 'o':
    +      qh_option(qh, "offFile", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINToff);
    +      break;
    +    case 'p':
    +      qh_option(qh, "points", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTpoints);
    +      break;
    +    case 's':
    +      qh_option(qh, "summary", NULL, NULL);
    +      qh->PRINTsummary= True;
    +      break;
    +    case 'v':
    +      qh_option(qh, "voronoi", NULL, NULL);
    +      qh->VORONOI= True;
    +      qh->DELAUNAY= True;
    +      break;
    +    case 'A':
    +      if (!isdigit(*s) && *s != '.' && *s != '-')
    +        qh_fprintf(qh, qh->ferr, 7002, "qhull warning: no maximum cosine angle given for option 'An'.  Ignored.\n");
    +      else {
    +        if (*s == '-') {
    +          qh->premerge_cos= -qh_strtod(s, &s);
    +          qh_option(qh, "Angle-premerge-", NULL, &qh->premerge_cos);
    +          qh->PREmerge= True;
    +        }else {
    +          qh->postmerge_cos= qh_strtod(s, &s);
    +          qh_option(qh, "Angle-postmerge", NULL, &qh->postmerge_cos);
    +          qh->POSTmerge= True;
    +        }
    +        qh->MERGING= True;
    +      }
    +      break;
    +    case 'C':
    +      if (!isdigit(*s) && *s != '.' && *s != '-')
    +        qh_fprintf(qh, qh->ferr, 7003, "qhull warning: no centrum radius given for option 'Cn'.  Ignored.\n");
    +      else {
    +        if (*s == '-') {
    +          qh->premerge_centrum= -qh_strtod(s, &s);
    +          qh_option(qh, "Centrum-premerge-", NULL, &qh->premerge_centrum);
    +          qh->PREmerge= True;
    +        }else {
    +          qh->postmerge_centrum= qh_strtod(s, &s);
    +          qh_option(qh, "Centrum-postmerge", NULL, &qh->postmerge_centrum);
    +          qh->POSTmerge= True;
    +        }
    +        qh->MERGING= True;
    +      }
    +      break;
    +    case 'E':
    +      if (*s == '-')
    +        qh_fprintf(qh, qh->ferr, 7004, "qhull warning: negative maximum roundoff given for option 'An'.  Ignored.\n");
    +      else if (!isdigit(*s))
    +        qh_fprintf(qh, qh->ferr, 7005, "qhull warning: no maximum roundoff given for option 'En'.  Ignored.\n");
    +      else {
    +        qh->DISTround= qh_strtod(s, &s);
    +        qh_option(qh, "Distance-roundoff", NULL, &qh->DISTround);
    +        qh->SETroundoff= True;
    +      }
    +      break;
    +    case 'H':
    +      start= s;
    +      qh->HALFspace= True;
    +      qh_strtod(s, &t);
    +      while (t > s)  {
    +        if (*t && !isspace(*t)) {
    +          if (*t == ',')
    +            t++;
    +          else
    +            qh_fprintf(qh, qh->ferr, 7006, "qhull warning: origin for Halfspace intersection should be 'Hn,n,n,...'\n");
    +        }
    +        s= t;
    +        qh_strtod(s, &t);
    +      }
    +      if (start < t) {
    +        if (!(qh->feasible_string= (char*)calloc((size_t)(t-start+1), (size_t)1))) {
    +          qh_fprintf(qh, qh->ferr, 6034, "qhull error: insufficient memory for 'Hn,n,n'\n");
    +          qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +        }
    +        strncpy(qh->feasible_string, start, (size_t)(t-start));
    +        qh_option(qh, "Halfspace-about", NULL, NULL);
    +        qh_option(qh, qh->feasible_string, NULL, NULL);
    +      }else
    +        qh_option(qh, "Halfspace", NULL, NULL);
    +      break;
    +    case 'R':
    +      if (!isdigit(*s))
    +        qh_fprintf(qh, qh->ferr, 7007, "qhull warning: missing random perturbation for option 'Rn'.  Ignored\n");
    +      else {
    +        qh->RANDOMfactor= qh_strtod(s, &s);
    +        qh_option(qh, "Random_perturb", NULL, &qh->RANDOMfactor);
    +        qh->RANDOMdist= True;
    +      }
    +      break;
    +    case 'V':
    +      if (!isdigit(*s) && *s != '-')
    +        qh_fprintf(qh, qh->ferr, 7008, "qhull warning: missing visible distance for option 'Vn'.  Ignored\n");
    +      else {
    +        qh->MINvisible= qh_strtod(s, &s);
    +        qh_option(qh, "Visible", NULL, &qh->MINvisible);
    +      }
    +      break;
    +    case 'U':
    +      if (!isdigit(*s) && *s != '-')
    +        qh_fprintf(qh, qh->ferr, 7009, "qhull warning: missing coplanar distance for option 'Un'.  Ignored\n");
    +      else {
    +        qh->MAXcoplanar= qh_strtod(s, &s);
    +        qh_option(qh, "U-coplanar", NULL, &qh->MAXcoplanar);
    +      }
    +      break;
    +    case 'W':
    +      if (*s == '-')
    +        qh_fprintf(qh, qh->ferr, 7010, "qhull warning: negative outside width for option 'Wn'.  Ignored.\n");
    +      else if (!isdigit(*s))
    +        qh_fprintf(qh, qh->ferr, 7011, "qhull warning: missing outside width for option 'Wn'.  Ignored\n");
    +      else {
    +        qh->MINoutside= qh_strtod(s, &s);
    +        qh_option(qh, "W-outside", NULL, &qh->MINoutside);
    +        qh->APPROXhull= True;
    +      }
    +      break;
    +    /************  sub menus ***************/
    +    case 'F':
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'a':
    +          qh_option(qh, "Farea", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTarea);
    +          qh->GETarea= True;
    +          break;
    +        case 'A':
    +          qh_option(qh, "FArea-total", NULL, NULL);
    +          qh->GETarea= True;
    +          break;
    +        case 'c':
    +          qh_option(qh, "Fcoplanars", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTcoplanars);
    +          break;
    +        case 'C':
    +          qh_option(qh, "FCentrums", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTcentrums);
    +          break;
    +        case 'd':
    +          qh_option(qh, "Fd-cdd-in", NULL, NULL);
    +          qh->CDDinput= True;
    +          break;
    +        case 'D':
    +          qh_option(qh, "FD-cdd-out", NULL, NULL);
    +          qh->CDDoutput= True;
    +          break;
    +        case 'F':
    +          qh_option(qh, "FFacets-xridge", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTfacets_xridge);
    +          break;
    +        case 'i':
    +          qh_option(qh, "Finner", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTinner);
    +          break;
    +        case 'I':
    +          qh_option(qh, "FIDs", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTids);
    +          break;
    +        case 'm':
    +          qh_option(qh, "Fmerges", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTmerges);
    +          break;
    +        case 'M':
    +          qh_option(qh, "FMaple", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTmaple);
    +          break;
    +        case 'n':
    +          qh_option(qh, "Fneighbors", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTneighbors);
    +          break;
    +        case 'N':
    +          qh_option(qh, "FNeighbors-vertex", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTvneighbors);
    +          break;
    +        case 'o':
    +          qh_option(qh, "Fouter", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTouter);
    +          break;
    +        case 'O':
    +          if (qh->PRINToptions1st) {
    +            qh_option(qh, "FOptions", NULL, NULL);
    +            qh_appendprint(qh, qh_PRINToptions);
    +          }else
    +            qh->PRINToptions1st= True;
    +          break;
    +        case 'p':
    +          qh_option(qh, "Fpoint-intersect", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTpointintersect);
    +          break;
    +        case 'P':
    +          qh_option(qh, "FPoint-nearest", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTpointnearest);
    +          break;
    +        case 'Q':
    +          qh_option(qh, "FQhull", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTqhull);
    +          break;
    +        case 's':
    +          qh_option(qh, "Fsummary", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTsummary);
    +          break;
    +        case 'S':
    +          qh_option(qh, "FSize", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTsize);
    +          qh->GETarea= True;
    +          break;
    +        case 't':
    +          qh_option(qh, "Ftriangles", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTtriangles);
    +          break;
    +        case 'v':
    +          /* option set in qh_initqhull_globals */
    +          qh_appendprint(qh, qh_PRINTvertices);
    +          break;
    +        case 'V':
    +          qh_option(qh, "FVertex-average", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTaverage);
    +          break;
    +        case 'x':
    +          qh_option(qh, "Fxtremes", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTextremes);
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7012, "qhull warning: unknown 'F' output option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'G':
    +      isgeom= True;
    +      qh_appendprint(qh, qh_PRINTgeom);
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'a':
    +          qh_option(qh, "Gall-points", NULL, NULL);
    +          qh->PRINTdots= True;
    +          break;
    +        case 'c':
    +          qh_option(qh, "Gcentrums", NULL, NULL);
    +          qh->PRINTcentrums= True;
    +          break;
    +        case 'h':
    +          qh_option(qh, "Gintersections", NULL, NULL);
    +          qh->DOintersections= True;
    +          break;
    +        case 'i':
    +          qh_option(qh, "Ginner", NULL, NULL);
    +          qh->PRINTinner= True;
    +          break;
    +        case 'n':
    +          qh_option(qh, "Gno-planes", NULL, NULL);
    +          qh->PRINTnoplanes= True;
    +          break;
    +        case 'o':
    +          qh_option(qh, "Gouter", NULL, NULL);
    +          qh->PRINTouter= True;
    +          break;
    +        case 'p':
    +          qh_option(qh, "Gpoints", NULL, NULL);
    +          qh->PRINTcoplanar= True;
    +          break;
    +        case 'r':
    +          qh_option(qh, "Gridges", NULL, NULL);
    +          qh->PRINTridges= True;
    +          break;
    +        case 't':
    +          qh_option(qh, "Gtransparent", NULL, NULL);
    +          qh->PRINTtransparent= True;
    +          break;
    +        case 'v':
    +          qh_option(qh, "Gvertices", NULL, NULL);
    +          qh->PRINTspheres= True;
    +          break;
    +        case 'D':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6035, "qhull input error: missing dimension for option 'GDn'\n");
    +          else {
    +            if (qh->DROPdim >= 0)
    +              qh_fprintf(qh, qh->ferr, 7013, "qhull warning: can only drop one dimension.  Previous 'GD%d' ignored\n",
    +                   qh->DROPdim);
    +            qh->DROPdim= qh_strtol(s, &s);
    +            qh_option(qh, "GDrop-dim", &qh->DROPdim, NULL);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7014, "qhull warning: unknown 'G' print option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'P':
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'd': case 'D':  /* see qh_initthresholds() */
    +          key= s[-1];
    +          i= qh_strtol(s, &s);
    +          r= 0;
    +          if (*s == ':') {
    +            s++;
    +            r= qh_strtod(s, &s);
    +          }
    +          if (key == 'd')
    +            qh_option(qh, "Pdrop-facets-dim-less", &i, &r);
    +          else
    +            qh_option(qh, "PDrop-facets-dim-more", &i, &r);
    +          break;
    +        case 'g':
    +          qh_option(qh, "Pgood-facets", NULL, NULL);
    +          qh->PRINTgood= True;
    +          break;
    +        case 'G':
    +          qh_option(qh, "PGood-facet-neighbors", NULL, NULL);
    +          qh->PRINTneighbors= True;
    +          break;
    +        case 'o':
    +          qh_option(qh, "Poutput-forced", NULL, NULL);
    +          qh->FORCEoutput= True;
    +          break;
    +        case 'p':
    +          qh_option(qh, "Pprecision-ignore", NULL, NULL);
    +          qh->PRINTprecision= False;
    +          break;
    +        case 'A':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6036, "qhull input error: missing facet count for keep area option 'PAn'\n");
    +          else {
    +            qh->KEEParea= qh_strtol(s, &s);
    +            qh_option(qh, "PArea-keep", &qh->KEEParea, NULL);
    +            qh->GETarea= True;
    +          }
    +          break;
    +        case 'F':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6037, "qhull input error: missing facet area for option 'PFn'\n");
    +          else {
    +            qh->KEEPminArea= qh_strtod(s, &s);
    +            qh_option(qh, "PFacet-area-keep", NULL, &qh->KEEPminArea);
    +            qh->GETarea= True;
    +          }
    +          break;
    +        case 'M':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6038, "qhull input error: missing merge count for option 'PMn'\n");
    +          else {
    +            qh->KEEPmerge= qh_strtol(s, &s);
    +            qh_option(qh, "PMerge-keep", &qh->KEEPmerge, NULL);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7015, "qhull warning: unknown 'P' print option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'Q':
    +      lastproject= -1;
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'b': case 'B':  /* handled by qh_initthresholds */
    +          key= s[-1];
    +          if (key == 'b' && *s == 'B') {
    +            s++;
    +            r= qh_DEFAULTbox;
    +            qh->SCALEinput= True;
    +            qh_option(qh, "QbBound-unit-box", NULL, &r);
    +            break;
    +          }
    +          if (key == 'b' && *s == 'b') {
    +            s++;
    +            qh->SCALElast= True;
    +            qh_option(qh, "Qbbound-last", NULL, NULL);
    +            break;
    +          }
    +          k= qh_strtol(s, &s);
    +          r= 0.0;
    +          wasproject= False;
    +          if (*s == ':') {
    +            s++;
    +            if ((r= qh_strtod(s, &s)) == 0.0) {
    +              t= s;            /* need true dimension for memory allocation */
    +              while (*t && !isspace(*t)) {
    +                if (toupper(*t++) == 'B'
    +                 && k == qh_strtol(t, &t)
    +                 && *t++ == ':'
    +                 && qh_strtod(t, &t) == 0.0) {
    +                  qh->PROJECTinput++;
    +                  trace2((qh, qh->ferr, 2004, "qh_initflags: project dimension %d\n", k));
    +                  qh_option(qh, "Qb-project-dim", &k, NULL);
    +                  wasproject= True;
    +                  lastproject= k;
    +                  break;
    +                }
    +              }
    +            }
    +          }
    +          if (!wasproject) {
    +            if (lastproject == k && r == 0.0)
    +              lastproject= -1;  /* doesn't catch all possible sequences */
    +            else if (key == 'b') {
    +              qh->SCALEinput= True;
    +              if (r == 0.0)
    +                r= -qh_DEFAULTbox;
    +              qh_option(qh, "Qbound-dim-low", &k, &r);
    +            }else {
    +              qh->SCALEinput= True;
    +              if (r == 0.0)
    +                r= qh_DEFAULTbox;
    +              qh_option(qh, "QBound-dim-high", &k, &r);
    +            }
    +          }
    +          break;
    +        case 'c':
    +          qh_option(qh, "Qcoplanar-keep", NULL, NULL);
    +          qh->KEEPcoplanar= True;
    +          break;
    +        case 'f':
    +          qh_option(qh, "Qfurthest-outside", NULL, NULL);
    +          qh->BESToutside= True;
    +          break;
    +        case 'g':
    +          qh_option(qh, "Qgood-facets-only", NULL, NULL);
    +          qh->ONLYgood= True;
    +          break;
    +        case 'i':
    +          qh_option(qh, "Qinterior-keep", NULL, NULL);
    +          qh->KEEPinside= True;
    +          break;
    +        case 'm':
    +          qh_option(qh, "Qmax-outside-only", NULL, NULL);
    +          qh->ONLYmax= True;
    +          break;
    +        case 'r':
    +          qh_option(qh, "Qrandom-outside", NULL, NULL);
    +          qh->RANDOMoutside= True;
    +          break;
    +        case 's':
    +          qh_option(qh, "Qsearch-initial-simplex", NULL, NULL);
    +          qh->ALLpoints= True;
    +          break;
    +        case 't':
    +          qh_option(qh, "Qtriangulate", NULL, NULL);
    +          qh->TRIangulate= True;
    +          break;
    +        case 'T':
    +          qh_option(qh, "QTestPoints", NULL, NULL);
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6039, "qhull input error: missing number of test points for option 'QTn'\n");
    +          else {
    +            qh->TESTpoints= qh_strtol(s, &s);
    +            qh_option(qh, "QTestPoints", &qh->TESTpoints, NULL);
    +          }
    +          break;
    +        case 'u':
    +          qh_option(qh, "QupperDelaunay", NULL, NULL);
    +          qh->UPPERdelaunay= True;
    +          break;
    +        case 'v':
    +          qh_option(qh, "Qvertex-neighbors-convex", NULL, NULL);
    +          qh->TESTvneighbors= True;
    +          break;
    +        case 'x':
    +          qh_option(qh, "Qxact-merge", NULL, NULL);
    +          qh->MERGEexact= True;
    +          break;
    +        case 'z':
    +          qh_option(qh, "Qz-infinity-point", NULL, NULL);
    +          qh->ATinfinity= True;
    +          break;
    +        case '0':
    +          qh_option(qh, "Q0-no-premerge", NULL, NULL);
    +          qh->NOpremerge= True;
    +          break;
    +        case '1':
    +          if (!isdigit(*s)) {
    +            qh_option(qh, "Q1-no-angle-sort", NULL, NULL);
    +            qh->ANGLEmerge= False;
    +            break;
    +          }
    +          switch (*s++) {
    +          case '0':
    +            qh_option(qh, "Q10-no-narrow", NULL, NULL);
    +            qh->NOnarrow= True;
    +            break;
    +          case '1':
    +            qh_option(qh, "Q11-trinormals Qtriangulate", NULL, NULL);
    +            qh->TRInormals= True;
    +            qh->TRIangulate= True;
    +            break;
    +          case '2':
    +              qh_option(qh, "Q12-no-wide-dup", NULL, NULL);
    +              qh->NOwide= True;
    +            break;
    +          default:
    +            s--;
    +            qh_fprintf(qh, qh->ferr, 7016, "qhull warning: unknown 'Q' qhull option 1%c, rest ignored\n", (int)s[0]);
    +            while (*++s && !isspace(*s));
    +            break;
    +          }
    +          break;
    +        case '2':
    +          qh_option(qh, "Q2-no-merge-independent", NULL, NULL);
    +          qh->MERGEindependent= False;
    +          goto LABELcheckdigit;
    +          break; /* no warnings */
    +        case '3':
    +          qh_option(qh, "Q3-no-merge-vertices", NULL, NULL);
    +          qh->MERGEvertices= False;
    +        LABELcheckdigit:
    +          if (isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7017, "qhull warning: can not follow '1', '2', or '3' with a digit.  '%c' skipped.\n",
    +                     *s++);
    +          break;
    +        case '4':
    +          qh_option(qh, "Q4-avoid-old-into-new", NULL, NULL);
    +          qh->AVOIDold= True;
    +          break;
    +        case '5':
    +          qh_option(qh, "Q5-no-check-outer", NULL, NULL);
    +          qh->SKIPcheckmax= True;
    +          break;
    +        case '6':
    +          qh_option(qh, "Q6-no-concave-merge", NULL, NULL);
    +          qh->SKIPconvex= True;
    +          break;
    +        case '7':
    +          qh_option(qh, "Q7-no-breadth-first", NULL, NULL);
    +          qh->VIRTUALmemory= True;
    +          break;
    +        case '8':
    +          qh_option(qh, "Q8-no-near-inside", NULL, NULL);
    +          qh->NOnearinside= True;
    +          break;
    +        case '9':
    +          qh_option(qh, "Q9-pick-furthest", NULL, NULL);
    +          qh->PICKfurthest= True;
    +          break;
    +        case 'G':
    +          i= qh_strtol(s, &t);
    +          if (qh->GOODpoint)
    +            qh_fprintf(qh, qh->ferr, 7018, "qhull warning: good point already defined for option 'QGn'.  Ignored\n");
    +          else if (s == t)
    +            qh_fprintf(qh, qh->ferr, 7019, "qhull warning: missing good point id for option 'QGn'.  Ignored\n");
    +          else if (i < 0 || *s == '-') {
    +            qh->GOODpoint= i-1;
    +            qh_option(qh, "QGood-if-dont-see-point", &i, NULL);
    +          }else {
    +            qh->GOODpoint= i+1;
    +            qh_option(qh, "QGood-if-see-point", &i, NULL);
    +          }
    +          s= t;
    +          break;
    +        case 'J':
    +          if (!isdigit(*s) && *s != '-')
    +            qh->JOGGLEmax= 0.0;
    +          else {
    +            qh->JOGGLEmax= (realT) qh_strtod(s, &s);
    +            qh_option(qh, "QJoggle", NULL, &qh->JOGGLEmax);
    +          }
    +          break;
    +        case 'R':
    +          if (!isdigit(*s) && *s != '-')
    +            qh_fprintf(qh, qh->ferr, 7020, "qhull warning: missing random seed for option 'QRn'.  Ignored\n");
    +          else {
    +            qh->ROTATErandom= i= qh_strtol(s, &s);
    +            if (i > 0)
    +              qh_option(qh, "QRotate-id", &i, NULL );
    +            else if (i < -1)
    +              qh_option(qh, "QRandom-seed", &i, NULL );
    +          }
    +          break;
    +        case 'V':
    +          i= qh_strtol(s, &t);
    +          if (qh->GOODvertex)
    +            qh_fprintf(qh, qh->ferr, 7021, "qhull warning: good vertex already defined for option 'QVn'.  Ignored\n");
    +          else if (s == t)
    +            qh_fprintf(qh, qh->ferr, 7022, "qhull warning: no good point id given for option 'QVn'.  Ignored\n");
    +          else if (i < 0) {
    +            qh->GOODvertex= i - 1;
    +            qh_option(qh, "QV-good-facets-not-point", &i, NULL);
    +          }else {
    +            qh_option(qh, "QV-good-facets-point", &i, NULL);
    +            qh->GOODvertex= i + 1;
    +          }
    +          s= t;
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7023, "qhull warning: unknown 'Q' qhull option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'T':
    +      while (*s && !isspace(*s)) {
    +        if (isdigit(*s) || *s == '-')
    +          qh->IStracing= qh_strtol(s, &s);
    +        else switch (*s++) {
    +        case 'a':
    +          qh_option(qh, "Tannotate-output", NULL, NULL);
    +          qh->ANNOTATEoutput= True;
    +          break;
    +        case 'c':
    +          qh_option(qh, "Tcheck-frequently", NULL, NULL);
    +          qh->CHECKfrequently= True;
    +          break;
    +        case 's':
    +          qh_option(qh, "Tstatistics", NULL, NULL);
    +          qh->PRINTstatistics= True;
    +          break;
    +        case 'v':
    +          qh_option(qh, "Tverify", NULL, NULL);
    +          qh->VERIFYoutput= True;
    +          break;
    +        case 'z':
    +          if (qh->ferr == qh_FILEstderr) {
    +            /* The C++ interface captures the output in qh_fprint_qhull() */
    +            qh_option(qh, "Tz-stdout", NULL, NULL);
    +            qh->USEstdout= True;
    +          }else if (!qh->fout)
    +            qh_fprintf(qh, qh->ferr, 7024, "qhull warning: output file undefined(stdout).  Option 'Tz' ignored.\n");
    +          else {
    +            qh_option(qh, "Tz-stdout", NULL, NULL);
    +            qh->USEstdout= True;
    +            qh->ferr= qh->fout;
    +            qh->qhmem.ferr= qh->fout;
    +          }
    +          break;
    +        case 'C':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7025, "qhull warning: missing point id for cone for trace option 'TCn'.  Ignored\n");
    +          else {
    +            i= qh_strtol(s, &s);
    +            qh_option(qh, "TCone-stop", &i, NULL);
    +            qh->STOPcone= i + 1;
    +          }
    +          break;
    +        case 'F':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7026, "qhull warning: missing frequency count for trace option 'TFn'.  Ignored\n");
    +          else {
    +            qh->REPORTfreq= qh_strtol(s, &s);
    +            qh_option(qh, "TFacet-log", &qh->REPORTfreq, NULL);
    +            qh->REPORTfreq2= qh->REPORTfreq/2;  /* for tracemerging() */
    +          }
    +          break;
    +        case 'I':
    +          if (!isspace(*s))
    +            qh_fprintf(qh, qh->ferr, 7027, "qhull warning: missing space between 'TI' and filename, %s\n", s);
    +          while (isspace(*s))
    +            s++;
    +          t= qh_skipfilename(qh, s);
    +          {
    +            char filename[qh_FILENAMElen];
    +
    +            qh_copyfilename(qh, filename, (int)sizeof(filename), s, (int)(t-s));   /* WARN64 */
    +            s= t;
    +            if (!freopen(filename, "r", stdin)) {
    +              qh_fprintf(qh, qh->ferr, 6041, "qhull error: could not open file \"%s\".", filename);
    +              qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +            }else {
    +              qh_option(qh, "TInput-file", NULL, NULL);
    +              qh_option(qh, filename, NULL, NULL);
    +            }
    +          }
    +          break;
    +        case 'O':
    +            if (!isspace(*s))
    +                qh_fprintf(qh, qh->ferr, 7028, "qhull warning: missing space between 'TO' and filename, %s\n", s);
    +            while (isspace(*s))
    +                s++;
    +            t= qh_skipfilename(qh, s);
    +            {
    +              char filename[qh_FILENAMElen];
    +
    +              qh_copyfilename(qh, filename, (int)sizeof(filename), s, (int)(t-s));  /* WARN64 */
    +              s= t;
    +              if (!qh->fout) {
    +                qh_fprintf(qh, qh->ferr, 6266, "qhull input warning: qh.fout was not set by caller.  Cannot use option 'TO' to redirect output.  Ignoring option 'TO'\n");
    +              }else if (!freopen(filename, "w", qh->fout)) {
    +                qh_fprintf(qh, qh->ferr, 6044, "qhull error: could not open file \"%s\".", filename);
    +                qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +              }else {
    +                qh_option(qh, "TOutput-file", NULL, NULL);
    +              qh_option(qh, filename, NULL, NULL);
    +            }
    +          }
    +          break;
    +        case 'P':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7029, "qhull warning: missing point id for trace option 'TPn'.  Ignored\n");
    +          else {
    +            qh->TRACEpoint= qh_strtol(s, &s);
    +            qh_option(qh, "Trace-point", &qh->TRACEpoint, NULL);
    +          }
    +          break;
    +        case 'M':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7030, "qhull warning: missing merge id for trace option 'TMn'.  Ignored\n");
    +          else {
    +            qh->TRACEmerge= qh_strtol(s, &s);
    +            qh_option(qh, "Trace-merge", &qh->TRACEmerge, NULL);
    +          }
    +          break;
    +        case 'R':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7031, "qhull warning: missing rerun count for trace option 'TRn'.  Ignored\n");
    +          else {
    +            qh->RERUN= qh_strtol(s, &s);
    +            qh_option(qh, "TRerun", &qh->RERUN, NULL);
    +          }
    +          break;
    +        case 'V':
    +          i= qh_strtol(s, &t);
    +          if (s == t)
    +            qh_fprintf(qh, qh->ferr, 7032, "qhull warning: missing furthest point id for trace option 'TVn'.  Ignored\n");
    +          else if (i < 0) {
    +            qh->STOPpoint= i - 1;
    +            qh_option(qh, "TV-stop-before-point", &i, NULL);
    +          }else {
    +            qh->STOPpoint= i + 1;
    +            qh_option(qh, "TV-stop-after-point", &i, NULL);
    +          }
    +          s= t;
    +          break;
    +        case 'W':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7033, "qhull warning: missing max width for trace option 'TWn'.  Ignored\n");
    +          else {
    +            qh->TRACEdist= (realT) qh_strtod(s, &s);
    +            qh_option(qh, "TWide-trace", NULL, &qh->TRACEdist);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7034, "qhull warning: unknown 'T' trace option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    default:
    +      qh_fprintf(qh, qh->ferr, 7035, "qhull warning: unknown flag %c(%x)\n", (int)s[-1],
    +               (int)s[-1]);
    +      break;
    +    }
    +    if (s-1 == prev_s && *s && !isspace(*s)) {
    +      qh_fprintf(qh, qh->ferr, 7036, "qhull warning: missing space after flag %c(%x); reserved for menu. Skipped.\n",
    +               (int)*prev_s, (int)*prev_s);
    +      while (*s && !isspace(*s))
    +        s++;
    +    }
    +  }
    +  if (qh->STOPcone && qh->JOGGLEmax < REALmax/2)
    +    qh_fprintf(qh, qh->ferr, 7078, "qhull warning: 'TCn' (stopCone) ignored when used with 'QJn' (joggle)\n");
    +  if (isgeom && !qh->FORCEoutput && qh->PRINTout[1])
    +    qh_fprintf(qh, qh->ferr, 7037, "qhull warning: additional output formats are not compatible with Geomview\n");
    +  /* set derived values in qh_initqhull_globals */
    +} /* initflags */
    +
    +
    +/*---------------------------------
    +
    +  qh_initqhull_buffers(qh)
    +    initialize global memory buffers
    +
    +  notes:
    +    must match qh_freebuffers()
    +*/
    +void qh_initqhull_buffers(qhT *qh) {
    +  int k;
    +
    +  qh->TEMPsize= (qh->qhmem.LASTsize - sizeof(setT))/SETelemsize;
    +  if (qh->TEMPsize <= 0 || qh->TEMPsize > qh->qhmem.LASTsize)
    +    qh->TEMPsize= 8;  /* e.g., if qh_NOmem */
    +  qh->other_points= qh_setnew(qh, qh->TEMPsize);
    +  qh->del_vertices= qh_setnew(qh, qh->TEMPsize);
    +  qh->coplanarfacetset= qh_setnew(qh, qh->TEMPsize);
    +  qh->NEARzero= (realT *)qh_memalloc(qh, qh->hull_dim * sizeof(realT));
    +  qh->lower_threshold= (realT *)qh_memalloc(qh, (qh->input_dim+1) * sizeof(realT));
    +  qh->upper_threshold= (realT *)qh_memalloc(qh, (qh->input_dim+1) * sizeof(realT));
    +  qh->lower_bound= (realT *)qh_memalloc(qh, (qh->input_dim+1) * sizeof(realT));
    +  qh->upper_bound= (realT *)qh_memalloc(qh, (qh->input_dim+1) * sizeof(realT));
    +  for (k=qh->input_dim+1; k--; ) {  /* duplicated in qh_initqhull_buffers and qh_clear_outputflags */
    +    qh->lower_threshold[k]= -REALmax;
    +    qh->upper_threshold[k]= REALmax;
    +    qh->lower_bound[k]= -REALmax;
    +    qh->upper_bound[k]= REALmax;
    +  }
    +  qh->gm_matrix= (coordT *)qh_memalloc(qh, (qh->hull_dim+1) * qh->hull_dim * sizeof(coordT));
    +  qh->gm_row= (coordT **)qh_memalloc(qh, (qh->hull_dim+1) * sizeof(coordT *));
    +} /* initqhull_buffers */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_globals(qh, points, numpoints, dim, ismalloc )
    +    initialize globals
    +    if ismalloc
    +      points were malloc'd and qhull should free at end
    +
    +  returns:
    +    sets qh.first_point, num_points, input_dim, hull_dim and others
    +    seeds random number generator (seed=1 if tracing)
    +    modifies qh.hull_dim if ((qh.DELAUNAY and qh.PROJECTdelaunay) or qh.PROJECTinput)
    +    adjust user flags as needed
    +    also checks DIM3 dependencies and constants
    +
    +  notes:
    +    do not use qh_point() since an input transformation may move them elsewhere
    +
    +  see:
    +    qh_initqhull_start() sets default values for non-zero globals
    +
    +  design:
    +    initialize points array from input arguments
    +    test for qh.ZEROcentrum
    +      (i.e., use opposite vertex instead of cetrum for convexity testing)
    +    initialize qh.CENTERtype, qh.normal_size,
    +      qh.center_size, qh.TRACEpoint/level,
    +    initialize and test random numbers
    +    qh_initqhull_outputflags() -- adjust and test output flags
    +*/
    +void qh_initqhull_globals(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc) {
    +  int seed, pointsneeded, extra= 0, i, randi, k;
    +  realT randr;
    +  realT factorial;
    +
    +  time_t timedata;
    +
    +  trace0((qh, qh->ferr, 13, "qh_initqhull_globals: for %s | %s\n", qh->rbox_command,
    +      qh->qhull_command));
    +  qh->POINTSmalloc= ismalloc;
    +  qh->first_point= points;
    +  qh->num_points= numpoints;
    +  qh->hull_dim= qh->input_dim= dim;
    +  if (!qh->NOpremerge && !qh->MERGEexact && !qh->PREmerge && qh->JOGGLEmax > REALmax/2) {
    +    qh->MERGING= True;
    +    if (qh->hull_dim <= 4) {
    +      qh->PREmerge= True;
    +      qh_option(qh, "_pre-merge", NULL, NULL);
    +    }else {
    +      qh->MERGEexact= True;
    +      qh_option(qh, "Qxact_merge", NULL, NULL);
    +    }
    +  }else if (qh->MERGEexact)
    +    qh->MERGING= True;
    +  if (!qh->NOpremerge && qh->JOGGLEmax > REALmax/2) {
    +#ifdef qh_NOmerge
    +    qh->JOGGLEmax= 0.0;
    +#endif
    +  }
    +  if (qh->TRIangulate && qh->JOGGLEmax < REALmax/2 && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 7038, "qhull warning: joggle('QJ') always produces simplicial output.  Triangulated output('Qt') does nothing.\n");
    +  if (qh->JOGGLEmax < REALmax/2 && qh->DELAUNAY && !qh->SCALEinput && !qh->SCALElast) {
    +    qh->SCALElast= True;
    +    qh_option(qh, "Qbbound-last-qj", NULL, NULL);
    +  }
    +  if (qh->MERGING && !qh->POSTmerge && qh->premerge_cos > REALmax/2
    +  && qh->premerge_centrum == 0) {
    +    qh->ZEROcentrum= True;
    +    qh->ZEROall_ok= True;
    +    qh_option(qh, "_zero-centrum", NULL, NULL);
    +  }
    +  if (qh->JOGGLEmax < REALmax/2 && REALepsilon > 2e-8 && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 7039, "qhull warning: real epsilon, %2.2g, is probably too large for joggle('QJn')\nRecompile with double precision reals(see user.h).\n",
    +          REALepsilon);
    +#ifdef qh_NOmerge
    +  if (qh->MERGING) {
    +    qh_fprintf(qh, qh->ferr, 6045, "qhull input error: merging not installed(qh_NOmerge + 'Qx', 'Cn' or 'An')\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +#endif
    +  if (qh->DELAUNAY && qh->KEEPcoplanar && !qh->KEEPinside) {
    +    qh->KEEPinside= True;
    +    qh_option(qh, "Qinterior-keep", NULL, NULL);
    +  }
    +  if (qh->DELAUNAY && qh->HALFspace) {
    +    qh_fprintf(qh, qh->ferr, 6046, "qhull input error: can not use Delaunay('d') or Voronoi('v') with halfspace intersection('H')\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (!qh->DELAUNAY && (qh->UPPERdelaunay || qh->ATinfinity)) {
    +    qh_fprintf(qh, qh->ferr, 6047, "qhull input error: use upper-Delaunay('Qu') or infinity-point('Qz') with Delaunay('d') or Voronoi('v')\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh->UPPERdelaunay && qh->ATinfinity) {
    +    qh_fprintf(qh, qh->ferr, 6048, "qhull input error: can not use infinity-point('Qz') with upper-Delaunay('Qu')\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh->SCALElast && !qh->DELAUNAY && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 7040, "qhull input warning: option 'Qbb' (scale-last-coordinate) is normally used with 'd' or 'v'\n");
    +  qh->DOcheckmax= (!qh->SKIPcheckmax && qh->MERGING );
    +  qh->KEEPnearinside= (qh->DOcheckmax && !(qh->KEEPinside && qh->KEEPcoplanar)
    +                          && !qh->NOnearinside);
    +  if (qh->MERGING)
    +    qh->CENTERtype= qh_AScentrum;
    +  else if (qh->VORONOI)
    +    qh->CENTERtype= qh_ASvoronoi;
    +  if (qh->TESTvneighbors && !qh->MERGING) {
    +    qh_fprintf(qh, qh->ferr, 6049, "qhull input error: test vertex neighbors('Qv') needs a merge option\n");
    +    qh_errexit(qh, qh_ERRinput, NULL ,NULL);
    +  }
    +  if (qh->PROJECTinput || (qh->DELAUNAY && qh->PROJECTdelaunay)) {
    +    qh->hull_dim -= qh->PROJECTinput;
    +    if (qh->DELAUNAY) {
    +      qh->hull_dim++;
    +      if (qh->ATinfinity)
    +        extra= 1;
    +    }
    +  }
    +  if (qh->hull_dim <= 1) {
    +    qh_fprintf(qh, qh->ferr, 6050, "qhull error: dimension %d must be > 1\n", qh->hull_dim);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  for (k=2, factorial=1.0; k < qh->hull_dim; k++)
    +    factorial *= k;
    +  qh->AREAfactor= 1.0 / factorial;
    +  trace2((qh, qh->ferr, 2005, "qh_initqhull_globals: initialize globals.  dim %d numpoints %d malloc? %d projected %d to hull_dim %d\n",
    +        dim, numpoints, ismalloc, qh->PROJECTinput, qh->hull_dim));
    +  qh->normal_size= qh->hull_dim * sizeof(coordT);
    +  qh->center_size= qh->normal_size - sizeof(coordT);
    +  pointsneeded= qh->hull_dim+1;
    +  if (qh->hull_dim > qh_DIMmergeVertex) {
    +    qh->MERGEvertices= False;
    +    qh_option(qh, "Q3-no-merge-vertices-dim-high", NULL, NULL);
    +  }
    +  if (qh->GOODpoint)
    +    pointsneeded++;
    +#ifdef qh_NOtrace
    +  if (qh->IStracing) {
    +    qh_fprintf(qh, qh->ferr, 6051, "qhull input error: tracing is not installed(qh_NOtrace in user.h)");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +#endif
    +  if (qh->RERUN > 1) {
    +    qh->TRACElastrun= qh->IStracing; /* qh_build_withrestart duplicates next conditional */
    +    if (qh->IStracing != -1)
    +      qh->IStracing= 0;
    +  }else if (qh->TRACEpoint != qh_IDunknown || qh->TRACEdist < REALmax/2 || qh->TRACEmerge) {
    +    qh->TRACElevel= (qh->IStracing? qh->IStracing : 3);
    +    qh->IStracing= 0;
    +  }
    +  if (qh->ROTATErandom == 0 || qh->ROTATErandom == -1) {
    +    seed= (int)time(&timedata);
    +    if (qh->ROTATErandom  == -1) {
    +      seed= -seed;
    +      qh_option(qh, "QRandom-seed", &seed, NULL );
    +    }else
    +      qh_option(qh, "QRotate-random", &seed, NULL);
    +    qh->ROTATErandom= seed;
    +  }
    +  seed= qh->ROTATErandom;
    +  if (seed == INT_MIN)    /* default value */
    +    seed= 1;
    +  else if (seed < 0)
    +    seed= -seed;
    +  qh_RANDOMseed_(qh, seed);
    +  randr= 0.0;
    +  for (i=1000; i--; ) {
    +    randi= qh_RANDOMint;
    +    randr += randi;
    +    if (randi > qh_RANDOMmax) {
    +      qh_fprintf(qh, qh->ferr, 8036, "\
    +qhull configuration error (qh_RANDOMmax in user.h):\n\
    +   random integer %d > qh_RANDOMmax(qh, %.8g)\n",
    +               randi, qh_RANDOMmax);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +  }
    +  qh_RANDOMseed_(qh, seed);
    +  randr = randr/1000;
    +  if (randr < qh_RANDOMmax * 0.1
    +  || randr > qh_RANDOMmax * 0.9)
    +    qh_fprintf(qh, qh->ferr, 8037, "\
    +qhull configuration warning (qh_RANDOMmax in user.h):\n\
    +   average of 1000 random integers (%.2g) is much different than expected (%.2g).\n\
    +   Is qh_RANDOMmax (%.2g) wrong?\n",
    +             randr, qh_RANDOMmax * 0.5, qh_RANDOMmax);
    +  qh->RANDOMa= 2.0 * qh->RANDOMfactor/qh_RANDOMmax;
    +  qh->RANDOMb= 1.0 - qh->RANDOMfactor;
    +  if (qh_HASHfactor < 1.1) {
    +    qh_fprintf(qh, qh->ferr, 6052, "qhull internal error (qh_initqhull_globals): qh_HASHfactor %d must be at least 1.1.  Qhull uses linear hash probing\n",
    +      qh_HASHfactor);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  if (numpoints+extra < pointsneeded) {
    +    qh_fprintf(qh, qh->ferr, 6214, "qhull input error: not enough points(%d) to construct initial simplex (need %d)\n",
    +            numpoints, pointsneeded);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  qh_initqhull_outputflags(qh);
    +} /* initqhull_globals */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_mem(qh, )
    +    initialize mem_r.c for qhull
    +    qh.hull_dim and qh.normal_size determine some of the allocation sizes
    +    if qh.MERGING,
    +      includes ridgeT
    +    calls qh_user_memsizes(qh) to add up to 10 additional sizes for quick allocation
    +      (see numsizes below)
    +
    +  returns:
    +    mem_r.c already for qh_memalloc/qh_memfree (errors if called beforehand)
    +
    +  notes:
    +    qh_produceoutput() prints memsizes
    +
    +*/
    +void qh_initqhull_mem(qhT *qh) {
    +  int numsizes;
    +  int i;
    +
    +  numsizes= 8+10;
    +  qh_meminitbuffers(qh, qh->IStracing, qh_MEMalign, numsizes,
    +                     qh_MEMbufsize, qh_MEMinitbuf);
    +  qh_memsize(qh, (int)sizeof(vertexT));
    +  if (qh->MERGING) {
    +    qh_memsize(qh, (int)sizeof(ridgeT));
    +    qh_memsize(qh, (int)sizeof(mergeT));
    +  }
    +  qh_memsize(qh, (int)sizeof(facetT));
    +  i= sizeof(setT) + (qh->hull_dim - 1) * SETelemsize;  /* ridge.vertices */
    +  qh_memsize(qh, i);
    +  qh_memsize(qh, qh->normal_size);        /* normal */
    +  i += SETelemsize;                 /* facet.vertices, .ridges, .neighbors */
    +  qh_memsize(qh, i);
    +  qh_user_memsizes(qh);
    +  qh_memsetup(qh);
    +} /* initqhull_mem */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_outputflags
    +    initialize flags concerned with output
    +
    +  returns:
    +    adjust user flags as needed
    +
    +  see:
    +    qh_clear_outputflags() resets the flags
    +
    +  design:
    +    test for qh.PRINTgood (i.e., only print 'good' facets)
    +    check for conflicting print output options
    +*/
    +void qh_initqhull_outputflags(qhT *qh) {
    +  boolT printgeom= False, printmath= False, printcoplanar= False;
    +  int i;
    +
    +  trace3((qh, qh->ferr, 3024, "qh_initqhull_outputflags: %s\n", qh->qhull_command));
    +  if (!(qh->PRINTgood || qh->PRINTneighbors)) {
    +    if (qh->KEEParea || qh->KEEPminArea < REALmax/2 || qh->KEEPmerge || qh->DELAUNAY
    +        || (!qh->ONLYgood && (qh->GOODvertex || qh->GOODpoint))) {
    +      qh->PRINTgood= True;
    +      qh_option(qh, "Pgood", NULL, NULL);
    +    }
    +  }
    +  if (qh->PRINTtransparent) {
    +    if (qh->hull_dim != 4 || !qh->DELAUNAY || qh->VORONOI || qh->DROPdim >= 0) {
    +      qh_fprintf(qh, qh->ferr, 6215, "qhull input error: transparent Delaunay('Gt') needs 3-d Delaunay('d') w/o 'GDn'\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    qh->DROPdim = 3;
    +    qh->PRINTridges = True;
    +  }
    +  for (i=qh_PRINTEND; i--; ) {
    +    if (qh->PRINTout[i] == qh_PRINTgeom)
    +      printgeom= True;
    +    else if (qh->PRINTout[i] == qh_PRINTmathematica || qh->PRINTout[i] == qh_PRINTmaple)
    +      printmath= True;
    +    else if (qh->PRINTout[i] == qh_PRINTcoplanars)
    +      printcoplanar= True;
    +    else if (qh->PRINTout[i] == qh_PRINTpointnearest)
    +      printcoplanar= True;
    +    else if (qh->PRINTout[i] == qh_PRINTpointintersect && !qh->HALFspace) {
    +      qh_fprintf(qh, qh->ferr, 6053, "qhull input error: option 'Fp' is only used for \nhalfspace intersection('Hn,n,n').\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }else if (qh->PRINTout[i] == qh_PRINTtriangles && (qh->HALFspace || qh->VORONOI)) {
    +      qh_fprintf(qh, qh->ferr, 6054, "qhull input error: option 'Ft' is not available for Voronoi vertices or halfspace intersection\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }else if (qh->PRINTout[i] == qh_PRINTcentrums && qh->VORONOI) {
    +      qh_fprintf(qh, qh->ferr, 6055, "qhull input error: option 'FC' is not available for Voronoi vertices('v')\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }else if (qh->PRINTout[i] == qh_PRINTvertices) {
    +      if (qh->VORONOI)
    +        qh_option(qh, "Fvoronoi", NULL, NULL);
    +      else
    +        qh_option(qh, "Fvertices", NULL, NULL);
    +    }
    +  }
    +  if (printcoplanar && qh->DELAUNAY && qh->JOGGLEmax < REALmax/2) {
    +    if (qh->PRINTprecision)
    +      qh_fprintf(qh, qh->ferr, 7041, "qhull input warning: 'QJ' (joggle) will usually prevent coincident input sites for options 'Fc' and 'FP'\n");
    +  }
    +  if (printmath && (qh->hull_dim > 3 || qh->VORONOI)) {
    +    qh_fprintf(qh, qh->ferr, 6056, "qhull input error: Mathematica and Maple output is only available for 2-d and 3-d convex hulls and 2-d Delaunay triangulations\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (printgeom) {
    +    if (qh->hull_dim > 4) {
    +      qh_fprintf(qh, qh->ferr, 6057, "qhull input error: Geomview output is only available for 2-d, 3-d and 4-d\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh->PRINTnoplanes && !(qh->PRINTcoplanar + qh->PRINTcentrums
    +     + qh->PRINTdots + qh->PRINTspheres + qh->DOintersections + qh->PRINTridges)) {
    +      qh_fprintf(qh, qh->ferr, 6058, "qhull input error: no output specified for Geomview\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh->VORONOI && (qh->hull_dim > 3 || qh->DROPdim >= 0)) {
    +      qh_fprintf(qh, qh->ferr, 6059, "qhull input error: Geomview output for Voronoi diagrams only for 2-d\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    /* can not warn about furthest-site Geomview output: no lower_threshold */
    +    if (qh->hull_dim == 4 && qh->DROPdim == -1 &&
    +        (qh->PRINTcoplanar || qh->PRINTspheres || qh->PRINTcentrums)) {
    +      qh_fprintf(qh, qh->ferr, 7042, "qhull input warning: coplanars, vertices, and centrums output not\n\
    +available for 4-d output(ignored).  Could use 'GDn' instead.\n");
    +      qh->PRINTcoplanar= qh->PRINTspheres= qh->PRINTcentrums= False;
    +    }
    +  }
    +  if (!qh->KEEPcoplanar && !qh->KEEPinside && !qh->ONLYgood) {
    +    if ((qh->PRINTcoplanar && qh->PRINTspheres) || printcoplanar) {
    +      if (qh->QHULLfinished) {
    +        qh_fprintf(qh, qh->ferr, 7072, "qhull output warning: ignoring coplanar points, option 'Qc' was not set for the first run of qhull.\n");
    +      }else {
    +        qh->KEEPcoplanar = True;
    +        qh_option(qh, "Qcoplanar", NULL, NULL);
    +      }
    +    }
    +  }
    +  qh->PRINTdim= qh->hull_dim;
    +  if (qh->DROPdim >=0) {    /* after Geomview checks */
    +    if (qh->DROPdim < qh->hull_dim) {
    +      qh->PRINTdim--;
    +      if (!printgeom || qh->hull_dim < 3)
    +        qh_fprintf(qh, qh->ferr, 7043, "qhull input warning: drop dimension 'GD%d' is only available for 3-d/4-d Geomview\n", qh->DROPdim);
    +    }else
    +      qh->DROPdim= -1;
    +  }else if (qh->VORONOI) {
    +    qh->DROPdim= qh->hull_dim-1;
    +    qh->PRINTdim= qh->hull_dim-1;
    +  }
    +} /* qh_initqhull_outputflags */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_start(qh, infile, outfile, errfile )
    +    allocate memory if needed and call qh_initqhull_start2()
    +*/
    +void qh_initqhull_start(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile) {
    +
    +  qh_initstatistics(qh);
    +  qh_initqhull_start2(qh, infile, outfile, errfile);
    +} /* initqhull_start */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_start2(qh, infile, outfile, errfile )
    +    start initialization of qhull
    +    initialize statistics, stdio, default values for global variables
    +    assumes qh is allocated
    +  notes:
    +    report errors elsewhere, error handling and g_qhull_output [Qhull.cpp, QhullQh()] not in initialized
    +  see:
    +    qh_maxmin() determines the precision constants
    +    qh_freeqhull()
    +*/
    +void qh_initqhull_start2(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile) {
    +  time_t timedata;
    +  int seed;
    +
    +  qh_CPUclock; /* start the clock(for qh_clock).  One-shot. */
    +  /* memset is the same in qh_freeqhull() and qh_initqhull_start2() */
    +  memset((char *)qh, 0, sizeof(qhT)-sizeof(qhmemT)-sizeof(qhstatT));   /* every field is 0, FALSE, NULL */
    +  qh->NOerrexit= True;
    +  qh->ANGLEmerge= True;
    +  qh->DROPdim= -1;
    +  qh->ferr= errfile;
    +  qh->fin= infile;
    +  qh->fout= outfile;
    +  qh->furthest_id= qh_IDunknown;
    +  qh->JOGGLEmax= REALmax;
    +  qh->KEEPminArea = REALmax;
    +  qh->last_low= REALmax;
    +  qh->last_high= REALmax;
    +  qh->last_newhigh= REALmax;
    +  qh->last_random= 1;
    +  qh->max_outside= 0.0;
    +  qh->max_vertex= 0.0;
    +  qh->MAXabs_coord= 0.0;
    +  qh->MAXsumcoord= 0.0;
    +  qh->MAXwidth= -REALmax;
    +  qh->MERGEindependent= True;
    +  qh->MINdenom_1= fmax_(1.0/REALmax, REALmin); /* used by qh_scalepoints */
    +  qh->MINoutside= 0.0;
    +  qh->MINvisible= REALmax;
    +  qh->MAXcoplanar= REALmax;
    +  qh->outside_err= REALmax;
    +  qh->premerge_centrum= 0.0;
    +  qh->premerge_cos= REALmax;
    +  qh->PRINTprecision= True;
    +  qh->PRINTradius= 0.0;
    +  qh->postmerge_cos= REALmax;
    +  qh->postmerge_centrum= 0.0;
    +  qh->ROTATErandom= INT_MIN;
    +  qh->MERGEvertices= True;
    +  qh->totarea= 0.0;
    +  qh->totvol= 0.0;
    +  qh->TRACEdist= REALmax;
    +  qh->TRACEpoint= qh_IDunknown; /* recompile or use 'TPn' */
    +  qh->tracefacet_id= UINT_MAX;  /* recompile to trace a facet */
    +  qh->tracevertex_id= UINT_MAX; /* recompile to trace a vertex */
    +  seed= (int)time(&timedata);
    +  qh_RANDOMseed_(qh, seed);
    +  qh->run_id= qh_RANDOMint;
    +  if(!qh->run_id)
    +      qh->run_id++;  /* guarantee non-zero */
    +  qh_option(qh, "run-id", &qh->run_id, NULL);
    +  strcat(qh->qhull, "qhull");
    +} /* initqhull_start2 */
    +
    +/*---------------------------------
    +
    +  qh_initthresholds(qh, commandString )
    +    set thresholds for printing and scaling from commandString
    +
    +  returns:
    +    sets qh.GOODthreshold or qh.SPLITthreshold if 'Pd0D1' used
    +
    +  see:
    +    qh_initflags(), 'Qbk' 'QBk' 'Pdk' and 'PDk'
    +    qh_inthresholds()
    +
    +  design:
    +    for each 'Pdn' or 'PDn' option
    +      check syntax
    +      set qh.lower_threshold or qh.upper_threshold
    +    set qh.GOODthreshold if an unbounded threshold is used
    +    set qh.SPLITthreshold if a bounded threshold is used
    +*/
    +void qh_initthresholds(qhT *qh, char *command) {
    +  realT value;
    +  int idx, maxdim, k;
    +  char *s= command; /* non-const due to strtol */
    +  char key;
    +
    +  maxdim= qh->input_dim;
    +  if (qh->DELAUNAY && (qh->PROJECTdelaunay || qh->PROJECTinput))
    +    maxdim++;
    +  while (*s) {
    +    if (*s == '-')
    +      s++;
    +    if (*s == 'P') {
    +      s++;
    +      while (*s && !isspace(key= *s++)) {
    +        if (key == 'd' || key == 'D') {
    +          if (!isdigit(*s)) {
    +            qh_fprintf(qh, qh->ferr, 7044, "qhull warning: no dimension given for Print option '%c' at: %s.  Ignored\n",
    +                    key, s-1);
    +            continue;
    +          }
    +          idx= qh_strtol(s, &s);
    +          if (idx >= qh->hull_dim) {
    +            qh_fprintf(qh, qh->ferr, 7045, "qhull warning: dimension %d for Print option '%c' is >= %d.  Ignored\n",
    +                idx, key, qh->hull_dim);
    +            continue;
    +          }
    +          if (*s == ':') {
    +            s++;
    +            value= qh_strtod(s, &s);
    +            if (fabs((double)value) > 1.0) {
    +              qh_fprintf(qh, qh->ferr, 7046, "qhull warning: value %2.4g for Print option %c is > +1 or < -1.  Ignored\n",
    +                      value, key);
    +              continue;
    +            }
    +          }else
    +            value= 0.0;
    +          if (key == 'd')
    +            qh->lower_threshold[idx]= value;
    +          else
    +            qh->upper_threshold[idx]= value;
    +        }
    +      }
    +    }else if (*s == 'Q') {
    +      s++;
    +      while (*s && !isspace(key= *s++)) {
    +        if (key == 'b' && *s == 'B') {
    +          s++;
    +          for (k=maxdim; k--; ) {
    +            qh->lower_bound[k]= -qh_DEFAULTbox;
    +            qh->upper_bound[k]= qh_DEFAULTbox;
    +          }
    +        }else if (key == 'b' && *s == 'b')
    +          s++;
    +        else if (key == 'b' || key == 'B') {
    +          if (!isdigit(*s)) {
    +            qh_fprintf(qh, qh->ferr, 7047, "qhull warning: no dimension given for Qhull option %c.  Ignored\n",
    +                    key);
    +            continue;
    +          }
    +          idx= qh_strtol(s, &s);
    +          if (idx >= maxdim) {
    +            qh_fprintf(qh, qh->ferr, 7048, "qhull warning: dimension %d for Qhull option %c is >= %d.  Ignored\n",
    +                idx, key, maxdim);
    +            continue;
    +          }
    +          if (*s == ':') {
    +            s++;
    +            value= qh_strtod(s, &s);
    +          }else if (key == 'b')
    +            value= -qh_DEFAULTbox;
    +          else
    +            value= qh_DEFAULTbox;
    +          if (key == 'b')
    +            qh->lower_bound[idx]= value;
    +          else
    +            qh->upper_bound[idx]= value;
    +        }
    +      }
    +    }else {
    +      while (*s && !isspace(*s))
    +        s++;
    +    }
    +    while (isspace(*s))
    +      s++;
    +  }
    +  for (k=qh->hull_dim; k--; ) {
    +    if (qh->lower_threshold[k] > -REALmax/2) {
    +      qh->GOODthreshold= True;
    +      if (qh->upper_threshold[k] < REALmax/2) {
    +        qh->SPLITthresholds= True;
    +        qh->GOODthreshold= False;
    +        break;
    +      }
    +    }else if (qh->upper_threshold[k] < REALmax/2)
    +      qh->GOODthreshold= True;
    +  }
    +} /* initthresholds */
    +
    +/*---------------------------------
    +
    +  qh_lib_check( qhullLibraryType, qhTsize, vertexTsize, ridgeTsize, facetTsize, setTsize, qhmemTsize )
    +    Report error if library does not agree with caller
    +
    +  notes:
    +    NOerrors -- qh_lib_check can not call qh_errexit()
    +*/
    +void qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeTsize, int facetTsize, int setTsize, int qhmemTsize) {
    +    boolT iserror= False;
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)  /* user_r.h */
    +    // _CrtSetBreakAlloc(744);  /* Break at memalloc {744}, or 'watch' _crtBreakAlloc */
    +    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) );
    +    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
    +    _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR );
    +    _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR );
    +#endif
    +
    +    if (qhullLibraryType==QHULL_NON_REENTRANT) { /* 0 */
    +        qh_fprintf_stderr(6257, "qh_lib_check: Incorrect qhull library called.  Caller uses non-reentrant Qhull with a static qhT.  Library is reentrant.\n");
    +        iserror= True;
    +    }else if (qhullLibraryType==QHULL_QH_POINTER) { /* 1 */
    +        qh_fprintf_stderr(6258, "qh_lib_check: Incorrect qhull library called.  Caller uses non-reentrant Qhull with a dynamic qhT via qh_QHpointer.  Library is reentrant.\n");
    +        iserror= True;
    +    }else if (qhullLibraryType!=QHULL_REENTRANT) { /* 2 */
    +        qh_fprintf_stderr(6262, "qh_lib_check: Expecting qhullLibraryType QHULL_NON_REENTRANT(0), QHULL_QH_POINTER(1), or QHULL_REENTRANT(2).  Got %d\n", qhullLibraryType);
    +        iserror= True;
    +    }
    +    if (qhTsize != sizeof(qhT)) {
    +        qh_fprintf_stderr(6249, "qh_lib_check: Incorrect qhull library called.  Size of qhT for caller is %d, but for library is %d.\n", qhTsize, sizeof(qhT));
    +        iserror= True;
    +    }
    +    if (vertexTsize != sizeof(vertexT)) {
    +        qh_fprintf_stderr(6250, "qh_lib_check: Incorrect qhull library called.  Size of vertexT for caller is %d, but for library is %d.\n", vertexTsize, sizeof(vertexT));
    +        iserror= True;
    +    }
    +    if (ridgeTsize != sizeof(ridgeT)) {
    +        qh_fprintf_stderr(6251, "qh_lib_check: Incorrect qhull library called.  Size of ridgeT for caller is %d, but for library is %d.\n", ridgeTsize, sizeof(ridgeT));
    +        iserror= True;
    +    }
    +    if (facetTsize != sizeof(facetT)) {
    +        qh_fprintf_stderr(6252, "qh_lib_check: Incorrect qhull library called.  Size of facetT for caller is %d, but for library is %d.\n", facetTsize, sizeof(facetT));
    +        iserror= True;
    +    }
    +    if (setTsize && setTsize != sizeof(setT)) {
    +        qh_fprintf_stderr(6253, "qh_lib_check: Incorrect qhull library called.  Size of setT for caller is %d, but for library is %d.\n", setTsize, sizeof(setT));
    +        iserror= True;
    +    }
    +    if (qhmemTsize && qhmemTsize != sizeof(qhmemT)) {
    +        qh_fprintf_stderr(6254, "qh_lib_check: Incorrect qhull library called.  Size of qhmemT for caller is %d, but for library is %d.\n", qhmemTsize, sizeof(qhmemT));
    +        iserror= True;
    +    }
    +    if (iserror) {
    +        qh_fprintf_stderr(6259, "qh_lib_check: Cannot continue.  Library '%s' is reentrant (e.g., qhull_r.so)\n", qh_version2);
    +        qh_exit(qh_ERRqhull);  /* can not use qh_errexit() */
    +    }
    +} /* lib_check */
    +
    +/*---------------------------------
    +
    +  qh_option(qh, option, intVal, realVal )
    +    add an option description to qh.qhull_options
    +
    +  notes:
    +    NOerrors -- qh_option can not call qh_errexit() [qh_initqhull_start2]
    +    will be printed with statistics ('Ts') and errors
    +    strlen(option) < 40
    +*/
    +void qh_option(qhT *qh, const char *option, int *i, realT *r) {
    +  char buf[200];
    +  int len, maxlen;
    +
    +  sprintf(buf, "  %s", option);
    +  if (i)
    +    sprintf(buf+strlen(buf), " %d", *i);
    +  if (r)
    +    sprintf(buf+strlen(buf), " %2.2g", *r);
    +  len= (int)strlen(buf);  /* WARN64 */
    +  qh->qhull_optionlen += len;
    +  maxlen= sizeof(qh->qhull_options) - len -1;
    +  maximize_(maxlen, 0);
    +  if (qh->qhull_optionlen >= qh_OPTIONline && maxlen > 0) {
    +    qh->qhull_optionlen= len;
    +    strncat(qh->qhull_options, "\n", (size_t)(maxlen--));
    +  }
    +  strncat(qh->qhull_options, buf, (size_t)maxlen);
    +} /* option */
    +
    +/*---------------------------------
    +
    +  qh_zero( qh, errfile )
    +    Initialize and zero Qhull's memory for qh_new_qhull()
    +
    +  notes:
    +    Not needed in global.c because static variables are initialized to zero
    +*/
    +void qh_zero(qhT *qh, FILE *errfile) {
    +    memset((char *)qh, 0, sizeof(qhT));   /* every field is 0, FALSE, NULL */
    +    qh->NOerrexit= True;
    +    qh_meminit(qh, errfile);
    +} /* zero */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/index.htm b/xs/src/qhull/src/libqhull_r/index.htm
    new file mode 100644
    index 0000000000..c62030e06b
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/index.htm
    @@ -0,0 +1,266 @@
    +
    +
    +
    +
    +Reentrant Qhull functions, macros, and data structures
    +
    +
    +
    +
    +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code
    +To: Qhull files
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser + +


    + + +

    Reentrant Qhull functions, macros, and data structures

    +
    +

    The following sections provide an overview and index to +reentrant Qhull's functions, macros, and data structures. +Each section starts with an introduction. +See also Calling +Qhull from C programs and Calling Qhull from C++ programs.

    + +

    Qhull uses the following conventions:

    +
    + +
      +
    • in code, global variables start with "qh " +
    • in documentation, global variables start with 'qh.' +
    • constants start with an upper case word +
    • important globals include an '_' +
    • functions, macros, and constants start with "qh_"
    • +
    • data types end in "T"
    • +
    • macros with arguments end in "_"
    • +
    • iterators are macros that use local variables
    • +
    • iterators for sets start with "FOREACH"
    • +
    • iterators for lists start with "FORALL"
    • +
    • qhull options are in single quotes (e.g., 'Pdn')
    • +
    • lists are sorted alphabetically
    • +
    • preprocessor directives on left margin for older compilers
    • +
    +
    +

    +Reentrant Qhull is nearly the same as non-reentrant Qhull. In reentrant +Qhull, the qhT data structure is the first parameter to most functions. Qhull accesses +this data structure with 'qh->...'. +In non-reentrant Qhull, the global data structure is either a struct (qh_QHpointer==0) +or a pointer (qh_QHpointer==1). The non-reentrant code looks different because this data +structure is accessed via the 'qh' macro. This macro expands to 'qh_qh.' or 'qh_qh->' (resp.). +

    +When reading code with an editor, a search for +'"function' +will locate the header of qh_function. A search for '* function' +will locate the tail of qh_function. + +

    A useful starting point is libqhull_r.h. It defines most +of Qhull data structures and top-level functions. Search for 'PFn' to +determine the corresponding constant in Qhull. Search for 'Fp' to +determine the corresponding qh_PRINT... constant. +Search io_r.c to learn how the print function is implemented.

    + +

    If your web browser is configured for .c and .h files, the function, macro, and data type links +go to the corresponding source location. To configure your web browser for .c and .h files. +

      +
    • In the Download Preferences or Options panel, add file extensions 'c' and 'h' to mime type 'text/html'. +
    • Opera 12.10 +
        +
      1. In Tools > Preferences > Advanced > Downloads +
      2. Uncheck 'Hide file types opened with Opera' +
      3. Quick find 'html' +
      4. Select 'text/html' > Edit +
      5. Add File extensions 'c,h,' +
      6. Click 'OK' +
      +
    • Internet Explorer -- Mime types are not available from 'Internet Options'. Is there a registry key for these settings? +
    • Firefox -- Mime types are not available from 'Preferences'. Is there an add-on to change the file extensions for a mime type? +
    • Chrome -- Can Chrome be configured? +
    + +

    +Please report documentation and link errors +to qhull-bug@qhull.org. +

    + +

    Copyright © 1997-2015 C.B. Barber

    + +
    + +

    »Qhull files

    +
    + +

    This sections lists the .c and .h files for Qhull. Please +refer to these files for detailed information.

    +
    + +
    +
    Makefile, CMakeLists.txt
    +
    Makefile is preconfigured for gcc. CMakeLists.txt supports multiple +platforms with CMake. +Qhull includes project files for Visual Studio and Qt. +
    + +
     
    +
    libqhull_r.h
    +
    Include file for the Qhull library (libqhull.so, qhull.dll, libqhullstatic.a). +Data structures are documented under Poly. +Global variables are documented under Global. +Other data structures and variables are documented under +Qhull or Geom.
    + +
     
    +
    Geom, +geom_r.h, +geom_r.c, +geom2_r.c, +random_r.c, +random_r.h
    +
    Geometric routines. These routines implement mathematical +functions such as Gaussian elimination and geometric +routines needed for Qhull. Frequently used routines are +in geom_r.c while infrequent ones are in geom2_r.c. +
    + +
     
    +
    Global, +global_r.c, +libqhull_r.h
    +
    Global routines. Qhull uses a global data structure, qh, +to store globally defined constants, lists, sets, and +variables. +global_r.c initializes and frees these +structures.
    + +
     
    +
    Io, io_r.h, +io_r.c
    +
    Input and output routines. Qhull provides a wide range of +input and output options.
    + +
     
    +
    Mem, +mem_r.h, +mem_r.c
    +
    Memory routines. Qhull provides memory allocation and +deallocation. It uses quick-fit allocation.
    + +
     
    +
    Merge, +merge_r.h, +merge_r.c
    +
    Merge routines. Qhull handles precision problems by +merged facets or joggled input. These routines merge simplicial facets, +merge non-simplicial facets, merge cycles of facets, and +rename redundant vertices.
    + +
     
    +
    Poly, +poly_r.h, +poly_r.c, +poly2_r.c, +libqhull_r.h
    +
    Polyhedral routines. Qhull produces a polyhedron as a +list of facets with vertices, neighbors, ridges, and +geometric information. libqhull_r.h defines the main +data structures. Frequently used routines are in poly_r.c +while infrequent ones are in poly2_r.c.
    + +
     
    +
    Qhull, +libqhull_r.c, +libqhull_r.h, +qhull_ra.h, +unix_r.c , +qconvex_r.c , +qdelaun_r.c , +qhalf_r.c , +qvoronoi_r.c
    +
    Top-level routines. The Quickhull algorithm is +implemented by libqhull_r.c. qhull_ra.h +includes all header files.
    + +
     
    +
    Set, +qset_r.h, +qset_r.c
    +
    Set routines. Qhull implements its data structures as +sets. A set is an array of pointers that is expanded as +needed. This is a separate package that may be used in +other applications.
    + +
     
    +
    Stat, +stat_r.h, +stat_r.c
    +
    Statistical routines. Qhull maintains statistics about +its implementation.
    + +
     
    +
    User, +user_r.h, +user_r.c, +user_eg_r.c, +user_eg2_r.c, +user_eg3_r.cpp, +
    +
    User-defined routines. Qhull allows the user to configure +the code with defined constants and specialized routines. +
    +
    +
    + +
    +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull files
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    + +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/io_r.c b/xs/src/qhull/src/libqhull_r/io_r.c new file mode 100644 index 0000000000..9721a000dd --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/io_r.c @@ -0,0 +1,4062 @@ +/*
      ---------------------------------
    +
    +   io_r.c
    +   Input/Output routines of qhull application
    +
    +   see qh-io_r.htm and io_r.h
    +
    +   see user_r.c for qh_errprint and qh_printfacetlist
    +
    +   unix_r.c calls qh_readpoints and qh_produce_output
    +
    +   unix_r.c and user_r.c are the only callers of io_r.c functions
    +   This allows the user to avoid loading io_r.o from qhull.a
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/io_r.c#4 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*========= -functions in alphabetical order after qh_produce_output(qh)  =====*/
    +
    +/*---------------------------------
    +
    +  qh_produce_output(qh)
    +  qh_produce_output2(qh)
    +    prints out the result of qhull in desired format
    +    qh_produce_output2(qh) does not call qh_prepare_output(qh)
    +    if qh.GETarea
    +      computes and prints area and volume
    +    qh.PRINTout[] is an array of output formats
    +
    +  notes:
    +    prints output in qh.PRINTout order
    +*/
    +void qh_produce_output(qhT *qh) {
    +    int tempsize= qh_setsize(qh, qh->qhmem.tempstack);
    +
    +    qh_prepare_output(qh);
    +    qh_produce_output2(qh);
    +    if (qh_setsize(qh, qh->qhmem.tempstack) != tempsize) {
    +        qh_fprintf(qh, qh->ferr, 6206, "qhull internal error (qh_produce_output): temporary sets not empty(%d)\n",
    +            qh_setsize(qh, qh->qhmem.tempstack));
    +        qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +} /* produce_output */
    +
    +
    +void qh_produce_output2(qhT *qh) {
    +  int i, tempsize= qh_setsize(qh, qh->qhmem.tempstack), d_1;
    +
    +  if (qh->PRINTsummary)
    +    qh_printsummary(qh, qh->ferr);
    +  else if (qh->PRINTout[0] == qh_PRINTnone)
    +    qh_printsummary(qh, qh->fout);
    +  for (i=0; i < qh_PRINTEND; i++)
    +    qh_printfacets(qh, qh->fout, qh->PRINTout[i], qh->facet_list, NULL, !qh_ALL);
    +  qh_allstatistics(qh);
    +  if (qh->PRINTprecision && !qh->MERGING && (qh->JOGGLEmax > REALmax/2 || qh->RERUN))
    +    qh_printstats(qh, qh->ferr, qh->qhstat.precision, NULL);
    +  if (qh->VERIFYoutput && (zzval_(Zridge) > 0 || zzval_(Zridgemid) > 0))
    +    qh_printstats(qh, qh->ferr, qh->qhstat.vridges, NULL);
    +  if (qh->PRINTstatistics) {
    +    qh_printstatistics(qh, qh->ferr, "");
    +    qh_memstatistics(qh, qh->ferr);
    +    d_1= sizeof(setT) + (qh->hull_dim - 1) * SETelemsize;
    +    qh_fprintf(qh, qh->ferr, 8040, "\
    +    size in bytes: merge %d ridge %d vertex %d facet %d\n\
    +         normal %d ridge vertices %d facet vertices or neighbors %d\n",
    +            (int)sizeof(mergeT), (int)sizeof(ridgeT),
    +            (int)sizeof(vertexT), (int)sizeof(facetT),
    +            qh->normal_size, d_1, d_1 + SETelemsize);
    +  }
    +  if (qh_setsize(qh, qh->qhmem.tempstack) != tempsize) {
    +    qh_fprintf(qh, qh->ferr, 6065, "qhull internal error (qh_produce_output2): temporary sets not empty(%d)\n",
    +             qh_setsize(qh, qh->qhmem.tempstack));
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +} /* produce_output2 */
    +
    +/*---------------------------------
    +
    +  qh_dfacet(qh, id )
    +    print facet by id, for debugging
    +
    +*/
    +void qh_dfacet(qhT *qh, unsigned id) {
    +  facetT *facet;
    +
    +  FORALLfacets {
    +    if (facet->id == id) {
    +      qh_printfacet(qh, qh->fout, facet);
    +      break;
    +    }
    +  }
    +} /* dfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_dvertex(qh, id )
    +    print vertex by id, for debugging
    +*/
    +void qh_dvertex(qhT *qh, unsigned id) {
    +  vertexT *vertex;
    +
    +  FORALLvertices {
    +    if (vertex->id == id) {
    +      qh_printvertex(qh, qh->fout, vertex);
    +      break;
    +    }
    +  }
    +} /* dvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_compare_facetarea(p1, p2 )
    +    used by qsort() to order facets by area
    +*/
    +int qh_compare_facetarea(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +
    +  if (!a->isarea)
    +    return -1;
    +  if (!b->isarea)
    +    return 1;
    +  if (a->f.area > b->f.area)
    +    return 1;
    +  else if (a->f.area == b->f.area)
    +    return 0;
    +  return -1;
    +} /* compare_facetarea */
    +
    +/*---------------------------------
    +
    +  qh_compare_facetmerge(p1, p2 )
    +    used by qsort() to order facets by number of merges
    +*/
    +int qh_compare_facetmerge(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +
    +  return(a->nummerge - b->nummerge);
    +} /* compare_facetvisit */
    +
    +/*---------------------------------
    +
    +  qh_compare_facetvisit(p1, p2 )
    +    used by qsort() to order facets by visit id or id
    +*/
    +int qh_compare_facetvisit(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +  int i,j;
    +
    +  if (!(i= a->visitid))
    +    i= 0 - a->id; /* do not convert to int, sign distinguishes id from visitid */
    +  if (!(j= b->visitid))
    +    j= 0 - b->id;
    +  return(i - j);
    +} /* compare_facetvisit */
    +
    +/*---------------------------------
    +
    +  qh_compare_vertexpoint( p1, p2 )
    +    used by qsort() to order vertices by point id
    +
    +  Not usable in qhulllib_r since qh_pointid depends on qh
    +
    +  int qh_compare_vertexpoint(const void *p1, const void *p2) {
    +  const vertexT *a= *((vertexT *const*)p1), *b= *((vertexT *const*)p2);
    +
    +  return((qh_pointid(qh, a->point) > qh_pointid(qh, b->point)?1:-1));
    +}*/
    +
    +/*---------------------------------
    +
    +  qh_copyfilename(qh, dest, size, source, length )
    +    copy filename identified by qh_skipfilename()
    +
    +  notes:
    +    see qh_skipfilename() for syntax
    +*/
    +void qh_copyfilename(qhT *qh, char *filename, int size, const char* source, int length) {
    +  char c= *source;
    +
    +  if (length > size + 1) {
    +      qh_fprintf(qh, qh->ferr, 6040, "qhull error: filename is more than %d characters, %s\n",  size-1, source);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  strncpy(filename, source, length);
    +  filename[length]= '\0';
    +  if (c == '\'' || c == '"') {
    +    char *s= filename + 1;
    +    char *t= filename;
    +    while (*s) {
    +      if (*s == c) {
    +          if (s[-1] == '\\')
    +              t[-1]= c;
    +      }else
    +          *t++= *s;
    +      s++;
    +    }
    +    *t= '\0';
    +  }
    +} /* copyfilename */
    +
    +/*---------------------------------
    +
    +  qh_countfacets(qh, facetlist, facets, printall,
    +          numfacets, numsimplicial, totneighbors, numridges, numcoplanar, numtricoplanars  )
    +    count good facets for printing and set visitid
    +    if allfacets, ignores qh_skipfacet()
    +
    +  notes:
    +    qh_printsummary and qh_countfacets must match counts
    +
    +  returns:
    +    numfacets, numsimplicial, total neighbors, numridges, coplanars
    +    each facet with ->visitid indicating 1-relative position
    +      ->visitid==0 indicates not good
    +
    +  notes
    +    numfacets >= numsimplicial
    +    if qh.NEWfacets,
    +      does not count visible facets (matches qh_printafacet)
    +
    +  design:
    +    for all facets on facetlist and in facets set
    +      unless facet is skipped or visible (i.e., will be deleted)
    +        mark facet->visitid
    +        update counts
    +*/
    +void qh_countfacets(qhT *qh, facetT *facetlist, setT *facets, boolT printall,
    +    int *numfacetsp, int *numsimplicialp, int *totneighborsp, int *numridgesp, int *numcoplanarsp, int *numtricoplanarsp) {
    +  facetT *facet, **facetp;
    +  int numfacets= 0, numsimplicial= 0, numridges= 0, totneighbors= 0, numcoplanars= 0, numtricoplanars= 0;
    +
    +  FORALLfacet_(facetlist) {
    +    if ((facet->visible && qh->NEWfacets)
    +    || (!printall && qh_skipfacet(qh, facet)))
    +      facet->visitid= 0;
    +    else {
    +      facet->visitid= ++numfacets;
    +      totneighbors += qh_setsize(qh, facet->neighbors);
    +      if (facet->simplicial) {
    +        numsimplicial++;
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else
    +        numridges += qh_setsize(qh, facet->ridges);
    +      if (facet->coplanarset)
    +        numcoplanars += qh_setsize(qh, facet->coplanarset);
    +    }
    +  }
    +
    +  FOREACHfacet_(facets) {
    +    if ((facet->visible && qh->NEWfacets)
    +    || (!printall && qh_skipfacet(qh, facet)))
    +      facet->visitid= 0;
    +    else {
    +      facet->visitid= ++numfacets;
    +      totneighbors += qh_setsize(qh, facet->neighbors);
    +      if (facet->simplicial){
    +        numsimplicial++;
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else
    +        numridges += qh_setsize(qh, facet->ridges);
    +      if (facet->coplanarset)
    +        numcoplanars += qh_setsize(qh, facet->coplanarset);
    +    }
    +  }
    +  qh->visit_id += numfacets+1;
    +  *numfacetsp= numfacets;
    +  *numsimplicialp= numsimplicial;
    +  *totneighborsp= totneighbors;
    +  *numridgesp= numridges;
    +  *numcoplanarsp= numcoplanars;
    +  *numtricoplanarsp= numtricoplanars;
    +} /* countfacets */
    +
    +/*---------------------------------
    +
    +  qh_detvnorm(qh, vertex, vertexA, centers, offset )
    +    compute separating plane of the Voronoi diagram for a pair of input sites
    +    centers= set of facets (i.e., Voronoi vertices)
    +      facet->visitid= 0 iff vertex-at-infinity (i.e., unbounded)
    +
    +  assumes:
    +    qh_ASvoronoi and qh_vertexneighbors() already set
    +
    +  returns:
    +    norm
    +      a pointer into qh.gm_matrix to qh.hull_dim-1 reals
    +      copy the data before reusing qh.gm_matrix
    +    offset
    +      if 'QVn'
    +        sign adjusted so that qh.GOODvertexp is inside
    +      else
    +        sign adjusted so that vertex is inside
    +
    +    qh.gm_matrix= simplex of points from centers relative to first center
    +
    +  notes:
    +    in io_r.c so that code for 'v Tv' can be removed by removing io_r.c
    +    returns pointer into qh.gm_matrix to avoid tracking of temporary memory
    +
    +  design:
    +    determine midpoint of input sites
    +    build points as the set of Voronoi vertices
    +    select a simplex from points (if necessary)
    +      include midpoint if the Voronoi region is unbounded
    +    relocate the first vertex of the simplex to the origin
    +    compute the normalized hyperplane through the simplex
    +    orient the hyperplane toward 'QVn' or 'vertex'
    +    if 'Tv' or 'Ts'
    +      if bounded
    +        test that hyperplane is the perpendicular bisector of the input sites
    +      test that Voronoi vertices not in the simplex are still on the hyperplane
    +    free up temporary memory
    +*/
    +pointT *qh_detvnorm(qhT *qh, vertexT *vertex, vertexT *vertexA, setT *centers, realT *offsetp) {
    +  facetT *facet, **facetp;
    +  int  i, k, pointid, pointidA, point_i, point_n;
    +  setT *simplex= NULL;
    +  pointT *point, **pointp, *point0, *midpoint, *normal, *inpoint;
    +  coordT *coord, *gmcoord, *normalp;
    +  setT *points= qh_settemp(qh, qh->TEMPsize);
    +  boolT nearzero= False;
    +  boolT unbounded= False;
    +  int numcenters= 0;
    +  int dim= qh->hull_dim - 1;
    +  realT dist, offset, angle, zero= 0.0;
    +
    +  midpoint= qh->gm_matrix + qh->hull_dim * qh->hull_dim;  /* last row */
    +  for (k=0; k < dim; k++)
    +    midpoint[k]= (vertex->point[k] + vertexA->point[k])/2;
    +  FOREACHfacet_(centers) {
    +    numcenters++;
    +    if (!facet->visitid)
    +      unbounded= True;
    +    else {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(qh, facet->vertices);
    +      qh_setappend(qh, &points, facet->center);
    +    }
    +  }
    +  if (numcenters > dim) {
    +    simplex= qh_settemp(qh, qh->TEMPsize);
    +    qh_setappend(qh, &simplex, vertex->point);
    +    if (unbounded)
    +      qh_setappend(qh, &simplex, midpoint);
    +    qh_maxsimplex(qh, dim, points, NULL, 0, &simplex);
    +    qh_setdelnth(qh, simplex, 0);
    +  }else if (numcenters == dim) {
    +    if (unbounded)
    +      qh_setappend(qh, &points, midpoint);
    +    simplex= points;
    +  }else {
    +    qh_fprintf(qh, qh->ferr, 6216, "qhull internal error (qh_detvnorm): too few points(%d) to compute separating plane\n", numcenters);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  i= 0;
    +  gmcoord= qh->gm_matrix;
    +  point0= SETfirstt_(simplex, pointT);
    +  FOREACHpoint_(simplex) {
    +    if (qh->IStracing >= 4)
    +      qh_printmatrix(qh, qh->ferr, "qh_detvnorm: Voronoi vertex or midpoint",
    +                              &point, 1, dim);
    +    if (point != point0) {
    +      qh->gm_row[i++]= gmcoord;
    +      coord= point0;
    +      for (k=dim; k--; )
    +        *(gmcoord++)= *point++ - *coord++;
    +    }
    +  }
    +  qh->gm_row[i]= gmcoord;  /* does not overlap midpoint, may be used later for qh_areasimplex */
    +  normal= gmcoord;
    +  qh_sethyperplane_gauss(qh, dim, qh->gm_row, point0, True,
    +                normal, &offset, &nearzero);
    +  if (qh->GOODvertexp == vertexA->point)
    +    inpoint= vertexA->point;
    +  else
    +    inpoint= vertex->point;
    +  zinc_(Zdistio);
    +  dist= qh_distnorm(dim, inpoint, normal, &offset);
    +  if (dist > 0) {
    +    offset= -offset;
    +    normalp= normal;
    +    for (k=dim; k--; ) {
    +      *normalp= -(*normalp);
    +      normalp++;
    +    }
    +  }
    +  if (qh->VERIFYoutput || qh->PRINTstatistics) {
    +    pointid= qh_pointid(qh, vertex->point);
    +    pointidA= qh_pointid(qh, vertexA->point);
    +    if (!unbounded) {
    +      zinc_(Zdiststat);
    +      dist= qh_distnorm(dim, midpoint, normal, &offset);
    +      if (dist < 0)
    +        dist= -dist;
    +      zzinc_(Zridgemid);
    +      wwmax_(Wridgemidmax, dist);
    +      wwadd_(Wridgemid, dist);
    +      trace4((qh, qh->ferr, 4014, "qh_detvnorm: points %d %d midpoint dist %2.2g\n",
    +                 pointid, pointidA, dist));
    +      for (k=0; k < dim; k++)
    +        midpoint[k]= vertexA->point[k] - vertex->point[k];  /* overwrites midpoint! */
    +      qh_normalize(qh, midpoint, dim, False);
    +      angle= qh_distnorm(dim, midpoint, normal, &zero); /* qh_detangle uses dim+1 */
    +      if (angle < 0.0)
    +        angle= angle + 1.0;
    +      else
    +        angle= angle - 1.0;
    +      if (angle < 0.0)
    +        angle -= angle;
    +      trace4((qh, qh->ferr, 4015, "qh_detvnorm: points %d %d angle %2.2g nearzero %d\n",
    +                 pointid, pointidA, angle, nearzero));
    +      if (nearzero) {
    +        zzinc_(Zridge0);
    +        wwmax_(Wridge0max, angle);
    +        wwadd_(Wridge0, angle);
    +      }else {
    +        zzinc_(Zridgeok)
    +        wwmax_(Wridgeokmax, angle);
    +        wwadd_(Wridgeok, angle);
    +      }
    +    }
    +    if (simplex != points) {
    +      FOREACHpoint_i_(qh, points) {
    +        if (!qh_setin(simplex, point)) {
    +          facet= SETelemt_(centers, point_i, facetT);
    +          zinc_(Zdiststat);
    +          dist= qh_distnorm(dim, point, normal, &offset);
    +          if (dist < 0)
    +            dist= -dist;
    +          zzinc_(Zridge);
    +          wwmax_(Wridgemax, dist);
    +          wwadd_(Wridge, dist);
    +          trace4((qh, qh->ferr, 4016, "qh_detvnorm: points %d %d Voronoi vertex %d dist %2.2g\n",
    +                             pointid, pointidA, facet->visitid, dist));
    +        }
    +      }
    +    }
    +  }
    +  *offsetp= offset;
    +  if (simplex != points)
    +    qh_settempfree(qh, &simplex);
    +  qh_settempfree(qh, &points);
    +  return normal;
    +} /* detvnorm */
    +
    +/*---------------------------------
    +
    +  qh_detvridge(qh, vertexA )
    +    determine Voronoi ridge from 'seen' neighbors of vertexA
    +    include one vertex-at-infinite if an !neighbor->visitid
    +
    +  returns:
    +    temporary set of centers (facets, i.e., Voronoi vertices)
    +    sorted by center id
    +*/
    +setT *qh_detvridge(qhT *qh, vertexT *vertex) {
    +  setT *centers= qh_settemp(qh, qh->TEMPsize);
    +  setT *tricenters= qh_settemp(qh, qh->TEMPsize);
    +  facetT *neighbor, **neighborp;
    +  boolT firstinf= True;
    +
    +  FOREACHneighbor_(vertex) {
    +    if (neighbor->seen) {
    +      if (neighbor->visitid) {
    +        if (!neighbor->tricoplanar || qh_setunique(qh, &tricenters, neighbor->center))
    +          qh_setappend(qh, ¢ers, neighbor);
    +      }else if (firstinf) {
    +        firstinf= False;
    +        qh_setappend(qh, ¢ers, neighbor);
    +      }
    +    }
    +  }
    +  qsort(SETaddr_(centers, facetT), (size_t)qh_setsize(qh, centers),
    +             sizeof(facetT *), qh_compare_facetvisit);
    +  qh_settempfree(qh, &tricenters);
    +  return centers;
    +} /* detvridge */
    +
    +/*---------------------------------
    +
    +  qh_detvridge3(qh, atvertex, vertex )
    +    determine 3-d Voronoi ridge from 'seen' neighbors of atvertex and vertex
    +    include one vertex-at-infinite for !neighbor->visitid
    +    assumes all facet->seen2= True
    +
    +  returns:
    +    temporary set of centers (facets, i.e., Voronoi vertices)
    +    listed in adjacency order (!oriented)
    +    all facet->seen2= True
    +
    +  design:
    +    mark all neighbors of atvertex
    +    for each adjacent neighbor of both atvertex and vertex
    +      if neighbor selected
    +        add neighbor to set of Voronoi vertices
    +*/
    +setT *qh_detvridge3(qhT *qh, vertexT *atvertex, vertexT *vertex) {
    +  setT *centers= qh_settemp(qh, qh->TEMPsize);
    +  setT *tricenters= qh_settemp(qh, qh->TEMPsize);
    +  facetT *neighbor, **neighborp, *facet= NULL;
    +  boolT firstinf= True;
    +
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen2= False;
    +  FOREACHneighbor_(vertex) {
    +    if (!neighbor->seen2) {
    +      facet= neighbor;
    +      break;
    +    }
    +  }
    +  while (facet) {
    +    facet->seen2= True;
    +    if (neighbor->seen) {
    +      if (facet->visitid) {
    +        if (!facet->tricoplanar || qh_setunique(qh, &tricenters, facet->center))
    +          qh_setappend(qh, ¢ers, facet);
    +      }else if (firstinf) {
    +        firstinf= False;
    +        qh_setappend(qh, ¢ers, facet);
    +      }
    +    }
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->seen2) {
    +        if (qh_setin(vertex->neighbors, neighbor))
    +          break;
    +        else
    +          neighbor->seen2= True;
    +      }
    +    }
    +    facet= neighbor;
    +  }
    +  if (qh->CHECKfrequently) {
    +    FOREACHneighbor_(vertex) {
    +      if (!neighbor->seen2) {
    +          qh_fprintf(qh, qh->ferr, 6217, "qhull internal error (qh_detvridge3): neighbors of vertex p%d are not connected at facet %d\n",
    +                 qh_pointid(qh, vertex->point), neighbor->id);
    +        qh_errexit(qh, qh_ERRqhull, neighbor, NULL);
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen2= True;
    +  qh_settempfree(qh, &tricenters);
    +  return centers;
    +} /* detvridge3 */
    +
    +/*---------------------------------
    +
    +  qh_eachvoronoi(qh, fp, printvridge, vertex, visitall, innerouter, inorder )
    +    if visitall,
    +      visit all Voronoi ridges for vertex (i.e., an input site)
    +    else
    +      visit all unvisited Voronoi ridges for vertex
    +      all vertex->seen= False if unvisited
    +    assumes
    +      all facet->seen= False
    +      all facet->seen2= True (for qh_detvridge3)
    +      all facet->visitid == 0 if vertex_at_infinity
    +                         == index of Voronoi vertex
    +                         >= qh.num_facets if ignored
    +    innerouter:
    +      qh_RIDGEall--  both inner (bounded) and outer(unbounded) ridges
    +      qh_RIDGEinner- only inner
    +      qh_RIDGEouter- only outer
    +
    +    if inorder
    +      orders vertices for 3-d Voronoi diagrams
    +
    +  returns:
    +    number of visited ridges (does not include previously visited ridges)
    +
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers)
    +        fp== any pointer (assumes FILE*)
    +        vertex,vertexA= pair of input sites that define a Voronoi ridge
    +        centers= set of facets (i.e., Voronoi vertices)
    +                 ->visitid == index or 0 if vertex_at_infinity
    +                 ordered for 3-d Voronoi diagram
    +  notes:
    +    uses qh.vertex_visit
    +
    +  see:
    +    qh_eachvoronoi_all()
    +
    +  design:
    +    mark selected neighbors of atvertex
    +    for each selected neighbor (either Voronoi vertex or vertex-at-infinity)
    +      for each unvisited vertex
    +        if atvertex and vertex share more than d-1 neighbors
    +          bump totalcount
    +          if printvridge defined
    +            build the set of shared neighbors (i.e., Voronoi vertices)
    +            call printvridge
    +*/
    +int qh_eachvoronoi(qhT *qh, FILE *fp, printvridgeT printvridge, vertexT *atvertex, boolT visitall, qh_RIDGE innerouter, boolT inorder) {
    +  boolT unbounded;
    +  int count;
    +  facetT *neighbor, **neighborp, *neighborA, **neighborAp;
    +  setT *centers;
    +  setT *tricenters= qh_settemp(qh, qh->TEMPsize);
    +
    +  vertexT *vertex, **vertexp;
    +  boolT firstinf;
    +  unsigned int numfacets= (unsigned int)qh->num_facets;
    +  int totridges= 0;
    +
    +  qh->vertex_visit++;
    +  atvertex->seen= True;
    +  if (visitall) {
    +    FORALLvertices
    +      vertex->seen= False;
    +  }
    +  FOREACHneighbor_(atvertex) {
    +    if (neighbor->visitid < numfacets)
    +      neighbor->seen= True;
    +  }
    +  FOREACHneighbor_(atvertex) {
    +    if (neighbor->seen) {
    +      FOREACHvertex_(neighbor->vertices) {
    +        if (vertex->visitid != qh->vertex_visit && !vertex->seen) {
    +          vertex->visitid= qh->vertex_visit;
    +          count= 0;
    +          firstinf= True;
    +          qh_settruncate(qh, tricenters, 0);
    +          FOREACHneighborA_(vertex) {
    +            if (neighborA->seen) {
    +              if (neighborA->visitid) {
    +                if (!neighborA->tricoplanar || qh_setunique(qh, &tricenters, neighborA->center))
    +                  count++;
    +              }else if (firstinf) {
    +                count++;
    +                firstinf= False;
    +              }
    +            }
    +          }
    +          if (count >= qh->hull_dim - 1) {  /* e.g., 3 for 3-d Voronoi */
    +            if (firstinf) {
    +              if (innerouter == qh_RIDGEouter)
    +                continue;
    +              unbounded= False;
    +            }else {
    +              if (innerouter == qh_RIDGEinner)
    +                continue;
    +              unbounded= True;
    +            }
    +            totridges++;
    +            trace4((qh, qh->ferr, 4017, "qh_eachvoronoi: Voronoi ridge of %d vertices between sites %d and %d\n",
    +                  count, qh_pointid(qh, atvertex->point), qh_pointid(qh, vertex->point)));
    +            if (printvridge && fp) {
    +              if (inorder && qh->hull_dim == 3+1) /* 3-d Voronoi diagram */
    +                centers= qh_detvridge3(qh, atvertex, vertex);
    +              else
    +                centers= qh_detvridge(qh, vertex);
    +              (*printvridge)(qh, fp, atvertex, vertex, centers, unbounded);
    +              qh_settempfree(qh, ¢ers);
    +            }
    +          }
    +        }
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen= False;
    +  qh_settempfree(qh, &tricenters);
    +  return totridges;
    +} /* eachvoronoi */
    +
    +
    +/*---------------------------------
    +
    +  qh_eachvoronoi_all(qh, fp, printvridge, isUpper, innerouter, inorder )
    +    visit all Voronoi ridges
    +
    +    innerouter:
    +      see qh_eachvoronoi()
    +
    +    if inorder
    +      orders vertices for 3-d Voronoi diagrams
    +
    +  returns
    +    total number of ridges
    +
    +    if isUpper == facet->upperdelaunay  (i.e., a Vornoi vertex)
    +      facet->visitid= Voronoi vertex index(same as 'o' format)
    +    else
    +      facet->visitid= 0
    +
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers)
    +      [see qh_eachvoronoi]
    +
    +  notes:
    +    Not used for qhull.exe
    +    same effect as qh_printvdiagram but ridges not sorted by point id
    +*/
    +int qh_eachvoronoi_all(qhT *qh, FILE *fp, printvridgeT printvridge, boolT isUpper, qh_RIDGE innerouter, boolT inorder) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  int numcenters= 1;  /* vertex 0 is vertex-at-infinity */
    +  int totridges= 0;
    +
    +  qh_clearcenters(qh, qh_ASvoronoi);
    +  qh_vertexneighbors(qh);
    +  maximize_(qh->visit_id, (unsigned) qh->num_facets);
    +  FORALLfacets {
    +    facet->visitid= 0;
    +    facet->seen= False;
    +    facet->seen2= True;
    +  }
    +  FORALLfacets {
    +    if (facet->upperdelaunay == isUpper)
    +      facet->visitid= numcenters++;
    +  }
    +  FORALLvertices
    +    vertex->seen= False;
    +  FORALLvertices {
    +    if (qh->GOODvertex > 0 && qh_pointid(qh, vertex->point)+1 != qh->GOODvertex)
    +      continue;
    +    totridges += qh_eachvoronoi(qh, fp, printvridge, vertex,
    +                   !qh_ALL, innerouter, inorder);
    +  }
    +  return totridges;
    +} /* eachvoronoi_all */
    +
    +/*---------------------------------
    +
    +  qh_facet2point(qh, facet, point0, point1, mindist )
    +    return two projected temporary vertices for a 2-d facet
    +    may be non-simplicial
    +
    +  returns:
    +    point0 and point1 oriented and projected to the facet
    +    returns mindist (maximum distance below plane)
    +*/
    +void qh_facet2point(qhT *qh, facetT *facet, pointT **point0, pointT **point1, realT *mindist) {
    +  vertexT *vertex0, *vertex1;
    +  realT dist;
    +
    +  if (facet->toporient ^ qh_ORIENTclock) {
    +    vertex0= SETfirstt_(facet->vertices, vertexT);
    +    vertex1= SETsecondt_(facet->vertices, vertexT);
    +  }else {
    +    vertex1= SETfirstt_(facet->vertices, vertexT);
    +    vertex0= SETsecondt_(facet->vertices, vertexT);
    +  }
    +  zadd_(Zdistio, 2);
    +  qh_distplane(qh, vertex0->point, facet, &dist);
    +  *mindist= dist;
    +  *point0= qh_projectpoint(qh, vertex0->point, facet, dist);
    +  qh_distplane(qh, vertex1->point, facet, &dist);
    +  minimize_(*mindist, dist);
    +  *point1= qh_projectpoint(qh, vertex1->point, facet, dist);
    +} /* facet2point */
    +
    +
    +/*---------------------------------
    +
    +  qh_facetvertices(qh, facetlist, facets, allfacets )
    +    returns temporary set of vertices in a set and/or list of facets
    +    if allfacets, ignores qh_skipfacet()
    +
    +  returns:
    +    vertices with qh.vertex_visit
    +
    +  notes:
    +    optimized for allfacets of facet_list
    +
    +  design:
    +    if allfacets of facet_list
    +      create vertex set from vertex_list
    +    else
    +      for each selected facet in facets or facetlist
    +        append unvisited vertices to vertex set
    +*/
    +setT *qh_facetvertices(qhT *qh, facetT *facetlist, setT *facets, boolT allfacets) {
    +  setT *vertices;
    +  facetT *facet, **facetp;
    +  vertexT *vertex, **vertexp;
    +
    +  qh->vertex_visit++;
    +  if (facetlist == qh->facet_list && allfacets && !facets) {
    +    vertices= qh_settemp(qh, qh->num_vertices);
    +    FORALLvertices {
    +      vertex->visitid= qh->vertex_visit;
    +      qh_setappend(qh, &vertices, vertex);
    +    }
    +  }else {
    +    vertices= qh_settemp(qh, qh->TEMPsize);
    +    FORALLfacet_(facetlist) {
    +      if (!allfacets && qh_skipfacet(qh, facet))
    +        continue;
    +      FOREACHvertex_(facet->vertices) {
    +        if (vertex->visitid != qh->vertex_visit) {
    +          vertex->visitid= qh->vertex_visit;
    +          qh_setappend(qh, &vertices, vertex);
    +        }
    +      }
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (!allfacets && qh_skipfacet(qh, facet))
    +      continue;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh->vertex_visit) {
    +        vertex->visitid= qh->vertex_visit;
    +        qh_setappend(qh, &vertices, vertex);
    +      }
    +    }
    +  }
    +  return vertices;
    +} /* facetvertices */
    +
    +/*---------------------------------
    +
    +  qh_geomplanes(qh, facet, outerplane, innerplane )
    +    return outer and inner planes for Geomview
    +    qh.PRINTradius is size of vertices and points (includes qh.JOGGLEmax)
    +
    +  notes:
    +    assume precise calculations in io.c with roundoff covered by qh_GEOMepsilon
    +*/
    +void qh_geomplanes(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane) {
    +  realT radius;
    +
    +  if (qh->MERGING || qh->JOGGLEmax < REALmax/2) {
    +    qh_outerinner(qh, facet, outerplane, innerplane);
    +    radius= qh->PRINTradius;
    +    if (qh->JOGGLEmax < REALmax/2)
    +      radius -= qh->JOGGLEmax * sqrt((realT)qh->hull_dim);  /* already accounted for in qh_outerinner() */
    +    *outerplane += radius;
    +    *innerplane -= radius;
    +    if (qh->PRINTcoplanar || qh->PRINTspheres) {
    +      *outerplane += qh->MAXabs_coord * qh_GEOMepsilon;
    +      *innerplane -= qh->MAXabs_coord * qh_GEOMepsilon;
    +    }
    +  }else
    +    *innerplane= *outerplane= 0;
    +} /* geomplanes */
    +
    +
    +/*---------------------------------
    +
    +  qh_markkeep(qh, facetlist )
    +    mark good facets that meet qh.KEEParea, qh.KEEPmerge, and qh.KEEPminArea
    +    ignores visible facets (!part of convex hull)
    +
    +  returns:
    +    may clear facet->good
    +    recomputes qh.num_good
    +
    +  design:
    +    get set of good facets
    +    if qh.KEEParea
    +      sort facets by area
    +      clear facet->good for all but n largest facets
    +    if qh.KEEPmerge
    +      sort facets by merge count
    +      clear facet->good for all but n most merged facets
    +    if qh.KEEPminarea
    +      clear facet->good if area too small
    +    update qh.num_good
    +*/
    +void qh_markkeep(qhT *qh, facetT *facetlist) {
    +  facetT *facet, **facetp;
    +  setT *facets= qh_settemp(qh, qh->num_facets);
    +  int size, count;
    +
    +  trace2((qh, qh->ferr, 2006, "qh_markkeep: only keep %d largest and/or %d most merged facets and/or min area %.2g\n",
    +          qh->KEEParea, qh->KEEPmerge, qh->KEEPminArea));
    +  FORALLfacet_(facetlist) {
    +    if (!facet->visible && facet->good)
    +      qh_setappend(qh, &facets, facet);
    +  }
    +  size= qh_setsize(qh, facets);
    +  if (qh->KEEParea) {
    +    qsort(SETaddr_(facets, facetT), (size_t)size,
    +             sizeof(facetT *), qh_compare_facetarea);
    +    if ((count= size - qh->KEEParea) > 0) {
    +      FOREACHfacet_(facets) {
    +        facet->good= False;
    +        if (--count == 0)
    +          break;
    +      }
    +    }
    +  }
    +  if (qh->KEEPmerge) {
    +    qsort(SETaddr_(facets, facetT), (size_t)size,
    +             sizeof(facetT *), qh_compare_facetmerge);
    +    if ((count= size - qh->KEEPmerge) > 0) {
    +      FOREACHfacet_(facets) {
    +        facet->good= False;
    +        if (--count == 0)
    +          break;
    +      }
    +    }
    +  }
    +  if (qh->KEEPminArea < REALmax/2) {
    +    FOREACHfacet_(facets) {
    +      if (!facet->isarea || facet->f.area < qh->KEEPminArea)
    +        facet->good= False;
    +    }
    +  }
    +  qh_settempfree(qh, &facets);
    +  count= 0;
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      count++;
    +  }
    +  qh->num_good= count;
    +} /* markkeep */
    +
    +
    +/*---------------------------------
    +
    +  qh_markvoronoi(qh, facetlist, facets, printall, isLower, numcenters )
    +    mark voronoi vertices for printing by site pairs
    +
    +  returns:
    +    temporary set of vertices indexed by pointid
    +    isLower set if printing lower hull (i.e., at least one facet is lower hull)
    +    numcenters= total number of Voronoi vertices
    +    bumps qh.printoutnum for vertex-at-infinity
    +    clears all facet->seen and sets facet->seen2
    +
    +    if selected
    +      facet->visitid= Voronoi vertex id
    +    else if upper hull (or 'Qu' and lower hull)
    +      facet->visitid= 0
    +    else
    +      facet->visitid >= qh->num_facets
    +
    +  notes:
    +    ignores qh.ATinfinity, if defined
    +*/
    +setT *qh_markvoronoi(qhT *qh, facetT *facetlist, setT *facets, boolT printall, boolT *isLowerp, int *numcentersp) {
    +  int numcenters=0;
    +  facetT *facet, **facetp;
    +  setT *vertices;
    +  boolT isLower= False;
    +
    +  qh->printoutnum++;
    +  qh_clearcenters(qh, qh_ASvoronoi);  /* in case, qh_printvdiagram2 called by user */
    +  qh_vertexneighbors(qh);
    +  vertices= qh_pointvertex(qh);
    +  if (qh->ATinfinity)
    +    SETelem_(vertices, qh->num_points-1)= NULL;
    +  qh->visit_id++;
    +  maximize_(qh->visit_id, (unsigned) qh->num_facets);
    +  FORALLfacet_(facetlist) {
    +    if (printall || !qh_skipfacet(qh, facet)) {
    +      if (!facet->upperdelaunay) {
    +        isLower= True;
    +        break;
    +      }
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (printall || !qh_skipfacet(qh, facet)) {
    +      if (!facet->upperdelaunay) {
    +        isLower= True;
    +        break;
    +      }
    +    }
    +  }
    +  FORALLfacets {
    +    if (facet->normal && (facet->upperdelaunay == isLower))
    +      facet->visitid= 0;  /* facetlist or facets may overwrite */
    +    else
    +      facet->visitid= qh->visit_id;
    +    facet->seen= False;
    +    facet->seen2= True;
    +  }
    +  numcenters++;  /* qh_INFINITE */
    +  FORALLfacet_(facetlist) {
    +    if (printall || !qh_skipfacet(qh, facet))
    +      facet->visitid= numcenters++;
    +  }
    +  FOREACHfacet_(facets) {
    +    if (printall || !qh_skipfacet(qh, facet))
    +      facet->visitid= numcenters++;
    +  }
    +  *isLowerp= isLower;
    +  *numcentersp= numcenters;
    +  trace2((qh, qh->ferr, 2007, "qh_markvoronoi: isLower %d numcenters %d\n", isLower, numcenters));
    +  return vertices;
    +} /* markvoronoi */
    +
    +/*---------------------------------
    +
    +  qh_order_vertexneighbors(qh, vertex )
    +    order facet neighbors of a 2-d or 3-d vertex by adjacency
    +
    +  notes:
    +    does not orient the neighbors
    +
    +  design:
    +    initialize a new neighbor set with the first facet in vertex->neighbors
    +    while vertex->neighbors non-empty
    +      select next neighbor in the previous facet's neighbor set
    +    set vertex->neighbors to the new neighbor set
    +*/
    +void qh_order_vertexneighbors(qhT *qh, vertexT *vertex) {
    +  setT *newset;
    +  facetT *facet, *neighbor, **neighborp;
    +
    +  trace4((qh, qh->ferr, 4018, "qh_order_vertexneighbors: order neighbors of v%d for 3-d\n", vertex->id));
    +  newset= qh_settemp(qh, qh_setsize(qh, vertex->neighbors));
    +  facet= (facetT*)qh_setdellast(vertex->neighbors);
    +  qh_setappend(qh, &newset, facet);
    +  while (qh_setsize(qh, vertex->neighbors)) {
    +    FOREACHneighbor_(vertex) {
    +      if (qh_setin(facet->neighbors, neighbor)) {
    +        qh_setdel(vertex->neighbors, neighbor);
    +        qh_setappend(qh, &newset, neighbor);
    +        facet= neighbor;
    +        break;
    +      }
    +    }
    +    if (!neighbor) {
    +      qh_fprintf(qh, qh->ferr, 6066, "qhull internal error (qh_order_vertexneighbors): no neighbor of v%d for f%d\n",
    +        vertex->id, facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +  }
    +  qh_setfree(qh, &vertex->neighbors);
    +  qh_settemppop(qh);
    +  vertex->neighbors= newset;
    +} /* order_vertexneighbors */
    +
    +/*---------------------------------
    +
    +  qh_prepare_output(qh, )
    +    prepare for qh_produce_output2(qh) according to
    +      qh.KEEPminArea, KEEParea, KEEPmerge, GOODvertex, GOODthreshold, GOODpoint, ONLYgood, SPLITthresholds
    +    does not reset facet->good
    +
    +  notes
    +    except for PRINTstatistics, no-op if previously called with same options
    +*/
    +void qh_prepare_output(qhT *qh) {
    +  if (qh->VORONOI) {
    +    qh_clearcenters(qh, qh_ASvoronoi);  /* must be before qh_triangulate */
    +    qh_vertexneighbors(qh);
    +  }
    +  if (qh->TRIangulate && !qh->hasTriangulation) {
    +    qh_triangulate(qh);
    +    if (qh->VERIFYoutput && !qh->CHECKfrequently)
    +      qh_checkpolygon(qh, qh->facet_list);
    +  }
    +  qh_findgood_all(qh, qh->facet_list);
    +  if (qh->GETarea)
    +    qh_getarea(qh, qh->facet_list);
    +  if (qh->KEEParea || qh->KEEPmerge || qh->KEEPminArea < REALmax/2)
    +    qh_markkeep(qh, qh->facet_list);
    +  if (qh->PRINTstatistics)
    +    qh_collectstatistics(qh);
    +}
    +
    +/*---------------------------------
    +
    +  qh_printafacet(qh, fp, format, facet, printall )
    +    print facet to fp in given output format (see qh.PRINTout)
    +
    +  returns:
    +    nop if !printall and qh_skipfacet()
    +    nop if visible facet and NEWfacets and format != PRINTfacets
    +    must match qh_countfacets
    +
    +  notes
    +    preserves qh.visit_id
    +    facet->normal may be null if PREmerge/MERGEexact and STOPcone before merge
    +
    +  see
    +    qh_printbegin() and qh_printend()
    +
    +  design:
    +    test for printing facet
    +    call appropriate routine for format
    +    or output results directly
    +*/
    +void qh_printafacet(qhT *qh, FILE *fp, qh_PRINT format, facetT *facet, boolT printall) {
    +  realT color[4], offset, dist, outerplane, innerplane;
    +  boolT zerodiv;
    +  coordT *point, *normp, *coordp, **pointp, *feasiblep;
    +  int k;
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +
    +  if (!printall && qh_skipfacet(qh, facet))
    +    return;
    +  if (facet->visible && qh->NEWfacets && format != qh_PRINTfacets)
    +    return;
    +  qh->printoutnum++;
    +  switch (format) {
    +  case qh_PRINTarea:
    +    if (facet->isarea) {
    +      qh_fprintf(qh, fp, 9009, qh_REAL_1, facet->f.area);
    +      qh_fprintf(qh, fp, 9010, "\n");
    +    }else
    +      qh_fprintf(qh, fp, 9011, "0\n");
    +    break;
    +  case qh_PRINTcoplanars:
    +    qh_fprintf(qh, fp, 9012, "%d", qh_setsize(qh, facet->coplanarset));
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_fprintf(qh, fp, 9013, " %d", qh_pointid(qh, point));
    +    qh_fprintf(qh, fp, 9014, "\n");
    +    break;
    +  case qh_PRINTcentrums:
    +    qh_printcenter(qh, fp, format, NULL, facet);
    +    break;
    +  case qh_PRINTfacets:
    +    qh_printfacet(qh, fp, facet);
    +    break;
    +  case qh_PRINTfacets_xridge:
    +    qh_printfacetheader(qh, fp, facet);
    +    break;
    +  case qh_PRINTgeom:  /* either 2 , 3, or 4-d by qh_printbegin */
    +    if (!facet->normal)
    +      break;
    +    for (k=qh->hull_dim; k--; ) {
    +      color[k]= (facet->normal[k]+1.0)/2.0;
    +      maximize_(color[k], -1.0);
    +      minimize_(color[k], +1.0);
    +    }
    +    qh_projectdim3(qh, color, color);
    +    if (qh->PRINTdim != qh->hull_dim)
    +      qh_normalize2(qh, color, 3, True, NULL, NULL);
    +    if (qh->hull_dim <= 2)
    +      qh_printfacet2geom(qh, fp, facet, color);
    +    else if (qh->hull_dim == 3) {
    +      if (facet->simplicial)
    +        qh_printfacet3geom_simplicial(qh, fp, facet, color);
    +      else
    +        qh_printfacet3geom_nonsimplicial(qh, fp, facet, color);
    +    }else {
    +      if (facet->simplicial)
    +        qh_printfacet4geom_simplicial(qh, fp, facet, color);
    +      else
    +        qh_printfacet4geom_nonsimplicial(qh, fp, facet, color);
    +    }
    +    break;
    +  case qh_PRINTids:
    +    qh_fprintf(qh, fp, 9015, "%d\n", facet->id);
    +    break;
    +  case qh_PRINTincidences:
    +  case qh_PRINToff:
    +  case qh_PRINTtriangles:
    +    if (qh->hull_dim == 3 && format != qh_PRINTtriangles)
    +      qh_printfacet3vertex(qh, fp, facet, format);
    +    else if (facet->simplicial || qh->hull_dim == 2 || format == qh_PRINToff)
    +      qh_printfacetNvertex_simplicial(qh, fp, facet, format);
    +    else
    +      qh_printfacetNvertex_nonsimplicial(qh, fp, facet, qh->printoutvar++, format);
    +    break;
    +  case qh_PRINTinner:
    +    qh_outerinner(qh, facet, NULL, &innerplane);
    +    offset= facet->offset - innerplane;
    +    goto LABELprintnorm;
    +    break; /* prevent warning */
    +  case qh_PRINTmerges:
    +    qh_fprintf(qh, fp, 9016, "%d\n", facet->nummerge);
    +    break;
    +  case qh_PRINTnormals:
    +    offset= facet->offset;
    +    goto LABELprintnorm;
    +    break; /* prevent warning */
    +  case qh_PRINTouter:
    +    qh_outerinner(qh, facet, &outerplane, NULL);
    +    offset= facet->offset - outerplane;
    +  LABELprintnorm:
    +    if (!facet->normal) {
    +      qh_fprintf(qh, fp, 9017, "no normal for facet f%d\n", facet->id);
    +      break;
    +    }
    +    if (qh->CDDoutput) {
    +      qh_fprintf(qh, fp, 9018, qh_REAL_1, -offset);
    +      for (k=0; k < qh->hull_dim; k++)
    +        qh_fprintf(qh, fp, 9019, qh_REAL_1, -facet->normal[k]);
    +    }else {
    +      for (k=0; k < qh->hull_dim; k++)
    +        qh_fprintf(qh, fp, 9020, qh_REAL_1, facet->normal[k]);
    +      qh_fprintf(qh, fp, 9021, qh_REAL_1, offset);
    +    }
    +    qh_fprintf(qh, fp, 9022, "\n");
    +    break;
    +  case qh_PRINTmathematica:  /* either 2 or 3-d by qh_printbegin */
    +  case qh_PRINTmaple:
    +    if (qh->hull_dim == 2)
    +      qh_printfacet2math(qh, fp, facet, format, qh->printoutvar++);
    +    else
    +      qh_printfacet3math(qh, fp, facet, format, qh->printoutvar++);
    +    break;
    +  case qh_PRINTneighbors:
    +    qh_fprintf(qh, fp, 9023, "%d", qh_setsize(qh, facet->neighbors));
    +    FOREACHneighbor_(facet)
    +      qh_fprintf(qh, fp, 9024, " %d",
    +               neighbor->visitid ? neighbor->visitid - 1: 0 - neighbor->id);
    +    qh_fprintf(qh, fp, 9025, "\n");
    +    break;
    +  case qh_PRINTpointintersect:
    +    if (!qh->feasible_point) {
    +      qh_fprintf(qh, qh->ferr, 6067, "qhull input error (qh_printafacet): option 'Fp' needs qh->feasible_point\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (facet->offset > 0)
    +      goto LABELprintinfinite;
    +    point= coordp= (coordT*)qh_memalloc(qh, qh->normal_size);
    +    normp= facet->normal;
    +    feasiblep= qh->feasible_point;
    +    if (facet->offset < -qh->MINdenom) {
    +      for (k=qh->hull_dim; k--; )
    +        *(coordp++)= (*(normp++) / - facet->offset) + *(feasiblep++);
    +    }else {
    +      for (k=qh->hull_dim; k--; ) {
    +        *(coordp++)= qh_divzero(*(normp++), facet->offset, qh->MINdenom_1,
    +                                 &zerodiv) + *(feasiblep++);
    +        if (zerodiv) {
    +          qh_memfree(qh, point, qh->normal_size);
    +          goto LABELprintinfinite;
    +        }
    +      }
    +    }
    +    qh_printpoint(qh, fp, NULL, point);
    +    qh_memfree(qh, point, qh->normal_size);
    +    break;
    +  LABELprintinfinite:
    +    for (k=qh->hull_dim; k--; )
    +      qh_fprintf(qh, fp, 9026, qh_REAL_1, qh_INFINITE);
    +    qh_fprintf(qh, fp, 9027, "\n");
    +    break;
    +  case qh_PRINTpointnearest:
    +    FOREACHpoint_(facet->coplanarset) {
    +      int id, id2;
    +      vertex= qh_nearvertex(qh, facet, point, &dist);
    +      id= qh_pointid(qh, vertex->point);
    +      id2= qh_pointid(qh, point);
    +      qh_fprintf(qh, fp, 9028, "%d %d %d " qh_REAL_1 "\n", id, id2, facet->id, dist);
    +    }
    +    break;
    +  case qh_PRINTpoints:  /* VORONOI only by qh_printbegin */
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9029, "1 ");
    +    qh_printcenter(qh, fp, format, NULL, facet);
    +    break;
    +  case qh_PRINTvertices:
    +    qh_fprintf(qh, fp, 9030, "%d", qh_setsize(qh, facet->vertices));
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(qh, fp, 9031, " %d", qh_pointid(qh, vertex->point));
    +    qh_fprintf(qh, fp, 9032, "\n");
    +    break;
    +  default:
    +    break;
    +  }
    +} /* printafacet */
    +
    +/*---------------------------------
    +
    +  qh_printbegin(qh, )
    +    prints header for all output formats
    +
    +  returns:
    +    checks for valid format
    +
    +  notes:
    +    uses qh.visit_id for 3/4off
    +    changes qh.interior_point if printing centrums
    +    qh_countfacets clears facet->visitid for non-good facets
    +
    +  see
    +    qh_printend() and qh_printafacet()
    +
    +  design:
    +    count facets and related statistics
    +    print header for format
    +*/
    +void qh_printbegin(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numcoplanars, numtricoplanars;
    +  int i, num;
    +  facetT *facet, **facetp;
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +  pointT *point, **pointp, *pointtemp;
    +
    +  qh->printoutnum= 0;
    +  qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);
    +  switch (format) {
    +  case qh_PRINTnone:
    +    break;
    +  case qh_PRINTarea:
    +    qh_fprintf(qh, fp, 9033, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTcoplanars:
    +    qh_fprintf(qh, fp, 9034, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTcentrums:
    +    if (qh->CENTERtype == qh_ASnone)
    +      qh_clearcenters(qh, qh_AScentrum);
    +    qh_fprintf(qh, fp, 9035, "%d\n%d\n", qh->hull_dim, numfacets);
    +    break;
    +  case qh_PRINTfacets:
    +  case qh_PRINTfacets_xridge:
    +    if (facetlist)
    +      qh_printvertexlist(qh, fp, "Vertices and facets:\n", facetlist, facets, printall);
    +    break;
    +  case qh_PRINTgeom:
    +    if (qh->hull_dim > 4)  /* qh_initqhull_globals also checks */
    +      goto LABELnoformat;
    +    if (qh->VORONOI && qh->hull_dim > 3)  /* PRINTdim == DROPdim == hull_dim-1 */
    +      goto LABELnoformat;
    +    if (qh->hull_dim == 2 && (qh->PRINTridges || qh->DOintersections))
    +      qh_fprintf(qh, qh->ferr, 7049, "qhull warning: output for ridges and intersections not implemented in 2-d\n");
    +    if (qh->hull_dim == 4 && (qh->PRINTinner || qh->PRINTouter ||
    +                             (qh->PRINTdim == 4 && qh->PRINTcentrums)))
    +      qh_fprintf(qh, qh->ferr, 7050, "qhull warning: output for outer/inner planes and centrums not implemented in 4-d\n");
    +    if (qh->PRINTdim == 4 && (qh->PRINTspheres))
    +      qh_fprintf(qh, qh->ferr, 7051, "qhull warning: output for vertices not implemented in 4-d\n");
    +    if (qh->PRINTdim == 4 && qh->DOintersections && qh->PRINTnoplanes)
    +      qh_fprintf(qh, qh->ferr, 7052, "qhull warning: 'Gnh' generates no output in 4-d\n");
    +    if (qh->PRINTdim == 2) {
    +      qh_fprintf(qh, fp, 9036, "{appearance {linewidth 3} LIST # %s | %s\n",
    +              qh->rbox_command, qh->qhull_command);
    +    }else if (qh->PRINTdim == 3) {
    +      qh_fprintf(qh, fp, 9037, "{appearance {+edge -evert linewidth 2} LIST # %s | %s\n",
    +              qh->rbox_command, qh->qhull_command);
    +    }else if (qh->PRINTdim == 4) {
    +      qh->visit_id++;
    +      num= 0;
    +      FORALLfacet_(facetlist)    /* get number of ridges to be printed */
    +        qh_printend4geom(qh, NULL, facet, &num, printall);
    +      FOREACHfacet_(facets)
    +        qh_printend4geom(qh, NULL, facet, &num, printall);
    +      qh->ridgeoutnum= num;
    +      qh->printoutvar= 0;  /* counts number of ridges in output */
    +      qh_fprintf(qh, fp, 9038, "LIST # %s | %s\n", qh->rbox_command, qh->qhull_command);
    +    }
    +
    +    if (qh->PRINTdots) {
    +      qh->printoutnum++;
    +      num= qh->num_points + qh_setsize(qh, qh->other_points);
    +      if (qh->DELAUNAY && qh->ATinfinity)
    +        num--;
    +      if (qh->PRINTdim == 4)
    +        qh_fprintf(qh, fp, 9039, "4VECT %d %d 1\n", num, num);
    +      else
    +        qh_fprintf(qh, fp, 9040, "VECT %d %d 1\n", num, num);
    +
    +      for (i=num; i--; ) {
    +        if (i % 20 == 0)
    +          qh_fprintf(qh, fp, 9041, "\n");
    +        qh_fprintf(qh, fp, 9042, "1 ");
    +      }
    +      qh_fprintf(qh, fp, 9043, "# 1 point per line\n1 ");
    +      for (i=num-1; i--; ) { /* num at least 3 for D2 */
    +        if (i % 20 == 0)
    +          qh_fprintf(qh, fp, 9044, "\n");
    +        qh_fprintf(qh, fp, 9045, "0 ");
    +      }
    +      qh_fprintf(qh, fp, 9046, "# 1 color for all\n");
    +      FORALLpoints {
    +        if (!qh->DELAUNAY || !qh->ATinfinity || qh_pointid(qh, point) != qh->num_points-1) {
    +          if (qh->PRINTdim == 4)
    +            qh_printpoint(qh, fp, NULL, point);
    +            else
    +              qh_printpoint3(qh, fp, point);
    +        }
    +      }
    +      FOREACHpoint_(qh->other_points) {
    +        if (qh->PRINTdim == 4)
    +          qh_printpoint(qh, fp, NULL, point);
    +        else
    +          qh_printpoint3(qh, fp, point);
    +      }
    +      qh_fprintf(qh, fp, 9047, "0 1 1 1  # color of points\n");
    +    }
    +
    +    if (qh->PRINTdim == 4  && !qh->PRINTnoplanes)
    +      /* 4dview loads up multiple 4OFF objects slowly */
    +      qh_fprintf(qh, fp, 9048, "4OFF %d %d 1\n", 3*qh->ridgeoutnum, qh->ridgeoutnum);
    +    qh->PRINTcradius= 2 * qh->DISTround;  /* include test DISTround */
    +    if (qh->PREmerge) {
    +      maximize_(qh->PRINTcradius, qh->premerge_centrum + qh->DISTround);
    +    }else if (qh->POSTmerge)
    +      maximize_(qh->PRINTcradius, qh->postmerge_centrum + qh->DISTround);
    +    qh->PRINTradius= qh->PRINTcradius;
    +    if (qh->PRINTspheres + qh->PRINTcoplanar)
    +      maximize_(qh->PRINTradius, qh->MAXabs_coord * qh_MINradius);
    +    if (qh->premerge_cos < REALmax/2) {
    +      maximize_(qh->PRINTradius, (1- qh->premerge_cos) * qh->MAXabs_coord);
    +    }else if (!qh->PREmerge && qh->POSTmerge && qh->postmerge_cos < REALmax/2) {
    +      maximize_(qh->PRINTradius, (1- qh->postmerge_cos) * qh->MAXabs_coord);
    +    }
    +    maximize_(qh->PRINTradius, qh->MINvisible);
    +    if (qh->JOGGLEmax < REALmax/2)
    +      qh->PRINTradius += qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
    +    if (qh->PRINTdim != 4 &&
    +        (qh->PRINTcoplanar || qh->PRINTspheres || qh->PRINTcentrums)) {
    +      vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +      if (qh->PRINTspheres && qh->PRINTdim <= 3)
    +        qh_printspheres(qh, fp, vertices, qh->PRINTradius);
    +      if (qh->PRINTcoplanar || qh->PRINTcentrums) {
    +        qh->firstcentrum= True;
    +        if (qh->PRINTcoplanar&& !qh->PRINTspheres) {
    +          FOREACHvertex_(vertices)
    +            qh_printpointvect2(qh, fp, vertex->point, NULL, qh->interior_point, qh->PRINTradius);
    +        }
    +        FORALLfacet_(facetlist) {
    +          if (!printall && qh_skipfacet(qh, facet))
    +            continue;
    +          if (!facet->normal)
    +            continue;
    +          if (qh->PRINTcentrums && qh->PRINTdim <= 3)
    +            qh_printcentrum(qh, fp, facet, qh->PRINTcradius);
    +          if (!qh->PRINTcoplanar)
    +            continue;
    +          FOREACHpoint_(facet->coplanarset)
    +            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
    +          FOREACHpoint_(facet->outsideset)
    +            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
    +        }
    +        FOREACHfacet_(facets) {
    +          if (!printall && qh_skipfacet(qh, facet))
    +            continue;
    +          if (!facet->normal)
    +            continue;
    +          if (qh->PRINTcentrums && qh->PRINTdim <= 3)
    +            qh_printcentrum(qh, fp, facet, qh->PRINTcradius);
    +          if (!qh->PRINTcoplanar)
    +            continue;
    +          FOREACHpoint_(facet->coplanarset)
    +            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
    +          FOREACHpoint_(facet->outsideset)
    +            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
    +        }
    +      }
    +      qh_settempfree(qh, &vertices);
    +    }
    +    qh->visit_id++; /* for printing hyperplane intersections */
    +    break;
    +  case qh_PRINTids:
    +    qh_fprintf(qh, fp, 9049, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTincidences:
    +    if (qh->VORONOI && qh->PRINTprecision)
    +      qh_fprintf(qh, qh->ferr, 7053, "qhull warning: writing Delaunay.  Use 'p' or 'o' for Voronoi centers\n");
    +    qh->printoutvar= qh->vertex_id;  /* centrum id for non-simplicial facets */
    +    if (qh->hull_dim <= 3)
    +      qh_fprintf(qh, fp, 9050, "%d\n", numfacets);
    +    else
    +      qh_fprintf(qh, fp, 9051, "%d\n", numsimplicial+numridges);
    +    break;
    +  case qh_PRINTinner:
    +  case qh_PRINTnormals:
    +  case qh_PRINTouter:
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9052, "%s | %s\nbegin\n    %d %d real\n", qh->rbox_command,
    +            qh->qhull_command, numfacets, qh->hull_dim+1);
    +    else
    +      qh_fprintf(qh, fp, 9053, "%d\n%d\n", qh->hull_dim+1, numfacets);
    +    break;
    +  case qh_PRINTmathematica:
    +  case qh_PRINTmaple:
    +    if (qh->hull_dim > 3)  /* qh_initbuffers also checks */
    +      goto LABELnoformat;
    +    if (qh->VORONOI)
    +      qh_fprintf(qh, qh->ferr, 7054, "qhull warning: output is the Delaunay triangulation\n");
    +    if (format == qh_PRINTmaple) {
    +      if (qh->hull_dim == 2)
    +        qh_fprintf(qh, fp, 9054, "PLOT(CURVES(\n");
    +      else
    +        qh_fprintf(qh, fp, 9055, "PLOT3D(POLYGONS(\n");
    +    }else
    +      qh_fprintf(qh, fp, 9056, "{\n");
    +    qh->printoutvar= 0;   /* counts number of facets for notfirst */
    +    break;
    +  case qh_PRINTmerges:
    +    qh_fprintf(qh, fp, 9057, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTpointintersect:
    +    qh_fprintf(qh, fp, 9058, "%d\n%d\n", qh->hull_dim, numfacets);
    +    break;
    +  case qh_PRINTneighbors:
    +    qh_fprintf(qh, fp, 9059, "%d\n", numfacets);
    +    break;
    +  case qh_PRINToff:
    +  case qh_PRINTtriangles:
    +    if (qh->VORONOI)
    +      goto LABELnoformat;
    +    num = qh->hull_dim;
    +    if (format == qh_PRINToff || qh->hull_dim == 2)
    +      qh_fprintf(qh, fp, 9060, "%d\n%d %d %d\n", num,
    +        qh->num_points+qh_setsize(qh, qh->other_points), numfacets, totneighbors/2);
    +    else { /* qh_PRINTtriangles */
    +      qh->printoutvar= qh->num_points+qh_setsize(qh, qh->other_points); /* first centrum */
    +      if (qh->DELAUNAY)
    +        num--;  /* drop last dimension */
    +      qh_fprintf(qh, fp, 9061, "%d\n%d %d %d\n", num, qh->printoutvar
    +        + numfacets - numsimplicial, numsimplicial + numridges, totneighbors/2);
    +    }
    +    FORALLpoints
    +      qh_printpointid(qh, qh->fout, NULL, num, point, qh_IDunknown);
    +    FOREACHpoint_(qh->other_points)
    +      qh_printpointid(qh, qh->fout, NULL, num, point, qh_IDunknown);
    +    if (format == qh_PRINTtriangles && qh->hull_dim > 2) {
    +      FORALLfacets {
    +        if (!facet->simplicial && facet->visitid)
    +          qh_printcenter(qh, qh->fout, format, NULL, facet);
    +      }
    +    }
    +    break;
    +  case qh_PRINTpointnearest:
    +    qh_fprintf(qh, fp, 9062, "%d\n", numcoplanars);
    +    break;
    +  case qh_PRINTpoints:
    +    if (!qh->VORONOI)
    +      goto LABELnoformat;
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9063, "%s | %s\nbegin\n%d %d real\n", qh->rbox_command,
    +           qh->qhull_command, numfacets, qh->hull_dim);
    +    else
    +      qh_fprintf(qh, fp, 9064, "%d\n%d\n", qh->hull_dim-1, numfacets);
    +    break;
    +  case qh_PRINTvertices:
    +    qh_fprintf(qh, fp, 9065, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTsummary:
    +  default:
    +  LABELnoformat:
    +    qh_fprintf(qh, qh->ferr, 6068, "qhull internal error (qh_printbegin): can not use this format for dimension %d\n",
    +         qh->hull_dim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +} /* printbegin */
    +
    +/*---------------------------------
    +
    +  qh_printcenter(qh, fp, string, facet )
    +    print facet->center as centrum or Voronoi center
    +    string may be NULL.  Don't include '%' codes.
    +    nop if qh->CENTERtype neither CENTERvoronoi nor CENTERcentrum
    +    if upper envelope of Delaunay triangulation and point at-infinity
    +      prints qh_INFINITE instead;
    +
    +  notes:
    +    defines facet->center if needed
    +    if format=PRINTgeom, adds a 0 if would otherwise be 2-d
    +    Same as QhullFacet::printCenter
    +*/
    +void qh_printcenter(qhT *qh, FILE *fp, qh_PRINT format, const char *string, facetT *facet) {
    +  int k, num;
    +
    +  if (qh->CENTERtype != qh_ASvoronoi && qh->CENTERtype != qh_AScentrum)
    +    return;
    +  if (string)
    +    qh_fprintf(qh, fp, 9066, string);
    +  if (qh->CENTERtype == qh_ASvoronoi) {
    +    num= qh->hull_dim-1;
    +    if (!facet->normal || !facet->upperdelaunay || !qh->ATinfinity) {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(qh, facet->vertices);
    +      for (k=0; k < num; k++)
    +        qh_fprintf(qh, fp, 9067, qh_REAL_1, facet->center[k]);
    +    }else {
    +      for (k=0; k < num; k++)
    +        qh_fprintf(qh, fp, 9068, qh_REAL_1, qh_INFINITE);
    +    }
    +  }else /* qh->CENTERtype == qh_AScentrum */ {
    +    num= qh->hull_dim;
    +    if (format == qh_PRINTtriangles && qh->DELAUNAY)
    +      num--;
    +    if (!facet->center)
    +      facet->center= qh_getcentrum(qh, facet);
    +    for (k=0; k < num; k++)
    +      qh_fprintf(qh, fp, 9069, qh_REAL_1, facet->center[k]);
    +  }
    +  if (format == qh_PRINTgeom && num == 2)
    +    qh_fprintf(qh, fp, 9070, " 0\n");
    +  else
    +    qh_fprintf(qh, fp, 9071, "\n");
    +} /* printcenter */
    +
    +/*---------------------------------
    +
    +  qh_printcentrum(qh, fp, facet, radius )
    +    print centrum for a facet in OOGL format
    +    radius defines size of centrum
    +    2-d or 3-d only
    +
    +  returns:
    +    defines facet->center if needed
    +*/
    +void qh_printcentrum(qhT *qh, FILE *fp, facetT *facet, realT radius) {
    +  pointT *centrum, *projpt;
    +  boolT tempcentrum= False;
    +  realT xaxis[4], yaxis[4], normal[4], dist;
    +  realT green[3]={0, 1, 0};
    +  vertexT *apex;
    +  int k;
    +
    +  if (qh->CENTERtype == qh_AScentrum) {
    +    if (!facet->center)
    +      facet->center= qh_getcentrum(qh, facet);
    +    centrum= facet->center;
    +  }else {
    +    centrum= qh_getcentrum(qh, facet);
    +    tempcentrum= True;
    +  }
    +  qh_fprintf(qh, fp, 9072, "{appearance {-normal -edge normscale 0} ");
    +  if (qh->firstcentrum) {
    +    qh->firstcentrum= False;
    +    qh_fprintf(qh, fp, 9073, "{INST geom { define centrum CQUAD  # f%d\n\
    +-0.3 -0.3 0.0001     0 0 1 1\n\
    + 0.3 -0.3 0.0001     0 0 1 1\n\
    + 0.3  0.3 0.0001     0 0 1 1\n\
    +-0.3  0.3 0.0001     0 0 1 1 } transform { \n", facet->id);
    +  }else
    +    qh_fprintf(qh, fp, 9074, "{INST geom { : centrum } transform { # f%d\n", facet->id);
    +  apex= SETfirstt_(facet->vertices, vertexT);
    +  qh_distplane(qh, apex->point, facet, &dist);
    +  projpt= qh_projectpoint(qh, apex->point, facet, dist);
    +  for (k=qh->hull_dim; k--; ) {
    +    xaxis[k]= projpt[k] - centrum[k];
    +    normal[k]= facet->normal[k];
    +  }
    +  if (qh->hull_dim == 2) {
    +    xaxis[2]= 0;
    +    normal[2]= 0;
    +  }else if (qh->hull_dim == 4) {
    +    qh_projectdim3(qh, xaxis, xaxis);
    +    qh_projectdim3(qh, normal, normal);
    +    qh_normalize2(qh, normal, qh->PRINTdim, True, NULL, NULL);
    +  }
    +  qh_crossproduct(3, xaxis, normal, yaxis);
    +  qh_fprintf(qh, fp, 9075, "%8.4g %8.4g %8.4g 0\n", xaxis[0], xaxis[1], xaxis[2]);
    +  qh_fprintf(qh, fp, 9076, "%8.4g %8.4g %8.4g 0\n", yaxis[0], yaxis[1], yaxis[2]);
    +  qh_fprintf(qh, fp, 9077, "%8.4g %8.4g %8.4g 0\n", normal[0], normal[1], normal[2]);
    +  qh_printpoint3(qh, fp, centrum);
    +  qh_fprintf(qh, fp, 9078, "1 }}}\n");
    +  qh_memfree(qh, projpt, qh->normal_size);
    +  qh_printpointvect(qh, fp, centrum, facet->normal, NULL, radius, green);
    +  if (tempcentrum)
    +    qh_memfree(qh, centrum, qh->normal_size);
    +} /* printcentrum */
    +
    +/*---------------------------------
    +
    +  qh_printend(qh, fp, format )
    +    prints trailer for all output formats
    +
    +  see:
    +    qh_printbegin() and qh_printafacet()
    +
    +*/
    +void qh_printend(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int num;
    +  facetT *facet, **facetp;
    +
    +  if (!qh->printoutnum)
    +    qh_fprintf(qh, qh->ferr, 7055, "qhull warning: no facets printed\n");
    +  switch (format) {
    +  case qh_PRINTgeom:
    +    if (qh->hull_dim == 4 && qh->DROPdim < 0  && !qh->PRINTnoplanes) {
    +      qh->visit_id++;
    +      num= 0;
    +      FORALLfacet_(facetlist)
    +        qh_printend4geom(qh, fp, facet,&num, printall);
    +      FOREACHfacet_(facets)
    +        qh_printend4geom(qh, fp, facet, &num, printall);
    +      if (num != qh->ridgeoutnum || qh->printoutvar != qh->ridgeoutnum) {
    +        qh_fprintf(qh, qh->ferr, 6069, "qhull internal error (qh_printend): number of ridges %d != number printed %d and at end %d\n", qh->ridgeoutnum, qh->printoutvar, num);
    +        qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +      }
    +    }else
    +      qh_fprintf(qh, fp, 9079, "}\n");
    +    break;
    +  case qh_PRINTinner:
    +  case qh_PRINTnormals:
    +  case qh_PRINTouter:
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9080, "end\n");
    +    break;
    +  case qh_PRINTmaple:
    +    qh_fprintf(qh, fp, 9081, "));\n");
    +    break;
    +  case qh_PRINTmathematica:
    +    qh_fprintf(qh, fp, 9082, "}\n");
    +    break;
    +  case qh_PRINTpoints:
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9083, "end\n");
    +    break;
    +  default:
    +    break;
    +  }
    +} /* printend */
    +
    +/*---------------------------------
    +
    +  qh_printend4geom(qh, fp, facet, numridges, printall )
    +    helper function for qh_printbegin/printend
    +
    +  returns:
    +    number of printed ridges
    +
    +  notes:
    +    just counts printed ridges if fp=NULL
    +    uses facet->visitid
    +    must agree with qh_printfacet4geom...
    +
    +  design:
    +    computes color for facet from its normal
    +    prints each ridge of facet
    +*/
    +void qh_printend4geom(qhT *qh, FILE *fp, facetT *facet, int *nump, boolT printall) {
    +  realT color[3];
    +  int i, num= *nump;
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (!printall && qh_skipfacet(qh, facet))
    +    return;
    +  if (qh->PRINTnoplanes || (facet->visible && qh->NEWfacets))
    +    return;
    +  if (!facet->normal)
    +    return;
    +  if (fp) {
    +    for (i=0; i < 3; i++) {
    +      color[i]= (facet->normal[i]+1.0)/2.0;
    +      maximize_(color[i], -1.0);
    +      minimize_(color[i], +1.0);
    +    }
    +  }
    +  facet->visitid= qh->visit_id;
    +  if (facet->simplicial) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh->visit_id) {
    +        if (fp)
    +          qh_fprintf(qh, fp, 9084, "3 %d %d %d %8.4g %8.4g %8.4g 1 # f%d f%d\n",
    +                 3*num, 3*num+1, 3*num+2, color[0], color[1], color[2],
    +                 facet->id, neighbor->id);
    +        num++;
    +      }
    +    }
    +  }else {
    +    FOREACHridge_(facet->ridges) {
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->visitid != qh->visit_id) {
    +        if (fp)
    +          qh_fprintf(qh, fp, 9085, "3 %d %d %d %8.4g %8.4g %8.4g 1 #r%d f%d f%d\n",
    +                 3*num, 3*num+1, 3*num+2, color[0], color[1], color[2],
    +                 ridge->id, facet->id, neighbor->id);
    +        num++;
    +      }
    +    }
    +  }
    +  *nump= num;
    +} /* printend4geom */
    +
    +/*---------------------------------
    +
    +  qh_printextremes(qh, fp, facetlist, facets, printall )
    +    print extreme points for convex hulls or halfspace intersections
    +
    +  notes:
    +    #points, followed by ids, one per line
    +
    +    sorted by id
    +    same order as qh_printpoints_out if no coplanar/interior points
    +*/
    +void qh_printextremes(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices, *points;
    +  pointT *point;
    +  vertexT *vertex, **vertexp;
    +  int id;
    +  int numpoints=0, point_i, point_n;
    +  int allpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +
    +  points= qh_settemp(qh, allpoints);
    +  qh_setzero(qh, points, 0, allpoints);
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  FOREACHvertex_(vertices) {
    +    id= qh_pointid(qh, vertex->point);
    +    if (id >= 0) {
    +      SETelem_(points, id)= vertex->point;
    +      numpoints++;
    +    }
    +  }
    +  qh_settempfree(qh, &vertices);
    +  qh_fprintf(qh, fp, 9086, "%d\n", numpoints);
    +  FOREACHpoint_i_(qh, points) {
    +    if (point)
    +      qh_fprintf(qh, fp, 9087, "%d\n", point_i);
    +  }
    +  qh_settempfree(qh, &points);
    +} /* printextremes */
    +
    +/*---------------------------------
    +
    +  qh_printextremes_2d(qh, fp, facetlist, facets, printall )
    +    prints point ids for facets in qh_ORIENTclock order
    +
    +  notes:
    +    #points, followed by ids, one per line
    +    if facetlist/facets are disjoint than the output includes skips
    +    errors if facets form a loop
    +    does not print coplanar points
    +*/
    +void qh_printextremes_2d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numridges, totneighbors, numcoplanars, numsimplicial, numtricoplanars;
    +  setT *vertices;
    +  facetT *facet, *startfacet, *nextfacet;
    +  vertexT *vertexA, *vertexB;
    +
    +  qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars); /* marks qh->visit_id */
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  qh_fprintf(qh, fp, 9088, "%d\n", qh_setsize(qh, vertices));
    +  qh_settempfree(qh, &vertices);
    +  if (!numfacets)
    +    return;
    +  facet= startfacet= facetlist ? facetlist : SETfirstt_(facets, facetT);
    +  qh->vertex_visit++;
    +  qh->visit_id++;
    +  do {
    +    if (facet->toporient ^ qh_ORIENTclock) {
    +      vertexA= SETfirstt_(facet->vertices, vertexT);
    +      vertexB= SETsecondt_(facet->vertices, vertexT);
    +      nextfacet= SETfirstt_(facet->neighbors, facetT);
    +    }else {
    +      vertexA= SETsecondt_(facet->vertices, vertexT);
    +      vertexB= SETfirstt_(facet->vertices, vertexT);
    +      nextfacet= SETsecondt_(facet->neighbors, facetT);
    +    }
    +    if (facet->visitid == qh->visit_id) {
    +      qh_fprintf(qh, qh->ferr, 6218, "Qhull internal error (qh_printextremes_2d): loop in facet list.  facet %d nextfacet %d\n",
    +                 facet->id, nextfacet->id);
    +      qh_errexit2(qh, qh_ERRqhull, facet, nextfacet);
    +    }
    +    if (facet->visitid) {
    +      if (vertexA->visitid != qh->vertex_visit) {
    +        vertexA->visitid= qh->vertex_visit;
    +        qh_fprintf(qh, fp, 9089, "%d\n", qh_pointid(qh, vertexA->point));
    +      }
    +      if (vertexB->visitid != qh->vertex_visit) {
    +        vertexB->visitid= qh->vertex_visit;
    +        qh_fprintf(qh, fp, 9090, "%d\n", qh_pointid(qh, vertexB->point));
    +      }
    +    }
    +    facet->visitid= qh->visit_id;
    +    facet= nextfacet;
    +  }while (facet && facet != startfacet);
    +} /* printextremes_2d */
    +
    +/*---------------------------------
    +
    +  qh_printextremes_d(qh, fp, facetlist, facets, printall )
    +    print extreme points of input sites for Delaunay triangulations
    +
    +  notes:
    +    #points, followed by ids, one per line
    +
    +    unordered
    +*/
    +void qh_printextremes_d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices;
    +  vertexT *vertex, **vertexp;
    +  boolT upperseen, lowerseen;
    +  facetT *neighbor, **neighborp;
    +  int numpoints=0;
    +
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  qh_vertexneighbors(qh);
    +  FOREACHvertex_(vertices) {
    +    upperseen= lowerseen= False;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay)
    +        upperseen= True;
    +      else
    +        lowerseen= True;
    +    }
    +    if (upperseen && lowerseen) {
    +      vertex->seen= True;
    +      numpoints++;
    +    }else
    +      vertex->seen= False;
    +  }
    +  qh_fprintf(qh, fp, 9091, "%d\n", numpoints);
    +  FOREACHvertex_(vertices) {
    +    if (vertex->seen)
    +      qh_fprintf(qh, fp, 9092, "%d\n", qh_pointid(qh, vertex->point));
    +  }
    +  qh_settempfree(qh, &vertices);
    +} /* printextremes_d */
    +
    +/*---------------------------------
    +
    +  qh_printfacet(qh, fp, facet )
    +    prints all fields of a facet to fp
    +
    +  notes:
    +    ridges printed in neighbor order
    +*/
    +void qh_printfacet(qhT *qh, FILE *fp, facetT *facet) {
    +
    +  qh_printfacetheader(qh, fp, facet);
    +  if (facet->ridges)
    +    qh_printfacetridges(qh, fp, facet);
    +} /* printfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet2geom(qh, fp, facet, color )
    +    print facet as part of a 2-d VECT for Geomview
    +
    +    notes:
    +      assume precise calculations in io_r.c with roundoff covered by qh_GEOMepsilon
    +      mindist is calculated within io_r.c.  maxoutside is calculated elsewhere
    +      so a DISTround error may have occurred.
    +*/
    +void qh_printfacet2geom(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  pointT *point0, *point1;
    +  realT mindist, innerplane, outerplane;
    +  int k;
    +
    +  qh_facet2point(qh, facet, &point0, &point1, &mindist);
    +  qh_geomplanes(qh, facet, &outerplane, &innerplane);
    +  if (qh->PRINTouter || (!qh->PRINTnoplanes && !qh->PRINTinner))
    +    qh_printfacet2geom_points(qh, fp, point0, point1, facet, outerplane, color);
    +  if (qh->PRINTinner || (!qh->PRINTnoplanes && !qh->PRINTouter &&
    +                outerplane - innerplane > 2 * qh->MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet2geom_points(qh, fp, point0, point1, facet, innerplane, color);
    +  }
    +  qh_memfree(qh, point1, qh->normal_size);
    +  qh_memfree(qh, point0, qh->normal_size);
    +} /* printfacet2geom */
    +
    +/*---------------------------------
    +
    +  qh_printfacet2geom_points(qh, fp, point1, point2, facet, offset, color )
    +    prints a 2-d facet as a VECT with 2 points at some offset.
    +    The points are on the facet's plane.
    +*/
    +void qh_printfacet2geom_points(qhT *qh, FILE *fp, pointT *point1, pointT *point2,
    +                               facetT *facet, realT offset, realT color[3]) {
    +  pointT *p1= point1, *p2= point2;
    +
    +  qh_fprintf(qh, fp, 9093, "VECT 1 2 1 2 1 # f%d\n", facet->id);
    +  if (offset != 0.0) {
    +    p1= qh_projectpoint(qh, p1, facet, -offset);
    +    p2= qh_projectpoint(qh, p2, facet, -offset);
    +  }
    +  qh_fprintf(qh, fp, 9094, "%8.4g %8.4g %8.4g\n%8.4g %8.4g %8.4g\n",
    +           p1[0], p1[1], 0.0, p2[0], p2[1], 0.0);
    +  if (offset != 0.0) {
    +    qh_memfree(qh, p1, qh->normal_size);
    +    qh_memfree(qh, p2, qh->normal_size);
    +  }
    +  qh_fprintf(qh, fp, 9095, "%8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +} /* printfacet2geom_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet2math(qh, fp, facet, format, notfirst )
    +    print 2-d Maple or Mathematica output for a facet
    +    may be non-simplicial
    +
    +  notes:
    +    use %16.8f since Mathematica 2.2 does not handle exponential format
    +    see qh_printfacet3math
    +*/
    +void qh_printfacet2math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst) {
    +  pointT *point0, *point1;
    +  realT mindist;
    +  const char *pointfmt;
    +
    +  qh_facet2point(qh, facet, &point0, &point1, &mindist);
    +  if (notfirst)
    +    qh_fprintf(qh, fp, 9096, ",");
    +  if (format == qh_PRINTmaple)
    +    pointfmt= "[[%16.8f, %16.8f], [%16.8f, %16.8f]]\n";
    +  else
    +    pointfmt= "Line[{{%16.8f, %16.8f}, {%16.8f, %16.8f}}]\n";
    +  qh_fprintf(qh, fp, 9097, pointfmt, point0[0], point0[1], point1[0], point1[1]);
    +  qh_memfree(qh, point1, qh->normal_size);
    +  qh_memfree(qh, point0, qh->normal_size);
    +} /* printfacet2math */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_nonsimplicial(qh, fp, facet, color )
    +    print Geomview OFF for a 3-d nonsimplicial facet.
    +    if DOintersections, prints ridges to unvisited neighbors(qh->visit_id)
    +
    +  notes
    +    uses facet->visitid for intersections and ridges
    +*/
    +void qh_printfacet3geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  ridgeT *ridge, **ridgep;
    +  setT *projectedpoints, *vertices;
    +  vertexT *vertex, **vertexp, *vertexA, *vertexB;
    +  pointT *projpt, *point, **pointp;
    +  facetT *neighbor;
    +  realT dist, outerplane, innerplane;
    +  int cntvertices, k;
    +  realT black[3]={0, 0, 0}, green[3]={0, 1, 0};
    +
    +  qh_geomplanes(qh, facet, &outerplane, &innerplane);
    +  vertices= qh_facet3vertex(qh, facet); /* oriented */
    +  cntvertices= qh_setsize(qh, vertices);
    +  projectedpoints= qh_settemp(qh, cntvertices);
    +  FOREACHvertex_(vertices) {
    +    zinc_(Zdistio);
    +    qh_distplane(qh, vertex->point, facet, &dist);
    +    projpt= qh_projectpoint(qh, vertex->point, facet, dist);
    +    qh_setappend(qh, &projectedpoints, projpt);
    +  }
    +  if (qh->PRINTouter || (!qh->PRINTnoplanes && !qh->PRINTinner))
    +    qh_printfacet3geom_points(qh, fp, projectedpoints, facet, outerplane, color);
    +  if (qh->PRINTinner || (!qh->PRINTnoplanes && !qh->PRINTouter &&
    +                outerplane - innerplane > 2 * qh->MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet3geom_points(qh, fp, projectedpoints, facet, innerplane, color);
    +  }
    +  FOREACHpoint_(projectedpoints)
    +    qh_memfree(qh, point, qh->normal_size);
    +  qh_settempfree(qh, &projectedpoints);
    +  qh_settempfree(qh, &vertices);
    +  if ((qh->DOintersections || qh->PRINTridges)
    +  && (!facet->visible || !qh->NEWfacets)) {
    +    facet->visitid= qh->visit_id;
    +    FOREACHridge_(facet->ridges) {
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->visitid != qh->visit_id) {
    +        if (qh->DOintersections)
    +          qh_printhyperplaneintersection(qh, fp, facet, neighbor, ridge->vertices, black);
    +        if (qh->PRINTridges) {
    +          vertexA= SETfirstt_(ridge->vertices, vertexT);
    +          vertexB= SETsecondt_(ridge->vertices, vertexT);
    +          qh_printline3geom(qh, fp, vertexA->point, vertexB->point, green);
    +        }
    +      }
    +    }
    +  }
    +} /* printfacet3geom_nonsimplicial */
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_points(qh, fp, points, facet, offset )
    +    prints a 3-d facet as OFF Geomview object.
    +    offset is relative to the facet's hyperplane
    +    Facet is determined as a list of points
    +*/
    +void qh_printfacet3geom_points(qhT *qh, FILE *fp, setT *points, facetT *facet, realT offset, realT color[3]) {
    +  int k, n= qh_setsize(qh, points), i;
    +  pointT *point, **pointp;
    +  setT *printpoints;
    +
    +  qh_fprintf(qh, fp, 9098, "{ OFF %d 1 1 # f%d\n", n, facet->id);
    +  if (offset != 0.0) {
    +    printpoints= qh_settemp(qh, n);
    +    FOREACHpoint_(points)
    +      qh_setappend(qh, &printpoints, qh_projectpoint(qh, point, facet, -offset));
    +  }else
    +    printpoints= points;
    +  FOREACHpoint_(printpoints) {
    +    for (k=0; k < qh->hull_dim; k++) {
    +      if (k == qh->DROPdim)
    +        qh_fprintf(qh, fp, 9099, "0 ");
    +      else
    +        qh_fprintf(qh, fp, 9100, "%8.4g ", point[k]);
    +    }
    +    if (printpoints != points)
    +      qh_memfree(qh, point, qh->normal_size);
    +    qh_fprintf(qh, fp, 9101, "\n");
    +  }
    +  if (printpoints != points)
    +    qh_settempfree(qh, &printpoints);
    +  qh_fprintf(qh, fp, 9102, "%d ", n);
    +  for (i=0; i < n; i++)
    +    qh_fprintf(qh, fp, 9103, "%d ", i);
    +  qh_fprintf(qh, fp, 9104, "%8.4g %8.4g %8.4g 1.0 }\n", color[0], color[1], color[2]);
    +} /* printfacet3geom_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_simplicial(qh, )
    +    print Geomview OFF for a 3-d simplicial facet.
    +
    +  notes:
    +    may flip color
    +    uses facet->visitid for intersections and ridges
    +
    +    assume precise calculations in io_r.c with roundoff covered by qh_GEOMepsilon
    +    innerplane may be off by qh->DISTround.  Maxoutside is calculated elsewhere
    +    so a DISTround error may have occurred.
    +*/
    +void qh_printfacet3geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  setT *points, *vertices;
    +  vertexT *vertex, **vertexp, *vertexA, *vertexB;
    +  facetT *neighbor, **neighborp;
    +  realT outerplane, innerplane;
    +  realT black[3]={0, 0, 0}, green[3]={0, 1, 0};
    +  int k;
    +
    +  qh_geomplanes(qh, facet, &outerplane, &innerplane);
    +  vertices= qh_facet3vertex(qh, facet);
    +  points= qh_settemp(qh, qh->TEMPsize);
    +  FOREACHvertex_(vertices)
    +    qh_setappend(qh, &points, vertex->point);
    +  if (qh->PRINTouter || (!qh->PRINTnoplanes && !qh->PRINTinner))
    +    qh_printfacet3geom_points(qh, fp, points, facet, outerplane, color);
    +  if (qh->PRINTinner || (!qh->PRINTnoplanes && !qh->PRINTouter &&
    +              outerplane - innerplane > 2 * qh->MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet3geom_points(qh, fp, points, facet, innerplane, color);
    +  }
    +  qh_settempfree(qh, &points);
    +  qh_settempfree(qh, &vertices);
    +  if ((qh->DOintersections || qh->PRINTridges)
    +  && (!facet->visible || !qh->NEWfacets)) {
    +    facet->visitid= qh->visit_id;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh->visit_id) {
    +        vertices= qh_setnew_delnthsorted(qh, facet->vertices, qh->hull_dim,
    +                          SETindex_(facet->neighbors, neighbor), 0);
    +        if (qh->DOintersections)
    +           qh_printhyperplaneintersection(qh, fp, facet, neighbor, vertices, black);
    +        if (qh->PRINTridges) {
    +          vertexA= SETfirstt_(vertices, vertexT);
    +          vertexB= SETsecondt_(vertices, vertexT);
    +          qh_printline3geom(qh, fp, vertexA->point, vertexB->point, green);
    +        }
    +        qh_setfree(qh, &vertices);
    +      }
    +    }
    +  }
    +} /* printfacet3geom_simplicial */
    +
    +/*---------------------------------
    +
    +  qh_printfacet3math(qh, fp, facet, notfirst )
    +    print 3-d Maple or Mathematica output for a facet
    +
    +  notes:
    +    may be non-simplicial
    +    use %16.8f since Mathematica 2.2 does not handle exponential format
    +    see qh_printfacet2math
    +*/
    +void qh_printfacet3math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst) {
    +  vertexT *vertex, **vertexp;
    +  setT *points, *vertices;
    +  pointT *point, **pointp;
    +  boolT firstpoint= True;
    +  realT dist;
    +  const char *pointfmt, *endfmt;
    +
    +  if (notfirst)
    +    qh_fprintf(qh, fp, 9105, ",\n");
    +  vertices= qh_facet3vertex(qh, facet);
    +  points= qh_settemp(qh, qh_setsize(qh, vertices));
    +  FOREACHvertex_(vertices) {
    +    zinc_(Zdistio);
    +    qh_distplane(qh, vertex->point, facet, &dist);
    +    point= qh_projectpoint(qh, vertex->point, facet, dist);
    +    qh_setappend(qh, &points, point);
    +  }
    +  if (format == qh_PRINTmaple) {
    +    qh_fprintf(qh, fp, 9106, "[");
    +    pointfmt= "[%16.8f, %16.8f, %16.8f]";
    +    endfmt= "]";
    +  }else {
    +    qh_fprintf(qh, fp, 9107, "Polygon[{");
    +    pointfmt= "{%16.8f, %16.8f, %16.8f}";
    +    endfmt= "}]";
    +  }
    +  FOREACHpoint_(points) {
    +    if (firstpoint)
    +      firstpoint= False;
    +    else
    +      qh_fprintf(qh, fp, 9108, ",\n");
    +    qh_fprintf(qh, fp, 9109, pointfmt, point[0], point[1], point[2]);
    +  }
    +  FOREACHpoint_(points)
    +    qh_memfree(qh, point, qh->normal_size);
    +  qh_settempfree(qh, &points);
    +  qh_settempfree(qh, &vertices);
    +  qh_fprintf(qh, fp, 9110, "%s", endfmt);
    +} /* printfacet3math */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3vertex(qh, fp, facet, format )
    +    print vertices in a 3-d facet as point ids
    +
    +  notes:
    +    prints number of vertices first if format == qh_PRINToff
    +    the facet may be non-simplicial
    +*/
    +void qh_printfacet3vertex(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +
    +  vertices= qh_facet3vertex(qh, facet);
    +  if (format == qh_PRINToff)
    +    qh_fprintf(qh, fp, 9111, "%d ", qh_setsize(qh, vertices));
    +  FOREACHvertex_(vertices)
    +    qh_fprintf(qh, fp, 9112, "%d ", qh_pointid(qh, vertex->point));
    +  qh_fprintf(qh, fp, 9113, "\n");
    +  qh_settempfree(qh, &vertices);
    +} /* printfacet3vertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet4geom_nonsimplicial(qh, )
    +    print Geomview 4OFF file for a 4d nonsimplicial facet
    +    prints all ridges to unvisited neighbors (qh.visit_id)
    +    if qh.DROPdim
    +      prints in OFF format
    +
    +  notes:
    +    must agree with printend4geom()
    +*/
    +void qh_printfacet4geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  facetT *neighbor;
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +  pointT *point;
    +  int k;
    +  realT dist;
    +
    +  facet->visitid= qh->visit_id;
    +  if (qh->PRINTnoplanes || (facet->visible && qh->NEWfacets))
    +    return;
    +  FOREACHridge_(facet->ridges) {
    +    neighbor= otherfacet_(ridge, facet);
    +    if (neighbor->visitid == qh->visit_id)
    +      continue;
    +    if (qh->PRINTtransparent && !neighbor->good)
    +      continue;
    +    if (qh->DOintersections)
    +      qh_printhyperplaneintersection(qh, fp, facet, neighbor, ridge->vertices, color);
    +    else {
    +      if (qh->DROPdim >= 0)
    +        qh_fprintf(qh, fp, 9114, "OFF 3 1 1 # f%d\n", facet->id);
    +      else {
    +        qh->printoutvar++;
    +        qh_fprintf(qh, fp, 9115, "# r%d between f%d f%d\n", ridge->id, facet->id, neighbor->id);
    +      }
    +      FOREACHvertex_(ridge->vertices) {
    +        zinc_(Zdistio);
    +        qh_distplane(qh, vertex->point,facet, &dist);
    +        point=qh_projectpoint(qh, vertex->point,facet, dist);
    +        for (k=0; k < qh->hull_dim; k++) {
    +          if (k != qh->DROPdim)
    +            qh_fprintf(qh, fp, 9116, "%8.4g ", point[k]);
    +        }
    +        qh_fprintf(qh, fp, 9117, "\n");
    +        qh_memfree(qh, point, qh->normal_size);
    +      }
    +      if (qh->DROPdim >= 0)
    +        qh_fprintf(qh, fp, 9118, "3 0 1 2 %8.4g %8.4g %8.4g\n", color[0], color[1], color[2]);
    +    }
    +  }
    +} /* printfacet4geom_nonsimplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet4geom_simplicial(qh, fp, facet, color )
    +    print Geomview 4OFF file for a 4d simplicial facet
    +    prints triangles for unvisited neighbors (qh.visit_id)
    +
    +  notes:
    +    must agree with printend4geom()
    +*/
    +void qh_printfacet4geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  setT *vertices;
    +  facetT *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  int k;
    +
    +  facet->visitid= qh->visit_id;
    +  if (qh->PRINTnoplanes || (facet->visible && qh->NEWfacets))
    +    return;
    +  FOREACHneighbor_(facet) {
    +    if (neighbor->visitid == qh->visit_id)
    +      continue;
    +    if (qh->PRINTtransparent && !neighbor->good)
    +      continue;
    +    vertices= qh_setnew_delnthsorted(qh, facet->vertices, qh->hull_dim,
    +                          SETindex_(facet->neighbors, neighbor), 0);
    +    if (qh->DOintersections)
    +      qh_printhyperplaneintersection(qh, fp, facet, neighbor, vertices, color);
    +    else {
    +      if (qh->DROPdim >= 0)
    +        qh_fprintf(qh, fp, 9119, "OFF 3 1 1 # ridge between f%d f%d\n",
    +                facet->id, neighbor->id);
    +      else {
    +        qh->printoutvar++;
    +        qh_fprintf(qh, fp, 9120, "# ridge between f%d f%d\n", facet->id, neighbor->id);
    +      }
    +      FOREACHvertex_(vertices) {
    +        for (k=0; k < qh->hull_dim; k++) {
    +          if (k != qh->DROPdim)
    +            qh_fprintf(qh, fp, 9121, "%8.4g ", vertex->point[k]);
    +        }
    +        qh_fprintf(qh, fp, 9122, "\n");
    +      }
    +      if (qh->DROPdim >= 0)
    +        qh_fprintf(qh, fp, 9123, "3 0 1 2 %8.4g %8.4g %8.4g\n", color[0], color[1], color[2]);
    +    }
    +    qh_setfree(qh, &vertices);
    +  }
    +} /* printfacet4geom_simplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetNvertex_nonsimplicial(qh, fp, facet, id, format )
    +    print vertices for an N-d non-simplicial facet
    +    triangulates each ridge to the id
    +*/
    +void qh_printfacetNvertex_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, int id, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (facet->visible && qh->NEWfacets)
    +    return;
    +  FOREACHridge_(facet->ridges) {
    +    if (format == qh_PRINTtriangles)
    +      qh_fprintf(qh, fp, 9124, "%d ", qh->hull_dim);
    +    qh_fprintf(qh, fp, 9125, "%d ", id);
    +    if ((ridge->top == facet) ^ qh_ORIENTclock) {
    +      FOREACHvertex_(ridge->vertices)
    +        qh_fprintf(qh, fp, 9126, "%d ", qh_pointid(qh, vertex->point));
    +    }else {
    +      FOREACHvertexreverse12_(ridge->vertices)
    +        qh_fprintf(qh, fp, 9127, "%d ", qh_pointid(qh, vertex->point));
    +    }
    +    qh_fprintf(qh, fp, 9128, "\n");
    +  }
    +} /* printfacetNvertex_nonsimplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetNvertex_simplicial(qh, fp, facet, format )
    +    print vertices for an N-d simplicial facet
    +    prints vertices for non-simplicial facets
    +      2-d facets (orientation preserved by qh_mergefacet2d)
    +      PRINToff ('o') for 4-d and higher
    +*/
    +void qh_printfacetNvertex_simplicial(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +
    +  if (format == qh_PRINToff || format == qh_PRINTtriangles)
    +    qh_fprintf(qh, fp, 9129, "%d ", qh_setsize(qh, facet->vertices));
    +  if ((facet->toporient ^ qh_ORIENTclock)
    +  || (qh->hull_dim > 2 && !facet->simplicial)) {
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(qh, fp, 9130, "%d ", qh_pointid(qh, vertex->point));
    +  }else {
    +    FOREACHvertexreverse12_(facet->vertices)
    +      qh_fprintf(qh, fp, 9131, "%d ", qh_pointid(qh, vertex->point));
    +  }
    +  qh_fprintf(qh, fp, 9132, "\n");
    +} /* printfacetNvertex_simplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetheader(qh, fp, facet )
    +    prints header fields of a facet to fp
    +
    +  notes:
    +    for 'f' output and debugging
    +    Same as QhullFacet::printHeader()
    +*/
    +void qh_printfacetheader(qhT *qh, FILE *fp, facetT *facet) {
    +  pointT *point, **pointp, *furthest;
    +  facetT *neighbor, **neighborp;
    +  realT dist;
    +
    +  if (facet == qh_MERGEridge) {
    +    qh_fprintf(qh, fp, 9133, " MERGEridge\n");
    +    return;
    +  }else if (facet == qh_DUPLICATEridge) {
    +    qh_fprintf(qh, fp, 9134, " DUPLICATEridge\n");
    +    return;
    +  }else if (!facet) {
    +    qh_fprintf(qh, fp, 9135, " NULLfacet\n");
    +    return;
    +  }
    +  qh->old_randomdist= qh->RANDOMdist;
    +  qh->RANDOMdist= False;
    +  qh_fprintf(qh, fp, 9136, "- f%d\n", facet->id);
    +  qh_fprintf(qh, fp, 9137, "    - flags:");
    +  if (facet->toporient)
    +    qh_fprintf(qh, fp, 9138, " top");
    +  else
    +    qh_fprintf(qh, fp, 9139, " bottom");
    +  if (facet->simplicial)
    +    qh_fprintf(qh, fp, 9140, " simplicial");
    +  if (facet->tricoplanar)
    +    qh_fprintf(qh, fp, 9141, " tricoplanar");
    +  if (facet->upperdelaunay)
    +    qh_fprintf(qh, fp, 9142, " upperDelaunay");
    +  if (facet->visible)
    +    qh_fprintf(qh, fp, 9143, " visible");
    +  if (facet->newfacet)
    +    qh_fprintf(qh, fp, 9144, " new");
    +  if (facet->tested)
    +    qh_fprintf(qh, fp, 9145, " tested");
    +  if (!facet->good)
    +    qh_fprintf(qh, fp, 9146, " notG");
    +  if (facet->seen)
    +    qh_fprintf(qh, fp, 9147, " seen");
    +  if (facet->coplanar)
    +    qh_fprintf(qh, fp, 9148, " coplanar");
    +  if (facet->mergehorizon)
    +    qh_fprintf(qh, fp, 9149, " mergehorizon");
    +  if (facet->keepcentrum)
    +    qh_fprintf(qh, fp, 9150, " keepcentrum");
    +  if (facet->dupridge)
    +    qh_fprintf(qh, fp, 9151, " dupridge");
    +  if (facet->mergeridge && !facet->mergeridge2)
    +    qh_fprintf(qh, fp, 9152, " mergeridge1");
    +  if (facet->mergeridge2)
    +    qh_fprintf(qh, fp, 9153, " mergeridge2");
    +  if (facet->newmerge)
    +    qh_fprintf(qh, fp, 9154, " newmerge");
    +  if (facet->flipped)
    +    qh_fprintf(qh, fp, 9155, " flipped");
    +  if (facet->notfurthest)
    +    qh_fprintf(qh, fp, 9156, " notfurthest");
    +  if (facet->degenerate)
    +    qh_fprintf(qh, fp, 9157, " degenerate");
    +  if (facet->redundant)
    +    qh_fprintf(qh, fp, 9158, " redundant");
    +  qh_fprintf(qh, fp, 9159, "\n");
    +  if (facet->isarea)
    +    qh_fprintf(qh, fp, 9160, "    - area: %2.2g\n", facet->f.area);
    +  else if (qh->NEWfacets && facet->visible && facet->f.replace)
    +    qh_fprintf(qh, fp, 9161, "    - replacement: f%d\n", facet->f.replace->id);
    +  else if (facet->newfacet) {
    +    if (facet->f.samecycle && facet->f.samecycle != facet)
    +      qh_fprintf(qh, fp, 9162, "    - shares same visible/horizon as f%d\n", facet->f.samecycle->id);
    +  }else if (facet->tricoplanar /* !isarea */) {
    +    if (facet->f.triowner)
    +      qh_fprintf(qh, fp, 9163, "    - owner of normal & centrum is facet f%d\n", facet->f.triowner->id);
    +  }else if (facet->f.newcycle)
    +    qh_fprintf(qh, fp, 9164, "    - was horizon to f%d\n", facet->f.newcycle->id);
    +  if (facet->nummerge)
    +    qh_fprintf(qh, fp, 9165, "    - merges: %d\n", facet->nummerge);
    +  qh_printpointid(qh, fp, "    - normal: ", qh->hull_dim, facet->normal, qh_IDunknown);
    +  qh_fprintf(qh, fp, 9166, "    - offset: %10.7g\n", facet->offset);
    +  if (qh->CENTERtype == qh_ASvoronoi || facet->center)
    +    qh_printcenter(qh, fp, qh_PRINTfacets, "    - center: ", facet);
    +#if qh_MAXoutside
    +  if (facet->maxoutside > qh->DISTround)
    +    qh_fprintf(qh, fp, 9167, "    - maxoutside: %10.7g\n", facet->maxoutside);
    +#endif
    +  if (!SETempty_(facet->outsideset)) {
    +    furthest= (pointT*)qh_setlast(facet->outsideset);
    +    if (qh_setsize(qh, facet->outsideset) < 6) {
    +      qh_fprintf(qh, fp, 9168, "    - outside set(furthest p%d):\n", qh_pointid(qh, furthest));
    +      FOREACHpoint_(facet->outsideset)
    +        qh_printpoint(qh, fp, "     ", point);
    +    }else if (qh_setsize(qh, facet->outsideset) < 21) {
    +      qh_printpoints(qh, fp, "    - outside set:", facet->outsideset);
    +    }else {
    +      qh_fprintf(qh, fp, 9169, "    - outside set:  %d points.", qh_setsize(qh, facet->outsideset));
    +      qh_printpoint(qh, fp, "  Furthest", furthest);
    +    }
    +#if !qh_COMPUTEfurthest
    +    qh_fprintf(qh, fp, 9170, "    - furthest distance= %2.2g\n", facet->furthestdist);
    +#endif
    +  }
    +  if (!SETempty_(facet->coplanarset)) {
    +    furthest= (pointT*)qh_setlast(facet->coplanarset);
    +    if (qh_setsize(qh, facet->coplanarset) < 6) {
    +      qh_fprintf(qh, fp, 9171, "    - coplanar set(furthest p%d):\n", qh_pointid(qh, furthest));
    +      FOREACHpoint_(facet->coplanarset)
    +        qh_printpoint(qh, fp, "     ", point);
    +    }else if (qh_setsize(qh, facet->coplanarset) < 21) {
    +      qh_printpoints(qh, fp, "    - coplanar set:", facet->coplanarset);
    +    }else {
    +      qh_fprintf(qh, fp, 9172, "    - coplanar set:  %d points.", qh_setsize(qh, facet->coplanarset));
    +      qh_printpoint(qh, fp, "  Furthest", furthest);
    +    }
    +    zinc_(Zdistio);
    +    qh_distplane(qh, furthest, facet, &dist);
    +    qh_fprintf(qh, fp, 9173, "      furthest distance= %2.2g\n", dist);
    +  }
    +  qh_printvertices(qh, fp, "    - vertices:", facet->vertices);
    +  qh_fprintf(qh, fp, 9174, "    - neighboring facets:");
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      qh_fprintf(qh, fp, 9175, " MERGE");
    +    else if (neighbor == qh_DUPLICATEridge)
    +      qh_fprintf(qh, fp, 9176, " DUP");
    +    else
    +      qh_fprintf(qh, fp, 9177, " f%d", neighbor->id);
    +  }
    +  qh_fprintf(qh, fp, 9178, "\n");
    +  qh->RANDOMdist= qh->old_randomdist;
    +} /* printfacetheader */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetridges(qh, fp, facet )
    +    prints ridges of a facet to fp
    +
    +  notes:
    +    ridges printed in neighbor order
    +    assumes the ridges exist
    +    for 'f' output
    +    same as QhullFacet::printRidges
    +*/
    +void qh_printfacetridges(qhT *qh, FILE *fp, facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int numridges= 0;
    +
    +
    +  if (facet->visible && qh->NEWfacets) {
    +    qh_fprintf(qh, fp, 9179, "    - ridges(ids may be garbage):");
    +    FOREACHridge_(facet->ridges)
    +      qh_fprintf(qh, fp, 9180, " r%d", ridge->id);
    +    qh_fprintf(qh, fp, 9181, "\n");
    +  }else {
    +    qh_fprintf(qh, fp, 9182, "    - ridges:\n");
    +    FOREACHridge_(facet->ridges)
    +      ridge->seen= False;
    +    if (qh->hull_dim == 3) {
    +      ridge= SETfirstt_(facet->ridges, ridgeT);
    +      while (ridge && !ridge->seen) {
    +        ridge->seen= True;
    +        qh_printridge(qh, fp, ridge);
    +        numridges++;
    +        ridge= qh_nextridge3d(ridge, facet, NULL);
    +        }
    +    }else {
    +      FOREACHneighbor_(facet) {
    +        FOREACHridge_(facet->ridges) {
    +          if (otherfacet_(ridge,facet) == neighbor) {
    +            ridge->seen= True;
    +            qh_printridge(qh, fp, ridge);
    +            numridges++;
    +          }
    +        }
    +      }
    +    }
    +    if (numridges != qh_setsize(qh, facet->ridges)) {
    +      qh_fprintf(qh, fp, 9183, "     - all ridges:");
    +      FOREACHridge_(facet->ridges)
    +        qh_fprintf(qh, fp, 9184, " r%d", ridge->id);
    +        qh_fprintf(qh, fp, 9185, "\n");
    +    }
    +    FOREACHridge_(facet->ridges) {
    +      if (!ridge->seen)
    +        qh_printridge(qh, fp, ridge);
    +    }
    +  }
    +} /* printfacetridges */
    +
    +/*---------------------------------
    +
    +  qh_printfacets(qh, fp, format, facetlist, facets, printall )
    +    prints facetlist and/or facet set in output format
    +
    +  notes:
    +    also used for specialized formats ('FO' and summary)
    +    turns off 'Rn' option since want actual numbers
    +*/
    +void qh_printfacets(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numcoplanars, numtricoplanars;
    +  facetT *facet, **facetp;
    +  setT *vertices;
    +  coordT *center;
    +  realT outerplane, innerplane;
    +
    +  qh->old_randomdist= qh->RANDOMdist;
    +  qh->RANDOMdist= False;
    +  if (qh->CDDoutput && (format == qh_PRINTcentrums || format == qh_PRINTpointintersect || format == qh_PRINToff))
    +    qh_fprintf(qh, qh->ferr, 7056, "qhull warning: CDD format is not available for centrums, halfspace\nintersections, and OFF file format.\n");
    +  if (format == qh_PRINTnone)
    +    ; /* print nothing */
    +  else if (format == qh_PRINTaverage) {
    +    vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +    center= qh_getcenter(qh, vertices);
    +    qh_fprintf(qh, fp, 9186, "%d 1\n", qh->hull_dim);
    +    qh_printpointid(qh, fp, NULL, qh->hull_dim, center, qh_IDunknown);
    +    qh_memfree(qh, center, qh->normal_size);
    +    qh_settempfree(qh, &vertices);
    +  }else if (format == qh_PRINTextremes) {
    +    if (qh->DELAUNAY)
    +      qh_printextremes_d(qh, fp, facetlist, facets, printall);
    +    else if (qh->hull_dim == 2)
    +      qh_printextremes_2d(qh, fp, facetlist, facets, printall);
    +    else
    +      qh_printextremes(qh, fp, facetlist, facets, printall);
    +  }else if (format == qh_PRINToptions)
    +    qh_fprintf(qh, fp, 9187, "Options selected for Qhull %s:\n%s\n", qh_version, qh->qhull_options);
    +  else if (format == qh_PRINTpoints && !qh->VORONOI)
    +    qh_printpoints_out(qh, fp, facetlist, facets, printall);
    +  else if (format == qh_PRINTqhull)
    +    qh_fprintf(qh, fp, 9188, "%s | %s\n", qh->rbox_command, qh->qhull_command);
    +  else if (format == qh_PRINTsize) {
    +    qh_fprintf(qh, fp, 9189, "0\n2 ");
    +    qh_fprintf(qh, fp, 9190, qh_REAL_1, qh->totarea);
    +    qh_fprintf(qh, fp, 9191, qh_REAL_1, qh->totvol);
    +    qh_fprintf(qh, fp, 9192, "\n");
    +  }else if (format == qh_PRINTsummary) {
    +    qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);
    +    vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +    qh_fprintf(qh, fp, 9193, "10 %d %d %d %d %d %d %d %d %d %d\n2 ", qh->hull_dim,
    +                qh->num_points + qh_setsize(qh, qh->other_points),
    +                qh->num_vertices, qh->num_facets - qh->num_visible,
    +                qh_setsize(qh, vertices), numfacets, numcoplanars,
    +                numfacets - numsimplicial, zzval_(Zdelvertextot),
    +                numtricoplanars);
    +    qh_settempfree(qh, &vertices);
    +    qh_outerinner(qh, NULL, &outerplane, &innerplane);
    +    qh_fprintf(qh, fp, 9194, qh_REAL_2n, outerplane, innerplane);
    +  }else if (format == qh_PRINTvneighbors)
    +    qh_printvneighbors(qh, fp, facetlist, facets, printall);
    +  else if (qh->VORONOI && format == qh_PRINToff)
    +    qh_printvoronoi(qh, fp, format, facetlist, facets, printall);
    +  else if (qh->VORONOI && format == qh_PRINTgeom) {
    +    qh_printbegin(qh, fp, format, facetlist, facets, printall);
    +    qh_printvoronoi(qh, fp, format, facetlist, facets, printall);
    +    qh_printend(qh, fp, format, facetlist, facets, printall);
    +  }else if (qh->VORONOI
    +  && (format == qh_PRINTvertices || format == qh_PRINTinner || format == qh_PRINTouter))
    +    qh_printvdiagram(qh, fp, format, facetlist, facets, printall);
    +  else {
    +    qh_printbegin(qh, fp, format, facetlist, facets, printall);
    +    FORALLfacet_(facetlist)
    +      qh_printafacet(qh, fp, format, facet, printall);
    +    FOREACHfacet_(facets)
    +      qh_printafacet(qh, fp, format, facet, printall);
    +    qh_printend(qh, fp, format, facetlist, facets, printall);
    +  }
    +  qh->RANDOMdist= qh->old_randomdist;
    +} /* printfacets */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhyperplaneintersection(qh, fp, facet1, facet2, vertices, color )
    +    print Geomview OFF or 4OFF for the intersection of two hyperplanes in 3-d or 4-d
    +*/
    +void qh_printhyperplaneintersection(qhT *qh, FILE *fp, facetT *facet1, facetT *facet2,
    +                   setT *vertices, realT color[3]) {
    +  realT costheta, denominator, dist1, dist2, s, t, mindenom, p[4];
    +  vertexT *vertex, **vertexp;
    +  int i, k;
    +  boolT nearzero1, nearzero2;
    +
    +  costheta= qh_getangle(qh, facet1->normal, facet2->normal);
    +  denominator= 1 - costheta * costheta;
    +  i= qh_setsize(qh, vertices);
    +  if (qh->hull_dim == 3)
    +    qh_fprintf(qh, fp, 9195, "VECT 1 %d 1 %d 1 ", i, i);
    +  else if (qh->hull_dim == 4 && qh->DROPdim >= 0)
    +    qh_fprintf(qh, fp, 9196, "OFF 3 1 1 ");
    +  else
    +    qh->printoutvar++;
    +  qh_fprintf(qh, fp, 9197, "# intersect f%d f%d\n", facet1->id, facet2->id);
    +  mindenom= 1 / (10.0 * qh->MAXabs_coord);
    +  FOREACHvertex_(vertices) {
    +    zadd_(Zdistio, 2);
    +    qh_distplane(qh, vertex->point, facet1, &dist1);
    +    qh_distplane(qh, vertex->point, facet2, &dist2);
    +    s= qh_divzero(-dist1 + costheta * dist2, denominator,mindenom,&nearzero1);
    +    t= qh_divzero(-dist2 + costheta * dist1, denominator,mindenom,&nearzero2);
    +    if (nearzero1 || nearzero2)
    +      s= t= 0.0;
    +    for (k=qh->hull_dim; k--; )
    +      p[k]= vertex->point[k] + facet1->normal[k] * s + facet2->normal[k] * t;
    +    if (qh->PRINTdim <= 3) {
    +      qh_projectdim3(qh, p, p);
    +      qh_fprintf(qh, fp, 9198, "%8.4g %8.4g %8.4g # ", p[0], p[1], p[2]);
    +    }else
    +      qh_fprintf(qh, fp, 9199, "%8.4g %8.4g %8.4g %8.4g # ", p[0], p[1], p[2], p[3]);
    +    if (nearzero1+nearzero2)
    +      qh_fprintf(qh, fp, 9200, "p%d(coplanar facets)\n", qh_pointid(qh, vertex->point));
    +    else
    +      qh_fprintf(qh, fp, 9201, "projected p%d\n", qh_pointid(qh, vertex->point));
    +  }
    +  if (qh->hull_dim == 3)
    +    qh_fprintf(qh, fp, 9202, "%8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +  else if (qh->hull_dim == 4 && qh->DROPdim >= 0)
    +    qh_fprintf(qh, fp, 9203, "3 0 1 2 %8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +} /* printhyperplaneintersection */
    +
    +/*---------------------------------
    +
    +  qh_printline3geom(qh, fp, pointA, pointB, color )
    +    prints a line as a VECT
    +    prints 0's for qh.DROPdim
    +
    +  notes:
    +    if pointA == pointB,
    +      it's a 1 point VECT
    +*/
    +void qh_printline3geom(qhT *qh, FILE *fp, pointT *pointA, pointT *pointB, realT color[3]) {
    +  int k;
    +  realT pA[4], pB[4];
    +
    +  qh_projectdim3(qh, pointA, pA);
    +  qh_projectdim3(qh, pointB, pB);
    +  if ((fabs(pA[0] - pB[0]) > 1e-3) ||
    +      (fabs(pA[1] - pB[1]) > 1e-3) ||
    +      (fabs(pA[2] - pB[2]) > 1e-3)) {
    +    qh_fprintf(qh, fp, 9204, "VECT 1 2 1 2 1\n");
    +    for (k=0; k < 3; k++)
    +       qh_fprintf(qh, fp, 9205, "%8.4g ", pB[k]);
    +    qh_fprintf(qh, fp, 9206, " # p%d\n", qh_pointid(qh, pointB));
    +  }else
    +    qh_fprintf(qh, fp, 9207, "VECT 1 1 1 1 1\n");
    +  for (k=0; k < 3; k++)
    +    qh_fprintf(qh, fp, 9208, "%8.4g ", pA[k]);
    +  qh_fprintf(qh, fp, 9209, " # p%d\n", qh_pointid(qh, pointA));
    +  qh_fprintf(qh, fp, 9210, "%8.4g %8.4g %8.4g 1\n", color[0], color[1], color[2]);
    +}
    +
    +/*---------------------------------
    +
    +  qh_printneighborhood(qh, fp, format, facetA, facetB, printall )
    +    print neighborhood of one or two facets
    +
    +  notes:
    +    calls qh_findgood_all()
    +    bumps qh.visit_id
    +*/
    +void qh_printneighborhood(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall) {
    +  facetT *neighbor, **neighborp, *facet;
    +  setT *facets;
    +
    +  if (format == qh_PRINTnone)
    +    return;
    +  qh_findgood_all(qh, qh->facet_list);
    +  if (facetA == facetB)
    +    facetB= NULL;
    +  facets= qh_settemp(qh, 2*(qh_setsize(qh, facetA->neighbors)+1));
    +  qh->visit_id++;
    +  for (facet= facetA; facet; facet= ((facet == facetA) ? facetB : NULL)) {
    +    if (facet->visitid != qh->visit_id) {
    +      facet->visitid= qh->visit_id;
    +      qh_setappend(qh, &facets, facet);
    +    }
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == qh->visit_id)
    +        continue;
    +      neighbor->visitid= qh->visit_id;
    +      if (printall || !qh_skipfacet(qh, neighbor))
    +        qh_setappend(qh, &facets, neighbor);
    +    }
    +  }
    +  qh_printfacets(qh, fp, format, NULL, facets, printall);
    +  qh_settempfree(qh, &facets);
    +} /* printneighborhood */
    +
    +/*---------------------------------
    +
    +  qh_printpoint(qh, fp, string, point )
    +  qh_printpointid(qh, fp, string, dim, point, id )
    +    prints the coordinates of a point
    +
    +  returns:
    +    if string is defined
    +      prints 'string p%d'.  Skips p%d if id=qh_IDunknown(-1) or qh_IDnone(-3)
    +
    +  notes:
    +    nop if point is NULL
    +    Same as QhullPoint's printPoint
    +*/
    +void qh_printpoint(qhT *qh, FILE *fp, const char *string, pointT *point) {
    +  int id= qh_pointid(qh, point);
    +
    +  qh_printpointid(qh, fp, string, qh->hull_dim, point, id);
    +} /* printpoint */
    +
    +void qh_printpointid(qhT *qh, FILE *fp, const char *string, int dim, pointT *point, int id) {
    +  int k;
    +  realT r; /*bug fix*/
    +
    +  if (!point)
    +    return;
    +  if (string) {
    +    qh_fprintf(qh, fp, 9211, "%s", string);
    +    if (id != qh_IDunknown && id != qh_IDnone)
    +      qh_fprintf(qh, fp, 9212, " p%d: ", id);
    +  }
    +  for (k=dim; k--; ) {
    +    r= *point++;
    +    if (string)
    +      qh_fprintf(qh, fp, 9213, " %8.4g", r);
    +    else
    +      qh_fprintf(qh, fp, 9214, qh_REAL_1, r);
    +  }
    +  qh_fprintf(qh, fp, 9215, "\n");
    +} /* printpointid */
    +
    +/*---------------------------------
    +
    +  qh_printpoint3(qh, fp, point )
    +    prints 2-d, 3-d, or 4-d point as Geomview 3-d coordinates
    +*/
    +void qh_printpoint3(qhT *qh, FILE *fp, pointT *point) {
    +  int k;
    +  realT p[4];
    +
    +  qh_projectdim3(qh, point, p);
    +  for (k=0; k < 3; k++)
    +    qh_fprintf(qh, fp, 9216, "%8.4g ", p[k]);
    +  qh_fprintf(qh, fp, 9217, " # p%d\n", qh_pointid(qh, point));
    +} /* printpoint3 */
    +
    +/*----------------------------------------
    +-printpoints- print pointids for a set of points starting at index
    +   see geom_r.c
    +*/
    +
    +/*---------------------------------
    +
    +  qh_printpoints_out(qh, fp, facetlist, facets, printall )
    +    prints vertices, coplanar/inside points, for facets by their point coordinates
    +    allows qh.CDDoutput
    +
    +  notes:
    +    same format as qhull input
    +    if no coplanar/interior points,
    +      same order as qh_printextremes
    +*/
    +void qh_printpoints_out(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  int allpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +  int numpoints=0, point_i, point_n;
    +  setT *vertices, *points;
    +  facetT *facet, **facetp;
    +  pointT *point, **pointp;
    +  vertexT *vertex, **vertexp;
    +  int id;
    +
    +  points= qh_settemp(qh, allpoints);
    +  qh_setzero(qh, points, 0, allpoints);
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  FOREACHvertex_(vertices) {
    +    id= qh_pointid(qh, vertex->point);
    +    if (id >= 0)
    +      SETelem_(points, id)= vertex->point;
    +  }
    +  if (qh->KEEPinside || qh->KEEPcoplanar || qh->KEEPnearinside) {
    +    FORALLfacet_(facetlist) {
    +      if (!printall && qh_skipfacet(qh, facet))
    +        continue;
    +      FOREACHpoint_(facet->coplanarset) {
    +        id= qh_pointid(qh, point);
    +        if (id >= 0)
    +          SETelem_(points, id)= point;
    +      }
    +    }
    +    FOREACHfacet_(facets) {
    +      if (!printall && qh_skipfacet(qh, facet))
    +        continue;
    +      FOREACHpoint_(facet->coplanarset) {
    +        id= qh_pointid(qh, point);
    +        if (id >= 0)
    +          SETelem_(points, id)= point;
    +      }
    +    }
    +  }
    +  qh_settempfree(qh, &vertices);
    +  FOREACHpoint_i_(qh, points) {
    +    if (point)
    +      numpoints++;
    +  }
    +  if (qh->CDDoutput)
    +    qh_fprintf(qh, fp, 9218, "%s | %s\nbegin\n%d %d real\n", qh->rbox_command,
    +             qh->qhull_command, numpoints, qh->hull_dim + 1);
    +  else
    +    qh_fprintf(qh, fp, 9219, "%d\n%d\n", qh->hull_dim, numpoints);
    +  FOREACHpoint_i_(qh, points) {
    +    if (point) {
    +      if (qh->CDDoutput)
    +        qh_fprintf(qh, fp, 9220, "1 ");
    +      qh_printpoint(qh, fp, NULL, point);
    +    }
    +  }
    +  if (qh->CDDoutput)
    +    qh_fprintf(qh, fp, 9221, "end\n");
    +  qh_settempfree(qh, &points);
    +} /* printpoints_out */
    +
    +
    +/*---------------------------------
    +
    +  qh_printpointvect(qh, fp, point, normal, center, radius, color )
    +    prints a 2-d, 3-d, or 4-d point as 3-d VECT's relative to normal or to center point
    +*/
    +void qh_printpointvect(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius, realT color[3]) {
    +  realT diff[4], pointA[4];
    +  int k;
    +
    +  for (k=qh->hull_dim; k--; ) {
    +    if (center)
    +      diff[k]= point[k]-center[k];
    +    else if (normal)
    +      diff[k]= normal[k];
    +    else
    +      diff[k]= 0;
    +  }
    +  if (center)
    +    qh_normalize2(qh, diff, qh->hull_dim, True, NULL, NULL);
    +  for (k=qh->hull_dim; k--; )
    +    pointA[k]= point[k]+diff[k] * radius;
    +  qh_printline3geom(qh, fp, point, pointA, color);
    +} /* printpointvect */
    +
    +/*---------------------------------
    +
    +  qh_printpointvect2(qh, fp, point, normal, center, radius )
    +    prints a 2-d, 3-d, or 4-d point as 2 3-d VECT's for an imprecise point
    +*/
    +void qh_printpointvect2(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius) {
    +  realT red[3]={1, 0, 0}, yellow[3]={1, 1, 0};
    +
    +  qh_printpointvect(qh, fp, point, normal, center, radius, red);
    +  qh_printpointvect(qh, fp, point, normal, center, -radius, yellow);
    +} /* printpointvect2 */
    +
    +/*---------------------------------
    +
    +  qh_printridge(qh, fp, ridge )
    +    prints the information in a ridge
    +
    +  notes:
    +    for qh_printfacetridges()
    +    same as operator<< [QhullRidge.cpp]
    +*/
    +void qh_printridge(qhT *qh, FILE *fp, ridgeT *ridge) {
    +
    +  qh_fprintf(qh, fp, 9222, "     - r%d", ridge->id);
    +  if (ridge->tested)
    +    qh_fprintf(qh, fp, 9223, " tested");
    +  if (ridge->nonconvex)
    +    qh_fprintf(qh, fp, 9224, " nonconvex");
    +  qh_fprintf(qh, fp, 9225, "\n");
    +  qh_printvertices(qh, fp, "           vertices:", ridge->vertices);
    +  if (ridge->top && ridge->bottom)
    +    qh_fprintf(qh, fp, 9226, "           between f%d and f%d\n",
    +            ridge->top->id, ridge->bottom->id);
    +} /* printridge */
    +
    +/*---------------------------------
    +
    +  qh_printspheres(qh, fp, vertices, radius )
    +    prints 3-d vertices as OFF spheres
    +
    +  notes:
    +    inflated octahedron from Stuart Levy earth/mksphere2
    +*/
    +void qh_printspheres(qhT *qh, FILE *fp, setT *vertices, realT radius) {
    +  vertexT *vertex, **vertexp;
    +
    +  qh->printoutnum++;
    +  qh_fprintf(qh, fp, 9227, "{appearance {-edge -normal normscale 0} {\n\
    +INST geom {define vsphere OFF\n\
    +18 32 48\n\
    +\n\
    +0 0 1\n\
    +1 0 0\n\
    +0 1 0\n\
    +-1 0 0\n\
    +0 -1 0\n\
    +0 0 -1\n\
    +0.707107 0 0.707107\n\
    +0 -0.707107 0.707107\n\
    +0.707107 -0.707107 0\n\
    +-0.707107 0 0.707107\n\
    +-0.707107 -0.707107 0\n\
    +0 0.707107 0.707107\n\
    +-0.707107 0.707107 0\n\
    +0.707107 0.707107 0\n\
    +0.707107 0 -0.707107\n\
    +0 0.707107 -0.707107\n\
    +-0.707107 0 -0.707107\n\
    +0 -0.707107 -0.707107\n\
    +\n\
    +3 0 6 11\n\
    +3 0 7 6 \n\
    +3 0 9 7 \n\
    +3 0 11 9\n\
    +3 1 6 8 \n\
    +3 1 8 14\n\
    +3 1 13 6\n\
    +3 1 14 13\n\
    +3 2 11 13\n\
    +3 2 12 11\n\
    +3 2 13 15\n\
    +3 2 15 12\n\
    +3 3 9 12\n\
    +3 3 10 9\n\
    +3 3 12 16\n\
    +3 3 16 10\n\
    +3 4 7 10\n\
    +3 4 8 7\n\
    +3 4 10 17\n\
    +3 4 17 8\n\
    +3 5 14 17\n\
    +3 5 15 14\n\
    +3 5 16 15\n\
    +3 5 17 16\n\
    +3 6 13 11\n\
    +3 7 8 6\n\
    +3 9 10 7\n\
    +3 11 12 9\n\
    +3 14 8 17\n\
    +3 15 13 14\n\
    +3 16 12 15\n\
    +3 17 10 16\n} transforms { TLIST\n");
    +  FOREACHvertex_(vertices) {
    +    qh_fprintf(qh, fp, 9228, "%8.4g 0 0 0 # v%d\n 0 %8.4g 0 0\n0 0 %8.4g 0\n",
    +      radius, vertex->id, radius, radius);
    +    qh_printpoint3(qh, fp, vertex->point);
    +    qh_fprintf(qh, fp, 9229, "1\n");
    +  }
    +  qh_fprintf(qh, fp, 9230, "}}}\n");
    +} /* printspheres */
    +
    +
    +/*----------------------------------------------
    +-printsummary-
    +                see libqhull_r.c
    +*/
    +
    +/*---------------------------------
    +
    +  qh_printvdiagram(qh, fp, format, facetlist, facets, printall )
    +    print voronoi diagram
    +      # of pairs of input sites
    +      #indices site1 site2 vertex1 ...
    +
    +    sites indexed by input point id
    +      point 0 is the first input point
    +    vertices indexed by 'o' and 'p' order
    +      vertex 0 is the 'vertex-at-infinity'
    +      vertex 1 is the first Voronoi vertex
    +
    +  see:
    +    qh_printvoronoi()
    +    qh_eachvoronoi_all()
    +
    +  notes:
    +    if all facets are upperdelaunay,
    +      prints upper hull (furthest-site Voronoi diagram)
    +*/
    +void qh_printvdiagram(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices;
    +  int totcount, numcenters;
    +  boolT isLower;
    +  qh_RIDGE innerouter= qh_RIDGEall;
    +  printvridgeT printvridge= NULL;
    +
    +  if (format == qh_PRINTvertices) {
    +    innerouter= qh_RIDGEall;
    +    printvridge= qh_printvridge;
    +  }else if (format == qh_PRINTinner) {
    +    innerouter= qh_RIDGEinner;
    +    printvridge= qh_printvnorm;
    +  }else if (format == qh_PRINTouter) {
    +    innerouter= qh_RIDGEouter;
    +    printvridge= qh_printvnorm;
    +  }else {
    +    qh_fprintf(qh, qh->ferr, 6219, "Qhull internal error (qh_printvdiagram): unknown print format %d.\n", format);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  vertices= qh_markvoronoi(qh, facetlist, facets, printall, &isLower, &numcenters);
    +  totcount= qh_printvdiagram2(qh, NULL, NULL, vertices, innerouter, False);
    +  qh_fprintf(qh, fp, 9231, "%d\n", totcount);
    +  totcount= qh_printvdiagram2(qh, fp, printvridge, vertices, innerouter, True /* inorder*/);
    +  qh_settempfree(qh, &vertices);
    +#if 0  /* for testing qh_eachvoronoi_all */
    +  qh_fprintf(qh, fp, 9232, "\n");
    +  totcount= qh_eachvoronoi_all(qh, fp, printvridge, qh->UPPERdelaunay, innerouter, True /* inorder*/);
    +  qh_fprintf(qh, fp, 9233, "%d\n", totcount);
    +#endif
    +} /* printvdiagram */
    +
    +/*---------------------------------
    +
    +  qh_printvdiagram2(qh, fp, printvridge, vertices, innerouter, inorder )
    +    visit all pairs of input sites (vertices) for selected Voronoi vertices
    +    vertices may include NULLs
    +
    +  innerouter:
    +    qh_RIDGEall   print inner ridges(bounded) and outer ridges(unbounded)
    +    qh_RIDGEinner print only inner ridges
    +    qh_RIDGEouter print only outer ridges
    +
    +  inorder:
    +    print 3-d Voronoi vertices in order
    +
    +  assumes:
    +    qh_markvoronoi marked facet->visitid for Voronoi vertices
    +    all facet->seen= False
    +    all facet->seen2= True
    +
    +  returns:
    +    total number of Voronoi ridges
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers) for each ridge
    +      [see qh_eachvoronoi()]
    +
    +  see:
    +    qh_eachvoronoi_all()
    +*/
    +int qh_printvdiagram2(qhT *qh, FILE *fp, printvridgeT printvridge, setT *vertices, qh_RIDGE innerouter, boolT inorder) {
    +  int totcount= 0;
    +  int vertex_i, vertex_n;
    +  vertexT *vertex;
    +
    +  FORALLvertices
    +    vertex->seen= False;
    +  FOREACHvertex_i_(qh, vertices) {
    +    if (vertex) {
    +      if (qh->GOODvertex > 0 && qh_pointid(qh, vertex->point)+1 != qh->GOODvertex)
    +        continue;
    +      totcount += qh_eachvoronoi(qh, fp, printvridge, vertex, !qh_ALL, innerouter, inorder);
    +    }
    +  }
    +  return totcount;
    +} /* printvdiagram2 */
    +
    +/*---------------------------------
    +
    +  qh_printvertex(qh, fp, vertex )
    +    prints the information in a vertex
    +    Duplicated as operator<< [QhullVertex.cpp]
    +*/
    +void qh_printvertex(qhT *qh, FILE *fp, vertexT *vertex) {
    +  pointT *point;
    +  int k, count= 0;
    +  facetT *neighbor, **neighborp;
    +  realT r; /*bug fix*/
    +
    +  if (!vertex) {
    +    qh_fprintf(qh, fp, 9234, "  NULLvertex\n");
    +    return;
    +  }
    +  qh_fprintf(qh, fp, 9235, "- p%d(v%d):", qh_pointid(qh, vertex->point), vertex->id);
    +  point= vertex->point;
    +  if (point) {
    +    for (k=qh->hull_dim; k--; ) {
    +      r= *point++;
    +      qh_fprintf(qh, fp, 9236, " %5.2g", r);
    +    }
    +  }
    +  if (vertex->deleted)
    +    qh_fprintf(qh, fp, 9237, " deleted");
    +  if (vertex->delridge)
    +    qh_fprintf(qh, fp, 9238, " ridgedeleted");
    +  qh_fprintf(qh, fp, 9239, "\n");
    +  if (vertex->neighbors) {
    +    qh_fprintf(qh, fp, 9240, "  neighbors:");
    +    FOREACHneighbor_(vertex) {
    +      if (++count % 100 == 0)
    +        qh_fprintf(qh, fp, 9241, "\n     ");
    +      qh_fprintf(qh, fp, 9242, " f%d", neighbor->id);
    +    }
    +    qh_fprintf(qh, fp, 9243, "\n");
    +  }
    +} /* printvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_printvertexlist(qh, fp, string, facetlist, facets, printall )
    +    prints vertices used by a facetlist or facet set
    +    tests qh_skipfacet() if !printall
    +*/
    +void qh_printvertexlist(qhT *qh, FILE *fp, const char* string, facetT *facetlist,
    +                         setT *facets, boolT printall) {
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  qh_fprintf(qh, fp, 9244, "%s", string);
    +  FOREACHvertex_(vertices)
    +    qh_printvertex(qh, fp, vertex);
    +  qh_settempfree(qh, &vertices);
    +} /* printvertexlist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printvertices(qh, fp, string, vertices )
    +    prints vertices in a set
    +    duplicated as printVertexSet [QhullVertex.cpp]
    +*/
    +void qh_printvertices(qhT *qh, FILE *fp, const char* string, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  qh_fprintf(qh, fp, 9245, "%s", string);
    +  FOREACHvertex_(vertices)
    +    qh_fprintf(qh, fp, 9246, " p%d(v%d)", qh_pointid(qh, vertex->point), vertex->id);
    +  qh_fprintf(qh, fp, 9247, "\n");
    +} /* printvertices */
    +
    +/*---------------------------------
    +
    +  qh_printvneighbors(qh, fp, facetlist, facets, printall )
    +    print vertex neighbors of vertices in facetlist and facets ('FN')
    +
    +  notes:
    +    qh_countfacets clears facet->visitid for non-printed facets
    +
    +  design:
    +    collect facet count and related statistics
    +    if necessary, build neighbor sets for each vertex
    +    collect vertices in facetlist and facets
    +    build a point array for point->vertex and point->coplanar facet
    +    for each point
    +      list vertex neighbors or coplanar facet
    +*/
    +void qh_printvneighbors(qhT *qh, FILE *fp, facetT* facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numneighbors, numcoplanars, numtricoplanars;
    +  setT *vertices, *vertex_points, *coplanar_points;
    +  int numpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +  vertexT *vertex, **vertexp;
    +  int vertex_i, vertex_n;
    +  facetT *facet, **facetp, *neighbor, **neighborp;
    +  pointT *point, **pointp;
    +
    +  qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);  /* sets facet->visitid */
    +  qh_fprintf(qh, fp, 9248, "%d\n", numpoints);
    +  qh_vertexneighbors(qh);
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  vertex_points= qh_settemp(qh, numpoints);
    +  coplanar_points= qh_settemp(qh, numpoints);
    +  qh_setzero(qh, vertex_points, 0, numpoints);
    +  qh_setzero(qh, coplanar_points, 0, numpoints);
    +  FOREACHvertex_(vertices)
    +    qh_point_add(qh, vertex_points, vertex->point, vertex);
    +  FORALLfacet_(facetlist) {
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(qh, coplanar_points, point, facet);
    +  }
    +  FOREACHfacet_(facets) {
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(qh, coplanar_points, point, facet);
    +  }
    +  FOREACHvertex_i_(qh, vertex_points) {
    +    if (vertex) {
    +      numneighbors= qh_setsize(qh, vertex->neighbors);
    +      qh_fprintf(qh, fp, 9249, "%d", numneighbors);
    +      if (qh->hull_dim == 3)
    +        qh_order_vertexneighbors(qh, vertex);
    +      else if (qh->hull_dim >= 4)
    +        qsort(SETaddr_(vertex->neighbors, facetT), (size_t)numneighbors,
    +             sizeof(facetT *), qh_compare_facetvisit);
    +      FOREACHneighbor_(vertex)
    +        qh_fprintf(qh, fp, 9250, " %d",
    +                 neighbor->visitid ? neighbor->visitid - 1 : 0 - neighbor->id);
    +      qh_fprintf(qh, fp, 9251, "\n");
    +    }else if ((facet= SETelemt_(coplanar_points, vertex_i, facetT)))
    +      qh_fprintf(qh, fp, 9252, "1 %d\n",
    +                  facet->visitid ? facet->visitid - 1 : 0 - facet->id);
    +    else
    +      qh_fprintf(qh, fp, 9253, "0\n");
    +  }
    +  qh_settempfree(qh, &coplanar_points);
    +  qh_settempfree(qh, &vertex_points);
    +  qh_settempfree(qh, &vertices);
    +} /* printvneighbors */
    +
    +/*---------------------------------
    +
    +  qh_printvoronoi(qh, fp, format, facetlist, facets, printall )
    +    print voronoi diagram in 'o' or 'G' format
    +    for 'o' format
    +      prints voronoi centers for each facet and for infinity
    +      for each vertex, lists ids of printed facets or infinity
    +      assumes facetlist and facets are disjoint
    +    for 'G' format
    +      prints an OFF object
    +      adds a 0 coordinate to center
    +      prints infinity but does not list in vertices
    +
    +  see:
    +    qh_printvdiagram()
    +
    +  notes:
    +    if 'o',
    +      prints a line for each point except "at-infinity"
    +    if all facets are upperdelaunay,
    +      reverses lower and upper hull
    +*/
    +void qh_printvoronoi(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int k, numcenters, numvertices= 0, numneighbors, numinf, vid=1, vertex_i, vertex_n;
    +  facetT *facet, **facetp, *neighbor, **neighborp;
    +  setT *vertices;
    +  vertexT *vertex;
    +  boolT isLower;
    +  unsigned int numfacets= (unsigned int) qh->num_facets;
    +
    +  vertices= qh_markvoronoi(qh, facetlist, facets, printall, &isLower, &numcenters);
    +  FOREACHvertex_i_(qh, vertices) {
    +    if (vertex) {
    +      numvertices++;
    +      numneighbors = numinf = 0;
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visitid == 0)
    +          numinf= 1;
    +        else if (neighbor->visitid < numfacets)
    +          numneighbors++;
    +      }
    +      if (numinf && !numneighbors) {
    +        SETelem_(vertices, vertex_i)= NULL;
    +        numvertices--;
    +      }
    +    }
    +  }
    +  if (format == qh_PRINTgeom)
    +    qh_fprintf(qh, fp, 9254, "{appearance {+edge -face} OFF %d %d 1 # Voronoi centers and cells\n",
    +                numcenters, numvertices);
    +  else
    +    qh_fprintf(qh, fp, 9255, "%d\n%d %d 1\n", qh->hull_dim-1, numcenters, qh_setsize(qh, vertices));
    +  if (format == qh_PRINTgeom) {
    +    for (k=qh->hull_dim-1; k--; )
    +      qh_fprintf(qh, fp, 9256, qh_REAL_1, 0.0);
    +    qh_fprintf(qh, fp, 9257, " 0 # infinity not used\n");
    +  }else {
    +    for (k=qh->hull_dim-1; k--; )
    +      qh_fprintf(qh, fp, 9258, qh_REAL_1, qh_INFINITE);
    +    qh_fprintf(qh, fp, 9259, "\n");
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->visitid && facet->visitid < numfacets) {
    +      if (format == qh_PRINTgeom)
    +        qh_fprintf(qh, fp, 9260, "# %d f%d\n", vid++, facet->id);
    +      qh_printcenter(qh, fp, format, NULL, facet);
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (facet->visitid && facet->visitid < numfacets) {
    +      if (format == qh_PRINTgeom)
    +        qh_fprintf(qh, fp, 9261, "# %d f%d\n", vid++, facet->id);
    +      qh_printcenter(qh, fp, format, NULL, facet);
    +    }
    +  }
    +  FOREACHvertex_i_(qh, vertices) {
    +    numneighbors= 0;
    +    numinf=0;
    +    if (vertex) {
    +      if (qh->hull_dim == 3)
    +        qh_order_vertexneighbors(qh, vertex);
    +      else if (qh->hull_dim >= 4)
    +        qsort(SETaddr_(vertex->neighbors, facetT),
    +             (size_t)qh_setsize(qh, vertex->neighbors),
    +             sizeof(facetT *), qh_compare_facetvisit);
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visitid == 0)
    +          numinf= 1;
    +        else if (neighbor->visitid < numfacets)
    +          numneighbors++;
    +      }
    +    }
    +    if (format == qh_PRINTgeom) {
    +      if (vertex) {
    +        qh_fprintf(qh, fp, 9262, "%d", numneighbors);
    +        FOREACHneighbor_(vertex) {
    +          if (neighbor->visitid && neighbor->visitid < numfacets)
    +            qh_fprintf(qh, fp, 9263, " %d", neighbor->visitid);
    +        }
    +        qh_fprintf(qh, fp, 9264, " # p%d(v%d)\n", vertex_i, vertex->id);
    +      }else
    +        qh_fprintf(qh, fp, 9265, " # p%d is coplanar or isolated\n", vertex_i);
    +    }else {
    +      if (numinf)
    +        numneighbors++;
    +      qh_fprintf(qh, fp, 9266, "%d", numneighbors);
    +      if (vertex) {
    +        FOREACHneighbor_(vertex) {
    +          if (neighbor->visitid == 0) {
    +            if (numinf) {
    +              numinf= 0;
    +              qh_fprintf(qh, fp, 9267, " %d", neighbor->visitid);
    +            }
    +          }else if (neighbor->visitid < numfacets)
    +            qh_fprintf(qh, fp, 9268, " %d", neighbor->visitid);
    +        }
    +      }
    +      qh_fprintf(qh, fp, 9269, "\n");
    +    }
    +  }
    +  if (format == qh_PRINTgeom)
    +    qh_fprintf(qh, fp, 9270, "}\n");
    +  qh_settempfree(qh, &vertices);
    +} /* printvoronoi */
    +
    +/*---------------------------------
    +
    +  qh_printvnorm(qh, fp, vertex, vertexA, centers, unbounded )
    +    print one separating plane of the Voronoi diagram for a pair of input sites
    +    unbounded==True if centers includes vertex-at-infinity
    +
    +  assumes:
    +    qh_ASvoronoi and qh_vertexneighbors() already set
    +
    +  note:
    +    parameter unbounded is UNUSED by this callback
    +
    +  see:
    +    qh_printvdiagram()
    +    qh_eachvoronoi()
    +*/
    +void qh_printvnorm(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded) {
    +  pointT *normal;
    +  realT offset;
    +  int k;
    +  QHULL_UNUSED(unbounded);
    +
    +  normal= qh_detvnorm(qh, vertex, vertexA, centers, &offset);
    +  qh_fprintf(qh, fp, 9271, "%d %d %d ",
    +      2+qh->hull_dim, qh_pointid(qh, vertex->point), qh_pointid(qh, vertexA->point));
    +  for (k=0; k< qh->hull_dim-1; k++)
    +    qh_fprintf(qh, fp, 9272, qh_REAL_1, normal[k]);
    +  qh_fprintf(qh, fp, 9273, qh_REAL_1, offset);
    +  qh_fprintf(qh, fp, 9274, "\n");
    +} /* printvnorm */
    +
    +/*---------------------------------
    +
    +  qh_printvridge(qh, fp, vertex, vertexA, centers, unbounded )
    +    print one ridge of the Voronoi diagram for a pair of input sites
    +    unbounded==True if centers includes vertex-at-infinity
    +
    +  see:
    +    qh_printvdiagram()
    +
    +  notes:
    +    the user may use a different function
    +    parameter unbounded is UNUSED
    +*/
    +void qh_printvridge(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded) {
    +  facetT *facet, **facetp;
    +  QHULL_UNUSED(unbounded);
    +
    +  qh_fprintf(qh, fp, 9275, "%d %d %d", qh_setsize(qh, centers)+2,
    +       qh_pointid(qh, vertex->point), qh_pointid(qh, vertexA->point));
    +  FOREACHfacet_(centers)
    +    qh_fprintf(qh, fp, 9276, " %d", facet->visitid);
    +  qh_fprintf(qh, fp, 9277, "\n");
    +} /* printvridge */
    +
    +/*---------------------------------
    +
    +  qh_projectdim3(qh, source, destination )
    +    project 2-d 3-d or 4-d point to a 3-d point
    +    uses qh.DROPdim and qh.hull_dim
    +    source and destination may be the same
    +
    +  notes:
    +    allocate 4 elements to destination just in case
    +*/
    +void qh_projectdim3(qhT *qh, pointT *source, pointT *destination) {
    +  int i,k;
    +
    +  for (k=0, i=0; k < qh->hull_dim; k++) {
    +    if (qh->hull_dim == 4) {
    +      if (k != qh->DROPdim)
    +        destination[i++]= source[k];
    +    }else if (k == qh->DROPdim)
    +      destination[i++]= 0;
    +    else
    +      destination[i++]= source[k];
    +  }
    +  while (i < 3)
    +    destination[i++]= 0.0;
    +} /* projectdim3 */
    +
    +/*---------------------------------
    +
    +  qh_readfeasible(qh, dim, curline )
    +    read feasible point from current line and qh.fin
    +
    +  returns:
    +    number of lines read from qh.fin
    +    sets qh.feasible_point with malloc'd coordinates
    +
    +  notes:
    +    checks for qh.HALFspace
    +    assumes dim > 1
    +
    +  see:
    +    qh_setfeasible
    +*/
    +int qh_readfeasible(qhT *qh, int dim, const char *curline) {
    +  boolT isfirst= True;
    +  int linecount= 0, tokcount= 0;
    +  const char *s;
    +  char *t, firstline[qh_MAXfirst+1];
    +  coordT *coords, value;
    +
    +  if (!qh->HALFspace) {
    +    qh_fprintf(qh, qh->ferr, 6070, "qhull input error: feasible point(dim 1 coords) is only valid for halfspace intersection\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh->feasible_string)
    +    qh_fprintf(qh, qh->ferr, 7057, "qhull input warning: feasible point(dim 1 coords) overrides 'Hn,n,n' feasible point for halfspace intersection\n");
    +  if (!(qh->feasible_point= (coordT*)qh_malloc(dim* sizeof(coordT)))) {
    +    qh_fprintf(qh, qh->ferr, 6071, "qhull error: insufficient memory for feasible point\n");
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  coords= qh->feasible_point;
    +  while ((s= (isfirst ?  curline : fgets(firstline, qh_MAXfirst, qh->fin)))) {
    +    if (isfirst)
    +      isfirst= False;
    +    else
    +      linecount++;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      value= qh_strtod(s, &t);
    +      if (s == t)
    +        break;
    +      s= t;
    +      *(coords++)= value;
    +      if (++tokcount == dim) {
    +        while (isspace(*s))
    +          s++;
    +        qh_strtod(s, &t);
    +        if (s != t) {
    +          qh_fprintf(qh, qh->ferr, 6072, "qhull input error: coordinates for feasible point do not finish out the line: %s\n",
    +               s);
    +          qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +        }
    +        return linecount;
    +      }
    +    }
    +  }
    +  qh_fprintf(qh, qh->ferr, 6073, "qhull input error: only %d coordinates.  Could not read %d-d feasible point.\n",
    +           tokcount, dim);
    +  qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  return 0;
    +} /* readfeasible */
    +
    +/*---------------------------------
    +
    +  qh_readpoints(qh, numpoints, dimension, ismalloc )
    +    read points from qh.fin into qh.first_point, qh.num_points
    +    qh.fin is lines of coordinates, one per vertex, first line number of points
    +    if 'rbox D4',
    +      gives message
    +    if qh.ATinfinity,
    +      adds point-at-infinity for Delaunay triangulations
    +
    +  returns:
    +    number of points, array of point coordinates, dimension, ismalloc True
    +    if qh.DELAUNAY & !qh.PROJECTinput, projects points to paraboloid
    +        and clears qh.PROJECTdelaunay
    +    if qh.HALFspace, reads optional feasible point, reads halfspaces,
    +        converts to dual.
    +
    +  for feasible point in "cdd format" in 3-d:
    +    3 1
    +    coordinates
    +    comments
    +    begin
    +    n 4 real/integer
    +    ...
    +    end
    +
    +  notes:
    +    dimension will change in qh_initqhull_globals if qh.PROJECTinput
    +    uses malloc() since qh_mem not initialized
    +    FIXUP QH11012: qh_readpoints needs rewriting, too long
    +*/
    +coordT *qh_readpoints(qhT *qh, int *numpoints, int *dimension, boolT *ismalloc) {
    +  coordT *points, *coords, *infinity= NULL;
    +  realT paraboloid, maxboloid= -REALmax, value;
    +  realT *coordp= NULL, *offsetp= NULL, *normalp= NULL;
    +  char *s= 0, *t, firstline[qh_MAXfirst+1];
    +  int diminput=0, numinput=0, dimfeasible= 0, newnum, k, tempi;
    +  int firsttext=0, firstshort=0, firstlong=0, firstpoint=0;
    +  int tokcount= 0, linecount=0, maxcount, coordcount=0;
    +  boolT islong, isfirst= True, wasbegin= False;
    +  boolT isdelaunay= qh->DELAUNAY && !qh->PROJECTinput;
    +
    +  if (qh->CDDinput) {
    +    while ((s= fgets(firstline, qh_MAXfirst, qh->fin))) {
    +      linecount++;
    +      if (qh->HALFspace && linecount == 1 && isdigit(*s)) {
    +        dimfeasible= qh_strtol(s, &s);
    +        while (isspace(*s))
    +          s++;
    +        if (qh_strtol(s, &s) == 1)
    +          linecount += qh_readfeasible(qh, dimfeasible, s);
    +        else
    +          dimfeasible= 0;
    +      }else if (!memcmp(firstline, "begin", (size_t)5) || !memcmp(firstline, "BEGIN", (size_t)5))
    +        break;
    +      else if (!*qh->rbox_command)
    +        strncat(qh->rbox_command, s, sizeof(qh->rbox_command)-1);
    +    }
    +    if (!s) {
    +      qh_fprintf(qh, qh->ferr, 6074, "qhull input error: missing \"begin\" for cdd-formated input\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +  }
    +  while (!numinput && (s= fgets(firstline, qh_MAXfirst, qh->fin))) {
    +    linecount++;
    +    if (!memcmp(s, "begin", (size_t)5) || !memcmp(s, "BEGIN", (size_t)5))
    +      wasbegin= True;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      if (!*s)
    +        break;
    +      if (!isdigit(*s)) {
    +        if (!*qh->rbox_command) {
    +          strncat(qh->rbox_command, s, sizeof(qh->rbox_command)-1);
    +          firsttext= linecount;
    +        }
    +        break;
    +      }
    +      if (!diminput)
    +        diminput= qh_strtol(s, &s);
    +      else {
    +        numinput= qh_strtol(s, &s);
    +        if (numinput == 1 && diminput >= 2 && qh->HALFspace && !qh->CDDinput) {
    +          linecount += qh_readfeasible(qh, diminput, s); /* checks if ok */
    +          dimfeasible= diminput;
    +          diminput= numinput= 0;
    +        }else
    +          break;
    +      }
    +    }
    +  }
    +  if (!s) {
    +    qh_fprintf(qh, qh->ferr, 6075, "qhull input error: short input file.  Did not find dimension and number of points\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (diminput > numinput) {
    +    tempi= diminput;    /* exchange dim and n, e.g., for cdd input format */
    +    diminput= numinput;
    +    numinput= tempi;
    +  }
    +  if (diminput < 2) {
    +    qh_fprintf(qh, qh->ferr, 6220,"qhull input error: dimension %d(first number) should be at least 2\n",
    +            diminput);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (isdelaunay) {
    +    qh->PROJECTdelaunay= False;
    +    if (qh->CDDinput)
    +      *dimension= diminput;
    +    else
    +      *dimension= diminput+1;
    +    *numpoints= numinput;
    +    if (qh->ATinfinity)
    +      (*numpoints)++;
    +  }else if (qh->HALFspace) {
    +    *dimension= diminput - 1;
    +    *numpoints= numinput;
    +    if (diminput < 3) {
    +      qh_fprintf(qh, qh->ferr, 6221,"qhull input error: dimension %d(first number, includes offset) should be at least 3 for halfspaces\n",
    +            diminput);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (dimfeasible) {
    +      if (dimfeasible != *dimension) {
    +        qh_fprintf(qh, qh->ferr, 6222,"qhull input error: dimension %d of feasible point is not one less than dimension %d for halfspaces\n",
    +          dimfeasible, diminput);
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +    }else
    +      qh_setfeasible(qh, *dimension);
    +  }else {
    +    if (qh->CDDinput)
    +      *dimension= diminput-1;
    +    else
    +      *dimension= diminput;
    +    *numpoints= numinput;
    +  }
    +  qh->normal_size= *dimension * sizeof(coordT); /* for tracing with qh_printpoint */
    +  if (qh->HALFspace) {
    +    qh->half_space= coordp= (coordT*)qh_malloc(qh->normal_size + sizeof(coordT));
    +    if (qh->CDDinput) {
    +      offsetp= qh->half_space;
    +      normalp= offsetp + 1;
    +    }else {
    +      normalp= qh->half_space;
    +      offsetp= normalp + *dimension;
    +    }
    +  }
    +  qh->maxline= diminput * (qh_REALdigits + 5);
    +  maximize_(qh->maxline, 500);
    +  qh->line= (char*)qh_malloc((qh->maxline+1) * sizeof(char));
    +  *ismalloc= True;  /* use malloc since memory not setup */
    +  coords= points= qh->temp_malloc=  /* numinput and diminput >=2 by QH6220 */
    +        (coordT*)qh_malloc((*numpoints)*(*dimension)*sizeof(coordT));
    +  if (!coords || !qh->line || (qh->HALFspace && !qh->half_space)) {
    +    qh_fprintf(qh, qh->ferr, 6076, "qhull error: insufficient memory to read %d points\n",
    +            numinput);
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  if (isdelaunay && qh->ATinfinity) {
    +    infinity= points + numinput * (*dimension);
    +    for (k= (*dimension) - 1; k--; )
    +      infinity[k]= 0.0;
    +  }
    +  maxcount= numinput * diminput;
    +  paraboloid= 0.0;
    +  while ((s= (isfirst ?  s : fgets(qh->line, qh->maxline, qh->fin)))) {
    +    if (!isfirst) {
    +      linecount++;
    +      if (*s == 'e' || *s == 'E') {
    +        if (!memcmp(s, "end", (size_t)3) || !memcmp(s, "END", (size_t)3)) {
    +          if (qh->CDDinput )
    +            break;
    +          else if (wasbegin)
    +            qh_fprintf(qh, qh->ferr, 7058, "qhull input warning: the input appears to be in cdd format.  If so, use 'Fd'\n");
    +        }
    +      }
    +    }
    +    islong= False;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      value= qh_strtod(s, &t);
    +      if (s == t) {
    +        if (!*qh->rbox_command)
    +         strncat(qh->rbox_command, s, sizeof(qh->rbox_command)-1);
    +        if (*s && !firsttext)
    +          firsttext= linecount;
    +        if (!islong && !firstshort && coordcount)
    +          firstshort= linecount;
    +        break;
    +      }
    +      if (!firstpoint)
    +        firstpoint= linecount;
    +      s= t;
    +      if (++tokcount > maxcount)
    +        continue;
    +      if (qh->HALFspace) {
    +        if (qh->CDDinput)
    +          *(coordp++)= -value; /* both coefficients and offset */
    +        else
    +          *(coordp++)= value;
    +      }else {
    +        *(coords++)= value;
    +        if (qh->CDDinput && !coordcount) {
    +          if (value != 1.0) {
    +            qh_fprintf(qh, qh->ferr, 6077, "qhull input error: for cdd format, point at line %d does not start with '1'\n",
    +                   linecount);
    +            qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +          }
    +          coords--;
    +        }else if (isdelaunay) {
    +          paraboloid += value * value;
    +          if (qh->ATinfinity) {
    +            if (qh->CDDinput)
    +              infinity[coordcount-1] += value;
    +            else
    +              infinity[coordcount] += value;
    +          }
    +        }
    +      }
    +      if (++coordcount == diminput) {
    +        coordcount= 0;
    +        if (isdelaunay) {
    +          *(coords++)= paraboloid;
    +          maximize_(maxboloid, paraboloid);
    +          paraboloid= 0.0;
    +        }else if (qh->HALFspace) {
    +          if (!qh_sethalfspace(qh, *dimension, coords, &coords, normalp, offsetp, qh->feasible_point)) {
    +            qh_fprintf(qh, qh->ferr, 8048, "The halfspace was on line %d\n", linecount);
    +            if (wasbegin)
    +              qh_fprintf(qh, qh->ferr, 8049, "The input appears to be in cdd format.  If so, you should use option 'Fd'\n");
    +            qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +          }
    +          coordp= qh->half_space;
    +        }
    +        while (isspace(*s))
    +          s++;
    +        if (*s) {
    +          islong= True;
    +          if (!firstlong)
    +            firstlong= linecount;
    +        }
    +      }
    +    }
    +    if (!islong && !firstshort && coordcount)
    +      firstshort= linecount;
    +    if (!isfirst && s - qh->line >= qh->maxline) {
    +      qh_fprintf(qh, qh->ferr, 6078, "qhull input error: line %d contained more than %d characters\n",
    +              linecount, (int) (s - qh->line));   /* WARN64 */
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    isfirst= False;
    +  }
    +  if (tokcount != maxcount) {
    +    newnum= fmin_(numinput, tokcount/diminput);
    +    qh_fprintf(qh, qh->ferr, 7073,"\
    +qhull warning: instead of %d %d-dimensional points, input contains\n\
    +%d points and %d extra coordinates.  Line %d is the first\npoint",
    +       numinput, diminput, tokcount/diminput, tokcount % diminput, firstpoint);
    +    if (firsttext)
    +      qh_fprintf(qh, qh->ferr, 8051, ", line %d is the first comment", firsttext);
    +    if (firstshort)
    +      qh_fprintf(qh, qh->ferr, 8052, ", line %d is the first short\nline", firstshort);
    +    if (firstlong)
    +      qh_fprintf(qh, qh->ferr, 8053, ", line %d is the first long line", firstlong);
    +    qh_fprintf(qh, qh->ferr, 8054, ".  Continue with %d points.\n", newnum);
    +    numinput= newnum;
    +    if (isdelaunay && qh->ATinfinity) {
    +      for (k= tokcount % diminput; k--; )
    +        infinity[k] -= *(--coords);
    +      *numpoints= newnum+1;
    +    }else {
    +      coords -= tokcount % diminput;
    +      *numpoints= newnum;
    +    }
    +  }
    +  if (isdelaunay && qh->ATinfinity) {
    +    for (k= (*dimension) -1; k--; )
    +      infinity[k] /= numinput;
    +    if (coords == infinity)
    +      coords += (*dimension) -1;
    +    else {
    +      for (k=0; k < (*dimension) -1; k++)
    +        *(coords++)= infinity[k];
    +    }
    +    *(coords++)= maxboloid * 1.1;
    +  }
    +  if (qh->rbox_command[0]) {
    +    qh->rbox_command[strlen(qh->rbox_command)-1]= '\0';
    +    if (!strcmp(qh->rbox_command, "./rbox D4"))
    +      qh_fprintf(qh, qh->ferr, 8055, "\n\
    +This is the qhull test case.  If any errors or core dumps occur,\n\
    +recompile qhull with 'make new'.  If errors still occur, there is\n\
    +an incompatibility.  You should try a different compiler.  You can also\n\
    +change the choices in user.h.  If you discover the source of the problem,\n\
    +please send mail to qhull_bug@qhull.org.\n\
    +\n\
    +Type 'qhull' for a short list of options.\n");
    +  }
    +  qh_free(qh->line);
    +  qh->line= NULL;
    +  if (qh->half_space) {
    +    qh_free(qh->half_space);
    +    qh->half_space= NULL;
    +  }
    +  qh->temp_malloc= NULL;
    +  trace1((qh, qh->ferr, 1008,"qh_readpoints: read in %d %d-dimensional points\n",
    +          numinput, diminput));
    +  return(points);
    +} /* readpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfeasible(qh, dim )
    +    set qh.feasible_point from qh.feasible_string in "n,n,n" or "n n n" format
    +
    +  notes:
    +    "n,n,n" already checked by qh_initflags()
    +    see qh_readfeasible()
    +    called only once from qh_new_qhull, otherwise leaks memory
    +*/
    +void qh_setfeasible(qhT *qh, int dim) {
    +  int tokcount= 0;
    +  char *s;
    +  coordT *coords, value;
    +
    +  if (!(s= qh->feasible_string)) {
    +    qh_fprintf(qh, qh->ferr, 6223, "\
    +qhull input error: halfspace intersection needs a feasible point.\n\
    +Either prepend the input with 1 point or use 'Hn,n,n'.  See manual.\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (!(qh->feasible_point= (pointT*)qh_malloc(dim * sizeof(coordT)))) {
    +    qh_fprintf(qh, qh->ferr, 6079, "qhull error: insufficient memory for 'Hn,n,n'\n");
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  coords= qh->feasible_point;
    +  while (*s) {
    +    value= qh_strtod(s, &s);
    +    if (++tokcount > dim) {
    +      qh_fprintf(qh, qh->ferr, 7059, "qhull input warning: more coordinates for 'H%s' than dimension %d\n",
    +          qh->feasible_string, dim);
    +      break;
    +    }
    +    *(coords++)= value;
    +    if (*s)
    +      s++;
    +  }
    +  while (++tokcount <= dim)
    +    *(coords++)= 0.0;
    +} /* setfeasible */
    +
    +/*---------------------------------
    +
    +  qh_skipfacet(qh, facet )
    +    returns 'True' if this facet is not to be printed
    +
    +  notes:
    +    based on the user provided slice thresholds and 'good' specifications
    +*/
    +boolT qh_skipfacet(qhT *qh, facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +
    +  if (qh->PRINTneighbors) {
    +    if (facet->good)
    +      return !qh->PRINTgood;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->good)
    +        return False;
    +    }
    +    return True;
    +  }else if (qh->PRINTgood)
    +    return !facet->good;
    +  else if (!facet->normal)
    +    return True;
    +  return(!qh_inthresholds(qh, facet->normal, NULL));
    +} /* skipfacet */
    +
    +/*---------------------------------
    +
    +  qh_skipfilename(qh, string )
    +    returns pointer to character after filename
    +
    +  notes:
    +    skips leading spaces
    +    ends with spacing or eol
    +    if starts with ' or " ends with the same, skipping \' or \"
    +    For qhull, qh_argv_to_command() only uses double quotes
    +*/
    +char *qh_skipfilename(qhT *qh, char *filename) {
    +  char *s= filename;  /* non-const due to return */
    +  char c;
    +
    +  while (*s && isspace(*s))
    +    s++;
    +  c= *s++;
    +  if (c == '\0') {
    +    qh_fprintf(qh, qh->ferr, 6204, "qhull input error: filename expected, none found.\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (c == '\'' || c == '"') {
    +    while (*s !=c || s[-1] == '\\') {
    +      if (!*s) {
    +        qh_fprintf(qh, qh->ferr, 6203, "qhull input error: missing quote after filename -- %s\n", filename);
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +      s++;
    +    }
    +    s++;
    +  }
    +  else while (*s && !isspace(*s))
    +      s++;
    +  return s;
    +} /* skipfilename */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/io_r.h b/xs/src/qhull/src/libqhull_r/io_r.h
    new file mode 100644
    index 0000000000..12e05ae7ac
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/io_r.h
    @@ -0,0 +1,167 @@
    +/*
      ---------------------------------
    +
    +   io_r.h
    +   declarations of Input/Output functions
    +
    +   see README, libqhull_r.h and io_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/io_r.h#3 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFio
    +#define qhDEFio 1
    +
    +#include "libqhull_r.h"
    +
    +/*============ constants and flags ==================*/
    +
    +/*----------------------------------
    +
    +  qh_MAXfirst
    +    maximum length of first two lines of stdin
    +*/
    +#define qh_MAXfirst  200
    +
    +/*----------------------------------
    +
    +  qh_MINradius
    +    min radius for Gp and Gv, fraction of maxcoord
    +*/
    +#define qh_MINradius 0.02
    +
    +/*----------------------------------
    +
    +  qh_GEOMepsilon
    +    adjust outer planes for 'lines closer' and geomview roundoff.
    +    This prevents bleed through.
    +*/
    +#define qh_GEOMepsilon 2e-3
    +
    +/*----------------------------------
    +
    +  qh_WHITESPACE
    +    possible values of white space
    +*/
    +#define qh_WHITESPACE " \n\t\v\r\f"
    +
    +
    +/*----------------------------------
    +
    +  qh_RIDGE
    +    to select which ridges to print in qh_eachvoronoi
    +*/
    +typedef enum
    +{
    +    qh_RIDGEall = 0, qh_RIDGEinner, qh_RIDGEouter
    +}
    +qh_RIDGE;
    +
    +/*----------------------------------
    +
    +  printvridgeT
    +    prints results of qh_printvdiagram
    +
    +  see:
    +    qh_printvridge for an example
    +*/
    +typedef void (*printvridgeT)(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +
    +/*============== -prototypes in alphabetical order =========*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_dfacet(qhT *qh, unsigned id);
    +void    qh_dvertex(qhT *qh, unsigned id);
    +int     qh_compare_facetarea(const void *p1, const void *p2);
    +int     qh_compare_facetmerge(const void *p1, const void *p2);
    +int     qh_compare_facetvisit(const void *p1, const void *p2);
    +/* int  qh_compare_vertexpoint(const void *p1, const void *p2); Not useable since it depends on qh */
    +void    qh_copyfilename(qhT *qh, char *filename, int size, const char* source, int length);
    +void    qh_countfacets(qhT *qh, facetT *facetlist, setT *facets, boolT printall,
    +              int *numfacetsp, int *numsimplicialp, int *totneighborsp,
    +              int *numridgesp, int *numcoplanarsp, int *numnumtricoplanarsp);
    +pointT *qh_detvnorm(qhT *qh, vertexT *vertex, vertexT *vertexA, setT *centers, realT *offsetp);
    +setT   *qh_detvridge(qhT *qh, vertexT *vertex);
    +setT   *qh_detvridge3(qhT *qh, vertexT *atvertex, vertexT *vertex);
    +int     qh_eachvoronoi(qhT *qh, FILE *fp, printvridgeT printvridge, vertexT *atvertex, boolT visitall, qh_RIDGE innerouter, boolT inorder);
    +int     qh_eachvoronoi_all(qhT *qh, FILE *fp, printvridgeT printvridge, boolT isUpper, qh_RIDGE innerouter, boolT inorder);
    +void    qh_facet2point(qhT *qh, facetT *facet, pointT **point0, pointT **point1, realT *mindist);
    +setT   *qh_facetvertices(qhT *qh, facetT *facetlist, setT *facets, boolT allfacets);
    +void    qh_geomplanes(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane);
    +void    qh_markkeep(qhT *qh, facetT *facetlist);
    +setT   *qh_markvoronoi(qhT *qh, facetT *facetlist, setT *facets, boolT printall, boolT *isLowerp, int *numcentersp);
    +void    qh_order_vertexneighbors(qhT *qh, vertexT *vertex);
    +void    qh_prepare_output(qhT *qh);
    +void    qh_printafacet(qhT *qh, FILE *fp, qh_PRINT format, facetT *facet, boolT printall);
    +void    qh_printbegin(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printcenter(qhT *qh, FILE *fp, qh_PRINT format, const char *string, facetT *facet);
    +void    qh_printcentrum(qhT *qh, FILE *fp, facetT *facet, realT radius);
    +void    qh_printend(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printend4geom(qhT *qh, FILE *fp, facetT *facet, int *num, boolT printall);
    +void    qh_printextremes(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printextremes_2d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printextremes_d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printfacet(qhT *qh, FILE *fp, facetT *facet);
    +void    qh_printfacet2math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst);
    +void    qh_printfacet2geom(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet2geom_points(qhT *qh, FILE *fp, pointT *point1, pointT *point2,
    +                               facetT *facet, realT offset, realT color[3]);
    +void    qh_printfacet3math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst);
    +void    qh_printfacet3geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet3geom_points(qhT *qh, FILE *fp, setT *points, facetT *facet, realT offset, realT color[3]);
    +void    qh_printfacet3geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet3vertex(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format);
    +void    qh_printfacet4geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet4geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacetNvertex_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, int id, qh_PRINT format);
    +void    qh_printfacetNvertex_simplicial(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format);
    +void    qh_printfacetheader(qhT *qh, FILE *fp, facetT *facet);
    +void    qh_printfacetridges(qhT *qh, FILE *fp, facetT *facet);
    +void    qh_printfacets(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printhyperplaneintersection(qhT *qh, FILE *fp, facetT *facet1, facetT *facet2,
    +                   setT *vertices, realT color[3]);
    +void    qh_printneighborhood(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall);
    +void    qh_printline3geom(qhT *qh, FILE *fp, pointT *pointA, pointT *pointB, realT color[3]);
    +void    qh_printpoint(qhT *qh, FILE *fp, const char *string, pointT *point);
    +void    qh_printpointid(qhT *qh, FILE *fp, const char *string, int dim, pointT *point, int id);
    +void    qh_printpoint3(qhT *qh, FILE *fp, pointT *point);
    +void    qh_printpoints_out(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printpointvect(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius, realT color[3]);
    +void    qh_printpointvect2(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius);
    +void    qh_printridge(qhT *qh, FILE *fp, ridgeT *ridge);
    +void    qh_printspheres(qhT *qh, FILE *fp, setT *vertices, realT radius);
    +void    qh_printvdiagram(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +int     qh_printvdiagram2(qhT *qh, FILE *fp, printvridgeT printvridge, setT *vertices, qh_RIDGE innerouter, boolT inorder);
    +void    qh_printvertex(qhT *qh, FILE *fp, vertexT *vertex);
    +void    qh_printvertexlist(qhT *qh, FILE *fp, const char* string, facetT *facetlist,
    +                         setT *facets, boolT printall);
    +void    qh_printvertices(qhT *qh, FILE *fp, const char* string, setT *vertices);
    +void    qh_printvneighbors(qhT *qh, FILE *fp, facetT* facetlist, setT *facets, boolT printall);
    +void    qh_printvoronoi(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printvnorm(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +void    qh_printvridge(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +void    qh_produce_output(qhT *qh);
    +void    qh_produce_output2(qhT *qh);
    +void    qh_projectdim3(qhT *qh, pointT *source, pointT *destination);
    +int     qh_readfeasible(qhT *qh, int dim, const char *curline);
    +coordT *qh_readpoints(qhT *qh, int *numpoints, int *dimension, boolT *ismalloc);
    +void    qh_setfeasible(qhT *qh, int dim);
    +boolT   qh_skipfacet(qhT *qh, facetT *facet);
    +char   *qh_skipfilename(qhT *qh, char *filename);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFio */
    diff --git a/xs/src/qhull/src/libqhull_r/libqhull_r.c b/xs/src/qhull/src/libqhull_r/libqhull_r.c
    new file mode 100644
    index 0000000000..0fe0c980dc
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/libqhull_r.c
    @@ -0,0 +1,1403 @@
    +/*
      ---------------------------------
    +
    +   libqhull_r.c
    +   Quickhull algorithm for convex hulls
    +
    +   qhull() and top-level routines
    +
    +   see qh-qhull_r.htm, libqhull.h, unix_r.c
    +
    +   see qhull_ra.h for internal functions
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/libqhull_r.c#2 $$Change: 2047 $
    +   $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*============= functions in alphabetic order after qhull() =======*/
    +
    +/*---------------------------------
    +
    +  qh_qhull(qh)
    +    compute DIM3 convex hull of qh.num_points starting at qh.first_point
    +    qh->contains all global options and variables
    +
    +  returns:
    +    returns polyhedron
    +      qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices,
    +
    +    returns global variables
    +      qh.hulltime, qh.max_outside, qh.interior_point, qh.max_vertex, qh.min_vertex
    +
    +    returns precision constants
    +      qh.ANGLEround, centrum_radius, cos_max, DISTround, MAXabs_coord, ONEmerge
    +
    +  notes:
    +    unless needed for output
    +      qh.max_vertex and qh.min_vertex are max/min due to merges
    +
    +  see:
    +    to add individual points to either qh.num_points
    +      use qh_addpoint()
    +
    +    if qh.GETarea
    +      qh_produceoutput() returns qh.totarea and qh.totvol via qh_getarea()
    +
    +  design:
    +    record starting time
    +    initialize hull and partition points
    +    build convex hull
    +    unless early termination
    +      update facet->maxoutside for vertices, coplanar, and near-inside points
    +    error if temporary sets exist
    +    record end time
    +*/
    +
    +void qh_qhull(qhT *qh) {
    +  int numoutside;
    +
    +  qh->hulltime= qh_CPUclock;
    +  if (qh->RERUN || qh->JOGGLEmax < REALmax/2)
    +    qh_build_withrestart(qh);
    +  else {
    +    qh_initbuild(qh);
    +    qh_buildhull(qh);
    +  }
    +  if (!qh->STOPpoint && !qh->STOPcone) {
    +    if (qh->ZEROall_ok && !qh->TESTvneighbors && qh->MERGEexact)
    +      qh_checkzero(qh, qh_ALL);
    +    if (qh->ZEROall_ok && !qh->TESTvneighbors && !qh->WAScoplanar) {
    +      trace2((qh, qh->ferr, 2055, "qh_qhull: all facets are clearly convex and no coplanar points.  Post-merging and check of maxout not needed.\n"));
    +      qh->DOcheckmax= False;
    +    }else {
    +      if (qh->MERGEexact || (qh->hull_dim > qh_DIMreduceBuild && qh->PREmerge))
    +        qh_postmerge(qh, "First post-merge", qh->premerge_centrum, qh->premerge_cos,
    +             (qh->POSTmerge ? False : qh->TESTvneighbors));
    +      else if (!qh->POSTmerge && qh->TESTvneighbors)
    +        qh_postmerge(qh, "For testing vertex neighbors", qh->premerge_centrum,
    +             qh->premerge_cos, True);
    +      if (qh->POSTmerge)
    +        qh_postmerge(qh, "For post-merging", qh->postmerge_centrum,
    +             qh->postmerge_cos, qh->TESTvneighbors);
    +      if (qh->visible_list == qh->facet_list) { /* i.e., merging done */
    +        qh->findbestnew= True;
    +        qh_partitionvisible(qh /*qh.visible_list*/, !qh_ALL, &numoutside);
    +        qh->findbestnew= False;
    +        qh_deletevisible(qh /*qh.visible_list*/);
    +        qh_resetlists(qh, False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +      }
    +    }
    +    if (qh->DOcheckmax){
    +      if (qh->REPORTfreq) {
    +        qh_buildtracing(qh, NULL, NULL);
    +        qh_fprintf(qh, qh->ferr, 8115, "\nTesting all coplanar points.\n");
    +      }
    +      qh_check_maxout(qh);
    +    }
    +    if (qh->KEEPnearinside && !qh->maxoutdone)
    +      qh_nearcoplanar(qh);
    +  }
    +  if (qh_setsize(qh, qh->qhmem.tempstack) != 0) {
    +    qh_fprintf(qh, qh->ferr, 6164, "qhull internal error (qh_qhull): temporary sets not empty(%d)\n",
    +             qh_setsize(qh, qh->qhmem.tempstack));
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  qh->hulltime= qh_CPUclock - qh->hulltime;
    +  qh->QHULLfinished= True;
    +  trace1((qh, qh->ferr, 1036, "Qhull: algorithm completed\n"));
    +} /* qhull */
    +
    +/*---------------------------------
    +
    +  qh_addpoint(qh, furthest, facet, checkdist )
    +    add point (usually furthest point) above facet to hull
    +    if checkdist,
    +      check that point is above facet.
    +      if point is not outside of the hull, uses qh_partitioncoplanar()
    +      assumes that facet is defined by qh_findbestfacet()
    +    else if facet specified,
    +      assumes that point is above facet (major damage if below)
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    returns False if user requested an early termination
    +     qh.visible_list, newfacet_list, delvertex_list, NEWfacets may be defined
    +    updates qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices
    +    clear qh.maxoutdone (will need to call qh_check_maxout() for facet->maxoutside)
    +    if unknown point, adds a pointer to qh.other_points
    +      do not deallocate the point's coordinates
    +
    +  notes:
    +    assumes point is near its best facet and not at a local minimum of a lens
    +      distributions.  Use qh_findbestfacet to avoid this case.
    +    uses qh.visible_list, qh.newfacet_list, qh.delvertex_list, qh.NEWfacets
    +
    +  see also:
    +    qh_triangulate() -- triangulate non-simplicial facets
    +
    +  design:
    +    add point to other_points if needed
    +    if checkdist
    +      if point not above facet
    +        partition coplanar point
    +        exit
    +    exit if pre STOPpoint requested
    +    find horizon and visible facets for point
    +    make new facets for point to horizon
    +    make hyperplanes for point
    +    compute balance statistics
    +    match neighboring new facets
    +    update vertex neighbors and delete interior vertices
    +    exit if STOPcone requested
    +    merge non-convex new facets
    +    if merge found, many merges, or 'Qf'
    +       use qh_findbestnew() instead of qh_findbest()
    +    partition outside points from visible facets
    +    delete visible facets
    +    check polyhedron if requested
    +    exit if post STOPpoint requested
    +    reset working lists of facets and vertices
    +*/
    +boolT qh_addpoint(qhT *qh, pointT *furthest, facetT *facet, boolT checkdist) {
    +  int goodvisible, goodhorizon;
    +  vertexT *vertex;
    +  facetT *newfacet;
    +  realT dist, newbalance, pbalance;
    +  boolT isoutside= False;
    +  int numpart, numpoints, numnew, firstnew;
    +
    +  qh->maxoutdone= False;
    +  if (qh_pointid(qh, furthest) == qh_IDunknown)
    +    qh_setappend(qh, &qh->other_points, furthest);
    +  if (!facet) {
    +    qh_fprintf(qh, qh->ferr, 6213, "qhull internal error (qh_addpoint): NULL facet.  Need to call qh_findbestfacet first\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  if (checkdist) {
    +    facet= qh_findbest(qh, furthest, facet, !qh_ALL, !qh_ISnewfacets, !qh_NOupper,
    +                        &dist, &isoutside, &numpart);
    +    zzadd_(Zpartition, numpart);
    +    if (!isoutside) {
    +      zinc_(Znotmax);  /* last point of outsideset is no longer furthest. */
    +      facet->notfurthest= True;
    +      qh_partitioncoplanar(qh, furthest, facet, &dist);
    +      return True;
    +    }
    +  }
    +  qh_buildtracing(qh, furthest, facet);
    +  if (qh->STOPpoint < 0 && qh->furthest_id == -qh->STOPpoint-1) {
    +    facet->notfurthest= True;
    +    return False;
    +  }
    +  qh_findhorizon(qh, furthest, facet, &goodvisible, &goodhorizon);
    +  if (qh->ONLYgood && !(goodvisible+goodhorizon) && !qh->GOODclosest) {
    +    zinc_(Znotgood);
    +    facet->notfurthest= True;
    +    /* last point of outsideset is no longer furthest.  This is ok
    +       since all points of the outside are likely to be bad */
    +    qh_resetlists(qh, False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +    return True;
    +  }
    +  zzinc_(Zprocessed);
    +  firstnew= qh->facet_id;
    +  vertex= qh_makenewfacets(qh, furthest /*visible_list, attaches if !ONLYgood */);
    +  qh_makenewplanes(qh /* newfacet_list */);
    +  numnew= qh->facet_id - firstnew;
    +  newbalance= numnew - (realT) (qh->num_facets-qh->num_visible)
    +                         * qh->hull_dim/qh->num_vertices;
    +  wadd_(Wnewbalance, newbalance);
    +  wadd_(Wnewbalance2, newbalance * newbalance);
    +  if (qh->ONLYgood
    +  && !qh_findgood(qh, qh->newfacet_list, goodhorizon) && !qh->GOODclosest) {
    +    FORALLnew_facets
    +      qh_delfacet(qh, newfacet);
    +    qh_delvertex(qh, vertex);
    +    qh_resetlists(qh, True, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +    zinc_(Znotgoodnew);
    +    facet->notfurthest= True;
    +    return True;
    +  }
    +  if (qh->ONLYgood)
    +    qh_attachnewfacets(qh /*visible_list*/);
    +  qh_matchnewfacets(qh);
    +  qh_updatevertices(qh);
    +  if (qh->STOPcone && qh->furthest_id == qh->STOPcone-1) {
    +    facet->notfurthest= True;
    +    return False;  /* visible_list etc. still defined */
    +  }
    +  qh->findbestnew= False;
    +  if (qh->PREmerge || qh->MERGEexact) {
    +    qh_premerge(qh, vertex, qh->premerge_centrum, qh->premerge_cos);
    +    if (qh_USEfindbestnew)
    +      qh->findbestnew= True;
    +    else {
    +      FORALLnew_facets {
    +        if (!newfacet->simplicial) {
    +          qh->findbestnew= True;  /* use qh_findbestnew instead of qh_findbest*/
    +          break;
    +        }
    +      }
    +    }
    +  }else if (qh->BESToutside)
    +    qh->findbestnew= True;
    +  qh_partitionvisible(qh /*qh.visible_list*/, !qh_ALL, &numpoints);
    +  qh->findbestnew= False;
    +  qh->findbest_notsharp= False;
    +  zinc_(Zpbalance);
    +  pbalance= numpoints - (realT) qh->hull_dim /* assumes all points extreme */
    +                * (qh->num_points - qh->num_vertices)/qh->num_vertices;
    +  wadd_(Wpbalance, pbalance);
    +  wadd_(Wpbalance2, pbalance * pbalance);
    +  qh_deletevisible(qh /*qh.visible_list*/);
    +  zmax_(Zmaxvertex, qh->num_vertices);
    +  qh->NEWfacets= False;
    +  if (qh->IStracing >= 4) {
    +    if (qh->num_facets < 2000)
    +      qh_printlists(qh);
    +    qh_printfacetlist(qh, qh->newfacet_list, NULL, True);
    +    qh_checkpolygon(qh, qh->facet_list);
    +  }else if (qh->CHECKfrequently) {
    +    if (qh->num_facets < 50)
    +      qh_checkpolygon(qh, qh->facet_list);
    +    else
    +      qh_checkpolygon(qh, qh->newfacet_list);
    +  }
    +  if (qh->STOPpoint > 0 && qh->furthest_id == qh->STOPpoint-1)
    +    return False;
    +  qh_resetlists(qh, True, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +  /* qh_triangulate(qh); to test qh.TRInormals */
    +  trace2((qh, qh->ferr, 2056, "qh_addpoint: added p%d new facets %d new balance %2.2g point balance %2.2g\n",
    +    qh_pointid(qh, furthest), numnew, newbalance, pbalance));
    +  return True;
    +} /* addpoint */
    +
    +/*---------------------------------
    +
    +  qh_build_withrestart(qh)
    +    allow restarts due to qh.JOGGLEmax while calling qh_buildhull()
    +       qh_errexit always undoes qh_build_withrestart()
    +    qh.FIRSTpoint/qh.NUMpoints is point array
    +       it may be moved by qh_joggleinput(qh)
    +*/
    +void qh_build_withrestart(qhT *qh) {
    +  int restart;
    +
    +  qh->ALLOWrestart= True;
    +  while (True) {
    +    restart= setjmp(qh->restartexit); /* simple statement for CRAY J916 */
    +    if (restart) {       /* only from qh_precision() */
    +      zzinc_(Zretry);
    +      wmax_(Wretrymax, qh->JOGGLEmax);
    +      /* QH7078 warns about using 'TCn' with 'QJn' */
    +      qh->STOPcone= qh_IDunknown; /* if break from joggle, prevents normal output */
    +    }
    +    if (!qh->RERUN && qh->JOGGLEmax < REALmax/2) {
    +      if (qh->build_cnt > qh_JOGGLEmaxretry) {
    +        qh_fprintf(qh, qh->ferr, 6229, "qhull precision error: %d attempts to construct a convex hull\n\
    +        with joggled input.  Increase joggle above 'QJ%2.2g'\n\
    +        or modify qh_JOGGLE... parameters in user.h\n",
    +           qh->build_cnt, qh->JOGGLEmax);
    +        qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +      }
    +      if (qh->build_cnt && !restart)
    +        break;
    +    }else if (qh->build_cnt && qh->build_cnt >= qh->RERUN)
    +      break;
    +    qh->STOPcone= 0;
    +    qh_freebuild(qh, True);  /* first call is a nop */
    +    qh->build_cnt++;
    +    if (!qh->qhull_optionsiz)
    +      qh->qhull_optionsiz= (int)strlen(qh->qhull_options);   /* WARN64 */
    +    else {
    +      qh->qhull_options [qh->qhull_optionsiz]= '\0';
    +      qh->qhull_optionlen= qh_OPTIONline;  /* starts a new line */
    +    }
    +    qh_option(qh, "_run", &qh->build_cnt, NULL);
    +    if (qh->build_cnt == qh->RERUN) {
    +      qh->IStracing= qh->TRACElastrun;  /* duplicated from qh_initqhull_globals */
    +      if (qh->TRACEpoint != qh_IDunknown || qh->TRACEdist < REALmax/2 || qh->TRACEmerge) {
    +        qh->TRACElevel= (qh->IStracing? qh->IStracing : 3);
    +        qh->IStracing= 0;
    +      }
    +      qh->qhmem.IStracing= qh->IStracing;
    +    }
    +    if (qh->JOGGLEmax < REALmax/2)
    +      qh_joggleinput(qh);
    +    qh_initbuild(qh);
    +    qh_buildhull(qh);
    +    if (qh->JOGGLEmax < REALmax/2 && !qh->MERGING)
    +      qh_checkconvex(qh, qh->facet_list, qh_ALGORITHMfault);
    +  }
    +  qh->ALLOWrestart= False;
    +} /* qh_build_withrestart */
    +
    +/*---------------------------------
    +
    +  qh_buildhull(qh)
    +    construct a convex hull by adding outside points one at a time
    +
    +  returns:
    +
    +  notes:
    +    may be called multiple times
    +    checks facet and vertex lists for incorrect flags
    +    to recover from STOPcone, call qh_deletevisible and qh_resetlists
    +
    +  design:
    +    check visible facet and newfacet flags
    +    check newlist vertex flags and qh.STOPcone/STOPpoint
    +    for each facet with a furthest outside point
    +      add point to facet
    +      exit if qh.STOPcone or qh.STOPpoint requested
    +    if qh.NARROWhull for initial simplex
    +      partition remaining outside points to coplanar sets
    +*/
    +void qh_buildhull(qhT *qh) {
    +  facetT *facet;
    +  pointT *furthest;
    +  vertexT *vertex;
    +  int id;
    +
    +  trace1((qh, qh->ferr, 1037, "qh_buildhull: start build hull\n"));
    +  FORALLfacets {
    +    if (facet->visible || facet->newfacet) {
    +      qh_fprintf(qh, qh->ferr, 6165, "qhull internal error (qh_buildhull): visible or new facet f%d in facet list\n",
    +                   facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +  }
    +  FORALLvertices {
    +    if (vertex->newlist) {
    +      qh_fprintf(qh, qh->ferr, 6166, "qhull internal error (qh_buildhull): new vertex f%d in vertex list\n",
    +                   vertex->id);
    +      qh_errprint(qh, "ERRONEOUS", NULL, NULL, NULL, vertex);
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +    id= qh_pointid(qh, vertex->point);
    +    if ((qh->STOPpoint>0 && id == qh->STOPpoint-1) ||
    +        (qh->STOPpoint<0 && id == -qh->STOPpoint-1) ||
    +        (qh->STOPcone>0 && id == qh->STOPcone-1)) {
    +      trace1((qh, qh->ferr, 1038,"qh_buildhull: stop point or cone P%d in initial hull\n", id));
    +      return;
    +    }
    +  }
    +  qh->facet_next= qh->facet_list;      /* advance facet when processed */
    +  while ((furthest= qh_nextfurthest(qh, &facet))) {
    +    qh->num_outside--;  /* if ONLYmax, furthest may not be outside */
    +    if (!qh_addpoint(qh, furthest, facet, qh->ONLYmax))
    +      break;
    +  }
    +  if (qh->NARROWhull) /* move points from outsideset to coplanarset */
    +    qh_outcoplanar(qh /* facet_list */ );
    +  if (qh->num_outside && !furthest) {
    +    qh_fprintf(qh, qh->ferr, 6167, "qhull internal error (qh_buildhull): %d outside points were never processed.\n", qh->num_outside);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  trace1((qh, qh->ferr, 1039, "qh_buildhull: completed the hull construction\n"));
    +} /* buildhull */
    +
    +
    +/*---------------------------------
    +
    +  qh_buildtracing(qh, furthest, facet )
    +    trace an iteration of qh_buildhull() for furthest point and facet
    +    if !furthest, prints progress message
    +
    +  returns:
    +    tracks progress with qh.lastreport
    +    updates qh.furthest_id (-3 if furthest is NULL)
    +    also resets visit_id, vertext_visit on wrap around
    +
    +  see:
    +    qh_tracemerging()
    +
    +  design:
    +    if !furthest
    +      print progress message
    +      exit
    +    if 'TFn' iteration
    +      print progress message
    +    else if tracing
    +      trace furthest point and facet
    +    reset qh.visit_id and qh.vertex_visit if overflow may occur
    +    set qh.furthest_id for tracing
    +*/
    +void qh_buildtracing(qhT *qh, pointT *furthest, facetT *facet) {
    +  realT dist= 0;
    +  float cpu;
    +  int total, furthestid;
    +  time_t timedata;
    +  struct tm *tp;
    +  vertexT *vertex;
    +
    +  qh->old_randomdist= qh->RANDOMdist;
    +  qh->RANDOMdist= False;
    +  if (!furthest) {
    +    time(&timedata);
    +    tp= localtime(&timedata);
    +    cpu= (float)qh_CPUclock - (float)qh->hulltime;
    +    cpu /= (float)qh_SECticks;
    +    total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +    qh_fprintf(qh, qh->ferr, 8118, "\n\
    +At %02d:%02d:%02d & %2.5g CPU secs, qhull has created %d facets and merged %d.\n\
    + The current hull contains %d facets and %d vertices.  Last point was p%d\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, qh->facet_id -1,
    +      total, qh->num_facets, qh->num_vertices, qh->furthest_id);
    +    return;
    +  }
    +  furthestid= qh_pointid(qh, furthest);
    +  if (qh->TRACEpoint == furthestid) {
    +    qh->IStracing= qh->TRACElevel;
    +    qh->qhmem.IStracing= qh->TRACElevel;
    +  }else if (qh->TRACEpoint != qh_IDunknown && qh->TRACEdist < REALmax/2) {
    +    qh->IStracing= 0;
    +    qh->qhmem.IStracing= 0;
    +  }
    +  if (qh->REPORTfreq && (qh->facet_id-1 > qh->lastreport+qh->REPORTfreq)) {
    +    qh->lastreport= qh->facet_id-1;
    +    time(&timedata);
    +    tp= localtime(&timedata);
    +    cpu= (float)qh_CPUclock - (float)qh->hulltime;
    +    cpu /= (float)qh_SECticks;
    +    total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +    zinc_(Zdistio);
    +    qh_distplane(qh, furthest, facet, &dist);
    +    qh_fprintf(qh, qh->ferr, 8119, "\n\
    +At %02d:%02d:%02d & %2.5g CPU secs, qhull has created %d facets and merged %d.\n\
    + The current hull contains %d facets and %d vertices.  There are %d\n\
    + outside points.  Next is point p%d(v%d), %2.2g above f%d.\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, qh->facet_id -1,
    +      total, qh->num_facets, qh->num_vertices, qh->num_outside+1,
    +      furthestid, qh->vertex_id, dist, getid_(facet));
    +  }else if (qh->IStracing >=1) {
    +    cpu= (float)qh_CPUclock - (float)qh->hulltime;
    +    cpu /= (float)qh_SECticks;
    +    qh_distplane(qh, furthest, facet, &dist);
    +    qh_fprintf(qh, qh->ferr, 8120, "qh_addpoint: add p%d(v%d) to hull of %d facets(%2.2g above f%d) and %d outside at %4.4g CPU secs.  Previous was p%d.\n",
    +      furthestid, qh->vertex_id, qh->num_facets, dist,
    +      getid_(facet), qh->num_outside+1, cpu, qh->furthest_id);
    +  }
    +  zmax_(Zvisit2max, (int)qh->visit_id/2);
    +  if (qh->visit_id > (unsigned) INT_MAX) { /* 31 bits */
    +    zinc_(Zvisit);
    +    qh->visit_id= 0;
    +    FORALLfacets
    +      facet->visitid= 0;
    +  }
    +  zmax_(Zvvisit2max, (int)qh->vertex_visit/2);
    +  if (qh->vertex_visit > (unsigned) INT_MAX) { /* 31 bits */ 
    +    zinc_(Zvvisit);
    +    qh->vertex_visit= 0;
    +    FORALLvertices
    +      vertex->visitid= 0;
    +  }
    +  qh->furthest_id= furthestid;
    +  qh->RANDOMdist= qh->old_randomdist;
    +} /* buildtracing */
    +
    +/*---------------------------------
    +
    +  qh_errexit2(qh, exitcode, facet, otherfacet )
    +    return exitcode to system after an error
    +    report two facets
    +
    +  returns:
    +    assumes exitcode non-zero
    +
    +  see:
    +    normally use qh_errexit() in user.c(reports a facet and a ridge)
    +*/
    +void qh_errexit2(qhT *qh, int exitcode, facetT *facet, facetT *otherfacet) {
    +
    +  qh_errprint(qh, "ERRONEOUS", facet, otherfacet, NULL, NULL);
    +  qh_errexit(qh, exitcode, NULL, NULL);
    +} /* errexit2 */
    +
    +
    +/*---------------------------------
    +
    +  qh_findhorizon(qh, point, facet, goodvisible, goodhorizon )
    +    given a visible facet, find the point's horizon and visible facets
    +    for all facets, !facet-visible
    +
    +  returns:
    +    returns qh.visible_list/num_visible with all visible facets
    +      marks visible facets with ->visible
    +    updates count of good visible and good horizon facets
    +    updates qh.max_outside, qh.max_vertex, facet->maxoutside
    +
    +  see:
    +    similar to qh_delpoint()
    +
    +  design:
    +    move facet to qh.visible_list at end of qh.facet_list
    +    for all visible facets
    +     for each unvisited neighbor of a visible facet
    +       compute distance of point to neighbor
    +       if point above neighbor
    +         move neighbor to end of qh.visible_list
    +       else if point is coplanar with neighbor
    +         update qh.max_outside, qh.max_vertex, neighbor->maxoutside
    +         mark neighbor coplanar (will create a samecycle later)
    +         update horizon statistics
    +*/
    +void qh_findhorizon(qhT *qh, pointT *point, facetT *facet, int *goodvisible, int *goodhorizon) {
    +  facetT *neighbor, **neighborp, *visible;
    +  int numhorizon= 0, coplanar= 0;
    +  realT dist;
    +
    +  trace1((qh, qh->ferr, 1040,"qh_findhorizon: find horizon for point p%d facet f%d\n",qh_pointid(qh, point),facet->id));
    +  *goodvisible= *goodhorizon= 0;
    +  zinc_(Ztotvisible);
    +  qh_removefacet(qh, facet);  /* visible_list at end of qh->facet_list */
    +  qh_appendfacet(qh, facet);
    +  qh->num_visible= 1;
    +  if (facet->good)
    +    (*goodvisible)++;
    +  qh->visible_list= facet;
    +  facet->visible= True;
    +  facet->f.replace= NULL;
    +  if (qh->IStracing >=4)
    +    qh_errprint(qh, "visible", facet, NULL, NULL, NULL);
    +  qh->visit_id++;
    +  FORALLvisible_facets {
    +    if (visible->tricoplanar && !qh->TRInormals) {
    +      qh_fprintf(qh, qh->ferr, 6230, "Qhull internal error (qh_findhorizon): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit(qh, qh_ERRqhull, visible, NULL);
    +    }
    +    visible->visitid= qh->visit_id;
    +    FOREACHneighbor_(visible) {
    +      if (neighbor->visitid == qh->visit_id)
    +        continue;
    +      neighbor->visitid= qh->visit_id;
    +      zzinc_(Znumvisibility);
    +      qh_distplane(qh, point, neighbor, &dist);
    +      if (dist > qh->MINvisible) {
    +        zinc_(Ztotvisible);
    +        qh_removefacet(qh, neighbor);  /* append to end of qh->visible_list */
    +        qh_appendfacet(qh, neighbor);
    +        neighbor->visible= True;
    +        neighbor->f.replace= NULL;
    +        qh->num_visible++;
    +        if (neighbor->good)
    +          (*goodvisible)++;
    +        if (qh->IStracing >=4)
    +          qh_errprint(qh, "visible", neighbor, NULL, NULL, NULL);
    +      }else {
    +        if (dist > - qh->MAXcoplanar) {
    +          neighbor->coplanar= True;
    +          zzinc_(Zcoplanarhorizon);
    +          qh_precision(qh, "coplanar horizon");
    +          coplanar++;
    +          if (qh->MERGING) {
    +            if (dist > 0) {
    +              maximize_(qh->max_outside, dist);
    +              maximize_(qh->max_vertex, dist);
    +#if qh_MAXoutside
    +              maximize_(neighbor->maxoutside, dist);
    +#endif
    +            }else
    +              minimize_(qh->min_vertex, dist);  /* due to merge later */
    +          }
    +          trace2((qh, qh->ferr, 2057, "qh_findhorizon: point p%d is coplanar to horizon f%d, dist=%2.7g < qh->MINvisible(%2.7g)\n",
    +              qh_pointid(qh, point), neighbor->id, dist, qh->MINvisible));
    +        }else
    +          neighbor->coplanar= False;
    +        zinc_(Ztothorizon);
    +        numhorizon++;
    +        if (neighbor->good)
    +          (*goodhorizon)++;
    +        if (qh->IStracing >=4)
    +          qh_errprint(qh, "horizon", neighbor, NULL, NULL, NULL);
    +      }
    +    }
    +  }
    +  if (!numhorizon) {
    +    qh_precision(qh, "empty horizon");
    +    qh_fprintf(qh, qh->ferr, 6168, "qhull precision error (qh_findhorizon): empty horizon\n\
    +QhullPoint p%d was above all facets.\n", qh_pointid(qh, point));
    +    qh_printfacetlist(qh, qh->facet_list, NULL, True);
    +    qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +  }
    +  trace1((qh, qh->ferr, 1041, "qh_findhorizon: %d horizon facets(good %d), %d visible(good %d), %d coplanar\n",
    +       numhorizon, *goodhorizon, qh->num_visible, *goodvisible, coplanar));
    +  if (qh->IStracing >= 4 && qh->num_facets < 50)
    +    qh_printlists(qh);
    +} /* findhorizon */
    +
    +/*---------------------------------
    +
    +  qh_nextfurthest(qh, visible )
    +    returns next furthest point and visible facet for qh_addpoint()
    +    starts search at qh.facet_next
    +
    +  returns:
    +    removes furthest point from outside set
    +    NULL if none available
    +    advances qh.facet_next over facets with empty outside sets
    +
    +  design:
    +    for each facet from qh.facet_next
    +      if empty outside set
    +        advance qh.facet_next
    +      else if qh.NARROWhull
    +        determine furthest outside point
    +        if furthest point is not outside
    +          advance qh.facet_next(point will be coplanar)
    +    remove furthest point from outside set
    +*/
    +pointT *qh_nextfurthest(qhT *qh, facetT **visible) {
    +  facetT *facet;
    +  int size, idx;
    +  realT randr, dist;
    +  pointT *furthest;
    +
    +  while ((facet= qh->facet_next) != qh->facet_tail) {
    +    if (!facet->outsideset) {
    +      qh->facet_next= facet->next;
    +      continue;
    +    }
    +    SETreturnsize_(facet->outsideset, size);
    +    if (!size) {
    +      qh_setfree(qh, &facet->outsideset);
    +      qh->facet_next= facet->next;
    +      continue;
    +    }
    +    if (qh->NARROWhull) {
    +      if (facet->notfurthest)
    +        qh_furthestout(qh, facet);
    +      furthest= (pointT*)qh_setlast(facet->outsideset);
    +#if qh_COMPUTEfurthest
    +      qh_distplane(qh, furthest, facet, &dist);
    +      zinc_(Zcomputefurthest);
    +#else
    +      dist= facet->furthestdist;
    +#endif
    +      if (dist < qh->MINoutside) { /* remainder of outside set is coplanar for qh_outcoplanar */
    +        qh->facet_next= facet->next;
    +        continue;
    +      }
    +    }
    +    if (!qh->RANDOMoutside && !qh->VIRTUALmemory) {
    +      if (qh->PICKfurthest) {
    +        qh_furthestnext(qh /* qh->facet_list */);
    +        facet= qh->facet_next;
    +      }
    +      *visible= facet;
    +      return((pointT*)qh_setdellast(facet->outsideset));
    +    }
    +    if (qh->RANDOMoutside) {
    +      int outcoplanar = 0;
    +      if (qh->NARROWhull) {
    +        FORALLfacets {
    +          if (facet == qh->facet_next)
    +            break;
    +          if (facet->outsideset)
    +            outcoplanar += qh_setsize(qh, facet->outsideset);
    +        }
    +      }
    +      randr= qh_RANDOMint;
    +      randr= randr/(qh_RANDOMmax+1);
    +      idx= (int)floor((qh->num_outside - outcoplanar) * randr);
    +      FORALLfacet_(qh->facet_next) {
    +        if (facet->outsideset) {
    +          SETreturnsize_(facet->outsideset, size);
    +          if (!size)
    +            qh_setfree(qh, &facet->outsideset);
    +          else if (size > idx) {
    +            *visible= facet;
    +            return((pointT*)qh_setdelnth(qh, facet->outsideset, idx));
    +          }else
    +            idx -= size;
    +        }
    +      }
    +      qh_fprintf(qh, qh->ferr, 6169, "qhull internal error (qh_nextfurthest): num_outside %d is too low\nby at least %d, or a random real %g >= 1.0\n",
    +              qh->num_outside, idx+1, randr);
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }else { /* VIRTUALmemory */
    +      facet= qh->facet_tail->previous;
    +      if (!(furthest= (pointT*)qh_setdellast(facet->outsideset))) {
    +        if (facet->outsideset)
    +          qh_setfree(qh, &facet->outsideset);
    +        qh_removefacet(qh, facet);
    +        qh_prependfacet(qh, facet, &qh->facet_list);
    +        continue;
    +      }
    +      *visible= facet;
    +      return furthest;
    +    }
    +  }
    +  return NULL;
    +} /* nextfurthest */
    +
    +/*---------------------------------
    +
    +  qh_partitionall(qh, vertices, points, numpoints )
    +    partitions all points in points/numpoints to the outsidesets of facets
    +    vertices= vertices in qh.facet_list(!partitioned)
    +
    +  returns:
    +    builds facet->outsideset
    +    does not partition qh.GOODpoint
    +    if qh.ONLYgood && !qh.MERGING,
    +      does not partition qh.GOODvertex
    +
    +  notes:
    +    faster if qh.facet_list sorted by anticipated size of outside set
    +
    +  design:
    +    initialize pointset with all points
    +    remove vertices from pointset
    +    remove qh.GOODpointp from pointset (unless it's qh.STOPcone or qh.STOPpoint)
    +    for all facets
    +      for all remaining points in pointset
    +        compute distance from point to facet
    +        if point is outside facet
    +          remove point from pointset (by not reappending)
    +          update bestpoint
    +          append point or old bestpoint to facet's outside set
    +      append bestpoint to facet's outside set (furthest)
    +    for all points remaining in pointset
    +      partition point into facets' outside sets and coplanar sets
    +*/
    +void qh_partitionall(qhT *qh, setT *vertices, pointT *points, int numpoints){
    +  setT *pointset;
    +  vertexT *vertex, **vertexp;
    +  pointT *point, **pointp, *bestpoint;
    +  int size, point_i, point_n, point_end, remaining, i, id;
    +  facetT *facet;
    +  realT bestdist= -REALmax, dist, distoutside;
    +
    +  trace1((qh, qh->ferr, 1042, "qh_partitionall: partition all points into outside sets\n"));
    +  pointset= qh_settemp(qh, numpoints);
    +  qh->num_outside= 0;
    +  pointp= SETaddr_(pointset, pointT);
    +  for (i=numpoints, point= points; i--; point += qh->hull_dim)
    +    *(pointp++)= point;
    +  qh_settruncate(qh, pointset, numpoints);
    +  FOREACHvertex_(vertices) {
    +    if ((id= qh_pointid(qh, vertex->point)) >= 0)
    +      SETelem_(pointset, id)= NULL;
    +  }
    +  id= qh_pointid(qh, qh->GOODpointp);
    +  if (id >=0 && qh->STOPcone-1 != id && -qh->STOPpoint-1 != id)
    +    SETelem_(pointset, id)= NULL;
    +  if (qh->GOODvertexp && qh->ONLYgood && !qh->MERGING) { /* matches qhull()*/
    +    if ((id= qh_pointid(qh, qh->GOODvertexp)) >= 0)
    +      SETelem_(pointset, id)= NULL;
    +  }
    +  if (!qh->BESToutside) {  /* matches conditional for qh_partitionpoint below */
    +    distoutside= qh_DISToutside; /* multiple of qh.MINoutside & qh.max_outside, see user.h */
    +    zval_(Ztotpartition)= qh->num_points - qh->hull_dim - 1; /*misses GOOD... */
    +    remaining= qh->num_facets;
    +    point_end= numpoints;
    +    FORALLfacets {
    +      size= point_end/(remaining--) + 100;
    +      facet->outsideset= qh_setnew(qh, size);
    +      bestpoint= NULL;
    +      point_end= 0;
    +      FOREACHpoint_i_(qh, pointset) {
    +        if (point) {
    +          zzinc_(Zpartitionall);
    +          qh_distplane(qh, point, facet, &dist);
    +          if (dist < distoutside)
    +            SETelem_(pointset, point_end++)= point;
    +          else {
    +            qh->num_outside++;
    +            if (!bestpoint) {
    +              bestpoint= point;
    +              bestdist= dist;
    +            }else if (dist > bestdist) {
    +              qh_setappend(qh, &facet->outsideset, bestpoint);
    +              bestpoint= point;
    +              bestdist= dist;
    +            }else
    +              qh_setappend(qh, &facet->outsideset, point);
    +          }
    +        }
    +      }
    +      if (bestpoint) {
    +        qh_setappend(qh, &facet->outsideset, bestpoint);
    +#if !qh_COMPUTEfurthest
    +        facet->furthestdist= bestdist;
    +#endif
    +      }else
    +        qh_setfree(qh, &facet->outsideset);
    +      qh_settruncate(qh, pointset, point_end);
    +    }
    +  }
    +  /* if !qh->BESToutside, pointset contains points not assigned to outsideset */
    +  if (qh->BESToutside || qh->MERGING || qh->KEEPcoplanar || qh->KEEPinside) {
    +    qh->findbestnew= True;
    +    FOREACHpoint_i_(qh, pointset) {
    +      if (point)
    +        qh_partitionpoint(qh, point, qh->facet_list);
    +    }
    +    qh->findbestnew= False;
    +  }
    +  zzadd_(Zpartitionall, zzval_(Zpartition));
    +  zzval_(Zpartition)= 0;
    +  qh_settempfree(qh, &pointset);
    +  if (qh->IStracing >= 4)
    +    qh_printfacetlist(qh, qh->facet_list, NULL, True);
    +} /* partitionall */
    +
    +
    +/*---------------------------------
    +
    +  qh_partitioncoplanar(qh, point, facet, dist )
    +    partition coplanar point to a facet
    +    dist is distance from point to facet
    +    if dist NULL,
    +      searches for bestfacet and does nothing if inside
    +    if qh.findbestnew set,
    +      searches new facets instead of using qh_findbest()
    +
    +  returns:
    +    qh.max_ouside updated
    +    if qh.KEEPcoplanar or qh.KEEPinside
    +      point assigned to best coplanarset
    +
    +  notes:
    +    facet->maxoutside is updated at end by qh_check_maxout
    +
    +  design:
    +    if dist undefined
    +      find best facet for point
    +      if point sufficiently below facet (depends on qh.NEARinside and qh.KEEPinside)
    +        exit
    +    if keeping coplanar/nearinside/inside points
    +      if point is above furthest coplanar point
    +        append point to coplanar set (it is the new furthest)
    +        update qh.max_outside
    +      else
    +        append point one before end of coplanar set
    +    else if point is clearly outside of qh.max_outside and bestfacet->coplanarset
    +    and bestfacet is more than perpendicular to facet
    +      repartition the point using qh_findbest() -- it may be put on an outsideset
    +    else
    +      update qh.max_outside
    +*/
    +void qh_partitioncoplanar(qhT *qh, pointT *point, facetT *facet, realT *dist) {
    +  facetT *bestfacet;
    +  pointT *oldfurthest;
    +  realT bestdist, dist2= 0, angle;
    +  int numpart= 0, oldfindbest;
    +  boolT isoutside;
    +
    +  qh->WAScoplanar= True;
    +  if (!dist) {
    +    if (qh->findbestnew)
    +      bestfacet= qh_findbestnew(qh, point, facet, &bestdist, qh_ALL, &isoutside, &numpart);
    +    else
    +      bestfacet= qh_findbest(qh, point, facet, qh_ALL, !qh_ISnewfacets, qh->DELAUNAY,
    +                          &bestdist, &isoutside, &numpart);
    +    zinc_(Ztotpartcoplanar);
    +    zzadd_(Zpartcoplanar, numpart);
    +    if (!qh->DELAUNAY && !qh->KEEPinside) { /*  for 'd', bestdist skips upperDelaunay facets */
    +      if (qh->KEEPnearinside) {
    +        if (bestdist < -qh->NEARinside) {
    +          zinc_(Zcoplanarinside);
    +          trace4((qh, qh->ferr, 4062, "qh_partitioncoplanar: point p%d is more than near-inside facet f%d dist %2.2g findbestnew %d\n",
    +                  qh_pointid(qh, point), bestfacet->id, bestdist, qh->findbestnew));
    +          return;
    +        }
    +      }else if (bestdist < -qh->MAXcoplanar) {
    +          trace4((qh, qh->ferr, 4063, "qh_partitioncoplanar: point p%d is inside facet f%d dist %2.2g findbestnew %d\n",
    +                  qh_pointid(qh, point), bestfacet->id, bestdist, qh->findbestnew));
    +        zinc_(Zcoplanarinside);
    +        return;
    +      }
    +    }
    +  }else {
    +    bestfacet= facet;
    +    bestdist= *dist;
    +  }
    +  if (bestdist > qh->max_outside) {
    +    if (!dist && facet != bestfacet) {
    +      zinc_(Zpartangle);
    +      angle= qh_getangle(qh, facet->normal, bestfacet->normal);
    +      if (angle < 0) {
    +        /* typically due to deleted vertex and coplanar facets, e.g.,
    +             RBOX 1000 s Z1 G1e-13 t1001185205 | QHULL Tv */
    +        zinc_(Zpartflip);
    +        trace2((qh, qh->ferr, 2058, "qh_partitioncoplanar: repartition point p%d from f%d.  It is above flipped facet f%d dist %2.2g\n",
    +                qh_pointid(qh, point), facet->id, bestfacet->id, bestdist));
    +        oldfindbest= qh->findbestnew;
    +        qh->findbestnew= False;
    +        qh_partitionpoint(qh, point, bestfacet);
    +        qh->findbestnew= oldfindbest;
    +        return;
    +      }
    +    }
    +    qh->max_outside= bestdist;
    +    if (bestdist > qh->TRACEdist) {
    +      qh_fprintf(qh, qh->ferr, 8122, "qh_partitioncoplanar: ====== p%d from f%d increases max_outside to %2.2g of f%d last p%d\n",
    +                     qh_pointid(qh, point), facet->id, bestdist, bestfacet->id, qh->furthest_id);
    +      qh_errprint(qh, "DISTANT", facet, bestfacet, NULL, NULL);
    +    }
    +  }
    +  if (qh->KEEPcoplanar + qh->KEEPinside + qh->KEEPnearinside) {
    +    oldfurthest= (pointT*)qh_setlast(bestfacet->coplanarset);
    +    if (oldfurthest) {
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(qh, oldfurthest, bestfacet, &dist2);
    +    }
    +    if (!oldfurthest || dist2 < bestdist)
    +      qh_setappend(qh, &bestfacet->coplanarset, point);
    +    else
    +      qh_setappend2ndlast(qh, &bestfacet->coplanarset, point);
    +  }
    +  trace4((qh, qh->ferr, 4064, "qh_partitioncoplanar: point p%d is coplanar with facet f%d(or inside) dist %2.2g\n",
    +          qh_pointid(qh, point), bestfacet->id, bestdist));
    +} /* partitioncoplanar */
    +
    +/*---------------------------------
    +
    +  qh_partitionpoint(qh, point, facet )
    +    assigns point to an outside set, coplanar set, or inside set (i.e., dropt)
    +    if qh.findbestnew
    +      uses qh_findbestnew() to search all new facets
    +    else
    +      uses qh_findbest()
    +
    +  notes:
    +    after qh_distplane(), this and qh_findbest() are most expensive in 3-d
    +
    +  design:
    +    find best facet for point
    +      (either exhaustive search of new facets or directed search from facet)
    +    if qh.NARROWhull
    +      retain coplanar and nearinside points as outside points
    +    if point is outside bestfacet
    +      if point above furthest point for bestfacet
    +        append point to outside set (it becomes the new furthest)
    +        if outside set was empty
    +          move bestfacet to end of qh.facet_list (i.e., after qh.facet_next)
    +        update bestfacet->furthestdist
    +      else
    +        append point one before end of outside set
    +    else if point is coplanar to bestfacet
    +      if keeping coplanar points or need to update qh.max_outside
    +        partition coplanar point into bestfacet
    +    else if near-inside point
    +      partition as coplanar point into bestfacet
    +    else is an inside point
    +      if keeping inside points
    +        partition as coplanar point into bestfacet
    +*/
    +void qh_partitionpoint(qhT *qh, pointT *point, facetT *facet) {
    +  realT bestdist;
    +  boolT isoutside;
    +  facetT *bestfacet;
    +  int numpart;
    +#if qh_COMPUTEfurthest
    +  realT dist;
    +#endif
    +
    +  if (qh->findbestnew)
    +    bestfacet= qh_findbestnew(qh, point, facet, &bestdist, qh->BESToutside, &isoutside, &numpart);
    +  else
    +    bestfacet= qh_findbest(qh, point, facet, qh->BESToutside, qh_ISnewfacets, !qh_NOupper,
    +                          &bestdist, &isoutside, &numpart);
    +  zinc_(Ztotpartition);
    +  zzadd_(Zpartition, numpart);
    +  if (qh->NARROWhull) {
    +    if (qh->DELAUNAY && !isoutside && bestdist >= -qh->MAXcoplanar)
    +      qh_precision(qh, "nearly incident point(narrow hull)");
    +    if (qh->KEEPnearinside) {
    +      if (bestdist >= -qh->NEARinside)
    +        isoutside= True;
    +    }else if (bestdist >= -qh->MAXcoplanar)
    +      isoutside= True;
    +  }
    +
    +  if (isoutside) {
    +    if (!bestfacet->outsideset
    +    || !qh_setlast(bestfacet->outsideset)) {
    +      qh_setappend(qh, &(bestfacet->outsideset), point);
    +      if (!bestfacet->newfacet) {
    +        qh_removefacet(qh, bestfacet);  /* make sure it's after qh->facet_next */
    +        qh_appendfacet(qh, bestfacet);
    +      }
    +#if !qh_COMPUTEfurthest
    +      bestfacet->furthestdist= bestdist;
    +#endif
    +    }else {
    +#if qh_COMPUTEfurthest
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(qh, oldfurthest, bestfacet, &dist);
    +      if (dist < bestdist)
    +        qh_setappend(qh, &(bestfacet->outsideset), point);
    +      else
    +        qh_setappend2ndlast(qh, &(bestfacet->outsideset), point);
    +#else
    +      if (bestfacet->furthestdist < bestdist) {
    +        qh_setappend(qh, &(bestfacet->outsideset), point);
    +        bestfacet->furthestdist= bestdist;
    +      }else
    +        qh_setappend2ndlast(qh, &(bestfacet->outsideset), point);
    +#endif
    +    }
    +    qh->num_outside++;
    +    trace4((qh, qh->ferr, 4065, "qh_partitionpoint: point p%d is outside facet f%d new? %d (or narrowhull)\n",
    +          qh_pointid(qh, point), bestfacet->id, bestfacet->newfacet));
    +  }else if (qh->DELAUNAY || bestdist >= -qh->MAXcoplanar) { /* for 'd', bestdist skips upperDelaunay facets */
    +    zzinc_(Zcoplanarpart);
    +    if (qh->DELAUNAY)
    +      qh_precision(qh, "nearly incident point");
    +    if ((qh->KEEPcoplanar + qh->KEEPnearinside) || bestdist > qh->max_outside)
    +      qh_partitioncoplanar(qh, point, bestfacet, &bestdist);
    +    else {
    +      trace4((qh, qh->ferr, 4066, "qh_partitionpoint: point p%d is coplanar to facet f%d (dropped)\n",
    +          qh_pointid(qh, point), bestfacet->id));
    +    }
    +  }else if (qh->KEEPnearinside && bestdist > -qh->NEARinside) {
    +    zinc_(Zpartnear);
    +    qh_partitioncoplanar(qh, point, bestfacet, &bestdist);
    +  }else {
    +    zinc_(Zpartinside);
    +    trace4((qh, qh->ferr, 4067, "qh_partitionpoint: point p%d is inside all facets, closest to f%d dist %2.2g\n",
    +          qh_pointid(qh, point), bestfacet->id, bestdist));
    +    if (qh->KEEPinside)
    +      qh_partitioncoplanar(qh, point, bestfacet, &bestdist);
    +  }
    +} /* partitionpoint */
    +
    +/*---------------------------------
    +
    +  qh_partitionvisible(qh, allpoints, numoutside )
    +    partitions points in visible facets to qh.newfacet_list
    +    qh.visible_list= visible facets
    +    for visible facets
    +      1st neighbor (if any) points to a horizon facet or a new facet
    +    if allpoints(!used),
    +      repartitions coplanar points
    +
    +  returns:
    +    updates outside sets and coplanar sets of qh.newfacet_list
    +    updates qh.num_outside (count of outside points)
    +
    +  notes:
    +    qh.findbest_notsharp should be clear (extra work if set)
    +
    +  design:
    +    for all visible facets with outside set or coplanar set
    +      select a newfacet for visible facet
    +      if outside set
    +        partition outside set into new facets
    +      if coplanar set and keeping coplanar/near-inside/inside points
    +        if allpoints
    +          partition coplanar set into new facets, may be assigned outside
    +        else
    +          partition coplanar set into coplanar sets of new facets
    +    for each deleted vertex
    +      if allpoints
    +        partition vertex into new facets, may be assigned outside
    +      else
    +        partition vertex into coplanar sets of new facets
    +*/
    +void qh_partitionvisible(qhT *qh /*qh.visible_list*/, boolT allpoints, int *numoutside) {
    +  facetT *visible, *newfacet;
    +  pointT *point, **pointp;
    +  int coplanar=0, size;
    +  unsigned count;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh->ONLYmax)
    +    maximize_(qh->MINoutside, qh->max_vertex);
    +  *numoutside= 0;
    +  FORALLvisible_facets {
    +    if (!visible->outsideset && !visible->coplanarset)
    +      continue;
    +    newfacet= visible->f.replace;
    +    count= 0;
    +    while (newfacet && newfacet->visible) {
    +      newfacet= newfacet->f.replace;
    +      if (count++ > qh->facet_id)
    +        qh_infiniteloop(qh, visible);
    +    }
    +    if (!newfacet)
    +      newfacet= qh->newfacet_list;
    +    if (newfacet == qh->facet_tail) {
    +      qh_fprintf(qh, qh->ferr, 6170, "qhull precision error (qh_partitionvisible): all new facets deleted as\n        degenerate facets. Can not continue.\n");
    +      qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +    }
    +    if (visible->outsideset) {
    +      size= qh_setsize(qh, visible->outsideset);
    +      *numoutside += size;
    +      qh->num_outside -= size;
    +      FOREACHpoint_(visible->outsideset)
    +        qh_partitionpoint(qh, point, newfacet);
    +    }
    +    if (visible->coplanarset && (qh->KEEPcoplanar + qh->KEEPinside + qh->KEEPnearinside)) {
    +      size= qh_setsize(qh, visible->coplanarset);
    +      coplanar += size;
    +      FOREACHpoint_(visible->coplanarset) {
    +        if (allpoints) /* not used */
    +          qh_partitionpoint(qh, point, newfacet);
    +        else
    +          qh_partitioncoplanar(qh, point, newfacet, NULL);
    +      }
    +    }
    +  }
    +  FOREACHvertex_(qh->del_vertices) {
    +    if (vertex->point) {
    +      if (allpoints) /* not used */
    +        qh_partitionpoint(qh, vertex->point, qh->newfacet_list);
    +      else
    +        qh_partitioncoplanar(qh, vertex->point, qh->newfacet_list, NULL);
    +    }
    +  }
    +  trace1((qh, qh->ferr, 1043,"qh_partitionvisible: partitioned %d points from outsidesets and %d points from coplanarsets\n", *numoutside, coplanar));
    +} /* partitionvisible */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_precision(qh, reason )
    +    restart on precision errors if not merging and if 'QJn'
    +*/
    +void qh_precision(qhT *qh, const char *reason) {
    +
    +  if (qh->ALLOWrestart && !qh->PREmerge && !qh->MERGEexact) {
    +    if (qh->JOGGLEmax < REALmax/2) {
    +      trace0((qh, qh->ferr, 26, "qh_precision: qhull restart because of %s\n", reason));
    +      /* May be called repeatedly if qh->ALLOWrestart */
    +      longjmp(qh->restartexit, qh_ERRprec);
    +    }
    +  }
    +} /* qh_precision */
    +
    +/*---------------------------------
    +
    +  qh_printsummary(qh, fp )
    +    prints summary to fp
    +
    +  notes:
    +    not in io_r.c so that user_eg.c can prevent io_r.c from loading
    +    qh_printsummary and qh_countfacets must match counts
    +
    +  design:
    +    determine number of points, vertices, and coplanar points
    +    print summary
    +*/
    +void qh_printsummary(qhT *qh, FILE *fp) {
    +  realT ratio, outerplane, innerplane;
    +  float cpu;
    +  int size, id, nummerged, numvertices, numcoplanars= 0, nonsimplicial=0;
    +  int goodused;
    +  facetT *facet;
    +  const char *s;
    +  int numdel= zzval_(Zdelvertextot);
    +  int numtricoplanars= 0;
    +
    +  size= qh->num_points + qh_setsize(qh, qh->other_points);
    +  numvertices= qh->num_vertices - qh_setsize(qh, qh->del_vertices);
    +  id= qh_pointid(qh, qh->GOODpointp);
    +  FORALLfacets {
    +    if (facet->coplanarset)
    +      numcoplanars += qh_setsize(qh, facet->coplanarset);
    +    if (facet->good) {
    +      if (facet->simplicial) {
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else if (qh_setsize(qh, facet->vertices) != qh->hull_dim)
    +        nonsimplicial++;
    +    }
    +  }
    +  if (id >=0 && qh->STOPcone-1 != id && -qh->STOPpoint-1 != id)
    +    size--;
    +  if (qh->STOPcone || qh->STOPpoint)
    +      qh_fprintf(qh, fp, 9288, "\nAt a premature exit due to 'TVn', 'TCn', 'TRn', or precision error with 'QJn'.");
    +  if (qh->UPPERdelaunay)
    +    goodused= qh->GOODvertex + qh->GOODpoint + qh->SPLITthresholds;
    +  else if (qh->DELAUNAY)
    +    goodused= qh->GOODvertex + qh->GOODpoint + qh->GOODthreshold;
    +  else
    +    goodused= qh->num_good;
    +  nummerged= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +  if (qh->VORONOI) {
    +    if (qh->UPPERdelaunay)
    +      qh_fprintf(qh, fp, 9289, "\n\
    +Furthest-site Voronoi vertices by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    else
    +      qh_fprintf(qh, fp, 9290, "\n\
    +Voronoi diagram by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    qh_fprintf(qh, fp, 9291, "  Number of Voronoi regions%s: %d\n",
    +              qh->ATinfinity ? " and at-infinity" : "", numvertices);
    +    if (numdel)
    +      qh_fprintf(qh, fp, 9292, "  Total number of deleted points due to merging: %d\n", numdel);
    +    if (numcoplanars - numdel > 0)
    +      qh_fprintf(qh, fp, 9293, "  Number of nearly incident points: %d\n", numcoplanars - numdel);
    +    else if (size - numvertices - numdel > 0)
    +      qh_fprintf(qh, fp, 9294, "  Total number of nearly incident points: %d\n", size - numvertices - numdel);
    +    qh_fprintf(qh, fp, 9295, "  Number of%s Voronoi vertices: %d\n",
    +              goodused ? " 'good'" : "", qh->num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(qh, fp, 9296, "  Number of%s non-simplicial Voronoi vertices: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else if (qh->DELAUNAY) {
    +    if (qh->UPPERdelaunay)
    +      qh_fprintf(qh, fp, 9297, "\n\
    +Furthest-site Delaunay triangulation by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    else
    +      qh_fprintf(qh, fp, 9298, "\n\
    +Delaunay triangulation by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    qh_fprintf(qh, fp, 9299, "  Number of input sites%s: %d\n",
    +              qh->ATinfinity ? " and at-infinity" : "", numvertices);
    +    if (numdel)
    +      qh_fprintf(qh, fp, 9300, "  Total number of deleted points due to merging: %d\n", numdel);
    +    if (numcoplanars - numdel > 0)
    +      qh_fprintf(qh, fp, 9301, "  Number of nearly incident points: %d\n", numcoplanars - numdel);
    +    else if (size - numvertices - numdel > 0)
    +      qh_fprintf(qh, fp, 9302, "  Total number of nearly incident points: %d\n", size - numvertices - numdel);
    +    qh_fprintf(qh, fp, 9303, "  Number of%s Delaunay regions: %d\n",
    +              goodused ? " 'good'" : "", qh->num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(qh, fp, 9304, "  Number of%s non-simplicial Delaunay regions: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else if (qh->HALFspace) {
    +    qh_fprintf(qh, fp, 9305, "\n\
    +Halfspace intersection by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    qh_fprintf(qh, fp, 9306, "  Number of halfspaces: %d\n", size);
    +    qh_fprintf(qh, fp, 9307, "  Number of non-redundant halfspaces: %d\n", numvertices);
    +    if (numcoplanars) {
    +      if (qh->KEEPinside && qh->KEEPcoplanar)
    +        s= "similar and redundant";
    +      else if (qh->KEEPinside)
    +        s= "redundant";
    +      else
    +        s= "similar";
    +      qh_fprintf(qh, fp, 9308, "  Number of %s halfspaces: %d\n", s, numcoplanars);
    +    }
    +    qh_fprintf(qh, fp, 9309, "  Number of intersection points: %d\n", qh->num_facets - qh->num_visible);
    +    if (goodused)
    +      qh_fprintf(qh, fp, 9310, "  Number of 'good' intersection points: %d\n", qh->num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(qh, fp, 9311, "  Number of%s non-simplicial intersection points: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else {
    +    qh_fprintf(qh, fp, 9312, "\n\
    +Convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    qh_fprintf(qh, fp, 9313, "  Number of vertices: %d\n", numvertices);
    +    if (numcoplanars) {
    +      if (qh->KEEPinside && qh->KEEPcoplanar)
    +        s= "coplanar and interior";
    +      else if (qh->KEEPinside)
    +        s= "interior";
    +      else
    +        s= "coplanar";
    +      qh_fprintf(qh, fp, 9314, "  Number of %s points: %d\n", s, numcoplanars);
    +    }
    +    qh_fprintf(qh, fp, 9315, "  Number of facets: %d\n", qh->num_facets - qh->num_visible);
    +    if (goodused)
    +      qh_fprintf(qh, fp, 9316, "  Number of 'good' facets: %d\n", qh->num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(qh, fp, 9317, "  Number of%s non-simplicial facets: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }
    +  if (numtricoplanars)
    +      qh_fprintf(qh, fp, 9318, "  Number of triangulated facets: %d\n", numtricoplanars);
    +  qh_fprintf(qh, fp, 9319, "\nStatistics for: %s | %s",
    +                      qh->rbox_command, qh->qhull_command);
    +  if (qh->ROTATErandom != INT_MIN)
    +    qh_fprintf(qh, fp, 9320, " QR%d\n\n", qh->ROTATErandom);
    +  else
    +    qh_fprintf(qh, fp, 9321, "\n\n");
    +  qh_fprintf(qh, fp, 9322, "  Number of points processed: %d\n", zzval_(Zprocessed));
    +  qh_fprintf(qh, fp, 9323, "  Number of hyperplanes created: %d\n", zzval_(Zsetplane));
    +  if (qh->DELAUNAY)
    +    qh_fprintf(qh, fp, 9324, "  Number of facets in hull: %d\n", qh->num_facets - qh->num_visible);
    +  qh_fprintf(qh, fp, 9325, "  Number of distance tests for qhull: %d\n", zzval_(Zpartition)+
    +      zzval_(Zpartitionall)+zzval_(Znumvisibility)+zzval_(Zpartcoplanar));
    +#if 0  /* NOTE: must print before printstatistics() */
    +  {realT stddev, ave;
    +  qh_fprintf(qh, fp, 9326, "  average new facet balance: %2.2g\n",
    +          wval_(Wnewbalance)/zval_(Zprocessed));
    +  stddev= qh_stddev(zval_(Zprocessed), wval_(Wnewbalance),
    +                                 wval_(Wnewbalance2), &ave);
    +  qh_fprintf(qh, fp, 9327, "  new facet standard deviation: %2.2g\n", stddev);
    +  qh_fprintf(qh, fp, 9328, "  average partition balance: %2.2g\n",
    +          wval_(Wpbalance)/zval_(Zpbalance));
    +  stddev= qh_stddev(zval_(Zpbalance), wval_(Wpbalance),
    +                                 wval_(Wpbalance2), &ave);
    +  qh_fprintf(qh, fp, 9329, "  partition standard deviation: %2.2g\n", stddev);
    +  }
    +#endif
    +  if (nummerged) {
    +    qh_fprintf(qh, fp, 9330,"  Number of distance tests for merging: %d\n",zzval_(Zbestdist)+
    +          zzval_(Zcentrumtests)+zzval_(Zdistconvex)+zzval_(Zdistcheck)+
    +          zzval_(Zdistzero));
    +    qh_fprintf(qh, fp, 9331,"  Number of distance tests for checking: %d\n",zzval_(Zcheckpart));
    +    qh_fprintf(qh, fp, 9332,"  Number of merged facets: %d\n", nummerged);
    +  }
    +  if (!qh->RANDOMoutside && qh->QHULLfinished) {
    +    cpu= (float)qh->hulltime;
    +    cpu /= (float)qh_SECticks;
    +    wval_(Wcpu)= cpu;
    +    qh_fprintf(qh, fp, 9333, "  CPU seconds to compute hull (after input): %2.4g\n", cpu);
    +  }
    +  if (qh->RERUN) {
    +    if (!qh->PREmerge && !qh->MERGEexact)
    +      qh_fprintf(qh, fp, 9334, "  Percentage of runs with precision errors: %4.1f\n",
    +           zzval_(Zretry)*100.0/qh->build_cnt);  /* careful of order */
    +  }else if (qh->JOGGLEmax < REALmax/2) {
    +    if (zzval_(Zretry))
    +      qh_fprintf(qh, fp, 9335, "  After %d retries, input joggled by: %2.2g\n",
    +         zzval_(Zretry), qh->JOGGLEmax);
    +    else
    +      qh_fprintf(qh, fp, 9336, "  Input joggled by: %2.2g\n", qh->JOGGLEmax);
    +  }
    +  if (qh->totarea != 0.0)
    +    qh_fprintf(qh, fp, 9337, "  %s facet area:   %2.8g\n",
    +            zzval_(Ztotmerge) ? "Approximate" : "Total", qh->totarea);
    +  if (qh->totvol != 0.0)
    +    qh_fprintf(qh, fp, 9338, "  %s volume:       %2.8g\n",
    +            zzval_(Ztotmerge) ? "Approximate" : "Total", qh->totvol);
    +  if (qh->MERGING) {
    +    qh_outerinner(qh, NULL, &outerplane, &innerplane);
    +    if (outerplane > 2 * qh->DISTround) {
    +      qh_fprintf(qh, fp, 9339, "  Maximum distance of %spoint above facet: %2.2g",
    +            (qh->QHULLfinished ? "" : "merged "), outerplane);
    +      ratio= outerplane/(qh->ONEmerge + qh->DISTround);
    +      /* don't report ratio if MINoutside is large */
    +      if (ratio > 0.05 && 2* qh->ONEmerge > qh->MINoutside && qh->JOGGLEmax > REALmax/2)
    +        qh_fprintf(qh, fp, 9340, " (%.1fx)\n", ratio);
    +      else
    +        qh_fprintf(qh, fp, 9341, "\n");
    +    }
    +    if (innerplane < -2 * qh->DISTround) {
    +      qh_fprintf(qh, fp, 9342, "  Maximum distance of %svertex below facet: %2.2g",
    +            (qh->QHULLfinished ? "" : "merged "), innerplane);
    +      ratio= -innerplane/(qh->ONEmerge+qh->DISTround);
    +      if (ratio > 0.05 && qh->JOGGLEmax > REALmax/2)
    +        qh_fprintf(qh, fp, 9343, " (%.1fx)\n", ratio);
    +      else
    +        qh_fprintf(qh, fp, 9344, "\n");
    +    }
    +  }
    +  qh_fprintf(qh, fp, 9345, "\n");
    +} /* printsummary */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/libqhull_r.h b/xs/src/qhull/src/libqhull_r/libqhull_r.h
    new file mode 100644
    index 0000000000..363e6da6a7
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/libqhull_r.h
    @@ -0,0 +1,1134 @@
    +/*
      ---------------------------------
    +
    +   libqhull_r.h
    +   user-level header file for using qhull.a library
    +
    +   see qh-qhull_r.htm, qhull_ra.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/libqhull_r.h#8 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +
    +   includes function prototypes for libqhull_r.c, geom_r.c, global_r.c, io_r.c, user.c
    +
    +   use mem_r.h for mem_r.c
    +   use qset_r.h for qset_r.c
    +
    +   see unix_r.c for an example of using libqhull_r.h
    +
    +   recompile qhull if you change this file
    +*/
    +
    +#ifndef qhDEFlibqhull
    +#define qhDEFlibqhull 1
    +
    +/*=========================== -included files ==============*/
    +
    +/* user_r.h first for QHULL_CRTDBG */
    +#include "user_r.h"      /* user definable constants (e.g., realT). */
    +
    +#include "mem_r.h"   /* Needed for qhT in libqhull_r.h */
    +#include "qset_r.h"   /* Needed for QHULL_LIB_CHECK */
    +/* include stat_r.h after defining boolT.  statT needed for qhT in libqhull_r.h */
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifndef __STDC__
    +#ifndef __cplusplus
    +#if     !_MSC_VER
    +#error  Neither __STDC__ nor __cplusplus is defined.  Please use strict ANSI C or C++ to compile
    +#error  Qhull.  You may need to turn off compiler extensions in your project configuration.  If
    +#error  your compiler is a standard C compiler, you can delete this warning from libqhull_r.h
    +#endif
    +#endif
    +#endif
    +
    +/*============ constants and basic types ====================*/
    +
    +extern const char qh_version[]; /* defined in global_r.c */
    +extern const char qh_version2[]; /* defined in global_r.c */
    +
    +/*----------------------------------
    +
    +  coordT
    +    coordinates and coefficients are stored as realT (i.e., double)
    +
    +  notes:
    +    Qhull works well if realT is 'float'.  If so joggle (QJ) is not effective.
    +
    +    Could use 'float' for data and 'double' for calculations (realT vs. coordT)
    +      This requires many type casts, and adjusted error bounds.
    +      Also C compilers may do expressions in double anyway.
    +*/
    +#define coordT realT
    +
    +/*----------------------------------
    +
    +  pointT
    +    a point is an array of coordinates, usually qh.hull_dim
    +    qh_pointid returns
    +      qh_IDnone if point==0 or qh is undefined
    +      qh_IDinterior for qh.interior_point
    +      qh_IDunknown if point is neither in qh.first_point... nor qh.other_points
    +
    +  notes:
    +    qh.STOPcone and qh.STOPpoint assume that qh_IDunknown==-1 (other negative numbers indicate points)
    +    qh_IDunknown is also returned by getid_() for unknown facet, ridge, or vertex
    +*/
    +#define pointT coordT
    +typedef enum
    +{
    +    qh_IDnone = -3, qh_IDinterior = -2, qh_IDunknown = -1
    +}
    +qh_pointT;
    +
    +/*----------------------------------
    +
    +  flagT
    +    Boolean flag as a bit
    +*/
    +#define flagT unsigned int
    +
    +/*----------------------------------
    +
    +  boolT
    +    boolean value, either True or False
    +
    +  notes:
    +    needed for portability
    +    Use qh_False/qh_True as synonyms
    +*/
    +#define boolT unsigned int
    +#ifdef False
    +#undef False
    +#endif
    +#ifdef True
    +#undef True
    +#endif
    +#define False 0
    +#define True 1
    +#define qh_False 0
    +#define qh_True 1
    +
    +#include "stat_r.h"  /* needs boolT */
    +
    +/*----------------------------------
    +
    +  qh_CENTER
    +    to distinguish facet->center
    +*/
    +typedef enum
    +{
    +    qh_ASnone = 0,   /* If not MERGING and not VORONOI */
    +    qh_ASvoronoi,    /* Set by qh_clearcenters on qh_prepare_output, or if not MERGING and VORONOI */
    +    qh_AScentrum     /* If MERGING (assumed during merging) */
    +}
    +qh_CENTER;
    +
    +/*----------------------------------
    +
    +  qh_PRINT
    +    output formats for printing (qh.PRINTout).
    +    'Fa' 'FV' 'Fc' 'FC'
    +
    +
    +   notes:
    +   some of these names are similar to qhT names.  The similar names are only
    +   used in switch statements in qh_printbegin() etc.
    +*/
    +typedef enum {qh_PRINTnone= 0,
    +  qh_PRINTarea, qh_PRINTaverage,           /* 'Fa' 'FV' 'Fc' 'FC' */
    +  qh_PRINTcoplanars, qh_PRINTcentrums,
    +  qh_PRINTfacets, qh_PRINTfacets_xridge,   /* 'f' 'FF' 'G' 'FI' 'Fi' 'Fn' */
    +  qh_PRINTgeom, qh_PRINTids, qh_PRINTinner, qh_PRINTneighbors,
    +  qh_PRINTnormals, qh_PRINTouter, qh_PRINTmaple, /* 'n' 'Fo' 'i' 'm' 'Fm' 'FM', 'o' */
    +  qh_PRINTincidences, qh_PRINTmathematica, qh_PRINTmerges, qh_PRINToff,
    +  qh_PRINToptions, qh_PRINTpointintersect, /* 'FO' 'Fp' 'FP' 'p' 'FQ' 'FS' */
    +  qh_PRINTpointnearest, qh_PRINTpoints, qh_PRINTqhull, qh_PRINTsize,
    +  qh_PRINTsummary, qh_PRINTtriangles,      /* 'Fs' 'Ft' 'Fv' 'FN' 'Fx' */
    +  qh_PRINTvertices, qh_PRINTvneighbors, qh_PRINTextremes,
    +  qh_PRINTEND} qh_PRINT;
    +
    +/*----------------------------------
    +
    +  qh_ALL
    +    argument flag for selecting everything
    +*/
    +#define qh_ALL      True
    +#define qh_NOupper  True     /* argument for qh_findbest */
    +#define qh_IScheckmax  True     /* argument for qh_findbesthorizon */
    +#define qh_ISnewfacets  True     /* argument for qh_findbest */
    +#define qh_RESETvisible  True     /* argument for qh_resetlists */
    +
    +/*----------------------------------
    +
    +  qh_ERR
    +    Qhull exit codes, for indicating errors
    +    See: MSG_ERROR and MSG_WARNING [user.h]
    +*/
    +#define qh_ERRnone  0    /* no error occurred during qhull */
    +#define qh_ERRinput 1    /* input inconsistency */
    +#define qh_ERRsingular 2 /* singular input data */
    +#define qh_ERRprec  3    /* precision error */
    +#define qh_ERRmem   4    /* insufficient memory, matches mem_r.h */
    +#define qh_ERRqhull 5    /* internal error detected, matches mem_r.h */
    +
    +/*----------------------------------
    +
    +qh_FILEstderr
    +Fake stderr to distinguish error output from normal output
    +For C++ interface.  Must redefine qh_fprintf_qhull
    +*/
    +#define qh_FILEstderr ((FILE*)1)
    +
    +/* ============ -structures- ====================
    +   each of the following structures is defined by a typedef
    +   all realT and coordT fields occur at the beginning of a structure
    +        (otherwise space may be wasted due to alignment)
    +   define all flags together and pack into 32-bit number
    +
    +   DEFqhT and DEFsetT are likewise defined in
    +   mem_r.h, qset_r.h, and stat_r.h.
    +
    +*/
    +
    +typedef struct vertexT vertexT;
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +
    +#ifndef DEFqhT
    +#define DEFqhT 1
    +typedef struct qhT qhT;          /* defined below */
    +#endif
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;          /* defined in qset_r.h */
    +#endif
    +
    +/*----------------------------------
    +
    +  facetT
    +    defines a facet
    +
    +  notes:
    +   qhull() generates the hull as a list of facets.
    +
    +  topological information:
    +    f.previous,next     doubly-linked list of facets
    +    f.vertices          set of vertices
    +    f.ridges            set of ridges
    +    f.neighbors         set of neighbors
    +    f.toporient         True if facet has top-orientation (else bottom)
    +
    +  geometric information:
    +    f.offset,normal     hyperplane equation
    +    f.maxoutside        offset to outer plane -- all points inside
    +    f.center            centrum for testing convexity or Voronoi center for output
    +    f.simplicial        True if facet is simplicial
    +    f.flipped           True if facet does not include qh.interior_point
    +
    +  for constructing hull:
    +    f.visible           True if facet on list of visible facets (will be deleted)
    +    f.newfacet          True if facet on list of newly created facets
    +    f.coplanarset       set of points coplanar with this facet
    +                        (includes near-inside points for later testing)
    +    f.outsideset        set of points outside of this facet
    +    f.furthestdist      distance to furthest point of outside set
    +    f.visitid           marks visited facets during a loop
    +    f.replace           replacement facet for to-be-deleted, visible facets
    +    f.samecycle,newcycle cycle of facets for merging into horizon facet
    +
    +  see below for other flags and fields
    +*/
    +struct facetT {
    +#if !qh_COMPUTEfurthest
    +  coordT   furthestdist;/* distance to furthest point of outsideset */
    +#endif
    +#if qh_MAXoutside
    +  coordT   maxoutside;  /* max computed distance of point to facet
    +                        Before QHULLfinished this is an approximation
    +                        since maxdist not always set for mergefacet
    +                        Actual outer plane is +DISTround and
    +                        computed outer plane is +2*DISTround */
    +#endif
    +  coordT   offset;      /* exact offset of hyperplane from origin */
    +  coordT  *normal;      /* normal of hyperplane, hull_dim coefficients */
    +                        /*   if ->tricoplanar, shared with a neighbor */
    +  union {               /* in order of testing */
    +   realT   area;        /* area of facet, only in io_r.c if  ->isarea */
    +   facetT *replace;     /*  replacement facet if ->visible and NEWfacets
    +                             is NULL only if qh_mergedegen_redundant or interior */
    +   facetT *samecycle;   /*  cycle of facets from the same visible/horizon intersection,
    +                             if ->newfacet */
    +   facetT *newcycle;    /*  in horizon facet, current samecycle of new facets */
    +   facetT *trivisible;  /* visible facet for ->tricoplanar facets during qh_triangulate() */
    +   facetT *triowner;    /* owner facet for ->tricoplanar, !isarea facets w/ ->keepcentrum */
    +  }f;
    +  coordT  *center;      /* set according to qh.CENTERtype */
    +                        /*   qh_ASnone:    no center (not MERGING) */
    +                        /*   qh_AScentrum: centrum for testing convexity (qh_getcentrum) */
    +                        /*                 assumed qh_AScentrum while merging */
    +                        /*   qh_ASvoronoi: Voronoi center (qh_facetcenter) */
    +                        /* after constructing the hull, it may be changed (qh_clearcenter) */
    +                        /* if tricoplanar and !keepcentrum, shared with a neighbor */
    +  facetT  *previous;    /* previous facet in the facet_list */
    +  facetT  *next;        /* next facet in the facet_list */
    +  setT    *vertices;    /* vertices for this facet, inverse sorted by ID
    +                           if simplicial, 1st vertex was apex/furthest */
    +  setT    *ridges;      /* explicit ridges for nonsimplicial facets.
    +                           for simplicial facets, neighbors define the ridges */
    +  setT    *neighbors;   /* neighbors of the facet.  If simplicial, the kth
    +                           neighbor is opposite the kth vertex, and the first
    +                           neighbor is the horizon facet for the first vertex*/
    +  setT    *outsideset;  /* set of points outside this facet
    +                           if non-empty, last point is furthest
    +                           if NARROWhull, includes coplanars for partitioning*/
    +  setT    *coplanarset; /* set of points coplanar with this facet
    +                           > qh.min_vertex and <= facet->max_outside
    +                           a point is assigned to the furthest facet
    +                           if non-empty, last point is furthest away */
    +  unsigned visitid;     /* visit_id, for visiting all neighbors,
    +                           all uses are independent */
    +  unsigned id;          /* unique identifier from qh.facet_id */
    +  unsigned nummerge:9;  /* number of merges */
    +#define qh_MAXnummerge 511 /*     2^9-1, 32 flags total, see "flags:" in io_r.c */
    +  flagT    tricoplanar:1; /* True if TRIangulate and simplicial and coplanar with a neighbor */
    +                          /*   all tricoplanars share the same apex */
    +                          /*   all tricoplanars share the same ->center, ->normal, ->offset, ->maxoutside */
    +                          /*     ->keepcentrum is true for the owner.  It has the ->coplanareset */
    +                          /*   if ->degenerate, does not span facet (one logical ridge) */
    +                          /*   during qh_triangulate, f.trivisible points to original facet */
    +  flagT    newfacet:1;  /* True if facet on qh.newfacet_list (new or merged) */
    +  flagT    visible:1;   /* True if visible facet (will be deleted) */
    +  flagT    toporient:1; /* True if created with top orientation
    +                           after merging, use ridge orientation */
    +  flagT    simplicial:1;/* True if simplicial facet, ->ridges may be implicit */
    +  flagT    seen:1;      /* used to perform operations only once, like visitid */
    +  flagT    seen2:1;     /* used to perform operations only once, like visitid */
    +  flagT    flipped:1;   /* True if facet is flipped */
    +  flagT    upperdelaunay:1; /* True if facet is upper envelope of Delaunay triangulation */
    +  flagT    notfurthest:1; /* True if last point of outsideset is not furthest*/
    +
    +/*-------- flags primarily for output ---------*/
    +  flagT    good:1;      /* True if a facet marked good for output */
    +  flagT    isarea:1;    /* True if facet->f.area is defined */
    +
    +/*-------- flags for merging ------------------*/
    +  flagT    dupridge:1;  /* True if duplicate ridge in facet */
    +  flagT    mergeridge:1; /* True if facet or neighbor contains a qh_MERGEridge
    +                            ->normal defined (also defined for mergeridge2) */
    +  flagT    mergeridge2:1; /* True if neighbor contains a qh_MERGEridge (qhT *qh, mark_dupridges */
    +  flagT    coplanar:1;  /* True if horizon facet is coplanar at last use */
    +  flagT     mergehorizon:1; /* True if will merge into horizon (->coplanar) */
    +  flagT     cycledone:1;/* True if mergecycle_all already done */
    +  flagT    tested:1;    /* True if facet convexity has been tested (false after merge */
    +  flagT    keepcentrum:1; /* True if keep old centrum after a merge, or marks owner for ->tricoplanar */
    +  flagT    newmerge:1;  /* True if facet is newly merged for reducevertices */
    +  flagT    degenerate:1; /* True if facet is degenerate (degen_mergeset or ->tricoplanar) */
    +  flagT    redundant:1;  /* True if facet is redundant (degen_mergeset) */
    +};
    +
    +
    +/*----------------------------------
    +
    +  ridgeT
    +    defines a ridge
    +
    +  notes:
    +  a ridge is hull_dim-1 simplex between two neighboring facets.  If the
    +  facets are non-simplicial, there may be more than one ridge between
    +  two facets.  E.G. a 4-d hypercube has two triangles between each pair
    +  of neighboring facets.
    +
    +  topological information:
    +    vertices            a set of vertices
    +    top,bottom          neighboring facets with orientation
    +
    +  geometric information:
    +    tested              True if ridge is clearly convex
    +    nonconvex           True if ridge is non-convex
    +*/
    +struct ridgeT {
    +  setT    *vertices;    /* vertices belonging to this ridge, inverse sorted by ID
    +                           NULL if a degen ridge (matchsame) */
    +  facetT  *top;         /* top facet this ridge is part of */
    +  facetT  *bottom;      /* bottom facet this ridge is part of */
    +  unsigned id;          /* unique identifier.  Same size as vertex_id and ridge_id */
    +  flagT    seen:1;      /* used to perform operations only once */
    +  flagT    tested:1;    /* True when ridge is tested for convexity */
    +  flagT    nonconvex:1; /* True if getmergeset detected a non-convex neighbor
    +                           only one ridge between neighbors may have nonconvex */
    +};
    +
    +/*----------------------------------
    +
    +  vertexT
    +     defines a vertex
    +
    +  topological information:
    +    next,previous       doubly-linked list of all vertices
    +    neighbors           set of adjacent facets (only if qh.VERTEXneighbors)
    +
    +  geometric information:
    +    point               array of DIM3 coordinates
    +*/
    +struct vertexT {
    +  vertexT *next;        /* next vertex in vertex_list */
    +  vertexT *previous;    /* previous vertex in vertex_list */
    +  pointT  *point;       /* hull_dim coordinates (coordT) */
    +  setT    *neighbors;   /* neighboring facets of vertex, qh_vertexneighbors()
    +                           inits in io_r.c or after first merge */
    +  unsigned id;          /* unique identifier.  Same size as qh.vertex_id and qh.ridge_id */
    +  unsigned visitid;    /* for use with qh.vertex_visit, size must match */
    +  flagT    seen:1;      /* used to perform operations only once */
    +  flagT    seen2:1;     /* another seen flag */
    +  flagT    delridge:1;  /* vertex was part of a deleted ridge */
    +  flagT    deleted:1;   /* true if vertex on qh.del_vertices */
    +  flagT    newlist:1;   /* true if vertex on qh.newvertex_list */
    +};
    +
    +/*======= -global variables -qh ============================*/
    +
    +/*----------------------------------
    +
    +  qhT
    +   All global variables for qhull are in qhT.  It includes qhmemT, qhstatT, and rbox globals
    +
    +   This version of Qhull is reentrant, but it is not thread-safe.
    +
    +   Do not run separate threads on the same instance of qhT.
    +
    +   QHULL_LIB_CHECK checks that a program and the corresponding
    +   qhull library were built with the same type of header files.
    +*/
    +
    +#define QHULL_NON_REENTRANT 0
    +#define QHULL_QH_POINTER 1
    +#define QHULL_REENTRANT 2
    +
    +#define QHULL_LIB_TYPE QHULL_REENTRANT
    +
    +#define QHULL_LIB_CHECK qh_lib_check(QHULL_LIB_TYPE, sizeof(qhT), sizeof(vertexT), sizeof(ridgeT), sizeof(facetT), sizeof(setT), sizeof(qhmemT));
    +#define QHULL_LIB_CHECK_RBOX qh_lib_check(QHULL_LIB_TYPE, sizeof(qhT), sizeof(vertexT), sizeof(ridgeT), sizeof(facetT), 0, 0);
    +
    +struct qhT {
    +
    +/*----------------------------------
    +
    +  qh constants
    +    configuration flags and constants for Qhull
    +
    +  notes:
    +    The user configures Qhull by defining flags.  They are
    +    copied into qh by qh_setflags().  qh-quick_r.htm#options defines the flags.
    +*/
    +  boolT ALLpoints;        /* true 'Qs' if search all points for initial simplex */
    +  boolT ANGLEmerge;       /* true 'Qa' if sort potential merges by angle */
    +  boolT APPROXhull;       /* true 'Wn' if MINoutside set */
    +  realT   MINoutside;     /*   'Wn' min. distance for an outside point */
    +  boolT ANNOTATEoutput;   /* true 'Ta' if annotate output with message codes */
    +  boolT ATinfinity;       /* true 'Qz' if point num_points-1 is "at-infinity"
    +                             for improving precision in Delaunay triangulations */
    +  boolT AVOIDold;         /* true 'Q4' if avoid old->new merges */
    +  boolT BESToutside;      /* true 'Qf' if partition points into best outsideset */
    +  boolT CDDinput;         /* true 'Pc' if input uses CDD format (1.0/offset first) */
    +  boolT CDDoutput;        /* true 'PC' if print normals in CDD format (offset first) */
    +  boolT CHECKfrequently;  /* true 'Tc' if checking frequently */
    +  realT premerge_cos;     /*   'A-n'   cos_max when pre merging */
    +  realT postmerge_cos;    /*   'An'    cos_max when post merging */
    +  boolT DELAUNAY;         /* true 'd' if computing DELAUNAY triangulation */
    +  boolT DOintersections;  /* true 'Gh' if print hyperplane intersections */
    +  int   DROPdim;          /* drops dim 'GDn' for 4-d -> 3-d output */
    +  boolT FORCEoutput;      /* true 'Po' if forcing output despite degeneracies */
    +  int   GOODpoint;        /* 1+n for 'QGn', good facet if visible/not(-) from point n*/
    +  pointT *GOODpointp;     /*   the actual point */
    +  boolT GOODthreshold;    /* true if qh.lower_threshold/upper_threshold defined
    +                             false if qh.SPLITthreshold */
    +  int   GOODvertex;       /* 1+n, good facet if vertex for point n */
    +  pointT *GOODvertexp;     /*   the actual point */
    +  boolT HALFspace;        /* true 'Hn,n,n' if halfspace intersection */
    +  boolT ISqhullQh;        /* Set by Qhull.cpp on initialization */
    +  int   IStracing;        /* trace execution, 0=none, 1=least, 4=most, -1=events */
    +  int   KEEParea;         /* 'PAn' number of largest facets to keep */
    +  boolT KEEPcoplanar;     /* true 'Qc' if keeping nearest facet for coplanar points */
    +  boolT KEEPinside;       /* true 'Qi' if keeping nearest facet for inside points
    +                              set automatically if 'd Qc' */
    +  int   KEEPmerge;        /* 'PMn' number of facets to keep with most merges */
    +  realT KEEPminArea;      /* 'PFn' minimum facet area to keep */
    +  realT MAXcoplanar;      /* 'Un' max distance below a facet to be coplanar*/
    +  boolT MERGEexact;       /* true 'Qx' if exact merges (coplanar, degen, dupridge, flipped) */
    +  boolT MERGEindependent; /* true 'Q2' if merging independent sets */
    +  boolT MERGING;          /* true if exact-, pre- or post-merging, with angle and centrum tests */
    +  realT   premerge_centrum;  /*   'C-n' centrum_radius when pre merging.  Default is round-off */
    +  realT   postmerge_centrum; /*   'Cn' centrum_radius when post merging.  Default is round-off */
    +  boolT MERGEvertices;    /* true 'Q3' if merging redundant vertices */
    +  realT MINvisible;       /* 'Vn' min. distance for a facet to be visible */
    +  boolT NOnarrow;         /* true 'Q10' if no special processing for narrow distributions */
    +  boolT NOnearinside;     /* true 'Q8' if ignore near-inside points when partitioning */
    +  boolT NOpremerge;       /* true 'Q0' if no defaults for C-0 or Qx */
    +  boolT NOwide;           /* true 'Q12' if no error on wide merge due to duplicate ridge */
    +  boolT ONLYgood;         /* true 'Qg' if process points with good visible or horizon facets */
    +  boolT ONLYmax;          /* true 'Qm' if only process points that increase max_outside */
    +  boolT PICKfurthest;     /* true 'Q9' if process furthest of furthest points*/
    +  boolT POSTmerge;        /* true if merging after buildhull (Cn or An) */
    +  boolT PREmerge;         /* true if merging during buildhull (C-n or A-n) */
    +                        /* NOTE: some of these names are similar to qh_PRINT names */
    +  boolT PRINTcentrums;    /* true 'Gc' if printing centrums */
    +  boolT PRINTcoplanar;    /* true 'Gp' if printing coplanar points */
    +  int   PRINTdim;         /* print dimension for Geomview output */
    +  boolT PRINTdots;        /* true 'Ga' if printing all points as dots */
    +  boolT PRINTgood;        /* true 'Pg' if printing good facets */
    +  boolT PRINTinner;       /* true 'Gi' if printing inner planes */
    +  boolT PRINTneighbors;   /* true 'PG' if printing neighbors of good facets */
    +  boolT PRINTnoplanes;    /* true 'Gn' if printing no planes */
    +  boolT PRINToptions1st;  /* true 'FO' if printing options to stderr */
    +  boolT PRINTouter;       /* true 'Go' if printing outer planes */
    +  boolT PRINTprecision;   /* false 'Pp' if not reporting precision problems */
    +  qh_PRINT PRINTout[qh_PRINTEND]; /* list of output formats to print */
    +  boolT PRINTridges;      /* true 'Gr' if print ridges */
    +  boolT PRINTspheres;     /* true 'Gv' if print vertices as spheres */
    +  boolT PRINTstatistics;  /* true 'Ts' if printing statistics to stderr */
    +  boolT PRINTsummary;     /* true 's' if printing summary to stderr */
    +  boolT PRINTtransparent; /* true 'Gt' if print transparent outer ridges */
    +  boolT PROJECTdelaunay;  /* true if DELAUNAY, no readpoints() and
    +                             need projectinput() for Delaunay in qh_init_B */
    +  int   PROJECTinput;     /* number of projected dimensions 'bn:0Bn:0' */
    +  boolT QUICKhelp;        /* true if quick help message for degen input */
    +  boolT RANDOMdist;       /* true if randomly change distplane and setfacetplane */
    +  realT RANDOMfactor;     /*    maximum random perturbation */
    +  realT RANDOMa;          /*    qh_randomfactor is randr * RANDOMa + RANDOMb */
    +  realT RANDOMb;
    +  boolT RANDOMoutside;    /* true if select a random outside point */
    +  int   REPORTfreq;       /* buildtracing reports every n facets */
    +  int   REPORTfreq2;      /* tracemerging reports every REPORTfreq/2 facets */
    +  int   RERUN;            /* 'TRn' rerun qhull n times (qh.build_cnt) */
    +  int   ROTATErandom;     /* 'QRn' seed, 0 time, >= rotate input */
    +  boolT SCALEinput;       /* true 'Qbk' if scaling input */
    +  boolT SCALElast;        /* true 'Qbb' if scale last coord to max prev coord */
    +  boolT SETroundoff;      /* true 'E' if qh.DISTround is predefined */
    +  boolT SKIPcheckmax;     /* true 'Q5' if skip qh_check_maxout */
    +  boolT SKIPconvex;       /* true 'Q6' if skip convexity testing during pre-merge */
    +  boolT SPLITthresholds;  /* true if upper_/lower_threshold defines a region
    +                               used only for printing (!for qh.ONLYgood) */
    +  int   STOPcone;         /* 'TCn' 1+n for stopping after cone for point n */
    +                          /*       also used by qh_build_withresart for err exit*/
    +  int   STOPpoint;        /* 'TVn' 'TV-n' 1+n for stopping after/before(-)
    +                                        adding point n */
    +  int   TESTpoints;       /* 'QTn' num of test points after qh.num_points.  Test points always coplanar. */
    +  boolT TESTvneighbors;   /*  true 'Qv' if test vertex neighbors at end */
    +  int   TRACElevel;       /* 'Tn' conditional IStracing level */
    +  int   TRACElastrun;     /*  qh.TRACElevel applies to last qh.RERUN */
    +  int   TRACEpoint;       /* 'TPn' start tracing when point n is a vertex */
    +  realT TRACEdist;        /* 'TWn' start tracing when merge distance too big */
    +  int   TRACEmerge;       /* 'TMn' start tracing before this merge */
    +  boolT TRIangulate;      /* true 'Qt' if triangulate non-simplicial facets */
    +  boolT TRInormals;       /* true 'Q11' if triangulate duplicates ->normal and ->center (sets Qt) */
    +  boolT UPPERdelaunay;    /* true 'Qu' if computing furthest-site Delaunay */
    +  boolT USEstdout;        /* true 'Tz' if using stdout instead of stderr */
    +  boolT VERIFYoutput;     /* true 'Tv' if verify output at end of qhull */
    +  boolT VIRTUALmemory;    /* true 'Q7' if depth-first processing in buildhull */
    +  boolT VORONOI;          /* true 'v' if computing Voronoi diagram */
    +
    +  /*--------input constants ---------*/
    +  realT AREAfactor;       /* 1/(hull_dim-1)! for converting det's to area */
    +  boolT DOcheckmax;       /* true if calling qh_check_maxout (qhT *qh, qh_initqhull_globals) */
    +  char  *feasible_string;  /* feasible point 'Hn,n,n' for halfspace intersection */
    +  coordT *feasible_point;  /*    as coordinates, both malloc'd */
    +  boolT GETarea;          /* true 'Fa', 'FA', 'FS', 'PAn', 'PFn' if compute facet area/Voronoi volume in io_r.c */
    +  boolT KEEPnearinside;   /* true if near-inside points in coplanarset */
    +  int   hull_dim;         /* dimension of hull, set by initbuffers */
    +  int   input_dim;        /* dimension of input, set by initbuffers */
    +  int   num_points;       /* number of input points */
    +  pointT *first_point;    /* array of input points, see POINTSmalloc */
    +  boolT POINTSmalloc;     /*   true if qh.first_point/num_points allocated */
    +  pointT *input_points;   /* copy of original qh.first_point for input points for qh_joggleinput */
    +  boolT input_malloc;     /* true if qh.input_points malloc'd */
    +  char  qhull_command[256];/* command line that invoked this program */
    +  int   qhull_commandsiz2; /*    size of qhull_command at qh_clear_outputflags */
    +  char  rbox_command[256]; /* command line that produced the input points */
    +  char  qhull_options[512];/* descriptive list of options */
    +  int   qhull_optionlen;  /*    length of last line */
    +  int   qhull_optionsiz;  /*    size of qhull_options at qh_build_withrestart */
    +  int   qhull_optionsiz2; /*    size of qhull_options at qh_clear_outputflags */
    +  int   run_id;           /* non-zero, random identifier for this instance of qhull */
    +  boolT VERTEXneighbors;  /* true if maintaining vertex neighbors */
    +  boolT ZEROcentrum;      /* true if 'C-0' or 'C-0 Qx'.  sets ZEROall_ok */
    +  realT *upper_threshold; /* don't print if facet->normal[k]>=upper_threshold[k]
    +                             must set either GOODthreshold or SPLITthreshold
    +                             if Delaunay, default is 0.0 for upper envelope */
    +  realT *lower_threshold; /* don't print if facet->normal[k] <=lower_threshold[k] */
    +  realT *upper_bound;     /* scale point[k] to new upper bound */
    +  realT *lower_bound;     /* scale point[k] to new lower bound
    +                             project if both upper_ and lower_bound == 0 */
    +
    +/*----------------------------------
    +
    +  qh precision constants
    +    precision constants for Qhull
    +
    +  notes:
    +    qh_detroundoff(qh) computes the maximum roundoff error for distance
    +    and other computations.  It also sets default values for the
    +    qh constants above.
    +*/
    +  realT ANGLEround;       /* max round off error for angles */
    +  realT centrum_radius;   /* max centrum radius for convexity (roundoff added) */
    +  realT cos_max;          /* max cosine for convexity (roundoff added) */
    +  realT DISTround;        /* max round off error for distances, 'E' overrides qh_distround() */
    +  realT MAXabs_coord;     /* max absolute coordinate */
    +  realT MAXlastcoord;     /* max last coordinate for qh_scalelast */
    +  realT MAXsumcoord;      /* max sum of coordinates */
    +  realT MAXwidth;         /* max rectilinear width of point coordinates */
    +  realT MINdenom_1;       /* min. abs. value for 1/x */
    +  realT MINdenom;         /*    use divzero if denominator < MINdenom */
    +  realT MINdenom_1_2;     /* min. abs. val for 1/x that allows normalization */
    +  realT MINdenom_2;       /*    use divzero if denominator < MINdenom_2 */
    +  realT MINlastcoord;     /* min. last coordinate for qh_scalelast */
    +  boolT NARROWhull;       /* set in qh_initialhull if angle < qh_MAXnarrow */
    +  realT *NEARzero;        /* hull_dim array for near zero in gausselim */
    +  realT NEARinside;       /* keep points for qh_check_maxout if close to facet */
    +  realT ONEmerge;         /* max distance for merging simplicial facets */
    +  realT outside_err;      /* application's epsilon for coplanar points
    +                             qh_check_bestdist() qh_check_points() reports error if point outside */
    +  realT WIDEfacet;        /* size of wide facet for skipping ridge in
    +                             area computation and locking centrum */
    +
    +/*----------------------------------
    +
    +  qh internal constants
    +    internal constants for Qhull
    +*/
    +  char qhull[sizeof("qhull")]; /* "qhull" for checking ownership while debugging */
    +  jmp_buf errexit;        /* exit label for qh_errexit, defined by setjmp() and NOerrexit */
    +  char jmpXtra[40];       /* extra bytes in case jmp_buf is defined wrong by compiler */
    +  jmp_buf restartexit;    /* restart label for qh_errexit, defined by setjmp() and ALLOWrestart */
    +  char jmpXtra2[40];      /* extra bytes in case jmp_buf is defined wrong by compiler*/
    +  FILE *fin;              /* pointer to input file, init by qh_initqhull_start */
    +  FILE *fout;             /* pointer to output file */
    +  FILE *ferr;             /* pointer to error file */
    +  pointT *interior_point; /* center point of the initial simplex*/
    +  int normal_size;     /* size in bytes for facet normals and point coords*/
    +  int center_size;     /* size in bytes for Voronoi centers */
    +  int   TEMPsize;         /* size for small, temporary sets (in quick mem) */
    +
    +/*----------------------------------
    +
    +  qh facet and vertex lists
    +    defines lists of facets, new facets, visible facets, vertices, and
    +    new vertices.  Includes counts, next ids, and trace ids.
    +  see:
    +    qh_resetlists()
    +*/
    +  facetT *facet_list;     /* first facet */
    +  facetT  *facet_tail;     /* end of facet_list (dummy facet) */
    +  facetT *facet_next;     /* next facet for buildhull()
    +                             previous facets do not have outside sets
    +                             NARROWhull: previous facets may have coplanar outside sets for qh_outcoplanar */
    +  facetT *newfacet_list;  /* list of new facets to end of facet_list */
    +  facetT *visible_list;   /* list of visible facets preceding newfacet_list,
    +                             facet->visible set */
    +  int       num_visible;  /* current number of visible facets */
    +  unsigned tracefacet_id;  /* set at init, then can print whenever */
    +  facetT *tracefacet;     /*   set in newfacet/mergefacet, undone in delfacet*/
    +  unsigned tracevertex_id;  /* set at buildtracing, can print whenever */
    +  vertexT *tracevertex;     /*   set in newvertex, undone in delvertex*/
    +  vertexT *vertex_list;     /* list of all vertices, to vertex_tail */
    +  vertexT  *vertex_tail;    /*      end of vertex_list (dummy vertex) */
    +  vertexT *newvertex_list; /* list of vertices in newfacet_list, to vertex_tail
    +                             all vertices have 'newlist' set */
    +  int   num_facets;       /* number of facets in facet_list
    +                             includes visible faces (num_visible) */
    +  int   num_vertices;     /* number of vertices in facet_list */
    +  int   num_outside;      /* number of points in outsidesets (for tracing and RANDOMoutside)
    +                               includes coplanar outsideset points for NARROWhull/qh_outcoplanar() */
    +  int   num_good;         /* number of good facets (after findgood_all) */
    +  unsigned facet_id;      /* ID of next, new facet from newfacet() */
    +  unsigned ridge_id;      /* ID of next, new ridge from newridge() */
    +  unsigned vertex_id;     /* ID of next, new vertex from newvertex() */
    +
    +/*----------------------------------
    +
    +  qh global variables
    +    defines minimum and maximum distances, next visit ids, several flags,
    +    and other global variables.
    +    initialize in qh_initbuild or qh_maxmin if used in qh_buildhull
    +*/
    +  unsigned long hulltime; /* ignore time to set up input and randomize */
    +                          /*   use unsigned to avoid wrap-around errors */
    +  boolT ALLOWrestart;     /* true if qh_precision can use qh.restartexit */
    +  int   build_cnt;        /* number of calls to qh_initbuild */
    +  qh_CENTER CENTERtype;   /* current type of facet->center, qh_CENTER */
    +  int   furthest_id;      /* pointid of furthest point, for tracing */
    +  facetT *GOODclosest;    /* closest facet to GOODthreshold in qh_findgood */
    +  boolT hasAreaVolume;    /* true if totarea, totvol was defined by qh_getarea */
    +  boolT hasTriangulation; /* true if triangulation created by qh_triangulate */
    +  realT JOGGLEmax;        /* set 'QJn' if randomly joggle input */
    +  boolT maxoutdone;       /* set qh_check_maxout(), cleared by qh_addpoint() */
    +  realT max_outside;      /* maximum distance from a point to a facet,
    +                               before roundoff, not simplicial vertices
    +                               actual outer plane is +DISTround and
    +                               computed outer plane is +2*DISTround */
    +  realT max_vertex;       /* maximum distance (>0) from vertex to a facet,
    +                               before roundoff, due to a merge */
    +  realT min_vertex;       /* minimum distance (<0) from vertex to a facet,
    +                               before roundoff, due to a merge
    +                               if qh.JOGGLEmax, qh_makenewplanes sets it
    +                               recomputed if qh.DOcheckmax, default -qh.DISTround */
    +  boolT NEWfacets;        /* true while visible facets invalid due to new or merge
    +                              from makecone/attachnewfacets to deletevisible */
    +  boolT findbestnew;      /* true if partitioning calls qh_findbestnew */
    +  boolT findbest_notsharp; /* true if new facets are at least 90 degrees */
    +  boolT NOerrexit;        /* true if qh.errexit is not available, cleared after setjmp */
    +  realT PRINTcradius;     /* radius for printing centrums */
    +  realT PRINTradius;      /* radius for printing vertex spheres and points */
    +  boolT POSTmerging;      /* true when post merging */
    +  int   printoutvar;      /* temporary variable for qh_printbegin, etc. */
    +  int   printoutnum;      /* number of facets printed */
    +  boolT QHULLfinished;    /* True after qhull() is finished */
    +  realT totarea;          /* 'FA': total facet area computed by qh_getarea, hasAreaVolume */
    +  realT totvol;           /* 'FA': total volume computed by qh_getarea, hasAreaVolume */
    +  unsigned int visit_id;  /* unique ID for searching neighborhoods, */
    +  unsigned int vertex_visit; /* unique ID for searching vertices, reset with qh_buildtracing */
    +  boolT ZEROall_ok;       /* True if qh_checkzero always succeeds */
    +  boolT WAScoplanar;      /* True if qh_partitioncoplanar (qhT *qh, qh_check_maxout) */
    +
    +/*----------------------------------
    +
    +  qh global sets
    +    defines sets for merging, initial simplex, hashing, extra input points,
    +    and deleted vertices
    +*/
    +  setT *facet_mergeset;   /* temporary set of merges to be done */
    +  setT *degen_mergeset;   /* temporary set of degenerate and redundant merges */
    +  setT *hash_table;       /* hash table for matching ridges in qh_matchfacets
    +                             size is setsize() */
    +  setT *other_points;     /* additional points */
    +  setT *del_vertices;     /* vertices to partition and delete with visible
    +                             facets.  Have deleted set for checkfacet */
    +
    +/*----------------------------------
    +
    +  qh global buffers
    +    defines buffers for maxtrix operations, input, and error messages
    +*/
    +  coordT *gm_matrix;      /* (dim+1)Xdim matrix for geom_r.c */
    +  coordT **gm_row;        /* array of gm_matrix rows */
    +  char* line;             /* malloc'd input line of maxline+1 chars */
    +  int maxline;
    +  coordT *half_space;     /* malloc'd input array for halfspace (qh.normal_size+coordT) */
    +  coordT *temp_malloc;    /* malloc'd input array for points */
    +
    +/*----------------------------------
    +
    +  qh static variables
    +    defines static variables for individual functions
    +
    +  notes:
    +    do not use 'static' within a function.  Multiple instances of qhull
    +    may exist.
    +
    +    do not assume zero initialization, 'QPn' may cause a restart
    +*/
    +  boolT ERREXITcalled;    /* true during qh_errexit (qhT *qh, prevents duplicate calls */
    +  boolT firstcentrum;     /* for qh_printcentrum */
    +  boolT old_randomdist;   /* save RANDOMdist flag during io, tracing, or statistics */
    +  setT *coplanarfacetset;  /* set of coplanar facets for searching qh_findbesthorizon() */
    +  realT last_low;         /* qh_scalelast parameters for qh_setdelaunay */
    +  realT last_high;
    +  realT last_newhigh;
    +  unsigned lastreport;    /* for qh_buildtracing */
    +  int mergereport;        /* for qh_tracemerging */
    +  setT *old_tempstack;    /* for saving qh->qhmem.tempstack in save_qhull */
    +  int   ridgeoutnum;      /* number of ridges for 4OFF output (qh_printbegin,etc) */
    +
    +/*----------------------------------
    +
    +  qh memory management, rbox globals, and statistics
    +
    +  Replaces global data structures defined for libqhull
    +*/
    +  int     last_random;    /* Last random number from qh_rand (random_r.c) */
    +  jmp_buf rbox_errexit;   /* errexit from rboxlib_r.c, defined by qh_rboxpoints() only */
    +  char    jmpXtra3[40];   /* extra bytes in case jmp_buf is defined wrong by compiler */
    +  int     rbox_isinteger;
    +  double  rbox_out_offset;
    +  void *  cpp_object;     /* C++ pointer.  Currently used by RboxPoints.qh_fprintf_rbox */
    +
    +  /* Last, otherwise zero'd by qh_initqhull_start2 (global_r.c */
    +  qhmemT  qhmem;          /* Qhull managed memory (mem_r.h) */
    +  /* After qhmem because its size depends on the number of statistics */
    +  qhstatT qhstat;         /* Qhull statistics (stat_r.h) */
    +};
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  otherfacet_(ridge, facet)
    +    return neighboring facet for a ridge in facet
    +*/
    +#define otherfacet_(ridge, facet) \
    +                        (((ridge)->top == (facet)) ? (ridge)->bottom : (ridge)->top)
    +
    +/*----------------------------------
    +
    +  getid_(p)
    +    return int ID for facet, ridge, or vertex
    +    return qh_IDunknown(-1) if NULL
    +*/
    +#define getid_(p)       ((p) ? (int)((p)->id) : qh_IDunknown)
    +
    +/*============== FORALL macros ===================*/
    +
    +/*----------------------------------
    +
    +  FORALLfacets { ... }
    +    assign 'facet' to each facet in qh.facet_list
    +
    +  notes:
    +    uses 'facetT *facet;'
    +    assumes last facet is a sentinel
    +    assumes qh defined
    +
    +  see:
    +    FORALLfacet_( facetlist )
    +*/
    +#define FORALLfacets for (facet=qh->facet_list;facet && facet->next;facet=facet->next)
    +
    +/*----------------------------------
    +
    +  FORALLpoints { ... }
    +    assign 'point' to each point in qh.first_point, qh.num_points
    +
    +  notes:
    +    assumes qh defined
    +
    +  declare:
    +    coordT *point, *pointtemp;
    +*/
    +#define FORALLpoints FORALLpoint_(qh, qh->first_point, qh->num_points)
    +
    +/*----------------------------------
    +
    +  FORALLpoint_( qh, points, num) { ... }
    +    assign 'point' to each point in points array of num points
    +
    +  declare:
    +    coordT *point, *pointtemp;
    +*/
    +#define FORALLpoint_(qh, points, num) for (point= (points), \
    +      pointtemp= (points)+qh->hull_dim*(num); point < pointtemp; point += qh->hull_dim)
    +
    +/*----------------------------------
    +
    +  FORALLvertices { ... }
    +    assign 'vertex' to each vertex in qh.vertex_list
    +
    +  declare:
    +    vertexT *vertex;
    +
    +  notes:
    +    assumes qh.vertex_list terminated with a sentinel
    +    assumes qh defined
    +*/
    +#define FORALLvertices for (vertex=qh->vertex_list;vertex && vertex->next;vertex= vertex->next)
    +
    +/*----------------------------------
    +
    +  FOREACHfacet_( facets ) { ... }
    +    assign 'facet' to each facet in facets
    +
    +  declare:
    +    facetT *facet, **facetp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHfacet_(facets)    FOREACHsetelement_(facetT, facets, facet)
    +
    +/*----------------------------------
    +
    +  FOREACHneighbor_( facet ) { ... }
    +    assign 'neighbor' to each neighbor in facet->neighbors
    +
    +  FOREACHneighbor_( vertex ) { ... }
    +    assign 'neighbor' to each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighbor, **neighborp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHneighbor_(facet)  FOREACHsetelement_(facetT, facet->neighbors, neighbor)
    +
    +/*----------------------------------
    +
    +  FOREACHpoint_( points ) { ... }
    +    assign 'point' to each point in points set
    +
    +  declare:
    +    pointT *point, **pointp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHpoint_(points)    FOREACHsetelement_(pointT, points, point)
    +
    +/*----------------------------------
    +
    +  FOREACHridge_( ridges ) { ... }
    +    assign 'ridge' to each ridge in ridges set
    +
    +  declare:
    +    ridgeT *ridge, **ridgep;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHridge_(ridges)    FOREACHsetelement_(ridgeT, ridges, ridge)
    +
    +/*----------------------------------
    +
    +  FOREACHvertex_( vertices ) { ... }
    +    assign 'vertex' to each vertex in vertices set
    +
    +  declare:
    +    vertexT *vertex, **vertexp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHvertex_(vertices) FOREACHsetelement_(vertexT, vertices,vertex)
    +
    +/*----------------------------------
    +
    +  FOREACHfacet_i_( qh, facets ) { ... }
    +    assign 'facet' and 'facet_i' for each facet in facets set
    +
    +  declare:
    +    facetT *facet;
    +    int     facet_n, facet_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHfacet_i_(qh, facets)    FOREACHsetelement_i_(qh, facetT, facets, facet)
    +
    +/*----------------------------------
    +
    +  FOREACHneighbor_i_( qh, facet ) { ... }
    +    assign 'neighbor' and 'neighbor_i' for each neighbor in facet->neighbors
    +
    +  FOREACHneighbor_i_( qh, vertex ) { ... }
    +    assign 'neighbor' and 'neighbor_i' for each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighbor;
    +    int     neighbor_n, neighbor_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHneighbor_i_(qh, facet)  FOREACHsetelement_i_(qh, facetT, facet->neighbors, neighbor)
    +
    +/*----------------------------------
    +
    +  FOREACHpoint_i_( qh, points ) { ... }
    +    assign 'point' and 'point_i' for each point in points set
    +
    +  declare:
    +    pointT *point;
    +    int     point_n, point_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHpoint_i_(qh, points)    FOREACHsetelement_i_(qh, pointT, points, point)
    +
    +/*----------------------------------
    +
    +  FOREACHridge_i_( qh, ridges ) { ... }
    +    assign 'ridge' and 'ridge_i' for each ridge in ridges set
    +
    +  declare:
    +    ridgeT *ridge;
    +    int     ridge_n, ridge_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHridge_i_(qh, ridges)    FOREACHsetelement_i_(qh, ridgeT, ridges, ridge)
    +
    +/*----------------------------------
    +
    +  FOREACHvertex_i_( qh, vertices ) { ... }
    +    assign 'vertex' and 'vertex_i' for each vertex in vertices set
    +
    +  declare:
    +    vertexT *vertex;
    +    int     vertex_n, vertex_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHvertex_i_(qh, vertices) FOREACHsetelement_i_(qh, vertexT, vertices,vertex)
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +/********* -libqhull_r.c prototypes (duplicated from qhull_ra.h) **********************/
    +
    +void    qh_qhull(qhT *qh);
    +boolT   qh_addpoint(qhT *qh, pointT *furthest, facetT *facet, boolT checkdist);
    +void    qh_printsummary(qhT *qh, FILE *fp);
    +
    +/********* -user.c prototypes (alphabetical) **********************/
    +
    +void    qh_errexit(qhT *qh, int exitcode, facetT *facet, ridgeT *ridge);
    +void    qh_errprint(qhT *qh, const char* string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex);
    +int     qh_new_qhull(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc,
    +                char *qhull_cmd, FILE *outfile, FILE *errfile);
    +void    qh_printfacetlist(qhT *qh, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printhelp_degenerate(qhT *qh, FILE *fp);
    +void    qh_printhelp_narrowhull(qhT *qh, FILE *fp, realT minangle);
    +void    qh_printhelp_singular(qhT *qh, FILE *fp);
    +void    qh_user_memsizes(qhT *qh);
    +
    +/********* -usermem_r.c prototypes (alphabetical) **********************/
    +void    qh_exit(int exitcode);
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +
    +/********* -userprintf_r.c and userprintf_rbox_r.c prototypes **********************/
    +void    qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +
    +/***** -geom_r.c/geom2_r.c/random_r.c prototypes (duplicated from geom_r.h, random_r.h) ****************/
    +
    +facetT *qh_findbest(qhT *qh, pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT newfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart);
    +facetT *qh_findbestnew(qhT *qh, pointT *point, facetT *startfacet,
    +                     realT *dist, boolT bestoutside, boolT *isoutside, int *numpart);
    +boolT   qh_gram_schmidt(qhT *qh, int dim, realT **rows);
    +void    qh_outerinner(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane);
    +void    qh_printsummary(qhT *qh, FILE *fp);
    +void    qh_projectinput(qhT *qh);
    +void    qh_randommatrix(qhT *qh, realT *buffer, int dim, realT **row);
    +void    qh_rotateinput(qhT *qh, realT **rows);
    +void    qh_scaleinput(qhT *qh);
    +void    qh_setdelaunay(qhT *qh, int dim, int count, pointT *points);
    +coordT  *qh_sethalfspace_all(qhT *qh, int dim, int count, coordT *halfspaces, pointT *feasible);
    +
    +/***** -global_r.c prototypes (alphabetical) ***********************/
    +
    +unsigned long qh_clock(qhT *qh);
    +void    qh_checkflags(qhT *qh, char *command, char *hiddenflags);
    +void    qh_clear_outputflags(qhT *qh);
    +void    qh_freebuffers(qhT *qh);
    +void    qh_freeqhull(qhT *qh, boolT allmem);
    +void    qh_init_A(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile, int argc, char *argv[]);
    +void    qh_init_B(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_init_qhull_command(qhT *qh, int argc, char *argv[]);
    +void    qh_initbuffers(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_initflags(qhT *qh, char *command);
    +void    qh_initqhull_buffers(qhT *qh);
    +void    qh_initqhull_globals(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_initqhull_mem(qhT *qh);
    +void    qh_initqhull_outputflags(qhT *qh);
    +void    qh_initqhull_start(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile);
    +void    qh_initqhull_start2(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile);
    +void    qh_initthresholds(qhT *qh, char *command);
    +void    qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeTsize, int facetTsize, int setTsize, int qhmemTsize);
    +void    qh_option(qhT *qh, const char *option, int *i, realT *r);
    +void    qh_zero(qhT *qh, FILE *errfile);
    +
    +/***** -io_r.c prototypes (duplicated from io_r.h) ***********************/
    +
    +void    qh_dfacet(qhT *qh, unsigned id);
    +void    qh_dvertex(qhT *qh, unsigned id);
    +void    qh_printneighborhood(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall);
    +void    qh_produce_output(qhT *qh);
    +coordT *qh_readpoints(qhT *qh, int *numpoints, int *dimension, boolT *ismalloc);
    +
    +
    +/********* -mem_r.c prototypes (duplicated from mem_r.h) **********************/
    +
    +void qh_meminit(qhT *qh, FILE *ferr);
    +void qh_memfreeshort(qhT *qh, int *curlong, int *totlong);
    +
    +/********* -poly_r.c/poly2_r.c prototypes (duplicated from poly_r.h) **********************/
    +
    +void    qh_check_output(qhT *qh);
    +void    qh_check_points(qhT *qh);
    +setT   *qh_facetvertices(qhT *qh, facetT *facetlist, setT *facets, boolT allfacets);
    +facetT *qh_findbestfacet(qhT *qh, pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside);
    +vertexT *qh_nearvertex(qhT *qh, facetT *facet, pointT *point, realT *bestdistp);
    +pointT *qh_point(qhT *qh, int id);
    +setT   *qh_pointfacet(qhT *qh /*qh.facet_list*/);
    +int     qh_pointid(qhT *qh, pointT *point);
    +setT   *qh_pointvertex(qhT *qh /*qh.facet_list*/);
    +void    qh_setvoronoi_all(qhT *qh);
    +void    qh_triangulate(qhT *qh /*qh.facet_list*/);
    +
    +/********* -rboxpoints_r.c prototypes **********************/
    +int     qh_rboxpoints(qhT *qh, char* rbox_command);
    +void    qh_errexit_rbox(qhT *qh, int exitcode);
    +
    +/********* -stat_r.c prototypes (duplicated from stat_r.h) **********************/
    +
    +void    qh_collectstatistics(qhT *qh);
    +void    qh_printallstatistics(qhT *qh, FILE *fp, const char *string);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFlibqhull */
    diff --git a/xs/src/qhull/src/libqhull_r/libqhull_r.pro b/xs/src/qhull/src/libqhull_r/libqhull_r.pro
    new file mode 100644
    index 0000000000..6b8db44b75
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/libqhull_r.pro
    @@ -0,0 +1,67 @@
    +# -------------------------------------------------
    +# libqhull_r.pro -- Qt project for Qhull shared library
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +DESTDIR = ../../lib
    +DLLDESTDIR = ../../bin
    +TEMPLATE = lib
    +CONFIG += shared warn_on
    +CONFIG -= qt
    +
    +build_pass:CONFIG(debug, debug|release):{
    +    TARGET = qhull_rd
    +    OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +    TARGET = qhull_r
    +    OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +win32-msvc* : DEF_FILE += ../../src/libqhull_r/qhull_r-exports.def
    +
    +# libqhull_r/libqhull_r.pro and ../qhull-libqhull-src_r.pri have the same SOURCES and HEADERS
    +
    +SOURCES += ../libqhull_r/global_r.c
    +SOURCES += ../libqhull_r/stat_r.c
    +SOURCES += ../libqhull_r/geom2_r.c
    +SOURCES += ../libqhull_r/poly2_r.c
    +SOURCES += ../libqhull_r/merge_r.c
    +SOURCES += ../libqhull_r/libqhull_r.c
    +SOURCES += ../libqhull_r/geom_r.c
    +SOURCES += ../libqhull_r/poly_r.c
    +SOURCES += ../libqhull_r/qset_r.c
    +SOURCES += ../libqhull_r/mem_r.c
    +SOURCES += ../libqhull_r/random_r.c
    +SOURCES += ../libqhull_r/usermem_r.c
    +SOURCES += ../libqhull_r/userprintf_r.c
    +SOURCES += ../libqhull_r/io_r.c
    +SOURCES += ../libqhull_r/user_r.c
    +SOURCES += ../libqhull_r/rboxlib_r.c
    +SOURCES += ../libqhull_r/userprintf_rbox_r.c
    +
    +HEADERS += ../libqhull_r/geom_r.h
    +HEADERS += ../libqhull_r/io_r.h
    +HEADERS += ../libqhull_r/libqhull_r.h
    +HEADERS += ../libqhull_r/mem_r.h
    +HEADERS += ../libqhull_r/merge_r.h
    +HEADERS += ../libqhull_r/poly_r.h
    +HEADERS += ../libqhull_r/random_r.h
    +HEADERS += ../libqhull_r/qhull_ra.h
    +HEADERS += ../libqhull_r/qset_r.h
    +HEADERS += ../libqhull_r/stat_r.h
    +HEADERS += ../libqhull_r/user_r.h
    +
    +OTHER_FILES += qh-geom_r.htm
    +OTHER_FILES += qh-globa_r.htm
    +OTHER_FILES += qh-io_r.htm
    +OTHER_FILES += qh-mem_r.htm
    +OTHER_FILES += qh-merge_r.htm
    +OTHER_FILES += qh-poly_r.htm
    +OTHER_FILES += qh-qhull_r.htm
    +OTHER_FILES += qh-set_r.htm
    +OTHER_FILES += qh-stat_r.htm
    +OTHER_FILES += qh-user_r.htm
    diff --git a/xs/src/qhull/src/libqhull_r/mem_r.c b/xs/src/qhull/src/libqhull_r/mem_r.c
    new file mode 100644
    index 0000000000..801a8c76a1
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/mem_r.c
    @@ -0,0 +1,562 @@
    +/*
      ---------------------------------
    +
    +  mem_r.c
    +    memory management routines for qhull
    +
    +  See libqhull/mem_r.c for a standalone program.
    +
    +  To initialize memory:
    +
    +    qh_meminit(qh, stderr);
    +    qh_meminitbuffers(qh, qh->IStracing, qh_MEMalign, 7, qh_MEMbufsize,qh_MEMinitbuf);
    +    qh_memsize(qh, (int)sizeof(facetT));
    +    qh_memsize(qh, (int)sizeof(facetT));
    +    ...
    +    qh_memsetup(qh);
    +
    +  To free up all memory buffers:
    +    qh_memfreeshort(qh, &curlong, &totlong);
    +
    +  if qh_NOmem,
    +    malloc/free is used instead of mem.c
    +
    +  notes:
    +    uses Quickfit algorithm (freelists for commonly allocated sizes)
    +    assumes small sizes for freelists (it discards the tail of memory buffers)
    +
    +  see:
    +    qh-mem_r.htm and mem_r.h
    +    global_r.c (qh_initbuffers) for an example of using mem_r.c
    +
    +  Copyright (c) 1993-2015 The Geometry Center.
    +  $Id: //main/2015/qhull/src/libqhull_r/mem_r.c#5 $$Change: 2065 $
    +  $DateTime: 2016/01/18 13:51:04 $$Author: bbarber $
    +*/
    +
    +#include "libqhull_r.h"  /* includes user_r.h and mem_r.h */
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifndef qh_NOmem
    +
    +/*============= internal functions ==============*/
    +
    +static int qh_intcompare(const void *i, const void *j);
    +
    +/*========== functions in alphabetical order ======== */
    +
    +/*---------------------------------
    +
    +  qh_intcompare( i, j )
    +    used by qsort and bsearch to compare two integers
    +*/
    +static int qh_intcompare(const void *i, const void *j) {
    +  return(*((const int *)i) - *((const int *)j));
    +} /* intcompare */
    +
    +
    +/*----------------------------------
    +
    +  qh_memalloc( qh, insize )
    +    returns object of insize bytes
    +    qhmem is the global memory structure
    +
    +  returns:
    +    pointer to allocated memory
    +    errors if insufficient memory
    +
    +  notes:
    +    use explicit type conversion to avoid type warnings on some compilers
    +    actual object may be larger than insize
    +    use qh_memalloc_() for inline code for quick allocations
    +    logs allocations if 'T5'
    +    caller is responsible for freeing the memory.
    +    short memory is freed on shutdown by qh_memfreeshort unless qh_NOmem
    +
    +  design:
    +    if size < qh->qhmem.LASTsize
    +      if qh->qhmem.freelists[size] non-empty
    +        return first object on freelist
    +      else
    +        round up request to size of qh->qhmem.freelists[size]
    +        allocate new allocation buffer if necessary
    +        allocate object from allocation buffer
    +    else
    +      allocate object with qh_malloc() in user.c
    +*/
    +void *qh_memalloc(qhT *qh, int insize) {
    +  void **freelistp, *newbuffer;
    +  int idx, size, n;
    +  int outsize, bufsize;
    +  void *object;
    +
    +  if (insize<0) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6235, "qhull error (qh_memalloc): negative request size (%d).  Did int overflow due to high-D?\n", insize); /* WARN64 */
    +      qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (insize>=0 && insize <= qh->qhmem.LASTsize) {
    +    idx= qh->qhmem.indextable[insize];
    +    outsize= qh->qhmem.sizetable[idx];
    +    qh->qhmem.totshort += outsize;
    +    freelistp= qh->qhmem.freelists+idx;
    +    if ((object= *freelistp)) {
    +      qh->qhmem.cntquick++;
    +      qh->qhmem.totfree -= outsize;
    +      *freelistp= *((void **)*freelistp);  /* replace freelist with next object */
    +#ifdef qh_TRACEshort
    +      n= qh->qhmem.cntshort+qh->qhmem.cntquick+qh->qhmem.freeshort;
    +      if (qh->qhmem.IStracing >= 5)
    +          qh_fprintf(qh, qh->qhmem.ferr, 8141, "qh_mem %p n %8d alloc quick: %d bytes (tot %d cnt %d)\n", object, n, outsize, qh->qhmem.totshort, qh->qhmem.cntshort+qh->qhmem.cntquick-qh->qhmem.freeshort);
    +#endif
    +      return(object);
    +    }else {
    +      qh->qhmem.cntshort++;
    +      if (outsize > qh->qhmem.freesize) {
    +        qh->qhmem.totdropped += qh->qhmem.freesize;
    +        if (!qh->qhmem.curbuffer)
    +          bufsize= qh->qhmem.BUFinit;
    +        else
    +          bufsize= qh->qhmem.BUFsize;
    +        if (!(newbuffer= qh_malloc((size_t)bufsize))) {
    +          qh_fprintf(qh, qh->qhmem.ferr, 6080, "qhull error (qh_memalloc): insufficient memory to allocate short memory buffer (%d bytes)\n", bufsize);
    +          qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +        }
    +        *((void **)newbuffer)= qh->qhmem.curbuffer;  /* prepend newbuffer to curbuffer
    +                                                    list.  newbuffer!=0 by QH6080 */
    +        qh->qhmem.curbuffer= newbuffer;
    +        size= (sizeof(void **) + qh->qhmem.ALIGNmask) & ~qh->qhmem.ALIGNmask;
    +        qh->qhmem.freemem= (void *)((char *)newbuffer+size);
    +        qh->qhmem.freesize= bufsize - size;
    +        qh->qhmem.totbuffer += bufsize - size; /* easier to check */
    +        /* Periodically test totbuffer.  It matches at beginning and exit of every call */
    +        n = qh->qhmem.totshort + qh->qhmem.totfree + qh->qhmem.totdropped + qh->qhmem.freesize - outsize;
    +        if (qh->qhmem.totbuffer != n) {
    +            qh_fprintf(qh, qh->qhmem.ferr, 6212, "qh_memalloc internal error: short totbuffer %d != totshort+totfree... %d\n", qh->qhmem.totbuffer, n);
    +            qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +        }
    +      }
    +      object= qh->qhmem.freemem;
    +      qh->qhmem.freemem= (void *)((char *)qh->qhmem.freemem + outsize);
    +      qh->qhmem.freesize -= outsize;
    +      qh->qhmem.totunused += outsize - insize;
    +#ifdef qh_TRACEshort
    +      n= qh->qhmem.cntshort+qh->qhmem.cntquick+qh->qhmem.freeshort;
    +      if (qh->qhmem.IStracing >= 5)
    +          qh_fprintf(qh, qh->qhmem.ferr, 8140, "qh_mem %p n %8d alloc short: %d bytes (tot %d cnt %d)\n", object, n, outsize, qh->qhmem.totshort, qh->qhmem.cntshort+qh->qhmem.cntquick-qh->qhmem.freeshort);
    +#endif
    +      return object;
    +    }
    +  }else {                     /* long allocation */
    +    if (!qh->qhmem.indextable) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6081, "qhull internal error (qh_memalloc): qhmem has not been initialized.\n");
    +      qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +    }
    +    outsize= insize;
    +    qh->qhmem.cntlong++;
    +    qh->qhmem.totlong += outsize;
    +    if (qh->qhmem.maxlong < qh->qhmem.totlong)
    +      qh->qhmem.maxlong= qh->qhmem.totlong;
    +    if (!(object= qh_malloc((size_t)outsize))) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6082, "qhull error (qh_memalloc): insufficient memory to allocate %d bytes\n", outsize);
    +      qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +    }
    +    if (qh->qhmem.IStracing >= 5)
    +      qh_fprintf(qh, qh->qhmem.ferr, 8057, "qh_mem %p n %8d alloc long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, outsize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
    +  }
    +  return(object);
    +} /* memalloc */
    +
    +
    +/*----------------------------------
    +
    +  qh_memcheck(qh)
    +*/
    +void qh_memcheck(qhT *qh) {
    +  int i, count, totfree= 0;
    +  void *object;
    +
    +  if (!qh) {
    +    qh_fprintf_stderr(6243, "qh_memcheck(qh) error: qh is 0.  It does not point to a qhT");
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (qh->qhmem.ferr == 0 || qh->qhmem.IStracing < 0 || qh->qhmem.IStracing > 10 || (((qh->qhmem.ALIGNmask+1) & qh->qhmem.ALIGNmask) != 0)) {
    +    qh_fprintf_stderr(6244, "qh_memcheck error: either qh->qhmem is overwritten or qh->qhmem is not initialized.  Call qh_mem_new() or qh_new_qhull() before calling qh_mem routines.  ferr 0x%x IsTracing %d ALIGNmask 0x%x", qh->qhmem.ferr, qh->qhmem.IStracing, qh->qhmem.ALIGNmask);
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (qh->qhmem.IStracing != 0)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8143, "qh_memcheck: check size of freelists on qh->qhmem\nqh_memcheck: A segmentation fault indicates an overwrite of qh->qhmem\n");
    +  for (i=0; i < qh->qhmem.TABLEsize; i++) {
    +    count=0;
    +    for (object= qh->qhmem.freelists[i]; object; object= *((void **)object))
    +      count++;
    +    totfree += qh->qhmem.sizetable[i] * count;
    +  }
    +  if (totfree != qh->qhmem.totfree) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6211, "Qhull internal error (qh_memcheck): totfree %d not equal to freelist total %d\n", qh->qhmem.totfree, totfree);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  if (qh->qhmem.IStracing != 0)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8144, "qh_memcheck: total size of freelists totfree is the same as qh->qhmem.totfree\n", totfree);
    +} /* memcheck */
    +
    +/*----------------------------------
    +
    +  qh_memfree(qh, object, insize )
    +    free up an object of size bytes
    +    size is insize from qh_memalloc
    +
    +  notes:
    +    object may be NULL
    +    type checking warns if using (void **)object
    +    use qh_memfree_() for quick free's of small objects
    +
    +  design:
    +    if size <= qh->qhmem.LASTsize
    +      append object to corresponding freelist
    +    else
    +      call qh_free(object)
    +*/
    +void qh_memfree(qhT *qh, void *object, int insize) {
    +  void **freelistp;
    +  int idx, outsize;
    +
    +  if (!object)
    +    return;
    +  if (insize <= qh->qhmem.LASTsize) {
    +    qh->qhmem.freeshort++;
    +    idx= qh->qhmem.indextable[insize];
    +    outsize= qh->qhmem.sizetable[idx];
    +    qh->qhmem.totfree += outsize;
    +    qh->qhmem.totshort -= outsize;
    +    freelistp= qh->qhmem.freelists + idx;
    +    *((void **)object)= *freelistp;
    +    *freelistp= object;
    +#ifdef qh_TRACEshort
    +    idx= qh->qhmem.cntshort+qh->qhmem.cntquick+qh->qhmem.freeshort;
    +    if (qh->qhmem.IStracing >= 5)
    +        qh_fprintf(qh, qh->qhmem.ferr, 8142, "qh_mem %p n %8d free short: %d bytes (tot %d cnt %d)\n", object, idx, outsize, qh->qhmem.totshort, qh->qhmem.cntshort+qh->qhmem.cntquick-qh->qhmem.freeshort);
    +#endif
    +  }else {
    +    qh->qhmem.freelong++;
    +    qh->qhmem.totlong -= insize;
    +    if (qh->qhmem.IStracing >= 5)
    +      qh_fprintf(qh, qh->qhmem.ferr, 8058, "qh_mem %p n %8d free long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, insize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
    +    qh_free(object);
    +  }
    +} /* memfree */
    +
    +
    +/*---------------------------------
    +
    +  qh_memfreeshort(qh, curlong, totlong )
    +    frees up all short and qhmem memory allocations
    +
    +  returns:
    +    number and size of current long allocations
    +  
    +  notes:
    +    if qh_NOmem (qh_malloc() for all allocations), 
    +       short objects (e.g., facetT) are not recovered.
    +       use qh_freeqhull(qh, qh_ALL) instead.
    + 
    +  see:
    +    qh_freeqhull(qh, allMem)
    +    qh_memtotal(qh, curlong, totlong, curshort, totshort, maxlong, totbuffer);
    +*/
    +void qh_memfreeshort(qhT *qh, int *curlong, int *totlong) {
    +  void *buffer, *nextbuffer;
    +  FILE *ferr;
    +
    +  *curlong= qh->qhmem.cntlong - qh->qhmem.freelong;
    +  *totlong= qh->qhmem.totlong;
    +  for (buffer= qh->qhmem.curbuffer; buffer; buffer= nextbuffer) {
    +    nextbuffer= *((void **) buffer);
    +    qh_free(buffer);
    +  }
    +  qh->qhmem.curbuffer= NULL;
    +  if (qh->qhmem.LASTsize) {
    +    qh_free(qh->qhmem.indextable);
    +    qh_free(qh->qhmem.freelists);
    +    qh_free(qh->qhmem.sizetable);
    +  }
    +  ferr= qh->qhmem.ferr;
    +  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
    +  qh->qhmem.ferr= ferr;
    +} /* memfreeshort */
    +
    +
    +/*----------------------------------
    +
    +  qh_meminit(qh, ferr )
    +    initialize qhmem and test sizeof( void*)
    +    Does not throw errors.  qh_exit on failure
    +*/
    +void qh_meminit(qhT *qh, FILE *ferr) {
    +
    +  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
    +  if (ferr)
    +      qh->qhmem.ferr= ferr;
    +  else
    +      qh->qhmem.ferr= stderr;
    +  if (sizeof(void*) < sizeof(int)) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6083, "qhull internal error (qh_meminit): sizeof(void*) %d < sizeof(int) %d.  qset.c will not work\n", (int)sizeof(void*), (int)sizeof(int));
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (sizeof(void*) > sizeof(ptr_intT)) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6084, "qhull internal error (qh_meminit): sizeof(void*) %d > sizeof(ptr_intT) %d. Change ptr_intT in mem.h to 'long long'\n", (int)sizeof(void*), (int)sizeof(ptr_intT));
    +      qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  qh_memcheck(qh);
    +} /* meminit */
    +
    +/*---------------------------------
    +
    +  qh_meminitbuffers(qh, tracelevel, alignment, numsizes, bufsize, bufinit )
    +    initialize qhmem
    +    if tracelevel >= 5, trace memory allocations
    +    alignment= desired address alignment for memory allocations
    +    numsizes= number of freelists
    +    bufsize=  size of additional memory buffers for short allocations
    +    bufinit=  size of initial memory buffer for short allocations
    +*/
    +void qh_meminitbuffers(qhT *qh, int tracelevel, int alignment, int numsizes, int bufsize, int bufinit) {
    +
    +  qh->qhmem.IStracing= tracelevel;
    +  qh->qhmem.NUMsizes= numsizes;
    +  qh->qhmem.BUFsize= bufsize;
    +  qh->qhmem.BUFinit= bufinit;
    +  qh->qhmem.ALIGNmask= alignment-1;
    +  if (qh->qhmem.ALIGNmask & ~qh->qhmem.ALIGNmask) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6085, "qhull internal error (qh_meminit): memory alignment %d is not a power of 2\n", alignment);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh->qhmem.sizetable= (int *) calloc((size_t)numsizes, sizeof(int));
    +  qh->qhmem.freelists= (void **) calloc((size_t)numsizes, sizeof(void *));
    +  if (!qh->qhmem.sizetable || !qh->qhmem.freelists) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6086, "qhull error (qh_meminit): insufficient memory\n");
    +    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (qh->qhmem.IStracing >= 1)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8059, "qh_meminitbuffers: memory initialized with alignment %d\n", alignment);
    +} /* meminitbuffers */
    +
    +/*---------------------------------
    +
    +  qh_memsetup(qh)
    +    set up memory after running memsize()
    +*/
    +void qh_memsetup(qhT *qh) {
    +  int k,i;
    +
    +  qsort(qh->qhmem.sizetable, (size_t)qh->qhmem.TABLEsize, sizeof(int), qh_intcompare);
    +  qh->qhmem.LASTsize= qh->qhmem.sizetable[qh->qhmem.TABLEsize-1];
    +  if(qh->qhmem.LASTsize >= qh->qhmem.BUFsize || qh->qhmem.LASTsize >= qh->qhmem.BUFinit) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6087, "qhull error (qh_memsetup): largest mem size %d is >= buffer size %d or initial buffer size %d\n",
    +            qh->qhmem.LASTsize, qh->qhmem.BUFsize, qh->qhmem.BUFinit);
    +    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (!(qh->qhmem.indextable= (int *)qh_malloc((qh->qhmem.LASTsize+1) * sizeof(int)))) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6088, "qhull error (qh_memsetup): insufficient memory\n");
    +    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  for (k=qh->qhmem.LASTsize+1; k--; )
    +    qh->qhmem.indextable[k]= k;
    +  i= 0;
    +  for (k=0; k <= qh->qhmem.LASTsize; k++) {
    +    if (qh->qhmem.indextable[k] <= qh->qhmem.sizetable[i])
    +      qh->qhmem.indextable[k]= i;
    +    else
    +      qh->qhmem.indextable[k]= ++i;
    +  }
    +} /* memsetup */
    +
    +/*---------------------------------
    +
    +  qh_memsize(qh, size )
    +    define a free list for this size
    +*/
    +void qh_memsize(qhT *qh, int size) {
    +  int k;
    +
    +  if(qh->qhmem.LASTsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6089, "qhull error (qh_memsize): called after qhmem_setup\n");
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  size= (size + qh->qhmem.ALIGNmask) & ~qh->qhmem.ALIGNmask;
    +  for (k=qh->qhmem.TABLEsize; k--; ) {
    +    if (qh->qhmem.sizetable[k] == size)
    +      return;
    +  }
    +  if (qh->qhmem.TABLEsize < qh->qhmem.NUMsizes)
    +    qh->qhmem.sizetable[qh->qhmem.TABLEsize++]= size;
    +  else
    +    qh_fprintf(qh, qh->qhmem.ferr, 7060, "qhull warning (memsize): free list table has room for only %d sizes\n", qh->qhmem.NUMsizes);
    +} /* memsize */
    +
    +
    +/*---------------------------------
    +
    +  qh_memstatistics(qh, fp )
    +    print out memory statistics
    +
    +    Verifies that qh->qhmem.totfree == sum of freelists
    +*/
    +void qh_memstatistics(qhT *qh, FILE *fp) {
    +  int i;
    +  int count;
    +  void *object;
    +
    +  qh_memcheck(qh);
    +  qh_fprintf(qh, fp, 9278, "\nmemory statistics:\n\
    +%7d quick allocations\n\
    +%7d short allocations\n\
    +%7d long allocations\n\
    +%7d short frees\n\
    +%7d long frees\n\
    +%7d bytes of short memory in use\n\
    +%7d bytes of short memory in freelists\n\
    +%7d bytes of dropped short memory\n\
    +%7d bytes of unused short memory (estimated)\n\
    +%7d bytes of long memory allocated (max, except for input)\n\
    +%7d bytes of long memory in use (in %d pieces)\n\
    +%7d bytes of short memory buffers (minus links)\n\
    +%7d bytes per short memory buffer (initially %d bytes)\n",
    +           qh->qhmem.cntquick, qh->qhmem.cntshort, qh->qhmem.cntlong,
    +           qh->qhmem.freeshort, qh->qhmem.freelong,
    +           qh->qhmem.totshort, qh->qhmem.totfree,
    +           qh->qhmem.totdropped + qh->qhmem.freesize, qh->qhmem.totunused,
    +           qh->qhmem.maxlong, qh->qhmem.totlong, qh->qhmem.cntlong - qh->qhmem.freelong,
    +           qh->qhmem.totbuffer, qh->qhmem.BUFsize, qh->qhmem.BUFinit);
    +  if (qh->qhmem.cntlarger) {
    +    qh_fprintf(qh, fp, 9279, "%7d calls to qh_setlarger\n%7.2g     average copy size\n",
    +           qh->qhmem.cntlarger, ((float)qh->qhmem.totlarger)/(float)qh->qhmem.cntlarger);
    +    qh_fprintf(qh, fp, 9280, "  freelists(bytes->count):");
    +  }
    +  for (i=0; i < qh->qhmem.TABLEsize; i++) {
    +    count=0;
    +    for (object= qh->qhmem.freelists[i]; object; object= *((void **)object))
    +      count++;
    +    qh_fprintf(qh, fp, 9281, " %d->%d", qh->qhmem.sizetable[i], count);
    +  }
    +  qh_fprintf(qh, fp, 9282, "\n\n");
    +} /* memstatistics */
    +
    +
    +/*---------------------------------
    +
    +  qh_NOmem
    +    turn off quick-fit memory allocation
    +
    +  notes:
    +    uses qh_malloc() and qh_free() instead
    +*/
    +#else /* qh_NOmem */
    +
    +void *qh_memalloc(qhT *qh, int insize) {
    +  void *object;
    +
    +  if (!(object= qh_malloc((size_t)insize))) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6090, "qhull error (qh_memalloc): insufficient memory\n");
    +    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  qh->qhmem.cntlong++;
    +  qh->qhmem.totlong += insize;
    +  if (qh->qhmem.maxlong < qh->qhmem.totlong)
    +      qh->qhmem.maxlong= qh->qhmem.totlong;
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8060, "qh_mem %p n %8d alloc long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, insize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
    +  return object;
    +}
    +
    +void qh_memfree(qhT *qh, void *object, int insize) {
    +
    +  if (!object)
    +    return;
    +  qh_free(object);
    +  qh->qhmem.freelong++;
    +  qh->qhmem.totlong -= insize;
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8061, "qh_mem %p n %8d free long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, insize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
    +}
    +
    +void qh_memfreeshort(qhT *qh, int *curlong, int *totlong) {
    +  *totlong= qh->qhmem.totlong;
    +  *curlong= qh->qhmem.cntlong - qh->qhmem.freelong;
    +  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
    +}
    +
    +void qh_meminit(qhT *qh, FILE *ferr) {
    +
    +  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
    +  if (ferr)
    +      qh->qhmem.ferr= ferr;
    +  else
    +      qh->qhmem.ferr= stderr;
    +  if (sizeof(void*) < sizeof(int)) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6091, "qhull internal error (qh_meminit): sizeof(void*) %d < sizeof(int) %d.  qset.c will not work\n", (int)sizeof(void*), (int)sizeof(int));
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +}
    +
    +void qh_meminitbuffers(qhT *qh, int tracelevel, int alignment, int numsizes, int bufsize, int bufinit) {
    +
    +  qh->qhmem.IStracing= tracelevel;
    +}
    +
    +void qh_memsetup(qhT *qh) {
    +
    +}
    +
    +void qh_memsize(qhT *qh, int size) {
    +
    +}
    +
    +void qh_memstatistics(qhT *qh, FILE *fp) {
    +
    +  qh_fprintf(qh, fp, 9409, "\nmemory statistics:\n\
    +%7d long allocations\n\
    +%7d long frees\n\
    +%7d bytes of long memory allocated (max, except for input)\n\
    +%7d bytes of long memory in use (in %d pieces)\n",
    +           qh->qhmem.cntlong,
    +           qh->qhmem.freelong,
    +           qh->qhmem.maxlong, qh->qhmem.totlong, qh->qhmem.cntlong - qh->qhmem.freelong);
    +}
    +
    +#endif /* qh_NOmem */
    +
    +/*---------------------------------
    +
    +  qh_memtotal(qh, totlong, curlong, totshort, curshort, maxlong, totbuffer )
    +    Return the total, allocated long and short memory
    +
    +  returns:
    +    Returns the total current bytes of long and short allocations
    +    Returns the current count of long and short allocations
    +    Returns the maximum long memory and total short buffer (minus one link per buffer)
    +    Does not error (for deprecated UsingLibQhull.cpp (libqhullpcpp))
    +*/
    +void qh_memtotal(qhT *qh, int *totlong, int *curlong, int *totshort, int *curshort, int *maxlong, int *totbuffer) {
    +    *totlong= qh->qhmem.totlong;
    +    *curlong= qh->qhmem.cntlong - qh->qhmem.freelong;
    +    *totshort= qh->qhmem.totshort;
    +    *curshort= qh->qhmem.cntshort + qh->qhmem.cntquick - qh->qhmem.freeshort;
    +    *maxlong= qh->qhmem.maxlong;
    +    *totbuffer= qh->qhmem.totbuffer;
    +} /* memtotlong */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/mem_r.h b/xs/src/qhull/src/libqhull_r/mem_r.h
    new file mode 100644
    index 0000000000..25b5513330
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/mem_r.h
    @@ -0,0 +1,234 @@
    +/*
      ---------------------------------
    +
    +   mem_r.h
    +     prototypes for memory management functions
    +
    +   see qh-mem_r.htm, mem_r.c and qset_r.h
    +
    +   for error handling, writes message and calls
    +     qh_errexit(qhT *qh, qhmem_ERRmem, NULL, NULL) if insufficient memory
    +       and
    +     qh_errexit(qhT *qh, qhmem_ERRqhull, NULL, NULL) otherwise
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/mem_r.h#4 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFmem
    +#define qhDEFmem 1
    +
    +#include 
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;          /* defined in qset_r.h */
    +#endif
    +
    +#ifndef DEFqhT
    +#define DEFqhT 1
    +typedef struct qhT qhT;          /* defined in libqhull_r.h */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_NOmem
    +    turn off quick-fit memory allocation
    +
    +  notes:
    +    mem_r.c implements Quickfit memory allocation for about 20% time
    +    savings.  If it fails on your machine, try to locate the
    +    problem, and send the answer to qhull@qhull.org.  If this can
    +    not be done, define qh_NOmem to use malloc/free instead.
    +
    +   #define qh_NOmem
    +*/
    +
    +/*---------------------------------
    +
    +qh_TRACEshort
    +Trace short and quick memory allocations at T5
    +
    +*/
    +#define qh_TRACEshort
    +
    +/*-------------------------------------------
    +    to avoid bus errors, memory allocation must consider alignment requirements.
    +    malloc() automatically takes care of alignment.   Since mem_r.c manages
    +    its own memory, we need to explicitly specify alignment in
    +    qh_meminitbuffers().
    +
    +    A safe choice is sizeof(double).  sizeof(float) may be used if doubles
    +    do not occur in data structures and pointers are the same size.  Be careful
    +    of machines (e.g., DEC Alpha) with large pointers.  If gcc is available,
    +    use __alignof__(double) or fmax_(__alignof__(float), __alignof__(void *)).
    +
    +   see qh_MEMalign in user.h for qhull's alignment
    +*/
    +
    +#define qhmem_ERRmem 4    /* matches qh_ERRmem in libqhull_r.h */
    +#define qhmem_ERRqhull 5  /* matches qh_ERRqhull in libqhull_r.h */
    +
    +/*----------------------------------
    +
    +  ptr_intT
    +    for casting a void * to an integer-type that holds a pointer
    +    Used for integer expressions (e.g., computing qh_gethash() in poly_r.c)
    +
    +  notes:
    +    WARN64 -- these notes indicate 64-bit issues
    +    On 64-bit machines, a pointer may be larger than an 'int'.
    +    qh_meminit()/mem_r.c checks that 'ptr_intT' holds a 'void*'
    +    ptr_intT is typically a signed value, but not necessarily so
    +    size_t is typically unsigned, but should match the parameter type
    +    Qhull uses int instead of size_t except for system calls such as malloc, qsort, qh_malloc, etc.
    +    This matches Qt convention and is easier to work with.
    +*/
    +#if (defined(__MINGW64__)) && defined(_WIN64)
    +typedef long long ptr_intT;
    +#elif (_MSC_VER) && defined(_WIN64)
    +typedef long long ptr_intT;
    +#else
    +typedef long ptr_intT;
    +#endif
    +
    +/*----------------------------------
    +
    +  qhmemT
    +    global memory structure for mem_r.c
    +
    + notes:
    +   users should ignore qhmem except for writing extensions
    +   qhmem is allocated in mem_r.c
    +
    +   qhmem could be swapable like qh and qhstat, but then
    +   multiple qh's and qhmem's would need to keep in synch.
    +   A swapable qhmem would also waste memory buffers.  As long
    +   as memory operations are atomic, there is no problem with
    +   multiple qh structures being active at the same time.
    +   If you need separate address spaces, you can swap the
    +   contents of qh->qhmem.
    +*/
    +typedef struct qhmemT qhmemT;
    +
    +/* Update qhmem in mem_r.c if add or remove fields */
    +struct qhmemT {               /* global memory management variables */
    +  int      BUFsize;           /* size of memory allocation buffer */
    +  int      BUFinit;           /* initial size of memory allocation buffer */
    +  int      TABLEsize;         /* actual number of sizes in free list table */
    +  int      NUMsizes;          /* maximum number of sizes in free list table */
    +  int      LASTsize;          /* last size in free list table */
    +  int      ALIGNmask;         /* worst-case alignment, must be 2^n-1 */
    +  void   **freelists;          /* free list table, linked by offset 0 */
    +  int     *sizetable;         /* size of each freelist */
    +  int     *indextable;        /* size->index table */
    +  void    *curbuffer;         /* current buffer, linked by offset 0 */
    +  void    *freemem;           /*   free memory in curbuffer */
    +  int      freesize;          /*   size of freemem in bytes */
    +  setT    *tempstack;         /* stack of temporary memory, managed by users */
    +  FILE    *ferr;              /* file for reporting errors when 'qh' may be undefined */
    +  int      IStracing;         /* =5 if tracing memory allocations */
    +  int      cntquick;          /* count of quick allocations */
    +                              /* Note: removing statistics doesn't effect speed */
    +  int      cntshort;          /* count of short allocations */
    +  int      cntlong;           /* count of long allocations */
    +  int      freeshort;         /* count of short memfrees */
    +  int      freelong;          /* count of long memfrees */
    +  int      totbuffer;         /* total short memory buffers minus buffer links */
    +  int      totdropped;        /* total dropped memory at end of short memory buffers (e.g., freesize) */
    +  int      totfree;           /* total size of free, short memory on freelists */
    +  int      totlong;           /* total size of long memory in use */
    +  int      maxlong;           /*   maximum totlong */
    +  int      totshort;          /* total size of short memory in use */
    +  int      totunused;         /* total unused short memory (estimated, short size - request size of first allocations) */
    +  int      cntlarger;         /* count of setlarger's */
    +  int      totlarger;         /* total copied by setlarger */
    +};
    +
    +
    +/*==================== -macros ====================*/
    +
    +/*----------------------------------
    +
    +  qh_memalloc_(qh, insize, freelistp, object, type)
    +    returns object of size bytes
    +        assumes size<=qh->qhmem.LASTsize and void **freelistp is a temp
    +*/
    +
    +#if defined qh_NOmem
    +#define qh_memalloc_(qh, insize, freelistp, object, type) {\
    +  object= (type*)qh_memalloc(qh, insize); }
    +#elif defined qh_TRACEshort
    +#define qh_memalloc_(qh, insize, freelistp, object, type) {\
    +    freelistp= NULL; /* Avoid warnings */ \
    +    object= (type*)qh_memalloc(qh, insize); }
    +#else /* !qh_NOmem */
    +
    +#define qh_memalloc_(qh, insize, freelistp, object, type) {\
    +  freelistp= qh->qhmem.freelists + qh->qhmem.indextable[insize];\
    +  if ((object= (type*)*freelistp)) {\
    +    qh->qhmem.totshort += qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
    +    qh->qhmem.totfree -= qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
    +    qh->qhmem.cntquick++;  \
    +    *freelistp= *((void **)*freelistp);\
    +  }else object= (type*)qh_memalloc(qh, insize);}
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_memfree_(qh, object, insize, freelistp)
    +    free up an object
    +
    +  notes:
    +    object may be NULL
    +    assumes size<=qh->qhmem.LASTsize and void **freelistp is a temp
    +*/
    +#if defined qh_NOmem
    +#define qh_memfree_(qh, object, insize, freelistp) {\
    +  qh_memfree(qh, object, insize); }
    +#elif defined qh_TRACEshort
    +#define qh_memfree_(qh, object, insize, freelistp) {\
    +    freelistp= NULL; /* Avoid warnings */ \
    +    qh_memfree(qh, object, insize); }
    +#else /* !qh_NOmem */
    +
    +#define qh_memfree_(qh, object, insize, freelistp) {\
    +  if (object) { \
    +    qh->qhmem.freeshort++;\
    +    freelistp= qh->qhmem.freelists + qh->qhmem.indextable[insize];\
    +    qh->qhmem.totshort -= qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
    +    qh->qhmem.totfree += qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
    +    *((void **)object)= *freelistp;\
    +    *freelistp= object;}}
    +#endif
    +
    +/*=============== prototypes in alphabetical order ============*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void *qh_memalloc(qhT *qh, int insize);
    +void qh_memcheck(qhT *qh);
    +void qh_memfree(qhT *qh, void *object, int insize);
    +void qh_memfreeshort(qhT *qh, int *curlong, int *totlong);
    +void qh_meminit(qhT *qh, FILE *ferr);
    +void qh_meminitbuffers(qhT *qh, int tracelevel, int alignment, int numsizes,
    +                        int bufsize, int bufinit);
    +void qh_memsetup(qhT *qh);
    +void qh_memsize(qhT *qh, int size);
    +void qh_memstatistics(qhT *qh, FILE *fp);
    +void qh_memtotal(qhT *qh, int *totlong, int *curlong, int *totshort, int *curshort, int *maxlong, int *totbuffer);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFmem */
    diff --git a/xs/src/qhull/src/libqhull_r/merge_r.c b/xs/src/qhull/src/libqhull_r/merge_r.c
    new file mode 100644
    index 0000000000..e5823de8d1
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/merge_r.c
    @@ -0,0 +1,3627 @@
    +/*
      ---------------------------------
    +
    +   merge_r.c
    +   merges non-convex facets
    +
    +   see qh-merge_r.htm and merge_r.h
    +
    +   other modules call qh_premerge() and qh_postmerge()
    +
    +   the user may call qh_postmerge() to perform additional merges.
    +
    +   To remove deleted facets and vertices (qhull() in libqhull_r.c):
    +     qh_partitionvisible(qh, !qh_ALL, &numoutside);  // visible_list, newfacet_list
    +     qh_deletevisible();         // qh.visible_list
    +     qh_resetlists(qh, False, qh_RESETvisible);       // qh.visible_list newvertex_list newfacet_list
    +
    +   assumes qh.CENTERtype= centrum
    +
    +   merges occur in qh_mergefacet and in qh_mergecycle
    +   vertex->neighbors not set until the first merge occurs
    +
    +   Copyright (c) 1993-2015 C.B. Barber.
    +   $Id: //main/2015/qhull/src/libqhull_r/merge_r.c#5 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +#ifndef qh_NOmerge
    +
    +/*===== functions(alphabetical after premerge and postmerge) ======*/
    +
    +/*---------------------------------
    +
    +  qh_premerge(qh, apex, maxcentrum )
    +    pre-merge nonconvex facets in qh.newfacet_list for apex
    +    maxcentrum defines coplanar and concave (qh_test_appendmerge)
    +
    +  returns:
    +    deleted facets added to qh.visible_list with facet->visible set
    +
    +  notes:
    +    uses globals, qh.MERGEexact, qh.PREmerge
    +
    +  design:
    +    mark duplicate ridges in qh.newfacet_list
    +    merge facet cycles in qh.newfacet_list
    +    merge duplicate ridges and concave facets in qh.newfacet_list
    +    check merged facet cycles for degenerate and redundant facets
    +    merge degenerate and redundant facets
    +    collect coplanar and concave facets
    +    merge concave, coplanar, degenerate, and redundant facets
    +*/
    +void qh_premerge(qhT *qh, vertexT *apex, realT maxcentrum, realT maxangle) {
    +  boolT othermerge= False;
    +  facetT *newfacet;
    +
    +  if (qh->ZEROcentrum && qh_checkzero(qh, !qh_ALL))
    +    return;
    +  trace2((qh, qh->ferr, 2008, "qh_premerge: premerge centrum %2.2g angle %2.2g for apex v%d facetlist f%d\n",
    +            maxcentrum, maxangle, apex->id, getid_(qh->newfacet_list)));
    +  if (qh->IStracing >= 4 && qh->num_facets < 50)
    +    qh_printlists(qh);
    +  qh->centrum_radius= maxcentrum;
    +  qh->cos_max= maxangle;
    +  qh->degen_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  if (qh->hull_dim >=3) {
    +    qh_mark_dupridges(qh, qh->newfacet_list); /* facet_mergeset */
    +    qh_mergecycle_all(qh, qh->newfacet_list, &othermerge);
    +    qh_forcedmerges(qh, &othermerge /* qh->facet_mergeset */);
    +    FORALLnew_facets {  /* test samecycle merges */
    +      if (!newfacet->simplicial && !newfacet->mergeridge)
    +        qh_degen_redundant_neighbors(qh, newfacet, NULL);
    +    }
    +    if (qh_merge_degenredundant(qh))
    +      othermerge= True;
    +  }else /* qh->hull_dim == 2 */
    +    qh_mergecycle_all(qh, qh->newfacet_list, &othermerge);
    +  qh_flippedmerges(qh, qh->newfacet_list, &othermerge);
    +  if (!qh->MERGEexact || zzval_(Ztotmerge)) {
    +    zinc_(Zpremergetot);
    +    qh->POSTmerging= False;
    +    qh_getmergeset_initial(qh, qh->newfacet_list);
    +    qh_all_merges(qh, othermerge, False);
    +  }
    +  qh_settempfree(qh, &qh->facet_mergeset);
    +  qh_settempfree(qh, &qh->degen_mergeset);
    +} /* premerge */
    +
    +/*---------------------------------
    +
    +  qh_postmerge(qh, reason, maxcentrum, maxangle, vneighbors )
    +    post-merge nonconvex facets as defined by maxcentrum and maxangle
    +    'reason' is for reporting progress
    +    if vneighbors,
    +      calls qh_test_vneighbors at end of qh_all_merge
    +    if firstmerge,
    +      calls qh_reducevertices before qh_getmergeset
    +
    +  returns:
    +    if first call (qh.visible_list != qh.facet_list),
    +      builds qh.facet_newlist, qh.newvertex_list
    +    deleted facets added to qh.visible_list with facet->visible
    +    qh.visible_list == qh.facet_list
    +
    +  notes:
    +
    +
    +  design:
    +    if first call
    +      set qh.visible_list and qh.newfacet_list to qh.facet_list
    +      add all facets to qh.newfacet_list
    +      mark non-simplicial facets, facet->newmerge
    +      set qh.newvertext_list to qh.vertex_list
    +      add all vertices to qh.newvertex_list
    +      if a pre-merge occured
    +        set vertex->delridge {will retest the ridge}
    +        if qh.MERGEexact
    +          call qh_reducevertices()
    +      if no pre-merging
    +        merge flipped facets
    +    determine non-convex facets
    +    merge all non-convex facets
    +*/
    +void qh_postmerge(qhT *qh, const char *reason, realT maxcentrum, realT maxangle,
    +                      boolT vneighbors) {
    +  facetT *newfacet;
    +  boolT othermerges= False;
    +  vertexT *vertex;
    +
    +  if (qh->REPORTfreq || qh->IStracing) {
    +    qh_buildtracing(qh, NULL, NULL);
    +    qh_printsummary(qh, qh->ferr);
    +    if (qh->PRINTstatistics)
    +      qh_printallstatistics(qh, qh->ferr, "reason");
    +    qh_fprintf(qh, qh->ferr, 8062, "\n%s with 'C%.2g' and 'A%.2g'\n",
    +        reason, maxcentrum, maxangle);
    +  }
    +  trace2((qh, qh->ferr, 2009, "qh_postmerge: postmerge.  test vneighbors? %d\n",
    +            vneighbors));
    +  qh->centrum_radius= maxcentrum;
    +  qh->cos_max= maxangle;
    +  qh->POSTmerging= True;
    +  qh->degen_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  if (qh->visible_list != qh->facet_list) {  /* first call */
    +    qh->NEWfacets= True;
    +    qh->visible_list= qh->newfacet_list= qh->facet_list;
    +    FORALLnew_facets {
    +      newfacet->newfacet= True;
    +       if (!newfacet->simplicial)
    +        newfacet->newmerge= True;
    +     zinc_(Zpostfacets);
    +    }
    +    qh->newvertex_list= qh->vertex_list;
    +    FORALLvertices
    +      vertex->newlist= True;
    +    if (qh->VERTEXneighbors) { /* a merge has occurred */
    +      FORALLvertices
    +        vertex->delridge= True; /* test for redundant, needed? */
    +      if (qh->MERGEexact) {
    +        if (qh->hull_dim <= qh_DIMreduceBuild)
    +          qh_reducevertices(qh); /* was skipped during pre-merging */
    +      }
    +    }
    +    if (!qh->PREmerge && !qh->MERGEexact)
    +      qh_flippedmerges(qh, qh->newfacet_list, &othermerges);
    +  }
    +  qh_getmergeset_initial(qh, qh->newfacet_list);
    +  qh_all_merges(qh, False, vneighbors);
    +  qh_settempfree(qh, &qh->facet_mergeset);
    +  qh_settempfree(qh, &qh->degen_mergeset);
    +} /* post_merge */
    +
    +/*---------------------------------
    +
    +  qh_all_merges(qh, othermerge, vneighbors )
    +    merge all non-convex facets
    +
    +    set othermerge if already merged facets (for qh_reducevertices)
    +    if vneighbors
    +      tests vertex neighbors for convexity at end
    +    qh.facet_mergeset lists the non-convex ridges in qh_newfacet_list
    +    qh.degen_mergeset is defined
    +    if qh.MERGEexact && !qh.POSTmerging,
    +      does not merge coplanar facets
    +
    +  returns:
    +    deleted facets added to qh.visible_list with facet->visible
    +    deleted vertices added qh.delvertex_list with vertex->delvertex
    +
    +  notes:
    +    unless !qh.MERGEindependent,
    +      merges facets in independent sets
    +    uses qh.newfacet_list as argument since merges call qh_removefacet()
    +
    +  design:
    +    while merges occur
    +      for each merge in qh.facet_mergeset
    +        unless one of the facets was already merged in this pass
    +          merge the facets
    +        test merged facets for additional merges
    +        add merges to qh.facet_mergeset
    +      if vertices record neighboring facets
    +        rename redundant vertices
    +          update qh.facet_mergeset
    +    if vneighbors ??
    +      tests vertex neighbors for convexity at end
    +*/
    +void qh_all_merges(qhT *qh, boolT othermerge, boolT vneighbors) {
    +  facetT *facet1, *facet2;
    +  mergeT *merge;
    +  boolT wasmerge= True, isreduce;
    +  void **freelistp;  /* used if !qh_NOmem by qh_memfree_() */
    +  vertexT *vertex;
    +  mergeType mergetype;
    +  int numcoplanar=0, numconcave=0, numdegenredun= 0, numnewmerges= 0;
    +
    +  trace2((qh, qh->ferr, 2010, "qh_all_merges: starting to merge facets beginning from f%d\n",
    +            getid_(qh->newfacet_list)));
    +  while (True) {
    +    wasmerge= False;
    +    while (qh_setsize(qh, qh->facet_mergeset)) {
    +      while ((merge= (mergeT*)qh_setdellast(qh->facet_mergeset))) {
    +        facet1= merge->facet1;
    +        facet2= merge->facet2;
    +        mergetype= merge->type;
    +        qh_memfree_(qh, merge, (int)sizeof(mergeT), freelistp);
    +        if (facet1->visible || facet2->visible) /*deleted facet*/
    +          continue;
    +        if ((facet1->newfacet && !facet1->tested)
    +                || (facet2->newfacet && !facet2->tested)) {
    +          if (qh->MERGEindependent && mergetype <= MRGanglecoplanar)
    +            continue;      /* perform independent sets of merges */
    +        }
    +        qh_merge_nonconvex(qh, facet1, facet2, mergetype);
    +        numdegenredun += qh_merge_degenredundant(qh);
    +        numnewmerges++;
    +        wasmerge= True;
    +        if (mergetype == MRGconcave)
    +          numconcave++;
    +        else /* MRGcoplanar or MRGanglecoplanar */
    +          numcoplanar++;
    +      } /* while setdellast */
    +      if (qh->POSTmerging && qh->hull_dim <= qh_DIMreduceBuild
    +      && numnewmerges > qh_MAXnewmerges) {
    +        numnewmerges= 0;
    +        qh_reducevertices(qh);  /* otherwise large post merges too slow */
    +      }
    +      qh_getmergeset(qh, qh->newfacet_list); /* facet_mergeset */
    +    } /* while mergeset */
    +    if (qh->VERTEXneighbors) {
    +      isreduce= False;
    +      if (qh->hull_dim >=4 && qh->POSTmerging) {
    +        FORALLvertices
    +          vertex->delridge= True;
    +        isreduce= True;
    +      }
    +      if ((wasmerge || othermerge) && (!qh->MERGEexact || qh->POSTmerging)
    +          && qh->hull_dim <= qh_DIMreduceBuild) {
    +        othermerge= False;
    +        isreduce= True;
    +      }
    +      if (isreduce) {
    +        if (qh_reducevertices(qh)) {
    +          qh_getmergeset(qh, qh->newfacet_list); /* facet_mergeset */
    +          continue;
    +        }
    +      }
    +    }
    +    if (vneighbors && qh_test_vneighbors(qh /* qh->newfacet_list */))
    +      continue;
    +    break;
    +  } /* while (True) */
    +  if (qh->CHECKfrequently && !qh->MERGEexact) {
    +    qh->old_randomdist= qh->RANDOMdist;
    +    qh->RANDOMdist= False;
    +    qh_checkconvex(qh, qh->newfacet_list, qh_ALGORITHMfault);
    +    /* qh_checkconnect(qh); [this is slow and it changes the facet order] */
    +    qh->RANDOMdist= qh->old_randomdist;
    +  }
    +  trace1((qh, qh->ferr, 1009, "qh_all_merges: merged %d coplanar facets %d concave facets and %d degen or redundant facets.\n",
    +    numcoplanar, numconcave, numdegenredun));
    +  if (qh->IStracing >= 4 && qh->num_facets < 50)
    +    qh_printlists(qh);
    +} /* all_merges */
    +
    +
    +/*---------------------------------
    +
    +  qh_appendmergeset(qh, facet, neighbor, mergetype, angle )
    +    appends an entry to qh.facet_mergeset or qh.degen_mergeset
    +
    +    angle ignored if NULL or !qh.ANGLEmerge
    +
    +  returns:
    +    merge appended to facet_mergeset or degen_mergeset
    +      sets ->degenerate or ->redundant if degen_mergeset
    +
    +  see:
    +    qh_test_appendmerge()
    +
    +  design:
    +    allocate merge entry
    +    if regular merge
    +      append to qh.facet_mergeset
    +    else if degenerate merge and qh.facet_mergeset is all degenerate
    +      append to qh.degen_mergeset
    +    else if degenerate merge
    +      prepend to qh.degen_mergeset
    +    else if redundant merge
    +      append to qh.degen_mergeset
    +*/
    +void qh_appendmergeset(qhT *qh, facetT *facet, facetT *neighbor, mergeType mergetype, realT *angle) {
    +  mergeT *merge, *lastmerge;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  if (facet->redundant)
    +    return;
    +  if (facet->degenerate && mergetype == MRGdegen)
    +    return;
    +  qh_memalloc_(qh, (int)sizeof(mergeT), freelistp, merge, mergeT);
    +  merge->facet1= facet;
    +  merge->facet2= neighbor;
    +  merge->type= mergetype;
    +  if (angle && qh->ANGLEmerge)
    +    merge->angle= *angle;
    +  if (mergetype < MRGdegen)
    +    qh_setappend(qh, &(qh->facet_mergeset), merge);
    +  else if (mergetype == MRGdegen) {
    +    facet->degenerate= True;
    +    if (!(lastmerge= (mergeT*)qh_setlast(qh->degen_mergeset))
    +    || lastmerge->type == MRGdegen)
    +      qh_setappend(qh, &(qh->degen_mergeset), merge);
    +    else
    +      qh_setaddnth(qh, &(qh->degen_mergeset), 0, merge);
    +  }else if (mergetype == MRGredundant) {
    +    facet->redundant= True;
    +    qh_setappend(qh, &(qh->degen_mergeset), merge);
    +  }else /* mergetype == MRGmirror */ {
    +    if (facet->redundant || neighbor->redundant) {
    +      qh_fprintf(qh, qh->ferr, 6092, "qhull error (qh_appendmergeset): facet f%d or f%d is already a mirrored facet\n",
    +           facet->id, neighbor->id);
    +      qh_errexit2(qh, qh_ERRqhull, facet, neighbor);
    +    }
    +    if (!qh_setequal(facet->vertices, neighbor->vertices)) {
    +      qh_fprintf(qh, qh->ferr, 6093, "qhull error (qh_appendmergeset): mirrored facets f%d and f%d do not have the same vertices\n",
    +           facet->id, neighbor->id);
    +      qh_errexit2(qh, qh_ERRqhull, facet, neighbor);
    +    }
    +    facet->redundant= True;
    +    neighbor->redundant= True;
    +    qh_setappend(qh, &(qh->degen_mergeset), merge);
    +  }
    +} /* appendmergeset */
    +
    +
    +/*---------------------------------
    +
    +  qh_basevertices(qh, samecycle )
    +    return temporary set of base vertices for samecycle
    +    samecycle is first facet in the cycle
    +    assumes apex is SETfirst_( samecycle->vertices )
    +
    +  returns:
    +    vertices(settemp)
    +    all ->seen are cleared
    +
    +  notes:
    +    uses qh_vertex_visit;
    +
    +  design:
    +    for each facet in samecycle
    +      for each unseen vertex in facet->vertices
    +        append to result
    +*/
    +setT *qh_basevertices(qhT *qh, facetT *samecycle) {
    +  facetT *same;
    +  vertexT *apex, *vertex, **vertexp;
    +  setT *vertices= qh_settemp(qh, qh->TEMPsize);
    +
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  apex->visitid= ++qh->vertex_visit;
    +  FORALLsame_cycle_(samecycle) {
    +    if (same->mergeridge)
    +      continue;
    +    FOREACHvertex_(same->vertices) {
    +      if (vertex->visitid != qh->vertex_visit) {
    +        qh_setappend(qh, &vertices, vertex);
    +        vertex->visitid= qh->vertex_visit;
    +        vertex->seen= False;
    +      }
    +    }
    +  }
    +  trace4((qh, qh->ferr, 4019, "qh_basevertices: found %d vertices\n",
    +         qh_setsize(qh, vertices)));
    +  return vertices;
    +} /* basevertices */
    +
    +/*---------------------------------
    +
    +  qh_checkconnect(qh)
    +    check that new facets are connected
    +    new facets are on qh.newfacet_list
    +
    +  notes:
    +    this is slow and it changes the order of the facets
    +    uses qh.visit_id
    +
    +  design:
    +    move first new facet to end of qh.facet_list
    +    for all newly appended facets
    +      append unvisited neighbors to end of qh.facet_list
    +    for all new facets
    +      report error if unvisited
    +*/
    +void qh_checkconnect(qhT *qh /* qh->newfacet_list */) {
    +  facetT *facet, *newfacet, *errfacet= NULL, *neighbor, **neighborp;
    +
    +  facet= qh->newfacet_list;
    +  qh_removefacet(qh, facet);
    +  qh_appendfacet(qh, facet);
    +  facet->visitid= ++qh->visit_id;
    +  FORALLfacet_(facet) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh->visit_id) {
    +        qh_removefacet(qh, neighbor);
    +        qh_appendfacet(qh, neighbor);
    +        neighbor->visitid= qh->visit_id;
    +      }
    +    }
    +  }
    +  FORALLnew_facets {
    +    if (newfacet->visitid == qh->visit_id)
    +      break;
    +    qh_fprintf(qh, qh->ferr, 6094, "qhull error: f%d is not attached to the new facets\n",
    +         newfacet->id);
    +    errfacet= newfacet;
    +  }
    +  if (errfacet)
    +    qh_errexit(qh, qh_ERRqhull, errfacet, NULL);
    +} /* checkconnect */
    +
    +/*---------------------------------
    +
    +  qh_checkzero(qh, testall )
    +    check that facets are clearly convex for qh.DISTround with qh.MERGEexact
    +
    +    if testall,
    +      test all facets for qh.MERGEexact post-merging
    +    else
    +      test qh.newfacet_list
    +
    +    if qh.MERGEexact,
    +      allows coplanar ridges
    +      skips convexity test while qh.ZEROall_ok
    +
    +  returns:
    +    True if all facets !flipped, !dupridge, normal
    +         if all horizon facets are simplicial
    +         if all vertices are clearly below neighbor
    +         if all opposite vertices of horizon are below
    +    clears qh.ZEROall_ok if any problems or coplanar facets
    +
    +  notes:
    +    uses qh.vertex_visit
    +    horizon facets may define multiple new facets
    +
    +  design:
    +    for all facets in qh.newfacet_list or qh.facet_list
    +      check for flagged faults (flipped, etc.)
    +    for all facets in qh.newfacet_list or qh.facet_list
    +      for each neighbor of facet
    +        skip horizon facets for qh.newfacet_list
    +        test the opposite vertex
    +      if qh.newfacet_list
    +        test the other vertices in the facet's horizon facet
    +*/
    +boolT qh_checkzero(qhT *qh, boolT testall) {
    +  facetT *facet, *neighbor, **neighborp;
    +  facetT *horizon, *facetlist;
    +  int neighbor_i;
    +  vertexT *vertex, **vertexp;
    +  realT dist;
    +
    +  if (testall)
    +    facetlist= qh->facet_list;
    +  else {
    +    facetlist= qh->newfacet_list;
    +    FORALLfacet_(facetlist) {
    +      horizon= SETfirstt_(facet->neighbors, facetT);
    +      if (!horizon->simplicial)
    +        goto LABELproblem;
    +      if (facet->flipped || facet->dupridge || !facet->normal)
    +        goto LABELproblem;
    +    }
    +    if (qh->MERGEexact && qh->ZEROall_ok) {
    +      trace2((qh, qh->ferr, 2011, "qh_checkzero: skip convexity check until first pre-merge\n"));
    +      return True;
    +    }
    +  }
    +  FORALLfacet_(facetlist) {
    +    qh->vertex_visit++;
    +    neighbor_i= 0;
    +    horizon= NULL;
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor_i && !testall) {
    +        horizon= neighbor;
    +        neighbor_i++;
    +        continue; /* horizon facet tested in qh_findhorizon */
    +      }
    +      vertex= SETelemt_(facet->vertices, neighbor_i++, vertexT);
    +      vertex->visitid= qh->vertex_visit;
    +      zzinc_(Zdistzero);
    +      qh_distplane(qh, vertex->point, neighbor, &dist);
    +      if (dist >= -qh->DISTround) {
    +        qh->ZEROall_ok= False;
    +        if (!qh->MERGEexact || testall || dist > qh->DISTround)
    +          goto LABELnonconvex;
    +      }
    +    }
    +    if (!testall && horizon) {
    +      FOREACHvertex_(horizon->vertices) {
    +        if (vertex->visitid != qh->vertex_visit) {
    +          zzinc_(Zdistzero);
    +          qh_distplane(qh, vertex->point, facet, &dist);
    +          if (dist >= -qh->DISTround) {
    +            qh->ZEROall_ok= False;
    +            if (!qh->MERGEexact || dist > qh->DISTround)
    +              goto LABELnonconvex;
    +          }
    +          break;
    +        }
    +      }
    +    }
    +  }
    +  trace2((qh, qh->ferr, 2012, "qh_checkzero: testall %d, facets are %s\n", testall,
    +        (qh->MERGEexact && !testall) ?
    +           "not concave, flipped, or duplicate ridged" : "clearly convex"));
    +  return True;
    +
    + LABELproblem:
    +  qh->ZEROall_ok= False;
    +  trace2((qh, qh->ferr, 2013, "qh_checkzero: facet f%d needs pre-merging\n",
    +       facet->id));
    +  return False;
    +
    + LABELnonconvex:
    +  trace2((qh, qh->ferr, 2014, "qh_checkzero: facet f%d and f%d are not clearly convex.  v%d dist %.2g\n",
    +         facet->id, neighbor->id, vertex->id, dist));
    +  return False;
    +} /* checkzero */
    +
    +/*---------------------------------
    +
    +  qh_compareangle(angle1, angle2 )
    +    used by qsort() to order merges by angle
    +*/
    +int qh_compareangle(const void *p1, const void *p2) {
    +  const mergeT *a= *((mergeT *const*)p1), *b= *((mergeT *const*)p2);
    +
    +  return((a->angle > b->angle) ? 1 : -1);
    +} /* compareangle */
    +
    +/*---------------------------------
    +
    +  qh_comparemerge(merge1, merge2 )
    +    used by qsort() to order merges
    +*/
    +int qh_comparemerge(const void *p1, const void *p2) {
    +  const mergeT *a= *((mergeT *const*)p1), *b= *((mergeT *const*)p2);
    +
    +  return(a->type - b->type);
    +} /* comparemerge */
    +
    +/*---------------------------------
    +
    +  qh_comparevisit(vertex1, vertex2 )
    +    used by qsort() to order vertices by their visitid
    +*/
    +int qh_comparevisit(const void *p1, const void *p2) {
    +  const vertexT *a= *((vertexT *const*)p1), *b= *((vertexT *const*)p2);
    +
    +  return(a->visitid - b->visitid);
    +} /* comparevisit */
    +
    +/*---------------------------------
    +
    +  qh_copynonconvex(qh, atridge )
    +    set non-convex flag on other ridges (if any) between same neighbors
    +
    +  notes:
    +    may be faster if use smaller ridge set
    +
    +  design:
    +    for each ridge of atridge's top facet
    +      if ridge shares the same neighbor
    +        set nonconvex flag
    +*/
    +void qh_copynonconvex(qhT *qh, ridgeT *atridge) {
    +  facetT *facet, *otherfacet;
    +  ridgeT *ridge, **ridgep;
    +
    +  facet= atridge->top;
    +  otherfacet= atridge->bottom;
    +  FOREACHridge_(facet->ridges) {
    +    if (otherfacet == otherfacet_(ridge, facet) && ridge != atridge) {
    +      ridge->nonconvex= True;
    +      trace4((qh, qh->ferr, 4020, "qh_copynonconvex: moved nonconvex flag from r%d to r%d\n",
    +              atridge->id, ridge->id));
    +      break;
    +    }
    +  }
    +} /* copynonconvex */
    +
    +/*---------------------------------
    +
    +  qh_degen_redundant_facet(qh, facet )
    +    check facet for degen. or redundancy
    +
    +  notes:
    +    bumps vertex_visit
    +    called if a facet was redundant but no longer is (qh_merge_degenredundant)
    +    qh_appendmergeset() only appends first reference to facet (i.e., redundant)
    +
    +  see:
    +    qh_degen_redundant_neighbors()
    +
    +  design:
    +    test for redundant neighbor
    +    test for degenerate facet
    +*/
    +void qh_degen_redundant_facet(qhT *qh, facetT *facet) {
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +
    +  trace4((qh, qh->ferr, 4021, "qh_degen_redundant_facet: test facet f%d for degen/redundant\n",
    +          facet->id));
    +  FOREACHneighbor_(facet) {
    +    qh->vertex_visit++;
    +    FOREACHvertex_(neighbor->vertices)
    +      vertex->visitid= qh->vertex_visit;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh->vertex_visit)
    +        break;
    +    }
    +    if (!vertex) {
    +      qh_appendmergeset(qh, facet, neighbor, MRGredundant, NULL);
    +      trace2((qh, qh->ferr, 2015, "qh_degen_redundant_facet: f%d is contained in f%d.  merge\n", facet->id, neighbor->id));
    +      return;
    +    }
    +  }
    +  if (qh_setsize(qh, facet->neighbors) < qh->hull_dim) {
    +    qh_appendmergeset(qh, facet, facet, MRGdegen, NULL);
    +    trace2((qh, qh->ferr, 2016, "qh_degen_redundant_neighbors: f%d is degenerate.\n", facet->id));
    +  }
    +} /* degen_redundant_facet */
    +
    +
    +/*---------------------------------
    +
    +  qh_degen_redundant_neighbors(qh, facet, delfacet,  )
    +    append degenerate and redundant neighbors to facet_mergeset
    +    if delfacet,
    +      only checks neighbors of both delfacet and facet
    +    also checks current facet for degeneracy
    +
    +  notes:
    +    bumps vertex_visit
    +    called for each qh_mergefacet() and qh_mergecycle()
    +    merge and statistics occur in merge_nonconvex
    +    qh_appendmergeset() only appends first reference to facet (i.e., redundant)
    +      it appends redundant facets after degenerate ones
    +
    +    a degenerate facet has fewer than hull_dim neighbors
    +    a redundant facet's vertices is a subset of its neighbor's vertices
    +    tests for redundant merges first (appendmergeset is nop for others)
    +    in a merge, only needs to test neighbors of merged facet
    +
    +  see:
    +    qh_merge_degenredundant() and qh_degen_redundant_facet()
    +
    +  design:
    +    test for degenerate facet
    +    test for redundant neighbor
    +    test for degenerate neighbor
    +*/
    +void qh_degen_redundant_neighbors(qhT *qh, facetT *facet, facetT *delfacet) {
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +  int size;
    +
    +  trace4((qh, qh->ferr, 4022, "qh_degen_redundant_neighbors: test neighbors of f%d with delfacet f%d\n",
    +          facet->id, getid_(delfacet)));
    +  if ((size= qh_setsize(qh, facet->neighbors)) < qh->hull_dim) {
    +    qh_appendmergeset(qh, facet, facet, MRGdegen, NULL);
    +    trace2((qh, qh->ferr, 2017, "qh_degen_redundant_neighbors: f%d is degenerate with %d neighbors.\n", facet->id, size));
    +  }
    +  if (!delfacet)
    +    delfacet= facet;
    +  qh->vertex_visit++;
    +  FOREACHvertex_(facet->vertices)
    +    vertex->visitid= qh->vertex_visit;
    +  FOREACHneighbor_(delfacet) {
    +    /* uses early out instead of checking vertex count */
    +    if (neighbor == facet)
    +      continue;
    +    FOREACHvertex_(neighbor->vertices) {
    +      if (vertex->visitid != qh->vertex_visit)
    +        break;
    +    }
    +    if (!vertex) {
    +      qh_appendmergeset(qh, neighbor, facet, MRGredundant, NULL);
    +      trace2((qh, qh->ferr, 2018, "qh_degen_redundant_neighbors: f%d is contained in f%d.  merge\n", neighbor->id, facet->id));
    +    }
    +  }
    +  FOREACHneighbor_(delfacet) {   /* redundant merges occur first */
    +    if (neighbor == facet)
    +      continue;
    +    if ((size= qh_setsize(qh, neighbor->neighbors)) < qh->hull_dim) {
    +      qh_appendmergeset(qh, neighbor, neighbor, MRGdegen, NULL);
    +      trace2((qh, qh->ferr, 2019, "qh_degen_redundant_neighbors: f%d is degenerate with %d neighbors.  Neighbor of f%d.\n", neighbor->id, size, facet->id));
    +    }
    +  }
    +} /* degen_redundant_neighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_find_newvertex(qh, oldvertex, vertices, ridges )
    +    locate new vertex for renaming old vertex
    +    vertices is a set of possible new vertices
    +      vertices sorted by number of deleted ridges
    +
    +  returns:
    +    newvertex or NULL
    +      each ridge includes both vertex and oldvertex
    +    vertices sorted by number of deleted ridges
    +
    +  notes:
    +    modifies vertex->visitid
    +    new vertex is in one of the ridges
    +    renaming will not cause a duplicate ridge
    +    renaming will minimize the number of deleted ridges
    +    newvertex may not be adjacent in the dual (though unlikely)
    +
    +  design:
    +    for each vertex in vertices
    +      set vertex->visitid to number of references in ridges
    +    remove unvisited vertices
    +    set qh.vertex_visit above all possible values
    +    sort vertices by number of references in ridges
    +    add each ridge to qh.hash_table
    +    for each vertex in vertices
    +      look for a vertex that would not cause a duplicate ridge after a rename
    +*/
    +vertexT *qh_find_newvertex(qhT *qh, vertexT *oldvertex, setT *vertices, setT *ridges) {
    +  vertexT *vertex, **vertexp;
    +  setT *newridges;
    +  ridgeT *ridge, **ridgep;
    +  int size, hashsize;
    +  int hash;
    +
    +#ifndef qh_NOtrace
    +  if (qh->IStracing >= 4) {
    +    qh_fprintf(qh, qh->ferr, 8063, "qh_find_newvertex: find new vertex for v%d from ",
    +             oldvertex->id);
    +    FOREACHvertex_(vertices)
    +      qh_fprintf(qh, qh->ferr, 8064, "v%d ", vertex->id);
    +    FOREACHridge_(ridges)
    +      qh_fprintf(qh, qh->ferr, 8065, "r%d ", ridge->id);
    +    qh_fprintf(qh, qh->ferr, 8066, "\n");
    +  }
    +#endif
    +  FOREACHvertex_(vertices)
    +    vertex->visitid= 0;
    +  FOREACHridge_(ridges) {
    +    FOREACHvertex_(ridge->vertices)
    +      vertex->visitid++;
    +  }
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->visitid) {
    +      qh_setdelnth(qh, vertices, SETindex_(vertices,vertex));
    +      vertexp--; /* repeat since deleted this vertex */
    +    }
    +  }
    +  qh->vertex_visit += (unsigned int)qh_setsize(qh, ridges);
    +  if (!qh_setsize(qh, vertices)) {
    +    trace4((qh, qh->ferr, 4023, "qh_find_newvertex: vertices not in ridges for v%d\n",
    +            oldvertex->id));
    +    return NULL;
    +  }
    +  qsort(SETaddr_(vertices, vertexT), (size_t)qh_setsize(qh, vertices),
    +                sizeof(vertexT *), qh_comparevisit);
    +  /* can now use qh->vertex_visit */
    +  if (qh->PRINTstatistics) {
    +    size= qh_setsize(qh, vertices);
    +    zinc_(Zintersect);
    +    zadd_(Zintersecttot, size);
    +    zmax_(Zintersectmax, size);
    +  }
    +  hashsize= qh_newhashtable(qh, qh_setsize(qh, ridges));
    +  FOREACHridge_(ridges)
    +    qh_hashridge(qh, qh->hash_table, hashsize, ridge, oldvertex);
    +  FOREACHvertex_(vertices) {
    +    newridges= qh_vertexridges(qh, vertex);
    +    FOREACHridge_(newridges) {
    +      if (qh_hashridge_find(qh, qh->hash_table, hashsize, ridge, vertex, oldvertex, &hash)) {
    +        zinc_(Zdupridge);
    +        break;
    +      }
    +    }
    +    qh_settempfree(qh, &newridges);
    +    if (!ridge)
    +      break;  /* found a rename */
    +  }
    +  if (vertex) {
    +    /* counted in qh_renamevertex */
    +    trace2((qh, qh->ferr, 2020, "qh_find_newvertex: found v%d for old v%d from %d vertices and %d ridges.\n",
    +      vertex->id, oldvertex->id, qh_setsize(qh, vertices), qh_setsize(qh, ridges)));
    +  }else {
    +    zinc_(Zfindfail);
    +    trace0((qh, qh->ferr, 14, "qh_find_newvertex: no vertex for renaming v%d(all duplicated ridges) during p%d\n",
    +      oldvertex->id, qh->furthest_id));
    +  }
    +  qh_setfree(qh, &qh->hash_table);
    +  return vertex;
    +} /* find_newvertex */
    +
    +/*---------------------------------
    +
    +  qh_findbest_test(qh, testcentrum, facet, neighbor, bestfacet, dist, mindist, maxdist )
    +    test neighbor of facet for qh_findbestneighbor()
    +    if testcentrum,
    +      tests centrum (assumes it is defined)
    +    else
    +      tests vertices
    +
    +  returns:
    +    if a better facet (i.e., vertices/centrum of facet closer to neighbor)
    +      updates bestfacet, dist, mindist, and maxdist
    +*/
    +void qh_findbest_test(qhT *qh, boolT testcentrum, facetT *facet, facetT *neighbor,
    +      facetT **bestfacet, realT *distp, realT *mindistp, realT *maxdistp) {
    +  realT dist, mindist, maxdist;
    +
    +  if (testcentrum) {
    +    zzinc_(Zbestdist);
    +    qh_distplane(qh, facet->center, neighbor, &dist);
    +    dist *= qh->hull_dim; /* estimate furthest vertex */
    +    if (dist < 0) {
    +      maxdist= 0;
    +      mindist= dist;
    +      dist= -dist;
    +    }else {
    +      mindist= 0;
    +      maxdist= dist;
    +    }
    +  }else
    +    dist= qh_getdistance(qh, facet, neighbor, &mindist, &maxdist);
    +  if (dist < *distp) {
    +    *bestfacet= neighbor;
    +    *mindistp= mindist;
    +    *maxdistp= maxdist;
    +    *distp= dist;
    +  }
    +} /* findbest_test */
    +
    +/*---------------------------------
    +
    +  qh_findbestneighbor(qh, facet, dist, mindist, maxdist )
    +    finds best neighbor (least dist) of a facet for merging
    +
    +  returns:
    +    returns min and max distances and their max absolute value
    +
    +  notes:
    +    error if qh_ASvoronoi
    +    avoids merging old into new
    +    assumes ridge->nonconvex only set on one ridge between a pair of facets
    +    could use an early out predicate but not worth it
    +
    +  design:
    +    if a large facet
    +      will test centrum
    +    else
    +      will test vertices
    +    if a large facet
    +      test nonconvex neighbors for best merge
    +    else
    +      test all neighbors for the best merge
    +    if testing centrum
    +      get distance information
    +*/
    +facetT *qh_findbestneighbor(qhT *qh, facetT *facet, realT *distp, realT *mindistp, realT *maxdistp) {
    +  facetT *neighbor, **neighborp, *bestfacet= NULL;
    +  ridgeT *ridge, **ridgep;
    +  boolT nonconvex= True, testcentrum= False;
    +  int size= qh_setsize(qh, facet->vertices);
    +
    +  if(qh->CENTERtype==qh_ASvoronoi){
    +    qh_fprintf(qh, qh->ferr, 6272, "qhull error: cannot call qh_findbestneighor for f%d while qh.CENTERtype is qh_ASvoronoi\n", facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  *distp= REALmax;
    +  if (size > qh_BESTcentrum2 * qh->hull_dim + qh_BESTcentrum) {
    +    testcentrum= True;
    +    zinc_(Zbestcentrum);
    +    if (!facet->center)
    +       facet->center= qh_getcentrum(qh, facet);
    +  }
    +  if (size > qh->hull_dim + qh_BESTnonconvex) {
    +    FOREACHridge_(facet->ridges) {
    +      if (ridge->nonconvex) {
    +        neighbor= otherfacet_(ridge, facet);
    +        qh_findbest_test(qh, testcentrum, facet, neighbor,
    +                          &bestfacet, distp, mindistp, maxdistp);
    +      }
    +    }
    +  }
    +  if (!bestfacet) {
    +    nonconvex= False;
    +    FOREACHneighbor_(facet)
    +      qh_findbest_test(qh, testcentrum, facet, neighbor,
    +                        &bestfacet, distp, mindistp, maxdistp);
    +  }
    +  if (!bestfacet) {
    +    qh_fprintf(qh, qh->ferr, 6095, "qhull internal error (qh_findbestneighbor): no neighbors for f%d\n", facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  if (testcentrum)
    +    qh_getdistance(qh, facet, bestfacet, mindistp, maxdistp);
    +  trace3((qh, qh->ferr, 3002, "qh_findbestneighbor: f%d is best neighbor for f%d testcentrum? %d nonconvex? %d dist %2.2g min %2.2g max %2.2g\n",
    +     bestfacet->id, facet->id, testcentrum, nonconvex, *distp, *mindistp, *maxdistp));
    +  return(bestfacet);
    +} /* findbestneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_flippedmerges(qh, facetlist, wasmerge )
    +    merge flipped facets into best neighbor
    +    assumes qh.facet_mergeset at top of temporary stack
    +
    +  returns:
    +    no flipped facets on facetlist
    +    sets wasmerge if merge occurred
    +    degen/redundant merges passed through
    +
    +  notes:
    +    othermerges not needed since qh.facet_mergeset is empty before & after
    +      keep it in case of change
    +
    +  design:
    +    append flipped facets to qh.facetmergeset
    +    for each flipped merge
    +      find best neighbor
    +      merge facet into neighbor
    +      merge degenerate and redundant facets
    +    remove flipped merges from qh.facet_mergeset
    +*/
    +void qh_flippedmerges(qhT *qh, facetT *facetlist, boolT *wasmerge) {
    +  facetT *facet, *neighbor, *facet1;
    +  realT dist, mindist, maxdist;
    +  mergeT *merge, **mergep;
    +  setT *othermerges;
    +  int nummerge=0;
    +
    +  trace4((qh, qh->ferr, 4024, "qh_flippedmerges: begin\n"));
    +  FORALLfacet_(facetlist) {
    +    if (facet->flipped && !facet->visible)
    +      qh_appendmergeset(qh, facet, facet, MRGflip, NULL);
    +  }
    +  othermerges= qh_settemppop(qh); /* was facet_mergeset */
    +  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh_settemppush(qh, othermerges);
    +  FOREACHmerge_(othermerges) {
    +    facet1= merge->facet1;
    +    if (merge->type != MRGflip || facet1->visible)
    +      continue;
    +    if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +      qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +    neighbor= qh_findbestneighbor(qh, facet1, &dist, &mindist, &maxdist);
    +    trace0((qh, qh->ferr, 15, "qh_flippedmerges: merge flipped f%d into f%d dist %2.2g during p%d\n",
    +      facet1->id, neighbor->id, dist, qh->furthest_id));
    +    qh_mergefacet(qh, facet1, neighbor, &mindist, &maxdist, !qh_MERGEapex);
    +    nummerge++;
    +    if (qh->PRINTstatistics) {
    +      zinc_(Zflipped);
    +      wadd_(Wflippedtot, dist);
    +      wmax_(Wflippedmax, dist);
    +    }
    +    qh_merge_degenredundant(qh);
    +  }
    +  FOREACHmerge_(othermerges) {
    +    if (merge->facet1->visible || merge->facet2->visible)
    +      qh_memfree(qh, merge, (int)sizeof(mergeT));
    +    else
    +      qh_setappend(qh, &qh->facet_mergeset, merge);
    +  }
    +  qh_settempfree(qh, &othermerges);
    +  if (nummerge)
    +    *wasmerge= True;
    +  trace1((qh, qh->ferr, 1010, "qh_flippedmerges: merged %d flipped facets into a good neighbor\n", nummerge));
    +} /* flippedmerges */
    +
    +
    +/*---------------------------------
    +
    +  qh_forcedmerges(qh, wasmerge )
    +    merge duplicated ridges
    +
    +  returns:
    +    removes all duplicate ridges on facet_mergeset
    +    wasmerge set if merge
    +    qh.facet_mergeset may include non-forced merges(none for now)
    +    qh.degen_mergeset includes degen/redun merges
    +
    +  notes:
    +    duplicate ridges occur when the horizon is pinched,
    +        i.e. a subridge occurs in more than two horizon ridges.
    +     could rename vertices that pinch the horizon
    +    assumes qh_merge_degenredundant() has not be called
    +    othermerges isn't needed since facet_mergeset is empty afterwards
    +      keep it in case of change
    +
    +  design:
    +    for each duplicate ridge
    +      find current facets by chasing f.replace links
    +      check for wide merge due to duplicate ridge
    +      determine best direction for facet
    +      merge one facet into the other
    +      remove duplicate ridges from qh.facet_mergeset
    +*/
    +void qh_forcedmerges(qhT *qh, boolT *wasmerge) {
    +  facetT *facet1, *facet2;
    +  mergeT *merge, **mergep;
    +  realT dist1, dist2, mindist1, mindist2, maxdist1, maxdist2;
    +  setT *othermerges;
    +  int nummerge=0, numflip=0;
    +
    +  if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +    qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +  trace4((qh, qh->ferr, 4025, "qh_forcedmerges: begin\n"));
    +  othermerges= qh_settemppop(qh); /* was facet_mergeset */
    +  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh_settemppush(qh, othermerges);
    +  FOREACHmerge_(othermerges) {
    +    if (merge->type != MRGridge)
    +        continue;
    +    if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +        qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    while (facet1->visible)      /* must exist, no qh_merge_degenredunant */
    +      facet1= facet1->f.replace; /* previously merged facet */
    +    while (facet2->visible)
    +      facet2= facet2->f.replace; /* previously merged facet */
    +    if (facet1 == facet2)
    +      continue;
    +    if (!qh_setin(facet2->neighbors, facet1)) {
    +      qh_fprintf(qh, qh->ferr, 6096, "qhull internal error (qh_forcedmerges): f%d and f%d had a duplicate ridge but as f%d and f%d they are no longer neighbors\n",
    +               merge->facet1->id, merge->facet2->id, facet1->id, facet2->id);
    +      qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +    }
    +    dist1= qh_getdistance(qh, facet1, facet2, &mindist1, &maxdist1);
    +    dist2= qh_getdistance(qh, facet2, facet1, &mindist2, &maxdist2);
    +    qh_check_dupridge(qh, facet1, dist1, facet2, dist2);
    +    if (dist1 < dist2)
    +      qh_mergefacet(qh, facet1, facet2, &mindist1, &maxdist1, !qh_MERGEapex);
    +    else {
    +      qh_mergefacet(qh, facet2, facet1, &mindist2, &maxdist2, !qh_MERGEapex);
    +      dist1= dist2;
    +      facet1= facet2;
    +    }
    +    if (facet1->flipped) {
    +      zinc_(Zmergeflipdup);
    +      numflip++;
    +    }else
    +      nummerge++;
    +    if (qh->PRINTstatistics) {
    +      zinc_(Zduplicate);
    +      wadd_(Wduplicatetot, dist1);
    +      wmax_(Wduplicatemax, dist1);
    +    }
    +  }
    +  FOREACHmerge_(othermerges) {
    +    if (merge->type == MRGridge)
    +      qh_memfree(qh, merge, (int)sizeof(mergeT));
    +    else
    +      qh_setappend(qh, &qh->facet_mergeset, merge);
    +  }
    +  qh_settempfree(qh, &othermerges);
    +  if (nummerge)
    +    *wasmerge= True;
    +  trace1((qh, qh->ferr, 1011, "qh_forcedmerges: merged %d facets and %d flipped facets across duplicated ridges\n",
    +                nummerge, numflip));
    +} /* forcedmerges */
    +
    +
    +/*---------------------------------
    +
    +  qh_getmergeset(qh, facetlist )
    +    determines nonconvex facets on facetlist
    +    tests !tested ridges and nonconvex ridges of !tested facets
    +
    +  returns:
    +    returns sorted qh.facet_mergeset of facet-neighbor pairs to be merged
    +    all ridges tested
    +
    +  notes:
    +    assumes no nonconvex ridges with both facets tested
    +    uses facet->tested/ridge->tested to prevent duplicate tests
    +    can not limit tests to modified ridges since the centrum changed
    +    uses qh.visit_id
    +
    +  see:
    +    qh_getmergeset_initial()
    +
    +  design:
    +    for each facet on facetlist
    +      for each ridge of facet
    +        if untested ridge
    +          test ridge for convexity
    +          if non-convex
    +            append ridge to qh.facet_mergeset
    +    sort qh.facet_mergeset by angle
    +*/
    +void qh_getmergeset(qhT *qh, facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int nummerges;
    +
    +  nummerges= qh_setsize(qh, qh->facet_mergeset);
    +  trace4((qh, qh->ferr, 4026, "qh_getmergeset: started.\n"));
    +  qh->visit_id++;
    +  FORALLfacet_(facetlist) {
    +    if (facet->tested)
    +      continue;
    +    facet->visitid= qh->visit_id;
    +    facet->tested= True;  /* must be non-simplicial due to merge */
    +    FOREACHneighbor_(facet)
    +      neighbor->seen= False;
    +    FOREACHridge_(facet->ridges) {
    +      if (ridge->tested && !ridge->nonconvex)
    +        continue;
    +      /* if tested & nonconvex, need to append merge */
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->seen) {
    +        ridge->tested= True;
    +        ridge->nonconvex= False;
    +      }else if (neighbor->visitid != qh->visit_id) {
    +        ridge->tested= True;
    +        ridge->nonconvex= False;
    +        neighbor->seen= True;      /* only one ridge is marked nonconvex */
    +        if (qh_test_appendmerge(qh, facet, neighbor))
    +          ridge->nonconvex= True;
    +      }
    +    }
    +  }
    +  nummerges= qh_setsize(qh, qh->facet_mergeset);
    +  if (qh->ANGLEmerge)
    +    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compareangle);
    +  else
    +    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_comparemerge);
    +  if (qh->POSTmerging) {
    +    zadd_(Zmergesettot2, nummerges);
    +  }else {
    +    zadd_(Zmergesettot, nummerges);
    +    zmax_(Zmergesetmax, nummerges);
    +  }
    +  trace2((qh, qh->ferr, 2021, "qh_getmergeset: %d merges found\n", nummerges));
    +} /* getmergeset */
    +
    +
    +/*---------------------------------
    +
    +  qh_getmergeset_initial(qh, facetlist )
    +    determine initial qh.facet_mergeset for facets
    +    tests all facet/neighbor pairs on facetlist
    +
    +  returns:
    +    sorted qh.facet_mergeset with nonconvex ridges
    +    sets facet->tested, ridge->tested, and ridge->nonconvex
    +
    +  notes:
    +    uses visit_id, assumes ridge->nonconvex is False
    +
    +  see:
    +    qh_getmergeset()
    +
    +  design:
    +    for each facet on facetlist
    +      for each untested neighbor of facet
    +        test facet and neighbor for convexity
    +        if non-convex
    +          append merge to qh.facet_mergeset
    +          mark one of the ridges as nonconvex
    +    sort qh.facet_mergeset by angle
    +*/
    +void qh_getmergeset_initial(qhT *qh, facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int nummerges;
    +
    +  qh->visit_id++;
    +  FORALLfacet_(facetlist) {
    +    facet->visitid= qh->visit_id;
    +    facet->tested= True;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh->visit_id) {
    +        if (qh_test_appendmerge(qh, facet, neighbor)) {
    +          FOREACHridge_(neighbor->ridges) {
    +            if (facet == otherfacet_(ridge, neighbor)) {
    +              ridge->nonconvex= True;
    +              break;    /* only one ridge is marked nonconvex */
    +            }
    +          }
    +        }
    +      }
    +    }
    +    FOREACHridge_(facet->ridges)
    +      ridge->tested= True;
    +  }
    +  nummerges= qh_setsize(qh, qh->facet_mergeset);
    +  if (qh->ANGLEmerge)
    +    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compareangle);
    +  else
    +    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_comparemerge);
    +  if (qh->POSTmerging) {
    +    zadd_(Zmergeinittot2, nummerges);
    +  }else {
    +    zadd_(Zmergeinittot, nummerges);
    +    zmax_(Zmergeinitmax, nummerges);
    +  }
    +  trace2((qh, qh->ferr, 2022, "qh_getmergeset_initial: %d merges found\n", nummerges));
    +} /* getmergeset_initial */
    +
    +
    +/*---------------------------------
    +
    +  qh_hashridge(qh, hashtable, hashsize, ridge, oldvertex )
    +    add ridge to hashtable without oldvertex
    +
    +  notes:
    +    assumes hashtable is large enough
    +
    +  design:
    +    determine hash value for ridge without oldvertex
    +    find next empty slot for ridge
    +*/
    +void qh_hashridge(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge, vertexT *oldvertex) {
    +  int hash;
    +  ridgeT *ridgeA;
    +
    +  hash= qh_gethash(qh, hashsize, ridge->vertices, qh->hull_dim-1, 0, oldvertex);
    +  while (True) {
    +    if (!(ridgeA= SETelemt_(hashtable, hash, ridgeT))) {
    +      SETelem_(hashtable, hash)= ridge;
    +      break;
    +    }else if (ridgeA == ridge)
    +      break;
    +    if (++hash == hashsize)
    +      hash= 0;
    +  }
    +} /* hashridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_hashridge_find(qh, hashtable, hashsize, ridge, vertex, oldvertex, hashslot )
    +    returns matching ridge without oldvertex in hashtable
    +      for ridge without vertex
    +    if oldvertex is NULL
    +      matches with any one skip
    +
    +  returns:
    +    matching ridge or NULL
    +    if no match,
    +      if ridge already in   table
    +        hashslot= -1
    +      else
    +        hashslot= next NULL index
    +
    +  notes:
    +    assumes hashtable is large enough
    +    can't match ridge to itself
    +
    +  design:
    +    get hash value for ridge without vertex
    +    for each hashslot
    +      return match if ridge matches ridgeA without oldvertex
    +*/
    +ridgeT *qh_hashridge_find(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge,
    +              vertexT *vertex, vertexT *oldvertex, int *hashslot) {
    +  int hash;
    +  ridgeT *ridgeA;
    +
    +  *hashslot= 0;
    +  zinc_(Zhashridge);
    +  hash= qh_gethash(qh, hashsize, ridge->vertices, qh->hull_dim-1, 0, vertex);
    +  while ((ridgeA= SETelemt_(hashtable, hash, ridgeT))) {
    +    if (ridgeA == ridge)
    +      *hashslot= -1;
    +    else {
    +      zinc_(Zhashridgetest);
    +      if (qh_setequal_except(ridge->vertices, vertex, ridgeA->vertices, oldvertex))
    +        return ridgeA;
    +    }
    +    if (++hash == hashsize)
    +      hash= 0;
    +  }
    +  if (!*hashslot)
    +    *hashslot= hash;
    +  return NULL;
    +} /* hashridge_find */
    +
    +
    +/*---------------------------------
    +
    +  qh_makeridges(qh, facet )
    +    creates explicit ridges between simplicial facets
    +
    +  returns:
    +    facet with ridges and without qh_MERGEridge
    +    ->simplicial is False
    +
    +  notes:
    +    allows qh_MERGEridge flag
    +    uses existing ridges
    +    duplicate neighbors ok if ridges already exist (qh_mergecycle_ridges)
    +
    +  see:
    +    qh_mergecycle_ridges()
    +
    +  design:
    +    look for qh_MERGEridge neighbors
    +    mark neighbors that already have ridges
    +    for each unprocessed neighbor of facet
    +      create a ridge for neighbor and facet
    +    if any qh_MERGEridge neighbors
    +      delete qh_MERGEridge flags (already handled by qh_mark_dupridges)
    +*/
    +void qh_makeridges(qhT *qh, facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int neighbor_i, neighbor_n;
    +  boolT toporient, mergeridge= False;
    +
    +  if (!facet->simplicial)
    +    return;
    +  trace4((qh, qh->ferr, 4027, "qh_makeridges: make ridges for f%d\n", facet->id));
    +  facet->simplicial= False;
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      mergeridge= True;
    +    else
    +      neighbor->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges)
    +    otherfacet_(ridge, facet)->seen= True;
    +  FOREACHneighbor_i_(qh, facet) {
    +    if (neighbor == qh_MERGEridge)
    +      continue;  /* fixed by qh_mark_dupridges */
    +    else if (!neighbor->seen) {  /* no current ridges */
    +      ridge= qh_newridge(qh);
    +      ridge->vertices= qh_setnew_delnthsorted(qh, facet->vertices, qh->hull_dim,
    +                                                          neighbor_i, 0);
    +      toporient= facet->toporient ^ (neighbor_i & 0x1);
    +      if (toporient) {
    +        ridge->top= facet;
    +        ridge->bottom= neighbor;
    +      }else {
    +        ridge->top= neighbor;
    +        ridge->bottom= facet;
    +      }
    +#if 0 /* this also works */
    +      flip= (facet->toporient ^ neighbor->toporient)^(skip1 & 0x1) ^ (skip2 & 0x1);
    +      if (facet->toporient ^ (skip1 & 0x1) ^ flip) {
    +        ridge->top= neighbor;
    +        ridge->bottom= facet;
    +      }else {
    +        ridge->top= facet;
    +        ridge->bottom= neighbor;
    +      }
    +#endif
    +      qh_setappend(qh, &(facet->ridges), ridge);
    +      qh_setappend(qh, &(neighbor->ridges), ridge);
    +    }
    +  }
    +  if (mergeridge) {
    +    while (qh_setdel(facet->neighbors, qh_MERGEridge))
    +      ; /* delete each one */
    +  }
    +} /* makeridges */
    +
    +
    +/*---------------------------------
    +
    +  qh_mark_dupridges(qh, facetlist )
    +    add duplicated ridges to qh.facet_mergeset
    +    facet->dupridge is true
    +
    +  returns:
    +    duplicate ridges on qh.facet_mergeset
    +    ->mergeridge/->mergeridge2 set
    +    duplicate ridges marked by qh_MERGEridge and both sides facet->dupridge
    +    no MERGEridges in neighbor sets
    +
    +  notes:
    +    duplicate ridges occur when the horizon is pinched,
    +        i.e. a subridge occurs in more than two horizon ridges.
    +    could rename vertices that pinch the horizon (thus removing subridge)
    +    uses qh.visit_id
    +
    +  design:
    +    for all facets on facetlist
    +      if facet contains a duplicate ridge
    +        for each neighbor of facet
    +          if neighbor marked qh_MERGEridge (one side of the merge)
    +            set facet->mergeridge
    +          else
    +            if neighbor contains a duplicate ridge
    +            and the back link is qh_MERGEridge
    +              append duplicate ridge to qh.facet_mergeset
    +   for each duplicate ridge
    +     make ridge sets in preparation for merging
    +     remove qh_MERGEridge from neighbor set
    +   for each duplicate ridge
    +     restore the missing neighbor from the neighbor set that was qh_MERGEridge
    +     add the missing ridge for this neighbor
    +*/
    +void qh_mark_dupridges(qhT *qh, facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  int nummerge=0;
    +  mergeT *merge, **mergep;
    +
    +
    +  trace4((qh, qh->ferr, 4028, "qh_mark_dupridges: identify duplicate ridges\n"));
    +  FORALLfacet_(facetlist) {
    +    if (facet->dupridge) {
    +      FOREACHneighbor_(facet) {
    +        if (neighbor == qh_MERGEridge) {
    +          facet->mergeridge= True;
    +          continue;
    +        }
    +        if (neighbor->dupridge
    +        && !qh_setin(neighbor->neighbors, facet)) { /* qh_MERGEridge */
    +          qh_appendmergeset(qh, facet, neighbor, MRGridge, NULL);
    +          facet->mergeridge2= True;
    +          facet->mergeridge= True;
    +          nummerge++;
    +        }
    +      }
    +    }
    +  }
    +  if (!nummerge)
    +    return;
    +  FORALLfacet_(facetlist) {            /* gets rid of qh_MERGEridge */
    +    if (facet->mergeridge && !facet->mergeridge2)
    +      qh_makeridges(qh, facet);
    +  }
    +  FOREACHmerge_(qh->facet_mergeset) {   /* restore the missing neighbors */
    +    if (merge->type == MRGridge) {
    +      qh_setappend(qh, &merge->facet2->neighbors, merge->facet1);
    +      qh_makeridges(qh, merge->facet1);   /* and the missing ridges */
    +    }
    +  }
    +  trace1((qh, qh->ferr, 1012, "qh_mark_dupridges: found %d duplicated ridges\n",
    +                nummerge));
    +} /* mark_dupridges */
    +
    +/*---------------------------------
    +
    +  qh_maydropneighbor(qh, facet )
    +    drop neighbor relationship if no ridge between facet and neighbor
    +
    +  returns:
    +    neighbor sets updated
    +    appends degenerate facets to qh.facet_mergeset
    +
    +  notes:
    +    won't cause redundant facets since vertex inclusion is the same
    +    may drop vertex and neighbor if no ridge
    +    uses qh.visit_id
    +
    +  design:
    +    visit all neighbors with ridges
    +    for each unvisited neighbor of facet
    +      delete neighbor and facet from the neighbor sets
    +      if neighbor becomes degenerate
    +        append neighbor to qh.degen_mergeset
    +    if facet is degenerate
    +      append facet to qh.degen_mergeset
    +*/
    +void qh_maydropneighbor(qhT *qh, facetT *facet) {
    +  ridgeT *ridge, **ridgep;
    +  realT angledegen= qh_ANGLEdegen;
    +  facetT *neighbor, **neighborp;
    +
    +  qh->visit_id++;
    +  trace4((qh, qh->ferr, 4029, "qh_maydropneighbor: test f%d for no ridges to a neighbor\n",
    +          facet->id));
    +  FOREACHridge_(facet->ridges) {
    +    ridge->top->visitid= qh->visit_id;
    +    ridge->bottom->visitid= qh->visit_id;
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (neighbor->visitid != qh->visit_id) {
    +      trace0((qh, qh->ferr, 17, "qh_maydropneighbor: facets f%d and f%d are no longer neighbors during p%d\n",
    +            facet->id, neighbor->id, qh->furthest_id));
    +      zinc_(Zdropneighbor);
    +      qh_setdel(facet->neighbors, neighbor);
    +      neighborp--;  /* repeat, deleted a neighbor */
    +      qh_setdel(neighbor->neighbors, facet);
    +      if (qh_setsize(qh, neighbor->neighbors) < qh->hull_dim) {
    +        zinc_(Zdropdegen);
    +        qh_appendmergeset(qh, neighbor, neighbor, MRGdegen, &angledegen);
    +        trace2((qh, qh->ferr, 2023, "qh_maydropneighbors: f%d is degenerate.\n", neighbor->id));
    +      }
    +    }
    +  }
    +  if (qh_setsize(qh, facet->neighbors) < qh->hull_dim) {
    +    zinc_(Zdropdegen);
    +    qh_appendmergeset(qh, facet, facet, MRGdegen, &angledegen);
    +    trace2((qh, qh->ferr, 2024, "qh_maydropneighbors: f%d is degenerate.\n", facet->id));
    +  }
    +} /* maydropneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_merge_degenredundant(qh)
    +    merge all degenerate and redundant facets
    +    qh.degen_mergeset contains merges from qh_degen_redundant_neighbors()
    +
    +  returns:
    +    number of merges performed
    +    resets facet->degenerate/redundant
    +    if deleted (visible) facet has no neighbors
    +      sets ->f.replace to NULL
    +
    +  notes:
    +    redundant merges happen before degenerate ones
    +    merging and renaming vertices can result in degen/redundant facets
    +
    +  design:
    +    for each merge on qh.degen_mergeset
    +      if redundant merge
    +        if non-redundant facet merged into redundant facet
    +          recheck facet for redundancy
    +        else
    +          merge redundant facet into other facet
    +*/
    +int qh_merge_degenredundant(qhT *qh) {
    +  int size;
    +  mergeT *merge;
    +  facetT *bestneighbor, *facet1, *facet2;
    +  realT dist, mindist, maxdist;
    +  vertexT *vertex, **vertexp;
    +  int nummerges= 0;
    +  mergeType mergetype;
    +
    +  while ((merge= (mergeT*)qh_setdellast(qh->degen_mergeset))) {
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    mergetype= merge->type;
    +    qh_memfree(qh, merge, (int)sizeof(mergeT));
    +    if (facet1->visible)
    +      continue;
    +    facet1->degenerate= False;
    +    facet1->redundant= False;
    +    if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +      qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +    if (mergetype == MRGredundant) {
    +      zinc_(Zneighbor);
    +      while (facet2->visible) {
    +        if (!facet2->f.replace) {
    +          qh_fprintf(qh, qh->ferr, 6097, "qhull internal error (qh_merge_degenredunant): f%d redundant but f%d has no replacement\n",
    +               facet1->id, facet2->id);
    +          qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +        }
    +        facet2= facet2->f.replace;
    +      }
    +      if (facet1 == facet2) {
    +        qh_degen_redundant_facet(qh, facet1); /* in case of others */
    +        continue;
    +      }
    +      trace2((qh, qh->ferr, 2025, "qh_merge_degenredundant: facet f%d is contained in f%d, will merge\n",
    +            facet1->id, facet2->id));
    +      qh_mergefacet(qh, facet1, facet2, NULL, NULL, !qh_MERGEapex);
    +      /* merge distance is already accounted for */
    +      nummerges++;
    +    }else {  /* mergetype == MRGdegen, other merges may have fixed */
    +      if (!(size= qh_setsize(qh, facet1->neighbors))) {
    +        zinc_(Zdelfacetdup);
    +        trace2((qh, qh->ferr, 2026, "qh_merge_degenredundant: facet f%d has no neighbors.  Deleted\n", facet1->id));
    +        qh_willdelete(qh, facet1, NULL);
    +        FOREACHvertex_(facet1->vertices) {
    +          qh_setdel(vertex->neighbors, facet1);
    +          if (!SETfirst_(vertex->neighbors)) {
    +            zinc_(Zdegenvertex);
    +            trace2((qh, qh->ferr, 2027, "qh_merge_degenredundant: deleted v%d because f%d has no neighbors\n",
    +                 vertex->id, facet1->id));
    +            vertex->deleted= True;
    +            qh_setappend(qh, &qh->del_vertices, vertex);
    +          }
    +        }
    +        nummerges++;
    +      }else if (size < qh->hull_dim) {
    +        bestneighbor= qh_findbestneighbor(qh, facet1, &dist, &mindist, &maxdist);
    +        trace2((qh, qh->ferr, 2028, "qh_merge_degenredundant: facet f%d has %d neighbors, merge into f%d dist %2.2g\n",
    +              facet1->id, size, bestneighbor->id, dist));
    +        qh_mergefacet(qh, facet1, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +        nummerges++;
    +        if (qh->PRINTstatistics) {
    +          zinc_(Zdegen);
    +          wadd_(Wdegentot, dist);
    +          wmax_(Wdegenmax, dist);
    +        }
    +      } /* else, another merge fixed the degeneracy and redundancy tested */
    +    }
    +  }
    +  return nummerges;
    +} /* merge_degenredundant */
    +
    +/*---------------------------------
    +
    +  qh_merge_nonconvex(qh, facet1, facet2, mergetype )
    +    remove non-convex ridge between facet1 into facet2
    +    mergetype gives why the facet's are non-convex
    +
    +  returns:
    +    merges one of the facets into the best neighbor
    +
    +  design:
    +    if one of the facets is a new facet
    +      prefer merging new facet into old facet
    +    find best neighbors for both facets
    +    merge the nearest facet into its best neighbor
    +    update the statistics
    +*/
    +void qh_merge_nonconvex(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype) {
    +  facetT *bestfacet, *bestneighbor, *neighbor;
    +  realT dist, dist2, mindist, mindist2, maxdist, maxdist2;
    +
    +  if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +    qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +  trace3((qh, qh->ferr, 3003, "qh_merge_nonconvex: merge #%d for f%d and f%d type %d\n",
    +      zzval_(Ztotmerge) + 1, facet1->id, facet2->id, mergetype));
    +  /* concave or coplanar */
    +  if (!facet1->newfacet) {
    +    bestfacet= facet2;   /* avoid merging old facet if new is ok */
    +    facet2= facet1;
    +    facet1= bestfacet;
    +  }else
    +    bestfacet= facet1;
    +  bestneighbor= qh_findbestneighbor(qh, bestfacet, &dist, &mindist, &maxdist);
    +  neighbor= qh_findbestneighbor(qh, facet2, &dist2, &mindist2, &maxdist2);
    +  if (dist < dist2) {
    +    qh_mergefacet(qh, bestfacet, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +  }else if (qh->AVOIDold && !facet2->newfacet
    +  && ((mindist >= -qh->MAXcoplanar && maxdist <= qh->max_outside)
    +       || dist * 1.5 < dist2)) {
    +    zinc_(Zavoidold);
    +    wadd_(Wavoidoldtot, dist);
    +    wmax_(Wavoidoldmax, dist);
    +    trace2((qh, qh->ferr, 2029, "qh_merge_nonconvex: avoid merging old facet f%d dist %2.2g.  Use f%d dist %2.2g instead\n",
    +           facet2->id, dist2, facet1->id, dist2));
    +    qh_mergefacet(qh, bestfacet, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +  }else {
    +    qh_mergefacet(qh, facet2, neighbor, &mindist2, &maxdist2, !qh_MERGEapex);
    +    dist= dist2;
    +  }
    +  if (qh->PRINTstatistics) {
    +    if (mergetype == MRGanglecoplanar) {
    +      zinc_(Zacoplanar);
    +      wadd_(Wacoplanartot, dist);
    +      wmax_(Wacoplanarmax, dist);
    +    }else if (mergetype == MRGconcave) {
    +      zinc_(Zconcave);
    +      wadd_(Wconcavetot, dist);
    +      wmax_(Wconcavemax, dist);
    +    }else { /* MRGcoplanar */
    +      zinc_(Zcoplanar);
    +      wadd_(Wcoplanartot, dist);
    +      wmax_(Wcoplanarmax, dist);
    +    }
    +  }
    +} /* merge_nonconvex */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle(qh, samecycle, newfacet )
    +    merge a cycle of facets starting at samecycle into a newfacet
    +    newfacet is a horizon facet with ->normal
    +    samecycle facets are simplicial from an apex
    +
    +  returns:
    +    initializes vertex neighbors on first merge
    +    samecycle deleted (placed on qh.visible_list)
    +    newfacet at end of qh.facet_list
    +    deleted vertices on qh.del_vertices
    +
    +  see:
    +    qh_mergefacet()
    +    called by qh_mergecycle_all() for multiple, same cycle facets
    +
    +  design:
    +    make vertex neighbors if necessary
    +    make ridges for newfacet
    +    merge neighbor sets of samecycle into newfacet
    +    merge ridges of samecycle into newfacet
    +    merge vertex neighbors of samecycle into newfacet
    +    make apex of samecycle the apex of newfacet
    +    if newfacet wasn't a new facet
    +      add its vertices to qh.newvertex_list
    +    delete samecycle facets a make newfacet a newfacet
    +*/
    +void qh_mergecycle(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  int traceonce= False, tracerestore= 0;
    +  vertexT *apex;
    +#ifndef qh_NOtrace
    +  facetT *same;
    +#endif
    +
    +  if (newfacet->tricoplanar) {
    +    if (!qh->TRInormals) {
    +      qh_fprintf(qh, qh->ferr, 6224, "Qhull internal error (qh_mergecycle): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit(qh, qh_ERRqhull, newfacet, NULL);
    +    }
    +    newfacet->tricoplanar= False;
    +    newfacet->keepcentrum= False;
    +  }
    +  if (!qh->VERTEXneighbors)
    +    qh_vertexneighbors(qh);
    +  zzinc_(Ztotmerge);
    +  if (qh->REPORTfreq2 && qh->POSTmerging) {
    +    if (zzval_(Ztotmerge) > qh->mergereport + qh->REPORTfreq2)
    +      qh_tracemerging(qh);
    +  }
    +#ifndef qh_NOtrace
    +  if (qh->TRACEmerge == zzval_(Ztotmerge))
    +    qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +  trace2((qh, qh->ferr, 2030, "qh_mergecycle: merge #%d for facets from cycle f%d into coplanar horizon f%d\n",
    +        zzval_(Ztotmerge), samecycle->id, newfacet->id));
    +  if (newfacet == qh->tracefacet) {
    +    tracerestore= qh->IStracing;
    +    qh->IStracing= 4;
    +    qh_fprintf(qh, qh->ferr, 8068, "qh_mergecycle: ========= trace merge %d of samecycle %d into trace f%d, furthest is p%d\n",
    +               zzval_(Ztotmerge), samecycle->id, newfacet->id,  qh->furthest_id);
    +    traceonce= True;
    +  }
    +  if (qh->IStracing >=4) {
    +    qh_fprintf(qh, qh->ferr, 8069, "  same cycle:");
    +    FORALLsame_cycle_(samecycle)
    +      qh_fprintf(qh, qh->ferr, 8070, " f%d", same->id);
    +    qh_fprintf(qh, qh->ferr, 8071, "\n");
    +  }
    +  if (qh->IStracing >=4)
    +    qh_errprint(qh, "MERGING CYCLE", samecycle, newfacet, NULL, NULL);
    +#endif /* !qh_NOtrace */
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  qh_makeridges(qh, newfacet);
    +  qh_mergecycle_neighbors(qh, samecycle, newfacet);
    +  qh_mergecycle_ridges(qh, samecycle, newfacet);
    +  qh_mergecycle_vneighbors(qh, samecycle, newfacet);
    +  if (SETfirstt_(newfacet->vertices, vertexT) != apex)
    +    qh_setaddnth(qh, &newfacet->vertices, 0, apex);  /* apex has last id */
    +  if (!newfacet->newfacet)
    +    qh_newvertices(qh, newfacet->vertices);
    +  qh_mergecycle_facets(qh, samecycle, newfacet);
    +  qh_tracemerge(qh, samecycle, newfacet);
    +  /* check for degen_redundant_neighbors after qh_forcedmerges() */
    +  if (traceonce) {
    +    qh_fprintf(qh, qh->ferr, 8072, "qh_mergecycle: end of trace facet\n");
    +    qh->IStracing= tracerestore;
    +  }
    +} /* mergecycle */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_all(qh, facetlist, wasmerge )
    +    merge all samecycles of coplanar facets into horizon
    +    don't merge facets with ->mergeridge (these already have ->normal)
    +    all facets are simplicial from apex
    +    all facet->cycledone == False
    +
    +  returns:
    +    all newfacets merged into coplanar horizon facets
    +    deleted vertices on  qh.del_vertices
    +    sets wasmerge if any merge
    +
    +  see:
    +    calls qh_mergecycle for multiple, same cycle facets
    +
    +  design:
    +    for each facet on facetlist
    +      skip facets with duplicate ridges and normals
    +      check that facet is in a samecycle (->mergehorizon)
    +      if facet only member of samecycle
    +        sets vertex->delridge for all vertices except apex
    +        merge facet into horizon
    +      else
    +        mark all facets in samecycle
    +        remove facets with duplicate ridges from samecycle
    +        merge samecycle into horizon (deletes facets from facetlist)
    +*/
    +void qh_mergecycle_all(qhT *qh, facetT *facetlist, boolT *wasmerge) {
    +  facetT *facet, *same, *prev, *horizon;
    +  facetT *samecycle= NULL, *nextfacet, *nextsame;
    +  vertexT *apex, *vertex, **vertexp;
    +  int cycles=0, total=0, facets, nummerge;
    +
    +  trace2((qh, qh->ferr, 2031, "qh_mergecycle_all: begin\n"));
    +  for (facet= facetlist; facet && (nextfacet= facet->next); facet= nextfacet) {
    +    if (facet->normal)
    +      continue;
    +    if (!facet->mergehorizon) {
    +      qh_fprintf(qh, qh->ferr, 6225, "Qhull internal error (qh_mergecycle_all): f%d without normal\n", facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +    horizon= SETfirstt_(facet->neighbors, facetT);
    +    if (facet->f.samecycle == facet) {
    +      zinc_(Zonehorizon);
    +      /* merge distance done in qh_findhorizon */
    +      apex= SETfirstt_(facet->vertices, vertexT);
    +      FOREACHvertex_(facet->vertices) {
    +        if (vertex != apex)
    +          vertex->delridge= True;
    +      }
    +      horizon->f.newcycle= NULL;
    +      qh_mergefacet(qh, facet, horizon, NULL, NULL, qh_MERGEapex);
    +    }else {
    +      samecycle= facet;
    +      facets= 0;
    +      prev= facet;
    +      for (same= facet->f.samecycle; same;  /* FORALLsame_cycle_(facet) */
    +           same= (same == facet ? NULL :nextsame)) { /* ends at facet */
    +        nextsame= same->f.samecycle;
    +        if (same->cycledone || same->visible)
    +          qh_infiniteloop(qh, same);
    +        same->cycledone= True;
    +        if (same->normal) {
    +          prev->f.samecycle= same->f.samecycle; /* unlink ->mergeridge */
    +          same->f.samecycle= NULL;
    +        }else {
    +          prev= same;
    +          facets++;
    +        }
    +      }
    +      while (nextfacet && nextfacet->cycledone)  /* will delete samecycle */
    +        nextfacet= nextfacet->next;
    +      horizon->f.newcycle= NULL;
    +      qh_mergecycle(qh, samecycle, horizon);
    +      nummerge= horizon->nummerge + facets;
    +      if (nummerge > qh_MAXnummerge)
    +        horizon->nummerge= qh_MAXnummerge;
    +      else
    +        horizon->nummerge= (short unsigned int)nummerge;
    +      zzinc_(Zcyclehorizon);
    +      total += facets;
    +      zzadd_(Zcyclefacettot, facets);
    +      zmax_(Zcyclefacetmax, facets);
    +    }
    +    cycles++;
    +  }
    +  if (cycles)
    +    *wasmerge= True;
    +  trace1((qh, qh->ferr, 1013, "qh_mergecycle_all: merged %d same cycles or facets into coplanar horizons\n", cycles));
    +} /* mergecycle_all */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_facets(qh, samecycle, newfacet )
    +    finish merge of samecycle into newfacet
    +
    +  returns:
    +    samecycle prepended to visible_list for later deletion and partitioning
    +      each facet->f.replace == newfacet
    +
    +    newfacet moved to end of qh.facet_list
    +      makes newfacet a newfacet (get's facet1->id if it was old)
    +      sets newfacet->newmerge
    +      clears newfacet->center (unless merging into a large facet)
    +      clears newfacet->tested and ridge->tested for facet1
    +
    +    adds neighboring facets to facet_mergeset if redundant or degenerate
    +
    +  design:
    +    make newfacet a new facet and set its flags
    +    move samecycle facets to qh.visible_list for later deletion
    +    unless newfacet is large
    +      remove its centrum
    +*/
    +void qh_mergecycle_facets(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *next;
    +
    +  trace4((qh, qh->ferr, 4030, "qh_mergecycle_facets: make newfacet new and samecycle deleted\n"));
    +  qh_removefacet(qh, newfacet);  /* append as a newfacet to end of qh->facet_list */
    +  qh_appendfacet(qh, newfacet);
    +  newfacet->newfacet= True;
    +  newfacet->simplicial= False;
    +  newfacet->newmerge= True;
    +
    +  for (same= samecycle->f.samecycle; same; same= (same == samecycle ?  NULL : next)) {
    +    next= same->f.samecycle;  /* reused by willdelete */
    +    qh_willdelete(qh, same, newfacet);
    +  }
    +  if (newfacet->center
    +      && qh_setsize(qh, newfacet->vertices) <= qh->hull_dim + qh_MAXnewcentrum) {
    +    qh_memfree(qh, newfacet->center, qh->normal_size);
    +    newfacet->center= NULL;
    +  }
    +  trace3((qh, qh->ferr, 3004, "qh_mergecycle_facets: merged facets from cycle f%d into f%d\n",
    +             samecycle->id, newfacet->id));
    +} /* mergecycle_facets */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_neighbors(qh, samecycle, newfacet )
    +    add neighbors for samecycle facets to newfacet
    +
    +  returns:
    +    newfacet with updated neighbors and vice-versa
    +    newfacet has ridges
    +    all neighbors of newfacet marked with qh.visit_id
    +    samecycle facets marked with qh.visit_id-1
    +    ridges updated for simplicial neighbors of samecycle with a ridge
    +
    +  notes:
    +    assumes newfacet not in samecycle
    +    usually, samecycle facets are new, simplicial facets without internal ridges
    +      not so if horizon facet is coplanar to two different samecycles
    +
    +  see:
    +    qh_mergeneighbors()
    +
    +  design:
    +    check samecycle
    +    delete neighbors from newfacet that are also in samecycle
    +    for each neighbor of a facet in samecycle
    +      if neighbor is simplicial
    +        if first visit
    +          move the neighbor relation to newfacet
    +          update facet links for its ridges
    +        else
    +          make ridges for neighbor
    +          remove samecycle reference
    +      else
    +        update neighbor sets
    +*/
    +void qh_mergecycle_neighbors(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *neighbor, **neighborp;
    +  int delneighbors= 0, newneighbors= 0;
    +  unsigned int samevisitid;
    +  ridgeT *ridge, **ridgep;
    +
    +  samevisitid= ++qh->visit_id;
    +  FORALLsame_cycle_(samecycle) {
    +    if (same->visitid == samevisitid || same->visible)
    +      qh_infiniteloop(qh, samecycle);
    +    same->visitid= samevisitid;
    +  }
    +  newfacet->visitid= ++qh->visit_id;
    +  trace4((qh, qh->ferr, 4031, "qh_mergecycle_neighbors: delete shared neighbors from newfacet\n"));
    +  FOREACHneighbor_(newfacet) {
    +    if (neighbor->visitid == samevisitid) {
    +      SETref_(neighbor)= NULL;  /* samecycle neighbors deleted */
    +      delneighbors++;
    +    }else
    +      neighbor->visitid= qh->visit_id;
    +  }
    +  qh_setcompact(qh, newfacet->neighbors);
    +
    +  trace4((qh, qh->ferr, 4032, "qh_mergecycle_neighbors: update neighbors\n"));
    +  FORALLsame_cycle_(samecycle) {
    +    FOREACHneighbor_(same) {
    +      if (neighbor->visitid == samevisitid)
    +        continue;
    +      if (neighbor->simplicial) {
    +        if (neighbor->visitid != qh->visit_id) {
    +          qh_setappend(qh, &newfacet->neighbors, neighbor);
    +          qh_setreplace(qh, neighbor->neighbors, same, newfacet);
    +          newneighbors++;
    +          neighbor->visitid= qh->visit_id;
    +          FOREACHridge_(neighbor->ridges) { /* update ridge in case of qh_makeridges */
    +            if (ridge->top == same) {
    +              ridge->top= newfacet;
    +              break;
    +            }else if (ridge->bottom == same) {
    +              ridge->bottom= newfacet;
    +              break;
    +            }
    +          }
    +        }else {
    +          qh_makeridges(qh, neighbor);
    +          qh_setdel(neighbor->neighbors, same);
    +          /* same can't be horizon facet for neighbor */
    +        }
    +      }else { /* non-simplicial neighbor */
    +        qh_setdel(neighbor->neighbors, same);
    +        if (neighbor->visitid != qh->visit_id) {
    +          qh_setappend(qh, &neighbor->neighbors, newfacet);
    +          qh_setappend(qh, &newfacet->neighbors, neighbor);
    +          neighbor->visitid= qh->visit_id;
    +          newneighbors++;
    +        }
    +      }
    +    }
    +  }
    +  trace2((qh, qh->ferr, 2032, "qh_mergecycle_neighbors: deleted %d neighbors and added %d\n",
    +             delneighbors, newneighbors));
    +} /* mergecycle_neighbors */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_ridges(qh, samecycle, newfacet )
    +    add ridges/neighbors for facets in samecycle to newfacet
    +    all new/old neighbors of newfacet marked with qh.visit_id
    +    facets in samecycle marked with qh.visit_id-1
    +    newfacet marked with qh.visit_id
    +
    +  returns:
    +    newfacet has merged ridges
    +
    +  notes:
    +    ridge already updated for simplicial neighbors of samecycle with a ridge
    +
    +  see:
    +    qh_mergeridges()
    +    qh_makeridges()
    +
    +  design:
    +    remove ridges between newfacet and samecycle
    +    for each facet in samecycle
    +      for each ridge in facet
    +        update facet pointers in ridge
    +        skip ridges processed in qh_mergecycle_neighors
    +        free ridges between newfacet and samecycle
    +        free ridges between facets of samecycle (on 2nd visit)
    +        append remaining ridges to newfacet
    +      if simpilicial facet
    +        for each neighbor of facet
    +          if simplicial facet
    +          and not samecycle facet or newfacet
    +            make ridge between neighbor and newfacet
    +*/
    +void qh_mergecycle_ridges(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *neighbor= NULL;
    +  int numold=0, numnew=0;
    +  int neighbor_i, neighbor_n;
    +  unsigned int samevisitid;
    +  ridgeT *ridge, **ridgep;
    +  boolT toporient;
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  trace4((qh, qh->ferr, 4033, "qh_mergecycle_ridges: delete shared ridges from newfacet\n"));
    +  samevisitid= qh->visit_id -1;
    +  FOREACHridge_(newfacet->ridges) {
    +    neighbor= otherfacet_(ridge, newfacet);
    +    if (neighbor->visitid == samevisitid)
    +      SETref_(ridge)= NULL; /* ridge free'd below */
    +  }
    +  qh_setcompact(qh, newfacet->ridges);
    +
    +  trace4((qh, qh->ferr, 4034, "qh_mergecycle_ridges: add ridges to newfacet\n"));
    +  FORALLsame_cycle_(samecycle) {
    +    FOREACHridge_(same->ridges) {
    +      if (ridge->top == same) {
    +        ridge->top= newfacet;
    +        neighbor= ridge->bottom;
    +      }else if (ridge->bottom == same) {
    +        ridge->bottom= newfacet;
    +        neighbor= ridge->top;
    +      }else if (ridge->top == newfacet || ridge->bottom == newfacet) {
    +        qh_setappend(qh, &newfacet->ridges, ridge);
    +        numold++;  /* already set by qh_mergecycle_neighbors */
    +        continue;
    +      }else {
    +        qh_fprintf(qh, qh->ferr, 6098, "qhull internal error (qh_mergecycle_ridges): bad ridge r%d\n", ridge->id);
    +        qh_errexit(qh, qh_ERRqhull, NULL, ridge);
    +      }
    +      if (neighbor == newfacet) {
    +        qh_setfree(qh, &(ridge->vertices));
    +        qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
    +        numold++;
    +      }else if (neighbor->visitid == samevisitid) {
    +        qh_setdel(neighbor->ridges, ridge);
    +        qh_setfree(qh, &(ridge->vertices));
    +        qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
    +        numold++;
    +      }else {
    +        qh_setappend(qh, &newfacet->ridges, ridge);
    +        numold++;
    +      }
    +    }
    +    if (same->ridges)
    +      qh_settruncate(qh, same->ridges, 0);
    +    if (!same->simplicial)
    +      continue;
    +    FOREACHneighbor_i_(qh, same) {       /* note: !newfact->simplicial */
    +      if (neighbor->visitid != samevisitid && neighbor->simplicial) {
    +        ridge= qh_newridge(qh);
    +        ridge->vertices= qh_setnew_delnthsorted(qh, same->vertices, qh->hull_dim,
    +                                                          neighbor_i, 0);
    +        toporient= same->toporient ^ (neighbor_i & 0x1);
    +        if (toporient) {
    +          ridge->top= newfacet;
    +          ridge->bottom= neighbor;
    +        }else {
    +          ridge->top= neighbor;
    +          ridge->bottom= newfacet;
    +        }
    +        qh_setappend(qh, &(newfacet->ridges), ridge);
    +        qh_setappend(qh, &(neighbor->ridges), ridge);
    +        numnew++;
    +      }
    +    }
    +  }
    +
    +  trace2((qh, qh->ferr, 2033, "qh_mergecycle_ridges: found %d old ridges and %d new ones\n",
    +             numold, numnew));
    +} /* mergecycle_ridges */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_vneighbors(qh, samecycle, newfacet )
    +    create vertex neighbors for newfacet from vertices of facets in samecycle
    +    samecycle marked with visitid == qh.visit_id - 1
    +
    +  returns:
    +    newfacet vertices with updated neighbors
    +    marks newfacet with qh.visit_id-1
    +    deletes vertices that are merged away
    +    sets delridge on all vertices (faster here than in mergecycle_ridges)
    +
    +  see:
    +    qh_mergevertex_neighbors()
    +
    +  design:
    +    for each vertex of samecycle facet
    +      set vertex->delridge
    +      delete samecycle facets from vertex neighbors
    +      append newfacet to vertex neighbors
    +      if vertex only in newfacet
    +        delete it from newfacet
    +        add it to qh.del_vertices for later deletion
    +*/
    +void qh_mergecycle_vneighbors(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  facetT *neighbor, **neighborp;
    +  unsigned int mergeid;
    +  vertexT *vertex, **vertexp, *apex;
    +  setT *vertices;
    +
    +  trace4((qh, qh->ferr, 4035, "qh_mergecycle_vneighbors: update vertex neighbors for newfacet\n"));
    +  mergeid= qh->visit_id - 1;
    +  newfacet->visitid= mergeid;
    +  vertices= qh_basevertices(qh, samecycle); /* temp */
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  qh_setappend(qh, &vertices, apex);
    +  FOREACHvertex_(vertices) {
    +    vertex->delridge= True;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->visitid == mergeid)
    +        SETref_(neighbor)= NULL;
    +    }
    +    qh_setcompact(qh, vertex->neighbors);
    +    qh_setappend(qh, &vertex->neighbors, newfacet);
    +    if (!SETsecond_(vertex->neighbors)) {
    +      zinc_(Zcyclevertex);
    +      trace2((qh, qh->ferr, 2034, "qh_mergecycle_vneighbors: deleted v%d when merging cycle f%d into f%d\n",
    +        vertex->id, samecycle->id, newfacet->id));
    +      qh_setdelsorted(newfacet->vertices, vertex);
    +      vertex->deleted= True;
    +      qh_setappend(qh, &qh->del_vertices, vertex);
    +    }
    +  }
    +  qh_settempfree(qh, &vertices);
    +  trace3((qh, qh->ferr, 3005, "qh_mergecycle_vneighbors: merged vertices from cycle f%d into f%d\n",
    +             samecycle->id, newfacet->id));
    +} /* mergecycle_vneighbors */
    +
    +/*---------------------------------
    +
    +  qh_mergefacet(qh, facet1, facet2, mindist, maxdist, mergeapex )
    +    merges facet1 into facet2
    +    mergeapex==qh_MERGEapex if merging new facet into coplanar horizon
    +
    +  returns:
    +    qh.max_outside and qh.min_vertex updated
    +    initializes vertex neighbors on first merge
    +
    +  returns:
    +    facet2 contains facet1's vertices, neighbors, and ridges
    +      facet2 moved to end of qh.facet_list
    +      makes facet2 a newfacet
    +      sets facet2->newmerge set
    +      clears facet2->center (unless merging into a large facet)
    +      clears facet2->tested and ridge->tested for facet1
    +
    +    facet1 prepended to visible_list for later deletion and partitioning
    +      facet1->f.replace == facet2
    +
    +    adds neighboring facets to facet_mergeset if redundant or degenerate
    +
    +  notes:
    +    mindist/maxdist may be NULL (only if both NULL)
    +    traces merge if fmax_(maxdist,-mindist) > TRACEdist
    +
    +  see:
    +    qh_mergecycle()
    +
    +  design:
    +    trace merge and check for degenerate simplex
    +    make ridges for both facets
    +    update qh.max_outside, qh.max_vertex, qh.min_vertex
    +    update facet2->maxoutside and keepcentrum
    +    update facet2->nummerge
    +    update tested flags for facet2
    +    if facet1 is simplicial
    +      merge facet1 into facet2
    +    else
    +      merge facet1's neighbors into facet2
    +      merge facet1's ridges into facet2
    +      merge facet1's vertices into facet2
    +      merge facet1's vertex neighbors into facet2
    +      add facet2's vertices to qh.new_vertexlist
    +      unless qh_MERGEapex
    +        test facet2 for degenerate or redundant neighbors
    +      move facet1 to qh.visible_list for later deletion
    +      move facet2 to end of qh.newfacet_list
    +*/
    +void qh_mergefacet(qhT *qh, facetT *facet1, facetT *facet2, realT *mindist, realT *maxdist, boolT mergeapex) {
    +  boolT traceonce= False;
    +  vertexT *vertex, **vertexp;
    +  int tracerestore=0, nummerge;
    +
    +  if (facet1->tricoplanar || facet2->tricoplanar) {
    +    if (!qh->TRInormals) {
    +      qh_fprintf(qh, qh->ferr, 6226, "Qhull internal error (qh_mergefacet): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +    }
    +    if (facet2->tricoplanar) {
    +      facet2->tricoplanar= False;
    +      facet2->keepcentrum= False;
    +    }
    +  }
    +  zzinc_(Ztotmerge);
    +  if (qh->REPORTfreq2 && qh->POSTmerging) {
    +    if (zzval_(Ztotmerge) > qh->mergereport + qh->REPORTfreq2)
    +      qh_tracemerging(qh);
    +  }
    +#ifndef qh_NOtrace
    +  if (qh->build_cnt >= qh->RERUN) {
    +    if (mindist && (-*mindist > qh->TRACEdist || *maxdist > qh->TRACEdist)) {
    +      tracerestore= 0;
    +      qh->IStracing= qh->TRACElevel;
    +      traceonce= True;
    +      qh_fprintf(qh, qh->ferr, 8075, "qh_mergefacet: ========= trace wide merge #%d(%2.2g) for f%d into f%d, last point was p%d\n", zzval_(Ztotmerge),
    +             fmax_(-*mindist, *maxdist), facet1->id, facet2->id, qh->furthest_id);
    +    }else if (facet1 == qh->tracefacet || facet2 == qh->tracefacet) {
    +      tracerestore= qh->IStracing;
    +      qh->IStracing= 4;
    +      traceonce= True;
    +      qh_fprintf(qh, qh->ferr, 8076, "qh_mergefacet: ========= trace merge #%d involving f%d, furthest is p%d\n",
    +                 zzval_(Ztotmerge), qh->tracefacet_id,  qh->furthest_id);
    +    }
    +  }
    +  if (qh->IStracing >= 2) {
    +    realT mergemin= -2;
    +    realT mergemax= -2;
    +
    +    if (mindist) {
    +      mergemin= *mindist;
    +      mergemax= *maxdist;
    +    }
    +    qh_fprintf(qh, qh->ferr, 8077, "qh_mergefacet: #%d merge f%d into f%d, mindist= %2.2g, maxdist= %2.2g\n",
    +    zzval_(Ztotmerge), facet1->id, facet2->id, mergemin, mergemax);
    +  }
    +#endif /* !qh_NOtrace */
    +  if (facet1 == facet2 || facet1->visible || facet2->visible) {
    +    qh_fprintf(qh, qh->ferr, 6099, "qhull internal error (qh_mergefacet): either f%d and f%d are the same or one is a visible facet\n",
    +             facet1->id, facet2->id);
    +    qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +  }
    +  if (qh->num_facets - qh->num_visible <= qh->hull_dim + 1) {
    +    qh_fprintf(qh, qh->ferr, 6227, "\n\
    +qhull precision error: Only %d facets remain.  Can not merge another\n\
    +pair.  The input is too degenerate or the convexity constraints are\n\
    +too strong.\n", qh->hull_dim+1);
    +    if (qh->hull_dim >= 5 && !qh->MERGEexact)
    +      qh_fprintf(qh, qh->ferr, 8079, "Option 'Qx' may avoid this problem.\n");
    +    qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +  }
    +  if (!qh->VERTEXneighbors)
    +    qh_vertexneighbors(qh);
    +  qh_makeridges(qh, facet1);
    +  qh_makeridges(qh, facet2);
    +  if (qh->IStracing >=4)
    +    qh_errprint(qh, "MERGING", facet1, facet2, NULL, NULL);
    +  if (mindist) {
    +    maximize_(qh->max_outside, *maxdist);
    +    maximize_(qh->max_vertex, *maxdist);
    +#if qh_MAXoutside
    +    maximize_(facet2->maxoutside, *maxdist);
    +#endif
    +    minimize_(qh->min_vertex, *mindist);
    +    if (!facet2->keepcentrum
    +    && (*maxdist > qh->WIDEfacet || *mindist < -qh->WIDEfacet)) {
    +      facet2->keepcentrum= True;
    +      zinc_(Zwidefacet);
    +    }
    +  }
    +  nummerge= facet1->nummerge + facet2->nummerge + 1;
    +  if (nummerge >= qh_MAXnummerge)
    +    facet2->nummerge= qh_MAXnummerge;
    +  else
    +    facet2->nummerge= (short unsigned int)nummerge;
    +  facet2->newmerge= True;
    +  facet2->dupridge= False;
    +  qh_updatetested(qh, facet1, facet2);
    +  if (qh->hull_dim > 2 && qh_setsize(qh, facet1->vertices) == qh->hull_dim)
    +    qh_mergesimplex(qh, facet1, facet2, mergeapex);
    +  else {
    +    qh->vertex_visit++;
    +    FOREACHvertex_(facet2->vertices)
    +      vertex->visitid= qh->vertex_visit;
    +    if (qh->hull_dim == 2)
    +      qh_mergefacet2d(qh, facet1, facet2);
    +    else {
    +      qh_mergeneighbors(qh, facet1, facet2);
    +      qh_mergevertices(qh, facet1->vertices, &facet2->vertices);
    +    }
    +    qh_mergeridges(qh, facet1, facet2);
    +    qh_mergevertex_neighbors(qh, facet1, facet2);
    +    if (!facet2->newfacet)
    +      qh_newvertices(qh, facet2->vertices);
    +  }
    +  if (!mergeapex)
    +    qh_degen_redundant_neighbors(qh, facet2, facet1);
    +  if (facet2->coplanar || !facet2->newfacet) {
    +    zinc_(Zmergeintohorizon);
    +  }else if (!facet1->newfacet && facet2->newfacet) {
    +    zinc_(Zmergehorizon);
    +  }else {
    +    zinc_(Zmergenew);
    +  }
    +  qh_willdelete(qh, facet1, facet2);
    +  qh_removefacet(qh, facet2);  /* append as a newfacet to end of qh->facet_list */
    +  qh_appendfacet(qh, facet2);
    +  facet2->newfacet= True;
    +  facet2->tested= False;
    +  qh_tracemerge(qh, facet1, facet2);
    +  if (traceonce) {
    +    qh_fprintf(qh, qh->ferr, 8080, "qh_mergefacet: end of wide tracing\n");
    +    qh->IStracing= tracerestore;
    +  }
    +} /* mergefacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergefacet2d(qh, facet1, facet2 )
    +    in 2d, merges neighbors and vertices of facet1 into facet2
    +
    +  returns:
    +    build ridges for neighbors if necessary
    +    facet2 looks like a simplicial facet except for centrum, ridges
    +      neighbors are opposite the corresponding vertex
    +      maintains orientation of facet2
    +
    +  notes:
    +    qh_mergefacet() retains non-simplicial structures
    +      they are not needed in 2d, but later routines may use them
    +    preserves qh.vertex_visit for qh_mergevertex_neighbors()
    +
    +  design:
    +    get vertices and neighbors
    +    determine new vertices and neighbors
    +    set new vertices and neighbors and adjust orientation
    +    make ridges for new neighbor if needed
    +*/
    +void qh_mergefacet2d(qhT *qh, facetT *facet1, facetT *facet2) {
    +  vertexT *vertex1A, *vertex1B, *vertex2A, *vertex2B, *vertexA, *vertexB;
    +  facetT *neighbor1A, *neighbor1B, *neighbor2A, *neighbor2B, *neighborA, *neighborB;
    +
    +  vertex1A= SETfirstt_(facet1->vertices, vertexT);
    +  vertex1B= SETsecondt_(facet1->vertices, vertexT);
    +  vertex2A= SETfirstt_(facet2->vertices, vertexT);
    +  vertex2B= SETsecondt_(facet2->vertices, vertexT);
    +  neighbor1A= SETfirstt_(facet1->neighbors, facetT);
    +  neighbor1B= SETsecondt_(facet1->neighbors, facetT);
    +  neighbor2A= SETfirstt_(facet2->neighbors, facetT);
    +  neighbor2B= SETsecondt_(facet2->neighbors, facetT);
    +  if (vertex1A == vertex2A) {
    +    vertexA= vertex1B;
    +    vertexB= vertex2B;
    +    neighborA= neighbor2A;
    +    neighborB= neighbor1A;
    +  }else if (vertex1A == vertex2B) {
    +    vertexA= vertex1B;
    +    vertexB= vertex2A;
    +    neighborA= neighbor2B;
    +    neighborB= neighbor1A;
    +  }else if (vertex1B == vertex2A) {
    +    vertexA= vertex1A;
    +    vertexB= vertex2B;
    +    neighborA= neighbor2A;
    +    neighborB= neighbor1B;
    +  }else { /* 1B == 2B */
    +    vertexA= vertex1A;
    +    vertexB= vertex2A;
    +    neighborA= neighbor2B;
    +    neighborB= neighbor1B;
    +  }
    +  /* vertexB always from facet2, neighborB always from facet1 */
    +  if (vertexA->id > vertexB->id) {
    +    SETfirst_(facet2->vertices)= vertexA;
    +    SETsecond_(facet2->vertices)= vertexB;
    +    if (vertexB == vertex2A)
    +      facet2->toporient= !facet2->toporient;
    +    SETfirst_(facet2->neighbors)= neighborA;
    +    SETsecond_(facet2->neighbors)= neighborB;
    +  }else {
    +    SETfirst_(facet2->vertices)= vertexB;
    +    SETsecond_(facet2->vertices)= vertexA;
    +    if (vertexB == vertex2B)
    +      facet2->toporient= !facet2->toporient;
    +    SETfirst_(facet2->neighbors)= neighborB;
    +    SETsecond_(facet2->neighbors)= neighborA;
    +  }
    +  qh_makeridges(qh, neighborB);
    +  qh_setreplace(qh, neighborB->neighbors, facet1, facet2);
    +  trace4((qh, qh->ferr, 4036, "qh_mergefacet2d: merged v%d and neighbor f%d of f%d into f%d\n",
    +       vertexA->id, neighborB->id, facet1->id, facet2->id));
    +} /* mergefacet2d */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergeneighbors(qh, facet1, facet2 )
    +    merges the neighbors of facet1 into facet2
    +
    +  see:
    +    qh_mergecycle_neighbors()
    +
    +  design:
    +    for each neighbor of facet1
    +      if neighbor is also a neighbor of facet2
    +        if neighbor is simpilicial
    +          make ridges for later deletion as a degenerate facet
    +        update its neighbor set
    +      else
    +        move the neighbor relation to facet2
    +    remove the neighbor relation for facet1 and facet2
    +*/
    +void qh_mergeneighbors(qhT *qh, facetT *facet1, facetT *facet2) {
    +  facetT *neighbor, **neighborp;
    +
    +  trace4((qh, qh->ferr, 4037, "qh_mergeneighbors: merge neighbors of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  qh->visit_id++;
    +  FOREACHneighbor_(facet2) {
    +    neighbor->visitid= qh->visit_id;
    +  }
    +  FOREACHneighbor_(facet1) {
    +    if (neighbor->visitid == qh->visit_id) {
    +      if (neighbor->simplicial)    /* is degen, needs ridges */
    +        qh_makeridges(qh, neighbor);
    +      if (SETfirstt_(neighbor->neighbors, facetT) != facet1) /*keep newfacet->horizon*/
    +        qh_setdel(neighbor->neighbors, facet1);
    +      else {
    +        qh_setdel(neighbor->neighbors, facet2);
    +        qh_setreplace(qh, neighbor->neighbors, facet1, facet2);
    +      }
    +    }else if (neighbor != facet2) {
    +      qh_setappend(qh, &(facet2->neighbors), neighbor);
    +      qh_setreplace(qh, neighbor->neighbors, facet1, facet2);
    +    }
    +  }
    +  qh_setdel(facet1->neighbors, facet2);  /* here for makeridges */
    +  qh_setdel(facet2->neighbors, facet1);
    +} /* mergeneighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergeridges(qh, facet1, facet2 )
    +    merges the ridge set of facet1 into facet2
    +
    +  returns:
    +    may delete all ridges for a vertex
    +    sets vertex->delridge on deleted ridges
    +
    +  see:
    +    qh_mergecycle_ridges()
    +
    +  design:
    +    delete ridges between facet1 and facet2
    +      mark (delridge) vertices on these ridges for later testing
    +    for each remaining ridge
    +      rename facet1 to facet2
    +*/
    +void qh_mergeridges(qhT *qh, facetT *facet1, facetT *facet2) {
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +
    +  trace4((qh, qh->ferr, 4038, "qh_mergeridges: merge ridges of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  FOREACHridge_(facet2->ridges) {
    +    if ((ridge->top == facet1) || (ridge->bottom == facet1)) {
    +      FOREACHvertex_(ridge->vertices)
    +        vertex->delridge= True;
    +      qh_delridge(qh, ridge);  /* expensive in high-d, could rebuild */
    +      ridgep--; /*repeat*/
    +    }
    +  }
    +  FOREACHridge_(facet1->ridges) {
    +    if (ridge->top == facet1)
    +      ridge->top= facet2;
    +    else
    +      ridge->bottom= facet2;
    +    qh_setappend(qh, &(facet2->ridges), ridge);
    +  }
    +} /* mergeridges */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergesimplex(qh, facet1, facet2, mergeapex )
    +    merge simplicial facet1 into facet2
    +    mergeapex==qh_MERGEapex if merging samecycle into horizon facet
    +      vertex id is latest (most recently created)
    +    facet1 may be contained in facet2
    +    ridges exist for both facets
    +
    +  returns:
    +    facet2 with updated vertices, ridges, neighbors
    +    updated neighbors for facet1's vertices
    +    facet1 not deleted
    +    sets vertex->delridge on deleted ridges
    +
    +  notes:
    +    special case code since this is the most common merge
    +    called from qh_mergefacet()
    +
    +  design:
    +    if qh_MERGEapex
    +      add vertices of facet2 to qh.new_vertexlist if necessary
    +      add apex to facet2
    +    else
    +      for each ridge between facet1 and facet2
    +        set vertex->delridge
    +      determine the apex for facet1 (i.e., vertex to be merged)
    +      unless apex already in facet2
    +        insert apex into vertices for facet2
    +      add vertices of facet2 to qh.new_vertexlist if necessary
    +      add apex to qh.new_vertexlist if necessary
    +      for each vertex of facet1
    +        if apex
    +          rename facet1 to facet2 in its vertex neighbors
    +        else
    +          delete facet1 from vertex neighors
    +          if only in facet2
    +            add vertex to qh.del_vertices for later deletion
    +      for each ridge of facet1
    +        delete ridges between facet1 and facet2
    +        append other ridges to facet2 after renaming facet to facet2
    +*/
    +void qh_mergesimplex(qhT *qh, facetT *facet1, facetT *facet2, boolT mergeapex) {
    +  vertexT *vertex, **vertexp, *apex;
    +  ridgeT *ridge, **ridgep;
    +  boolT issubset= False;
    +  int vertex_i= -1, vertex_n;
    +  facetT *neighbor, **neighborp, *otherfacet;
    +
    +  if (mergeapex) {
    +    if (!facet2->newfacet)
    +      qh_newvertices(qh, facet2->vertices);  /* apex is new */
    +    apex= SETfirstt_(facet1->vertices, vertexT);
    +    if (SETfirstt_(facet2->vertices, vertexT) != apex)
    +      qh_setaddnth(qh, &facet2->vertices, 0, apex);  /* apex has last id */
    +    else
    +      issubset= True;
    +  }else {
    +    zinc_(Zmergesimplex);
    +    FOREACHvertex_(facet1->vertices)
    +      vertex->seen= False;
    +    FOREACHridge_(facet1->ridges) {
    +      if (otherfacet_(ridge, facet1) == facet2) {
    +        FOREACHvertex_(ridge->vertices) {
    +          vertex->seen= True;
    +          vertex->delridge= True;
    +        }
    +        break;
    +      }
    +    }
    +    FOREACHvertex_(facet1->vertices) {
    +      if (!vertex->seen)
    +        break;  /* must occur */
    +    }
    +    apex= vertex;
    +    trace4((qh, qh->ferr, 4039, "qh_mergesimplex: merge apex v%d of f%d into facet f%d\n",
    +          apex->id, facet1->id, facet2->id));
    +    FOREACHvertex_i_(qh, facet2->vertices) {
    +      if (vertex->id < apex->id) {
    +        break;
    +      }else if (vertex->id == apex->id) {
    +        issubset= True;
    +        break;
    +      }
    +    }
    +    if (!issubset)
    +      qh_setaddnth(qh, &facet2->vertices, vertex_i, apex);
    +    if (!facet2->newfacet)
    +      qh_newvertices(qh, facet2->vertices);
    +    else if (!apex->newlist) {
    +      qh_removevertex(qh, apex);
    +      qh_appendvertex(qh, apex);
    +    }
    +  }
    +  trace4((qh, qh->ferr, 4040, "qh_mergesimplex: update vertex neighbors of f%d\n",
    +          facet1->id));
    +  FOREACHvertex_(facet1->vertices) {
    +    if (vertex == apex && !issubset)
    +      qh_setreplace(qh, vertex->neighbors, facet1, facet2);
    +    else {
    +      qh_setdel(vertex->neighbors, facet1);
    +      if (!SETsecond_(vertex->neighbors))
    +        qh_mergevertex_del(qh, vertex, facet1, facet2);
    +    }
    +  }
    +  trace4((qh, qh->ferr, 4041, "qh_mergesimplex: merge ridges and neighbors of f%d into f%d\n",
    +          facet1->id, facet2->id));
    +  qh->visit_id++;
    +  FOREACHneighbor_(facet2)
    +    neighbor->visitid= qh->visit_id;
    +  FOREACHridge_(facet1->ridges) {
    +    otherfacet= otherfacet_(ridge, facet1);
    +    if (otherfacet == facet2) {
    +      qh_setdel(facet2->ridges, ridge);
    +      qh_setfree(qh, &(ridge->vertices));
    +      qh_memfree(qh, ridge, (int)sizeof(ridgeT));
    +      qh_setdel(facet2->neighbors, facet1);
    +    }else {
    +      qh_setappend(qh, &facet2->ridges, ridge);
    +      if (otherfacet->visitid != qh->visit_id) {
    +        qh_setappend(qh, &facet2->neighbors, otherfacet);
    +        qh_setreplace(qh, otherfacet->neighbors, facet1, facet2);
    +        otherfacet->visitid= qh->visit_id;
    +      }else {
    +        if (otherfacet->simplicial)    /* is degen, needs ridges */
    +          qh_makeridges(qh, otherfacet);
    +        if (SETfirstt_(otherfacet->neighbors, facetT) != facet1)
    +          qh_setdel(otherfacet->neighbors, facet1);
    +        else {   /*keep newfacet->neighbors->horizon*/
    +          qh_setdel(otherfacet->neighbors, facet2);
    +          qh_setreplace(qh, otherfacet->neighbors, facet1, facet2);
    +        }
    +      }
    +      if (ridge->top == facet1) /* wait until after qh_makeridges */
    +        ridge->top= facet2;
    +      else
    +        ridge->bottom= facet2;
    +    }
    +  }
    +  SETfirst_(facet1->ridges)= NULL; /* it will be deleted */
    +  trace3((qh, qh->ferr, 3006, "qh_mergesimplex: merged simplex f%d apex v%d into facet f%d\n",
    +          facet1->id, getid_(apex), facet2->id));
    +} /* mergesimplex */
    +
    +/*---------------------------------
    +
    +  qh_mergevertex_del(qh, vertex, facet1, facet2 )
    +    delete a vertex because of merging facet1 into facet2
    +
    +  returns:
    +    deletes vertex from facet2
    +    adds vertex to qh.del_vertices for later deletion
    +*/
    +void qh_mergevertex_del(qhT *qh, vertexT *vertex, facetT *facet1, facetT *facet2) {
    +
    +  zinc_(Zmergevertex);
    +  trace2((qh, qh->ferr, 2035, "qh_mergevertex_del: deleted v%d when merging f%d into f%d\n",
    +          vertex->id, facet1->id, facet2->id));
    +  qh_setdelsorted(facet2->vertices, vertex);
    +  vertex->deleted= True;
    +  qh_setappend(qh, &qh->del_vertices, vertex);
    +} /* mergevertex_del */
    +
    +/*---------------------------------
    +
    +  qh_mergevertex_neighbors(qh, facet1, facet2 )
    +    merge the vertex neighbors of facet1 to facet2
    +
    +  returns:
    +    if vertex is current qh.vertex_visit
    +      deletes facet1 from vertex->neighbors
    +    else
    +      renames facet1 to facet2 in vertex->neighbors
    +    deletes vertices if only one neighbor
    +
    +  notes:
    +    assumes vertex neighbor sets are good
    +*/
    +void qh_mergevertex_neighbors(qhT *qh, facetT *facet1, facetT *facet2) {
    +  vertexT *vertex, **vertexp;
    +
    +  trace4((qh, qh->ferr, 4042, "qh_mergevertex_neighbors: merge vertex neighbors of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  if (qh->tracevertex) {
    +    qh_fprintf(qh, qh->ferr, 8081, "qh_mergevertex_neighbors: of f%d and f%d at furthest p%d f0= %p\n",
    +             facet1->id, facet2->id, qh->furthest_id, qh->tracevertex->neighbors->e[0].p);
    +    qh_errprint(qh, "TRACE", NULL, NULL, NULL, qh->tracevertex);
    +  }
    +  FOREACHvertex_(facet1->vertices) {
    +    if (vertex->visitid != qh->vertex_visit)
    +      qh_setreplace(qh, vertex->neighbors, facet1, facet2);
    +    else {
    +      qh_setdel(vertex->neighbors, facet1);
    +      if (!SETsecond_(vertex->neighbors))
    +        qh_mergevertex_del(qh, vertex, facet1, facet2);
    +    }
    +  }
    +  if (qh->tracevertex)
    +    qh_errprint(qh, "TRACE", NULL, NULL, NULL, qh->tracevertex);
    +} /* mergevertex_neighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergevertices(qh, vertices1, vertices2 )
    +    merges the vertex set of facet1 into facet2
    +
    +  returns:
    +    replaces vertices2 with merged set
    +    preserves vertex_visit for qh_mergevertex_neighbors
    +    updates qh.newvertex_list
    +
    +  design:
    +    create a merged set of both vertices (in inverse id order)
    +*/
    +void qh_mergevertices(qhT *qh, setT *vertices1, setT **vertices2) {
    +  int newsize= qh_setsize(qh, vertices1)+qh_setsize(qh, *vertices2) - qh->hull_dim + 1;
    +  setT *mergedvertices;
    +  vertexT *vertex, **vertexp, **vertex2= SETaddr_(*vertices2, vertexT);
    +
    +  mergedvertices= qh_settemp(qh, newsize);
    +  FOREACHvertex_(vertices1) {
    +    if (!*vertex2 || vertex->id > (*vertex2)->id)
    +      qh_setappend(qh, &mergedvertices, vertex);
    +    else {
    +      while (*vertex2 && (*vertex2)->id > vertex->id)
    +        qh_setappend(qh, &mergedvertices, *vertex2++);
    +      if (!*vertex2 || (*vertex2)->id < vertex->id)
    +        qh_setappend(qh, &mergedvertices, vertex);
    +      else
    +        qh_setappend(qh, &mergedvertices, *vertex2++);
    +    }
    +  }
    +  while (*vertex2)
    +    qh_setappend(qh, &mergedvertices, *vertex2++);
    +  if (newsize < qh_setsize(qh, mergedvertices)) {
    +    qh_fprintf(qh, qh->ferr, 6100, "qhull internal error (qh_mergevertices): facets did not share a ridge\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  qh_setfree(qh, vertices2);
    +  *vertices2= mergedvertices;
    +  qh_settemppop(qh);
    +} /* mergevertices */
    +
    +
    +/*---------------------------------
    +
    +  qh_neighbor_intersections(qh, vertex )
    +    return intersection of all vertices in vertex->neighbors except for vertex
    +
    +  returns:
    +    returns temporary set of vertices
    +    does not include vertex
    +    NULL if a neighbor is simplicial
    +    NULL if empty set
    +
    +  notes:
    +    used for renaming vertices
    +
    +  design:
    +    initialize the intersection set with vertices of the first two neighbors
    +    delete vertex from the intersection
    +    for each remaining neighbor
    +      intersect its vertex set with the intersection set
    +      return NULL if empty
    +    return the intersection set
    +*/
    +setT *qh_neighbor_intersections(qhT *qh, vertexT *vertex) {
    +  facetT *neighbor, **neighborp, *neighborA, *neighborB;
    +  setT *intersect;
    +  int neighbor_i, neighbor_n;
    +
    +  FOREACHneighbor_(vertex) {
    +    if (neighbor->simplicial)
    +      return NULL;
    +  }
    +  neighborA= SETfirstt_(vertex->neighbors, facetT);
    +  neighborB= SETsecondt_(vertex->neighbors, facetT);
    +  zinc_(Zintersectnum);
    +  if (!neighborA)
    +    return NULL;
    +  if (!neighborB)
    +    intersect= qh_setcopy(qh, neighborA->vertices, 0);
    +  else
    +    intersect= qh_vertexintersect_new(qh, neighborA->vertices, neighborB->vertices);
    +  qh_settemppush(qh, intersect);
    +  qh_setdelsorted(intersect, vertex);
    +  FOREACHneighbor_i_(qh, vertex) {
    +    if (neighbor_i >= 2) {
    +      zinc_(Zintersectnum);
    +      qh_vertexintersect(qh, &intersect, neighbor->vertices);
    +      if (!SETfirst_(intersect)) {
    +        zinc_(Zintersectfail);
    +        qh_settempfree(qh, &intersect);
    +        return NULL;
    +      }
    +    }
    +  }
    +  trace3((qh, qh->ferr, 3007, "qh_neighbor_intersections: %d vertices in neighbor intersection of v%d\n",
    +          qh_setsize(qh, intersect), vertex->id));
    +  return intersect;
    +} /* neighbor_intersections */
    +
    +/*---------------------------------
    +
    +  qh_newvertices(qh, vertices )
    +    add vertices to end of qh.vertex_list (marks as new vertices)
    +
    +  returns:
    +    vertices on qh.newvertex_list
    +    vertex->newlist set
    +*/
    +void qh_newvertices(qhT *qh, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->newlist) {
    +      qh_removevertex(qh, vertex);
    +      qh_appendvertex(qh, vertex);
    +    }
    +  }
    +} /* newvertices */
    +
    +/*---------------------------------
    +
    +  qh_reducevertices(qh)
    +    reduce extra vertices, shared vertices, and redundant vertices
    +    facet->newmerge is set if merged since last call
    +    if !qh.MERGEvertices, only removes extra vertices
    +
    +  returns:
    +    True if also merged degen_redundant facets
    +    vertices are renamed if possible
    +    clears facet->newmerge and vertex->delridge
    +
    +  notes:
    +    ignored if 2-d
    +
    +  design:
    +    merge any degenerate or redundant facets
    +    for each newly merged facet
    +      remove extra vertices
    +    if qh.MERGEvertices
    +      for each newly merged facet
    +        for each vertex
    +          if vertex was on a deleted ridge
    +            rename vertex if it is shared
    +      remove delridge flag from new vertices
    +*/
    +boolT qh_reducevertices(qhT *qh) {
    +  int numshare=0, numrename= 0;
    +  boolT degenredun= False;
    +  facetT *newfacet;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh->hull_dim == 2)
    +    return False;
    +  if (qh_merge_degenredundant(qh))
    +    degenredun= True;
    + LABELrestart:
    +  FORALLnew_facets {
    +    if (newfacet->newmerge) {
    +      if (!qh->MERGEvertices)
    +        newfacet->newmerge= False;
    +      qh_remove_extravertices(qh, newfacet);
    +    }
    +  }
    +  if (!qh->MERGEvertices)
    +    return False;
    +  FORALLnew_facets {
    +    if (newfacet->newmerge) {
    +      newfacet->newmerge= False;
    +      FOREACHvertex_(newfacet->vertices) {
    +        if (vertex->delridge) {
    +          if (qh_rename_sharedvertex(qh, vertex, newfacet)) {
    +            numshare++;
    +            vertexp--; /* repeat since deleted vertex */
    +          }
    +        }
    +      }
    +    }
    +  }
    +  FORALLvertex_(qh->newvertex_list) {
    +    if (vertex->delridge && !vertex->deleted) {
    +      vertex->delridge= False;
    +      if (qh->hull_dim >= 4 && qh_redundant_vertex(qh, vertex)) {
    +        numrename++;
    +        if (qh_merge_degenredundant(qh)) {
    +          degenredun= True;
    +          goto LABELrestart;
    +        }
    +      }
    +    }
    +  }
    +  trace1((qh, qh->ferr, 1014, "qh_reducevertices: renamed %d shared vertices and %d redundant vertices. Degen? %d\n",
    +          numshare, numrename, degenredun));
    +  return degenredun;
    +} /* reducevertices */
    +
    +/*---------------------------------
    +
    +  qh_redundant_vertex(qh, vertex )
    +    detect and rename a redundant vertex
    +    vertices have full vertex->neighbors
    +
    +  returns:
    +    returns true if find a redundant vertex
    +      deletes vertex(vertex->deleted)
    +
    +  notes:
    +    only needed if vertex->delridge and hull_dim >= 4
    +    may add degenerate facets to qh.facet_mergeset
    +    doesn't change vertex->neighbors or create redundant facets
    +
    +  design:
    +    intersect vertices of all facet neighbors of vertex
    +    determine ridges for these vertices
    +    if find a new vertex for vertex amoung these ridges and vertices
    +      rename vertex to the new vertex
    +*/
    +vertexT *qh_redundant_vertex(qhT *qh, vertexT *vertex) {
    +  vertexT *newvertex= NULL;
    +  setT *vertices, *ridges;
    +
    +  trace3((qh, qh->ferr, 3008, "qh_redundant_vertex: check if v%d can be renamed\n", vertex->id));
    +  if ((vertices= qh_neighbor_intersections(qh, vertex))) {
    +    ridges= qh_vertexridges(qh, vertex);
    +    if ((newvertex= qh_find_newvertex(qh, vertex, vertices, ridges)))
    +      qh_renamevertex(qh, vertex, newvertex, ridges, NULL, NULL);
    +    qh_settempfree(qh, &ridges);
    +    qh_settempfree(qh, &vertices);
    +  }
    +  return newvertex;
    +} /* redundant_vertex */
    +
    +/*---------------------------------
    +
    +  qh_remove_extravertices(qh, facet )
    +    remove extra vertices from non-simplicial facets
    +
    +  returns:
    +    returns True if it finds them
    +
    +  design:
    +    for each vertex in facet
    +      if vertex not in a ridge (i.e., no longer used)
    +        delete vertex from facet
    +        delete facet from vertice's neighbors
    +        unless vertex in another facet
    +          add vertex to qh.del_vertices for later deletion
    +*/
    +boolT qh_remove_extravertices(qhT *qh, facetT *facet) {
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +  boolT foundrem= False;
    +
    +  trace4((qh, qh->ferr, 4043, "qh_remove_extravertices: test f%d for extra vertices\n",
    +          facet->id));
    +  FOREACHvertex_(facet->vertices)
    +    vertex->seen= False;
    +  FOREACHridge_(facet->ridges) {
    +    FOREACHvertex_(ridge->vertices)
    +      vertex->seen= True;
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    if (!vertex->seen) {
    +      foundrem= True;
    +      zinc_(Zremvertex);
    +      qh_setdelsorted(facet->vertices, vertex);
    +      qh_setdel(vertex->neighbors, facet);
    +      if (!qh_setsize(qh, vertex->neighbors)) {
    +        vertex->deleted= True;
    +        qh_setappend(qh, &qh->del_vertices, vertex);
    +        zinc_(Zremvertexdel);
    +        trace2((qh, qh->ferr, 2036, "qh_remove_extravertices: v%d deleted because it's lost all ridges\n", vertex->id));
    +      }else
    +        trace3((qh, qh->ferr, 3009, "qh_remove_extravertices: v%d removed from f%d because it's lost all ridges\n", vertex->id, facet->id));
    +      vertexp--; /*repeat*/
    +    }
    +  }
    +  return foundrem;
    +} /* remove_extravertices */
    +
    +/*---------------------------------
    +
    +  qh_rename_sharedvertex(qh, vertex, facet )
    +    detect and rename if shared vertex in facet
    +    vertices have full ->neighbors
    +
    +  returns:
    +    newvertex or NULL
    +    the vertex may still exist in other facets (i.e., a neighbor was pinched)
    +    does not change facet->neighbors
    +    updates vertex->neighbors
    +
    +  notes:
    +    a shared vertex for a facet is only in ridges to one neighbor
    +    this may undo a pinched facet
    +
    +    it does not catch pinches involving multiple facets.  These appear
    +      to be difficult to detect, since an exhaustive search is too expensive.
    +
    +  design:
    +    if vertex only has two neighbors
    +      determine the ridges that contain the vertex
    +      determine the vertices shared by both neighbors
    +      if can find a new vertex in this set
    +        rename the vertex to the new vertex
    +*/
    +vertexT *qh_rename_sharedvertex(qhT *qh, vertexT *vertex, facetT *facet) {
    +  facetT *neighbor, **neighborp, *neighborA= NULL;
    +  setT *vertices, *ridges;
    +  vertexT *newvertex;
    +
    +  if (qh_setsize(qh, vertex->neighbors) == 2) {
    +    neighborA= SETfirstt_(vertex->neighbors, facetT);
    +    if (neighborA == facet)
    +      neighborA= SETsecondt_(vertex->neighbors, facetT);
    +  }else if (qh->hull_dim == 3)
    +    return NULL;
    +  else {
    +    qh->visit_id++;
    +    FOREACHneighbor_(facet)
    +      neighbor->visitid= qh->visit_id;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->visitid == qh->visit_id) {
    +        if (neighborA)
    +          return NULL;
    +        neighborA= neighbor;
    +      }
    +    }
    +    if (!neighborA) {
    +      qh_fprintf(qh, qh->ferr, 6101, "qhull internal error (qh_rename_sharedvertex): v%d's neighbors not in f%d\n",
    +        vertex->id, facet->id);
    +      qh_errprint(qh, "ERRONEOUS", facet, NULL, NULL, vertex);
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +  }
    +  /* the vertex is shared by facet and neighborA */
    +  ridges= qh_settemp(qh, qh->TEMPsize);
    +  neighborA->visitid= ++qh->visit_id;
    +  qh_vertexridges_facet(qh, vertex, facet, &ridges);
    +  trace2((qh, qh->ferr, 2037, "qh_rename_sharedvertex: p%d(v%d) is shared by f%d(%d ridges) and f%d\n",
    +    qh_pointid(qh, vertex->point), vertex->id, facet->id, qh_setsize(qh, ridges), neighborA->id));
    +  zinc_(Zintersectnum);
    +  vertices= qh_vertexintersect_new(qh, facet->vertices, neighborA->vertices);
    +  qh_setdel(vertices, vertex);
    +  qh_settemppush(qh, vertices);
    +  if ((newvertex= qh_find_newvertex(qh, vertex, vertices, ridges)))
    +    qh_renamevertex(qh, vertex, newvertex, ridges, facet, neighborA);
    +  qh_settempfree(qh, &vertices);
    +  qh_settempfree(qh, &ridges);
    +  return newvertex;
    +} /* rename_sharedvertex */
    +
    +/*---------------------------------
    +
    +  qh_renameridgevertex(qh, ridge, oldvertex, newvertex )
    +    renames oldvertex as newvertex in ridge
    +
    +  returns:
    +
    +  design:
    +    delete oldvertex from ridge
    +    if newvertex already in ridge
    +      copy ridge->noconvex to another ridge if possible
    +      delete the ridge
    +    else
    +      insert newvertex into the ridge
    +      adjust the ridge's orientation
    +*/
    +void qh_renameridgevertex(qhT *qh, ridgeT *ridge, vertexT *oldvertex, vertexT *newvertex) {
    +  int nth= 0, oldnth;
    +  facetT *temp;
    +  vertexT *vertex, **vertexp;
    +
    +  oldnth= qh_setindex(ridge->vertices, oldvertex);
    +  qh_setdelnthsorted(qh, ridge->vertices, oldnth);
    +  FOREACHvertex_(ridge->vertices) {
    +    if (vertex == newvertex) {
    +      zinc_(Zdelridge);
    +      if (ridge->nonconvex) /* only one ridge has nonconvex set */
    +        qh_copynonconvex(qh, ridge);
    +      trace2((qh, qh->ferr, 2038, "qh_renameridgevertex: ridge r%d deleted.  It contained both v%d and v%d\n",
    +        ridge->id, oldvertex->id, newvertex->id));
    +      qh_delridge(qh, ridge);
    +      return;
    +    }
    +    if (vertex->id < newvertex->id)
    +      break;
    +    nth++;
    +  }
    +  qh_setaddnth(qh, &ridge->vertices, nth, newvertex);
    +  if (abs(oldnth - nth)%2) {
    +    trace3((qh, qh->ferr, 3010, "qh_renameridgevertex: swapped the top and bottom of ridge r%d\n",
    +            ridge->id));
    +    temp= ridge->top;
    +    ridge->top= ridge->bottom;
    +    ridge->bottom= temp;
    +  }
    +} /* renameridgevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_renamevertex(qh, oldvertex, newvertex, ridges, oldfacet, neighborA )
    +    renames oldvertex as newvertex in ridges
    +    gives oldfacet/neighborA if oldvertex is shared between two facets
    +
    +  returns:
    +    oldvertex may still exist afterwards
    +
    +
    +  notes:
    +    can not change neighbors of newvertex (since it's a subset)
    +
    +  design:
    +    for each ridge in ridges
    +      rename oldvertex to newvertex and delete degenerate ridges
    +    if oldfacet not defined
    +      for each neighbor of oldvertex
    +        delete oldvertex from neighbor's vertices
    +        remove extra vertices from neighbor
    +      add oldvertex to qh.del_vertices
    +    else if oldvertex only between oldfacet and neighborA
    +      delete oldvertex from oldfacet and neighborA
    +      add oldvertex to qh.del_vertices
    +    else oldvertex is in oldfacet and neighborA and other facets (i.e., pinched)
    +      delete oldvertex from oldfacet
    +      delete oldfacet from oldvertice's neighbors
    +      remove extra vertices (e.g., oldvertex) from neighborA
    +*/
    +void qh_renamevertex(qhT *qh, vertexT *oldvertex, vertexT *newvertex, setT *ridges, facetT *oldfacet, facetT *neighborA) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  boolT istrace= False;
    +
    +  if (qh->IStracing >= 2 || oldvertex->id == qh->tracevertex_id ||
    +        newvertex->id == qh->tracevertex_id)
    +    istrace= True;
    +  FOREACHridge_(ridges)
    +    qh_renameridgevertex(qh, ridge, oldvertex, newvertex);
    +  if (!oldfacet) {
    +    zinc_(Zrenameall);
    +    if (istrace)
    +      qh_fprintf(qh, qh->ferr, 8082, "qh_renamevertex: renamed v%d to v%d in several facets\n",
    +               oldvertex->id, newvertex->id);
    +    FOREACHneighbor_(oldvertex) {
    +      qh_maydropneighbor(qh, neighbor);
    +      qh_setdelsorted(neighbor->vertices, oldvertex);
    +      if (qh_remove_extravertices(qh, neighbor))
    +        neighborp--; /* neighbor may be deleted */
    +    }
    +    if (!oldvertex->deleted) {
    +      oldvertex->deleted= True;
    +      qh_setappend(qh, &qh->del_vertices, oldvertex);
    +    }
    +  }else if (qh_setsize(qh, oldvertex->neighbors) == 2) {
    +    zinc_(Zrenameshare);
    +    if (istrace)
    +      qh_fprintf(qh, qh->ferr, 8083, "qh_renamevertex: renamed v%d to v%d in oldfacet f%d\n",
    +               oldvertex->id, newvertex->id, oldfacet->id);
    +    FOREACHneighbor_(oldvertex)
    +      qh_setdelsorted(neighbor->vertices, oldvertex);
    +    oldvertex->deleted= True;
    +    qh_setappend(qh, &qh->del_vertices, oldvertex);
    +  }else {
    +    zinc_(Zrenamepinch);
    +    if (istrace || qh->IStracing)
    +      qh_fprintf(qh, qh->ferr, 8084, "qh_renamevertex: renamed pinched v%d to v%d between f%d and f%d\n",
    +               oldvertex->id, newvertex->id, oldfacet->id, neighborA->id);
    +    qh_setdelsorted(oldfacet->vertices, oldvertex);
    +    qh_setdel(oldvertex->neighbors, oldfacet);
    +    qh_remove_extravertices(qh, neighborA);
    +  }
    +} /* renamevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_test_appendmerge(qh, facet, neighbor )
    +    tests facet/neighbor for convexity
    +    appends to mergeset if non-convex
    +    if pre-merging,
    +      nop if qh.SKIPconvex, or qh.MERGEexact and coplanar
    +
    +  returns:
    +    true if appends facet/neighbor to mergeset
    +    sets facet->center as needed
    +    does not change facet->seen
    +
    +  design:
    +    if qh.cos_max is defined
    +      if the angle between facet normals is too shallow
    +        append an angle-coplanar merge to qh.mergeset
    +        return True
    +    make facet's centrum if needed
    +    if facet's centrum is above the neighbor
    +      set isconcave
    +    else
    +      if facet's centrum is not below the neighbor
    +        set iscoplanar
    +      make neighbor's centrum if needed
    +      if neighbor's centrum is above the facet
    +        set isconcave
    +      else if neighbor's centrum is not below the facet
    +        set iscoplanar
    +   if isconcave or iscoplanar
    +     get angle if needed
    +     append concave or coplanar merge to qh.mergeset
    +*/
    +boolT qh_test_appendmerge(qhT *qh, facetT *facet, facetT *neighbor) {
    +  realT dist, dist2= -REALmax, angle= -REALmax;
    +  boolT isconcave= False, iscoplanar= False, okangle= False;
    +
    +  if (qh->SKIPconvex && !qh->POSTmerging)
    +    return False;
    +  if ((!qh->MERGEexact || qh->POSTmerging) && qh->cos_max < REALmax/2) {
    +    angle= qh_getangle(qh, facet->normal, neighbor->normal);
    +    zinc_(Zangletests);
    +    if (angle > qh->cos_max) {
    +      zinc_(Zcoplanarangle);
    +      qh_appendmergeset(qh, facet, neighbor, MRGanglecoplanar, &angle);
    +      trace2((qh, qh->ferr, 2039, "qh_test_appendmerge: coplanar angle %4.4g between f%d and f%d\n",
    +         angle, facet->id, neighbor->id));
    +      return True;
    +    }else
    +      okangle= True;
    +  }
    +  if (!facet->center)
    +    facet->center= qh_getcentrum(qh, facet);
    +  zzinc_(Zcentrumtests);
    +  qh_distplane(qh, facet->center, neighbor, &dist);
    +  if (dist > qh->centrum_radius)
    +    isconcave= True;
    +  else {
    +    if (dist > -qh->centrum_radius)
    +      iscoplanar= True;
    +    if (!neighbor->center)
    +      neighbor->center= qh_getcentrum(qh, neighbor);
    +    zzinc_(Zcentrumtests);
    +    qh_distplane(qh, neighbor->center, facet, &dist2);
    +    if (dist2 > qh->centrum_radius)
    +      isconcave= True;
    +    else if (!iscoplanar && dist2 > -qh->centrum_radius)
    +      iscoplanar= True;
    +  }
    +  if (!isconcave && (!iscoplanar || (qh->MERGEexact && !qh->POSTmerging)))
    +    return False;
    +  if (!okangle && qh->ANGLEmerge) {
    +    angle= qh_getangle(qh, facet->normal, neighbor->normal);
    +    zinc_(Zangletests);
    +  }
    +  if (isconcave) {
    +    zinc_(Zconcaveridge);
    +    if (qh->ANGLEmerge)
    +      angle += qh_ANGLEconcave + 0.5;
    +    qh_appendmergeset(qh, facet, neighbor, MRGconcave, &angle);
    +    trace0((qh, qh->ferr, 18, "qh_test_appendmerge: concave f%d to f%d dist %4.4g and reverse dist %4.4g angle %4.4g during p%d\n",
    +           facet->id, neighbor->id, dist, dist2, angle, qh->furthest_id));
    +  }else /* iscoplanar */ {
    +    zinc_(Zcoplanarcentrum);
    +    qh_appendmergeset(qh, facet, neighbor, MRGcoplanar, &angle);
    +    trace2((qh, qh->ferr, 2040, "qh_test_appendmerge: coplanar f%d to f%d dist %4.4g, reverse dist %4.4g angle %4.4g\n",
    +              facet->id, neighbor->id, dist, dist2, angle));
    +  }
    +  return True;
    +} /* test_appendmerge */
    +
    +/*---------------------------------
    +
    +  qh_test_vneighbors(qh)
    +    test vertex neighbors for convexity
    +    tests all facets on qh.newfacet_list
    +
    +  returns:
    +    true if non-convex vneighbors appended to qh.facet_mergeset
    +    initializes vertex neighbors if needed
    +
    +  notes:
    +    assumes all facet neighbors have been tested
    +    this can be expensive
    +    this does not guarantee that a centrum is below all facets
    +      but it is unlikely
    +    uses qh.visit_id
    +
    +  design:
    +    build vertex neighbors if necessary
    +    for all new facets
    +      for all vertices
    +        for each unvisited facet neighbor of the vertex
    +          test new facet and neighbor for convexity
    +*/
    +boolT qh_test_vneighbors(qhT *qh /* qh->newfacet_list */) {
    +  facetT *newfacet, *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  int nummerges= 0;
    +
    +  trace1((qh, qh->ferr, 1015, "qh_test_vneighbors: testing vertex neighbors for convexity\n"));
    +  if (!qh->VERTEXneighbors)
    +    qh_vertexneighbors(qh);
    +  FORALLnew_facets
    +    newfacet->seen= False;
    +  FORALLnew_facets {
    +    newfacet->seen= True;
    +    newfacet->visitid= qh->visit_id++;
    +    FOREACHneighbor_(newfacet)
    +      newfacet->visitid= qh->visit_id;
    +    FOREACHvertex_(newfacet->vertices) {
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->seen || neighbor->visitid == qh->visit_id)
    +          continue;
    +        if (qh_test_appendmerge(qh, newfacet, neighbor))
    +          nummerges++;
    +      }
    +    }
    +  }
    +  zadd_(Ztestvneighbor, nummerges);
    +  trace1((qh, qh->ferr, 1016, "qh_test_vneighbors: found %d non-convex, vertex neighbors\n",
    +           nummerges));
    +  return (nummerges > 0);
    +} /* test_vneighbors */
    +
    +/*---------------------------------
    +
    +  qh_tracemerge(qh, facet1, facet2 )
    +    print trace message after merge
    +*/
    +void qh_tracemerge(qhT *qh, facetT *facet1, facetT *facet2) {
    +  boolT waserror= False;
    +
    +#ifndef qh_NOtrace
    +  if (qh->IStracing >= 4)
    +    qh_errprint(qh, "MERGED", facet2, NULL, NULL, NULL);
    +  if (facet2 == qh->tracefacet || (qh->tracevertex && qh->tracevertex->newlist)) {
    +    qh_fprintf(qh, qh->ferr, 8085, "qh_tracemerge: trace facet and vertex after merge of f%d and f%d, furthest p%d\n", facet1->id, facet2->id, qh->furthest_id);
    +    if (facet2 != qh->tracefacet)
    +      qh_errprint(qh, "TRACE", qh->tracefacet,
    +        (qh->tracevertex && qh->tracevertex->neighbors) ?
    +           SETfirstt_(qh->tracevertex->neighbors, facetT) : NULL,
    +        NULL, qh->tracevertex);
    +  }
    +  if (qh->tracevertex) {
    +    if (qh->tracevertex->deleted)
    +      qh_fprintf(qh, qh->ferr, 8086, "qh_tracemerge: trace vertex deleted at furthest p%d\n",
    +            qh->furthest_id);
    +    else
    +      qh_checkvertex(qh, qh->tracevertex);
    +  }
    +  if (qh->tracefacet) {
    +    qh_checkfacet(qh, qh->tracefacet, True, &waserror);
    +    if (waserror)
    +      qh_errexit(qh, qh_ERRqhull, qh->tracefacet, NULL);
    +  }
    +#endif /* !qh_NOtrace */
    +  if (qh->CHECKfrequently || qh->IStracing >= 4) { /* can't check polygon here */
    +    qh_checkfacet(qh, facet2, True, &waserror);
    +    if (waserror)
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +} /* tracemerge */
    +
    +/*---------------------------------
    +
    +  qh_tracemerging(qh)
    +    print trace message during POSTmerging
    +
    +  returns:
    +    updates qh.mergereport
    +
    +  notes:
    +    called from qh_mergecycle() and qh_mergefacet()
    +
    +  see:
    +    qh_buildtracing()
    +*/
    +void qh_tracemerging(qhT *qh) {
    +  realT cpu;
    +  int total;
    +  time_t timedata;
    +  struct tm *tp;
    +
    +  qh->mergereport= zzval_(Ztotmerge);
    +  time(&timedata);
    +  tp= localtime(&timedata);
    +  cpu= qh_CPUclock;
    +  cpu /= qh_SECticks;
    +  total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +  qh_fprintf(qh, qh->ferr, 8087, "\n\
    +At %d:%d:%d & %2.5g CPU secs, qhull has merged %d facets.  The hull\n\
    +  contains %d facets and %d vertices.\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu,
    +      total, qh->num_facets - qh->num_visible,
    +      qh->num_vertices-qh_setsize(qh, qh->del_vertices));
    +} /* tracemerging */
    +
    +/*---------------------------------
    +
    +  qh_updatetested(qh, facet1, facet2 )
    +    clear facet2->tested and facet1->ridge->tested for merge
    +
    +  returns:
    +    deletes facet2->center unless it's already large
    +      if so, clears facet2->ridge->tested
    +
    +  design:
    +    clear facet2->tested
    +    clear ridge->tested for facet1's ridges
    +    if facet2 has a centrum
    +      if facet2 is large
    +        set facet2->keepcentrum
    +      else if facet2 has 3 vertices due to many merges, or not large and post merging
    +        clear facet2->keepcentrum
    +      unless facet2->keepcentrum
    +        clear facet2->center to recompute centrum later
    +        clear ridge->tested for facet2's ridges
    +*/
    +void qh_updatetested(qhT *qh, facetT *facet1, facetT *facet2) {
    +  ridgeT *ridge, **ridgep;
    +  int size;
    +
    +  facet2->tested= False;
    +  FOREACHridge_(facet1->ridges)
    +    ridge->tested= False;
    +  if (!facet2->center)
    +    return;
    +  size= qh_setsize(qh, facet2->vertices);
    +  if (!facet2->keepcentrum) {
    +    if (size > qh->hull_dim + qh_MAXnewcentrum) {
    +      facet2->keepcentrum= True;
    +      zinc_(Zwidevertices);
    +    }
    +  }else if (size <= qh->hull_dim + qh_MAXnewcentrum) {
    +    /* center and keepcentrum was set */
    +    if (size == qh->hull_dim || qh->POSTmerging)
    +      facet2->keepcentrum= False; /* if many merges need to recompute centrum */
    +  }
    +  if (!facet2->keepcentrum) {
    +    qh_memfree(qh, facet2->center, qh->normal_size);
    +    facet2->center= NULL;
    +    FOREACHridge_(facet2->ridges)
    +      ridge->tested= False;
    +  }
    +} /* updatetested */
    +
    +/*---------------------------------
    +
    +  qh_vertexridges(qh, vertex )
    +    return temporary set of ridges adjacent to a vertex
    +    vertex->neighbors defined
    +
    +  ntoes:
    +    uses qh.visit_id
    +    does not include implicit ridges for simplicial facets
    +
    +  design:
    +    for each neighbor of vertex
    +      add ridges that include the vertex to ridges
    +*/
    +setT *qh_vertexridges(qhT *qh, vertexT *vertex) {
    +  facetT *neighbor, **neighborp;
    +  setT *ridges= qh_settemp(qh, qh->TEMPsize);
    +  int size;
    +
    +  qh->visit_id++;
    +  FOREACHneighbor_(vertex)
    +    neighbor->visitid= qh->visit_id;
    +  FOREACHneighbor_(vertex) {
    +    if (*neighborp)   /* no new ridges in last neighbor */
    +      qh_vertexridges_facet(qh, vertex, neighbor, &ridges);
    +  }
    +  if (qh->PRINTstatistics || qh->IStracing) {
    +    size= qh_setsize(qh, ridges);
    +    zinc_(Zvertexridge);
    +    zadd_(Zvertexridgetot, size);
    +    zmax_(Zvertexridgemax, size);
    +    trace3((qh, qh->ferr, 3011, "qh_vertexridges: found %d ridges for v%d\n",
    +             size, vertex->id));
    +  }
    +  return ridges;
    +} /* vertexridges */
    +
    +/*---------------------------------
    +
    +  qh_vertexridges_facet(qh, vertex, facet, ridges )
    +    add adjacent ridges for vertex in facet
    +    neighbor->visitid==qh.visit_id if it hasn't been visited
    +
    +  returns:
    +    ridges updated
    +    sets facet->visitid to qh.visit_id-1
    +
    +  design:
    +    for each ridge of facet
    +      if ridge of visited neighbor (i.e., unprocessed)
    +        if vertex in ridge
    +          append ridge to vertex
    +    mark facet processed
    +*/
    +void qh_vertexridges_facet(qhT *qh, vertexT *vertex, facetT *facet, setT **ridges) {
    +  ridgeT *ridge, **ridgep;
    +  facetT *neighbor;
    +
    +  FOREACHridge_(facet->ridges) {
    +    neighbor= otherfacet_(ridge, facet);
    +    if (neighbor->visitid == qh->visit_id
    +    && qh_setin(ridge->vertices, vertex))
    +      qh_setappend(qh, ridges, ridge);
    +  }
    +  facet->visitid= qh->visit_id-1;
    +} /* vertexridges_facet */
    +
    +/*---------------------------------
    +
    +  qh_willdelete(qh, facet, replace )
    +    moves facet to visible list
    +    sets facet->f.replace to replace (may be NULL)
    +
    +  returns:
    +    bumps qh.num_visible
    +*/
    +void qh_willdelete(qhT *qh, facetT *facet, facetT *replace) {
    +
    +  qh_removefacet(qh, facet);
    +  qh_prependfacet(qh, facet, &qh->visible_list);
    +  qh->num_visible++;
    +  facet->visible= True;
    +  facet->f.replace= replace;
    +} /* willdelete */
    +
    +#else /* qh_NOmerge */
    +void qh_premerge(qhT *qh, vertexT *apex, realT maxcentrum, realT maxangle) {
    +}
    +void qh_postmerge(qhT *qh, const char *reason, realT maxcentrum, realT maxangle,
    +                      boolT vneighbors) {
    +}
    +boolT qh_checkzero(qhT *qh, boolT testall) {
    +   }
    +#endif /* qh_NOmerge */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/merge_r.h b/xs/src/qhull/src/libqhull_r/merge_r.h
    new file mode 100644
    index 0000000000..30a51815da
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/merge_r.h
    @@ -0,0 +1,186 @@
    +/*
      ---------------------------------
    +
    +   merge_r.h
    +   header file for merge_r.c
    +
    +   see qh-merge_r.htm and merge_r.c
    +
    +   Copyright (c) 1993-2015 C.B. Barber.
    +   $Id: //main/2015/qhull/src/libqhull_r/merge_r.h#3 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFmerge
    +#define qhDEFmerge 1
    +
    +#include "libqhull_r.h"
    +
    +
    +/*============ -constants- ==============*/
    +
    +/*----------------------------------
    +
    +  qh_ANGLEredundant
    +    indicates redundant merge in mergeT->angle
    +*/
    +#define qh_ANGLEredundant 6.0
    +
    +/*----------------------------------
    +
    +  qh_ANGLEdegen
    +    indicates degenerate facet in mergeT->angle
    +*/
    +#define qh_ANGLEdegen     5.0
    +
    +/*----------------------------------
    +
    +  qh_ANGLEconcave
    +    offset to indicate concave facets in mergeT->angle
    +
    +  notes:
    +    concave facets are assigned the range of [2,4] in mergeT->angle
    +    roundoff error may make the angle less than 2
    +*/
    +#define qh_ANGLEconcave  1.5
    +
    +/*----------------------------------
    +
    +  MRG... (mergeType)
    +    indicates the type of a merge (mergeT->type)
    +*/
    +typedef enum {  /* in sort order for facet_mergeset */
    +  MRGnone= 0,
    +  MRGcoplanar,          /* centrum coplanar */
    +  MRGanglecoplanar,     /* angle coplanar */
    +                        /* could detect half concave ridges */
    +  MRGconcave,           /* concave ridge */
    +  MRGflip,              /* flipped facet. facet1 == facet2 */
    +  MRGridge,             /* duplicate ridge (qh_MERGEridge) */
    +                        /* degen and redundant go onto degen_mergeset */
    +  MRGdegen,             /* degenerate facet (!enough neighbors) facet1 == facet2 */
    +  MRGredundant,         /* redundant facet (vertex subset) */
    +                        /* merge_degenredundant assumes degen < redundant */
    +  MRGmirror,            /* mirror facet from qh_triangulate */
    +  ENDmrg
    +} mergeType;
    +
    +/*----------------------------------
    +
    +  qh_MERGEapex
    +    flag for qh_mergefacet() to indicate an apex merge
    +*/
    +#define qh_MERGEapex     True
    +
    +/*============ -structures- ====================*/
    +
    +/*----------------------------------
    +
    +  mergeT
    +    structure used to merge facets
    +*/
    +
    +typedef struct mergeT mergeT;
    +struct mergeT {         /* initialize in qh_appendmergeset */
    +  realT   angle;        /* angle between normals of facet1 and facet2 */
    +  facetT *facet1;       /* will merge facet1 into facet2 */
    +  facetT *facet2;
    +  mergeType type;
    +};
    +
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  FOREACHmerge_( merges ) {...}
    +    assign 'merge' to each merge in merges
    +
    +  notes:
    +    uses 'mergeT *merge, **mergep;'
    +    if qh_mergefacet(),
    +      restart since qh.facet_mergeset may change
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHmerge_( merges ) FOREACHsetelement_(mergeT, merges, merge)
    +
    +/*============ prototypes in alphabetical order after pre/postmerge =======*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_premerge(qhT *qh, vertexT *apex, realT maxcentrum, realT maxangle);
    +void    qh_postmerge(qhT *qh, const char *reason, realT maxcentrum, realT maxangle,
    +             boolT vneighbors);
    +void    qh_all_merges(qhT *qh, boolT othermerge, boolT vneighbors);
    +void    qh_appendmergeset(qhT *qh, facetT *facet, facetT *neighbor, mergeType mergetype, realT *angle);
    +setT   *qh_basevertices(qhT *qh, facetT *samecycle);
    +void    qh_checkconnect(qhT *qh /* qh.new_facets */);
    +boolT   qh_checkzero(qhT *qh, boolT testall);
    +int     qh_compareangle(const void *p1, const void *p2);
    +int     qh_comparemerge(const void *p1, const void *p2);
    +int     qh_comparevisit(const void *p1, const void *p2);
    +void    qh_copynonconvex(qhT *qh, ridgeT *atridge);
    +void    qh_degen_redundant_facet(qhT *qh, facetT *facet);
    +void    qh_degen_redundant_neighbors(qhT *qh, facetT *facet, facetT *delfacet);
    +vertexT *qh_find_newvertex(qhT *qh, vertexT *oldvertex, setT *vertices, setT *ridges);
    +void    qh_findbest_test(qhT *qh, boolT testcentrum, facetT *facet, facetT *neighbor,
    +           facetT **bestfacet, realT *distp, realT *mindistp, realT *maxdistp);
    +facetT *qh_findbestneighbor(qhT *qh, facetT *facet, realT *distp, realT *mindistp, realT *maxdistp);
    +void    qh_flippedmerges(qhT *qh, facetT *facetlist, boolT *wasmerge);
    +void    qh_forcedmerges(qhT *qh, boolT *wasmerge);
    +void    qh_getmergeset(qhT *qh, facetT *facetlist);
    +void    qh_getmergeset_initial(qhT *qh, facetT *facetlist);
    +void    qh_hashridge(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge, vertexT *oldvertex);
    +ridgeT *qh_hashridge_find(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge,
    +              vertexT *vertex, vertexT *oldvertex, int *hashslot);
    +void    qh_makeridges(qhT *qh, facetT *facet);
    +void    qh_mark_dupridges(qhT *qh, facetT *facetlist);
    +void    qh_maydropneighbor(qhT *qh, facetT *facet);
    +int     qh_merge_degenredundant(qhT *qh);
    +void    qh_merge_nonconvex(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype);
    +void    qh_mergecycle(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_all(qhT *qh, facetT *facetlist, boolT *wasmerge);
    +void    qh_mergecycle_facets(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_neighbors(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_ridges(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_vneighbors(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergefacet(qhT *qh, facetT *facet1, facetT *facet2, realT *mindist, realT *maxdist, boolT mergeapex);
    +void    qh_mergefacet2d(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_mergeneighbors(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_mergeridges(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_mergesimplex(qhT *qh, facetT *facet1, facetT *facet2, boolT mergeapex);
    +void    qh_mergevertex_del(qhT *qh, vertexT *vertex, facetT *facet1, facetT *facet2);
    +void    qh_mergevertex_neighbors(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_mergevertices(qhT *qh, setT *vertices1, setT **vertices);
    +setT   *qh_neighbor_intersections(qhT *qh, vertexT *vertex);
    +void    qh_newvertices(qhT *qh, setT *vertices);
    +boolT   qh_reducevertices(qhT *qh);
    +vertexT *qh_redundant_vertex(qhT *qh, vertexT *vertex);
    +boolT   qh_remove_extravertices(qhT *qh, facetT *facet);
    +vertexT *qh_rename_sharedvertex(qhT *qh, vertexT *vertex, facetT *facet);
    +void    qh_renameridgevertex(qhT *qh, ridgeT *ridge, vertexT *oldvertex, vertexT *newvertex);
    +void    qh_renamevertex(qhT *qh, vertexT *oldvertex, vertexT *newvertex, setT *ridges,
    +                        facetT *oldfacet, facetT *neighborA);
    +boolT   qh_test_appendmerge(qhT *qh, facetT *facet, facetT *neighbor);
    +boolT   qh_test_vneighbors(qhT *qh /* qh.newfacet_list */);
    +void    qh_tracemerge(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_tracemerging(qhT *qh);
    +void    qh_updatetested(qhT *qh, facetT *facet1, facetT *facet2);
    +setT   *qh_vertexridges(qhT *qh, vertexT *vertex);
    +void    qh_vertexridges_facet(qhT *qh, vertexT *vertex, facetT *facet, setT **ridges);
    +void    qh_willdelete(qhT *qh, facetT *facet, facetT *replace);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFmerge */
    diff --git a/xs/src/qhull/src/libqhull_r/poly2_r.c b/xs/src/qhull/src/libqhull_r/poly2_r.c
    new file mode 100644
    index 0000000000..b8ae9af9f4
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/poly2_r.c
    @@ -0,0 +1,3222 @@
    +/*
      ---------------------------------
    +
    +   poly2_r.c
    +   implements polygons and simplicies
    +
    +   see qh-poly_r.htm, poly_r.h and libqhull_r.h
    +
    +   frequently used code is in poly_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/poly2_r.c#10 $$Change: 2069 $
    +   $DateTime: 2016/01/18 22:05:03 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*======== functions in alphabetical order ==========*/
    +
    +/*---------------------------------
    +
    +  qh_addhash( newelem, hashtable, hashsize, hash )
    +    add newelem to linear hash table at hash if not already there
    +*/
    +void qh_addhash(void *newelem, setT *hashtable, int hashsize, int hash) {
    +  int scan;
    +  void *elem;
    +
    +  for (scan= (int)hash; (elem= SETelem_(hashtable, scan));
    +       scan= (++scan >= hashsize ? 0 : scan)) {
    +    if (elem == newelem)
    +      break;
    +  }
    +  /* loop terminates because qh_HASHfactor >= 1.1 by qh_initbuffers */
    +  if (!elem)
    +    SETelem_(hashtable, scan)= newelem;
    +} /* addhash */
    +
    +/*---------------------------------
    +
    +  qh_check_bestdist(qh)
    +    check that all points are within max_outside of the nearest facet
    +    if qh.ONLYgood,
    +      ignores !good facets
    +
    +  see:
    +    qh_check_maxout(), qh_outerinner()
    +
    +  notes:
    +    only called from qh_check_points()
    +      seldom used since qh.MERGING is almost always set
    +    if notverified>0 at end of routine
    +      some points were well inside the hull.  If the hull contains
    +      a lens-shaped component, these points were not verified.  Use
    +      options 'Qi Tv' to verify all points.  (Exhaustive check also verifies)
    +
    +  design:
    +    determine facet for each point (if any)
    +    for each point
    +      start with the assigned facet or with the first facet
    +      find the best facet for the point and check all coplanar facets
    +      error if point is outside of facet
    +*/
    +void qh_check_bestdist(qhT *qh) {
    +  boolT waserror= False, unassigned;
    +  facetT *facet, *bestfacet, *errfacet1= NULL, *errfacet2= NULL;
    +  facetT *facetlist;
    +  realT dist, maxoutside, maxdist= -REALmax;
    +  pointT *point;
    +  int numpart= 0, facet_i, facet_n, notgood= 0, notverified= 0;
    +  setT *facets;
    +
    +  trace1((qh, qh->ferr, 1020, "qh_check_bestdist: check points below nearest facet.  Facet_list f%d\n",
    +      qh->facet_list->id));
    +  maxoutside= qh_maxouter(qh);
    +  maxoutside += qh->DISTround;
    +  /* one more qh.DISTround for check computation */
    +  trace1((qh, qh->ferr, 1021, "qh_check_bestdist: check that all points are within %2.2g of best facet\n", maxoutside));
    +  facets= qh_pointfacet(qh /*qh.facet_list*/);
    +  if (!qh_QUICKhelp && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 8091, "\n\
    +qhull output completed.  Verifying that %d points are\n\
    +below %2.2g of the nearest %sfacet.\n",
    +             qh_setsize(qh, facets), maxoutside, (qh->ONLYgood ?  "good " : ""));
    +  FOREACHfacet_i_(qh, facets) {  /* for each point with facet assignment */
    +    if (facet)
    +      unassigned= False;
    +    else {
    +      unassigned= True;
    +      facet= qh->facet_list;
    +    }
    +    point= qh_point(qh, facet_i);
    +    if (point == qh->GOODpointp)
    +      continue;
    +    qh_distplane(qh, point, facet, &dist);
    +    numpart++;
    +    bestfacet= qh_findbesthorizon(qh, !qh_IScheckmax, point, facet, qh_NOupper, &dist, &numpart);
    +    /* occurs after statistics reported */
    +    maximize_(maxdist, dist);
    +    if (dist > maxoutside) {
    +      if (qh->ONLYgood && !bestfacet->good
    +          && !((bestfacet= qh_findgooddist(qh, point, bestfacet, &dist, &facetlist))
    +               && dist > maxoutside))
    +        notgood++;
    +      else {
    +        waserror= True;
    +        qh_fprintf(qh, qh->ferr, 6109, "qhull precision error: point p%d is outside facet f%d, distance= %6.8g maxoutside= %6.8g\n",
    +                facet_i, bestfacet->id, dist, maxoutside);
    +        if (errfacet1 != bestfacet) {
    +          errfacet2= errfacet1;
    +          errfacet1= bestfacet;
    +        }
    +      }
    +    }else if (unassigned && dist < -qh->MAXcoplanar)
    +      notverified++;
    +  }
    +  qh_settempfree(qh, &facets);
    +  if (notverified && !qh->DELAUNAY && !qh_QUICKhelp && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 8092, "\n%d points were well inside the hull.  If the hull contains\n\
    +a lens-shaped component, these points were not verified.  Use\n\
    +options 'Qci Tv' to verify all points.\n", notverified);
    +  if (maxdist > qh->outside_err) {
    +    qh_fprintf(qh, qh->ferr, 6110, "qhull precision error (qh_check_bestdist): a coplanar point is %6.2g from convex hull.  The maximum value(qh.outside_err) is %6.2g\n",
    +              maxdist, qh->outside_err);
    +    qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2);
    +  }else if (waserror && qh->outside_err > REALmax/2)
    +    qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2);
    +  /* else if waserror, the error was logged to qh.ferr but does not effect the output */
    +  trace0((qh, qh->ferr, 20, "qh_check_bestdist: max distance outside %2.2g\n", maxdist));
    +} /* check_bestdist */
    +
    +/*---------------------------------
    +
    +  qh_check_dupridge(qh, facet1, dist1, facet2, dist2)
    +    Check duplicate ridge between facet1 and facet2 for wide merge
    +    dist1 is the maximum distance of facet1's vertices to facet2
    +    dist2 is the maximum distance of facet2's vertices to facet1
    +
    +  Returns
    +    Level 1 log of the duplicate ridge with the minimum distance between vertices
    +    Throws error if the merge will increase the maximum facet width by qh_WIDEduplicate (100x)
    +
    +  called from:
    +    qh_forcedmerges()
    +*/
    +#ifndef qh_NOmerge
    +void qh_check_dupridge(qhT *qh, facetT *facet1, realT dist1, facetT *facet2, realT dist2) {
    +  vertexT *vertex, **vertexp, *vertexA, **vertexAp;
    +  realT dist, innerplane, mergedist, outerplane, prevdist, ratio;
    +  realT minvertex= REALmax;
    +
    +  mergedist= fmin_(dist1, dist2);
    +  qh_outerinner(qh, NULL, &outerplane, &innerplane);  /* ratio from qh_printsummary */
    +  prevdist= fmax_(outerplane, innerplane);
    +  maximize_(prevdist, qh->ONEmerge + qh->DISTround);
    +  maximize_(prevdist, qh->MINoutside + qh->DISTround);
    +  ratio= mergedist/prevdist;
    +  FOREACHvertex_(facet1->vertices) {     /* The duplicate ridge is between facet1 and facet2, so either facet can be tested */
    +    FOREACHvertexA_(facet1->vertices) {
    +      if (vertex > vertexA){   /* Test each pair once */
    +        dist= qh_pointdist(vertex->point, vertexA->point, qh->hull_dim);
    +        minimize_(minvertex, dist);
    +      }
    +    }
    +  }
    +  trace0((qh, qh->ferr, 16, "qh_check_dupridge: duplicate ridge between f%d and f%d due to nearly-coincident vertices (%2.2g), dist %2.2g, reverse dist %2.2g, ratio %2.2g while processing p%d\n",
    +        facet1->id, facet2->id, minvertex, dist1, dist2, ratio, qh->furthest_id));
    +  if (ratio > qh_WIDEduplicate) {
    +    qh_fprintf(qh, qh->ferr, 6271, "qhull precision error (qh_check_dupridge): wide merge (%.0f times wider) due to duplicate ridge with nearly coincident points (%2.2g) between f%d and f%d, merge dist %2.2g, while processing p%d\n- Ignore error with option 'Q12'\n- To be fixed in a later version of Qhull\n",
    +          ratio, minvertex, facet1->id, facet2->id, mergedist, qh->furthest_id);
    +    if (qh->DELAUNAY)
    +      qh_fprintf(qh, qh->ferr, 8145, "- A bounding box for the input sites may alleviate this error.\n");
    +    if(minvertex > qh_WIDEduplicate*prevdist)
    +      qh_fprintf(qh, qh->ferr, 8146, "- Vertex distance %2.2g is greater than %d times maximum distance %2.2g\n  Please report to bradb@shore.net with steps to reproduce and all output\n",
    +          minvertex, qh_WIDEduplicate, prevdist);
    +    if (!qh->NOwide)
    +      qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +  }
    +} /* check_dupridge */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_check_maxout(qh)
    +    updates qh.max_outside by checking all points against bestfacet
    +    if qh.ONLYgood, ignores !good facets
    +
    +  returns:
    +    updates facet->maxoutside via qh_findbesthorizon()
    +    sets qh.maxoutdone
    +    if printing qh.min_vertex (qh_outerinner),
    +      it is updated to the current vertices
    +    removes inside/coplanar points from coplanarset as needed
    +
    +  notes:
    +    defines coplanar as min_vertex instead of MAXcoplanar
    +    may not need to check near-inside points because of qh.MAXcoplanar
    +      and qh.KEEPnearinside (before it was -DISTround)
    +
    +  see also:
    +    qh_check_bestdist()
    +
    +  design:
    +    if qh.min_vertex is needed
    +      for all neighbors of all vertices
    +        test distance from vertex to neighbor
    +    determine facet for each point (if any)
    +    for each point with an assigned facet
    +      find the best facet for the point and check all coplanar facets
    +        (updates outer planes)
    +    remove near-inside points from coplanar sets
    +*/
    +#ifndef qh_NOmerge
    +void qh_check_maxout(qhT *qh) {
    +  facetT *facet, *bestfacet, *neighbor, **neighborp, *facetlist;
    +  realT dist, maxoutside, minvertex, old_maxoutside;
    +  pointT *point;
    +  int numpart= 0, facet_i, facet_n, notgood= 0;
    +  setT *facets, *vertices;
    +  vertexT *vertex;
    +
    +  trace1((qh, qh->ferr, 1022, "qh_check_maxout: check and update maxoutside for each facet.\n"));
    +  maxoutside= minvertex= 0;
    +  if (qh->VERTEXneighbors
    +  && (qh->PRINTsummary || qh->KEEPinside || qh->KEEPcoplanar
    +        || qh->TRACElevel || qh->PRINTstatistics
    +        || qh->PRINTout[0] == qh_PRINTsummary || qh->PRINTout[0] == qh_PRINTnone)) {
    +    trace1((qh, qh->ferr, 1023, "qh_check_maxout: determine actual maxoutside and minvertex\n"));
    +    vertices= qh_pointvertex(qh /*qh.facet_list*/);
    +    FORALLvertices {
    +      FOREACHneighbor_(vertex) {
    +        zinc_(Zdistvertex);  /* distance also computed by main loop below */
    +        qh_distplane(qh, vertex->point, neighbor, &dist);
    +        minimize_(minvertex, dist);
    +        if (-dist > qh->TRACEdist || dist > qh->TRACEdist
    +        || neighbor == qh->tracefacet || vertex == qh->tracevertex)
    +          qh_fprintf(qh, qh->ferr, 8093, "qh_check_maxout: p%d(v%d) is %.2g from f%d\n",
    +                    qh_pointid(qh, vertex->point), vertex->id, dist, neighbor->id);
    +      }
    +    }
    +    if (qh->MERGING) {
    +      wmin_(Wminvertex, qh->min_vertex);
    +    }
    +    qh->min_vertex= minvertex;
    +    qh_settempfree(qh, &vertices);
    +  }
    +  facets= qh_pointfacet(qh /*qh.facet_list*/);
    +  do {
    +    old_maxoutside= fmax_(qh->max_outside, maxoutside);
    +    FOREACHfacet_i_(qh, facets) {     /* for each point with facet assignment */
    +      if (facet) {
    +        point= qh_point(qh, facet_i);
    +        if (point == qh->GOODpointp)
    +          continue;
    +        zzinc_(Ztotcheck);
    +        qh_distplane(qh, point, facet, &dist);
    +        numpart++;
    +        bestfacet= qh_findbesthorizon(qh, qh_IScheckmax, point, facet, !qh_NOupper, &dist, &numpart);
    +        if (bestfacet && dist > maxoutside) {
    +          if (qh->ONLYgood && !bestfacet->good
    +          && !((bestfacet= qh_findgooddist(qh, point, bestfacet, &dist, &facetlist))
    +               && dist > maxoutside))
    +            notgood++;
    +          else
    +            maxoutside= dist;
    +        }
    +        if (dist > qh->TRACEdist || (bestfacet && bestfacet == qh->tracefacet))
    +          qh_fprintf(qh, qh->ferr, 8094, "qh_check_maxout: p%d is %.2g above f%d\n",
    +          qh_pointid(qh, point), dist, (bestfacet ? bestfacet->id : UINT_MAX));
    +      }
    +    }
    +  }while
    +    (maxoutside > 2*old_maxoutside);
    +    /* if qh.maxoutside increases substantially, qh_SEARCHdist is not valid
    +          e.g., RBOX 5000 s Z1 G1e-13 t1001200614 | qhull */
    +  zzadd_(Zcheckpart, numpart);
    +  qh_settempfree(qh, &facets);
    +  wval_(Wmaxout)= maxoutside - qh->max_outside;
    +  wmax_(Wmaxoutside, qh->max_outside);
    +  qh->max_outside= maxoutside;
    +  qh_nearcoplanar(qh /*qh.facet_list*/);
    +  qh->maxoutdone= True;
    +  trace1((qh, qh->ferr, 1024, "qh_check_maxout: maxoutside %2.2g, min_vertex %2.2g, outside of not good %d\n",
    +       maxoutside, qh->min_vertex, notgood));
    +} /* check_maxout */
    +#else /* qh_NOmerge */
    +void qh_check_maxout(qhT *qh) {
    +}
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_check_output(qh)
    +    performs the checks at the end of qhull algorithm
    +    Maybe called after voronoi output.  Will recompute otherwise centrums are Voronoi centers instead
    +*/
    +void qh_check_output(qhT *qh) {
    +  int i;
    +
    +  if (qh->STOPcone)
    +    return;
    +  if (qh->VERIFYoutput | qh->IStracing | qh->CHECKfrequently) {
    +    qh_checkpolygon(qh, qh->facet_list);
    +    qh_checkflipped_all(qh, qh->facet_list);
    +    qh_checkconvex(qh, qh->facet_list, qh_ALGORITHMfault);
    +  }else if (!qh->MERGING && qh_newstats(qh, qh->qhstat.precision, &i)) {
    +    qh_checkflipped_all(qh, qh->facet_list);
    +    qh_checkconvex(qh, qh->facet_list, qh_ALGORITHMfault);
    +  }
    +} /* check_output */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_check_point(qh, point, facet, maxoutside, maxdist, errfacet1, errfacet2 )
    +    check that point is less than maxoutside from facet
    +*/
    +void qh_check_point(qhT *qh, pointT *point, facetT *facet, realT *maxoutside, realT *maxdist, facetT **errfacet1, facetT **errfacet2) {
    +  realT dist;
    +
    +  /* occurs after statistics reported */
    +  qh_distplane(qh, point, facet, &dist);
    +  if (dist > *maxoutside) {
    +    if (*errfacet1 != facet) {
    +      *errfacet2= *errfacet1;
    +      *errfacet1= facet;
    +    }
    +    qh_fprintf(qh, qh->ferr, 6111, "qhull precision error: point p%d is outside facet f%d, distance= %6.8g maxoutside= %6.8g\n",
    +              qh_pointid(qh, point), facet->id, dist, *maxoutside);
    +  }
    +  maximize_(*maxdist, dist);
    +} /* qh_check_point */
    +
    +
    +/*---------------------------------
    +
    +  qh_check_points(qh)
    +    checks that all points are inside all facets
    +
    +  notes:
    +    if many points and qh_check_maxout not called (i.e., !qh.MERGING),
    +       calls qh_findbesthorizon (seldom done).
    +    ignores flipped facets
    +    maxoutside includes 2 qh.DISTrounds
    +      one qh.DISTround for the computed distances in qh_check_points
    +    qh_printafacet and qh_printsummary needs only one qh.DISTround
    +    the computation for qh.VERIFYdirect does not account for qh.other_points
    +
    +  design:
    +    if many points
    +      use qh_check_bestdist()
    +    else
    +      for all facets
    +        for all points
    +          check that point is inside facet
    +*/
    +void qh_check_points(qhT *qh) {
    +  facetT *facet, *errfacet1= NULL, *errfacet2= NULL;
    +  realT total, maxoutside, maxdist= -REALmax;
    +  pointT *point, **pointp, *pointtemp;
    +  boolT testouter;
    +
    +  maxoutside= qh_maxouter(qh);
    +  maxoutside += qh->DISTround;
    +  /* one more qh.DISTround for check computation */
    +  trace1((qh, qh->ferr, 1025, "qh_check_points: check all points below %2.2g of all facet planes\n",
    +          maxoutside));
    +  if (qh->num_good)   /* miss counts other_points and !good facets */
    +     total= (float)qh->num_good * (float)qh->num_points;
    +  else
    +     total= (float)qh->num_facets * (float)qh->num_points;
    +  if (total >= qh_VERIFYdirect && !qh->maxoutdone) {
    +    if (!qh_QUICKhelp && qh->SKIPcheckmax && qh->MERGING)
    +      qh_fprintf(qh, qh->ferr, 7075, "qhull input warning: merging without checking outer planes('Q5' or 'Po').\n\
    +Verify may report that a point is outside of a facet.\n");
    +    qh_check_bestdist(qh);
    +  }else {
    +    if (qh_MAXoutside && qh->maxoutdone)
    +      testouter= True;
    +    else
    +      testouter= False;
    +    if (!qh_QUICKhelp) {
    +      if (qh->MERGEexact)
    +        qh_fprintf(qh, qh->ferr, 7076, "qhull input warning: exact merge ('Qx').  Verify may report that a point\n\
    +is outside of a facet.  See qh-optq.htm#Qx\n");
    +      else if (qh->SKIPcheckmax || qh->NOnearinside)
    +        qh_fprintf(qh, qh->ferr, 7077, "qhull input warning: no outer plane check ('Q5') or no processing of\n\
    +near-inside points ('Q8').  Verify may report that a point is outside\n\
    +of a facet.\n");
    +    }
    +    if (qh->PRINTprecision) {
    +      if (testouter)
    +        qh_fprintf(qh, qh->ferr, 8098, "\n\
    +Output completed.  Verifying that all points are below outer planes of\n\
    +all %sfacets.  Will make %2.0f distance computations.\n",
    +              (qh->ONLYgood ?  "good " : ""), total);
    +      else
    +        qh_fprintf(qh, qh->ferr, 8099, "\n\
    +Output completed.  Verifying that all points are below %2.2g of\n\
    +all %sfacets.  Will make %2.0f distance computations.\n",
    +              maxoutside, (qh->ONLYgood ?  "good " : ""), total);
    +    }
    +    FORALLfacets {
    +      if (!facet->good && qh->ONLYgood)
    +        continue;
    +      if (facet->flipped)
    +        continue;
    +      if (!facet->normal) {
    +        qh_fprintf(qh, qh->ferr, 7061, "qhull warning (qh_check_points): missing normal for facet f%d\n", facet->id);
    +        continue;
    +      }
    +      if (testouter) {
    +#if qh_MAXoutside
    +        maxoutside= facet->maxoutside + 2* qh->DISTround;
    +        /* one DISTround to actual point and another to computed point */
    +#endif
    +      }
    +      FORALLpoints {
    +        if (point != qh->GOODpointp)
    +          qh_check_point(qh, point, facet, &maxoutside, &maxdist, &errfacet1, &errfacet2);
    +      }
    +      FOREACHpoint_(qh->other_points) {
    +        if (point != qh->GOODpointp)
    +          qh_check_point(qh, point, facet, &maxoutside, &maxdist, &errfacet1, &errfacet2);
    +      }
    +    }
    +    if (maxdist > qh->outside_err) {
    +      qh_fprintf(qh, qh->ferr, 6112, "qhull precision error (qh_check_points): a coplanar point is %6.2g from convex hull.  The maximum value(qh.outside_err) is %6.2g\n",
    +                maxdist, qh->outside_err );
    +      qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2 );
    +    }else if (errfacet1 && qh->outside_err > REALmax/2)
    +        qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2 );
    +    /* else if errfacet1, the error was logged to qh.ferr but does not effect the output */
    +    trace0((qh, qh->ferr, 21, "qh_check_points: max distance outside %2.2g\n", maxdist));
    +  }
    +} /* check_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkconvex(qh, facetlist, fault )
    +    check that each ridge in facetlist is convex
    +    fault = qh_DATAfault if reporting errors
    +          = qh_ALGORITHMfault otherwise
    +
    +  returns:
    +    counts Zconcaveridges and Zcoplanarridges
    +    errors if concaveridge or if merging an coplanar ridge
    +
    +  note:
    +    if not merging,
    +      tests vertices for neighboring simplicial facets
    +    else if ZEROcentrum,
    +      tests vertices for neighboring simplicial   facets
    +    else
    +      tests centrums of neighboring facets
    +
    +  design:
    +    for all facets
    +      report flipped facets
    +      if ZEROcentrum and simplicial neighbors
    +        test vertices for neighboring simplicial facets
    +      else
    +        test centrum against all neighbors
    +*/
    +void qh_checkconvex(qhT *qh, facetT *facetlist, int fault) {
    +  facetT *facet, *neighbor, **neighborp, *errfacet1=NULL, *errfacet2=NULL;
    +  vertexT *vertex;
    +  realT dist;
    +  pointT *centrum;
    +  boolT waserror= False, centrum_warning= False, tempcentrum= False, allsimplicial;
    +  int neighbor_i;
    +
    +  trace1((qh, qh->ferr, 1026, "qh_checkconvex: check all ridges are convex\n"));
    +  if (!qh->RERUN) {
    +    zzval_(Zconcaveridges)= 0;
    +    zzval_(Zcoplanarridges)= 0;
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->flipped) {
    +      qh_precision(qh, "flipped facet");
    +      qh_fprintf(qh, qh->ferr, 6113, "qhull precision error: f%d is flipped(interior point is outside)\n",
    +               facet->id);
    +      errfacet1= facet;
    +      waserror= True;
    +      continue;
    +    }
    +    if (qh->MERGING && (!qh->ZEROcentrum || !facet->simplicial || facet->tricoplanar))
    +      allsimplicial= False;
    +    else {
    +      allsimplicial= True;
    +      neighbor_i= 0;
    +      FOREACHneighbor_(facet) {
    +        vertex= SETelemt_(facet->vertices, neighbor_i++, vertexT);
    +        if (!neighbor->simplicial || neighbor->tricoplanar) {
    +          allsimplicial= False;
    +          continue;
    +        }
    +        qh_distplane(qh, vertex->point, neighbor, &dist);
    +        if (dist > -qh->DISTround) {
    +          if (fault == qh_DATAfault) {
    +            qh_precision(qh, "coplanar or concave ridge");
    +            qh_fprintf(qh, qh->ferr, 6114, "qhull precision error: initial simplex is not convex. Distance=%.2g\n", dist);
    +            qh_errexit(qh, qh_ERRsingular, NULL, NULL);
    +          }
    +          if (dist > qh->DISTround) {
    +            zzinc_(Zconcaveridges);
    +            qh_precision(qh, "concave ridge");
    +            qh_fprintf(qh, qh->ferr, 6115, "qhull precision error: f%d is concave to f%d, since p%d(v%d) is %6.4g above\n",
    +              facet->id, neighbor->id, qh_pointid(qh, vertex->point), vertex->id, dist);
    +            errfacet1= facet;
    +            errfacet2= neighbor;
    +            waserror= True;
    +          }else if (qh->ZEROcentrum) {
    +            if (dist > 0) {     /* qh_checkzero checks that dist < - qh->DISTround */
    +              zzinc_(Zcoplanarridges);
    +              qh_precision(qh, "coplanar ridge");
    +              qh_fprintf(qh, qh->ferr, 6116, "qhull precision error: f%d is clearly not convex to f%d, since p%d(v%d) is %6.4g above\n",
    +                facet->id, neighbor->id, qh_pointid(qh, vertex->point), vertex->id, dist);
    +              errfacet1= facet;
    +              errfacet2= neighbor;
    +              waserror= True;
    +            }
    +          }else {
    +            zzinc_(Zcoplanarridges);
    +            qh_precision(qh, "coplanar ridge");
    +            trace0((qh, qh->ferr, 22, "qhull precision error: f%d may be coplanar to f%d, since p%d(v%d) is within %6.4g during p%d\n",
    +              facet->id, neighbor->id, qh_pointid(qh, vertex->point), vertex->id, dist, qh->furthest_id));
    +          }
    +        }
    +      }
    +    }
    +    if (!allsimplicial) {
    +      if (qh->CENTERtype == qh_AScentrum) {
    +        if (!facet->center)
    +          facet->center= qh_getcentrum(qh, facet);
    +        centrum= facet->center;
    +      }else {
    +        if (!centrum_warning && (!facet->simplicial || facet->tricoplanar)) {
    +           centrum_warning= True;
    +           qh_fprintf(qh, qh->ferr, 7062, "qhull warning: recomputing centrums for convexity test.  This may lead to false, precision errors.\n");
    +        }
    +        centrum= qh_getcentrum(qh, facet);
    +        tempcentrum= True;
    +      }
    +      FOREACHneighbor_(facet) {
    +        if (qh->ZEROcentrum && facet->simplicial && neighbor->simplicial)
    +          continue;
    +        if (facet->tricoplanar || neighbor->tricoplanar)
    +          continue;
    +        zzinc_(Zdistconvex);
    +        qh_distplane(qh, centrum, neighbor, &dist);
    +        if (dist > qh->DISTround) {
    +          zzinc_(Zconcaveridges);
    +          qh_precision(qh, "concave ridge");
    +          qh_fprintf(qh, qh->ferr, 6117, "qhull precision error: f%d is concave to f%d.  Centrum of f%d is %6.4g above f%d\n",
    +            facet->id, neighbor->id, facet->id, dist, neighbor->id);
    +          errfacet1= facet;
    +          errfacet2= neighbor;
    +          waserror= True;
    +        }else if (dist >= 0.0) {   /* if arithmetic always rounds the same,
    +                                     can test against centrum radius instead */
    +          zzinc_(Zcoplanarridges);
    +          qh_precision(qh, "coplanar ridge");
    +          qh_fprintf(qh, qh->ferr, 6118, "qhull precision error: f%d is coplanar or concave to f%d.  Centrum of f%d is %6.4g above f%d\n",
    +            facet->id, neighbor->id, facet->id, dist, neighbor->id);
    +          errfacet1= facet;
    +          errfacet2= neighbor;
    +          waserror= True;
    +        }
    +      }
    +      if (tempcentrum)
    +        qh_memfree(qh, centrum, qh->normal_size);
    +    }
    +  }
    +  if (waserror && !qh->FORCEoutput)
    +    qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2);
    +} /* checkconvex */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkfacet(qh, facet, newmerge, waserror )
    +    checks for consistency errors in facet
    +    newmerge set if from merge_r.c
    +
    +  returns:
    +    sets waserror if any error occurs
    +
    +  checks:
    +    vertex ids are inverse sorted
    +    unless newmerge, at least hull_dim neighbors and vertices (exactly if simplicial)
    +    if non-simplicial, at least as many ridges as neighbors
    +    neighbors are not duplicated
    +    ridges are not duplicated
    +    in 3-d, ridges=verticies
    +    (qh.hull_dim-1) ridge vertices
    +    neighbors are reciprocated
    +    ridge neighbors are facet neighbors and a ridge for every neighbor
    +    simplicial neighbors match facetintersect
    +    vertex intersection matches vertices of common ridges
    +    vertex neighbors and facet vertices agree
    +    all ridges have distinct vertex sets
    +
    +  notes:
    +    uses neighbor->seen
    +
    +  design:
    +    check sets
    +    check vertices
    +    check sizes of neighbors and vertices
    +    check for qh_MERGEridge and qh_DUPLICATEridge flags
    +    check neighbor set
    +    check ridge set
    +    check ridges, neighbors, and vertices
    +*/
    +void qh_checkfacet(qhT *qh, facetT *facet, boolT newmerge, boolT *waserrorp) {
    +  facetT *neighbor, **neighborp, *errother=NULL;
    +  ridgeT *ridge, **ridgep, *errridge= NULL, *ridge2;
    +  vertexT *vertex, **vertexp;
    +  unsigned previousid= INT_MAX;
    +  int numneighbors, numvertices, numridges=0, numRvertices=0;
    +  boolT waserror= False;
    +  int skipA, skipB, ridge_i, ridge_n, i;
    +  setT *intersection;
    +
    +  if (facet->visible) {
    +    qh_fprintf(qh, qh->ferr, 6119, "qhull internal error (qh_checkfacet): facet f%d is on the visible_list\n",
    +      facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  if (!facet->normal) {
    +    qh_fprintf(qh, qh->ferr, 6120, "qhull internal error (qh_checkfacet): facet f%d does not have  a normal\n",
    +      facet->id);
    +    waserror= True;
    +  }
    +  qh_setcheck(qh, facet->vertices, "vertices for f", facet->id);
    +  qh_setcheck(qh, facet->ridges, "ridges for f", facet->id);
    +  qh_setcheck(qh, facet->outsideset, "outsideset for f", facet->id);
    +  qh_setcheck(qh, facet->coplanarset, "coplanarset for f", facet->id);
    +  qh_setcheck(qh, facet->neighbors, "neighbors for f", facet->id);
    +  FOREACHvertex_(facet->vertices) {
    +    if (vertex->deleted) {
    +      qh_fprintf(qh, qh->ferr, 6121, "qhull internal error (qh_checkfacet): deleted vertex v%d in f%d\n", vertex->id, facet->id);
    +      qh_errprint(qh, "ERRONEOUS", NULL, NULL, NULL, vertex);
    +      waserror= True;
    +    }
    +    if (vertex->id >= previousid) {
    +      qh_fprintf(qh, qh->ferr, 6122, "qhull internal error (qh_checkfacet): vertices of f%d are not in descending id order at v%d\n", facet->id, vertex->id);
    +      waserror= True;
    +      break;
    +    }
    +    previousid= vertex->id;
    +  }
    +  numneighbors= qh_setsize(qh, facet->neighbors);
    +  numvertices= qh_setsize(qh, facet->vertices);
    +  numridges= qh_setsize(qh, facet->ridges);
    +  if (facet->simplicial) {
    +    if (numvertices+numneighbors != 2*qh->hull_dim
    +    && !facet->degenerate && !facet->redundant) {
    +      qh_fprintf(qh, qh->ferr, 6123, "qhull internal error (qh_checkfacet): for simplicial facet f%d, #vertices %d + #neighbors %d != 2*qh->hull_dim\n",
    +                facet->id, numvertices, numneighbors);
    +      qh_setprint(qh, qh->ferr, "", facet->neighbors);
    +      waserror= True;
    +    }
    +  }else { /* non-simplicial */
    +    if (!newmerge
    +    &&(numvertices < qh->hull_dim || numneighbors < qh->hull_dim)
    +    && !facet->degenerate && !facet->redundant) {
    +      qh_fprintf(qh, qh->ferr, 6124, "qhull internal error (qh_checkfacet): for facet f%d, #vertices %d or #neighbors %d < qh->hull_dim\n",
    +         facet->id, numvertices, numneighbors);
    +       waserror= True;
    +    }
    +    /* in 3-d, can get a vertex twice in an edge list, e.g., RBOX 1000 s W1e-13 t995849315 D2 | QHULL d Tc Tv TP624 TW1e-13 T4 */
    +    if (numridges < numneighbors
    +    ||(qh->hull_dim == 3 && numvertices > numridges && !qh->NEWfacets)
    +    ||(qh->hull_dim == 2 && numridges + numvertices + numneighbors != 6)) {
    +      if (!facet->degenerate && !facet->redundant) {
    +        qh_fprintf(qh, qh->ferr, 6125, "qhull internal error (qh_checkfacet): for facet f%d, #ridges %d < #neighbors %d or(3-d) > #vertices %d or(2-d) not all 2\n",
    +            facet->id, numridges, numneighbors, numvertices);
    +        waserror= True;
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge || neighbor == qh_DUPLICATEridge) {
    +      qh_fprintf(qh, qh->ferr, 6126, "qhull internal error (qh_checkfacet): facet f%d still has a MERGE or DUP neighbor\n", facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +    neighbor->seen= True;
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (!qh_setin(neighbor->neighbors, facet)) {
    +      qh_fprintf(qh, qh->ferr, 6127, "qhull internal error (qh_checkfacet): facet f%d has neighbor f%d, but f%d does not have neighbor f%d\n",
    +              facet->id, neighbor->id, neighbor->id, facet->id);
    +      errother= neighbor;
    +      waserror= True;
    +    }
    +    if (!neighbor->seen) {
    +      qh_fprintf(qh, qh->ferr, 6128, "qhull internal error (qh_checkfacet): facet f%d has a duplicate neighbor f%d\n",
    +              facet->id, neighbor->id);
    +      errother= neighbor;
    +      waserror= True;
    +    }
    +    neighbor->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges) {
    +    qh_setcheck(qh, ridge->vertices, "vertices for r", ridge->id);
    +    ridge->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges) {
    +    if (ridge->seen) {
    +      qh_fprintf(qh, qh->ferr, 6129, "qhull internal error (qh_checkfacet): facet f%d has a duplicate ridge r%d\n",
    +              facet->id, ridge->id);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +    ridge->seen= True;
    +    numRvertices= qh_setsize(qh, ridge->vertices);
    +    if (numRvertices != qh->hull_dim - 1) {
    +      qh_fprintf(qh, qh->ferr, 6130, "qhull internal error (qh_checkfacet): ridge between f%d and f%d has %d vertices\n",
    +                ridge->top->id, ridge->bottom->id, numRvertices);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +    neighbor= otherfacet_(ridge, facet);
    +    neighbor->seen= True;
    +    if (!qh_setin(facet->neighbors, neighbor)) {
    +      qh_fprintf(qh, qh->ferr, 6131, "qhull internal error (qh_checkfacet): for facet f%d, neighbor f%d of ridge r%d not in facet\n",
    +           facet->id, neighbor->id, ridge->id);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +  }
    +  if (!facet->simplicial) {
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->seen) {
    +        qh_fprintf(qh, qh->ferr, 6132, "qhull internal error (qh_checkfacet): facet f%d does not have a ridge for neighbor f%d\n",
    +              facet->id, neighbor->id);
    +        errother= neighbor;
    +        waserror= True;
    +      }
    +      intersection= qh_vertexintersect_new(qh, facet->vertices, neighbor->vertices);
    +      qh_settemppush(qh, intersection);
    +      FOREACHvertex_(facet->vertices) {
    +        vertex->seen= False;
    +        vertex->seen2= False;
    +      }
    +      FOREACHvertex_(intersection)
    +        vertex->seen= True;
    +      FOREACHridge_(facet->ridges) {
    +        if (neighbor != otherfacet_(ridge, facet))
    +            continue;
    +        FOREACHvertex_(ridge->vertices) {
    +          if (!vertex->seen) {
    +            qh_fprintf(qh, qh->ferr, 6133, "qhull internal error (qh_checkfacet): vertex v%d in r%d not in f%d intersect f%d\n",
    +                  vertex->id, ridge->id, facet->id, neighbor->id);
    +            qh_errexit(qh, qh_ERRqhull, facet, ridge);
    +          }
    +          vertex->seen2= True;
    +        }
    +      }
    +      if (!newmerge) {
    +        FOREACHvertex_(intersection) {
    +          if (!vertex->seen2) {
    +            if (qh->IStracing >=3 || !qh->MERGING) {
    +              qh_fprintf(qh, qh->ferr, 6134, "qhull precision error (qh_checkfacet): vertex v%d in f%d intersect f%d but\n\
    + not in a ridge.  This is ok under merging.  Last point was p%d\n",
    +                     vertex->id, facet->id, neighbor->id, qh->furthest_id);
    +              if (!qh->FORCEoutput && !qh->MERGING) {
    +                qh_errprint(qh, "ERRONEOUS", facet, neighbor, NULL, vertex);
    +                if (!qh->MERGING)
    +                  qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +              }
    +            }
    +          }
    +        }
    +      }
    +      qh_settempfree(qh, &intersection);
    +    }
    +  }else { /* simplicial */
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->simplicial) {
    +        skipA= SETindex_(facet->neighbors, neighbor);
    +        skipB= qh_setindex(neighbor->neighbors, facet);
    +        if (skipA<0 || skipB<0 || !qh_setequal_skip(facet->vertices, skipA, neighbor->vertices, skipB)) {
    +          qh_fprintf(qh, qh->ferr, 6135, "qhull internal error (qh_checkfacet): facet f%d skip %d and neighbor f%d skip %d do not match \n",
    +                   facet->id, skipA, neighbor->id, skipB);
    +          errother= neighbor;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (qh->hull_dim < 5 && (qh->IStracing > 2 || qh->CHECKfrequently)) {
    +    FOREACHridge_i_(qh, facet->ridges) {           /* expensive */
    +      for (i=ridge_i+1; i < ridge_n; i++) {
    +        ridge2= SETelemt_(facet->ridges, i, ridgeT);
    +        if (qh_setequal(ridge->vertices, ridge2->vertices)) {
    +          qh_fprintf(qh, qh->ferr, 6227, "Qhull internal error (qh_checkfacet): ridges r%d and r%d have the same vertices\n",
    +                  ridge->id, ridge2->id);
    +          errridge= ridge;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_errprint(qh, "ERRONEOUS", facet, errother, errridge, NULL);
    +    *waserrorp= True;
    +  }
    +} /* checkfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkflipped_all(qh, facetlist )
    +    checks orientation of facets in list against interior point
    +*/
    +void qh_checkflipped_all(qhT *qh, facetT *facetlist) {
    +  facetT *facet;
    +  boolT waserror= False;
    +  realT dist;
    +
    +  if (facetlist == qh->facet_list)
    +    zzval_(Zflippedfacets)= 0;
    +  FORALLfacet_(facetlist) {
    +    if (facet->normal && !qh_checkflipped(qh, facet, &dist, !qh_ALL)) {
    +      qh_fprintf(qh, qh->ferr, 6136, "qhull precision error: facet f%d is flipped, distance= %6.12g\n",
    +              facet->id, dist);
    +      if (!qh->FORCEoutput) {
    +        qh_errprint(qh, "ERRONEOUS", facet, NULL, NULL, NULL);
    +        waserror= True;
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_fprintf(qh, qh->ferr, 8101, "\n\
    +A flipped facet occurs when its distance to the interior point is\n\
    +greater than %2.2g, the maximum roundoff error.\n", -qh->DISTround);
    +    qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +  }
    +} /* checkflipped_all */
    +
    +/*---------------------------------
    +
    +  qh_checkpolygon(qh, facetlist )
    +    checks the correctness of the structure
    +
    +  notes:
    +    call with either qh.facet_list or qh.newfacet_list
    +    checks num_facets and num_vertices if qh.facet_list
    +
    +  design:
    +    for each facet
    +      checks facet and outside set
    +    initializes vertexlist
    +    for each facet
    +      checks vertex set
    +    if checking all facets(qh.facetlist)
    +      check facet count
    +      if qh.VERTEXneighbors
    +        check vertex neighbors and count
    +      check vertex count
    +*/
    +void qh_checkpolygon(qhT *qh, facetT *facetlist) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp, *vertexlist;
    +  int numfacets= 0, numvertices= 0, numridges= 0;
    +  int totvneighbors= 0, totvertices= 0;
    +  boolT waserror= False, nextseen= False, visibleseen= False;
    +
    +  trace1((qh, qh->ferr, 1027, "qh_checkpolygon: check all facets from f%d\n", facetlist->id));
    +  if (facetlist != qh->facet_list || qh->ONLYgood)
    +    nextseen= True;
    +  FORALLfacet_(facetlist) {
    +    if (facet == qh->visible_list)
    +      visibleseen= True;
    +    if (!facet->visible) {
    +      if (!nextseen) {
    +        if (facet == qh->facet_next)
    +          nextseen= True;
    +        else if (qh_setsize(qh, facet->outsideset)) {
    +          if (!qh->NARROWhull
    +#if !qh_COMPUTEfurthest
    +               || facet->furthestdist >= qh->MINoutside
    +#endif
    +                        ) {
    +            qh_fprintf(qh, qh->ferr, 6137, "qhull internal error (qh_checkpolygon): f%d has outside points before qh->facet_next\n",
    +                     facet->id);
    +            qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +          }
    +        }
    +      }
    +      numfacets++;
    +      qh_checkfacet(qh, facet, False, &waserror);
    +    }
    +  }
    +  if (qh->visible_list && !visibleseen && facetlist == qh->facet_list) {
    +    qh_fprintf(qh, qh->ferr, 6138, "qhull internal error (qh_checkpolygon): visible list f%d no longer on facet list\n", qh->visible_list->id);
    +    qh_printlists(qh);
    +    qh_errexit(qh, qh_ERRqhull, qh->visible_list, NULL);
    +  }
    +  if (facetlist == qh->facet_list)
    +    vertexlist= qh->vertex_list;
    +  else if (facetlist == qh->newfacet_list)
    +    vertexlist= qh->newvertex_list;
    +  else
    +    vertexlist= NULL;
    +  FORALLvertex_(vertexlist) {
    +    vertex->seen= False;
    +    vertex->visitid= 0;
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->visible)
    +      continue;
    +    if (facet->simplicial)
    +      numridges += qh->hull_dim;
    +    else
    +      numridges += qh_setsize(qh, facet->ridges);
    +    FOREACHvertex_(facet->vertices) {
    +      vertex->visitid++;
    +      if (!vertex->seen) {
    +        vertex->seen= True;
    +        numvertices++;
    +        if (qh_pointid(qh, vertex->point) == qh_IDunknown) {
    +          qh_fprintf(qh, qh->ferr, 6139, "qhull internal error (qh_checkpolygon): unknown point %p for vertex v%d first_point %p\n",
    +                   vertex->point, vertex->id, qh->first_point);
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  qh->vertex_visit += (unsigned int)numfacets;
    +  if (facetlist == qh->facet_list) {
    +    if (numfacets != qh->num_facets - qh->num_visible) {
    +      qh_fprintf(qh, qh->ferr, 6140, "qhull internal error (qh_checkpolygon): actual number of facets is %d, cumulative facet count is %d - %d visible facets\n",
    +              numfacets, qh->num_facets, qh->num_visible);
    +      waserror= True;
    +    }
    +    qh->vertex_visit++;
    +    if (qh->VERTEXneighbors) {
    +      FORALLvertices {
    +        qh_setcheck(qh, vertex->neighbors, "neighbors for v", vertex->id);
    +        if (vertex->deleted)
    +          continue;
    +        totvneighbors += qh_setsize(qh, vertex->neighbors);
    +      }
    +      FORALLfacet_(facetlist)
    +        totvertices += qh_setsize(qh, facet->vertices);
    +      if (totvneighbors != totvertices) {
    +        qh_fprintf(qh, qh->ferr, 6141, "qhull internal error (qh_checkpolygon): vertex neighbors inconsistent.  Totvneighbors %d, totvertices %d\n",
    +                totvneighbors, totvertices);
    +        waserror= True;
    +      }
    +    }
    +    if (numvertices != qh->num_vertices - qh_setsize(qh, qh->del_vertices)) {
    +      qh_fprintf(qh, qh->ferr, 6142, "qhull internal error (qh_checkpolygon): actual number of vertices is %d, cumulative vertex count is %d\n",
    +              numvertices, qh->num_vertices - qh_setsize(qh, qh->del_vertices));
    +      waserror= True;
    +    }
    +    if (qh->hull_dim == 2 && numvertices != numfacets) {
    +      qh_fprintf(qh, qh->ferr, 6143, "qhull internal error (qh_checkpolygon): #vertices %d != #facets %d\n",
    +        numvertices, numfacets);
    +      waserror= True;
    +    }
    +    if (qh->hull_dim == 3 && numvertices + numfacets - numridges/2 != 2) {
    +      qh_fprintf(qh, qh->ferr, 7063, "qhull warning: #vertices %d + #facets %d - #edges %d != 2\n\
    +        A vertex appears twice in a edge list.  May occur during merging.",
    +        numvertices, numfacets, numridges/2);
    +      /* occurs if lots of merging and a vertex ends up twice in an edge list.  e.g., RBOX 1000 s W1e-13 t995849315 D2 | QHULL d Tc Tv */
    +    }
    +  }
    +  if (waserror)
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +} /* checkpolygon */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkvertex(qh, vertex )
    +    check vertex for consistency
    +    checks vertex->neighbors
    +
    +  notes:
    +    neighbors checked efficiently in checkpolygon
    +*/
    +void qh_checkvertex(qhT *qh, vertexT *vertex) {
    +  boolT waserror= False;
    +  facetT *neighbor, **neighborp, *errfacet=NULL;
    +
    +  if (qh_pointid(qh, vertex->point) == qh_IDunknown) {
    +    qh_fprintf(qh, qh->ferr, 6144, "qhull internal error (qh_checkvertex): unknown point id %p\n", vertex->point);
    +    waserror= True;
    +  }
    +  if (vertex->id >= qh->vertex_id) {
    +    qh_fprintf(qh, qh->ferr, 6145, "qhull internal error (qh_checkvertex): unknown vertex id %d\n", vertex->id);
    +    waserror= True;
    +  }
    +  if (!waserror && !vertex->deleted) {
    +    if (qh_setsize(qh, vertex->neighbors)) {
    +      FOREACHneighbor_(vertex) {
    +        if (!qh_setin(neighbor->vertices, vertex)) {
    +          qh_fprintf(qh, qh->ferr, 6146, "qhull internal error (qh_checkvertex): neighbor f%d does not contain v%d\n", neighbor->id, vertex->id);
    +          errfacet= neighbor;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_errprint(qh, "ERRONEOUS", NULL, NULL, NULL, vertex);
    +    qh_errexit(qh, qh_ERRqhull, errfacet, NULL);
    +  }
    +} /* checkvertex */
    +
    +/*---------------------------------
    +
    +  qh_clearcenters(qh, type )
    +    clear old data from facet->center
    +
    +  notes:
    +    sets new centertype
    +    nop if CENTERtype is the same
    +*/
    +void qh_clearcenters(qhT *qh, qh_CENTER type) {
    +  facetT *facet;
    +
    +  if (qh->CENTERtype != type) {
    +    FORALLfacets {
    +      if (facet->tricoplanar && !facet->keepcentrum)
    +          facet->center= NULL;  /* center is owned by the ->keepcentrum facet */
    +      else if (qh->CENTERtype == qh_ASvoronoi){
    +        if (facet->center) {
    +          qh_memfree(qh, facet->center, qh->center_size);
    +          facet->center= NULL;
    +        }
    +      }else /* qh->CENTERtype == qh_AScentrum */ {
    +        if (facet->center) {
    +          qh_memfree(qh, facet->center, qh->normal_size);
    +          facet->center= NULL;
    +        }
    +      }
    +    }
    +    qh->CENTERtype= type;
    +  }
    +  trace2((qh, qh->ferr, 2043, "qh_clearcenters: switched to center type %d\n", type));
    +} /* clearcenters */
    +
    +/*---------------------------------
    +
    +  qh_createsimplex(qh, vertices )
    +    creates a simplex from a set of vertices
    +
    +  returns:
    +    initializes qh.facet_list to the simplex
    +    initializes qh.newfacet_list, .facet_tail
    +    initializes qh.vertex_list, .newvertex_list, .vertex_tail
    +
    +  design:
    +    initializes lists
    +    for each vertex
    +      create a new facet
    +    for each new facet
    +      create its neighbor set
    +*/
    +void qh_createsimplex(qhT *qh, setT *vertices) {
    +  facetT *facet= NULL, *newfacet;
    +  boolT toporient= True;
    +  int vertex_i, vertex_n, nth;
    +  setT *newfacets= qh_settemp(qh, qh->hull_dim+1);
    +  vertexT *vertex;
    +
    +  qh->facet_list= qh->newfacet_list= qh->facet_tail= qh_newfacet(qh);
    +  qh->num_facets= qh->num_vertices= qh->num_visible= 0;
    +  qh->vertex_list= qh->newvertex_list= qh->vertex_tail= qh_newvertex(qh, NULL);
    +  FOREACHvertex_i_(qh, vertices) {
    +    newfacet= qh_newfacet(qh);
    +    newfacet->vertices= qh_setnew_delnthsorted(qh, vertices, vertex_n,
    +                                                vertex_i, 0);
    +    newfacet->toporient= (unsigned char)toporient;
    +    qh_appendfacet(qh, newfacet);
    +    newfacet->newfacet= True;
    +    qh_appendvertex(qh, vertex);
    +    qh_setappend(qh, &newfacets, newfacet);
    +    toporient ^= True;
    +  }
    +  FORALLnew_facets {
    +    nth= 0;
    +    FORALLfacet_(qh->newfacet_list) {
    +      if (facet != newfacet)
    +        SETelem_(newfacet->neighbors, nth++)= facet;
    +    }
    +    qh_settruncate(qh, newfacet->neighbors, qh->hull_dim);
    +  }
    +  qh_settempfree(qh, &newfacets);
    +  trace1((qh, qh->ferr, 1028, "qh_createsimplex: created simplex\n"));
    +} /* createsimplex */
    +
    +/*---------------------------------
    +
    +  qh_delridge(qh, ridge )
    +    deletes ridge from data structures it belongs to
    +    frees up its memory
    +
    +  notes:
    +    in merge_r.c, caller sets vertex->delridge for each vertex
    +    ridges also freed in qh_freeqhull
    +*/
    +void qh_delridge(qhT *qh, ridgeT *ridge) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  qh_setdel(ridge->top->ridges, ridge);
    +  qh_setdel(ridge->bottom->ridges, ridge);
    +  qh_setfree(qh, &(ridge->vertices));
    +  qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
    +} /* delridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_delvertex(qh, vertex )
    +    deletes a vertex and frees its memory
    +
    +  notes:
    +    assumes vertex->adjacencies have been updated if needed
    +    unlinks from vertex_list
    +*/
    +void qh_delvertex(qhT *qh, vertexT *vertex) {
    +
    +  if (vertex == qh->tracevertex)
    +    qh->tracevertex= NULL;
    +  qh_removevertex(qh, vertex);
    +  qh_setfree(qh, &vertex->neighbors);
    +  qh_memfree(qh, vertex, (int)sizeof(vertexT));
    +} /* delvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_facet3vertex(qh, )
    +    return temporary set of 3-d vertices in qh_ORIENTclock order
    +
    +  design:
    +    if simplicial facet
    +      build set from facet->vertices with facet->toporient
    +    else
    +      for each ridge in order
    +        build set from ridge's vertices
    +*/
    +setT *qh_facet3vertex(qhT *qh, facetT *facet) {
    +  ridgeT *ridge, *firstridge;
    +  vertexT *vertex;
    +  int cntvertices, cntprojected=0;
    +  setT *vertices;
    +
    +  cntvertices= qh_setsize(qh, facet->vertices);
    +  vertices= qh_settemp(qh, cntvertices);
    +  if (facet->simplicial) {
    +    if (cntvertices != 3) {
    +      qh_fprintf(qh, qh->ferr, 6147, "qhull internal error (qh_facet3vertex): only %d vertices for simplicial facet f%d\n",
    +                  cntvertices, facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +    qh_setappend(qh, &vertices, SETfirst_(facet->vertices));
    +    if (facet->toporient ^ qh_ORIENTclock)
    +      qh_setappend(qh, &vertices, SETsecond_(facet->vertices));
    +    else
    +      qh_setaddnth(qh, &vertices, 0, SETsecond_(facet->vertices));
    +    qh_setappend(qh, &vertices, SETelem_(facet->vertices, 2));
    +  }else {
    +    ridge= firstridge= SETfirstt_(facet->ridges, ridgeT);   /* no infinite */
    +    while ((ridge= qh_nextridge3d(ridge, facet, &vertex))) {
    +      qh_setappend(qh, &vertices, vertex);
    +      if (++cntprojected > cntvertices || ridge == firstridge)
    +        break;
    +    }
    +    if (!ridge || cntprojected != cntvertices) {
    +      qh_fprintf(qh, qh->ferr, 6148, "qhull internal error (qh_facet3vertex): ridges for facet %d don't match up.  got at least %d\n",
    +                  facet->id, cntprojected);
    +      qh_errexit(qh, qh_ERRqhull, facet, ridge);
    +    }
    +  }
    +  return vertices;
    +} /* facet3vertex */
    +
    +/*---------------------------------
    +
    +  qh_findbestfacet(qh, point, bestoutside, bestdist, isoutside )
    +    find facet that is furthest below a point
    +
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    if bestoutside is set (e.g., qh_ALL)
    +      returns best facet that is not upperdelaunay
    +      if Delaunay and inside, point is outside circumsphere of bestfacet
    +    else
    +      returns first facet below point
    +      if point is inside, returns nearest, !upperdelaunay facet
    +    distance to facet
    +    isoutside set if outside of facet
    +
    +  notes:
    +    For tricoplanar facets, this finds one of the tricoplanar facets closest
    +    to the point.  For Delaunay triangulations, the point may be inside a
    +    different tricoplanar facet. See locate a facet with qh_findbestfacet()
    +
    +    If inside, qh_findbestfacet performs an exhaustive search
    +       this may be too conservative.  Sometimes it is clearly required.
    +
    +    qh_findbestfacet is not used by qhull.
    +    uses qh.visit_id and qh.coplanarset
    +
    +  see:
    +    qh_findbest
    +*/
    +facetT *qh_findbestfacet(qhT *qh, pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside) {
    +  facetT *bestfacet= NULL;
    +  int numpart, totpart= 0;
    +
    +  bestfacet= qh_findbest(qh, point, qh->facet_list,
    +                            bestoutside, !qh_ISnewfacets, bestoutside /* qh_NOupper */,
    +                            bestdist, isoutside, &totpart);
    +  if (*bestdist < -qh->DISTround) {
    +    bestfacet= qh_findfacet_all(qh, point, bestdist, isoutside, &numpart);
    +    totpart += numpart;
    +    if ((isoutside && *isoutside && bestoutside)
    +    || (isoutside && !*isoutside && bestfacet->upperdelaunay)) {
    +      bestfacet= qh_findbest(qh, point, bestfacet,
    +                            bestoutside, False, bestoutside,
    +                            bestdist, isoutside, &totpart);
    +      totpart += numpart;
    +    }
    +  }
    +  trace3((qh, qh->ferr, 3014, "qh_findbestfacet: f%d dist %2.2g isoutside %d totpart %d\n",
    +          bestfacet->id, *bestdist, (isoutside ? *isoutside : UINT_MAX), totpart));
    +  return bestfacet;
    +} /* findbestfacet */
    +
    +/*---------------------------------
    +
    +  qh_findbestlower(qh, facet, point, bestdist, numpart )
    +    returns best non-upper, non-flipped neighbor of facet for point
    +    if needed, searches vertex neighbors
    +
    +  returns:
    +    returns bestdist and updates numpart
    +
    +  notes:
    +    if Delaunay and inside, point is outside of circumsphere of bestfacet
    +    called by qh_findbest() for points above an upperdelaunay facet
    +
    +*/
    +facetT *qh_findbestlower(qhT *qh, facetT *upperfacet, pointT *point, realT *bestdistp, int *numpart) {
    +  facetT *neighbor, **neighborp, *bestfacet= NULL;
    +  realT bestdist= -REALmax/2 /* avoid underflow */;
    +  realT dist;
    +  vertexT *vertex;
    +  boolT isoutside= False;  /* not used */
    +
    +  zinc_(Zbestlower);
    +  FOREACHneighbor_(upperfacet) {
    +    if (neighbor->upperdelaunay || neighbor->flipped)
    +      continue;
    +    (*numpart)++;
    +    qh_distplane(qh, point, neighbor, &dist);
    +    if (dist > bestdist) {
    +      bestfacet= neighbor;
    +      bestdist= dist;
    +    }
    +  }
    +  if (!bestfacet) {
    +    zinc_(Zbestlowerv);
    +    /* rarely called, numpart does not count nearvertex computations */
    +    vertex= qh_nearvertex(qh, upperfacet, point, &dist);
    +    qh_vertexneighbors(qh);
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay || neighbor->flipped)
    +        continue;
    +      (*numpart)++;
    +      qh_distplane(qh, point, neighbor, &dist);
    +      if (dist > bestdist) {
    +        bestfacet= neighbor;
    +        bestdist= dist;
    +      }
    +    }
    +  }
    +  if (!bestfacet) {
    +    zinc_(Zbestlowerall);  /* invoked once per point in outsideset */
    +    zmax_(Zbestloweralln, qh->num_facets);
    +    /* [dec'15] Previously reported as QH6228 */
    +    trace3((qh, qh->ferr, 3025, "qh_findbestlower: all neighbors of facet %d are flipped or upper Delaunay.  Search all facets\n",
    +       upperfacet->id));
    +    /* rarely called */
    +    bestfacet= qh_findfacet_all(qh, point, &bestdist, &isoutside, numpart);
    +  }
    +  *bestdistp= bestdist;
    +  trace3((qh, qh->ferr, 3015, "qh_findbestlower: f%d dist %2.2g for f%d p%d\n",
    +          bestfacet->id, bestdist, upperfacet->id, qh_pointid(qh, point)));
    +  return bestfacet;
    +} /* findbestlower */
    +
    +/*---------------------------------
    +
    +  qh_findfacet_all(qh, point, bestdist, isoutside, numpart )
    +    exhaustive search for facet below a point
    +
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    returns first facet below point
    +    if point is inside,
    +      returns nearest facet
    +    distance to facet
    +    isoutside if point is outside of the hull
    +    number of distance tests
    +
    +  notes:
    +    primarily for library users, rarely used by Qhull
    +*/
    +facetT *qh_findfacet_all(qhT *qh, pointT *point, realT *bestdist, boolT *isoutside,
    +                          int *numpart) {
    +  facetT *bestfacet= NULL, *facet;
    +  realT dist;
    +  int totpart= 0;
    +
    +  *bestdist= -REALmax;
    +  *isoutside= False;
    +  FORALLfacets {
    +    if (facet->flipped || !facet->normal)
    +      continue;
    +    totpart++;
    +    qh_distplane(qh, point, facet, &dist);
    +    if (dist > *bestdist) {
    +      *bestdist= dist;
    +      bestfacet= facet;
    +      if (dist > qh->MINoutside) {
    +        *isoutside= True;
    +        break;
    +      }
    +    }
    +  }
    +  *numpart= totpart;
    +  trace3((qh, qh->ferr, 3016, "qh_findfacet_all: f%d dist %2.2g isoutside %d totpart %d\n",
    +          getid_(bestfacet), *bestdist, *isoutside, totpart));
    +  return bestfacet;
    +} /* findfacet_all */
    +
    +/*---------------------------------
    +
    +  qh_findgood(qh, facetlist, goodhorizon )
    +    identify good facets for qh.PRINTgood
    +    if qh.GOODvertex>0
    +      facet includes point as vertex
    +      if !match, returns goodhorizon
    +      inactive if qh.MERGING
    +    if qh.GOODpoint
    +      facet is visible or coplanar (>0) or not visible (<0)
    +    if qh.GOODthreshold
    +      facet->normal matches threshold
    +    if !goodhorizon and !match,
    +      selects facet with closest angle
    +      sets GOODclosest
    +
    +  returns:
    +    number of new, good facets found
    +    determines facet->good
    +    may update qh.GOODclosest
    +
    +  notes:
    +    qh_findgood_all further reduces the good region
    +
    +  design:
    +    count good facets
    +    mark good facets for qh.GOODpoint
    +    mark good facets for qh.GOODthreshold
    +    if necessary
    +      update qh.GOODclosest
    +*/
    +int qh_findgood(qhT *qh, facetT *facetlist, int goodhorizon) {
    +  facetT *facet, *bestfacet= NULL;
    +  realT angle, bestangle= REALmax, dist;
    +  int  numgood=0;
    +
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      numgood++;
    +  }
    +  if (qh->GOODvertex>0 && !qh->MERGING) {
    +    FORALLfacet_(facetlist) {
    +      if (!qh_isvertex(qh->GOODvertexp, facet->vertices)) {
    +        facet->good= False;
    +        numgood--;
    +      }
    +    }
    +  }
    +  if (qh->GOODpoint && numgood) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && facet->normal) {
    +        zinc_(Zdistgood);
    +        qh_distplane(qh, qh->GOODpointp, facet, &dist);
    +        if ((qh->GOODpoint > 0) ^ (dist > 0.0)) {
    +          facet->good= False;
    +          numgood--;
    +        }
    +      }
    +    }
    +  }
    +  if (qh->GOODthreshold && (numgood || goodhorizon || qh->GOODclosest)) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && facet->normal) {
    +        if (!qh_inthresholds(qh, facet->normal, &angle)) {
    +          facet->good= False;
    +          numgood--;
    +          if (angle < bestangle) {
    +            bestangle= angle;
    +            bestfacet= facet;
    +          }
    +        }
    +      }
    +    }
    +    if (!numgood && (!goodhorizon || qh->GOODclosest)) {
    +      if (qh->GOODclosest) {
    +        if (qh->GOODclosest->visible)
    +          qh->GOODclosest= NULL;
    +        else {
    +          qh_inthresholds(qh, qh->GOODclosest->normal, &angle);
    +          if (angle < bestangle)
    +            bestfacet= qh->GOODclosest;
    +        }
    +      }
    +      if (bestfacet && bestfacet != qh->GOODclosest) {
    +        if (qh->GOODclosest)
    +          qh->GOODclosest->good= False;
    +        qh->GOODclosest= bestfacet;
    +        bestfacet->good= True;
    +        numgood++;
    +        trace2((qh, qh->ferr, 2044, "qh_findgood: f%d is closest(%2.2g) to thresholds\n",
    +           bestfacet->id, bestangle));
    +        return numgood;
    +      }
    +    }else if (qh->GOODclosest) { /* numgood > 0 */
    +      qh->GOODclosest->good= False;
    +      qh->GOODclosest= NULL;
    +    }
    +  }
    +  zadd_(Zgoodfacet, numgood);
    +  trace2((qh, qh->ferr, 2045, "qh_findgood: found %d good facets with %d good horizon\n",
    +               numgood, goodhorizon));
    +  if (!numgood && qh->GOODvertex>0 && !qh->MERGING)
    +    return goodhorizon;
    +  return numgood;
    +} /* findgood */
    +
    +/*---------------------------------
    +
    +  qh_findgood_all(qh, facetlist )
    +    apply other constraints for good facets (used by qh.PRINTgood)
    +    if qh.GOODvertex
    +      facet includes (>0) or doesn't include (<0) point as vertex
    +      if last good facet and ONLYgood, prints warning and continues
    +    if qh.SPLITthresholds
    +      facet->normal matches threshold, or if none, the closest one
    +    calls qh_findgood
    +    nop if good not used
    +
    +  returns:
    +    clears facet->good if not good
    +    sets qh.num_good
    +
    +  notes:
    +    this is like qh_findgood but more restrictive
    +
    +  design:
    +    uses qh_findgood to mark good facets
    +    marks facets for qh.GOODvertex
    +    marks facets for qh.SPLITthreholds
    +*/
    +void qh_findgood_all(qhT *qh, facetT *facetlist) {
    +  facetT *facet, *bestfacet=NULL;
    +  realT angle, bestangle= REALmax;
    +  int  numgood=0, startgood;
    +
    +  if (!qh->GOODvertex && !qh->GOODthreshold && !qh->GOODpoint
    +  && !qh->SPLITthresholds)
    +    return;
    +  if (!qh->ONLYgood)
    +    qh_findgood(qh, qh->facet_list, 0);
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      numgood++;
    +  }
    +  if (qh->GOODvertex <0 || (qh->GOODvertex > 0 && qh->MERGING)) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && ((qh->GOODvertex > 0) ^ !!qh_isvertex(qh->GOODvertexp, facet->vertices))) {
    +        if (!--numgood) {
    +          if (qh->ONLYgood) {
    +            qh_fprintf(qh, qh->ferr, 7064, "qhull warning: good vertex p%d does not match last good facet f%d.  Ignored.\n",
    +               qh_pointid(qh, qh->GOODvertexp), facet->id);
    +            return;
    +          }else if (qh->GOODvertex > 0)
    +            qh_fprintf(qh, qh->ferr, 7065, "qhull warning: point p%d is not a vertex('QV%d').\n",
    +                qh->GOODvertex-1, qh->GOODvertex-1);
    +          else
    +            qh_fprintf(qh, qh->ferr, 7066, "qhull warning: point p%d is a vertex for every facet('QV-%d').\n",
    +                -qh->GOODvertex - 1, -qh->GOODvertex - 1);
    +        }
    +        facet->good= False;
    +      }
    +    }
    +  }
    +  startgood= numgood;
    +  if (qh->SPLITthresholds) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good) {
    +        if (!qh_inthresholds(qh, facet->normal, &angle)) {
    +          facet->good= False;
    +          numgood--;
    +          if (angle < bestangle) {
    +            bestangle= angle;
    +            bestfacet= facet;
    +          }
    +        }
    +      }
    +    }
    +    if (!numgood && bestfacet) {
    +      bestfacet->good= True;
    +      numgood++;
    +      trace0((qh, qh->ferr, 23, "qh_findgood_all: f%d is closest(%2.2g) to thresholds\n",
    +           bestfacet->id, bestangle));
    +      return;
    +    }
    +  }
    +  qh->num_good= numgood;
    +  trace0((qh, qh->ferr, 24, "qh_findgood_all: %d good facets remain out of %d facets\n",
    +        numgood, startgood));
    +} /* findgood_all */
    +
    +/*---------------------------------
    +
    +  qh_furthestnext()
    +    set qh.facet_next to facet with furthest of all furthest points
    +    searches all facets on qh.facet_list
    +
    +  notes:
    +    this may help avoid precision problems
    +*/
    +void qh_furthestnext(qhT *qh /* qh->facet_list */) {
    +  facetT *facet, *bestfacet= NULL;
    +  realT dist, bestdist= -REALmax;
    +
    +  FORALLfacets {
    +    if (facet->outsideset) {
    +#if qh_COMPUTEfurthest
    +      pointT *furthest;
    +      furthest= (pointT*)qh_setlast(facet->outsideset);
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(qh, furthest, facet, &dist);
    +#else
    +      dist= facet->furthestdist;
    +#endif
    +      if (dist > bestdist) {
    +        bestfacet= facet;
    +        bestdist= dist;
    +      }
    +    }
    +  }
    +  if (bestfacet) {
    +    qh_removefacet(qh, bestfacet);
    +    qh_prependfacet(qh, bestfacet, &qh->facet_next);
    +    trace1((qh, qh->ferr, 1029, "qh_furthestnext: made f%d next facet(dist %.2g)\n",
    +            bestfacet->id, bestdist));
    +  }
    +} /* furthestnext */
    +
    +/*---------------------------------
    +
    +  qh_furthestout(qh, facet )
    +    make furthest outside point the last point of outsideset
    +
    +  returns:
    +    updates facet->outsideset
    +    clears facet->notfurthest
    +    sets facet->furthestdist
    +
    +  design:
    +    determine best point of outsideset
    +    make it the last point of outsideset
    +*/
    +void qh_furthestout(qhT *qh, facetT *facet) {
    +  pointT *point, **pointp, *bestpoint= NULL;
    +  realT dist, bestdist= -REALmax;
    +
    +  FOREACHpoint_(facet->outsideset) {
    +    qh_distplane(qh, point, facet, &dist);
    +    zinc_(Zcomputefurthest);
    +    if (dist > bestdist) {
    +      bestpoint= point;
    +      bestdist= dist;
    +    }
    +  }
    +  if (bestpoint) {
    +    qh_setdel(facet->outsideset, point);
    +    qh_setappend(qh, &facet->outsideset, point);
    +#if !qh_COMPUTEfurthest
    +    facet->furthestdist= bestdist;
    +#endif
    +  }
    +  facet->notfurthest= False;
    +  trace3((qh, qh->ferr, 3017, "qh_furthestout: p%d is furthest outside point of f%d\n",
    +          qh_pointid(qh, point), facet->id));
    +} /* furthestout */
    +
    +
    +/*---------------------------------
    +
    +  qh_infiniteloop(qh, facet )
    +    report infinite loop error due to facet
    +*/
    +void qh_infiniteloop(qhT *qh, facetT *facet) {
    +
    +  qh_fprintf(qh, qh->ferr, 6149, "qhull internal error (qh_infiniteloop): potential infinite loop detected\n");
    +  qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +} /* qh_infiniteloop */
    +
    +/*---------------------------------
    +
    +  qh_initbuild()
    +    initialize hull and outside sets with point array
    +    qh.FIRSTpoint/qh.NUMpoints is point array
    +    if qh.GOODpoint
    +      adds qh.GOODpoint to initial hull
    +
    +  returns:
    +    qh_facetlist with initial hull
    +    points partioned into outside sets, coplanar sets, or inside
    +    initializes qh.GOODpointp, qh.GOODvertexp,
    +
    +  design:
    +    initialize global variables used during qh_buildhull
    +    determine precision constants and points with max/min coordinate values
    +      if qh.SCALElast, scale last coordinate(for 'd')
    +    build initial simplex
    +    partition input points into facets of initial simplex
    +    set up lists
    +    if qh.ONLYgood
    +      check consistency
    +      add qh.GOODvertex if defined
    +*/
    +void qh_initbuild(qhT *qh) {
    +  setT *maxpoints, *vertices;
    +  facetT *facet;
    +  int i, numpart;
    +  realT dist;
    +  boolT isoutside;
    +
    +  qh->furthest_id= qh_IDunknown;
    +  qh->lastreport= 0;
    +  qh->facet_id= qh->vertex_id= qh->ridge_id= 0;
    +  qh->visit_id= qh->vertex_visit= 0;
    +  qh->maxoutdone= False;
    +
    +  if (qh->GOODpoint > 0)
    +    qh->GOODpointp= qh_point(qh, qh->GOODpoint-1);
    +  else if (qh->GOODpoint < 0)
    +    qh->GOODpointp= qh_point(qh, -qh->GOODpoint-1);
    +  if (qh->GOODvertex > 0)
    +    qh->GOODvertexp= qh_point(qh, qh->GOODvertex-1);
    +  else if (qh->GOODvertex < 0)
    +    qh->GOODvertexp= qh_point(qh, -qh->GOODvertex-1);
    +  if ((qh->GOODpoint
    +       && (qh->GOODpointp < qh->first_point  /* also catches !GOODpointp */
    +           || qh->GOODpointp > qh_point(qh, qh->num_points-1)))
    +    || (qh->GOODvertex
    +        && (qh->GOODvertexp < qh->first_point  /* also catches !GOODvertexp */
    +            || qh->GOODvertexp > qh_point(qh, qh->num_points-1)))) {
    +    qh_fprintf(qh, qh->ferr, 6150, "qhull input error: either QGn or QVn point is > p%d\n",
    +             qh->num_points-1);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  maxpoints= qh_maxmin(qh, qh->first_point, qh->num_points, qh->hull_dim);
    +  if (qh->SCALElast)
    +    qh_scalelast(qh, qh->first_point, qh->num_points, qh->hull_dim,
    +               qh->MINlastcoord, qh->MAXlastcoord, qh->MAXwidth);
    +  qh_detroundoff(qh);
    +  if (qh->DELAUNAY && qh->upper_threshold[qh->hull_dim-1] > REALmax/2
    +                  && qh->lower_threshold[qh->hull_dim-1] < -REALmax/2) {
    +    for (i=qh_PRINTEND; i--; ) {
    +      if (qh->PRINTout[i] == qh_PRINTgeom && qh->DROPdim < 0
    +          && !qh->GOODthreshold && !qh->SPLITthresholds)
    +        break;  /* in this case, don't set upper_threshold */
    +    }
    +    if (i < 0) {
    +      if (qh->UPPERdelaunay) { /* matches qh.upperdelaunay in qh_setfacetplane */
    +        qh->lower_threshold[qh->hull_dim-1]= qh->ANGLEround * qh_ZEROdelaunay;
    +        qh->GOODthreshold= True;
    +      }else {
    +        qh->upper_threshold[qh->hull_dim-1]= -qh->ANGLEround * qh_ZEROdelaunay;
    +        if (!qh->GOODthreshold)
    +          qh->SPLITthresholds= True; /* build upper-convex hull even if Qg */
    +          /* qh_initqhull_globals errors if Qg without Pdk/etc. */
    +      }
    +    }
    +  }
    +  vertices= qh_initialvertices(qh, qh->hull_dim, maxpoints, qh->first_point, qh->num_points);
    +  qh_initialhull(qh, vertices);  /* initial qh->facet_list */
    +  qh_partitionall(qh, vertices, qh->first_point, qh->num_points);
    +  if (qh->PRINToptions1st || qh->TRACElevel || qh->IStracing) {
    +    if (qh->TRACElevel || qh->IStracing)
    +      qh_fprintf(qh, qh->ferr, 8103, "\nTrace level %d for %s | %s\n",
    +         qh->IStracing ? qh->IStracing : qh->TRACElevel, qh->rbox_command, qh->qhull_command);
    +    qh_fprintf(qh, qh->ferr, 8104, "Options selected for Qhull %s:\n%s\n", qh_version, qh->qhull_options);
    +  }
    +  qh_resetlists(qh, False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +  qh->facet_next= qh->facet_list;
    +  qh_furthestnext(qh /* qh->facet_list */);
    +  if (qh->PREmerge) {
    +    qh->cos_max= qh->premerge_cos;
    +    qh->centrum_radius= qh->premerge_centrum;
    +  }
    +  if (qh->ONLYgood) {
    +    if (qh->GOODvertex > 0 && qh->MERGING) {
    +      qh_fprintf(qh, qh->ferr, 6151, "qhull input error: 'Qg QVn' (only good vertex) does not work with merging.\nUse 'QJ' to joggle the input or 'Q0' to turn off merging.\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (!(qh->GOODthreshold || qh->GOODpoint
    +         || (!qh->MERGEexact && !qh->PREmerge && qh->GOODvertexp))) {
    +      qh_fprintf(qh, qh->ferr, 6152, "qhull input error: 'Qg' (ONLYgood) needs a good threshold('Pd0D0'), a\n\
    +good point(QGn or QG-n), or a good vertex with 'QJ' or 'Q0' (QVn).\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh->GOODvertex > 0  && !qh->MERGING  /* matches qh_partitionall */
    +        && !qh_isvertex(qh->GOODvertexp, vertices)) {
    +      facet= qh_findbestnew(qh, qh->GOODvertexp, qh->facet_list,
    +                          &dist, !qh_ALL, &isoutside, &numpart);
    +      zadd_(Zdistgood, numpart);
    +      if (!isoutside) {
    +        qh_fprintf(qh, qh->ferr, 6153, "qhull input error: point for QV%d is inside initial simplex.  It can not be made a vertex.\n",
    +               qh_pointid(qh, qh->GOODvertexp));
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +      if (!qh_addpoint(qh, qh->GOODvertexp, facet, False)) {
    +        qh_settempfree(qh, &vertices);
    +        qh_settempfree(qh, &maxpoints);
    +        return;
    +      }
    +    }
    +    qh_findgood(qh, qh->facet_list, 0);
    +  }
    +  qh_settempfree(qh, &vertices);
    +  qh_settempfree(qh, &maxpoints);
    +  trace1((qh, qh->ferr, 1030, "qh_initbuild: initial hull created and points partitioned\n"));
    +} /* initbuild */
    +
    +/*---------------------------------
    +
    +  qh_initialhull(qh, vertices )
    +    constructs the initial hull as a DIM3 simplex of vertices
    +
    +  design:
    +    creates a simplex (initializes lists)
    +    determines orientation of simplex
    +    sets hyperplanes for facets
    +    doubles checks orientation (in case of axis-parallel facets with Gaussian elimination)
    +    checks for flipped facets and qh.NARROWhull
    +    checks the result
    +*/
    +void qh_initialhull(qhT *qh, setT *vertices) {
    +  facetT *facet, *firstfacet, *neighbor, **neighborp;
    +  realT dist, angle, minangle= REALmax;
    +#ifndef qh_NOtrace
    +  int k;
    +#endif
    +
    +  qh_createsimplex(qh, vertices);  /* qh->facet_list */
    +  qh_resetlists(qh, False, qh_RESETvisible);
    +  qh->facet_next= qh->facet_list;      /* advance facet when processed */
    +  qh->interior_point= qh_getcenter(qh, vertices);
    +  firstfacet= qh->facet_list;
    +  qh_setfacetplane(qh, firstfacet);
    +  zinc_(Znumvisibility); /* needs to be in printsummary */
    +  qh_distplane(qh, qh->interior_point, firstfacet, &dist);
    +  if (dist > 0) {
    +    FORALLfacets
    +      facet->toporient ^= (unsigned char)True;
    +  }
    +  FORALLfacets
    +    qh_setfacetplane(qh, facet);
    +  FORALLfacets {
    +    if (!qh_checkflipped(qh, facet, NULL, qh_ALL)) {/* due to axis-parallel facet */
    +      trace1((qh, qh->ferr, 1031, "qh_initialhull: initial orientation incorrect.  Correct all facets\n"));
    +      facet->flipped= False;
    +      FORALLfacets {
    +        facet->toporient ^= (unsigned char)True;
    +        qh_orientoutside(qh, facet);
    +      }
    +      break;
    +    }
    +  }
    +  FORALLfacets {
    +    if (!qh_checkflipped(qh, facet, NULL, !qh_ALL)) {  /* can happen with 'R0.1' */
    +      if (qh->DELAUNAY && ! qh->ATinfinity) {
    +        if (qh->UPPERdelaunay)
    +          qh_fprintf(qh, qh->ferr, 6240, "Qhull precision error: Initial simplex is cocircular or cospherical.  Option 'Qs' searches all points.  Can not compute the upper Delaunay triangulation or upper Voronoi diagram of cocircular/cospherical points.\n");
    +        else
    +          qh_fprintf(qh, qh->ferr, 6239, "Qhull precision error: Initial simplex is cocircular or cospherical.  Use option 'Qz' for the Delaunay triangulation or Voronoi diagram of cocircular/cospherical points.  Option 'Qz' adds a point \"at infinity\".  Use option 'Qs' to search all points for the initial simplex.\n");
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +      qh_precision(qh, "initial simplex is flat");
    +      qh_fprintf(qh, qh->ferr, 6154, "Qhull precision error: Initial simplex is flat (facet %d is coplanar with the interior point)\n",
    +                   facet->id);
    +      qh_errexit(qh, qh_ERRsingular, NULL, NULL);  /* calls qh_printhelp_singular */
    +    }
    +    FOREACHneighbor_(facet) {
    +      angle= qh_getangle(qh, facet->normal, neighbor->normal);
    +      minimize_( minangle, angle);
    +    }
    +  }
    +  if (minangle < qh_MAXnarrow && !qh->NOnarrow) {
    +    realT diff= 1.0 + minangle;
    +
    +    qh->NARROWhull= True;
    +    qh_option(qh, "_narrow-hull", NULL, &diff);
    +    if (minangle < qh_WARNnarrow && !qh->RERUN && qh->PRINTprecision)
    +      qh_printhelp_narrowhull(qh, qh->ferr, minangle);
    +  }
    +  zzval_(Zprocessed)= qh->hull_dim+1;
    +  qh_checkpolygon(qh, qh->facet_list);
    +  qh_checkconvex(qh, qh->facet_list,   qh_DATAfault);
    +#ifndef qh_NOtrace
    +  if (qh->IStracing >= 1) {
    +    qh_fprintf(qh, qh->ferr, 8105, "qh_initialhull: simplex constructed, interior point:");
    +    for (k=0; k < qh->hull_dim; k++)
    +      qh_fprintf(qh, qh->ferr, 8106, " %6.4g", qh->interior_point[k]);
    +    qh_fprintf(qh, qh->ferr, 8107, "\n");
    +  }
    +#endif
    +} /* initialhull */
    +
    +/*---------------------------------
    +
    +  qh_initialvertices(qh, dim, maxpoints, points, numpoints )
    +    determines a non-singular set of initial vertices
    +    maxpoints may include duplicate points
    +
    +  returns:
    +    temporary set of dim+1 vertices in descending order by vertex id
    +    if qh.RANDOMoutside && !qh.ALLpoints
    +      picks random points
    +    if dim >= qh_INITIALmax,
    +      uses min/max x and max points with non-zero determinants
    +
    +  notes:
    +    unless qh.ALLpoints,
    +      uses maxpoints as long as determinate is non-zero
    +*/
    +setT *qh_initialvertices(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints) {
    +  pointT *point, **pointp;
    +  setT *vertices, *simplex, *tested;
    +  realT randr;
    +  int idx, point_i, point_n, k;
    +  boolT nearzero= False;
    +
    +  vertices= qh_settemp(qh, dim + 1);
    +  simplex= qh_settemp(qh, dim+1);
    +  if (qh->ALLpoints)
    +    qh_maxsimplex(qh, dim, NULL, points, numpoints, &simplex);
    +  else if (qh->RANDOMoutside) {
    +    while (qh_setsize(qh, simplex) != dim+1) {
    +      randr= qh_RANDOMint;
    +      randr= randr/(qh_RANDOMmax+1);
    +      idx= (int)floor(qh->num_points * randr);
    +      while (qh_setin(simplex, qh_point(qh, idx))) {
    +            idx++; /* in case qh_RANDOMint always returns the same value */
    +        idx= idx < qh->num_points ? idx : 0;
    +      }
    +      qh_setappend(qh, &simplex, qh_point(qh, idx));
    +    }
    +  }else if (qh->hull_dim >= qh_INITIALmax) {
    +    tested= qh_settemp(qh, dim+1);
    +    qh_setappend(qh, &simplex, SETfirst_(maxpoints));   /* max and min X coord */
    +    qh_setappend(qh, &simplex, SETsecond_(maxpoints));
    +    qh_maxsimplex(qh, fmin_(qh_INITIALsearch, dim), maxpoints, points, numpoints, &simplex);
    +    k= qh_setsize(qh, simplex);
    +    FOREACHpoint_i_(qh, maxpoints) {
    +      if (point_i & 0x1) {     /* first pick up max. coord. points */
    +        if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +          qh_detsimplex(qh, point, simplex, k, &nearzero);
    +          if (nearzero)
    +            qh_setappend(qh, &tested, point);
    +          else {
    +            qh_setappend(qh, &simplex, point);
    +            if (++k == dim)  /* use search for last point */
    +              break;
    +          }
    +        }
    +      }
    +    }
    +    while (k != dim && (point= (pointT*)qh_setdellast(maxpoints))) {
    +      if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +        qh_detsimplex(qh, point, simplex, k, &nearzero);
    +        if (nearzero)
    +          qh_setappend(qh, &tested, point);
    +        else {
    +          qh_setappend(qh, &simplex, point);
    +          k++;
    +        }
    +      }
    +    }
    +    idx= 0;
    +    while (k != dim && (point= qh_point(qh, idx++))) {
    +      if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +        qh_detsimplex(qh, point, simplex, k, &nearzero);
    +        if (!nearzero){
    +          qh_setappend(qh, &simplex, point);
    +          k++;
    +        }
    +      }
    +    }
    +    qh_settempfree(qh, &tested);
    +    qh_maxsimplex(qh, dim, maxpoints, points, numpoints, &simplex);
    +  }else
    +    qh_maxsimplex(qh, dim, maxpoints, points, numpoints, &simplex);
    +  FOREACHpoint_(simplex)
    +    qh_setaddnth(qh, &vertices, 0, qh_newvertex(qh, point)); /* descending order */
    +  qh_settempfree(qh, &simplex);
    +  return vertices;
    +} /* initialvertices */
    +
    +
    +/*---------------------------------
    +
    +  qh_isvertex( point, vertices )
    +    returns vertex if point is in vertex set, else returns NULL
    +
    +  notes:
    +    for qh.GOODvertex
    +*/
    +vertexT *qh_isvertex(pointT *point, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (vertex->point == point)
    +      return vertex;
    +  }
    +  return NULL;
    +} /* isvertex */
    +
    +/*---------------------------------
    +
    +  qh_makenewfacets(qh, point )
    +    make new facets from point and qh.visible_list
    +
    +  returns:
    +    qh.newfacet_list= list of new facets with hyperplanes and ->newfacet
    +    qh.newvertex_list= list of vertices in new facets with ->newlist set
    +
    +    if (qh.ONLYgood)
    +      newfacets reference horizon facets, but not vice versa
    +      ridges reference non-simplicial horizon ridges, but not vice versa
    +      does not change existing facets
    +    else
    +      sets qh.NEWfacets
    +      new facets attached to horizon facets and ridges
    +      for visible facets,
    +        visible->r.replace is corresponding new facet
    +
    +  see also:
    +    qh_makenewplanes() -- make hyperplanes for facets
    +    qh_attachnewfacets() -- attachnewfacets if not done here(qh->ONLYgood)
    +    qh_matchnewfacets() -- match up neighbors
    +    qh_updatevertices() -- update vertex neighbors and delvertices
    +    qh_deletevisible() -- delete visible facets
    +    qh_checkpolygon() --check the result
    +    qh_triangulate() -- triangulate a non-simplicial facet
    +
    +  design:
    +    for each visible facet
    +      make new facets to its horizon facets
    +      update its f.replace
    +      clear its neighbor set
    +*/
    +vertexT *qh_makenewfacets(qhT *qh, pointT *point /*visible_list*/) {
    +  facetT *visible, *newfacet= NULL, *newfacet2= NULL, *neighbor, **neighborp;
    +  vertexT *apex;
    +  int numnew=0;
    +
    +  qh->newfacet_list= qh->facet_tail;
    +  qh->newvertex_list= qh->vertex_tail;
    +  apex= qh_newvertex(qh, point);
    +  qh_appendvertex(qh, apex);
    +  qh->visit_id++;
    +  if (!qh->ONLYgood)
    +    qh->NEWfacets= True;
    +  FORALLvisible_facets {
    +    FOREACHneighbor_(visible)
    +      neighbor->seen= False;
    +    if (visible->ridges) {
    +      visible->visitid= qh->visit_id;
    +      newfacet2= qh_makenew_nonsimplicial(qh, visible, apex, &numnew);
    +    }
    +    if (visible->simplicial)
    +      newfacet= qh_makenew_simplicial(qh, visible, apex, &numnew);
    +    if (!qh->ONLYgood) {
    +      if (newfacet2)  /* newfacet is null if all ridges defined */
    +        newfacet= newfacet2;
    +      if (newfacet)
    +        visible->f.replace= newfacet;
    +      else
    +        zinc_(Zinsidevisible);
    +      SETfirst_(visible->neighbors)= NULL;
    +    }
    +  }
    +  trace1((qh, qh->ferr, 1032, "qh_makenewfacets: created %d new facets from point p%d to horizon\n",
    +          numnew, qh_pointid(qh, point)));
    +  if (qh->IStracing >= 4)
    +    qh_printfacetlist(qh, qh->newfacet_list, NULL, qh_ALL);
    +  return apex;
    +} /* makenewfacets */
    +
    +/*---------------------------------
    +
    +  qh_matchduplicates(qh, atfacet, atskip, hashsize, hashcount )
    +    match duplicate ridges in qh.hash_table for atfacet/atskip
    +    duplicates marked with ->dupridge and qh_DUPLICATEridge
    +
    +  returns:
    +    picks match with worst merge (min distance apart)
    +    updates hashcount
    +
    +  see also:
    +    qh_matchneighbor
    +
    +  notes:
    +
    +  design:
    +    compute hash value for atfacet and atskip
    +    repeat twice -- once to make best matches, once to match the rest
    +      for each possible facet in qh.hash_table
    +        if it is a matching facet and pass 2
    +          make match
    +          unless tricoplanar, mark match for merging (qh_MERGEridge)
    +          [e.g., tricoplanar RBOX s 1000 t993602376 | QHULL C-1e-3 d Qbb FA Qt]
    +        if it is a matching facet and pass 1
    +          test if this is a better match
    +      if pass 1,
    +        make best match (it will not be merged)
    +*/
    +#ifndef qh_NOmerge
    +void qh_matchduplicates(qhT *qh, facetT *atfacet, int atskip, int hashsize, int *hashcount) {
    +  boolT same, ismatch;
    +  int hash, scan;
    +  facetT *facet, *newfacet, *maxmatch= NULL, *maxmatch2= NULL, *nextfacet;
    +  int skip, newskip, nextskip= 0, maxskip= 0, maxskip2= 0, makematch;
    +  realT maxdist= -REALmax, mindist, dist2, low, high;
    +
    +  hash= qh_gethash(qh, hashsize, atfacet->vertices, qh->hull_dim, 1,
    +                     SETelem_(atfacet->vertices, atskip));
    +  trace2((qh, qh->ferr, 2046, "qh_matchduplicates: find duplicate matches for f%d skip %d hash %d hashcount %d\n",
    +          atfacet->id, atskip, hash, *hashcount));
    +  for (makematch= 0; makematch < 2; makematch++) {
    +    qh->visit_id++;
    +    for (newfacet= atfacet, newskip= atskip; newfacet; newfacet= nextfacet, newskip= nextskip) {
    +      zinc_(Zhashlookup);
    +      nextfacet= NULL;
    +      newfacet->visitid= qh->visit_id;
    +      for (scan= hash; (facet= SETelemt_(qh->hash_table, scan, facetT));
    +           scan= (++scan >= hashsize ? 0 : scan)) {
    +        if (!facet->dupridge || facet->visitid == qh->visit_id)
    +          continue;
    +        zinc_(Zhashtests);
    +        if (qh_matchvertices(qh, 1, newfacet->vertices, newskip, facet->vertices, &skip, &same)) {
    +          ismatch= (same == (boolT)(newfacet->toporient ^ facet->toporient));
    +          if (SETelemt_(facet->neighbors, skip, facetT) != qh_DUPLICATEridge) {
    +            if (!makematch) {
    +              qh_fprintf(qh, qh->ferr, 6155, "qhull internal error (qh_matchduplicates): missing dupridge at f%d skip %d for new f%d skip %d hash %d\n",
    +                     facet->id, skip, newfacet->id, newskip, hash);
    +              qh_errexit2(qh, qh_ERRqhull, facet, newfacet);
    +            }
    +          }else if (ismatch && makematch) {
    +            if (SETelemt_(newfacet->neighbors, newskip, facetT) == qh_DUPLICATEridge) {
    +              SETelem_(facet->neighbors, skip)= newfacet;
    +              if (newfacet->tricoplanar)
    +                SETelem_(newfacet->neighbors, newskip)= facet;
    +              else
    +                SETelem_(newfacet->neighbors, newskip)= qh_MERGEridge;
    +              *hashcount -= 2; /* removed two unmatched facets */
    +              trace4((qh, qh->ferr, 4059, "qh_matchduplicates: duplicate f%d skip %d matched with new f%d skip %d merge\n",
    +                    facet->id, skip, newfacet->id, newskip));
    +            }
    +          }else if (ismatch) {
    +            mindist= qh_getdistance(qh, facet, newfacet, &low, &high);
    +            dist2= qh_getdistance(qh, newfacet, facet, &low, &high);
    +            minimize_(mindist, dist2);
    +            if (mindist > maxdist) {
    +              maxdist= mindist;
    +              maxmatch= facet;
    +              maxskip= skip;
    +              maxmatch2= newfacet;
    +              maxskip2= newskip;
    +            }
    +            trace3((qh, qh->ferr, 3018, "qh_matchduplicates: duplicate f%d skip %d new f%d skip %d at dist %2.2g, max is now f%d f%d\n",
    +                    facet->id, skip, newfacet->id, newskip, mindist,
    +                    maxmatch->id, maxmatch2->id));
    +          }else { /* !ismatch */
    +            nextfacet= facet;
    +            nextskip= skip;
    +          }
    +        }
    +        if (makematch && !facet
    +        && SETelemt_(facet->neighbors, skip, facetT) == qh_DUPLICATEridge) {
    +          qh_fprintf(qh, qh->ferr, 6156, "qhull internal error (qh_matchduplicates): no MERGEridge match for duplicate f%d skip %d at hash %d\n",
    +                     newfacet->id, newskip, hash);
    +          qh_errexit(qh, qh_ERRqhull, newfacet, NULL);
    +        }
    +      }
    +    } /* end of for each new facet at hash */
    +    if (!makematch) {
    +      if (!maxmatch) {
    +        qh_fprintf(qh, qh->ferr, 6157, "qhull internal error (qh_matchduplicates): no maximum match at duplicate f%d skip %d at hash %d\n",
    +                     atfacet->id, atskip, hash);
    +        qh_errexit(qh, qh_ERRqhull, atfacet, NULL);
    +      }
    +      SETelem_(maxmatch->neighbors, maxskip)= maxmatch2; /* maxmatch!=0 by QH6157 */
    +      SETelem_(maxmatch2->neighbors, maxskip2)= maxmatch;
    +      *hashcount -= 2; /* removed two unmatched facets */
    +      zzinc_(Zmultiridge);
    +      trace0((qh, qh->ferr, 25, "qh_matchduplicates: duplicate f%d skip %d matched with new f%d skip %d keep\n",
    +              maxmatch->id, maxskip, maxmatch2->id, maxskip2));
    +      qh_precision(qh, "ridge with multiple neighbors");
    +      if (qh->IStracing >= 4)
    +        qh_errprint(qh, "DUPLICATED/MATCH", maxmatch, maxmatch2, NULL, NULL);
    +    }
    +  }
    +} /* matchduplicates */
    +
    +/*---------------------------------
    +
    +  qh_nearcoplanar()
    +    for all facets, remove near-inside points from facet->coplanarset
    +    coplanar points defined by innerplane from qh_outerinner()
    +
    +  returns:
    +    if qh->KEEPcoplanar && !qh->KEEPinside
    +      facet->coplanarset only contains coplanar points
    +    if qh.JOGGLEmax
    +      drops inner plane by another qh.JOGGLEmax diagonal since a
    +        vertex could shift out while a coplanar point shifts in
    +
    +  notes:
    +    used for qh.PREmerge and qh.JOGGLEmax
    +    must agree with computation of qh.NEARcoplanar in qh_detroundoff(qh)
    +  design:
    +    if not keeping coplanar or inside points
    +      free all coplanar sets
    +    else if not keeping both coplanar and inside points
    +      remove !coplanar or !inside points from coplanar sets
    +*/
    +void qh_nearcoplanar(qhT *qh /* qh.facet_list */) {
    +  facetT *facet;
    +  pointT *point, **pointp;
    +  int numpart;
    +  realT dist, innerplane;
    +
    +  if (!qh->KEEPcoplanar && !qh->KEEPinside) {
    +    FORALLfacets {
    +      if (facet->coplanarset)
    +        qh_setfree(qh, &facet->coplanarset);
    +    }
    +  }else if (!qh->KEEPcoplanar || !qh->KEEPinside) {
    +    qh_outerinner(qh, NULL, NULL, &innerplane);
    +    if (qh->JOGGLEmax < REALmax/2)
    +      innerplane -= qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
    +    numpart= 0;
    +    FORALLfacets {
    +      if (facet->coplanarset) {
    +        FOREACHpoint_(facet->coplanarset) {
    +          numpart++;
    +          qh_distplane(qh, point, facet, &dist);
    +          if (dist < innerplane) {
    +            if (!qh->KEEPinside)
    +              SETref_(point)= NULL;
    +          }else if (!qh->KEEPcoplanar)
    +            SETref_(point)= NULL;
    +        }
    +        qh_setcompact(qh, facet->coplanarset);
    +      }
    +    }
    +    zzadd_(Zcheckpart, numpart);
    +  }
    +} /* nearcoplanar */
    +
    +/*---------------------------------
    +
    +  qh_nearvertex(qh, facet, point, bestdist )
    +    return nearest vertex in facet to point
    +
    +  returns:
    +    vertex and its distance
    +
    +  notes:
    +    if qh.DELAUNAY
    +      distance is measured in the input set
    +    searches neighboring tricoplanar facets (requires vertexneighbors)
    +      Slow implementation.  Recomputes vertex set for each point.
    +    The vertex set could be stored in the qh.keepcentrum facet.
    +*/
    +vertexT *qh_nearvertex(qhT *qh, facetT *facet, pointT *point, realT *bestdistp) {
    +  realT bestdist= REALmax, dist;
    +  vertexT *bestvertex= NULL, *vertex, **vertexp, *apex;
    +  coordT *center;
    +  facetT *neighbor, **neighborp;
    +  setT *vertices;
    +  int dim= qh->hull_dim;
    +
    +  if (qh->DELAUNAY)
    +    dim--;
    +  if (facet->tricoplanar) {
    +    if (!qh->VERTEXneighbors || !facet->center) {
    +      qh_fprintf(qh, qh->ferr, 6158, "qhull internal error (qh_nearvertex): qh.VERTEXneighbors and facet->center required for tricoplanar facets\n");
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +    vertices= qh_settemp(qh, qh->TEMPsize);
    +    apex= SETfirstt_(facet->vertices, vertexT);
    +    center= facet->center;
    +    FOREACHneighbor_(apex) {
    +      if (neighbor->center == center) {
    +        FOREACHvertex_(neighbor->vertices)
    +          qh_setappend(qh, &vertices, vertex);
    +      }
    +    }
    +  }else
    +    vertices= facet->vertices;
    +  FOREACHvertex_(vertices) {
    +    dist= qh_pointdist(vertex->point, point, -dim);
    +    if (dist < bestdist) {
    +      bestdist= dist;
    +      bestvertex= vertex;
    +    }
    +  }
    +  if (facet->tricoplanar)
    +    qh_settempfree(qh, &vertices);
    +  *bestdistp= sqrt(bestdist);
    +  if (!bestvertex) {
    +      qh_fprintf(qh, qh->ferr, 6261, "qhull internal error (qh_nearvertex): did not find bestvertex for f%d p%d\n", facet->id, qh_pointid(qh, point));
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  trace3((qh, qh->ferr, 3019, "qh_nearvertex: v%d dist %2.2g for f%d p%d\n",
    +        bestvertex->id, *bestdistp, facet->id, qh_pointid(qh, point))); /* bestvertex!=0 by QH2161 */
    +  return bestvertex;
    +} /* nearvertex */
    +
    +/*---------------------------------
    +
    +  qh_newhashtable(qh, newsize )
    +    returns size of qh.hash_table of at least newsize slots
    +
    +  notes:
    +    assumes qh.hash_table is NULL
    +    qh_HASHfactor determines the number of extra slots
    +    size is not divisible by 2, 3, or 5
    +*/
    +int qh_newhashtable(qhT *qh, int newsize) {
    +  int size;
    +
    +  size= ((newsize+1)*qh_HASHfactor) | 0x1;  /* odd number */
    +  while (True) {
    +    if (newsize<0 || size<0) {
    +        qh_fprintf(qh, qh->qhmem.ferr, 6236, "qhull error (qh_newhashtable): negative request (%d) or size (%d).  Did int overflow due to high-D?\n", newsize, size); /* WARN64 */
    +        qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +    }
    +    if ((size%3) && (size%5))
    +      break;
    +    size += 2;
    +    /* loop terminates because there is an infinite number of primes */
    +  }
    +  qh->hash_table= qh_setnew(qh, size);
    +  qh_setzero(qh, qh->hash_table, 0, size);
    +  return size;
    +} /* newhashtable */
    +
    +/*---------------------------------
    +
    +  qh_newvertex(qh, point )
    +    returns a new vertex for point
    +*/
    +vertexT *qh_newvertex(qhT *qh, pointT *point) {
    +  vertexT *vertex;
    +
    +  zinc_(Ztotvertices);
    +  vertex= (vertexT *)qh_memalloc(qh, (int)sizeof(vertexT));
    +  memset((char *) vertex, (size_t)0, sizeof(vertexT));
    +  if (qh->vertex_id == UINT_MAX) {
    +    qh_memfree(qh, vertex, (int)sizeof(vertexT));
    +    qh_fprintf(qh, qh->ferr, 6159, "qhull error: more than 2^32 vertices.  vertexT.id field overflows.  Vertices would not be sorted correctly.\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  if (qh->vertex_id == qh->tracevertex_id)
    +    qh->tracevertex= vertex;
    +  vertex->id= qh->vertex_id++;
    +  vertex->point= point;
    +  trace4((qh, qh->ferr, 4060, "qh_newvertex: vertex p%d(v%d) created\n", qh_pointid(qh, vertex->point),
    +          vertex->id));
    +  return(vertex);
    +} /* newvertex */
    +
    +/*---------------------------------
    +
    +  qh_nextridge3d( atridge, facet, vertex )
    +    return next ridge and vertex for a 3d facet
    +    returns NULL on error
    +    [for QhullFacet::nextRidge3d] Does not call qh_errexit nor access qhT.
    +
    +  notes:
    +    in qh_ORIENTclock order
    +    this is a O(n^2) implementation to trace all ridges
    +    be sure to stop on any 2nd visit
    +    same as QhullRidge::nextRidge3d
    +    does not use qhT or qh_errexit [QhullFacet.cpp]
    +
    +  design:
    +    for each ridge
    +      exit if it is the ridge after atridge
    +*/
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp) {
    +  vertexT *atvertex, *vertex, *othervertex;
    +  ridgeT *ridge, **ridgep;
    +
    +  if ((atridge->top == facet) ^ qh_ORIENTclock)
    +    atvertex= SETsecondt_(atridge->vertices, vertexT);
    +  else
    +    atvertex= SETfirstt_(atridge->vertices, vertexT);
    +  FOREACHridge_(facet->ridges) {
    +    if (ridge == atridge)
    +      continue;
    +    if ((ridge->top == facet) ^ qh_ORIENTclock) {
    +      othervertex= SETsecondt_(ridge->vertices, vertexT);
    +      vertex= SETfirstt_(ridge->vertices, vertexT);
    +    }else {
    +      vertex= SETsecondt_(ridge->vertices, vertexT);
    +      othervertex= SETfirstt_(ridge->vertices, vertexT);
    +    }
    +    if (vertex == atvertex) {
    +      if (vertexp)
    +        *vertexp= othervertex;
    +      return ridge;
    +    }
    +  }
    +  return NULL;
    +} /* nextridge3d */
    +#else /* qh_NOmerge */
    +void qh_matchduplicates(qhT *qh, facetT *atfacet, int atskip, int hashsize, int *hashcount) {
    +}
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp) {
    +
    +  return NULL;
    +}
    +#endif /* qh_NOmerge */
    +
    +/*---------------------------------
    +
    +  qh_outcoplanar()
    +    move points from all facets' outsidesets to their coplanarsets
    +
    +  notes:
    +    for post-processing under qh.NARROWhull
    +
    +  design:
    +    for each facet
    +      for each outside point for facet
    +        partition point into coplanar set
    +*/
    +void qh_outcoplanar(qhT *qh /* facet_list */) {
    +  pointT *point, **pointp;
    +  facetT *facet;
    +  realT dist;
    +
    +  trace1((qh, qh->ferr, 1033, "qh_outcoplanar: move outsideset to coplanarset for qh->NARROWhull\n"));
    +  FORALLfacets {
    +    FOREACHpoint_(facet->outsideset) {
    +      qh->num_outside--;
    +      if (qh->KEEPcoplanar || qh->KEEPnearinside) {
    +        qh_distplane(qh, point, facet, &dist);
    +        zinc_(Zpartition);
    +        qh_partitioncoplanar(qh, point, facet, &dist);
    +      }
    +    }
    +    qh_setfree(qh, &facet->outsideset);
    +  }
    +} /* outcoplanar */
    +
    +/*---------------------------------
    +
    +  qh_point(qh, id )
    +    return point for a point id, or NULL if unknown
    +
    +  alternative code:
    +    return((pointT *)((unsigned   long)qh.first_point
    +           + (unsigned long)((id)*qh.normal_size)));
    +*/
    +pointT *qh_point(qhT *qh, int id) {
    +
    +  if (id < 0)
    +    return NULL;
    +  if (id < qh->num_points)
    +    return qh->first_point + id * qh->hull_dim;
    +  id -= qh->num_points;
    +  if (id < qh_setsize(qh, qh->other_points))
    +    return SETelemt_(qh->other_points, id, pointT);
    +  return NULL;
    +} /* point */
    +
    +/*---------------------------------
    +
    +  qh_point_add(qh, set, point, elem )
    +    stores elem at set[point.id]
    +
    +  returns:
    +    access function for qh_pointfacet and qh_pointvertex
    +
    +  notes:
    +    checks point.id
    +*/
    +void qh_point_add(qhT *qh, setT *set, pointT *point, void *elem) {
    +  int id, size;
    +
    +  SETreturnsize_(set, size);
    +  if ((id= qh_pointid(qh, point)) < 0)
    +    qh_fprintf(qh, qh->ferr, 7067, "qhull internal warning (point_add): unknown point %p id %d\n",
    +      point, id);
    +  else if (id >= size) {
    +    qh_fprintf(qh, qh->ferr, 6160, "qhull internal errror(point_add): point p%d is out of bounds(%d)\n",
    +             id, size);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }else
    +    SETelem_(set, id)= elem;
    +} /* point_add */
    +
    +
    +/*---------------------------------
    +
    +  qh_pointfacet()
    +    return temporary set of facet for each point
    +    the set is indexed by point id
    +
    +  notes:
    +    vertices assigned to one of the facets
    +    coplanarset assigned to the facet
    +    outside set assigned to the facet
    +    NULL if no facet for point (inside)
    +      includes qh.GOODpointp
    +
    +  access:
    +    FOREACHfacet_i_(qh, facets) { ... }
    +    SETelem_(facets, i)
    +
    +  design:
    +    for each facet
    +      add each vertex
    +      add each coplanar point
    +      add each outside point
    +*/
    +setT *qh_pointfacet(qhT *qh /*qh.facet_list*/) {
    +  int numpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +  setT *facets;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  pointT *point, **pointp;
    +
    +  facets= qh_settemp(qh, numpoints);
    +  qh_setzero(qh, facets, 0, numpoints);
    +  qh->vertex_visit++;
    +  FORALLfacets {
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh->vertex_visit) {
    +        vertex->visitid= qh->vertex_visit;
    +        qh_point_add(qh, facets, vertex->point, facet);
    +      }
    +    }
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(qh, facets, point, facet);
    +    FOREACHpoint_(facet->outsideset)
    +      qh_point_add(qh, facets, point, facet);
    +  }
    +  return facets;
    +} /* pointfacet */
    +
    +/*---------------------------------
    +
    +  qh_pointvertex(qh, )
    +    return temporary set of vertices indexed by point id
    +    entry is NULL if no vertex for a point
    +      this will include qh.GOODpointp
    +
    +  access:
    +    FOREACHvertex_i_(qh, vertices) { ... }
    +    SETelem_(vertices, i)
    +*/
    +setT *qh_pointvertex(qhT *qh /*qh.facet_list*/) {
    +  int numpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +  setT *vertices;
    +  vertexT *vertex;
    +
    +  vertices= qh_settemp(qh, numpoints);
    +  qh_setzero(qh, vertices, 0, numpoints);
    +  FORALLvertices
    +    qh_point_add(qh, vertices, vertex->point, vertex);
    +  return vertices;
    +} /* pointvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_prependfacet(qh, facet, facetlist )
    +    prepend facet to the start of a facetlist
    +
    +  returns:
    +    increments qh.numfacets
    +    updates facetlist, qh.facet_list, facet_next
    +
    +  notes:
    +    be careful of prepending since it can lose a pointer.
    +      e.g., can lose _next by deleting and then prepending before _next
    +*/
    +void qh_prependfacet(qhT *qh, facetT *facet, facetT **facetlist) {
    +  facetT *prevfacet, *list;
    +
    +
    +  trace4((qh, qh->ferr, 4061, "qh_prependfacet: prepend f%d before f%d\n",
    +          facet->id, getid_(*facetlist)));
    +  if (!*facetlist)
    +    (*facetlist)= qh->facet_tail;
    +  list= *facetlist;
    +  prevfacet= list->previous;
    +  facet->previous= prevfacet;
    +  if (prevfacet)
    +    prevfacet->next= facet;
    +  list->previous= facet;
    +  facet->next= *facetlist;
    +  if (qh->facet_list == list)  /* this may change *facetlist */
    +    qh->facet_list= facet;
    +  if (qh->facet_next == list)
    +    qh->facet_next= facet;
    +  *facetlist= facet;
    +  qh->num_facets++;
    +} /* prependfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhashtable(qh, fp )
    +    print hash table to fp
    +
    +  notes:
    +    not in I/O to avoid bringing io_r.c in
    +
    +  design:
    +    for each hash entry
    +      if defined
    +        if unmatched or will merge (NULL, qh_MERGEridge, qh_DUPLICATEridge)
    +          print entry and neighbors
    +*/
    +void qh_printhashtable(qhT *qh, FILE *fp) {
    +  facetT *facet, *neighbor;
    +  int id, facet_i, facet_n, neighbor_i= 0, neighbor_n= 0;
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHfacet_i_(qh, qh->hash_table) {
    +    if (facet) {
    +      FOREACHneighbor_i_(qh, facet) {
    +        if (!neighbor || neighbor == qh_MERGEridge || neighbor == qh_DUPLICATEridge)
    +          break;
    +      }
    +      if (neighbor_i == neighbor_n)
    +        continue;
    +      qh_fprintf(qh, fp, 9283, "hash %d f%d ", facet_i, facet->id);
    +      FOREACHvertex_(facet->vertices)
    +        qh_fprintf(qh, fp, 9284, "v%d ", vertex->id);
    +      qh_fprintf(qh, fp, 9285, "\n neighbors:");
    +      FOREACHneighbor_i_(qh, facet) {
    +        if (neighbor == qh_MERGEridge)
    +          id= -3;
    +        else if (neighbor == qh_DUPLICATEridge)
    +          id= -2;
    +        else
    +          id= getid_(neighbor);
    +        qh_fprintf(qh, fp, 9286, " %d", id);
    +      }
    +      qh_fprintf(qh, fp, 9287, "\n");
    +    }
    +  }
    +} /* printhashtable */
    +
    +
    +/*---------------------------------
    +
    +  qh_printlists(qh, fp )
    +    print out facet and vertex list for debugging (without 'f/v' tags)
    +*/
    +void qh_printlists(qhT *qh) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  int count= 0;
    +
    +  qh_fprintf(qh, qh->ferr, 8108, "qh_printlists: facets:");
    +  FORALLfacets {
    +    if (++count % 100 == 0)
    +      qh_fprintf(qh, qh->ferr, 8109, "\n     ");
    +    qh_fprintf(qh, qh->ferr, 8110, " %d", facet->id);
    +  }
    +  qh_fprintf(qh, qh->ferr, 8111, "\n  new facets %d visible facets %d next facet for qh_addpoint %d\n  vertices(new %d):",
    +     getid_(qh->newfacet_list), getid_(qh->visible_list), getid_(qh->facet_next),
    +     getid_(qh->newvertex_list));
    +  count = 0;
    +  FORALLvertices {
    +    if (++count % 100 == 0)
    +      qh_fprintf(qh, qh->ferr, 8112, "\n     ");
    +    qh_fprintf(qh, qh->ferr, 8113, " %d", vertex->id);
    +  }
    +  qh_fprintf(qh, qh->ferr, 8114, "\n");
    +} /* printlists */
    +
    +/*---------------------------------
    +
    +  qh_resetlists(qh, stats, qh_RESETvisible )
    +    reset newvertex_list, newfacet_list, visible_list
    +    if stats,
    +      maintains statistics
    +
    +  returns:
    +    visible_list is empty if qh_deletevisible was called
    +*/
    +void qh_resetlists(qhT *qh, boolT stats, boolT resetVisible /*qh.newvertex_list newfacet_list visible_list*/) {
    +  vertexT *vertex;
    +  facetT *newfacet, *visible;
    +  int totnew=0, totver=0;
    +
    +  if (stats) {
    +    FORALLvertex_(qh->newvertex_list)
    +      totver++;
    +    FORALLnew_facets
    +      totnew++;
    +    zadd_(Zvisvertextot, totver);
    +    zmax_(Zvisvertexmax, totver);
    +    zadd_(Znewfacettot, totnew);
    +    zmax_(Znewfacetmax, totnew);
    +  }
    +  FORALLvertex_(qh->newvertex_list)
    +    vertex->newlist= False;
    +  qh->newvertex_list= NULL;
    +  FORALLnew_facets
    +    newfacet->newfacet= False;
    +  qh->newfacet_list= NULL;
    +  if (resetVisible) {
    +    FORALLvisible_facets {
    +      visible->f.replace= NULL;
    +      visible->visible= False;
    +    }
    +    qh->num_visible= 0;
    +  }
    +  qh->visible_list= NULL; /* may still have visible facets via qh_triangulate */
    +  qh->NEWfacets= False;
    +} /* resetlists */
    +
    +/*---------------------------------
    +
    +  qh_setvoronoi_all(qh)
    +    compute Voronoi centers for all facets
    +    includes upperDelaunay facets if qh.UPPERdelaunay ('Qu')
    +
    +  returns:
    +    facet->center is the Voronoi center
    +
    +  notes:
    +    this is unused/untested code
    +      please email bradb@shore.net if this works ok for you
    +
    +  use:
    +    FORALLvertices {...} to locate the vertex for a point.
    +    FOREACHneighbor_(vertex) {...} to visit the Voronoi centers for a Voronoi cell.
    +*/
    +void qh_setvoronoi_all(qhT *qh) {
    +  facetT *facet;
    +
    +  qh_clearcenters(qh, qh_ASvoronoi);
    +  qh_vertexneighbors(qh);
    +
    +  FORALLfacets {
    +    if (!facet->normal || !facet->upperdelaunay || qh->UPPERdelaunay) {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(qh, facet->vertices);
    +    }
    +  }
    +} /* setvoronoi_all */
    +
    +#ifndef qh_NOmerge
    +
    +/*---------------------------------
    +
    +  qh_triangulate()
    +    triangulate non-simplicial facets on qh.facet_list,
    +    if qh->VORONOI, sets Voronoi centers of non-simplicial facets
    +    nop if hasTriangulation
    +
    +  returns:
    +    all facets simplicial
    +    each tricoplanar facet has ->f.triowner == owner of ->center,normal,etc.
    +
    +  notes:
    +    call after qh_check_output since may switch to Voronoi centers
    +    Output may overwrite ->f.triowner with ->f.area
    +*/
    +void qh_triangulate(qhT *qh /*qh.facet_list*/) {
    +  facetT *facet, *nextfacet, *owner;
    +  int onlygood= qh->ONLYgood;
    +  facetT *neighbor, *visible= NULL, *facet1, *facet2, *new_facet_list= NULL;
    +  facetT *orig_neighbor= NULL, *otherfacet;
    +  vertexT *new_vertex_list= NULL;
    +  mergeT *merge;
    +  mergeType mergetype;
    +  int neighbor_i, neighbor_n;
    +
    +  if (qh->hasTriangulation)
    +      return;
    +  trace1((qh, qh->ferr, 1034, "qh_triangulate: triangulate non-simplicial facets\n"));
    +  if (qh->hull_dim == 2)
    +    return;
    +  if (qh->VORONOI) {  /* otherwise lose Voronoi centers [could rebuild vertex set from tricoplanar] */
    +    qh_clearcenters(qh, qh_ASvoronoi);
    +    qh_vertexneighbors(qh);
    +  }
    +  qh->ONLYgood= False; /* for makenew_nonsimplicial */
    +  qh->visit_id++;
    +  qh->NEWfacets= True;
    +  qh->degen_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh->newvertex_list= qh->vertex_tail;
    +  for (facet= qh->facet_list; facet && facet->next; facet= nextfacet) { /* non-simplicial facets moved to end */
    +    nextfacet= facet->next;
    +    if (facet->visible || facet->simplicial)
    +      continue;
    +    /* triangulate all non-simplicial facets, otherwise merging does not work, e.g., RBOX c P-0.1 P+0.1 P+0.1 D3 | QHULL d Qt Tv */
    +    if (!new_facet_list)
    +      new_facet_list= facet;  /* will be moved to end */
    +    qh_triangulate_facet(qh, facet, &new_vertex_list);
    +  }
    +  trace2((qh, qh->ferr, 2047, "qh_triangulate: delete null facets from f%d -- apex same as second vertex\n", getid_(new_facet_list)));
    +  for (facet= new_facet_list; facet && facet->next; facet= nextfacet) { /* null facets moved to end */
    +    nextfacet= facet->next;
    +    if (facet->visible)
    +      continue;
    +    if (facet->ridges) {
    +      if (qh_setsize(qh, facet->ridges) > 0) {
    +        qh_fprintf(qh, qh->ferr, 6161, "qhull error (qh_triangulate): ridges still defined for f%d\n", facet->id);
    +        qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +      }
    +      qh_setfree(qh, &facet->ridges);
    +    }
    +    if (SETfirst_(facet->vertices) == SETsecond_(facet->vertices)) {
    +      zinc_(Ztrinull);
    +      qh_triangulate_null(qh, facet);
    +    }
    +  }
    +  trace2((qh, qh->ferr, 2048, "qh_triangulate: delete %d or more mirror facets -- same vertices and neighbors\n", qh_setsize(qh, qh->degen_mergeset)));
    +  qh->visible_list= qh->facet_tail;
    +  while ((merge= (mergeT*)qh_setdellast(qh->degen_mergeset))) {
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    mergetype= merge->type;
    +    qh_memfree(qh, merge, (int)sizeof(mergeT));
    +    if (mergetype == MRGmirror) {
    +      zinc_(Ztrimirror);
    +      qh_triangulate_mirror(qh, facet1, facet2);
    +    }
    +  }
    +  qh_settempfree(qh, &qh->degen_mergeset);
    +  trace2((qh, qh->ferr, 2049, "qh_triangulate: update neighbor lists for vertices from v%d\n", getid_(new_vertex_list)));
    +  qh->newvertex_list= new_vertex_list;  /* all vertices of new facets */
    +  qh->visible_list= NULL;
    +  qh_updatevertices(qh /*qh.newvertex_list, empty newfacet_list and visible_list*/);
    +  qh_resetlists(qh, False, !qh_RESETvisible /*qh.newvertex_list, empty newfacet_list and visible_list*/);
    +
    +  trace2((qh, qh->ferr, 2050, "qh_triangulate: identify degenerate tricoplanar facets from f%d\n", getid_(new_facet_list)));
    +  trace2((qh, qh->ferr, 2051, "qh_triangulate: and replace facet->f.triowner with tricoplanar facets that own center, normal, etc.\n"));
    +  FORALLfacet_(new_facet_list) {
    +    if (facet->tricoplanar && !facet->visible) {
    +      FOREACHneighbor_i_(qh, facet) {
    +        if (neighbor_i == 0) {  /* first iteration */
    +          if (neighbor->tricoplanar)
    +            orig_neighbor= neighbor->f.triowner;
    +          else
    +            orig_neighbor= neighbor;
    +        }else {
    +          if (neighbor->tricoplanar)
    +            otherfacet= neighbor->f.triowner;
    +          else
    +            otherfacet= neighbor;
    +          if (orig_neighbor == otherfacet) {
    +            zinc_(Ztridegen);
    +            facet->degenerate= True;
    +            break;
    +          }
    +        }
    +      }
    +    }
    +  }
    +
    +  trace2((qh, qh->ferr, 2052, "qh_triangulate: delete visible facets -- non-simplicial, null, and mirrored facets\n"));
    +  owner= NULL;
    +  visible= NULL;
    +  for (facet= new_facet_list; facet && facet->next; facet= nextfacet) { /* may delete facet */
    +    nextfacet= facet->next;
    +    if (facet->visible) {
    +      if (facet->tricoplanar) { /* a null or mirrored facet */
    +        qh_delfacet(qh, facet);
    +        qh->num_visible--;
    +      }else {  /* a non-simplicial facet followed by its tricoplanars */
    +        if (visible && !owner) {
    +          /*  RBOX 200 s D5 t1001471447 | QHULL Qt C-0.01 Qx Qc Tv Qt -- f4483 had 6 vertices/neighbors and 8 ridges */
    +          trace2((qh, qh->ferr, 2053, "qh_triangulate: all tricoplanar facets degenerate for non-simplicial facet f%d\n",
    +                       visible->id));
    +          qh_delfacet(qh, visible);
    +          qh->num_visible--;
    +        }
    +        visible= facet;
    +        owner= NULL;
    +      }
    +    }else if (facet->tricoplanar) {
    +      if (facet->f.triowner != visible || visible==NULL) {
    +        qh_fprintf(qh, qh->ferr, 6162, "qhull error (qh_triangulate): tricoplanar facet f%d not owned by its visible, non-simplicial facet f%d\n", facet->id, getid_(visible));
    +        qh_errexit2(qh, qh_ERRqhull, facet, visible);
    +      }
    +      if (owner)
    +        facet->f.triowner= owner;
    +      else if (!facet->degenerate) {
    +        owner= facet;
    +        nextfacet= visible->next; /* rescan tricoplanar facets with owner, visible!=0 by QH6162 */
    +        facet->keepcentrum= True;  /* one facet owns ->normal, etc. */
    +        facet->coplanarset= visible->coplanarset;
    +        facet->outsideset= visible->outsideset;
    +        visible->coplanarset= NULL;
    +        visible->outsideset= NULL;
    +        if (!qh->TRInormals) { /* center and normal copied to tricoplanar facets */
    +          visible->center= NULL;
    +          visible->normal= NULL;
    +        }
    +        qh_delfacet(qh, visible);
    +        qh->num_visible--;
    +      }
    +    }
    +  }
    +  if (visible && !owner) {
    +    trace2((qh, qh->ferr, 2054, "qh_triangulate: all tricoplanar facets degenerate for last non-simplicial facet f%d\n",
    +                 visible->id));
    +    qh_delfacet(qh, visible);
    +    qh->num_visible--;
    +  }
    +  qh->NEWfacets= False;
    +  qh->ONLYgood= onlygood; /* restore value */
    +  if (qh->CHECKfrequently)
    +    qh_checkpolygon(qh, qh->facet_list);
    +  qh->hasTriangulation= True;
    +} /* triangulate */
    +
    +
    +/*---------------------------------
    +
    +  qh_triangulate_facet(qh, facetA, &firstVertex )
    +    triangulate a non-simplicial facet
    +      if qh.CENTERtype=qh_ASvoronoi, sets its Voronoi center
    +  returns:
    +    qh.newfacet_list == simplicial facets
    +      facet->tricoplanar set and ->keepcentrum false
    +      facet->degenerate set if duplicated apex
    +      facet->f.trivisible set to facetA
    +      facet->center copied from facetA (created if qh_ASvoronoi)
    +        qh_eachvoronoi, qh_detvridge, qh_detvridge3 assume centers copied
    +      facet->normal,offset,maxoutside copied from facetA
    +
    +  notes:
    +      only called by qh_triangulate
    +      qh_makenew_nonsimplicial uses neighbor->seen for the same
    +      if qh.TRInormals, newfacet->normal will need qh_free
    +        if qh.TRInormals and qh_AScentrum, newfacet->center will need qh_free
    +        keepcentrum is also set on Zwidefacet in qh_mergefacet
    +        freed by qh_clearcenters
    +
    +  see also:
    +      qh_addpoint() -- add a point
    +      qh_makenewfacets() -- construct a cone of facets for a new vertex
    +
    +  design:
    +      if qh_ASvoronoi,
    +         compute Voronoi center (facet->center)
    +      select first vertex (highest ID to preserve ID ordering of ->vertices)
    +      triangulate from vertex to ridges
    +      copy facet->center, normal, offset
    +      update vertex neighbors
    +*/
    +void qh_triangulate_facet(qhT *qh, facetT *facetA, vertexT **first_vertex) {
    +  facetT *newfacet;
    +  facetT *neighbor, **neighborp;
    +  vertexT *apex;
    +  int numnew=0;
    +
    +  trace3((qh, qh->ferr, 3020, "qh_triangulate_facet: triangulate facet f%d\n", facetA->id));
    +
    +  if (qh->IStracing >= 4)
    +    qh_printfacet(qh, qh->ferr, facetA);
    +  FOREACHneighbor_(facetA) {
    +    neighbor->seen= False;
    +    neighbor->coplanar= False;
    +  }
    +  if (qh->CENTERtype == qh_ASvoronoi && !facetA->center  /* matches upperdelaunay in qh_setfacetplane() */
    +        && fabs_(facetA->normal[qh->hull_dim -1]) >= qh->ANGLEround * qh_ZEROdelaunay) {
    +    facetA->center= qh_facetcenter(qh, facetA->vertices);
    +  }
    +  qh_willdelete(qh, facetA, NULL);
    +  qh->newfacet_list= qh->facet_tail;
    +  facetA->visitid= qh->visit_id;
    +  apex= SETfirstt_(facetA->vertices, vertexT);
    +  qh_makenew_nonsimplicial(qh, facetA, apex, &numnew);
    +  SETfirst_(facetA->neighbors)= NULL;
    +  FORALLnew_facets {
    +    newfacet->tricoplanar= True;
    +    newfacet->f.trivisible= facetA;
    +    newfacet->degenerate= False;
    +    newfacet->upperdelaunay= facetA->upperdelaunay;
    +    newfacet->good= facetA->good;
    +    if (qh->TRInormals) { /* 'Q11' triangulate duplicates ->normal and ->center */
    +      newfacet->keepcentrum= True;
    +      if(facetA->normal){
    +        newfacet->normal= qh_memalloc(qh, qh->normal_size);
    +        memcpy((char *)newfacet->normal, facetA->normal, qh->normal_size);
    +      }
    +      if (qh->CENTERtype == qh_AScentrum)
    +        newfacet->center= qh_getcentrum(qh, newfacet);
    +      else if (qh->CENTERtype == qh_ASvoronoi && facetA->center){
    +        newfacet->center= qh_memalloc(qh, qh->center_size);
    +        memcpy((char *)newfacet->center, facetA->center, qh->center_size);
    +      }
    +    }else {
    +      newfacet->keepcentrum= False;
    +      /* one facet will have keepcentrum=True at end of qh_triangulate */
    +      newfacet->normal= facetA->normal;
    +      newfacet->center= facetA->center;
    +    }
    +    newfacet->offset= facetA->offset;
    +#if qh_MAXoutside
    +    newfacet->maxoutside= facetA->maxoutside;
    +#endif
    +  }
    +  qh_matchnewfacets(qh /*qh.newfacet_list*/);
    +  zinc_(Ztricoplanar);
    +  zadd_(Ztricoplanartot, numnew);
    +  zmax_(Ztricoplanarmax, numnew);
    +  qh->visible_list= NULL;
    +  if (!(*first_vertex))
    +    (*first_vertex)= qh->newvertex_list;
    +  qh->newvertex_list= NULL;
    +  qh_updatevertices(qh /*qh.newfacet_list, qh.empty visible_list and qh.newvertex_list*/);
    +  qh_resetlists(qh, False, !qh_RESETvisible /*qh.newfacet_list, qh.empty visible_list and qh.newvertex_list*/);
    +} /* triangulate_facet */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_link(qh, oldfacetA, facetA, oldfacetB, facetB)
    +    relink facetA to facetB via oldfacets
    +  returns:
    +    adds mirror facets to qh->degen_mergeset (4-d and up only)
    +  design:
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_link(qhT *qh, facetT *oldfacetA, facetT *facetA, facetT *oldfacetB, facetT *facetB) {
    +  int errmirror= False;
    +
    +  trace3((qh, qh->ferr, 3021, "qh_triangulate_link: relink old facets f%d and f%d between neighbors f%d and f%d\n",
    +         oldfacetA->id, oldfacetB->id, facetA->id, facetB->id));
    +  if (qh_setin(facetA->neighbors, facetB)) {
    +    if (!qh_setin(facetB->neighbors, facetA))
    +      errmirror= True;
    +    else
    +      qh_appendmergeset(qh, facetA, facetB, MRGmirror, NULL);
    +  }else if (qh_setin(facetB->neighbors, facetA))
    +    errmirror= True;
    +  if (errmirror) {
    +    qh_fprintf(qh, qh->ferr, 6163, "qhull error (qh_triangulate_link): mirror facets f%d and f%d do not match for old facets f%d and f%d\n",
    +       facetA->id, facetB->id, oldfacetA->id, oldfacetB->id);
    +    qh_errexit2(qh, qh_ERRqhull, facetA, facetB);
    +  }
    +  qh_setreplace(qh, facetB->neighbors, oldfacetB, facetA);
    +  qh_setreplace(qh, facetA->neighbors, oldfacetA, facetB);
    +} /* triangulate_link */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_mirror(qh, facetA, facetB)
    +    delete mirrored facets from qh_triangulate_null() and qh_triangulate_mirror
    +      a mirrored facet shares the same vertices of a logical ridge
    +  design:
    +    since a null facet duplicates the first two vertices, the opposing neighbors absorb the null facet
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_mirror(qhT *qh, facetT *facetA, facetT *facetB) {
    +  facetT *neighbor, *neighborB;
    +  int neighbor_i, neighbor_n;
    +
    +  trace3((qh, qh->ferr, 3022, "qh_triangulate_mirror: delete mirrored facets f%d and f%d\n",
    +         facetA->id, facetB->id));
    +  FOREACHneighbor_i_(qh, facetA) {
    +    neighborB= SETelemt_(facetB->neighbors, neighbor_i, facetT);
    +    if (neighbor == neighborB)
    +      continue; /* occurs twice */
    +    qh_triangulate_link(qh, facetA, neighbor, facetB, neighborB);
    +  }
    +  qh_willdelete(qh, facetA, NULL);
    +  qh_willdelete(qh, facetB, NULL);
    +} /* triangulate_mirror */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_null(qh, facetA)
    +    remove null facetA from qh_triangulate_facet()
    +      a null facet has vertex #1 (apex) == vertex #2
    +  returns:
    +    adds facetA to ->visible for deletion after qh_updatevertices
    +    qh->degen_mergeset contains mirror facets (4-d and up only)
    +  design:
    +    since a null facet duplicates the first two vertices, the opposing neighbors absorb the null facet
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_null(qhT *qh, facetT *facetA) {
    +  facetT *neighbor, *otherfacet;
    +
    +  trace3((qh, qh->ferr, 3023, "qh_triangulate_null: delete null facet f%d\n", facetA->id));
    +  neighbor= SETfirstt_(facetA->neighbors, facetT);
    +  otherfacet= SETsecondt_(facetA->neighbors, facetT);
    +  qh_triangulate_link(qh, facetA, neighbor, facetA, otherfacet);
    +  qh_willdelete(qh, facetA, NULL);
    +} /* triangulate_null */
    +
    +#else /* qh_NOmerge */
    +void qh_triangulate(qhT *qh) {
    +}
    +#endif /* qh_NOmerge */
    +
    +   /*---------------------------------
    +
    +  qh_vertexintersect(qh, vertexsetA, vertexsetB )
    +    intersects two vertex sets (inverse id ordered)
    +    vertexsetA is a temporary set at the top of qh->qhmem.tempstack
    +
    +  returns:
    +    replaces vertexsetA with the intersection
    +
    +  notes:
    +    could overwrite vertexsetA if currently too slow
    +*/
    +void qh_vertexintersect(qhT *qh, setT **vertexsetA,setT *vertexsetB) {
    +  setT *intersection;
    +
    +  intersection= qh_vertexintersect_new(qh, *vertexsetA, vertexsetB);
    +  qh_settempfree(qh, vertexsetA);
    +  *vertexsetA= intersection;
    +  qh_settemppush(qh, intersection);
    +} /* vertexintersect */
    +
    +/*---------------------------------
    +
    +  qh_vertexintersect_new(qh, )
    +    intersects two vertex sets (inverse id ordered)
    +
    +  returns:
    +    a new set
    +*/
    +setT *qh_vertexintersect_new(qhT *qh, setT *vertexsetA,setT *vertexsetB) {
    +  setT *intersection= qh_setnew(qh, qh->hull_dim - 1);
    +  vertexT **vertexA= SETaddr_(vertexsetA, vertexT);
    +  vertexT **vertexB= SETaddr_(vertexsetB, vertexT);
    +
    +  while (*vertexA && *vertexB) {
    +    if (*vertexA  == *vertexB) {
    +      qh_setappend(qh, &intersection, *vertexA);
    +      vertexA++; vertexB++;
    +    }else {
    +      if ((*vertexA)->id > (*vertexB)->id)
    +        vertexA++;
    +      else
    +        vertexB++;
    +    }
    +  }
    +  return intersection;
    +} /* vertexintersect_new */
    +
    +/*---------------------------------
    +
    +  qh_vertexneighbors(qh)
    +    for each vertex in qh.facet_list,
    +      determine its neighboring facets
    +
    +  returns:
    +    sets qh.VERTEXneighbors
    +      nop if qh.VERTEXneighbors already set
    +      qh_addpoint() will maintain them
    +
    +  notes:
    +    assumes all vertex->neighbors are NULL
    +
    +  design:
    +    for each facet
    +      for each vertex
    +        append facet to vertex->neighbors
    +*/
    +void qh_vertexneighbors(qhT *qh /*qh.facet_list*/) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh->VERTEXneighbors)
    +    return;
    +  trace1((qh, qh->ferr, 1035, "qh_vertexneighbors: determining neighboring facets for each vertex\n"));
    +  qh->vertex_visit++;
    +  FORALLfacets {
    +    if (facet->visible)
    +      continue;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh->vertex_visit) {
    +        vertex->visitid= qh->vertex_visit;
    +        vertex->neighbors= qh_setnew(qh, qh->hull_dim);
    +      }
    +      qh_setappend(qh, &vertex->neighbors, facet);
    +    }
    +  }
    +  qh->VERTEXneighbors= True;
    +} /* vertexneighbors */
    +
    +/*---------------------------------
    +
    +  qh_vertexsubset( vertexsetA, vertexsetB )
    +    returns True if vertexsetA is a subset of vertexsetB
    +    assumes vertexsets are sorted
    +
    +  note:
    +    empty set is a subset of any other set
    +*/
    +boolT qh_vertexsubset(setT *vertexsetA, setT *vertexsetB) {
    +  vertexT **vertexA= (vertexT **) SETaddr_(vertexsetA, vertexT);
    +  vertexT **vertexB= (vertexT **) SETaddr_(vertexsetB, vertexT);
    +
    +  while (True) {
    +    if (!*vertexA)
    +      return True;
    +    if (!*vertexB)
    +      return False;
    +    if ((*vertexA)->id > (*vertexB)->id)
    +      return False;
    +    if (*vertexA  == *vertexB)
    +      vertexA++;
    +    vertexB++;
    +  }
    +  return False; /* avoid warnings */
    +} /* vertexsubset */
    diff --git a/xs/src/qhull/src/libqhull_r/poly_r.c b/xs/src/qhull/src/libqhull_r/poly_r.c
    new file mode 100644
    index 0000000000..e5b4797437
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/poly_r.c
    @@ -0,0 +1,1205 @@
    +/*
      ---------------------------------
    +
    +   poly_r.c
    +   implements polygons and simplices
    +
    +   see qh-poly_r.htm, poly_r.h and libqhull_r.h
    +
    +   infrequent code is in poly2_r.c
    +   (all but top 50 and their callers 12/3/95)
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/poly_r.c#3 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*======== functions in alphabetical order ==========*/
    +
    +/*---------------------------------
    +
    +  qh_appendfacet(qh, facet )
    +    appends facet to end of qh.facet_list,
    +
    +  returns:
    +    updates qh.newfacet_list, facet_next, facet_list
    +    increments qh.numfacets
    +
    +  notes:
    +    assumes qh.facet_list/facet_tail is defined (createsimplex)
    +
    +  see:
    +    qh_removefacet()
    +
    +*/
    +void qh_appendfacet(qhT *qh, facetT *facet) {
    +  facetT *tail= qh->facet_tail;
    +
    +  if (tail == qh->newfacet_list)
    +    qh->newfacet_list= facet;
    +  if (tail == qh->facet_next)
    +    qh->facet_next= facet;
    +  facet->previous= tail->previous;
    +  facet->next= tail;
    +  if (tail->previous)
    +    tail->previous->next= facet;
    +  else
    +    qh->facet_list= facet;
    +  tail->previous= facet;
    +  qh->num_facets++;
    +  trace4((qh, qh->ferr, 4044, "qh_appendfacet: append f%d to facet_list\n", facet->id));
    +} /* appendfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_appendvertex(qh, vertex )
    +    appends vertex to end of qh.vertex_list,
    +
    +  returns:
    +    sets vertex->newlist
    +    updates qh.vertex_list, newvertex_list
    +    increments qh.num_vertices
    +
    +  notes:
    +    assumes qh.vertex_list/vertex_tail is defined (createsimplex)
    +
    +*/
    +void qh_appendvertex(qhT *qh, vertexT *vertex) {
    +  vertexT *tail= qh->vertex_tail;
    +
    +  if (tail == qh->newvertex_list)
    +    qh->newvertex_list= vertex;
    +  vertex->newlist= True;
    +  vertex->previous= tail->previous;
    +  vertex->next= tail;
    +  if (tail->previous)
    +    tail->previous->next= vertex;
    +  else
    +    qh->vertex_list= vertex;
    +  tail->previous= vertex;
    +  qh->num_vertices++;
    +  trace4((qh, qh->ferr, 4045, "qh_appendvertex: append v%d to vertex_list\n", vertex->id));
    +} /* appendvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_attachnewfacets(qh, )
    +    attach horizon facets to new facets in qh.newfacet_list
    +    newfacets have neighbor and ridge links to horizon but not vice versa
    +    only needed for qh.ONLYgood
    +
    +  returns:
    +    set qh.NEWfacets
    +    horizon facets linked to new facets
    +      ridges changed from visible facets to new facets
    +      simplicial ridges deleted
    +    qh.visible_list, no ridges valid
    +    facet->f.replace is a newfacet (if any)
    +
    +  design:
    +    delete interior ridges and neighbor sets by
    +      for each visible, non-simplicial facet
    +        for each ridge
    +          if last visit or if neighbor is simplicial
    +            if horizon neighbor
    +              delete ridge for horizon's ridge set
    +            delete ridge
    +        erase neighbor set
    +    attach horizon facets and new facets by
    +      for all new facets
    +        if corresponding horizon facet is simplicial
    +          locate corresponding visible facet {may be more than one}
    +          link visible facet to new facet
    +          replace visible facet with new facet in horizon
    +        else it's non-simplicial
    +          for all visible neighbors of the horizon facet
    +            link visible neighbor to new facet
    +            delete visible neighbor from horizon facet
    +          append new facet to horizon's neighbors
    +          the first ridge of the new facet is the horizon ridge
    +          link the new facet into the horizon ridge
    +*/
    +void qh_attachnewfacets(qhT *qh /* qh.visible_list, newfacet_list */) {
    +  facetT *newfacet= NULL, *neighbor, **neighborp, *horizon, *visible;
    +  ridgeT *ridge, **ridgep;
    +
    +  qh->NEWfacets= True;
    +  trace3((qh, qh->ferr, 3012, "qh_attachnewfacets: delete interior ridges\n"));
    +  qh->visit_id++;
    +  FORALLvisible_facets {
    +    visible->visitid= qh->visit_id;
    +    if (visible->ridges) {
    +      FOREACHridge_(visible->ridges) {
    +        neighbor= otherfacet_(ridge, visible);
    +        if (neighbor->visitid == qh->visit_id
    +            || (!neighbor->visible && neighbor->simplicial)) {
    +          if (!neighbor->visible)  /* delete ridge for simplicial horizon */
    +            qh_setdel(neighbor->ridges, ridge);
    +          qh_setfree(qh, &(ridge->vertices)); /* delete on 2nd visit */
    +          qh_memfree(qh, ridge, (int)sizeof(ridgeT));
    +        }
    +      }
    +      SETfirst_(visible->ridges)= NULL;
    +    }
    +    SETfirst_(visible->neighbors)= NULL;
    +  }
    +  trace1((qh, qh->ferr, 1017, "qh_attachnewfacets: attach horizon facets to new facets\n"));
    +  FORALLnew_facets {
    +    horizon= SETfirstt_(newfacet->neighbors, facetT);
    +    if (horizon->simplicial) {
    +      visible= NULL;
    +      FOREACHneighbor_(horizon) {   /* may have more than one horizon ridge */
    +        if (neighbor->visible) {
    +          if (visible) {
    +            if (qh_setequal_skip(newfacet->vertices, 0, horizon->vertices,
    +                                  SETindex_(horizon->neighbors, neighbor))) {
    +              visible= neighbor;
    +              break;
    +            }
    +          }else
    +            visible= neighbor;
    +        }
    +      }
    +      if (visible) {
    +        visible->f.replace= newfacet;
    +        qh_setreplace(qh, horizon->neighbors, visible, newfacet);
    +      }else {
    +        qh_fprintf(qh, qh->ferr, 6102, "qhull internal error (qh_attachnewfacets): couldn't find visible facet for horizon f%d of newfacet f%d\n",
    +                 horizon->id, newfacet->id);
    +        qh_errexit2(qh, qh_ERRqhull, horizon, newfacet);
    +      }
    +    }else { /* non-simplicial, with a ridge for newfacet */
    +      FOREACHneighbor_(horizon) {    /* may hold for many new facets */
    +        if (neighbor->visible) {
    +          neighbor->f.replace= newfacet;
    +          qh_setdelnth(qh, horizon->neighbors,
    +                        SETindex_(horizon->neighbors, neighbor));
    +          neighborp--; /* repeat */
    +        }
    +      }
    +      qh_setappend(qh, &horizon->neighbors, newfacet);
    +      ridge= SETfirstt_(newfacet->ridges, ridgeT);
    +      if (ridge->top == horizon)
    +        ridge->bottom= newfacet;
    +      else
    +        ridge->top= newfacet;
    +      }
    +  } /* newfacets */
    +  if (qh->PRINTstatistics) {
    +    FORALLvisible_facets {
    +      if (!visible->f.replace)
    +        zinc_(Zinsidevisible);
    +    }
    +  }
    +} /* attachnewfacets */
    +
    +/*---------------------------------
    +
    +  qh_checkflipped(qh, facet, dist, allerror )
    +    checks facet orientation to interior point
    +
    +    if allerror set,
    +      tests against qh.DISTround
    +    else
    +      tests against 0 since tested against DISTround before
    +
    +  returns:
    +    False if it flipped orientation (sets facet->flipped)
    +    distance if non-NULL
    +*/
    +boolT qh_checkflipped(qhT *qh, facetT *facet, realT *distp, boolT allerror) {
    +  realT dist;
    +
    +  if (facet->flipped && !distp)
    +    return False;
    +  zzinc_(Zdistcheck);
    +  qh_distplane(qh, qh->interior_point, facet, &dist);
    +  if (distp)
    +    *distp= dist;
    +  if ((allerror && dist > -qh->DISTround)|| (!allerror && dist >= 0.0)) {
    +    facet->flipped= True;
    +    zzinc_(Zflippedfacets);
    +    trace0((qh, qh->ferr, 19, "qh_checkflipped: facet f%d is flipped, distance= %6.12g during p%d\n",
    +              facet->id, dist, qh->furthest_id));
    +    qh_precision(qh, "flipped facet");
    +    return False;
    +  }
    +  return True;
    +} /* checkflipped */
    +
    +/*---------------------------------
    +
    +  qh_delfacet(qh, facet )
    +    removes facet from facet_list and frees up its memory
    +
    +  notes:
    +    assumes vertices and ridges already freed
    +*/
    +void qh_delfacet(qhT *qh, facetT *facet) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  trace4((qh, qh->ferr, 4046, "qh_delfacet: delete f%d\n", facet->id));
    +  if (facet == qh->tracefacet)
    +    qh->tracefacet= NULL;
    +  if (facet == qh->GOODclosest)
    +    qh->GOODclosest= NULL;
    +  qh_removefacet(qh, facet);
    +  if (!facet->tricoplanar || facet->keepcentrum) {
    +    qh_memfree_(qh, facet->normal, qh->normal_size, freelistp);
    +    if (qh->CENTERtype == qh_ASvoronoi) {   /* braces for macro calls */
    +      qh_memfree_(qh, facet->center, qh->center_size, freelistp);
    +    }else /* AScentrum */ {
    +      qh_memfree_(qh, facet->center, qh->normal_size, freelistp);
    +    }
    +  }
    +  qh_setfree(qh, &(facet->neighbors));
    +  if (facet->ridges)
    +    qh_setfree(qh, &(facet->ridges));
    +  qh_setfree(qh, &(facet->vertices));
    +  if (facet->outsideset)
    +    qh_setfree(qh, &(facet->outsideset));
    +  if (facet->coplanarset)
    +    qh_setfree(qh, &(facet->coplanarset));
    +  qh_memfree_(qh, facet, (int)sizeof(facetT), freelistp);
    +} /* delfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_deletevisible()
    +    delete visible facets and vertices
    +
    +  returns:
    +    deletes each facet and removes from facetlist
    +    at exit, qh.visible_list empty (== qh.newfacet_list)
    +
    +  notes:
    +    ridges already deleted
    +    horizon facets do not reference facets on qh.visible_list
    +    new facets in qh.newfacet_list
    +    uses   qh.visit_id;
    +*/
    +void qh_deletevisible(qhT *qh /*qh.visible_list*/) {
    +  facetT *visible, *nextfacet;
    +  vertexT *vertex, **vertexp;
    +  int numvisible= 0, numdel= qh_setsize(qh, qh->del_vertices);
    +
    +  trace1((qh, qh->ferr, 1018, "qh_deletevisible: delete %d visible facets and %d vertices\n",
    +         qh->num_visible, numdel));
    +  for (visible= qh->visible_list; visible && visible->visible;
    +                visible= nextfacet) { /* deleting current */
    +    nextfacet= visible->next;
    +    numvisible++;
    +    qh_delfacet(qh, visible);
    +  }
    +  if (numvisible != qh->num_visible) {
    +    qh_fprintf(qh, qh->ferr, 6103, "qhull internal error (qh_deletevisible): qh->num_visible %d is not number of visible facets %d\n",
    +             qh->num_visible, numvisible);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  qh->num_visible= 0;
    +  zadd_(Zvisfacettot, numvisible);
    +  zmax_(Zvisfacetmax, numvisible);
    +  zzadd_(Zdelvertextot, numdel);
    +  zmax_(Zdelvertexmax, numdel);
    +  FOREACHvertex_(qh->del_vertices)
    +    qh_delvertex(qh, vertex);
    +  qh_settruncate(qh, qh->del_vertices, 0);
    +} /* deletevisible */
    +
    +/*---------------------------------
    +
    +  qh_facetintersect(qh, facetA, facetB, skipa, skipB, prepend )
    +    return vertices for intersection of two simplicial facets
    +    may include 1 prepended entry (if more, need to settemppush)
    +
    +  returns:
    +    returns set of qh.hull_dim-1 + prepend vertices
    +    returns skipped index for each test and checks for exactly one
    +
    +  notes:
    +    does not need settemp since set in quick memory
    +
    +  see also:
    +    qh_vertexintersect and qh_vertexintersect_new
    +    use qh_setnew_delnthsorted to get nth ridge (no skip information)
    +
    +  design:
    +    locate skipped vertex by scanning facet A's neighbors
    +    locate skipped vertex by scanning facet B's neighbors
    +    intersect the vertex sets
    +*/
    +setT *qh_facetintersect(qhT *qh, facetT *facetA, facetT *facetB,
    +                         int *skipA,int *skipB, int prepend) {
    +  setT *intersect;
    +  int dim= qh->hull_dim, i, j;
    +  facetT **neighborsA, **neighborsB;
    +
    +  neighborsA= SETaddr_(facetA->neighbors, facetT);
    +  neighborsB= SETaddr_(facetB->neighbors, facetT);
    +  i= j= 0;
    +  if (facetB == *neighborsA++)
    +    *skipA= 0;
    +  else if (facetB == *neighborsA++)
    +    *skipA= 1;
    +  else if (facetB == *neighborsA++)
    +    *skipA= 2;
    +  else {
    +    for (i=3; i < dim; i++) {
    +      if (facetB == *neighborsA++) {
    +        *skipA= i;
    +        break;
    +      }
    +    }
    +  }
    +  if (facetA == *neighborsB++)
    +    *skipB= 0;
    +  else if (facetA == *neighborsB++)
    +    *skipB= 1;
    +  else if (facetA == *neighborsB++)
    +    *skipB= 2;
    +  else {
    +    for (j=3; j < dim; j++) {
    +      if (facetA == *neighborsB++) {
    +        *skipB= j;
    +        break;
    +      }
    +    }
    +  }
    +  if (i >= dim || j >= dim) {
    +    qh_fprintf(qh, qh->ferr, 6104, "qhull internal error (qh_facetintersect): f%d or f%d not in others neighbors\n",
    +            facetA->id, facetB->id);
    +    qh_errexit2(qh, qh_ERRqhull, facetA, facetB);
    +  }
    +  intersect= qh_setnew_delnthsorted(qh, facetA->vertices, qh->hull_dim, *skipA, prepend);
    +  trace4((qh, qh->ferr, 4047, "qh_facetintersect: f%d skip %d matches f%d skip %d\n",
    +          facetA->id, *skipA, facetB->id, *skipB));
    +  return(intersect);
    +} /* facetintersect */
    +
    +/*---------------------------------
    +
    +  qh_gethash(qh, hashsize, set, size, firstindex, skipelem )
    +    return hashvalue for a set with firstindex and skipelem
    +
    +  notes:
    +    returned hash is in [0,hashsize)
    +    assumes at least firstindex+1 elements
    +    assumes skipelem is NULL, in set, or part of hash
    +
    +    hashes memory addresses which may change over different runs of the same data
    +    using sum for hash does badly in high d
    +*/
    +int qh_gethash(qhT *qh, int hashsize, setT *set, int size, int firstindex, void *skipelem) {
    +  void **elemp= SETelemaddr_(set, firstindex, void);
    +  ptr_intT hash = 0, elem;
    +  unsigned result;
    +  int i;
    +#ifdef _MSC_VER                   /* Microsoft Visual C++ -- warn about 64-bit issues */
    +#pragma warning( push)            /* WARN64 -- ptr_intT holds a 64-bit pointer */
    +#pragma warning( disable : 4311)  /* 'type cast': pointer truncation from 'void*' to 'ptr_intT' */
    +#endif
    +
    +  switch (size-firstindex) {
    +  case 1:
    +    hash= (ptr_intT)(*elemp) - (ptr_intT) skipelem;
    +    break;
    +  case 2:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] - (ptr_intT) skipelem;
    +    break;
    +  case 3:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      - (ptr_intT) skipelem;
    +    break;
    +  case 4:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] - (ptr_intT) skipelem;
    +    break;
    +  case 5:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] + (ptr_intT)elemp[4] - (ptr_intT) skipelem;
    +    break;
    +  case 6:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] + (ptr_intT)elemp[4]+ (ptr_intT)elemp[5]
    +      - (ptr_intT) skipelem;
    +    break;
    +  default:
    +    hash= 0;
    +    i= 3;
    +    do {     /* this is about 10% in 10-d */
    +      if ((elem= (ptr_intT)*elemp++) != (ptr_intT)skipelem) {
    +        hash ^= (elem << i) + (elem >> (32-i));
    +        i += 3;
    +        if (i >= 32)
    +          i -= 32;
    +      }
    +    }while (*elemp);
    +    break;
    +  }
    +  if (hashsize<0) {
    +    qh_fprintf(qh, qh->ferr, 6202, "qhull internal error: negative hashsize %d passed to qh_gethash [poly.c]\n", hashsize);
    +    qh_errexit2(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  result= (unsigned)hash;
    +  result %= (unsigned)hashsize;
    +  /* result= 0; for debugging */
    +  return result;
    +#ifdef _MSC_VER
    +#pragma warning( pop)
    +#endif
    +} /* gethash */
    +
    +/*---------------------------------
    +
    +  qh_makenewfacet(qh, vertices, toporient, horizon )
    +    creates a toporient? facet from vertices
    +
    +  returns:
    +    returns newfacet
    +      adds newfacet to qh.facet_list
    +      newfacet->vertices= vertices
    +      if horizon
    +        newfacet->neighbor= horizon, but not vice versa
    +    newvertex_list updated with vertices
    +*/
    +facetT *qh_makenewfacet(qhT *qh, setT *vertices, boolT toporient,facetT *horizon) {
    +  facetT *newfacet;
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->newlist) {
    +      qh_removevertex(qh, vertex);
    +      qh_appendvertex(qh, vertex);
    +    }
    +  }
    +  newfacet= qh_newfacet(qh);
    +  newfacet->vertices= vertices;
    +  newfacet->toporient= (unsigned char)toporient;
    +  if (horizon)
    +    qh_setappend(qh, &(newfacet->neighbors), horizon);
    +  qh_appendfacet(qh, newfacet);
    +  return(newfacet);
    +} /* makenewfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_makenewplanes()
    +    make new hyperplanes for facets on qh.newfacet_list
    +
    +  returns:
    +    all facets have hyperplanes or are marked for   merging
    +    doesn't create hyperplane if horizon is coplanar (will merge)
    +    updates qh.min_vertex if qh.JOGGLEmax
    +
    +  notes:
    +    facet->f.samecycle is defined for facet->mergehorizon facets
    +*/
    +void qh_makenewplanes(qhT *qh /* qh.newfacet_list */) {
    +  facetT *newfacet;
    +
    +  FORALLnew_facets {
    +    if (!newfacet->mergehorizon)
    +      qh_setfacetplane(qh, newfacet);
    +  }
    +  if (qh->JOGGLEmax < REALmax/2)
    +    minimize_(qh->min_vertex, -wwval_(Wnewvertexmax));
    +} /* makenewplanes */
    +
    +/*---------------------------------
    +
    +  qh_makenew_nonsimplicial(qh, visible, apex, numnew )
    +    make new facets for ridges of a visible facet
    +
    +  returns:
    +    first newfacet, bumps numnew as needed
    +    attaches new facets if !qh.ONLYgood
    +    marks ridge neighbors for simplicial visible
    +    if (qh.ONLYgood)
    +      ridges on newfacet, horizon, and visible
    +    else
    +      ridge and neighbors between newfacet and   horizon
    +      visible facet's ridges are deleted
    +
    +  notes:
    +    qh.visit_id if visible has already been processed
    +    sets neighbor->seen for building f.samecycle
    +      assumes all 'seen' flags initially false
    +
    +  design:
    +    for each ridge of visible facet
    +      get neighbor of visible facet
    +      if neighbor was already processed
    +        delete the ridge (will delete all visible facets later)
    +      if neighbor is a horizon facet
    +        create a new facet
    +        if neighbor coplanar
    +          adds newfacet to f.samecycle for later merging
    +        else
    +          updates neighbor's neighbor set
    +          (checks for non-simplicial facet with multiple ridges to visible facet)
    +        updates neighbor's ridge set
    +        (checks for simplicial neighbor to non-simplicial visible facet)
    +        (deletes ridge if neighbor is simplicial)
    +
    +*/
    +#ifndef qh_NOmerge
    +facetT *qh_makenew_nonsimplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +  ridgeT *ridge, **ridgep;
    +  facetT *neighbor, *newfacet= NULL, *samecycle;
    +  setT *vertices;
    +  boolT toporient;
    +  int ridgeid;
    +
    +  FOREACHridge_(visible->ridges) {
    +    ridgeid= ridge->id;
    +    neighbor= otherfacet_(ridge, visible);
    +    if (neighbor->visible) {
    +      if (!qh->ONLYgood) {
    +        if (neighbor->visitid == qh->visit_id) {
    +          qh_setfree(qh, &(ridge->vertices));  /* delete on 2nd visit */
    +          qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
    +        }
    +      }
    +    }else {  /* neighbor is an horizon facet */
    +      toporient= (ridge->top == visible);
    +      vertices= qh_setnew(qh, qh->hull_dim); /* makes sure this is quick */
    +      qh_setappend(qh, &vertices, apex);
    +      qh_setappend_set(qh, &vertices, ridge->vertices);
    +      newfacet= qh_makenewfacet(qh, vertices, toporient, neighbor);
    +      (*numnew)++;
    +      if (neighbor->coplanar) {
    +        newfacet->mergehorizon= True;
    +        if (!neighbor->seen) {
    +          newfacet->f.samecycle= newfacet;
    +          neighbor->f.newcycle= newfacet;
    +        }else {
    +          samecycle= neighbor->f.newcycle;
    +          newfacet->f.samecycle= samecycle->f.samecycle;
    +          samecycle->f.samecycle= newfacet;
    +        }
    +      }
    +      if (qh->ONLYgood) {
    +        if (!neighbor->simplicial)
    +          qh_setappend(qh, &(newfacet->ridges), ridge);
    +      }else {  /* qh_attachnewfacets */
    +        if (neighbor->seen) {
    +          if (neighbor->simplicial) {
    +            qh_fprintf(qh, qh->ferr, 6105, "qhull internal error (qh_makenew_nonsimplicial): simplicial f%d sharing two ridges with f%d\n",
    +                   neighbor->id, visible->id);
    +            qh_errexit2(qh, qh_ERRqhull, neighbor, visible);
    +          }
    +          qh_setappend(qh, &(neighbor->neighbors), newfacet);
    +        }else
    +          qh_setreplace(qh, neighbor->neighbors, visible, newfacet);
    +        if (neighbor->simplicial) {
    +          qh_setdel(neighbor->ridges, ridge);
    +          qh_setfree(qh, &(ridge->vertices));
    +          qh_memfree(qh, ridge, (int)sizeof(ridgeT));
    +        }else {
    +          qh_setappend(qh, &(newfacet->ridges), ridge);
    +          if (toporient)
    +            ridge->top= newfacet;
    +          else
    +            ridge->bottom= newfacet;
    +        }
    +      trace4((qh, qh->ferr, 4048, "qh_makenew_nonsimplicial: created facet f%d from v%d and r%d of horizon f%d\n",
    +            newfacet->id, apex->id, ridgeid, neighbor->id));
    +      }
    +    }
    +    neighbor->seen= True;
    +  } /* for each ridge */
    +  if (!qh->ONLYgood)
    +    SETfirst_(visible->ridges)= NULL;
    +  return newfacet;
    +} /* makenew_nonsimplicial */
    +#else /* qh_NOmerge */
    +facetT *qh_makenew_nonsimplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew) {
    +  return NULL;
    +}
    +#endif /* qh_NOmerge */
    +
    +/*---------------------------------
    +
    +  qh_makenew_simplicial(qh, visible, apex, numnew )
    +    make new facets for simplicial visible facet and apex
    +
    +  returns:
    +    attaches new facets if (!qh.ONLYgood)
    +      neighbors between newfacet and horizon
    +
    +  notes:
    +    nop if neighbor->seen or neighbor->visible(see qh_makenew_nonsimplicial)
    +
    +  design:
    +    locate neighboring horizon facet for visible facet
    +    determine vertices and orientation
    +    create new facet
    +    if coplanar,
    +      add new facet to f.samecycle
    +    update horizon facet's neighbor list
    +*/
    +facetT *qh_makenew_simplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew) {
    +  facetT *neighbor, **neighborp, *newfacet= NULL;
    +  setT *vertices;
    +  boolT flip, toporient;
    +  int horizonskip= 0, visibleskip= 0;
    +
    +  FOREACHneighbor_(visible) {
    +    if (!neighbor->seen && !neighbor->visible) {
    +      vertices= qh_facetintersect(qh, neighbor,visible, &horizonskip, &visibleskip, 1);
    +      SETfirst_(vertices)= apex;
    +      flip= ((horizonskip & 0x1) ^ (visibleskip & 0x1));
    +      if (neighbor->toporient)
    +        toporient= horizonskip & 0x1;
    +      else
    +        toporient= (horizonskip & 0x1) ^ 0x1;
    +      newfacet= qh_makenewfacet(qh, vertices, toporient, neighbor);
    +      (*numnew)++;
    +      if (neighbor->coplanar && (qh->PREmerge || qh->MERGEexact)) {
    +#ifndef qh_NOmerge
    +        newfacet->f.samecycle= newfacet;
    +        newfacet->mergehorizon= True;
    +#endif
    +      }
    +      if (!qh->ONLYgood)
    +        SETelem_(neighbor->neighbors, horizonskip)= newfacet;
    +      trace4((qh, qh->ferr, 4049, "qh_makenew_simplicial: create facet f%d top %d from v%d and horizon f%d skip %d top %d and visible f%d skip %d, flip? %d\n",
    +            newfacet->id, toporient, apex->id, neighbor->id, horizonskip,
    +              neighbor->toporient, visible->id, visibleskip, flip));
    +    }
    +  }
    +  return newfacet;
    +} /* makenew_simplicial */
    +
    +/*---------------------------------
    +
    +  qh_matchneighbor(qh, newfacet, newskip, hashsize, hashcount )
    +    either match subridge of newfacet with neighbor or add to hash_table
    +
    +  returns:
    +    duplicate ridges are unmatched and marked by qh_DUPLICATEridge
    +
    +  notes:
    +    ridge is newfacet->vertices w/o newskip vertex
    +    do not allocate memory (need to free hash_table cleanly)
    +    uses linear hash chains
    +
    +  see also:
    +    qh_matchduplicates
    +
    +  design:
    +    for each possible matching facet in qh.hash_table
    +      if vertices match
    +        set ismatch, if facets have opposite orientation
    +        if ismatch and matching facet doesn't have a match
    +          match the facets by updating their neighbor sets
    +        else
    +          indicate a duplicate ridge
    +          set facet hyperplane for later testing
    +          add facet to hashtable
    +          unless the other facet was already a duplicate ridge
    +            mark both facets with a duplicate ridge
    +            add other facet (if defined) to hash table
    +*/
    +void qh_matchneighbor(qhT *qh, facetT *newfacet, int newskip, int hashsize, int *hashcount) {
    +  boolT newfound= False;   /* True, if new facet is already in hash chain */
    +  boolT same, ismatch;
    +  int hash, scan;
    +  facetT *facet, *matchfacet;
    +  int skip, matchskip;
    +
    +  hash= qh_gethash(qh, hashsize, newfacet->vertices, qh->hull_dim, 1,
    +                     SETelem_(newfacet->vertices, newskip));
    +  trace4((qh, qh->ferr, 4050, "qh_matchneighbor: newfacet f%d skip %d hash %d hashcount %d\n",
    +          newfacet->id, newskip, hash, *hashcount));
    +  zinc_(Zhashlookup);
    +  for (scan= hash; (facet= SETelemt_(qh->hash_table, scan, facetT));
    +       scan= (++scan >= hashsize ? 0 : scan)) {
    +    if (facet == newfacet) {
    +      newfound= True;
    +      continue;
    +    }
    +    zinc_(Zhashtests);
    +    if (qh_matchvertices(qh, 1, newfacet->vertices, newskip, facet->vertices, &skip, &same)) {
    +      if (SETelem_(newfacet->vertices, newskip) ==
    +          SETelem_(facet->vertices, skip)) {
    +        qh_precision(qh, "two facets with the same vertices");
    +        qh_fprintf(qh, qh->ferr, 6106, "qhull precision error: Vertex sets are the same for f%d and f%d.  Can not force output.\n",
    +          facet->id, newfacet->id);
    +        qh_errexit2(qh, qh_ERRprec, facet, newfacet);
    +      }
    +      ismatch= (same == (boolT)((newfacet->toporient ^ facet->toporient)));
    +      matchfacet= SETelemt_(facet->neighbors, skip, facetT);
    +      if (ismatch && !matchfacet) {
    +        SETelem_(facet->neighbors, skip)= newfacet;
    +        SETelem_(newfacet->neighbors, newskip)= facet;
    +        (*hashcount)--;
    +        trace4((qh, qh->ferr, 4051, "qh_matchneighbor: f%d skip %d matched with new f%d skip %d\n",
    +           facet->id, skip, newfacet->id, newskip));
    +        return;
    +      }
    +      if (!qh->PREmerge && !qh->MERGEexact) {
    +        qh_precision(qh, "a ridge with more than two neighbors");
    +        qh_fprintf(qh, qh->ferr, 6107, "qhull precision error: facets f%d, f%d and f%d meet at a ridge with more than 2 neighbors.  Can not continue.\n",
    +                 facet->id, newfacet->id, getid_(matchfacet));
    +        qh_errexit2(qh, qh_ERRprec, facet, newfacet);
    +      }
    +      SETelem_(newfacet->neighbors, newskip)= qh_DUPLICATEridge;
    +      newfacet->dupridge= True;
    +      if (!newfacet->normal)
    +        qh_setfacetplane(qh, newfacet);
    +      qh_addhash(newfacet, qh->hash_table, hashsize, hash);
    +      (*hashcount)++;
    +      if (!facet->normal)
    +        qh_setfacetplane(qh, facet);
    +      if (matchfacet != qh_DUPLICATEridge) {
    +        SETelem_(facet->neighbors, skip)= qh_DUPLICATEridge;
    +        facet->dupridge= True;
    +        if (!facet->normal)
    +          qh_setfacetplane(qh, facet);
    +        if (matchfacet) {
    +          matchskip= qh_setindex(matchfacet->neighbors, facet);
    +          if (matchskip<0) {
    +              qh_fprintf(qh, qh->ferr, 6260, "qhull internal error (qh_matchneighbor): matchfacet f%d is in f%d neighbors but not vice versa.  Can not continue.\n",
    +                  matchfacet->id, facet->id);
    +              qh_errexit2(qh, qh_ERRqhull, matchfacet, facet);
    +          }
    +          SETelem_(matchfacet->neighbors, matchskip)= qh_DUPLICATEridge; /* matchskip>=0 by QH6260 */
    +          matchfacet->dupridge= True;
    +          if (!matchfacet->normal)
    +            qh_setfacetplane(qh, matchfacet);
    +          qh_addhash(matchfacet, qh->hash_table, hashsize, hash);
    +          *hashcount += 2;
    +        }
    +      }
    +      trace4((qh, qh->ferr, 4052, "qh_matchneighbor: new f%d skip %d duplicates ridge for f%d skip %d matching f%d ismatch %d at hash %d\n",
    +           newfacet->id, newskip, facet->id, skip,
    +           (matchfacet == qh_DUPLICATEridge ? -2 : getid_(matchfacet)),
    +           ismatch, hash));
    +      return; /* end of duplicate ridge */
    +    }
    +  }
    +  if (!newfound)
    +    SETelem_(qh->hash_table, scan)= newfacet;  /* same as qh_addhash */
    +  (*hashcount)++;
    +  trace4((qh, qh->ferr, 4053, "qh_matchneighbor: no match for f%d skip %d at hash %d\n",
    +           newfacet->id, newskip, hash));
    +} /* matchneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_matchnewfacets()
    +    match newfacets in qh.newfacet_list to their newfacet neighbors
    +
    +  returns:
    +    qh.newfacet_list with full neighbor sets
    +      get vertices with nth neighbor by deleting nth vertex
    +    if qh.PREmerge/MERGEexact or qh.FORCEoutput
    +      sets facet->flippped if flipped normal (also prevents point partitioning)
    +    if duplicate ridges and qh.PREmerge/MERGEexact
    +      sets facet->dupridge
    +      missing neighbor links identifies extra ridges to be merging (qh_MERGEridge)
    +
    +  notes:
    +    newfacets already have neighbor[0] (horizon facet)
    +    assumes qh.hash_table is NULL
    +    vertex->neighbors has not been updated yet
    +    do not allocate memory after qh.hash_table (need to free it cleanly)
    +
    +  design:
    +    delete neighbor sets for all new facets
    +    initialize a hash table
    +    for all new facets
    +      match facet with neighbors
    +    if unmatched facets (due to duplicate ridges)
    +      for each new facet with a duplicate ridge
    +        match it with a facet
    +    check for flipped facets
    +*/
    +void qh_matchnewfacets(qhT *qh /* qh.newfacet_list */) {
    +  int numnew=0, hashcount=0, newskip;
    +  facetT *newfacet, *neighbor;
    +  int dim= qh->hull_dim, hashsize, neighbor_i, neighbor_n;
    +  setT *neighbors;
    +#ifndef qh_NOtrace
    +  int facet_i, facet_n, numfree= 0;
    +  facetT *facet;
    +#endif
    +
    +  trace1((qh, qh->ferr, 1019, "qh_matchnewfacets: match neighbors for new facets.\n"));
    +  FORALLnew_facets {
    +    numnew++;
    +    {  /* inline qh_setzero(qh, newfacet->neighbors, 1, qh->hull_dim); */
    +      neighbors= newfacet->neighbors;
    +      neighbors->e[neighbors->maxsize].i= dim+1; /*may be overwritten*/
    +      memset((char *)SETelemaddr_(neighbors, 1, void), 0, dim * SETelemsize);
    +    }
    +  }
    +
    +  qh_newhashtable(qh, numnew*(qh->hull_dim-1)); /* twice what is normally needed,
    +                                     but every ridge could be DUPLICATEridge */
    +  hashsize= qh_setsize(qh, qh->hash_table);
    +  FORALLnew_facets {
    +    for (newskip=1; newskiphull_dim; newskip++) /* furthest/horizon already matched */
    +      /* hashsize>0 because hull_dim>1 and numnew>0 */
    +      qh_matchneighbor(qh, newfacet, newskip, hashsize, &hashcount);
    +#if 0   /* use the following to trap hashcount errors */
    +    {
    +      int count= 0, k;
    +      facetT *facet, *neighbor;
    +
    +      count= 0;
    +      FORALLfacet_(qh->newfacet_list) {  /* newfacet already in use */
    +        for (k=1; k < qh->hull_dim; k++) {
    +          neighbor= SETelemt_(facet->neighbors, k, facetT);
    +          if (!neighbor || neighbor == qh_DUPLICATEridge)
    +            count++;
    +        }
    +        if (facet == newfacet)
    +          break;
    +      }
    +      if (count != hashcount) {
    +        qh_fprintf(qh, qh->ferr, 8088, "qh_matchnewfacets: after adding facet %d, hashcount %d != count %d\n",
    +                 newfacet->id, hashcount, count);
    +        qh_errexit(qh, qh_ERRqhull, newfacet, NULL);
    +      }
    +    }
    +#endif  /* end of trap code */
    +  }
    +  if (hashcount) {
    +    FORALLnew_facets {
    +      if (newfacet->dupridge) {
    +        FOREACHneighbor_i_(qh, newfacet) {
    +          if (neighbor == qh_DUPLICATEridge) {
    +            qh_matchduplicates(qh, newfacet, neighbor_i, hashsize, &hashcount);
    +                    /* this may report MERGEfacet */
    +          }
    +        }
    +      }
    +    }
    +  }
    +  if (hashcount) {
    +    qh_fprintf(qh, qh->ferr, 6108, "qhull internal error (qh_matchnewfacets): %d neighbors did not match up\n",
    +        hashcount);
    +    qh_printhashtable(qh, qh->ferr);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +#ifndef qh_NOtrace
    +  if (qh->IStracing >= 2) {
    +    FOREACHfacet_i_(qh, qh->hash_table) {
    +      if (!facet)
    +        numfree++;
    +    }
    +    qh_fprintf(qh, qh->ferr, 8089, "qh_matchnewfacets: %d new facets, %d unused hash entries .  hashsize %d\n",
    +             numnew, numfree, qh_setsize(qh, qh->hash_table));
    +  }
    +#endif /* !qh_NOtrace */
    +  qh_setfree(qh, &qh->hash_table);
    +  if (qh->PREmerge || qh->MERGEexact) {
    +    if (qh->IStracing >= 4)
    +      qh_printfacetlist(qh, qh->newfacet_list, NULL, qh_ALL);
    +    FORALLnew_facets {
    +      if (newfacet->normal)
    +        qh_checkflipped(qh, newfacet, NULL, qh_ALL);
    +    }
    +  }else if (qh->FORCEoutput)
    +    qh_checkflipped_all(qh, qh->newfacet_list);  /* prints warnings for flipped */
    +} /* matchnewfacets */
    +
    +
    +/*---------------------------------
    +
    +  qh_matchvertices(qh, firstindex, verticesA, skipA, verticesB, skipB, same )
    +    tests whether vertices match with a single skip
    +    starts match at firstindex since all new facets have a common vertex
    +
    +  returns:
    +    true if matched vertices
    +    skip index for each set
    +    sets same iff vertices have the same orientation
    +
    +  notes:
    +    assumes skipA is in A and both sets are the same size
    +
    +  design:
    +    set up pointers
    +    scan both sets checking for a match
    +    test orientation
    +*/
    +boolT qh_matchvertices(qhT *qh, int firstindex, setT *verticesA, int skipA,
    +       setT *verticesB, int *skipB, boolT *same) {
    +  vertexT **elemAp, **elemBp, **skipBp=NULL, **skipAp;
    +
    +  elemAp= SETelemaddr_(verticesA, firstindex, vertexT);
    +  elemBp= SETelemaddr_(verticesB, firstindex, vertexT);
    +  skipAp= SETelemaddr_(verticesA, skipA, vertexT);
    +  do if (elemAp != skipAp) {
    +    while (*elemAp != *elemBp++) {
    +      if (skipBp)
    +        return False;
    +      skipBp= elemBp;  /* one extra like FOREACH */
    +    }
    +  }while (*(++elemAp));
    +  if (!skipBp)
    +    skipBp= ++elemBp;
    +  *skipB= SETindex_(verticesB, skipB); /* i.e., skipBp - verticesB */
    +  *same= !((skipA & 0x1) ^ (*skipB & 0x1)); /* result is 0 or 1 */
    +  trace4((qh, qh->ferr, 4054, "qh_matchvertices: matched by skip %d(v%d) and skip %d(v%d) same? %d\n",
    +          skipA, (*skipAp)->id, *skipB, (*(skipBp-1))->id, *same));
    +  return(True);
    +} /* matchvertices */
    +
    +/*---------------------------------
    +
    +  qh_newfacet(qh)
    +    return a new facet
    +
    +  returns:
    +    all fields initialized or cleared   (NULL)
    +    preallocates neighbors set
    +*/
    +facetT *qh_newfacet(qhT *qh) {
    +  facetT *facet;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_(qh, (int)sizeof(facetT), freelistp, facet, facetT);
    +  memset((char *)facet, (size_t)0, sizeof(facetT));
    +  if (qh->facet_id == qh->tracefacet_id)
    +    qh->tracefacet= facet;
    +  facet->id= qh->facet_id++;
    +  facet->neighbors= qh_setnew(qh, qh->hull_dim);
    +#if !qh_COMPUTEfurthest
    +  facet->furthestdist= 0.0;
    +#endif
    +#if qh_MAXoutside
    +  if (qh->FORCEoutput && qh->APPROXhull)
    +    facet->maxoutside= qh->MINoutside;
    +  else
    +    facet->maxoutside= qh->DISTround;
    +#endif
    +  facet->simplicial= True;
    +  facet->good= True;
    +  facet->newfacet= True;
    +  trace4((qh, qh->ferr, 4055, "qh_newfacet: created facet f%d\n", facet->id));
    +  return(facet);
    +} /* newfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_newridge()
    +    return a new ridge
    +*/
    +ridgeT *qh_newridge(qhT *qh) {
    +  ridgeT *ridge;
    +  void **freelistp;   /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_(qh, (int)sizeof(ridgeT), freelistp, ridge, ridgeT);
    +  memset((char *)ridge, (size_t)0, sizeof(ridgeT));
    +  zinc_(Ztotridges);
    +  if (qh->ridge_id == UINT_MAX) {
    +    qh_fprintf(qh, qh->ferr, 7074, "\
    +qhull warning: more than 2^32 ridges.  Qhull results are OK.  Since the ridge ID wraps around to 0, two ridges may have the same identifier.\n");
    +  }
    +  ridge->id= qh->ridge_id++;
    +  trace4((qh, qh->ferr, 4056, "qh_newridge: created ridge r%d\n", ridge->id));
    +  return(ridge);
    +} /* newridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_pointid(qh, point )
    +    return id for a point,
    +    returns qh_IDnone(-3) if null, qh_IDinterior(-2) if interior, or qh_IDunknown(-1) if not known
    +
    +  alternative code if point is in qh.first_point...
    +    unsigned long id;
    +    id= ((unsigned long)point - (unsigned long)qh.first_point)/qh.normal_size;
    +
    +  notes:
    +    Valid points are non-negative
    +    WARN64 -- id truncated to 32-bits, at most 2G points
    +    NOerrors returned (QhullPoint::id)
    +    if point not in point array
    +      the code does a comparison of unrelated pointers.
    +*/
    +int qh_pointid(qhT *qh, pointT *point) {
    +  ptr_intT offset, id;
    +
    +  if (!point || !qh)
    +    return qh_IDnone;
    +  else if (point == qh->interior_point)
    +    return qh_IDinterior;
    +  else if (point >= qh->first_point
    +  && point < qh->first_point + qh->num_points * qh->hull_dim) {
    +    offset= (ptr_intT)(point - qh->first_point);
    +    id= offset / qh->hull_dim;
    +  }else if ((id= qh_setindex(qh->other_points, point)) != -1)
    +    id += qh->num_points;
    +  else
    +    return qh_IDunknown;
    +  return (int)id;
    +} /* pointid */
    +
    +/*---------------------------------
    +
    +  qh_removefacet(qh, facet )
    +    unlinks facet from qh.facet_list,
    +
    +  returns:
    +    updates qh.facet_list .newfacet_list .facet_next visible_list
    +    decrements qh.num_facets
    +
    +  see:
    +    qh_appendfacet
    +*/
    +void qh_removefacet(qhT *qh, facetT *facet) {
    +  facetT *next= facet->next, *previous= facet->previous;
    +
    +  if (facet == qh->newfacet_list)
    +    qh->newfacet_list= next;
    +  if (facet == qh->facet_next)
    +    qh->facet_next= next;
    +  if (facet == qh->visible_list)
    +    qh->visible_list= next;
    +  if (previous) {
    +    previous->next= next;
    +    next->previous= previous;
    +  }else {  /* 1st facet in qh->facet_list */
    +    qh->facet_list= next;
    +    qh->facet_list->previous= NULL;
    +  }
    +  qh->num_facets--;
    +  trace4((qh, qh->ferr, 4057, "qh_removefacet: remove f%d from facet_list\n", facet->id));
    +} /* removefacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_removevertex(qh, vertex )
    +    unlinks vertex from qh.vertex_list,
    +
    +  returns:
    +    updates qh.vertex_list .newvertex_list
    +    decrements qh.num_vertices
    +*/
    +void qh_removevertex(qhT *qh, vertexT *vertex) {
    +  vertexT *next= vertex->next, *previous= vertex->previous;
    +
    +  if (vertex == qh->newvertex_list)
    +    qh->newvertex_list= next;
    +  if (previous) {
    +    previous->next= next;
    +    next->previous= previous;
    +  }else {  /* 1st vertex in qh->vertex_list */
    +    qh->vertex_list= vertex->next;
    +    qh->vertex_list->previous= NULL;
    +  }
    +  qh->num_vertices--;
    +  trace4((qh, qh->ferr, 4058, "qh_removevertex: remove v%d from vertex_list\n", vertex->id));
    +} /* removevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_updatevertices()
    +    update vertex neighbors and delete interior vertices
    +
    +  returns:
    +    if qh.VERTEXneighbors, updates neighbors for each vertex
    +      if qh.newvertex_list,
    +         removes visible neighbors  from vertex neighbors
    +      if qh.newfacet_list
    +         adds new facets to vertex neighbors
    +    if qh.visible_list
    +       interior vertices added to qh.del_vertices for later partitioning
    +
    +  design:
    +    if qh.VERTEXneighbors
    +      deletes references to visible facets from vertex neighbors
    +      appends new facets to the neighbor list for each vertex
    +      checks all vertices of visible facets
    +        removes visible facets from neighbor lists
    +        marks unused vertices for deletion
    +*/
    +void qh_updatevertices(qhT *qh /*qh.newvertex_list, newfacet_list, visible_list*/) {
    +  facetT *newfacet= NULL, *neighbor, **neighborp, *visible;
    +  vertexT *vertex, **vertexp;
    +
    +  trace3((qh, qh->ferr, 3013, "qh_updatevertices: delete interior vertices and update vertex->neighbors\n"));
    +  if (qh->VERTEXneighbors) {
    +    FORALLvertex_(qh->newvertex_list) {
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visible)
    +          SETref_(neighbor)= NULL;
    +      }
    +      qh_setcompact(qh, vertex->neighbors);
    +    }
    +    FORALLnew_facets {
    +      FOREACHvertex_(newfacet->vertices)
    +        qh_setappend(qh, &vertex->neighbors, newfacet);
    +    }
    +    FORALLvisible_facets {
    +      FOREACHvertex_(visible->vertices) {
    +        if (!vertex->newlist && !vertex->deleted) {
    +          FOREACHneighbor_(vertex) { /* this can happen under merging */
    +            if (!neighbor->visible)
    +              break;
    +          }
    +          if (neighbor)
    +            qh_setdel(vertex->neighbors, visible);
    +          else {
    +            vertex->deleted= True;
    +            qh_setappend(qh, &qh->del_vertices, vertex);
    +            trace2((qh, qh->ferr, 2041, "qh_updatevertices: delete vertex p%d(v%d) in f%d\n",
    +                  qh_pointid(qh, vertex->point), vertex->id, visible->id));
    +          }
    +        }
    +      }
    +    }
    +  }else {  /* !VERTEXneighbors */
    +    FORALLvisible_facets {
    +      FOREACHvertex_(visible->vertices) {
    +        if (!vertex->newlist && !vertex->deleted) {
    +          vertex->deleted= True;
    +          qh_setappend(qh, &qh->del_vertices, vertex);
    +          trace2((qh, qh->ferr, 2042, "qh_updatevertices: delete vertex p%d(v%d) in f%d\n",
    +                  qh_pointid(qh, vertex->point), vertex->id, visible->id));
    +        }
    +      }
    +    }
    +  }
    +} /* updatevertices */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/poly_r.h b/xs/src/qhull/src/libqhull_r/poly_r.h
    new file mode 100644
    index 0000000000..c71511bd69
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/poly_r.h
    @@ -0,0 +1,303 @@
    +/*
      ---------------------------------
    +
    +   poly_r.h
    +   header file for poly_r.c and poly2_r.c
    +
    +   see qh-poly_r.htm, libqhull_r.h and poly_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/poly_r.h#5 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFpoly
    +#define qhDEFpoly 1
    +
    +#include "libqhull_r.h"
    +
    +/*===============   constants ========================== */
    +
    +/*----------------------------------
    +
    +  ALGORITHMfault
    +    use as argument to checkconvex() to report errors during buildhull
    +*/
    +#define qh_ALGORITHMfault 0
    +
    +/*----------------------------------
    +
    +  DATAfault
    +    use as argument to checkconvex() to report errors during initialhull
    +*/
    +#define qh_DATAfault 1
    +
    +/*----------------------------------
    +
    +  DUPLICATEridge
    +    special value for facet->neighbor to indicate a duplicate ridge
    +
    +  notes:
    +    set by matchneighbor, used by matchmatch and mark_dupridge
    +*/
    +#define qh_DUPLICATEridge (facetT *)1L
    +
    +/*----------------------------------
    +
    +  MERGEridge       flag in facet
    +    special value for facet->neighbor to indicate a merged ridge
    +
    +  notes:
    +    set by matchneighbor, used by matchmatch and mark_dupridge
    +*/
    +#define qh_MERGEridge (facetT *)2L
    +
    +
    +/*============ -structures- ====================*/
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  FORALLfacet_( facetlist ) { ... }
    +    assign 'facet' to each facet in facetlist
    +
    +  notes:
    +    uses 'facetT *facet;'
    +    assumes last facet is a sentinel
    +
    +  see:
    +    FORALLfacets
    +*/
    +#define FORALLfacet_( facetlist ) if (facetlist ) for ( facet=( facetlist ); facet && facet->next; facet= facet->next )
    +
    +/*----------------------------------
    +
    +  FORALLnew_facets { ... }
    +    assign 'newfacet' to each facet in qh.newfacet_list
    +
    +  notes:
    +    uses 'facetT *newfacet;'
    +    at exit, newfacet==NULL
    +*/
    +#define FORALLnew_facets for ( newfacet=qh->newfacet_list;newfacet && newfacet->next;newfacet=newfacet->next )
    +
    +/*----------------------------------
    +
    +  FORALLvertex_( vertexlist ) { ... }
    +    assign 'vertex' to each vertex in vertexlist
    +
    +  notes:
    +    uses 'vertexT *vertex;'
    +    at exit, vertex==NULL
    +*/
    +#define FORALLvertex_( vertexlist ) for (vertex=( vertexlist );vertex && vertex->next;vertex= vertex->next )
    +
    +/*----------------------------------
    +
    +  FORALLvisible_facets { ... }
    +    assign 'visible' to each visible facet in qh.visible_list
    +
    +  notes:
    +    uses 'vacetT *visible;'
    +    at exit, visible==NULL
    +*/
    +#define FORALLvisible_facets for (visible=qh->visible_list; visible && visible->visible; visible= visible->next)
    +
    +/*----------------------------------
    +
    +  FORALLsame_( newfacet ) { ... }
    +    assign 'same' to each facet in newfacet->f.samecycle
    +
    +  notes:
    +    uses 'facetT *same;'
    +    stops when it returns to newfacet
    +*/
    +#define FORALLsame_(newfacet) for (same= newfacet->f.samecycle; same != newfacet; same= same->f.samecycle)
    +
    +/*----------------------------------
    +
    +  FORALLsame_cycle_( newfacet ) { ... }
    +    assign 'same' to each facet in newfacet->f.samecycle
    +
    +  notes:
    +    uses 'facetT *same;'
    +    at exit, same == NULL
    +*/
    +#define FORALLsame_cycle_(newfacet) \
    +     for (same= newfacet->f.samecycle; \
    +         same; same= (same == newfacet ?  NULL : same->f.samecycle))
    +
    +/*----------------------------------
    +
    +  FOREACHneighborA_( facet ) { ... }
    +    assign 'neighborA' to each neighbor in facet->neighbors
    +
    +  FOREACHneighborA_( vertex ) { ... }
    +    assign 'neighborA' to each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighborA, **neighborAp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHneighborA_(facet)  FOREACHsetelement_(facetT, facet->neighbors, neighborA)
    +
    +/*----------------------------------
    +
    +  FOREACHvisible_( facets ) { ... }
    +    assign 'visible' to each facet in facets
    +
    +  notes:
    +    uses 'facetT *facet, *facetp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvisible_(facets) FOREACHsetelement_(facetT, facets, visible)
    +
    +/*----------------------------------
    +
    +  FOREACHnewfacet_( facets ) { ... }
    +    assign 'newfacet' to each facet in facets
    +
    +  notes:
    +    uses 'facetT *newfacet, *newfacetp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHnewfacet_(facets) FOREACHsetelement_(facetT, facets, newfacet)
    +
    +/*----------------------------------
    +
    +  FOREACHvertexA_( vertices ) { ... }
    +    assign 'vertexA' to each vertex in vertices
    +
    +  notes:
    +    uses 'vertexT *vertexA, *vertexAp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvertexA_(vertices) FOREACHsetelement_(vertexT, vertices, vertexA)
    +
    +/*----------------------------------
    +
    +  FOREACHvertexreverse12_( vertices ) { ... }
    +    assign 'vertex' to each vertex in vertices
    +    reverse order of first two vertices
    +
    +  notes:
    +    uses 'vertexT *vertex, *vertexp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvertexreverse12_(vertices) FOREACHsetelementreverse12_(vertexT, vertices, vertex)
    +
    +
    +/*=============== prototypes poly_r.c in alphabetical order ================*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_appendfacet(qhT *qh, facetT *facet);
    +void    qh_appendvertex(qhT *qh, vertexT *vertex);
    +void    qh_attachnewfacets(qhT *qh /* qh.visible_list, qh.newfacet_list */);
    +boolT   qh_checkflipped(qhT *qh, facetT *facet, realT *dist, boolT allerror);
    +void    qh_delfacet(qhT *qh, facetT *facet);
    +void    qh_deletevisible(qhT *qh /* qh.visible_list, qh.horizon_list */);
    +setT   *qh_facetintersect(qhT *qh, facetT *facetA, facetT *facetB, int *skipAp,int *skipBp, int extra);
    +int     qh_gethash(qhT *qh, int hashsize, setT *set, int size, int firstindex, void *skipelem);
    +facetT *qh_makenewfacet(qhT *qh, setT *vertices, boolT toporient, facetT *facet);
    +void    qh_makenewplanes(qhT *qh /* qh.newfacet_list */);
    +facetT *qh_makenew_nonsimplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew);
    +facetT *qh_makenew_simplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew);
    +void    qh_matchneighbor(qhT *qh, facetT *newfacet, int newskip, int hashsize,
    +                          int *hashcount);
    +void    qh_matchnewfacets(qhT *qh);
    +boolT   qh_matchvertices(qhT *qh, int firstindex, setT *verticesA, int skipA,
    +                          setT *verticesB, int *skipB, boolT *same);
    +facetT *qh_newfacet(qhT *qh);
    +ridgeT *qh_newridge(qhT *qh);
    +int     qh_pointid(qhT *qh, pointT *point);
    +void    qh_removefacet(qhT *qh, facetT *facet);
    +void    qh_removevertex(qhT *qh, vertexT *vertex);
    +void    qh_updatevertices(qhT *qh);
    +
    +
    +/*========== -prototypes poly2_r.c in alphabetical order ===========*/
    +
    +void    qh_addhash(void *newelem, setT *hashtable, int hashsize, int hash);
    +void    qh_check_bestdist(qhT *qh);
    +void    qh_check_dupridge(qhT *qh, facetT *facet1, realT dist1, facetT *facet2, realT dist2);
    +void    qh_check_maxout(qhT *qh);
    +void    qh_check_output(qhT *qh);
    +void    qh_check_point(qhT *qh, pointT *point, facetT *facet, realT *maxoutside, realT *maxdist, facetT **errfacet1, facetT **errfacet2);
    +void    qh_check_points(qhT *qh);
    +void    qh_checkconvex(qhT *qh, facetT *facetlist, int fault);
    +void    qh_checkfacet(qhT *qh, facetT *facet, boolT newmerge, boolT *waserrorp);
    +void    qh_checkflipped_all(qhT *qh, facetT *facetlist);
    +void    qh_checkpolygon(qhT *qh, facetT *facetlist);
    +void    qh_checkvertex(qhT *qh, vertexT *vertex);
    +void    qh_clearcenters(qhT *qh, qh_CENTER type);
    +void    qh_createsimplex(qhT *qh, setT *vertices);
    +void    qh_delridge(qhT *qh, ridgeT *ridge);
    +void    qh_delvertex(qhT *qh, vertexT *vertex);
    +setT   *qh_facet3vertex(qhT *qh, facetT *facet);
    +facetT *qh_findbestfacet(qhT *qh, pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside);
    +facetT *qh_findbestlower(qhT *qh, facetT *upperfacet, pointT *point, realT *bestdistp, int *numpart);
    +facetT *qh_findfacet_all(qhT *qh, pointT *point, realT *bestdist, boolT *isoutside,
    +                          int *numpart);
    +int     qh_findgood(qhT *qh, facetT *facetlist, int goodhorizon);
    +void    qh_findgood_all(qhT *qh, facetT *facetlist);
    +void    qh_furthestnext(qhT *qh /* qh.facet_list */);
    +void    qh_furthestout(qhT *qh, facetT *facet);
    +void    qh_infiniteloop(qhT *qh, facetT *facet);
    +void    qh_initbuild(qhT *qh);
    +void    qh_initialhull(qhT *qh, setT *vertices);
    +setT   *qh_initialvertices(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints);
    +vertexT *qh_isvertex(pointT *point, setT *vertices);
    +vertexT *qh_makenewfacets(qhT *qh, pointT *point /*horizon_list, visible_list*/);
    +void    qh_matchduplicates(qhT *qh, facetT *atfacet, int atskip, int hashsize, int *hashcount);
    +void    qh_nearcoplanar(qhT *qh /* qh.facet_list */);
    +vertexT *qh_nearvertex(qhT *qh, facetT *facet, pointT *point, realT *bestdistp);
    +int     qh_newhashtable(qhT *qh, int newsize);
    +vertexT *qh_newvertex(qhT *qh, pointT *point);
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp);
    +void    qh_outcoplanar(qhT *qh /* qh.facet_list */);
    +pointT *qh_point(qhT *qh, int id);
    +void    qh_point_add(qhT *qh, setT *set, pointT *point, void *elem);
    +setT   *qh_pointfacet(qhT *qh /*qh.facet_list*/);
    +setT   *qh_pointvertex(qhT *qh /*qh.facet_list*/);
    +void    qh_prependfacet(qhT *qh, facetT *facet, facetT **facetlist);
    +void    qh_printhashtable(qhT *qh, FILE *fp);
    +void    qh_printlists(qhT *qh);
    +void    qh_resetlists(qhT *qh, boolT stats, boolT resetVisible /*qh.newvertex_list qh.newfacet_list qh.visible_list*/);
    +void    qh_setvoronoi_all(qhT *qh);
    +void    qh_triangulate(qhT *qh /*qh.facet_list*/);
    +void    qh_triangulate_facet(qhT *qh, facetT *facetA, vertexT **first_vertex);
    +void    qh_triangulate_link(qhT *qh, facetT *oldfacetA, facetT *facetA, facetT *oldfacetB, facetT *facetB);
    +void    qh_triangulate_mirror(qhT *qh, facetT *facetA, facetT *facetB);
    +void    qh_triangulate_null(qhT *qh, facetT *facetA);
    +void    qh_vertexintersect(qhT *qh, setT **vertexsetA,setT *vertexsetB);
    +setT   *qh_vertexintersect_new(qhT *qh, setT *vertexsetA,setT *vertexsetB);
    +void    qh_vertexneighbors(qhT *qh /*qh.facet_list*/);
    +boolT   qh_vertexsubset(setT *vertexsetA, setT *vertexsetB);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFpoly */
    diff --git a/xs/src/qhull/src/libqhull_r/qh-geom_r.htm b/xs/src/qhull/src/libqhull_r/qh-geom_r.htm
    new file mode 100644
    index 0000000000..eeefc0c758
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/qh-geom_r.htm
    @@ -0,0 +1,295 @@
    +
    +
    +
    +
    +geom_r.c, geom2_r.c -- geometric and floating point routines
    +
    +
    +
    +
    +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + + +

    geom_r.c, geom2_r.c, random_r.c -- geometric and floating point routines

    +
    +

    Geometrically, a vertex is a point with d coordinates +and a facet is a halfspace. A halfspace is defined by an +oriented hyperplane through the facet's vertices. A hyperplane +is defined by d normalized coefficients and an offset. A +point is above a facet if its distance to the facet is +positive.

    + +

    Qhull uses floating point coordinates for input points, +vertices, halfspace equations, centrums, and an interior point.

    + +

    Qhull may be configured for single precision or double +precision floating point arithmetic (see realT +).

    + +

    Each floating point operation may incur round-off error (see +Merge). The maximum error for distance +computations is determined at initialization. The roundoff error +in halfspace computation is accounted for by computing the +distance from vertices to the halfspace.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly • +QhullSet • +StatUser

    + +

    Index to geom_r.c, +geom2_r.c, geom_r.h, +random_r.c, random_r.h +

    + + + +

    »geometric data types +and constants

    + +
      +
    • coordT coordinates and +coefficients are stored as realT
    • +
    • pointT a point is an array +of DIM3 coordinates
    • +
    + +

    »mathematical macros

    + +
      +
    • fabs_ returns the absolute +value of a
    • +
    • fmax_ returns the maximum +value of a and b
    • +
    • fmin_ returns the minimum +value of a and b
    • +
    • maximize_ maximize a value +
    • +
    • minimize_ minimize a value +
    • +
    • det2_ compute a 2-d +determinate
    • +
    • det3_ compute a 3-d +determinate
    • +
    • dX, dY, dZ compute the difference +between two coordinates
    • +
    + +

    »mathematical functions

    + + + +

    »computational geometry functions

    + + + +

    »point array functions

    + + +

    »geometric facet functions

    + + +

    »geometric roundoff functions

    +
      +
    • qh_detjoggle determine +default joggle for points and distance roundoff error
    • +
    • qh_detroundoff +determine maximum roundoff error and other precision constants
    • +
    • qh_distround compute +maximum roundoff error due to a distance computation to a +normalized hyperplane
    • +
    • qh_divzero divide by a +number that is nearly zero
    • +
    • qh_maxouter return maximum outer +plane
    • +
    • qh_outerinner return actual +outer and inner planes +
    + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    + + +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-globa_r.htm b/xs/src/qhull/src/libqhull_r/qh-globa_r.htm new file mode 100644 index 0000000000..45437a0597 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-globa_r.htm @@ -0,0 +1,163 @@ + + + + +global_r.c -- global variables and their functions + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + + +

    global_r.c -- global variables and their functions

    +
    +

    Qhull uses a data structure, qhT, to store +globally defined constants, lists, sets, and variables. It is passed as the +first argument to most functions. +

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly • +QhullSet • +StatUser

    + +

    Index to global_r.c and +libqhull_r.h

    + + + +

    »Qhull's global +variables

    + + + +

    »Global variable and +initialization routines

    + + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-io_r.htm b/xs/src/qhull/src/libqhull_r/qh-io_r.htm new file mode 100644 index 0000000000..8a8a96300f --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-io_r.htm @@ -0,0 +1,305 @@ + + + + +io_r.c -- input and output operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    io_r.c -- input and output operations

    +
    + +

    Qhull provides a wide range of input +and output options. To organize the code, most output formats use +the same driver:

    + +
    +    qh_printbegin( fp, format, facetlist, facets, printall );
    +
    +    FORALLfacet_( facetlist )
    +      qh_printafacet( fp, format, facet, printall );
    +
    +    FOREACHfacet_( facets )
    +      qh_printafacet( fp, format, facet, printall );
    +
    +    qh_printend( fp, format );
    +
    + +

    Note the 'printall' flag. It selects whether or not +qh_skipfacet() is tested.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom +GlobalIo • +MemMerge • +PolyQhull • +SetStat • +User

    + +

    Index to io_r.c and io_r.h

    + + + +

    »io_r.h constants and types

    + +
      +
    • qh_MAXfirst maximum length +of first two lines of stdin
    • +
    • qh_WHITESPACE possible +values of white space
    • +
    • printvridgeT function to +print results of qh_printvdiagram or qh_eachvoronoi
    • +
    + +

    »User level functions

    + + + +

    »Print functions for all +output formats

    + + + +

    »Text output functions

    + + +

    »Text utility functions

    + + +

    »Geomview output functions

    + +

    »Geomview utility functions

    + +

    +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-mem_r.htm b/xs/src/qhull/src/libqhull_r/qh-mem_r.htm new file mode 100644 index 0000000000..db59119cb9 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-mem_r.htm @@ -0,0 +1,145 @@ + + + + +mem_r.c -- memory operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    mem_r.c -- memory operations

    +
    +

    Qhull uses quick-fit memory allocation. It maintains a +set of free lists for a variety of small allocations. A +small request returns a block from the best fitting free +list. If the free list is empty, Qhull allocates a block +from a reserved buffer.

    +

    Use 'T5' to trace memory allocations.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to mem_r.c and +mem_r.h

    + +

    »mem_r.h data types and constants

    +
      +
    • ptr_intT for casting +a void* to an integer-type
    • +
    • qhmemT global memory +structure for mem_r.c
    • +
    • qh_NOmem disable memory allocation
    • +
    +

    »mem_r.h macros

    + +

    »User level +functions

    + + +

    »Initialization and +termination functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-merge_r.htm b/xs/src/qhull/src/libqhull_r/qh-merge_r.htm new file mode 100644 index 0000000000..63e5135be1 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-merge_r.htm @@ -0,0 +1,366 @@ + + + + +merge_r.c -- facet merge operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    merge_r.c -- facet merge operations

    +
    +

    Qhull handles precision problems by merged facets or joggled input. +Except for redundant vertices, it corrects a problem by +merging two facets. When done, all facets are clearly +convex. See Imprecision in Qhull +for further information.

    +

    Users may joggle the input ('QJn') +instead of merging facets.

    +

    Qhull detects and corrects the following problems:

    +
      +
    • More than two facets meeting at a ridge. When +Qhull creates facets, it creates an even number +of facets for each ridge. A convex hull always +has two facets for each ridge. More than two +facets may be created if non-adjacent facets +share a vertex. This is called a duplicate +ridge. In 2-d, a duplicate ridge would +create a loop of facets.
    • +
    +
      +
    • A facet contained in another facet. Facet +merging may leave all vertices of one facet as a +subset of the vertices of another facet. This is +called a redundant facet.
    • +
    +
      +
    • A facet with fewer than three neighbors. Facet +merging may leave a facet with one or two +neighbors. This is called a degenerate facet. +
    • +
    +
      +
    • A facet with flipped orientation. A +facet's hyperplane may define a halfspace that +does not include the interior point.This is +called a flipped facet.
    • +
    +
      +
    • A coplanar horizon facet. A +newly processed point may be coplanar with an +horizon facet. Qhull creates a new facet without +a hyperplane. It links new facets for the same +horizon facet together. This is called a samecycle. +The new facet or samecycle is merged into the +horizon facet.
    • +
    +
      +
    • Concave facets. A facet's centrum may be +above a neighboring facet. If so, the facets meet +at a concave angle.
    • +
    +
      +
    • Coplanar facets. A facet's centrum may be +coplanar with a neighboring facet (i.e., it is +neither clearly below nor clearly above the +facet's hyperplane). Qhull removes coplanar +facets in independent sets sorted by angle.
    • +
    +
      +
    • Redundant vertex. A vertex may have fewer +than three neighboring facets. If so, it is +redundant and may be renamed to an adjacent +vertex without changing the topological +structure.This is called a redundant vertex. +
    • +
    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to merge_r.c and +merge_r.h

    + + +

    »merge_r.h data +types, macros, and global sets

    +
      +
    • mergeT structure to +identify a merge of two facets
    • +
    • FOREACHmerge_ +assign 'merge' to each merge in merges
    • +
    • qh global sets +qh.facet_mergeset contains non-convex merges +while qh.degen_mergeset contains degenerate and +redundant facets
    • +
    +

    »merge_r.h +constants

    + +

    »top-level merge +functions

    + + +

    »functions for +identifying merges

    + + +

    »functions for +determining the best merge

    + + +

    »functions for +merging facets

    + + +

    »functions for +merging a cycle of facets

    +

    If a point is coplanar with an horizon facet, the +corresponding new facets are linked together (a samecycle) +for merging.

    + +

    »functions +for renaming a vertex

    + + +

    »functions +for identifying vertices for renaming

    + + +

    »functions for check and +trace

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-poly_r.htm b/xs/src/qhull/src/libqhull_r/qh-poly_r.htm new file mode 100644 index 0000000000..c5b6f2f836 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-poly_r.htm @@ -0,0 +1,485 @@ + + + + +poly_r.c, poly2_r.c -- polyhedron operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    poly_r.c, poly2_r.c -- polyhedron operations

    +
    + +

    Qhull uses dimension-free terminology. Qhull builds a +polyhedron in dimension d. A polyhedron is a +simplicial complex of faces with geometric information for the +top and bottom-level faces. A (d-1)-face is a facet, +a (d-2)-face is a ridge, and a 0-face +is a vertex. For example in 3-d, a facet is a polygon +and a ridge is an edge. A facet is built from a ridge (the base) +and a vertex (the apex). See +Qhull's data structures.

    + +

    Qhull's primary data structure is a polyhedron. A +polyhedron is a list of facets. Each facet has a set of +neighboring facets and a set of vertices. Each facet has a +hyperplane. For example, a tetrahedron has four facets. +If its vertices are a, b, c, d, and its facets +are 1, 2, 3, 4, the tetrahedron is

    +
    +
      +
    • facet 1
        +
      • vertices: b c d
      • +
      • neighbors: 2 3 4
      • +
      +
    • +
    • facet 2
        +
      • vertices: a c d
      • +
      • neighbors: 1 3 4
      • +
      +
    • +
    • facet 3
        +
      • vertices: a b d
      • +
      • neighbors: 1 2 4
      • +
      +
    • +
    • facet 4
        +
      • vertices: a b c
      • +
      • neighbors: 1 2 3
      • +
      +
    • +
    +
    +

    A facet may be simplicial or non-simplicial. In 3-d, a +simplicial facet has three vertices and three +neighbors. A nonsimplicial facet has more than +three vertices and more than three neighbors. A +nonsimplicial facet has a set of ridges and a centrum.

    +

    +A simplicial facet has an orientation. An orientation +is either top or bottom. +The flag, facet->toporient, +defines the orientation of the facet's vertices. For example in 3-d, +'top' is left-handed orientation (i.e., the vertex order follows the direction +of the left-hand fingers when the thumb is pointing away from the center). +Except for axis-parallel facets in 5-d and higher, topological orientation +determines the geometric orientation of the facet's hyperplane. + +

    A nonsimplicial facet is due to merging two or more +facets. The facet's ridge set determine a simplicial +decomposition of the facet. Each ridge is a 1-face (i.e., +it has two vertices and two neighboring facets). The +orientation of a ridge is determined by the order of the +neighboring facets. The flag, facet->toporient,is +ignored.

    +

    A nonsimplicial facet has a centrum for testing +convexity. A centrum is a point on the facet's +hyperplane that is near the center of the facet. Except +for large facets, it is the arithmetic average of the +facet's vertices.

    +

    A nonsimplicial facet is an approximation that is +defined by offsets from the facet's hyperplane. When +Qhull finishes, the outer plane is above all +points while the inner plane is below the facet's +vertices. This guarantees that any exact convex hull +passes between the inner and outer planes. The outer +plane is defined by facet->maxoutside while +the inner plane is computed from the facet's vertices.

    + +

    Qhull 3.1 includes triangulation of non-simplicial facets +('Qt'). +These facets, +called tricoplanar, share the same normal. centrum, and Voronoi center. +One facet (keepcentrum) owns these data structures. +While tricoplanar facets are more accurate than the simplicial facets from +joggled input, they +may have zero area or flipped orientation. + +

    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to poly_r.c, +poly2_r.c, poly_r.h, +and libqhull_r.h

    + +

    »Data +types and global lists for polyhedrons

    + +

    »poly_r.h constants

    +
      +
    • ALGORITHMfault +flag to not report errors in qh_checkconvex()
    • +
    • DATAfault flag to +report errors in qh_checkconvex()
    • +
    • DUPLICATEridge +special value for facet->neighbor to indicate +a duplicate ridge
    • +
    • MERGEridge +special value for facet->neighbor to indicate +a merged ridge
    • +
    +

    »Global FORALL +macros

    + +

    »FORALL macros

    + +

    »FOREACH macros

    + +

    »Indexed +FOREACH macros

    +
      +
    • FOREACHfacet_i_ +assign 'facet' and 'facet_i' to each facet in +facet set
    • +
    • FOREACHneighbor_i_ +assign 'neighbor' and 'neighbor_i' to each facet +in facet->neighbors or vertex->neighbors
    • +
    • FOREACHpoint_i_ +assign 'point' and 'point_i' to each point in +points set
    • +
    • FOREACHridge_i_ +assign 'ridge' and 'ridge_i' to each ridge in +ridges set
    • +
    • FOREACHvertex_i_ +assign 'vertex' and 'vertex_i' to each vertex in +vertices set
    • +
    • FOREACHvertexreverse12_ +assign 'vertex' to each vertex in vertex set; +reverse the order of first two vertices
    • +
    +

    »Other macros for polyhedrons

    +
      +
    • getid_ return ID for +a facet, ridge, or vertex
    • +
    • otherfacet_ +return neighboring facet for a ridge in a facet
    • +
    +

    »Facetlist +functions

    + +

    »Facet +functions

    + +

    »Vertex, +ridge, and point functions

    + +

    »Hashtable functions

    + +

    »Allocation and +deallocation functions

    + +

    »Check +functions

    + + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-qhull_r.htm b/xs/src/qhull/src/libqhull_r/qh-qhull_r.htm new file mode 100644 index 0000000000..25d5e49722 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-qhull_r.htm @@ -0,0 +1,279 @@ + + + + +libqhull_r.c -- top-level functions and basic data types + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    libqhull_r.c -- top-level functions and basic data types

    +
    +

    Qhull implements the Quickhull algorithm for computing +the convex hull. The Quickhull algorithm combines two +well-known algorithms: the 2-d quickhull algorithm and +the n-d beneath-beyond algorithm. See +Description of Qhull.

    +

    This section provides an index to the top-level +functions and base data types. The top-level header file, libqhull_r.h, +contains prototypes for these functions.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to libqhull_r.c, +libqhull_r.h, and +unix_r.c

    + + +

    »libqhull_r.h and unix_r.c +data types and constants

    +
      +
    • flagT Boolean flag as +a bit
    • +
    • boolT boolean value, +either True or False
    • +
    • CENTERtype to +distinguish facet->center
    • +
    • qh_PRINT output +formats for printing (qh.PRINTout)
    • +
    • qh_ALL argument flag +for selecting everything
    • +
    • qh_ERR Qhull exit +codes for indicating errors
    • +
    • qh_FILEstderr Fake stderr +to distinguish error output from normal output [C++ only]
    • +
    • qh_prompt version and long prompt for Qhull
    • +
    • qh_prompt2 synopsis for Qhull
    • +
    • qh_prompt3 concise prompt for Qhull
    • +
    • qh_version version stamp
    • +
    + +

    »libqhull_r.h other +macros

    +
      +
    • traceN print trace +message if qh.IStracing >= N.
    • +
    • QHULL_UNUSED declare an + unused variable to avoid warnings.
    • +
    + +

    »Quickhull +routines in call order

    + + +

    »Top-level routines for initializing and terminating Qhull (in other modules)

    + + +

    »Top-level routines for reading and modifying the input (in other modules)

    + + +

    »Top-level routines for calling Qhull (in other modules)

    + + +

    »Top-level routines for returning results (in other modules)

    + + +

    »Top-level routines for testing and debugging (in other modules)

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-set_r.htm b/xs/src/qhull/src/libqhull_r/qh-set_r.htm new file mode 100644 index 0000000000..cf8ab63af9 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-set_r.htm @@ -0,0 +1,308 @@ + + + + +qset_r.c -- set data type and operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    qset_r.c -- set data type and operations

    +
    +

    Qhull's data structures are constructed from sets. The +functions and macros in qset_r.c construct, iterate, and +modify these sets. They are the most frequently called +functions in Qhull. For this reason, efficiency is the +primary concern.

    +

    In Qhull, a set is represented by an unordered +array of pointers with a maximum size and a NULL +terminator (setT). +Most sets correspond to mathematical sets +(i.e., the pointers are unique). Some sets are sorted to +enforce uniqueness. Some sets are ordered. For example, +the order of vertices in a ridge determine the ridge's +orientation. If you reverse the order of adjacent +vertices, the orientation reverses. Some sets are not +mathematical sets. They may be indexed as an array and +they may include NULL pointers.

    +

    The most common operation on a set is to iterate its +members. This is done with a 'FOREACH...' macro. Each set +has a custom macro. For example, 'FOREACHvertex_' +iterates over a set of vertices. Each vertex is assigned +to the variable 'vertex' from the pointer 'vertexp'.

    +

    Most sets are constructed by appending elements to the +set. The last element of a set is either NULL or the +index of the terminating NULL for a partially full set. +If a set is full, appending an element copies the set to +a larger array.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly +• QhullSet +• StatUser +

    +

    Index to qset_r.c and +qset_r.h

    + +

    »Data types and +constants

    +
      +
    • SETelemsize size +of a set element in bytes
    • +
    • setT a set with a +maximum size and a current size
    • +
    • qh global sets +global sets for temporary sets, etc.
    • +
    +

    »FOREACH macros

    + +

    »Access and +size macros

    + +

    »Internal macros

    +
      +
    • SETsizeaddr_ +return pointer to end element of a set (indicates +current size)
    • +
    + +

    »address macros

    +
      +
    • SETaddr_ return +address of a set's elements
    • +
    • SETelemaddr_ +return address of the n'th element of a set
    • +
    • SETref_ l_r.h.s. for +modifying the current element in a FOREACH +iteration
    • +
    + +

    »Allocation and +deallocation functions

    + + +

    »Access and +predicate functions

    + + +

    »Add functions

    + + +

    »Check and print functions

    + + +

    »Copy, compact, and zero functions

    + + +

    »Delete functions

    + + +

    »Temporary set functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-stat_r.htm b/xs/src/qhull/src/libqhull_r/qh-stat_r.htm new file mode 100644 index 0000000000..ea9d7fc565 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-stat_r.htm @@ -0,0 +1,161 @@ + + + + +stat_r.c -- statistical operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    stat_r.c -- statistical operations

    +
    +

    Qhull records many statistics. These functions and +macros make it inexpensive to add a statistic. +

    As with Qhull's global variables, the statistics data structure is +accessed by a macro, 'qhstat'. If qh_QHpointer is defined, the macro +is 'qh_qhstat->', otherwise the macro is 'qh_qhstat.'. +Statistics +may be turned off in user_r.h. If so, all but the 'zz' +statistics are ignored.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to stat_r.c and +stat_r.h

    + + +

    »stat_r.h types

    +
      +
    • intrealT union of +integer and real
    • +
    • qhstat global data +structure for statistics
    • +
    +

    »stat_r.h +constants

    +
      +
    • qh_KEEPstatistics 0 turns off most statistics
    • +
    • Z..., W... integer (Z) and real (W) statistics +
    • +
    • ZZstat Z.../W... statistics that +remain defined if qh_KEEPstatistics=0 +
    • +
    • ztype zdoc, zinc, etc. +for definining statistics
    • +
    +

    »stat_r.h macros

    +
      +
    • MAYdebugx called +frequently for error trapping
    • +
    • zadd_/wadd_ add value +to an integer or real statistic
    • +
    • zdef_ define a +statistic
    • +
    • zinc_ increment an +integer statistic
    • +
    • zmax_/wmax_ update +integer or real maximum statistic
    • +
    • zmin_/wmin_ update +integer or real minimum statistic
    • +
    • zval_/wval_ set or +return value of a statistic
    • +
    + +

    »stat_r.c +functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-user_r.htm b/xs/src/qhull/src/libqhull_r/qh-user_r.htm new file mode 100644 index 0000000000..909fec6564 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-user_r.htm @@ -0,0 +1,271 @@ + + + + +user_r.c -- user-definable operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    +

    user_r.c -- user-definable operations

    +
    +

    This section contains functions and constants that the +user may want to change.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to user_r.c, usermem_r.c, userprintf_r.c, userprintf_rbox_r.c and +user_r.h

    + + +

    »Qhull library constants

    + + + +

    »user_r.h data +types and configuration macros

    + + +

    »definition constants

    +
      +
    • qh_DEFAULTbox +define default box size for rbox, 'Qbb', and 'QbB' (Geomview expects 0.5)
    • +
    • qh_INFINITE on +output, indicates Voronoi center at infinity
    • +
    • qh_ORIENTclock +define convention for orienting facets
    • +
    • qh_ZEROdelaunay +define facets that are ignored in Delaunay triangulations
    • +
    + +

    »joggle constants

    + + +

    »performance +related constants

    + + +

    »memory constants

    + + +

    »conditional compilation

    +
      +
    • compiler defined symbols, +e.g., _STDC_ and _cplusplus + +
    • qh_COMPUTEfurthest + compute furthest distance to an outside point instead of storing it with the facet +
    • qh_KEEPstatistics + enable statistic gathering and reporting with option 'Ts' +
    • qh_MAXoutside +record outer plane for each facet +
    • qh_NOmerge +disable facet merging +
    • qh_NOtrace +disable tracing with option 'T4' +
    • qh_QHpointer +access global data with pointer or static structure +
    • qh_QUICKhelp +use abbreviated help messages, e.g., for degenerate inputs +
    + +

    »merge +constants

    + + +

    »user_r.c +functions

    + + +

    »usermem_r.c +functions

    +
      +
    • qh_exit exit program, same as exit(). May be redefined as throw "QH10003.." by libqhullcpp/usermem_r-cpp.cpp
    • +
    • qh_fprintf_stderr print to stderr when qh->ferr is not defined.
    • +
    • qh_free free memory, same as free().
    • +
    • qh_malloc allocate memory, same as malloc()
    • +
    + +

    »userprintf_r.c + and userprintf_rbox,c functions

    +
      +
    • qh_fprintf print +information from Qhull, sames as fprintf().
    • +
    • qh_fprintf_rbox print +information from Rbox, sames as fprintf().
    • +
    + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qhull_r-exports.def b/xs/src/qhull/src/libqhull_r/qhull_r-exports.def new file mode 100644 index 0000000000..325d57c3b8 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qhull_r-exports.def @@ -0,0 +1,404 @@ +; qhull_r-exports.def -- msvc module-definition file +; +; Generated from depends.exe by cut-and-paste of exported symbols by mingw gcc +; [jan'14] 391 symbols +; Same as ../libqhullp/qhull-exports.def without DATA items (reentrant) +; +; $Id: //main/2015/qhull/src/libqhull_r/qhull_r-exports.def#3 $$Change: 2047 $ +; $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $ +; +; Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, and qhull-warn.pri +VERSION 7.0 +EXPORTS +qh_addhash +qh_addpoint +qh_all_merges +qh_allstatA +qh_allstatB +qh_allstatC +qh_allstatD +qh_allstatE +qh_allstatE2 +qh_allstatF +qh_allstatG +qh_allstatH +qh_allstatI +qh_allstatistics +qh_appendfacet +qh_appendmergeset +qh_appendprint +qh_appendvertex +qh_argv_to_command +qh_argv_to_command_size +qh_attachnewfacets +qh_backnormal +qh_basevertices +qh_build_withrestart +qh_buildhull +qh_buildtracing +qh_check_bestdist +qh_check_dupridge +qh_check_maxout +qh_check_output +qh_check_point +qh_check_points +qh_checkconnect +qh_checkconvex +qh_checkfacet +qh_checkflags +qh_checkflipped +qh_checkflipped_all +qh_checkpolygon +qh_checkvertex +qh_checkzero +qh_clear_outputflags +qh_clearcenters +qh_clock +qh_collectstatistics +qh_compare_facetarea +qh_compare_facetmerge +qh_compare_facetvisit +qh_compareangle +qh_comparemerge +qh_comparevisit +qh_copyfilename +qh_copynonconvex +qh_copypoints +qh_countfacets +qh_createsimplex +qh_crossproduct +qh_degen_redundant_facet +qh_degen_redundant_neighbors +qh_deletevisible +qh_delfacet +qh_delridge +qh_delvertex +qh_determinant +qh_detjoggle +qh_detroundoff +qh_detsimplex +qh_detvnorm +qh_detvridge +qh_detvridge3 +qh_dfacet +qh_distnorm +qh_distplane +qh_distround +qh_divzero +qh_dvertex +qh_eachvoronoi +qh_eachvoronoi_all +qh_errexit +qh_errexit2 +qh_errexit_rbox +qh_errprint +qh_exit +qh_facet2point +qh_facet3vertex +qh_facetarea +qh_facetarea_simplex +qh_facetcenter +qh_facetintersect +qh_facetvertices +qh_find_newvertex +qh_findbest +qh_findbest_test +qh_findbestfacet +qh_findbesthorizon +qh_findbestlower +qh_findbestneighbor +qh_findbestnew +qh_findfacet_all +qh_findgood +qh_findgood_all +qh_findgooddist +qh_findhorizon +qh_flippedmerges +qh_forcedmerges +qh_fprintf +qh_fprintf_rbox +qh_fprintf_stderr +qh_free +qh_freebuffers +qh_freebuild +qh_freeqhull +qh_furthestnext +qh_furthestout +qh_gausselim +qh_geomplanes +qh_getangle +qh_getarea +qh_getcenter +qh_getcentrum +qh_getdistance +qh_gethash +qh_getmergeset +qh_getmergeset_initial +qh_gram_schmidt +qh_hashridge +qh_hashridge_find +qh_infiniteloop +qh_init_A +qh_init_B +qh_init_qhull_command +qh_initbuild +qh_initflags +qh_initialhull +qh_initialvertices +qh_initqhull_buffers +qh_initqhull_globals +qh_initqhull_mem +qh_initqhull_outputflags +qh_initqhull_start +qh_initqhull_start2 +qh_initstatistics +qh_initthresholds +qh_inthresholds +qh_isvertex +qh_joggleinput +qh_lib_check +qh_makenew_nonsimplicial +qh_makenew_simplicial +qh_makenewfacet +qh_makenewfacets +qh_makenewplanes +qh_makeridges +qh_malloc +qh_mark_dupridges +qh_markkeep +qh_markvoronoi +qh_matchduplicates +qh_matchneighbor +qh_matchnewfacets +qh_matchvertices +qh_maxabsval +qh_maxmin +qh_maxouter +qh_maxsimplex +qh_maydropneighbor +qh_memalloc +qh_memfree +qh_memfreeshort +qh_meminit +qh_meminitbuffers +qh_memsetup +qh_memsize +qh_memstatistics +qh_memtotal +qh_merge_degenredundant +qh_merge_nonconvex +qh_mergecycle +qh_mergecycle_all +qh_mergecycle_facets +qh_mergecycle_neighbors +qh_mergecycle_ridges +qh_mergecycle_vneighbors +qh_mergefacet +qh_mergefacet2d +qh_mergeneighbors +qh_mergeridges +qh_mergesimplex +qh_mergevertex_del +qh_mergevertex_neighbors +qh_mergevertices +qh_minabsval +qh_mindiff +qh_nearcoplanar +qh_nearvertex +qh_neighbor_intersections +qh_new_qhull +qh_newfacet +qh_newhashtable +qh_newridge +qh_newstats +qh_newvertex +qh_newvertices +qh_nextfurthest +qh_nextridge3d +qh_normalize +qh_normalize2 +qh_nostatistic +qh_option +qh_order_vertexneighbors +qh_orientoutside +qh_out1 +qh_out2n +qh_out3n +qh_outcoplanar +qh_outerinner +qh_partitionall +qh_partitioncoplanar +qh_partitionpoint +qh_partitionvisible +qh_point +qh_point_add +qh_pointdist +qh_pointfacet +qh_pointid +qh_pointvertex +qh_postmerge +qh_precision +qh_premerge +qh_prepare_output +qh_prependfacet +qh_printafacet +qh_printallstatistics +qh_printbegin +qh_printcenter +qh_printcentrum +qh_printend +qh_printend4geom +qh_printextremes +qh_printextremes_2d +qh_printextremes_d +qh_printfacet +qh_printfacet2geom +qh_printfacet2geom_points +qh_printfacet2math +qh_printfacet3geom_nonsimplicial +qh_printfacet3geom_points +qh_printfacet3geom_simplicial +qh_printfacet3math +qh_printfacet3vertex +qh_printfacet4geom_nonsimplicial +qh_printfacet4geom_simplicial +qh_printfacetNvertex_nonsimplicial +qh_printfacetNvertex_simplicial +qh_printfacetheader +qh_printfacetlist +qh_printfacetridges +qh_printfacets +qh_printhashtable +qh_printhelp_degenerate +qh_printhelp_narrowhull +qh_printhelp_singular +qh_printhyperplaneintersection +qh_printline3geom +qh_printlists +qh_printmatrix +qh_printneighborhood +qh_printpoint +qh_printpoint3 +qh_printpointid +qh_printpoints +qh_printpoints_out +qh_printpointvect +qh_printpointvect2 +qh_printridge +qh_printspheres +qh_printstatistics +qh_printstatlevel +qh_printstats +qh_printsummary +qh_printvdiagram +qh_printvdiagram2 +qh_printvertex +qh_printvertexlist +qh_printvertices +qh_printvneighbors +qh_printvnorm +qh_printvoronoi +qh_printvridge +qh_produce_output +qh_produce_output2 +qh_projectdim3 +qh_projectinput +qh_projectpoint +qh_projectpoints +qh_qhull +qh_rand +qh_randomfactor +qh_randommatrix +qh_rboxpoints +qh_readfeasible +qh_readpoints +qh_reducevertices +qh_redundant_vertex +qh_remove_extravertices +qh_removefacet +qh_removevertex +qh_rename_sharedvertex +qh_renameridgevertex +qh_renamevertex +qh_resetlists +qh_rotateinput +qh_rotatepoints +qh_roundi +qh_scaleinput +qh_scalelast +qh_scalepoints +qh_setaddnth +qh_setaddsorted +qh_setappend +qh_setappend2ndlast +qh_setappend_set +qh_setcheck +qh_setcompact +qh_setcopy +qh_setdel +qh_setdelaunay +qh_setdellast +qh_setdelnth +qh_setdelnthsorted +qh_setdelsorted +qh_setduplicate +qh_setequal +qh_setequal_except +qh_setequal_skip +qh_setfacetplane +qh_setfeasible +qh_setfree +qh_setfree2 +qh_setfreelong +qh_sethalfspace +qh_sethalfspace_all +qh_sethyperplane_det +qh_sethyperplane_gauss +qh_setin +qh_setindex +qh_setlarger +qh_setlast +qh_setnew +qh_setnew_delnthsorted +qh_setprint +qh_setreplace +qh_setsize +qh_settemp +qh_settempfree +qh_settempfree_all +qh_settemppop +qh_settemppush +qh_settruncate +qh_setunique +qh_setvoronoi_all +qh_setzero +qh_sharpnewfacets +qh_skipfacet +qh_skipfilename +qh_srand +qh_stddev +qh_strtod +qh_strtol +qh_test_appendmerge +qh_test_vneighbors +qh_tracemerge +qh_tracemerging +qh_triangulate +qh_triangulate_facet +qh_triangulate_link +qh_triangulate_mirror +qh_triangulate_null +qh_updatetested +qh_updatevertices +qh_user_memsizes +qh_version +qh_version2 +qh_vertexintersect +qh_vertexintersect_new +qh_vertexneighbors +qh_vertexridges +qh_vertexridges_facet +qh_vertexsubset +qh_voronoi_center +qh_willdelete +qh_zero diff --git a/xs/src/qhull/src/libqhull_r/qhull_ra.h b/xs/src/qhull/src/libqhull_r/qhull_ra.h new file mode 100644 index 0000000000..5c5bd8779c --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qhull_ra.h @@ -0,0 +1,158 @@ +/*
      ---------------------------------
    +
    +   qhull_ra.h
    +   all header files for compiling qhull with reentrant code
    +   included before C++ headers for user_r.h:QHULL_CRTDBG
    +
    +   see qh-qhull.htm
    +
    +   see libqhull_r.h for user-level definitions
    +
    +   see user_r.h for user-definable constants
    +
    +   defines internal functions for libqhull_r.c global_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/qhull_ra.h#6 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +
    +   Notes:  grep for ((" and (" to catch fprintf("lkasdjf");
    +           full parens around (x?y:z)
    +           use '#include "libqhull_r/qhull_ra.h"' to avoid name clashes
    +*/
    +
    +#ifndef qhDEFqhulla
    +#define qhDEFqhulla 1
    +
    +#include "libqhull_r.h"  /* Includes user_r.h and data types */
    +
    +#include "stat_r.h"
    +#include "random_r.h"
    +#include "mem_r.h"
    +#include "qset_r.h"
    +#include "geom_r.h"
    +#include "merge_r.h"
    +#include "poly_r.h"
    +#include "io_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include     /* some compilers will not need float.h */
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +/*** uncomment here and qset_r.c
    +     if string.h does not define memcpy()
    +#include 
    +*/
    +
    +#if qh_CLOCKtype == 2  /* defined in user_r.h from libqhull_r.h */
    +#include 
    +#include 
    +#include 
    +#endif
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4100)  /* unreferenced formal parameter */
    +#pragma warning( disable : 4127)  /* conditional expression is constant */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#pragma warning( disable : 4996)  /* function was declared deprecated(strcpy, localtime, etc.) */
    +#endif
    +
    +/* ======= -macros- =========== */
    +
    +/*----------------------------------
    +
    +  traceN((qh, qh->ferr, 0Nnnn, "format\n", vars));
    +    calls qh_fprintf if qh.IStracing >= N
    +
    +    Add debugging traps to the end of qh_fprintf
    +
    +  notes:
    +    removing tracing reduces code size but doesn't change execution speed
    +*/
    +#ifndef qh_NOtrace
    +#define trace0(args) {if (qh->IStracing) qh_fprintf args;}
    +#define trace1(args) {if (qh->IStracing >= 1) qh_fprintf args;}
    +#define trace2(args) {if (qh->IStracing >= 2) qh_fprintf args;}
    +#define trace3(args) {if (qh->IStracing >= 3) qh_fprintf args;}
    +#define trace4(args) {if (qh->IStracing >= 4) qh_fprintf args;}
    +#define trace5(args) {if (qh->IStracing >= 5) qh_fprintf args;}
    +#else /* qh_NOtrace */
    +#define trace0(args) {}
    +#define trace1(args) {}
    +#define trace2(args) {}
    +#define trace3(args) {}
    +#define trace4(args) {}
    +#define trace5(args) {}
    +#endif /* qh_NOtrace */
    +
    +/*----------------------------------
    +
    +  Define an unused variable to avoid compiler warnings
    +
    +  Derived from Qt's corelib/global/qglobal.h
    +
    +*/
    +
    +#if defined(__cplusplus) && defined(__INTEL_COMPILER) && !defined(QHULL_OS_WIN)
    +template 
    +inline void qhullUnused(T &x) { (void)x; }
    +#  define QHULL_UNUSED(x) qhullUnused(x);
    +#else
    +#  define QHULL_UNUSED(x) (void)x;
    +#endif
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +/***** -libqhull_r.c prototypes (alphabetical after qhull) ********************/
    +
    +void    qh_qhull(qhT *qh);
    +boolT   qh_addpoint(qhT *qh, pointT *furthest, facetT *facet, boolT checkdist);
    +void    qh_buildhull(qhT *qh);
    +void    qh_buildtracing(qhT *qh, pointT *furthest, facetT *facet);
    +void    qh_build_withrestart(qhT *qh);
    +void    qh_errexit2(qhT *qh, int exitcode, facetT *facet, facetT *otherfacet);
    +void    qh_findhorizon(qhT *qh, pointT *point, facetT *facet, int *goodvisible,int *goodhorizon);
    +pointT *qh_nextfurthest(qhT *qh, facetT **visible);
    +void    qh_partitionall(qhT *qh, setT *vertices, pointT *points,int npoints);
    +void    qh_partitioncoplanar(qhT *qh, pointT *point, facetT *facet, realT *dist);
    +void    qh_partitionpoint(qhT *qh, pointT *point, facetT *facet);
    +void    qh_partitionvisible(qhT *qh, boolT allpoints, int *numpoints);
    +void    qh_precision(qhT *qh, const char *reason);
    +void    qh_printsummary(qhT *qh, FILE *fp);
    +
    +/***** -global_r.c internal prototypes (alphabetical) ***********************/
    +
    +void    qh_appendprint(qhT *qh, qh_PRINT format);
    +void    qh_freebuild(qhT *qh, boolT allmem);
    +void    qh_freebuffers(qhT *qh);
    +void    qh_initbuffers(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +
    +/***** -stat_r.c internal prototypes (alphabetical) ***********************/
    +
    +void    qh_allstatA(qhT *qh);
    +void    qh_allstatB(qhT *qh);
    +void    qh_allstatC(qhT *qh);
    +void    qh_allstatD(qhT *qh);
    +void    qh_allstatE(qhT *qh);
    +void    qh_allstatE2(qhT *qh);
    +void    qh_allstatF(qhT *qh);
    +void    qh_allstatG(qhT *qh);
    +void    qh_allstatH(qhT *qh);
    +void    qh_freebuffers(qhT *qh);
    +void    qh_initbuffers(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFqhulla */
    diff --git a/xs/src/qhull/src/libqhull_r/qset_r.c b/xs/src/qhull/src/libqhull_r/qset_r.c
    new file mode 100644
    index 0000000000..15cd3c0e29
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/qset_r.c
    @@ -0,0 +1,1340 @@
    +/*
      ---------------------------------
    +
    +   qset_r.c
    +   implements set manipulations needed for quickhull
    +
    +   see qh-set_r.htm and qset_r.h
    +
    +   Be careful of strict aliasing (two pointers of different types
    +   that reference the same location).  The last slot of a set is
    +   either the actual size of the set plus 1, or the NULL terminator
    +   of the set (i.e., setelemT).
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/qset_r.c#3 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "libqhull_r.h" /* for qhT and QHULL_CRTDBG */
    +#include "qset_r.h"
    +#include "mem_r.h"
    +#include 
    +#include 
    +/*** uncomment here and qhull_ra.h
    +     if string.h does not define memcpy()
    +#include 
    +*/
    +
    +#ifndef qhDEFlibqhull
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +void    qh_errexit(qhT *qh, int exitcode, facetT *, ridgeT *);
    +void    qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +#  ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#  pragma warning( disable : 4127)  /* conditional expression is constant */
    +#  pragma warning( disable : 4706)  /* assignment within conditional function */
    +#  endif
    +#endif
    +
    +/*=============== internal macros ===========================*/
    +
    +/*============ functions in alphabetical order ===================*/
    +
    +/*----------------------------------
    +
    +  qh_setaddnth(qh, setp, nth, newelem)
    +    adds newelem as n'th element of sorted or unsorted *setp
    +
    +  notes:
    +    *setp and newelem must be defined
    +    *setp may be a temp set
    +    nth=0 is first element
    +    errors if nth is out of bounds
    +
    +  design:
    +    expand *setp if empty or full
    +    move tail of *setp up one
    +    insert newelem
    +*/
    +void qh_setaddnth(qhT *qh, setT **setp, int nth, void *newelem) {
    +  int oldsize, i;
    +  setelemT *sizep;          /* avoid strict aliasing */
    +  setelemT *oldp, *newp;
    +
    +  if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +    qh_setlarger(qh, setp);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  oldsize= sizep->i - 1;
    +  if (nth < 0 || nth > oldsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6171, "qhull internal error (qh_setaddnth): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qh, qh->qhmem.ferr, "", *setp);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  sizep->i++;
    +  oldp= (setelemT *)SETelemaddr_(*setp, oldsize, void);   /* NULL */
    +  newp= oldp+1;
    +  for (i=oldsize-nth+1; i--; )  /* move at least NULL  */
    +    (newp--)->p= (oldp--)->p;       /* may overwrite *sizep */
    +  newp->p= newelem;
    +} /* setaddnth */
    +
    +
    +/*----------------------------------
    +
    +  setaddsorted( setp, newelem )
    +    adds an newelem into sorted *setp
    +
    +  notes:
    +    *setp and newelem must be defined
    +    *setp may be a temp set
    +    nop if newelem already in set
    +
    +  design:
    +    find newelem's position in *setp
    +    insert newelem
    +*/
    +void qh_setaddsorted(qhT *qh, setT **setp, void *newelem) {
    +  int newindex=0;
    +  void *elem, **elemp;
    +
    +  FOREACHelem_(*setp) {          /* could use binary search instead */
    +    if (elem < newelem)
    +      newindex++;
    +    else if (elem == newelem)
    +      return;
    +    else
    +      break;
    +  }
    +  qh_setaddnth(qh, setp, newindex, newelem);
    +} /* setaddsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setappend(qh, setp, newelem)
    +    append newelem to *setp
    +
    +  notes:
    +    *setp may be a temp set
    +    *setp and newelem may be NULL
    +
    +  design:
    +    expand *setp if empty or full
    +    append newelem to *setp
    +
    +*/
    +void qh_setappend(qhT *qh, setT **setp, void *newelem) {
    +  setelemT *sizep;  /* Avoid strict aliasing.  Writing to *endp may overwrite *sizep */
    +  setelemT *endp;
    +  int count;
    +
    +  if (!newelem)
    +    return;
    +  if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +    qh_setlarger(qh, setp);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  count= (sizep->i)++ - 1;
    +  endp= (setelemT *)SETelemaddr_(*setp, count, void);
    +  (endp++)->p= newelem;
    +  endp->p= NULL;
    +} /* setappend */
    +
    +/*---------------------------------
    +
    +  qh_setappend_set(qh, setp, setA)
    +    appends setA to *setp
    +
    +  notes:
    +    *setp can not be a temp set
    +    *setp and setA may be NULL
    +
    +  design:
    +    setup for copy
    +    expand *setp if it is too small
    +    append all elements of setA to *setp
    +*/
    +void qh_setappend_set(qhT *qh, setT **setp, setT *setA) {
    +  int sizeA, size;
    +  setT *oldset;
    +  setelemT *sizep;
    +
    +  if (!setA)
    +    return;
    +  SETreturnsize_(setA, sizeA);
    +  if (!*setp)
    +    *setp= qh_setnew(qh, sizeA);
    +  sizep= SETsizeaddr_(*setp);
    +  if (!(size= sizep->i))
    +    size= (*setp)->maxsize;
    +  else
    +    size--;
    +  if (size + sizeA > (*setp)->maxsize) {
    +    oldset= *setp;
    +    *setp= qh_setcopy(qh, oldset, sizeA);
    +    qh_setfree(qh, &oldset);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  if (sizeA > 0) {
    +    sizep->i= size+sizeA+1;   /* memcpy may overwrite */
    +    memcpy((char *)&((*setp)->e[size].p), (char *)&(setA->e[0].p), (size_t)(sizeA+1) * SETelemsize);
    +  }
    +} /* setappend_set */
    +
    +
    +/*---------------------------------
    +
    +  qh_setappend2ndlast(qh, setp, newelem )
    +    makes newelem the next to the last element in *setp
    +
    +  notes:
    +    *setp must have at least one element
    +    newelem must be defined
    +    *setp may be a temp set
    +
    +  design:
    +    expand *setp if empty or full
    +    move last element of *setp up one
    +    insert newelem
    +*/
    +void qh_setappend2ndlast(qhT *qh, setT **setp, void *newelem) {
    +    setelemT *sizep;  /* Avoid strict aliasing.  Writing to *endp may overwrite *sizep */
    +    setelemT *endp, *lastp;
    +    int count;
    +
    +    if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +        qh_setlarger(qh, setp);
    +        sizep= SETsizeaddr_(*setp);
    +    }
    +    count= (sizep->i)++ - 1;
    +    endp= (setelemT *)SETelemaddr_(*setp, count, void); /* NULL */
    +    lastp= endp-1;
    +    *(endp++)= *lastp;
    +    endp->p= NULL;    /* may overwrite *sizep */
    +    lastp->p= newelem;
    +} /* setappend2ndlast */
    +
    +/*---------------------------------
    +
    +  qh_setcheck(qh, set, typename, id )
    +    check set for validity
    +    report errors with typename and id
    +
    +  design:
    +    checks that maxsize, actual size, and NULL terminator agree
    +*/
    +void qh_setcheck(qhT *qh, setT *set, const char *tname, unsigned id) {
    +  int maxsize, size;
    +  int waserr= 0;
    +
    +  if (!set)
    +    return;
    +  SETreturnsize_(set, size);
    +  maxsize= set->maxsize;
    +  if (size > maxsize || !maxsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6172, "qhull internal error (qh_setcheck): actual size %d of %s%d is greater than max size %d\n",
    +             size, tname, id, maxsize);
    +    waserr= 1;
    +  }else if (set->e[size].p) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6173, "qhull internal error (qh_setcheck): %s%d(size %d max %d) is not null terminated.\n",
    +             tname, id, size-1, maxsize);
    +    waserr= 1;
    +  }
    +  if (waserr) {
    +    qh_setprint(qh, qh->qhmem.ferr, "ERRONEOUS", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +} /* setcheck */
    +
    +
    +/*---------------------------------
    +
    +  qh_setcompact(qh, set )
    +    remove internal NULLs from an unsorted set
    +
    +  returns:
    +    updated set
    +
    +  notes:
    +    set may be NULL
    +    it would be faster to swap tail of set into holes, like qh_setdel
    +
    +  design:
    +    setup pointers into set
    +    skip NULLs while copying elements to start of set
    +    update the actual size
    +*/
    +void qh_setcompact(qhT *qh, setT *set) {
    +  int size;
    +  void **destp, **elemp, **endp, **firstp;
    +
    +  if (!set)
    +    return;
    +  SETreturnsize_(set, size);
    +  destp= elemp= firstp= SETaddr_(set, void);
    +  endp= destp + size;
    +  while (1) {
    +    if (!(*destp++ = *elemp++)) {
    +      destp--;
    +      if (elemp > endp)
    +        break;
    +    }
    +  }
    +  qh_settruncate(qh, set, (int)(destp-firstp));   /* WARN64 */
    +} /* setcompact */
    +
    +
    +/*---------------------------------
    +
    +  qh_setcopy(qh, set, extra )
    +    make a copy of a sorted or unsorted set with extra slots
    +
    +  returns:
    +    new set
    +
    +  design:
    +    create a newset with extra slots
    +    copy the elements to the newset
    +
    +*/
    +setT *qh_setcopy(qhT *qh, setT *set, int extra) {
    +  setT *newset;
    +  int size;
    +
    +  if (extra < 0)
    +    extra= 0;
    +  SETreturnsize_(set, size);
    +  newset= qh_setnew(qh, size+extra);
    +  SETsizeaddr_(newset)->i= size+1;    /* memcpy may overwrite */
    +  memcpy((char *)&(newset->e[0].p), (char *)&(set->e[0].p), (size_t)(size+1) * SETelemsize);
    +  return(newset);
    +} /* setcopy */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdel(set, oldelem )
    +    delete oldelem from an unsorted set
    +
    +  returns:
    +    returns oldelem if found
    +    returns NULL otherwise
    +
    +  notes:
    +    set may be NULL
    +    oldelem must not be NULL;
    +    only deletes one copy of oldelem in set
    +
    +  design:
    +    locate oldelem
    +    update actual size if it was full
    +    move the last element to the oldelem's location
    +*/
    +void *qh_setdel(setT *set, void *oldelem) {
    +  setelemT *sizep;
    +  setelemT *elemp;
    +  setelemT *lastp;
    +
    +  if (!set)
    +    return NULL;
    +  elemp= (setelemT *)SETaddr_(set, void);
    +  while (elemp->p != oldelem && elemp->p)
    +    elemp++;
    +  if (elemp->p) {
    +    sizep= SETsizeaddr_(set);
    +    if (!(sizep->i)--)         /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (maxsize-1)+ 1 */
    +    lastp= (setelemT *)SETelemaddr_(set, sizep->i-1, void);
    +    elemp->p= lastp->p;      /* may overwrite itself */
    +    lastp->p= NULL;
    +    return oldelem;
    +  }
    +  return NULL;
    +} /* setdel */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdellast(set)
    +    return last element of set or NULL
    +
    +  notes:
    +    deletes element from set
    +    set may be NULL
    +
    +  design:
    +    return NULL if empty
    +    if full set
    +      delete last element and set actual size
    +    else
    +      delete last element and update actual size
    +*/
    +void *qh_setdellast(setT *set) {
    +  int setsize;  /* actually, actual_size + 1 */
    +  int maxsize;
    +  setelemT *sizep;
    +  void *returnvalue;
    +
    +  if (!set || !(set->e[0].p))
    +    return NULL;
    +  sizep= SETsizeaddr_(set);
    +  if ((setsize= sizep->i)) {
    +    returnvalue= set->e[setsize - 2].p;
    +    set->e[setsize - 2].p= NULL;
    +    sizep->i--;
    +  }else {
    +    maxsize= set->maxsize;
    +    returnvalue= set->e[maxsize - 1].p;
    +    set->e[maxsize - 1].p= NULL;
    +    sizep->i= maxsize;
    +  }
    +  return returnvalue;
    +} /* setdellast */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelnth(qh, set, nth )
    +    deletes nth element from unsorted set
    +    0 is first element
    +
    +  returns:
    +    returns the element (needs type conversion)
    +
    +  notes:
    +    errors if nth invalid
    +
    +  design:
    +    setup points and check nth
    +    delete nth element and overwrite with last element
    +*/
    +void *qh_setdelnth(qhT *qh, setT *set, int nth) {
    +  void *elem;
    +  setelemT *sizep;
    +  setelemT *elemp, *lastp;
    +
    +  sizep= SETsizeaddr_(set);
    +  if ((sizep->i--)==0)         /*  if was a full set */
    +    sizep->i= set->maxsize;  /*     *sizep= (maxsize-1)+ 1 */
    +  if (nth < 0 || nth >= sizep->i) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6174, "qhull internal error (qh_setdelnth): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  elemp= (setelemT *)SETelemaddr_(set, nth, void); /* nth valid by QH6174 */
    +  lastp= (setelemT *)SETelemaddr_(set, sizep->i-1, void);
    +  elem= elemp->p;
    +  elemp->p= lastp->p;      /* may overwrite itself */
    +  lastp->p= NULL;
    +  return elem;
    +} /* setdelnth */
    +
    +/*---------------------------------
    +
    +  qh_setdelnthsorted(qh, set, nth )
    +    deletes nth element from sorted set
    +
    +  returns:
    +    returns the element (use type conversion)
    +
    +  notes:
    +    errors if nth invalid
    +
    +  see also:
    +    setnew_delnthsorted
    +
    +  design:
    +    setup points and check nth
    +    copy remaining elements down one
    +    update actual size
    +*/
    +void *qh_setdelnthsorted(qhT *qh, setT *set, int nth) {
    +  void *elem;
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  sizep= SETsizeaddr_(set);
    +  if (nth < 0 || (sizep->i && nth >= sizep->i-1) || nth >= set->maxsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6175, "qhull internal error (qh_setdelnthsorted): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  newp= (setelemT *)SETelemaddr_(set, nth, void);
    +  elem= newp->p;
    +  oldp= newp+1;
    +  while (((newp++)->p= (oldp++)->p))
    +    ; /* copy remaining elements and NULL */
    +  if ((sizep->i--)==0)         /*  if was a full set */
    +    sizep->i= set->maxsize;  /*     *sizep= (max size-1)+ 1 */
    +  return elem;
    +} /* setdelnthsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelsorted(set, oldelem )
    +    deletes oldelem from sorted set
    +
    +  returns:
    +    returns oldelem if it was deleted
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    locate oldelem in set
    +    copy remaining elements down one
    +    update actual size
    +*/
    +void *qh_setdelsorted(setT *set, void *oldelem) {
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  if (!set)
    +    return NULL;
    +  newp= (setelemT *)SETaddr_(set, void);
    +  while(newp->p != oldelem && newp->p)
    +    newp++;
    +  if (newp->p) {
    +    oldp= newp+1;
    +    while (((newp++)->p= (oldp++)->p))
    +      ; /* copy remaining elements */
    +    sizep= SETsizeaddr_(set);
    +    if ((sizep->i--)==0)    /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (max size-1)+ 1 */
    +    return oldelem;
    +  }
    +  return NULL;
    +} /* setdelsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setduplicate(qh, set, elemsize )
    +    duplicate a set of elemsize elements
    +
    +  notes:
    +    use setcopy if retaining old elements
    +
    +  design:
    +    create a new set
    +    for each elem of the old set
    +      create a newelem
    +      append newelem to newset
    +*/
    +setT *qh_setduplicate(qhT *qh, setT *set, int elemsize) {
    +  void          *elem, **elemp, *newElem;
    +  setT          *newSet;
    +  int           size;
    +
    +  if (!(size= qh_setsize(qh, set)))
    +    return NULL;
    +  newSet= qh_setnew(qh, size);
    +  FOREACHelem_(set) {
    +    newElem= qh_memalloc(qh, elemsize);
    +    memcpy(newElem, elem, (size_t)elemsize);
    +    qh_setappend(qh, &newSet, newElem);
    +  }
    +  return newSet;
    +} /* setduplicate */
    +
    +
    +/*---------------------------------
    +
    +  qh_setendpointer( set )
    +    Returns pointer to NULL terminator of a set's elements
    +    set can not be NULL
    +
    +*/
    +void **qh_setendpointer(setT *set) {
    +
    +  setelemT *sizep= SETsizeaddr_(set);
    +  int n= sizep->i;
    +  return (n ? &set->e[n-1].p : &sizep->p);
    +} /* qh_setendpointer */
    +
    +/*---------------------------------
    +
    +  qh_setequal( setA, setB )
    +    returns 1 if two sorted sets are equal, otherwise returns 0
    +
    +  notes:
    +    either set may be NULL
    +
    +  design:
    +    check size of each set
    +    setup pointers
    +    compare elements of each set
    +*/
    +int qh_setequal(setT *setA, setT *setB) {
    +  void **elemAp, **elemBp;
    +  int sizeA= 0, sizeB= 0;
    +
    +  if (setA) {
    +    SETreturnsize_(setA, sizeA);
    +  }
    +  if (setB) {
    +    SETreturnsize_(setB, sizeB);
    +  }
    +  if (sizeA != sizeB)
    +    return 0;
    +  if (!sizeA)
    +    return 1;
    +  elemAp= SETaddr_(setA, void);
    +  elemBp= SETaddr_(setB, void);
    +  if (!memcmp((char *)elemAp, (char *)elemBp, sizeA*SETelemsize))
    +    return 1;
    +  return 0;
    +} /* setequal */
    +
    +
    +/*---------------------------------
    +
    +  qh_setequal_except( setA, skipelemA, setB, skipelemB )
    +    returns 1 if sorted setA and setB are equal except for skipelemA & B
    +
    +  returns:
    +    false if either skipelemA or skipelemB are missing
    +
    +  notes:
    +    neither set may be NULL
    +
    +    if skipelemB is NULL,
    +      can skip any one element of setB
    +
    +  design:
    +    setup pointers
    +    search for skipelemA, skipelemB, and mismatches
    +    check results
    +*/
    +int qh_setequal_except(setT *setA, void *skipelemA, setT *setB, void *skipelemB) {
    +  void **elemA, **elemB;
    +  int skip=0;
    +
    +  elemA= SETaddr_(setA, void);
    +  elemB= SETaddr_(setB, void);
    +  while (1) {
    +    if (*elemA == skipelemA) {
    +      skip++;
    +      elemA++;
    +    }
    +    if (skipelemB) {
    +      if (*elemB == skipelemB) {
    +        skip++;
    +        elemB++;
    +      }
    +    }else if (*elemA != *elemB) {
    +      skip++;
    +      if (!(skipelemB= *elemB++))
    +        return 0;
    +    }
    +    if (!*elemA)
    +      break;
    +    if (*elemA++ != *elemB++)
    +      return 0;
    +  }
    +  if (skip != 2 || *elemB)
    +    return 0;
    +  return 1;
    +} /* setequal_except */
    +
    +
    +/*---------------------------------
    +
    +  qh_setequal_skip( setA, skipA, setB, skipB )
    +    returns 1 if sorted setA and setB are equal except for elements skipA & B
    +
    +  returns:
    +    false if different size
    +
    +  notes:
    +    neither set may be NULL
    +
    +  design:
    +    setup pointers
    +    search for mismatches while skipping skipA and skipB
    +*/
    +int qh_setequal_skip(setT *setA, int skipA, setT *setB, int skipB) {
    +  void **elemA, **elemB, **skipAp, **skipBp;
    +
    +  elemA= SETaddr_(setA, void);
    +  elemB= SETaddr_(setB, void);
    +  skipAp= SETelemaddr_(setA, skipA, void);
    +  skipBp= SETelemaddr_(setB, skipB, void);
    +  while (1) {
    +    if (elemA == skipAp)
    +      elemA++;
    +    if (elemB == skipBp)
    +      elemB++;
    +    if (!*elemA)
    +      break;
    +    if (*elemA++ != *elemB++)
    +      return 0;
    +  }
    +  if (*elemB)
    +    return 0;
    +  return 1;
    +} /* setequal_skip */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfree(qh, setp )
    +    frees the space occupied by a sorted or unsorted set
    +
    +  returns:
    +    sets setp to NULL
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    free array
    +    free set
    +*/
    +void qh_setfree(qhT *qh, setT **setp) {
    +  int size;
    +  void **freelistp;  /* used if !qh_NOmem by qh_memfree_() */
    +
    +  if (*setp) {
    +    size= sizeof(setT) + ((*setp)->maxsize)*SETelemsize;
    +    if (size <= qh->qhmem.LASTsize) {
    +      qh_memfree_(qh, *setp, size, freelistp);
    +    }else
    +      qh_memfree(qh, *setp, size);
    +    *setp= NULL;
    +  }
    +} /* setfree */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfree2(qh, setp, elemsize )
    +    frees the space occupied by a set and its elements
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    free each element
    +    free set
    +*/
    +void qh_setfree2(qhT *qh, setT **setp, int elemsize) {
    +  void          *elem, **elemp;
    +
    +  FOREACHelem_(*setp)
    +    qh_memfree(qh, elem, elemsize);
    +  qh_setfree(qh, setp);
    +} /* setfree2 */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_setfreelong(qh, setp )
    +    frees a set only if it's in long memory
    +
    +  returns:
    +    sets setp to NULL if it is freed
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    if set is large
    +      free it
    +*/
    +void qh_setfreelong(qhT *qh, setT **setp) {
    +  int size;
    +
    +  if (*setp) {
    +    size= sizeof(setT) + ((*setp)->maxsize)*SETelemsize;
    +    if (size > qh->qhmem.LASTsize) {
    +      qh_memfree(qh, *setp, size);
    +      *setp= NULL;
    +    }
    +  }
    +} /* setfreelong */
    +
    +
    +/*---------------------------------
    +
    +  qh_setin(set, setelem )
    +    returns 1 if setelem is in a set, 0 otherwise
    +
    +  notes:
    +    set may be NULL or unsorted
    +
    +  design:
    +    scans set for setelem
    +*/
    +int qh_setin(setT *set, void *setelem) {
    +  void *elem, **elemp;
    +
    +  FOREACHelem_(set) {
    +    if (elem == setelem)
    +      return 1;
    +  }
    +  return 0;
    +} /* setin */
    +
    +
    +/*---------------------------------
    +
    +  qh_setindex(set, atelem )
    +    returns the index of atelem in set.
    +    returns -1, if not in set or maxsize wrong
    +
    +  notes:
    +    set may be NULL and may contain nulls.
    +    NOerrors returned (qh_pointid, QhullPoint::id)
    +
    +  design:
    +    checks maxsize
    +    scans set for atelem
    +*/
    +int qh_setindex(setT *set, void *atelem) {
    +  void **elem;
    +  int size, i;
    +
    +  if (!set)
    +    return -1;
    +  SETreturnsize_(set, size);
    +  if (size > set->maxsize)
    +    return -1;
    +  elem= SETaddr_(set, void);
    +  for (i=0; i < size; i++) {
    +    if (*elem++ == atelem)
    +      return i;
    +  }
    +  return -1;
    +} /* setindex */
    +
    +
    +/*---------------------------------
    +
    +  qh_setlarger(qh, oldsetp )
    +    returns a larger set that contains all elements of *oldsetp
    +
    +  notes:
    +    the set is at least twice as large
    +    if temp set, updates qh->qhmem.tempstack
    +
    +  design:
    +    creates a new set
    +    copies the old set to the new set
    +    updates pointers in tempstack
    +    deletes the old set
    +*/
    +void qh_setlarger(qhT *qh, setT **oldsetp) {
    +  int size= 1;
    +  setT *newset, *set, **setp, *oldset;
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  if (*oldsetp) {
    +    oldset= *oldsetp;
    +    SETreturnsize_(oldset, size);
    +    qh->qhmem.cntlarger++;
    +    qh->qhmem.totlarger += size+1;
    +    newset= qh_setnew(qh, 2 * size);
    +    oldp= (setelemT *)SETaddr_(oldset, void);
    +    newp= (setelemT *)SETaddr_(newset, void);
    +    memcpy((char *)newp, (char *)oldp, (size_t)(size+1) * SETelemsize);
    +    sizep= SETsizeaddr_(newset);
    +    sizep->i= size+1;
    +    FOREACHset_((setT *)qh->qhmem.tempstack) {
    +      if (set == oldset)
    +        *(setp-1)= newset;
    +    }
    +    qh_setfree(qh, oldsetp);
    +  }else
    +    newset= qh_setnew(qh, 3);
    +  *oldsetp= newset;
    +} /* setlarger */
    +
    +
    +/*---------------------------------
    +
    +  qh_setlast( set )
    +    return last element of set or NULL (use type conversion)
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    return last element
    +*/
    +void *qh_setlast(setT *set) {
    +  int size;
    +
    +  if (set) {
    +    size= SETsizeaddr_(set)->i;
    +    if (!size)
    +      return SETelem_(set, set->maxsize - 1);
    +    else if (size > 1)
    +      return SETelem_(set, size - 2);
    +  }
    +  return NULL;
    +} /* setlast */
    +
    +
    +/*---------------------------------
    +
    +  qh_setnew(qh, setsize )
    +    creates and allocates space for a set
    +
    +  notes:
    +    setsize means the number of elements (!including the NULL terminator)
    +    use qh_settemp/qh_setfreetemp if set is temporary
    +
    +  design:
    +    allocate memory for set
    +    roundup memory if small set
    +    initialize as empty set
    +*/
    +setT *qh_setnew(qhT *qh, int setsize) {
    +  setT *set;
    +  int sizereceived; /* used if !qh_NOmem */
    +  int size;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  if (!setsize)
    +    setsize++;
    +  size= sizeof(setT) + setsize * SETelemsize;
    +  if (size>0 && size <= qh->qhmem.LASTsize) {
    +    qh_memalloc_(qh, size, freelistp, set, setT);
    +#ifndef qh_NOmem
    +    sizereceived= qh->qhmem.sizetable[ qh->qhmem.indextable[size]];
    +    if (sizereceived > size)
    +      setsize += (sizereceived - size)/SETelemsize;
    +#endif
    +  }else
    +    set= (setT*)qh_memalloc(qh, size);
    +  set->maxsize= setsize;
    +  set->e[setsize].i= 1;
    +  set->e[0].p= NULL;
    +  return(set);
    +} /* setnew */
    +
    +
    +/*---------------------------------
    +
    +  qh_setnew_delnthsorted(qh, set, size, nth, prepend )
    +    creates a sorted set not containing nth element
    +    if prepend, the first prepend elements are undefined
    +
    +  notes:
    +    set must be defined
    +    checks nth
    +    see also: setdelnthsorted
    +
    +  design:
    +    create new set
    +    setup pointers and allocate room for prepend'ed entries
    +    append head of old set to new set
    +    append tail of old set to new set
    +*/
    +setT *qh_setnew_delnthsorted(qhT *qh, setT *set, int size, int nth, int prepend) {
    +  setT *newset;
    +  void **oldp, **newp;
    +  int tailsize= size - nth -1, newsize;
    +
    +  if (tailsize < 0) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6176, "qhull internal error (qh_setnew_delnthsorted): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  newsize= size-1 + prepend;
    +  newset= qh_setnew(qh, newsize);
    +  newset->e[newset->maxsize].i= newsize+1;  /* may be overwritten */
    +  oldp= SETaddr_(set, void);
    +  newp= SETaddr_(newset, void) + prepend;
    +  switch (nth) {
    +  case 0:
    +    break;
    +  case 1:
    +    *(newp++)= *oldp++;
    +    break;
    +  case 2:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 3:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 4:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  default:
    +    memcpy((char *)newp, (char *)oldp, (size_t)nth * SETelemsize);
    +    newp += nth;
    +    oldp += nth;
    +    break;
    +  }
    +  oldp++;
    +  switch (tailsize) {
    +  case 0:
    +    break;
    +  case 1:
    +    *(newp++)= *oldp++;
    +    break;
    +  case 2:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 3:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 4:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  default:
    +    memcpy((char *)newp, (char *)oldp, (size_t)tailsize * SETelemsize);
    +    newp += tailsize;
    +  }
    +  *newp= NULL;
    +  return(newset);
    +} /* setnew_delnthsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setprint(qh, fp, string, set )
    +    print set elements to fp with identifying string
    +
    +  notes:
    +    never errors
    +*/
    +void qh_setprint(qhT *qh, FILE *fp, const char* string, setT *set) {
    +  int size, k;
    +
    +  if (!set)
    +    qh_fprintf(qh, fp, 9346, "%s set is null\n", string);
    +  else {
    +    SETreturnsize_(set, size);
    +    qh_fprintf(qh, fp, 9347, "%s set=%p maxsize=%d size=%d elems=",
    +             string, set, set->maxsize, size);
    +    if (size > set->maxsize)
    +      size= set->maxsize+1;
    +    for (k=0; k < size; k++)
    +      qh_fprintf(qh, fp, 9348, " %p", set->e[k].p);
    +    qh_fprintf(qh, fp, 9349, "\n");
    +  }
    +} /* setprint */
    +
    +/*---------------------------------
    +
    +  qh_setreplace(qh, set, oldelem, newelem )
    +    replaces oldelem in set with newelem
    +
    +  notes:
    +    errors if oldelem not in the set
    +    newelem may be NULL, but it turns the set into an indexed set (no FOREACH)
    +
    +  design:
    +    find oldelem
    +    replace with newelem
    +*/
    +void qh_setreplace(qhT *qh, setT *set, void *oldelem, void *newelem) {
    +  void **elemp;
    +
    +  elemp= SETaddr_(set, void);
    +  while (*elemp != oldelem && *elemp)
    +    elemp++;
    +  if (*elemp)
    +    *elemp= newelem;
    +  else {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6177, "qhull internal error (qh_setreplace): elem %p not found in set\n",
    +       oldelem);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +} /* setreplace */
    +
    +
    +/*---------------------------------
    +
    +  qh_setsize(qh, set )
    +    returns the size of a set
    +
    +  notes:
    +    errors if set's maxsize is incorrect
    +    same as SETreturnsize_(set)
    +    same code for qh_setsize [qset_r.c] and QhullSetBase::count
    +
    +  design:
    +    determine actual size of set from maxsize
    +*/
    +int qh_setsize(qhT *qh, setT *set) {
    +  int size;
    +  setelemT *sizep;
    +
    +  if (!set)
    +    return(0);
    +  sizep= SETsizeaddr_(set);
    +  if ((size= sizep->i)) {
    +    size--;
    +    if (size > set->maxsize) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6178, "qhull internal error (qh_setsize): current set size %d is greater than maximum size %d\n",
    +               size, set->maxsize);
    +      qh_setprint(qh, qh->qhmem.ferr, "set: ", set);
    +      qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +    }
    +  }else
    +    size= set->maxsize;
    +  return size;
    +} /* setsize */
    +
    +/*---------------------------------
    +
    +  qh_settemp(qh, setsize )
    +    return a stacked, temporary set of upto setsize elements
    +
    +  notes:
    +    use settempfree or settempfree_all to release from qh->qhmem.tempstack
    +    see also qh_setnew
    +
    +  design:
    +    allocate set
    +    append to qh->qhmem.tempstack
    +
    +*/
    +setT *qh_settemp(qhT *qh, int setsize) {
    +  setT *newset;
    +
    +  newset= qh_setnew(qh, setsize);
    +  qh_setappend(qh, &qh->qhmem.tempstack, newset);
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8123, "qh_settemp: temp set %p of %d elements, depth %d\n",
    +       newset, newset->maxsize, qh_setsize(qh, qh->qhmem.tempstack));
    +  return newset;
    +} /* settemp */
    +
    +/*---------------------------------
    +
    +  qh_settempfree(qh, set )
    +    free temporary set at top of qh->qhmem.tempstack
    +
    +  notes:
    +    nop if set is NULL
    +    errors if set not from previous   qh_settemp
    +
    +  to locate errors:
    +    use 'T2' to find source and then find mis-matching qh_settemp
    +
    +  design:
    +    check top of qh->qhmem.tempstack
    +    free it
    +*/
    +void qh_settempfree(qhT *qh, setT **set) {
    +  setT *stackedset;
    +
    +  if (!*set)
    +    return;
    +  stackedset= qh_settemppop(qh);
    +  if (stackedset != *set) {
    +    qh_settemppush(qh, stackedset);
    +    qh_fprintf(qh, qh->qhmem.ferr, 6179, "qhull internal error (qh_settempfree): set %p(size %d) was not last temporary allocated(depth %d, set %p, size %d)\n",
    +             *set, qh_setsize(qh, *set), qh_setsize(qh, qh->qhmem.tempstack)+1,
    +             stackedset, qh_setsize(qh, stackedset));
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh_setfree(qh, set);
    +} /* settempfree */
    +
    +/*---------------------------------
    +
    +  qh_settempfree_all(qh)
    +    free all temporary sets in qh->qhmem.tempstack
    +
    +  design:
    +    for each set in tempstack
    +      free set
    +    free qh->qhmem.tempstack
    +*/
    +void qh_settempfree_all(qhT *qh) {
    +  setT *set, **setp;
    +
    +  FOREACHset_(qh->qhmem.tempstack)
    +    qh_setfree(qh, &set);
    +  qh_setfree(qh, &qh->qhmem.tempstack);
    +} /* settempfree_all */
    +
    +/*---------------------------------
    +
    +  qh_settemppop(qh)
    +    pop and return temporary set from qh->qhmem.tempstack
    +
    +  notes:
    +    the returned set is permanent
    +
    +  design:
    +    pop and check top of qh->qhmem.tempstack
    +*/
    +setT *qh_settemppop(qhT *qh) {
    +  setT *stackedset;
    +
    +  stackedset= (setT*)qh_setdellast(qh->qhmem.tempstack);
    +  if (!stackedset) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6180, "qhull internal error (qh_settemppop): pop from empty temporary stack\n");
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8124, "qh_settemppop: depth %d temp set %p of %d elements\n",
    +       qh_setsize(qh, qh->qhmem.tempstack)+1, stackedset, qh_setsize(qh, stackedset));
    +  return stackedset;
    +} /* settemppop */
    +
    +/*---------------------------------
    +
    +  qh_settemppush(qh, set )
    +    push temporary set unto qh->qhmem.tempstack (makes it temporary)
    +
    +  notes:
    +    duplicates settemp() for tracing
    +
    +  design:
    +    append set to tempstack
    +*/
    +void qh_settemppush(qhT *qh, setT *set) {
    +  if (!set) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6267, "qhull error (qh_settemppush): can not push a NULL temp\n");
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh_setappend(qh, &qh->qhmem.tempstack, set);
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8125, "qh_settemppush: depth %d temp set %p of %d elements\n",
    +      qh_setsize(qh, qh->qhmem.tempstack), set, qh_setsize(qh, set));
    +} /* settemppush */
    +
    +
    +/*---------------------------------
    +
    +  qh_settruncate(qh, set, size )
    +    truncate set to size elements
    +
    +  notes:
    +    set must be defined
    +
    +  see:
    +    SETtruncate_
    +
    +  design:
    +    check size
    +    update actual size of set
    +*/
    +void qh_settruncate(qhT *qh, setT *set, int size) {
    +
    +  if (size < 0 || size > set->maxsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6181, "qhull internal error (qh_settruncate): size %d out of bounds for set:\n", size);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  set->e[set->maxsize].i= size+1;   /* maybe overwritten */
    +  set->e[size].p= NULL;
    +} /* settruncate */
    +
    +/*---------------------------------
    +
    +  qh_setunique(qh, set, elem )
    +    add elem to unsorted set unless it is already in set
    +
    +  notes:
    +    returns 1 if it is appended
    +
    +  design:
    +    if elem not in set
    +      append elem to set
    +*/
    +int qh_setunique(qhT *qh, setT **set, void *elem) {
    +
    +  if (!qh_setin(*set, elem)) {
    +    qh_setappend(qh, set, elem);
    +    return 1;
    +  }
    +  return 0;
    +} /* setunique */
    +
    +/*---------------------------------
    +
    +  qh_setzero(qh, set, index, size )
    +    zero elements from index on
    +    set actual size of set to size
    +
    +  notes:
    +    set must be defined
    +    the set becomes an indexed set (can not use FOREACH...)
    +
    +  see also:
    +    qh_settruncate
    +
    +  design:
    +    check index and size
    +    update actual size
    +    zero elements starting at e[index]
    +*/
    +void qh_setzero(qhT *qh, setT *set, int idx, int size) {
    +  int count;
    +
    +  if (idx < 0 || idx >= size || size > set->maxsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6182, "qhull internal error (qh_setzero): index %d or size %d out of bounds for set:\n", idx, size);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  set->e[set->maxsize].i=  size+1;  /* may be overwritten */
    +  count= size - idx + 1;   /* +1 for NULL terminator */
    +  memset((char *)SETelemaddr_(set, idx, void), 0, (size_t)count * SETelemsize);
    +} /* setzero */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/qset_r.h b/xs/src/qhull/src/libqhull_r/qset_r.h
    new file mode 100644
    index 0000000000..7ba199d6f4
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/qset_r.h
    @@ -0,0 +1,502 @@
    +/*
      ---------------------------------
    +
    +   qset_r.h
    +     header file for qset_r.c that implements set
    +
    +   see qh-set_r.htm and qset_r.c
    +
    +   only uses mem_r.c, malloc/free
    +
    +   for error handling, writes message and calls
    +      qh_errexit(qhT *qh, qhmem_ERRqhull, NULL, NULL);
    +
    +   set operations satisfy the following properties:
    +    - sets have a max size, the actual size (if different) is stored at the end
    +    - every set is NULL terminated
    +    - sets may be sorted or unsorted, the caller must distinguish this
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/qset_r.h#4 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFset
    +#define qhDEFset 1
    +
    +#include 
    +
    +/*================= -structures- ===============*/
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;   /* a set is a sorted or unsorted array of pointers */
    +#endif
    +
    +#ifndef DEFqhT
    +#define DEFqhT 1
    +typedef struct qhT qhT;          /* defined in libqhull_r.h */
    +#endif
    +
    +/* [jan'15] Decided not to use countT.  Most sets are small.  The code uses signed tests */
    +
    +/*------------------------------------------
    +
    +setT
    +  a set or list of pointers with maximum size and actual size.
    +
    +variations:
    +  unsorted, unique   -- a list of unique pointers with NULL terminator
    +                           user guarantees uniqueness
    +  sorted             -- a sorted list of unique pointers with NULL terminator
    +                           qset_r.c guarantees uniqueness
    +  unsorted           -- a list of pointers terminated with NULL
    +  indexed            -- an array of pointers with NULL elements
    +
    +structure for set of n elements:
    +
    +        --------------
    +        |  maxsize
    +        --------------
    +        |  e[0] - a pointer, may be NULL for indexed sets
    +        --------------
    +        |  e[1]
    +
    +        --------------
    +        |  ...
    +        --------------
    +        |  e[n-1]
    +        --------------
    +        |  e[n] = NULL
    +        --------------
    +        |  ...
    +        --------------
    +        |  e[maxsize] - n+1 or NULL (determines actual size of set)
    +        --------------
    +
    +*/
    +
    +/*-- setelemT -- internal type to allow both pointers and indices
    +*/
    +typedef union setelemT setelemT;
    +union setelemT {
    +  void    *p;
    +  int   i;         /* integer used for e[maxSize] */
    +};
    +
    +struct setT {
    +  int maxsize;          /* maximum number of elements (except NULL) */
    +  setelemT e[1];        /* array of pointers, tail is NULL */
    +                        /* last slot (unless NULL) is actual size+1
    +                           e[maxsize]==NULL or e[e[maxsize]-1]==NULL */
    +                        /* this may generate a warning since e[] contains
    +                           maxsize elements */
    +};
    +
    +/*=========== -constants- =========================*/
    +
    +/*-------------------------------------
    +
    +  SETelemsize
    +    size of a set element in bytes
    +*/
    +#define SETelemsize ((int)sizeof(setelemT))
    +
    +
    +/*=========== -macros- =========================*/
    +
    +/*-------------------------------------
    +
    +   FOREACHsetelement_(type, set, variable)
    +     define FOREACH iterator
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +     no space in "variable)" [DEC Alpha cc compiler]
    +
    +   each iteration:
    +     variable is set element
    +     variablep is one beyond variable.
    +
    +   to repeat an element:
    +     variablep--; / *repeat* /
    +
    +   at exit:
    +     variable is NULL at end of loop
    +
    +   example:
    +     #define FOREACHfacet_( facets ) FOREACHsetelement_( facetT, facets, facet )
    +
    +   notes:
    +     use FOREACHsetelement_i_() if need index or include NULLs
    +
    +   WARNING:
    +     nested loops can't use the same variable (define another FOREACH)
    +
    +     needs braces if nested inside another FOREACH
    +     this includes intervening blocks, e.g. FOREACH...{ if () FOREACH...} )
    +*/
    +#define FOREACHsetelement_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##p= (type **)&((set)->e[0].p); \
    +          (variable= *variable##p++);)
    +
    +/*------------------------------------------
    +
    +   FOREACHsetelement_i_(qh, type, set, variable)
    +     define indexed FOREACH iterator
    +
    +   declare:
    +     type *variable, variable_n, variable_i;
    +
    +   each iteration:
    +     variable is set element, may be NULL
    +     variable_i is index, variable_n is qh_setsize()
    +
    +   to repeat an element:
    +     variable_i--; variable_n-- repeats for deleted element
    +
    +   at exit:
    +     variable==NULL and variable_i==variable_n
    +
    +   example:
    +     #define FOREACHfacet_i_( qh, facets ) FOREACHsetelement_i_( qh, facetT, facets, facet )
    +
    +   WARNING:
    +     nested loops can't use the same variable (define another FOREACH)
    +
    +     needs braces if nested inside another FOREACH
    +     this includes intervening blocks, e.g. FOREACH...{ if () FOREACH...} )
    +*/
    +#define FOREACHsetelement_i_(qh, type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##_i= 0, variable= (type *)((set)->e[0].p), \
    +                   variable##_n= qh_setsize(qh, set);\
    +          variable##_i < variable##_n;\
    +          variable= (type *)((set)->e[++variable##_i].p) )
    +
    +/*----------------------------------------
    +
    +   FOREACHsetelementreverse_(qh, type, set, variable)-
    +     define FOREACH iterator in reverse order
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +     also declare 'int variabletemp'
    +
    +   each iteration:
    +     variable is set element
    +
    +   to repeat an element:
    +     variabletemp++; / *repeat* /
    +
    +   at exit:
    +     variable is NULL
    +
    +   example:
    +     #define FOREACHvertexreverse_( vertices ) FOREACHsetelementreverse_( vertexT, vertices, vertex )
    +
    +   notes:
    +     use FOREACHsetelementreverse12_() to reverse first two elements
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHsetelementreverse_(qh, type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +           variable##temp= qh_setsize(qh, set)-1, variable= qh_setlast(qh, set);\
    +           variable; variable= \
    +           ((--variable##temp >= 0) ? SETelemt_(set, variable##temp, type) : NULL))
    +
    +/*-------------------------------------
    +
    +   FOREACHsetelementreverse12_(type, set, variable)-
    +     define FOREACH iterator with e[1] and e[0] reversed
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +
    +   each iteration:
    +     variable is set element
    +     variablep is one after variable.
    +
    +   to repeat an element:
    +     variablep--; / *repeat* /
    +
    +   at exit:
    +     variable is NULL at end of loop
    +
    +   example
    +     #define FOREACHvertexreverse12_( vertices ) FOREACHsetelementreverse12_( vertexT, vertices, vertex )
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHsetelementreverse12_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##p= (type **)&((set)->e[1].p); \
    +          (variable= *variable##p); \
    +          variable##p == ((type **)&((set)->e[0].p))?variable##p += 2: \
    +              (variable##p == ((type **)&((set)->e[1].p))?variable##p--:variable##p++))
    +
    +/*-------------------------------------
    +
    +   FOREACHelem_( set )-
    +     iterate elements in a set
    +
    +   declare:
    +     void *elem, *elemp;
    +
    +   each iteration:
    +     elem is set element
    +     elemp is one beyond
    +
    +   to repeat an element:
    +     elemp--; / *repeat* /
    +
    +   at exit:
    +     elem == NULL at end of loop
    +
    +   example:
    +     FOREACHelem_(set) {
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHelem_(set) FOREACHsetelement_(void, set, elem)
    +
    +/*-------------------------------------
    +
    +   FOREACHset_( set )-
    +     iterate a set of sets
    +
    +   declare:
    +     setT *set, **setp;
    +
    +   each iteration:
    +     set is set element
    +     setp is one beyond
    +
    +   to repeat an element:
    +     setp--; / *repeat* /
    +
    +   at exit:
    +     set == NULL at end of loop
    +
    +   example
    +     FOREACHset_(sets) {
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHset_(sets) FOREACHsetelement_(setT, sets, set)
    +
    +/*-------------------------------------------
    +
    +   SETindex_( set, elem )
    +     return index of elem in set
    +
    +   notes:
    +     for use with FOREACH iteration
    +     WARN64 -- Maximum set size is 2G
    +
    +   example:
    +     i= SETindex_(ridges, ridge)
    +*/
    +#define SETindex_(set, elem) ((int)((void **)elem##p - (void **)&(set)->e[1].p))
    +
    +/*-----------------------------------------
    +
    +   SETref_( elem )
    +     l.h.s. for modifying the current element in a FOREACH iteration
    +
    +   example:
    +     SETref_(ridge)= anotherridge;
    +*/
    +#define SETref_(elem) (elem##p[-1])
    +
    +/*-----------------------------------------
    +
    +   SETelem_(set, n)
    +     return the n'th element of set
    +
    +   notes:
    +      assumes that n is valid [0..size] and that set is defined
    +      use SETelemt_() for type cast
    +*/
    +#define SETelem_(set, n)           ((set)->e[n].p)
    +
    +/*-----------------------------------------
    +
    +   SETelemt_(set, n, type)
    +     return the n'th element of set as a type
    +
    +   notes:
    +      assumes that n is valid [0..size] and that set is defined
    +*/
    +#define SETelemt_(set, n, type)    ((type*)((set)->e[n].p))
    +
    +/*-----------------------------------------
    +
    +   SETelemaddr_(set, n, type)
    +     return address of the n'th element of a set
    +
    +   notes:
    +      assumes that n is valid [0..size] and set is defined
    +*/
    +#define SETelemaddr_(set, n, type) ((type **)(&((set)->e[n].p)))
    +
    +/*-----------------------------------------
    +
    +   SETfirst_(set)
    +     return first element of set
    +
    +*/
    +#define SETfirst_(set)             ((set)->e[0].p)
    +
    +/*-----------------------------------------
    +
    +   SETfirstt_(set, type)
    +     return first element of set as a type
    +
    +*/
    +#define SETfirstt_(set, type)      ((type*)((set)->e[0].p))
    +
    +/*-----------------------------------------
    +
    +   SETsecond_(set)
    +     return second element of set
    +
    +*/
    +#define SETsecond_(set)            ((set)->e[1].p)
    +
    +/*-----------------------------------------
    +
    +   SETsecondt_(set, type)
    +     return second element of set as a type
    +*/
    +#define SETsecondt_(set, type)     ((type*)((set)->e[1].p))
    +
    +/*-----------------------------------------
    +
    +   SETaddr_(set, type)
    +       return address of set's elements
    +*/
    +#define SETaddr_(set,type)         ((type **)(&((set)->e[0].p)))
    +
    +/*-----------------------------------------
    +
    +   SETreturnsize_(set, size)
    +     return size of a set
    +
    +   notes:
    +      set must be defined
    +      use qh_setsize(qhT *qh, set) unless speed is critical
    +*/
    +#define SETreturnsize_(set, size) (((size)= ((set)->e[(set)->maxsize].i))?(--(size)):((size)= (set)->maxsize))
    +
    +/*-----------------------------------------
    +
    +   SETempty_(set)
    +     return true(1) if set is empty
    +
    +   notes:
    +      set may be NULL
    +*/
    +#define SETempty_(set)            (!set || (SETfirst_(set) ? 0 : 1))
    +
    +/*---------------------------------
    +
    +  SETsizeaddr_(set)
    +    return pointer to 'actual size+1' of set (set CANNOT be NULL!!)
    +    Its type is setelemT* for strict aliasing
    +    All SETelemaddr_ must be cast to setelemT
    +
    +
    +  notes:
    +    *SETsizeaddr==NULL or e[*SETsizeaddr-1].p==NULL
    +*/
    +#define SETsizeaddr_(set) (&((set)->e[(set)->maxsize]))
    +
    +/*-----------------------------------------
    +
    +   SETtruncate_(set, size)
    +     truncate set to size
    +
    +   see:
    +     qh_settruncate()
    +
    +*/
    +#define SETtruncate_(set, size) {set->e[set->maxsize].i= size+1; /* maybe overwritten */ \
    +      set->e[size].p= NULL;}
    +
    +/*======= prototypes in alphabetical order ============*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void  qh_setaddsorted(qhT *qh, setT **setp, void *elem);
    +void  qh_setaddnth(qhT *qh, setT **setp, int nth, void *newelem);
    +void  qh_setappend(qhT *qh, setT **setp, void *elem);
    +void  qh_setappend_set(qhT *qh, setT **setp, setT *setA);
    +void  qh_setappend2ndlast(qhT *qh, setT **setp, void *elem);
    +void  qh_setcheck(qhT *qh, setT *set, const char *tname, unsigned id);
    +void  qh_setcompact(qhT *qh, setT *set);
    +setT *qh_setcopy(qhT *qh, setT *set, int extra);
    +void *qh_setdel(setT *set, void *elem);
    +void *qh_setdellast(setT *set);
    +void *qh_setdelnth(qhT *qh, setT *set, int nth);
    +void *qh_setdelnthsorted(qhT *qh, setT *set, int nth);
    +void *qh_setdelsorted(setT *set, void *newelem);
    +setT *qh_setduplicate(qhT *qh, setT *set, int elemsize);
    +void **qh_setendpointer(setT *set);
    +int   qh_setequal(setT *setA, setT *setB);
    +int   qh_setequal_except(setT *setA, void *skipelemA, setT *setB, void *skipelemB);
    +int   qh_setequal_skip(setT *setA, int skipA, setT *setB, int skipB);
    +void  qh_setfree(qhT *qh, setT **set);
    +void  qh_setfree2(qhT *qh, setT **setp, int elemsize);
    +void  qh_setfreelong(qhT *qh, setT **set);
    +int   qh_setin(setT *set, void *setelem);
    +int qh_setindex(setT *set, void *setelem);
    +void  qh_setlarger(qhT *qh, setT **setp);
    +void *qh_setlast(setT *set);
    +setT *qh_setnew(qhT *qh, int size);
    +setT *qh_setnew_delnthsorted(qhT *qh, setT *set, int size, int nth, int prepend);
    +void  qh_setprint(qhT *qh, FILE *fp, const char* string, setT *set);
    +void  qh_setreplace(qhT *qh, setT *set, void *oldelem, void *newelem);
    +int qh_setsize(qhT *qh, setT *set);
    +setT *qh_settemp(qhT *qh, int setsize);
    +void  qh_settempfree(qhT *qh, setT **set);
    +void  qh_settempfree_all(qhT *qh);
    +setT *qh_settemppop(qhT *qh);
    +void  qh_settemppush(qhT *qh, setT *set);
    +void  qh_settruncate(qhT *qh, setT *set, int size);
    +int   qh_setunique(qhT *qh, setT **set, void *elem);
    +void  qh_setzero(qhT *qh, setT *set, int idx, int size);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFset */
    diff --git a/xs/src/qhull/src/libqhull_r/random_r.c b/xs/src/qhull/src/libqhull_r/random_r.c
    new file mode 100644
    index 0000000000..1fefb51c36
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/random_r.c
    @@ -0,0 +1,247 @@
    +/*
      ---------------------------------
    +
    +   random_r.c and utilities
    +     Park & Miller's minimimal standard random number generator
    +     argc/argv conversion
    +
    +     Used by rbox.  Do not use 'qh' 
    +*/
    +
    +#include "libqhull_r.h"
    +#include "random_r.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#pragma warning( disable : 4996)  /* function was declared deprecated(strcpy, localtime, etc.) */
    +#endif
    +
    +/*---------------------------------
    +
    + qh_argv_to_command(argc, argv, command, max_size )
    +
    +    build command from argc/argv
    +    max_size is at least
    +
    + returns:
    +    a space-delimited string of options (just as typed)
    +    returns false if max_size is too short
    +
    + notes:
    +    silently removes
    +    makes option string easy to input and output
    +    matches qh_argv_to_command_size()
    +
    +    argc may be 0
    +*/
    +int qh_argv_to_command(int argc, char *argv[], char* command, int max_size) {
    +  int i, remaining;
    +  char *s;
    +  *command= '\0';  /* max_size > 0 */
    +
    +  if (argc) {
    +    if ((s= strrchr( argv[0], '\\')) /* get filename w/o .exe extension */
    +    || (s= strrchr( argv[0], '/')))
    +        s++;
    +    else
    +        s= argv[0];
    +    if ((int)strlen(s) < max_size)   /* WARN64 */
    +        strcpy(command, s);
    +    else
    +        goto error_argv;
    +    if ((s= strstr(command, ".EXE"))
    +    ||  (s= strstr(command, ".exe")))
    +        *s= '\0';
    +  }
    +  for (i=1; i < argc; i++) {
    +    s= argv[i];
    +    remaining= max_size - (int)strlen(command) - (int)strlen(s) - 2;   /* WARN64 */
    +    if (!*s || strchr(s, ' ')) {
    +      char *t= command + strlen(command);
    +      remaining -= 2;
    +      if (remaining < 0) {
    +        goto error_argv;
    +      }
    +      *t++= ' ';
    +      *t++= '"';
    +      while (*s) {
    +        if (*s == '"') {
    +          if (--remaining < 0)
    +            goto error_argv;
    +          *t++= '\\';
    +        }
    +        *t++= *s++;
    +      }
    +      *t++= '"';
    +      *t= '\0';
    +    }else if (remaining < 0) {
    +      goto error_argv;
    +    }else
    +      strcat(command, " ");
    +      strcat(command, s);
    +  }
    +  return 1;
    +
    +error_argv:
    +  return 0;
    +} /* argv_to_command */
    +
    +/*---------------------------------
    +
    +qh_argv_to_command_size(argc, argv )
    +
    +    return size to allocate for qh_argv_to_command()
    +
    +notes:
    +    argc may be 0
    +    actual size is usually shorter
    +*/
    +int qh_argv_to_command_size(int argc, char *argv[]) {
    +    unsigned int count= 1; /* null-terminator if argc==0 */
    +    int i;
    +    char *s;
    +
    +    for (i=0; i0 && strchr(argv[i], ' ')) {
    +        count += 2;  /* quote delimiters */
    +        for (s=argv[i]; *s; s++) {
    +          if (*s == '"') {
    +            count++;
    +          }
    +        }
    +      }
    +    }
    +    return count;
    +} /* argv_to_command_size */
    +
    +/*---------------------------------
    +
    +  qh_rand()
    +  qh_srand(qh, seed )
    +    generate pseudo-random number between 1 and 2^31 -2
    +
    +  notes:
    +    For qhull and rbox, called from qh_RANDOMint(),etc. [user.h]
    +
    +    From Park & Miller's minimal standard random number generator
    +      Communications of the ACM, 31:1192-1201, 1988.
    +    Does not use 0 or 2^31 -1
    +      this is silently enforced by qh_srand()
    +    Can make 'Rn' much faster by moving qh_rand to qh_distplane
    +*/
    +
    +/* Global variables and constants */
    +
    +#define qh_rand_a 16807
    +#define qh_rand_m 2147483647
    +#define qh_rand_q 127773  /* m div a */
    +#define qh_rand_r 2836    /* m mod a */
    +
    +int qh_rand(qhT *qh) {
    +    int lo, hi, test;
    +    int seed = qh->last_random;
    +
    +    hi = seed / qh_rand_q;  /* seed div q */
    +    lo = seed % qh_rand_q;  /* seed mod q */
    +    test = qh_rand_a * lo - qh_rand_r * hi;
    +    if (test > 0)
    +        seed= test;
    +    else
    +        seed= test + qh_rand_m;
    +    qh->last_random= seed;
    +    /* seed = seed < qh_RANDOMmax/2 ? 0 : qh_RANDOMmax;  for testing */
    +    /* seed = qh_RANDOMmax;  for testing */
    +    return seed;
    +} /* rand */
    +
    +void qh_srand(qhT *qh, int seed) {
    +    if (seed < 1)
    +        qh->last_random= 1;
    +    else if (seed >= qh_rand_m)
    +        qh->last_random= qh_rand_m - 1;
    +    else
    +        qh->last_random= seed;
    +} /* qh_srand */
    +
    +/*---------------------------------
    +
    +qh_randomfactor(qh, scale, offset )
    +  return a random factor r * scale + offset
    +
    +notes:
    +  qh.RANDOMa/b are defined in global_r.c
    +  qh_RANDOMint requires 'qh'
    +*/
    +realT qh_randomfactor(qhT *qh, realT scale, realT offset) {
    +    realT randr;
    +
    +    randr= qh_RANDOMint;
    +    return randr * scale + offset;
    +} /* randomfactor */
    +
    +/*---------------------------------
    +
    +qh_randommatrix(qh, buffer, dim, rows )
    +  generate a random dim X dim matrix in range [-1,1]
    +  assumes buffer is [dim+1, dim]
    +
    +  returns:
    +    sets buffer to random numbers
    +    sets rows to rows of buffer
    +    sets row[dim] as scratch row
    +
    +  notes:
    +    qh_RANDOMint requires 'qh'
    +*/
    +void qh_randommatrix(qhT *qh, realT *buffer, int dim, realT **rows) {
    +    int i, k;
    +    realT **rowi, *coord, realr;
    +
    +    coord= buffer;
    +    rowi= rows;
    +    for (i=0; i < dim; i++) {
    +        *(rowi++)= coord;
    +        for (k=0; k < dim; k++) {
    +            realr= qh_RANDOMint;
    +            *(coord++)= 2.0 * realr/(qh_RANDOMmax+1) - 1.0;
    +        }
    +    }
    +    *rowi= coord;
    +} /* randommatrix */
    +
    +/*---------------------------------
    +
    +  qh_strtol( s, endp) qh_strtod( s, endp)
    +    internal versions of strtol() and strtod()
    +    does not skip trailing spaces
    +  notes:
    +    some implementations of strtol()/strtod() skip trailing spaces
    +*/
    +double qh_strtod(const char *s, char **endp) {
    +  double result;
    +
    +  result= strtod(s, endp);
    +  if (s < (*endp) && (*endp)[-1] == ' ')
    +    (*endp)--;
    +  return result;
    +} /* strtod */
    +
    +int qh_strtol(const char *s, char **endp) {
    +  int result;
    +
    +  result= (int) strtol(s, endp, 10);     /* WARN64 */
    +  if (s< (*endp) && (*endp)[-1] == ' ')
    +    (*endp)--;
    +  return result;
    +} /* strtol */
    diff --git a/xs/src/qhull/src/libqhull_r/random_r.h b/xs/src/qhull/src/libqhull_r/random_r.h
    new file mode 100644
    index 0000000000..dc884b33cb
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/random_r.h
    @@ -0,0 +1,41 @@
    +/*
      ---------------------------------
    +
    +  random.h
    +    header file for random and utility routines
    +
    +   see qh-geom_r.htm and random_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/random_r.h#4 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFrandom
    +#define qhDEFrandom 1
    +
    +#include "libqhull_r.h"
    +
    +/*============= prototypes in alphabetical order ======= */
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +int     qh_argv_to_command(int argc, char *argv[], char* command, int max_size);
    +int     qh_argv_to_command_size(int argc, char *argv[]);
    +int     qh_rand(qhT *qh);
    +void    qh_srand(qhT *qh, int seed);
    +realT   qh_randomfactor(qhT *qh, realT scale, realT offset);
    +void    qh_randommatrix(qhT *qh, realT *buffer, int dim, realT **row);
    +int     qh_strtol(const char *s, char **endp);
    +double  qh_strtod(const char *s, char **endp);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFrandom */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/rboxlib_r.c b/xs/src/qhull/src/libqhull_r/rboxlib_r.c
    new file mode 100644
    index 0000000000..6c0c7e35ef
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/rboxlib_r.c
    @@ -0,0 +1,842 @@
    +/*
      ---------------------------------
    +
    +   rboxlib_r.c
    +     Generate input points
    +
    +   notes:
    +     For documentation, see prompt[] of rbox_r.c
    +     50 points generated for 'rbox D4'
    +
    +   WARNING:
    +     incorrect range if qh_RANDOMmax is defined wrong (user_r.h)
    +*/
    +
    +#include "libqhull_r.h"  /* First for user_r.h */
    +#include "random_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ */
    +#pragma warning( disable : 4706)  /* assignment within conditional expression. */
    +#pragma warning( disable : 4996)  /* this function (strncat,sprintf,strcpy) or variable may be unsafe. */
    +#endif
    +
    +#define MAXdim 200
    +#define PI 3.1415926535897932384
    +
    +/* ------------------------------ prototypes ----------------*/
    +int qh_roundi(qhT *qh, double a);
    +void qh_out1(qhT *qh, double a);
    +void qh_out2n(qhT *qh, double a, double b);
    +void qh_out3n(qhT *qh, double a, double b, double c);
    +void qh_outcoord(qhT *qh, int iscdd, double *coord, int dim);
    +void qh_outcoincident(qhT *qh, int coincidentpoints, double radius, int iscdd, double *coord, int dim);
    +
    +void    qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +int     qh_rand(qhT *qh);
    +void    qh_srand(qhT *qh, int seed);
    +
    +/*---------------------------------
    +
    +  qh_rboxpoints(qh, rbox_command )
    +    Generate points to qh->fout according to rbox options
    +    Report errors on qh->ferr
    +
    +  returns:
    +    0 (qh_ERRnone) on success
    +    1 (qh_ERRinput) on input error
    +    4 (qh_ERRmem) on memory error
    +    5 (qh_ERRqhull) on internal error
    +
    +  notes:
    +    To avoid using stdio, redefine qh_malloc, qh_free, and qh_fprintf_rbox (user_r.c)
    +
    +  design:
    +    Straight line code (consider defining a struct and functions):
    +
    +    Parse arguments into variables
    +    Determine the number of points
    +    Generate the points
    +*/
    +int qh_rboxpoints(qhT *qh, char* rbox_command) {
    +  int i,j,k;
    +  int gendim;
    +  int coincidentcount=0, coincidenttotal=0, coincidentpoints=0;
    +  int cubesize, diamondsize, seed=0, count, apex;
    +  int dim=3 , numpoints= 0, totpoints, addpoints=0;
    +  int issphere=0, isaxis=0,  iscdd= 0, islens= 0, isregular=0, iswidth=0, addcube=0;
    +  int isgap=0, isspiral=0, NOcommand= 0, adddiamond=0;
    +  int israndom=0, istime=0;
    +  int isbox=0, issimplex=0, issimplex2=0, ismesh=0;
    +  double width=0.0, gap=0.0, radius=0.0, coincidentradius=0.0;
    +  double coord[MAXdim], offset, meshm=3.0, meshn=4.0, meshr=5.0;
    +  double *coordp, *simplex= NULL, *simplexp;
    +  int nthroot, mult[MAXdim];
    +  double norm, factor, randr, rangap, lensangle= 0, lensbase= 1;
    +  double anglediff, angle, x, y, cube= 0.0, diamond= 0.0;
    +  double box= qh_DEFAULTbox; /* scale all numbers before output */
    +  double randmax= qh_RANDOMmax;
    +  char command[200], seedbuf[200];
    +  char *s= command, *t, *first_point= NULL;
    +  time_t timedata;
    +  int exitcode;
    +
    +  exitcode= setjmp(qh->rbox_errexit);
    +  if (exitcode) {
    +    /* same code for error exit and normal return, qh->NOerrexit is set */
    +    if (simplex)
    +        qh_free(simplex);
    +    return exitcode;
    +  }
    +
    +  *command= '\0';
    +  strncat(command, rbox_command, sizeof(command)-strlen(command)-1);
    +
    +  while (*s && !isspace(*s))  /* skip program name */
    +    s++;
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    if (isdigit(*s)) {
    +      numpoints= qh_strtol(s, &s);
    +      continue;
    +    }
    +    /* ============= read flags =============== */
    +    switch (*s++) {
    +    case 'c':
    +      addcube= 1;
    +      t= s;
    +      while (isspace(*t))
    +        t++;
    +      if (*t == 'G')
    +        cube= qh_strtod(++t, &s);
    +      break;
    +    case 'd':
    +      adddiamond= 1;
    +      t= s;
    +      while (isspace(*t))
    +        t++;
    +      if (*t == 'G')
    +        diamond= qh_strtod(++t, &s);
    +      break;
    +    case 'h':
    +      iscdd= 1;
    +      break;
    +    case 'l':
    +      isspiral= 1;
    +      break;
    +    case 'n':
    +      NOcommand= 1;
    +      break;
    +    case 'r':
    +      isregular= 1;
    +      break;
    +    case 's':
    +      issphere= 1;
    +      break;
    +    case 't':
    +      istime= 1;
    +      if (isdigit(*s)) {
    +        seed= qh_strtol(s, &s);
    +        israndom= 0;
    +      }else
    +        israndom= 1;
    +      break;
    +    case 'x':
    +      issimplex= 1;
    +      break;
    +    case 'y':
    +      issimplex2= 1;
    +      break;
    +    case 'z':
    +      qh->rbox_isinteger= 1;
    +      break;
    +    case 'B':
    +      box= qh_strtod(s, &s);
    +      isbox= 1;
    +      break;
    +    case 'C':
    +      if (*s)
    +        coincidentpoints=  qh_strtol(s, &s);
    +      if (*s == ',') {
    +        ++s;
    +        coincidentradius=  qh_strtod(s, &s);
    +      }
    +      if (*s == ',') {
    +        ++s;
    +        coincidenttotal=  qh_strtol(s, &s);
    +      }
    +      if (*s && !isspace(*s)) {
    +        qh_fprintf_rbox(qh, qh->ferr, 7080, "rbox error: arguments for 'Cn,r,m' are not 'int', 'float', and 'int'.  Remaining string is '%s'\n", s);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      if (coincidentpoints==0){
    +        qh_fprintf_rbox(qh, qh->ferr, 6268, "rbox error: missing arguments for 'Cn,r,m' where n is the number of coincident points, r is the radius (default 0.0), and m is the number of points\n");
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      if (coincidentpoints<0 || coincidenttotal<0 || coincidentradius<0.0){
    +        qh_fprintf_rbox(qh, qh->ferr, 6269, "rbox error: negative arguments for 'Cn,m,r' where n (%d) is the number of coincident points, m (%d) is the number of points, and r (%.2g) is the radius (default 0.0)\n", coincidentpoints, coincidenttotal, coincidentradius);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      break;
    +    case 'D':
    +      dim= qh_strtol(s, &s);
    +      if (dim < 1
    +      || dim > MAXdim) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6189, "rbox error: dimension, D%d, out of bounds (>=%d or <=0)", dim, MAXdim);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      break;
    +    case 'G':
    +      if (isdigit(*s))
    +        gap= qh_strtod(s, &s);
    +      else
    +        gap= 0.5;
    +      isgap= 1;
    +      break;
    +    case 'L':
    +      if (isdigit(*s))
    +        radius= qh_strtod(s, &s);
    +      else
    +        radius= 10;
    +      islens= 1;
    +      break;
    +    case 'M':
    +      ismesh= 1;
    +      if (*s)
    +        meshn= qh_strtod(s, &s);
    +      if (*s == ',') {
    +        ++s;
    +        meshm= qh_strtod(s, &s);
    +      }else
    +        meshm= 0.0;
    +      if (*s == ',') {
    +        ++s;
    +        meshr= qh_strtod(s, &s);
    +      }else
    +        meshr= sqrt(meshn*meshn + meshm*meshm);
    +      if (*s && !isspace(*s)) {
    +        qh_fprintf_rbox(qh, qh->ferr, 7069, "rbox warning: assuming 'M3,4,5' since mesh args are not integers or reals\n");
    +        meshn= 3.0, meshm=4.0, meshr=5.0;
    +      }
    +      break;
    +    case 'O':
    +      qh->rbox_out_offset= qh_strtod(s, &s);
    +      break;
    +    case 'P':
    +      if (!first_point)
    +        first_point= s-1;
    +      addpoints++;
    +      while (*s && !isspace(*s))   /* read points later */
    +        s++;
    +      break;
    +    case 'W':
    +      width= qh_strtod(s, &s);
    +      iswidth= 1;
    +      break;
    +    case 'Z':
    +      if (isdigit(*s))
    +        radius= qh_strtod(s, &s);
    +      else
    +        radius= 1.0;
    +      isaxis= 1;
    +      break;
    +    default:
    +      qh_fprintf_rbox(qh, qh->ferr, 7070, "rbox error: unknown flag at %s.\nExecute 'rbox' without arguments for documentation.\n", s);
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    if (*s && !isspace(*s)) {
    +      qh_fprintf_rbox(qh, qh->ferr, 7071, "rbox error: missing space between flags at %s.\n", s);
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +  }
    +
    +  /* ============= defaults, constants, and sizes =============== */
    +  if (qh->rbox_isinteger && !isbox)
    +    box= qh_DEFAULTzbox;
    +  if (addcube) {
    +    cubesize= (int)floor(ldexp(1.0,dim)+0.5);
    +    if (cube == 0.0)
    +      cube= box;
    +  }else
    +    cubesize= 0;
    +  if (adddiamond) {
    +    diamondsize= 2*dim;
    +    if (diamond == 0.0)
    +      diamond= box;
    +  }else
    +    diamondsize= 0;
    +  if (islens) {
    +    if (isaxis) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6190, "rbox error: can not combine 'Ln' with 'Zn'\n");
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    if (radius <= 1.0) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6191, "rbox error: lens radius %.2g should be greater than 1.0\n",
    +               radius);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    lensangle= asin(1.0/radius);
    +    lensbase= radius * cos(lensangle);
    +  }
    +
    +  if (!numpoints) {
    +    if (issimplex2)
    +        ; /* ok */
    +    else if (isregular + issimplex + islens + issphere + isaxis + isspiral + iswidth + ismesh) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6192, "rbox error: missing count\n");
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +    }else if (adddiamond + addcube + addpoints)
    +        ; /* ok */
    +    else {
    +        numpoints= 50;  /* ./rbox D4 is the test case */
    +        issphere= 1;
    +    }
    +  }
    +  if ((issimplex + islens + isspiral + ismesh > 1)
    +  || (issimplex + issphere + isspiral + ismesh > 1)) {
    +    qh_fprintf_rbox(qh, qh->ferr, 6193, "rbox error: can only specify one of 'l', 's', 'x', 'Ln', or 'Mn,m,r' ('Ln s' is ok).\n");
    +    qh_errexit_rbox(qh, qh_ERRinput);
    +  }
    +  if (coincidentpoints>0 && (numpoints == 0 || coincidenttotal > numpoints)) {
    +    qh_fprintf_rbox(qh, qh->ferr, 6270, "rbox error: 'Cn,r,m' requested n coincident points for each of m points.  Either there is no points or m (%d) is greater than the number of points (%d).\n", coincidenttotal, numpoints);
    +    qh_errexit_rbox(qh, qh_ERRinput);
    +  }
    +  if (coincidenttotal == 0)
    +    coincidenttotal= numpoints;
    +
    +  /* ============= print header with total points =============== */
    +  if (issimplex || ismesh)
    +    totpoints= numpoints;
    +  else if (issimplex2)
    +    totpoints= numpoints+dim+1;
    +  else if (isregular) {
    +    totpoints= numpoints;
    +    if (dim == 2) {
    +        if (islens)
    +          totpoints += numpoints - 2;
    +    }else if (dim == 3) {
    +        if (islens)
    +          totpoints += 2 * numpoints;
    +      else if (isgap)
    +        totpoints += 1 + numpoints;
    +      else
    +        totpoints += 2;
    +    }
    +  }else
    +    totpoints= numpoints + isaxis;
    +  totpoints += cubesize + diamondsize + addpoints;
    +  totpoints += coincidentpoints*coincidenttotal;
    +
    +  /* ============= seed randoms =============== */
    +  if (istime == 0) {
    +    for (s=command; *s; s++) {
    +      if (issimplex2 && *s == 'y') /* make 'y' same seed as 'x' */
    +        i= 'x';
    +      else
    +        i= *s;
    +      seed= 11*seed + i;
    +    }
    +  }else if (israndom) {
    +    seed= (int)time(&timedata);
    +    sprintf(seedbuf, " t%d", seed);  /* appends an extra t, not worth removing */
    +    strncat(command, seedbuf, sizeof(command)-strlen(command)-1);
    +    t= strstr(command, " t ");
    +    if (t)
    +      strcpy(t+1, t+3); /* remove " t " */
    +  } /* else, seed explicitly set to n */
    +  qh_RANDOMseed_(qh, seed);
    +
    +  /* ============= print header =============== */
    +
    +  if (iscdd)
    +      qh_fprintf_rbox(qh, qh->fout, 9391, "%s\nbegin\n        %d %d %s\n",
    +      NOcommand ? "" : command,
    +      totpoints, dim+1,
    +      qh->rbox_isinteger ? "integer" : "real");
    +  else if (NOcommand)
    +      qh_fprintf_rbox(qh, qh->fout, 9392, "%d\n%d\n", dim, totpoints);
    +  else
    +      /* qh_fprintf_rbox special cases 9393 to append 'command' to the RboxPoints.comment() */
    +      qh_fprintf_rbox(qh, qh->fout, 9393, "%d %s\n%d\n", dim, command, totpoints);
    +
    +  /* ============= explicit points =============== */
    +  if ((s= first_point)) {
    +    while (s && *s) { /* 'P' */
    +      count= 0;
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      while (*++s) {
    +        qh_out1(qh, qh_strtod(s, &s));
    +        count++;
    +        if (isspace(*s) || !*s)
    +          break;
    +        if (*s != ',') {
    +          qh_fprintf_rbox(qh, qh->ferr, 6194, "rbox error: missing comma after coordinate in %s\n\n", s);
    +          qh_errexit_rbox(qh, qh_ERRinput);
    +        }
    +      }
    +      if (count < dim) {
    +        for (k=dim-count; k--; )
    +          qh_out1(qh, 0.0);
    +      }else if (count > dim) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6195, "rbox error: %d coordinates instead of %d coordinates in %s\n\n",
    +                  count, dim, s);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      qh_fprintf_rbox(qh, qh->fout, 9394, "\n");
    +      while ((s= strchr(s, 'P'))) {
    +        if (isspace(s[-1]))
    +          break;
    +      }
    +    }
    +  }
    +
    +  /* ============= simplex distribution =============== */
    +  if (issimplex+issimplex2) {
    +    if (!(simplex= (double*)qh_malloc( dim * (dim+1) * sizeof(double)))) {
    +      qh_fprintf_rbox(qh, qh->ferr, 6196, "rbox error: insufficient memory for simplex\n");
    +      qh_errexit_rbox(qh, qh_ERRmem); /* qh_ERRmem */
    +    }
    +    simplexp= simplex;
    +    if (isregular) {
    +      for (i=0; ifout, 9395, "\n");
    +      }
    +    }
    +    for (j=0; jferr, 6197, "rbox error: regular points can be used only in 2-d and 3-d\n\n");
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    if (!isaxis || radius == 0.0) {
    +      isaxis= 1;
    +      radius= 1.0;
    +    }
    +    if (dim == 3) {
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out3n(qh, 0.0, 0.0, -box);
    +      if (!isgap) {
    +        if (iscdd)
    +          qh_out1(qh, 1.0);
    +        qh_out3n(qh, 0.0, 0.0, box);
    +      }
    +    }
    +    angle= 0.0;
    +    anglediff= 2.0 * PI/numpoints;
    +    for (i=0; i < numpoints; i++) {
    +      angle += anglediff;
    +      x= radius * cos(angle);
    +      y= radius * sin(angle);
    +      if (dim == 2) {
    +        if (iscdd)
    +          qh_out1(qh, 1.0);
    +        qh_out2n(qh, x*box, y*box);
    +      }else {
    +        norm= sqrt(1.0 + x*x + y*y);
    +        if (iscdd)
    +          qh_out1(qh, 1.0);
    +        qh_out3n(qh, box*x/norm, box*y/norm, box/norm);
    +        if (isgap) {
    +          x *= 1-gap;
    +          y *= 1-gap;
    +          norm= sqrt(1.0 + x*x + y*y);
    +          if (iscdd)
    +            qh_out1(qh, 1.0);
    +          qh_out3n(qh, box*x/norm, box*y/norm, box/norm);
    +        }
    +      }
    +    }
    +  }
    +  /* ============= regular points for 'r Ln D2' =============== */
    +  else if (isregular && islens && dim == 2) {
    +    double cos_0;
    +
    +    angle= lensangle;
    +    anglediff= 2 * lensangle/(numpoints - 1);
    +    cos_0= cos(lensangle);
    +    for (i=0; i < numpoints; i++, angle -= anglediff) {
    +      x= radius * sin(angle);
    +      y= radius * (cos(angle) - cos_0);
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out2n(qh, x*box, y*box);
    +      if (i != 0 && i != numpoints - 1) {
    +        if (iscdd)
    +          qh_out1(qh, 1.0);
    +        qh_out2n(qh, x*box, -y*box);
    +      }
    +    }
    +  }
    +  /* ============= regular points for 'r Ln D3' =============== */
    +  else if (isregular && islens && dim != 2) {
    +    if (dim != 3) {
    +      qh_free(simplex);
    +      qh_fprintf_rbox(qh, qh->ferr, 6198, "rbox error: regular points can be used only in 2-d and 3-d\n\n");
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    angle= 0.0;
    +    anglediff= 2* PI/numpoints;
    +    if (!isgap) {
    +      isgap= 1;
    +      gap= 0.5;
    +    }
    +    offset= sqrt(radius * radius - (1-gap)*(1-gap)) - lensbase;
    +    for (i=0; i < numpoints; i++, angle += anglediff) {
    +      x= cos(angle);
    +      y= sin(angle);
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out3n(qh, box*x, box*y, 0.0);
    +      x *= 1-gap;
    +      y *= 1-gap;
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out3n(qh, box*x, box*y, box * offset);
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out3n(qh, box*x, box*y, -box * offset);
    +    }
    +  }
    +  /* ============= apex of 'Zn' distribution + gendim =============== */
    +  else {
    +    if (isaxis) {
    +      gendim= dim-1;
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      for (j=0; j < gendim; j++)
    +        qh_out1(qh, 0.0);
    +      qh_out1(qh, -box);
    +      qh_fprintf_rbox(qh, qh->fout, 9398, "\n");
    +    }else if (islens)
    +      gendim= dim-1;
    +    else
    +      gendim= dim;
    +    /* ============= generate random point in unit cube =============== */
    +    for (i=0; i < numpoints; i++) {
    +      norm= 0.0;
    +      for (j=0; j < gendim; j++) {
    +        randr= qh_RANDOMint;
    +        coord[j]= 2.0 * randr/randmax - 1.0;
    +        norm += coord[j] * coord[j];
    +      }
    +      norm= sqrt(norm);
    +      /* ============= dim-1 point of 'Zn' distribution ========== */
    +      if (isaxis) {
    +        if (!isgap) {
    +          isgap= 1;
    +          gap= 1.0;
    +        }
    +        randr= qh_RANDOMint;
    +        rangap= 1.0 - gap * randr/randmax;
    +        factor= radius * rangap / norm;
    +        for (j=0; jferr, 6199, "rbox error: spiral distribution is available only in 3d\n\n");
    +          qh_errexit_rbox(qh, qh_ERRinput);
    +        }
    +        coord[0]= cos(2*PI*i/(numpoints - 1));
    +        coord[1]= sin(2*PI*i/(numpoints - 1));
    +        coord[2]= 2.0*(double)i/(double)(numpoints-1) - 1.0;
    +      /* ============= point of 's' distribution =============== */
    +      }else if (issphere) {
    +        factor= 1.0/norm;
    +        if (iswidth) {
    +          randr= qh_RANDOMint;
    +          factor *= 1.0 - width * randr/randmax;
    +        }
    +        for (j=0; j randmax/2)
    +          coord[dim-1]= -coord[dim-1];
    +      /* ============= project 'Wn' point toward boundary =============== */
    +      }else if (iswidth && !issphere) {
    +        j= qh_RANDOMint % gendim;
    +        if (coord[j] < 0)
    +          coord[j]= -1.0 - coord[j] * width;
    +        else
    +          coord[j]= 1.0 - coord[j] * width;
    +      }
    +      /* ============= scale point to box =============== */
    +      for (k=0; k=0; k--) {
    +        if (j & ( 1 << k))
    +          qh_out1(qh, cube);
    +        else
    +          qh_out1(qh, -cube);
    +      }
    +      qh_fprintf_rbox(qh, qh->fout, 9400, "\n");
    +    }
    +  }
    +
    +  /* ============= write diamond vertices =============== */
    +  if (adddiamond) {
    +    for (j=0; j=0; k--) {
    +        if (j/2 != k)
    +          qh_out1(qh, 0.0);
    +        else if (j & 0x1)
    +          qh_out1(qh, diamond);
    +        else
    +          qh_out1(qh, -diamond);
    +      }
    +      qh_fprintf_rbox(qh, qh->fout, 9401, "\n");
    +    }
    +  }
    +
    +  if (iscdd)
    +    qh_fprintf_rbox(qh, qh->fout, 9402, "end\nhull\n");
    +
    +  /* same code for error exit and normal return */
    +  qh_free(simplex);
    +  return qh_ERRnone;
    +} /* rboxpoints */
    +
    +/*------------------------------------------------
    +outxxx - output functions for qh_rboxpoints
    +*/
    +int qh_roundi(qhT *qh, double a) {
    +  if (a < 0.0) {
    +    if (a - 0.5 < INT_MIN) {
    +      qh_fprintf_rbox(qh, qh->ferr, 6200, "rbox input error: negative coordinate %2.2g is too large.  Reduce 'Bn'\n", a);
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    return (int)(a - 0.5);
    +  }else {
    +    if (a + 0.5 > INT_MAX) {
    +      qh_fprintf_rbox(qh, qh->ferr, 6201, "rbox input error: coordinate %2.2g is too large.  Reduce 'Bn'\n", a);
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    return (int)(a + 0.5);
    +  }
    +} /* qh_roundi */
    +
    +void qh_out1(qhT *qh, double a) {
    +
    +  if (qh->rbox_isinteger)
    +    qh_fprintf_rbox(qh, qh->fout, 9403, "%d ", qh_roundi(qh, a+qh->rbox_out_offset));
    +  else
    +    qh_fprintf_rbox(qh, qh->fout, 9404, qh_REAL_1, a+qh->rbox_out_offset);
    +} /* qh_out1 */
    +
    +void qh_out2n(qhT *qh, double a, double b) {
    +
    +  if (qh->rbox_isinteger)
    +    qh_fprintf_rbox(qh, qh->fout, 9405, "%d %d\n", qh_roundi(qh, a+qh->rbox_out_offset), qh_roundi(qh, b+qh->rbox_out_offset));
    +  else
    +    qh_fprintf_rbox(qh, qh->fout, 9406, qh_REAL_2n, a+qh->rbox_out_offset, b+qh->rbox_out_offset);
    +} /* qh_out2n */
    +
    +void qh_out3n(qhT *qh, double a, double b, double c) {
    +
    +  if (qh->rbox_isinteger)
    +    qh_fprintf_rbox(qh, qh->fout, 9407, "%d %d %d\n", qh_roundi(qh, a+qh->rbox_out_offset), qh_roundi(qh, b+qh->rbox_out_offset), qh_roundi(qh, c+qh->rbox_out_offset));
    +  else
    +    qh_fprintf_rbox(qh, qh->fout, 9408, qh_REAL_3n, a+qh->rbox_out_offset, b+qh->rbox_out_offset, c+qh->rbox_out_offset);
    +} /* qh_out3n */
    +
    +void qh_outcoord(qhT *qh, int iscdd, double *coord, int dim) {
    +    double *p= coord;
    +    int k;
    +
    +    if (iscdd)
    +      qh_out1(qh, 1.0);
    +    for (k=0; k < dim; k++)
    +      qh_out1(qh, *(p++));
    +    qh_fprintf_rbox(qh, qh->fout, 9396, "\n");
    +} /* qh_outcoord */
    +
    +void qh_outcoincident(qhT *qh, int coincidentpoints, double radius, int iscdd, double *coord, int dim) {
    +  double *p;
    +  double randr, delta;
    +  int i,k;
    +  double randmax= qh_RANDOMmax;
    +
    +  for (i= 0; ifout, 9410, "\n");
    +  }
    +} /* qh_outcoincident */
    +
    +/*------------------------------------------------
    +   Only called from qh_rboxpoints or qh_fprintf_rbox
    +   qh_fprintf_rbox is only called from qh_rboxpoints
    +*/
    +void qh_errexit_rbox(qhT *qh, int exitcode)
    +{
    +    longjmp(qh->rbox_errexit, exitcode);
    +} /* qh_errexit_rbox */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/stat_r.c b/xs/src/qhull/src/libqhull_r/stat_r.c
    new file mode 100644
    index 0000000000..0f3ff0d3d4
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/stat_r.c
    @@ -0,0 +1,682 @@
    +/*
      ---------------------------------
    +
    +   stat_r.c
    +   contains all statistics that are collected for qhull
    +
    +   see qh-stat_r.htm and stat_r.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/stat_r.c#5 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*========== functions in alphabetic order ================*/
    +
    +/*---------------------------------
    +
    +  qh_allstatA()
    +    define statistics in groups of 20
    +
    +  notes:
    +    (otherwise, 'gcc -O2' uses too much memory)
    +    uses qhstat.next
    +*/
    +void qh_allstatA(qhT *qh) {
    +
    +   /* zdef_(type,name,doc,average) */
    +  zzdef_(zdoc, Zdoc2, "precision statistics", -1);
    +  zdef_(zinc, Znewvertex, NULL, -1);
    +  zdef_(wadd, Wnewvertex, "ave. distance of a new vertex to a facet(!0s)", Znewvertex);
    +  zzdef_(wmax, Wnewvertexmax, "max. distance of a new vertex to a facet", -1);
    +  zdef_(wmax, Wvertexmax, "max. distance of an output vertex to a facet", -1);
    +  zdef_(wmin, Wvertexmin, "min. distance of an output vertex to a facet", -1);
    +  zdef_(wmin, Wmindenom, "min. denominator in hyperplane computation", -1);
    +
    +  qh->qhstat.precision= qh->qhstat.next;  /* call qh_precision for each of these */
    +  zzdef_(zdoc, Zdoc3, "precision problems (corrected unless 'Q0' or an error)", -1);
    +  zzdef_(zinc, Zcoplanarridges, "coplanar half ridges in output", -1);
    +  zzdef_(zinc, Zconcaveridges, "concave half ridges in output", -1);
    +  zzdef_(zinc, Zflippedfacets, "flipped facets", -1);
    +  zzdef_(zinc, Zcoplanarhorizon, "coplanar horizon facets for new vertices", -1);
    +  zzdef_(zinc, Zcoplanarpart, "coplanar points during partitioning", -1);
    +  zzdef_(zinc, Zminnorm, "degenerate hyperplanes recomputed with gaussian elimination", -1);
    +  zzdef_(zinc, Znearlysingular, "nearly singular or axis-parallel hyperplanes", -1);
    +  zzdef_(zinc, Zback0, "zero divisors during back substitute", -1);
    +  zzdef_(zinc, Zgauss0, "zero divisors during gaussian elimination", -1);
    +  zzdef_(zinc, Zmultiridge, "ridges with multiple neighbors", -1);
    +}
    +void qh_allstatB(qhT *qh) {
    +  zzdef_(zdoc, Zdoc1, "summary information", -1);
    +  zdef_(zinc, Zvertices, "number of vertices in output", -1);
    +  zdef_(zinc, Znumfacets, "number of facets in output", -1);
    +  zdef_(zinc, Znonsimplicial, "number of non-simplicial facets in output", -1);
    +  zdef_(zinc, Znowsimplicial, "number of simplicial facets that were merged", -1);
    +  zdef_(zinc, Znumridges, "number of ridges in output", -1);
    +  zdef_(zadd, Znumridges, "average number of ridges per facet", Znumfacets);
    +  zdef_(zmax, Zmaxridges, "maximum number of ridges", -1);
    +  zdef_(zadd, Znumneighbors, "average number of neighbors per facet", Znumfacets);
    +  zdef_(zmax, Zmaxneighbors, "maximum number of neighbors", -1);
    +  zdef_(zadd, Znumvertices, "average number of vertices per facet", Znumfacets);
    +  zdef_(zmax, Zmaxvertices, "maximum number of vertices", -1);
    +  zdef_(zadd, Znumvneighbors, "average number of neighbors per vertex", Zvertices);
    +  zdef_(zmax, Zmaxvneighbors, "maximum number of neighbors", -1);
    +  zdef_(wadd, Wcpu, "cpu seconds for qhull after input", -1);
    +  zdef_(zinc, Ztotvertices, "vertices created altogether", -1);
    +  zzdef_(zinc, Zsetplane, "facets created altogether", -1);
    +  zdef_(zinc, Ztotridges, "ridges created altogether", -1);
    +  zdef_(zinc, Zpostfacets, "facets before post merge", -1);
    +  zdef_(zadd, Znummergetot, "average merges per facet(at most 511)", Znumfacets);
    +  zdef_(zmax, Znummergemax, "  maximum merges for a facet(at most 511)", -1);
    +  zdef_(zinc, Zangle, NULL, -1);
    +  zdef_(wadd, Wangle, "average angle(cosine) of facet normals for all ridges", Zangle);
    +  zdef_(wmax, Wanglemax, "  maximum angle(cosine) of facet normals across a ridge", -1);
    +  zdef_(wmin, Wanglemin, "  minimum angle(cosine) of facet normals across a ridge", -1);
    +  zdef_(wadd, Wareatot, "total area of facets", -1);
    +  zdef_(wmax, Wareamax, "  maximum facet area", -1);
    +  zdef_(wmin, Wareamin, "  minimum facet area", -1);
    +}
    +void qh_allstatC(qhT *qh) {
    +  zdef_(zdoc, Zdoc9, "build hull statistics", -1);
    +  zzdef_(zinc, Zprocessed, "points processed", -1);
    +  zzdef_(zinc, Zretry, "retries due to precision problems", -1);
    +  zdef_(wmax, Wretrymax, "  max. random joggle", -1);
    +  zdef_(zmax, Zmaxvertex, "max. vertices at any one time", -1);
    +  zdef_(zinc, Ztotvisible, "ave. visible facets per iteration", Zprocessed);
    +  zdef_(zinc, Zinsidevisible, "  ave. visible facets without an horizon neighbor", Zprocessed);
    +  zdef_(zadd, Zvisfacettot,  "  ave. facets deleted per iteration", Zprocessed);
    +  zdef_(zmax, Zvisfacetmax,  "    maximum", -1);
    +  zdef_(zadd, Zvisvertextot, "ave. visible vertices per iteration", Zprocessed);
    +  zdef_(zmax, Zvisvertexmax, "    maximum", -1);
    +  zdef_(zinc, Ztothorizon, "ave. horizon facets per iteration", Zprocessed);
    +  zdef_(zadd, Znewfacettot,  "ave. new or merged facets per iteration", Zprocessed);
    +  zdef_(zmax, Znewfacetmax,  "    maximum(includes initial simplex)", -1);
    +  zdef_(wadd, Wnewbalance, "average new facet balance", Zprocessed);
    +  zdef_(wadd, Wnewbalance2, "  standard deviation", -1);
    +  zdef_(wadd, Wpbalance, "average partition balance", Zpbalance);
    +  zdef_(wadd, Wpbalance2, "  standard deviation", -1);
    +  zdef_(zinc, Zpbalance, "  number of trials", -1);
    +  zdef_(zinc, Zsearchpoints, "searches of all points for initial simplex", -1);
    +  zdef_(zinc, Zdetsimplex, "determinants computed(area & initial hull)", -1);
    +  zdef_(zinc, Znoarea, "determinants not computed because vertex too low", -1);
    +  zdef_(zinc, Znotmax, "points ignored(!above max_outside)", -1);
    +  zdef_(zinc, Znotgood, "points ignored(!above a good facet)", -1);
    +  zdef_(zinc, Znotgoodnew, "points ignored(didn't create a good new facet)", -1);
    +  zdef_(zinc, Zgoodfacet, "good facets found", -1);
    +  zzdef_(zinc, Znumvisibility, "distance tests for facet visibility", -1);
    +  zdef_(zinc, Zdistvertex, "distance tests to report minimum vertex", -1);
    +  zzdef_(zinc, Ztotcheck, "points checked for facets' outer planes", -1);
    +  zzdef_(zinc, Zcheckpart, "  ave. distance tests per check", Ztotcheck);
    +}
    +void qh_allstatD(qhT *qh) {
    +  zdef_(zinc, Zvisit, "resets of visit_id", -1);
    +  zdef_(zinc, Zvvisit, "  resets of vertex_visit", -1);
    +  zdef_(zmax, Zvisit2max, "  max visit_id/2", -1);
    +  zdef_(zmax, Zvvisit2max, "  max vertex_visit/2", -1);
    +
    +  zdef_(zdoc, Zdoc4, "partitioning statistics(see previous for outer planes)", -1);
    +  zzdef_(zadd, Zdelvertextot, "total vertices deleted", -1);
    +  zdef_(zmax, Zdelvertexmax, "    maximum vertices deleted per iteration", -1);
    +  zdef_(zinc, Zfindbest, "calls to findbest", -1);
    +  zdef_(zadd, Zfindbesttot, " ave. facets tested", Zfindbest);
    +  zdef_(zmax, Zfindbestmax, " max. facets tested", -1);
    +  zdef_(zadd, Zfindcoplanar, " ave. coplanar search", Zfindbest);
    +  zdef_(zinc, Zfindnew, "calls to findbestnew", -1);
    +  zdef_(zadd, Zfindnewtot, " ave. facets tested", Zfindnew);
    +  zdef_(zmax, Zfindnewmax, " max. facets tested", -1);
    +  zdef_(zinc, Zfindnewjump, " ave. clearly better", Zfindnew);
    +  zdef_(zinc, Zfindnewsharp, " calls due to qh_sharpnewfacets", -1);
    +  zdef_(zinc, Zfindhorizon, "calls to findhorizon", -1);
    +  zdef_(zadd, Zfindhorizontot, " ave. facets tested", Zfindhorizon);
    +  zdef_(zmax, Zfindhorizonmax, " max. facets tested", -1);
    +  zdef_(zinc, Zfindjump,       " ave. clearly better", Zfindhorizon);
    +  zdef_(zinc, Zparthorizon, " horizon facets better than bestfacet", -1);
    +  zdef_(zinc, Zpartangle, "angle tests for repartitioned coplanar points", -1);
    +  zdef_(zinc, Zpartflip, "  repartitioned coplanar points for flipped orientation", -1);
    +}
    +void qh_allstatE(qhT *qh) {
    +  zdef_(zinc, Zpartinside, "inside points", -1);
    +  zdef_(zinc, Zpartnear, "  inside points kept with a facet", -1);
    +  zdef_(zinc, Zcoplanarinside, "  inside points that were coplanar with a facet", -1);
    +  zdef_(zinc, Zbestlower, "calls to findbestlower", -1);
    +  zdef_(zinc, Zbestlowerv, "  with search of vertex neighbors", -1);
    +  zdef_(zinc, Zbestlowerall, "  with rare search of all facets", -1);
    +  zdef_(zmax, Zbestloweralln, "  facets per search of all facets", -1);
    +  zdef_(wadd, Wmaxout, "difference in max_outside at final check", -1);
    +  zzdef_(zinc, Zpartitionall, "distance tests for initial partition", -1);
    +  zdef_(zinc, Ztotpartition, "partitions of a point", -1);
    +  zzdef_(zinc, Zpartition, "distance tests for partitioning", -1);
    +  zzdef_(zinc, Zdistcheck, "distance tests for checking flipped facets", -1);
    +  zzdef_(zinc, Zdistconvex, "distance tests for checking convexity", -1);
    +  zdef_(zinc, Zdistgood, "distance tests for checking good point", -1);
    +  zdef_(zinc, Zdistio, "distance tests for output", -1);
    +  zdef_(zinc, Zdiststat, "distance tests for statistics", -1);
    +  zdef_(zinc, Zdistplane, "total number of distance tests", -1);
    +  zdef_(zinc, Ztotpartcoplanar, "partitions of coplanar points or deleted vertices", -1);
    +  zzdef_(zinc, Zpartcoplanar, "   distance tests for these partitions", -1);
    +  zdef_(zinc, Zcomputefurthest, "distance tests for computing furthest", -1);
    +}
    +void qh_allstatE2(qhT *qh) {
    +  zdef_(zdoc, Zdoc5, "statistics for matching ridges", -1);
    +  zdef_(zinc, Zhashlookup, "total lookups for matching ridges of new facets", -1);
    +  zdef_(zinc, Zhashtests, "average number of tests to match a ridge", Zhashlookup);
    +  zdef_(zinc, Zhashridge, "total lookups of subridges(duplicates and boundary)", -1);
    +  zdef_(zinc, Zhashridgetest, "average number of tests per subridge", Zhashridge);
    +  zdef_(zinc, Zdupsame, "duplicated ridges in same merge cycle", -1);
    +  zdef_(zinc, Zdupflip, "duplicated ridges with flipped facets", -1);
    +
    +  zdef_(zdoc, Zdoc6, "statistics for determining merges", -1);
    +  zdef_(zinc, Zangletests, "angles computed for ridge convexity", -1);
    +  zdef_(zinc, Zbestcentrum, "best merges used centrum instead of vertices",-1);
    +  zzdef_(zinc, Zbestdist, "distance tests for best merge", -1);
    +  zzdef_(zinc, Zcentrumtests, "distance tests for centrum convexity", -1);
    +  zzdef_(zinc, Zdistzero, "distance tests for checking simplicial convexity", -1);
    +  zdef_(zinc, Zcoplanarangle, "coplanar angles in getmergeset", -1);
    +  zdef_(zinc, Zcoplanarcentrum, "coplanar centrums in getmergeset", -1);
    +  zdef_(zinc, Zconcaveridge, "concave ridges in getmergeset", -1);
    +}
    +void qh_allstatF(qhT *qh) {
    +  zdef_(zdoc, Zdoc7, "statistics for merging", -1);
    +  zdef_(zinc, Zpremergetot, "merge iterations", -1);
    +  zdef_(zadd, Zmergeinittot, "ave. initial non-convex ridges per iteration", Zpremergetot);
    +  zdef_(zadd, Zmergeinitmax, "  maximum", -1);
    +  zdef_(zadd, Zmergesettot, "  ave. additional non-convex ridges per iteration", Zpremergetot);
    +  zdef_(zadd, Zmergesetmax, "  maximum additional in one pass", -1);
    +  zdef_(zadd, Zmergeinittot2, "initial non-convex ridges for post merging", -1);
    +  zdef_(zadd, Zmergesettot2, "  additional non-convex ridges", -1);
    +  zdef_(wmax, Wmaxoutside, "max distance of vertex or coplanar point above facet(w/roundoff)", -1);
    +  zdef_(wmin, Wminvertex, "max distance of merged vertex below facet(or roundoff)", -1);
    +  zdef_(zinc, Zwidefacet, "centrums frozen due to a wide merge", -1);
    +  zdef_(zinc, Zwidevertices, "centrums frozen due to extra vertices", -1);
    +  zzdef_(zinc, Ztotmerge, "total number of facets or cycles of facets merged", -1);
    +  zdef_(zinc, Zmergesimplex, "merged a simplex", -1);
    +  zdef_(zinc, Zonehorizon, "simplices merged into coplanar horizon", -1);
    +  zzdef_(zinc, Zcyclehorizon, "cycles of facets merged into coplanar horizon", -1);
    +  zzdef_(zadd, Zcyclefacettot, "  ave. facets per cycle", Zcyclehorizon);
    +  zdef_(zmax, Zcyclefacetmax, "  max. facets", -1);
    +  zdef_(zinc, Zmergeintohorizon, "new facets merged into horizon", -1);
    +  zdef_(zinc, Zmergenew, "new facets merged", -1);
    +  zdef_(zinc, Zmergehorizon, "horizon facets merged into new facets", -1);
    +  zdef_(zinc, Zmergevertex, "vertices deleted by merging", -1);
    +  zdef_(zinc, Zcyclevertex, "vertices deleted by merging into coplanar horizon", -1);
    +  zdef_(zinc, Zdegenvertex, "vertices deleted by degenerate facet", -1);
    +  zdef_(zinc, Zmergeflipdup, "merges due to flipped facets in duplicated ridge", -1);
    +  zdef_(zinc, Zneighbor, "merges due to redundant neighbors", -1);
    +  zdef_(zadd, Ztestvneighbor, "non-convex vertex neighbors", -1);
    +}
    +void qh_allstatG(qhT *qh) {
    +  zdef_(zinc, Zacoplanar, "merges due to angle coplanar facets", -1);
    +  zdef_(wadd, Wacoplanartot, "  average merge distance", Zacoplanar);
    +  zdef_(wmax, Wacoplanarmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zcoplanar, "merges due to coplanar facets", -1);
    +  zdef_(wadd, Wcoplanartot, "  average merge distance", Zcoplanar);
    +  zdef_(wmax, Wcoplanarmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zconcave, "merges due to concave facets", -1);
    +  zdef_(wadd, Wconcavetot, "  average merge distance", Zconcave);
    +  zdef_(wmax, Wconcavemax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zavoidold, "coplanar/concave merges due to avoiding old merge", -1);
    +  zdef_(wadd, Wavoidoldtot, "  average merge distance", Zavoidold);
    +  zdef_(wmax, Wavoidoldmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zdegen, "merges due to degenerate facets", -1);
    +  zdef_(wadd, Wdegentot, "  average merge distance", Zdegen);
    +  zdef_(wmax, Wdegenmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zflipped, "merges due to removing flipped facets", -1);
    +  zdef_(wadd, Wflippedtot, "  average merge distance", Zflipped);
    +  zdef_(wmax, Wflippedmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zduplicate, "merges due to duplicated ridges", -1);
    +  zdef_(wadd, Wduplicatetot, "  average merge distance", Zduplicate);
    +  zdef_(wmax, Wduplicatemax, "  maximum merge distance", -1);
    +}
    +void qh_allstatH(qhT *qh) {
    +  zdef_(zdoc, Zdoc8, "renamed vertex statistics", -1);
    +  zdef_(zinc, Zrenameshare, "renamed vertices shared by two facets", -1);
    +  zdef_(zinc, Zrenamepinch, "renamed vertices in a pinched facet", -1);
    +  zdef_(zinc, Zrenameall, "renamed vertices shared by multiple facets", -1);
    +  zdef_(zinc, Zfindfail, "rename failures due to duplicated ridges", -1);
    +  zdef_(zinc, Zdupridge, "  duplicate ridges detected", -1);
    +  zdef_(zinc, Zdelridge, "deleted ridges due to renamed vertices", -1);
    +  zdef_(zinc, Zdropneighbor, "dropped neighbors due to renamed vertices", -1);
    +  zdef_(zinc, Zdropdegen, "degenerate facets due to dropped neighbors", -1);
    +  zdef_(zinc, Zdelfacetdup, "  facets deleted because of no neighbors", -1);
    +  zdef_(zinc, Zremvertex, "vertices removed from facets due to no ridges", -1);
    +  zdef_(zinc, Zremvertexdel, "  deleted", -1);
    +  zdef_(zinc, Zintersectnum, "vertex intersections for locating redundant vertices", -1);
    +  zdef_(zinc, Zintersectfail, "intersections failed to find a redundant vertex", -1);
    +  zdef_(zinc, Zintersect, "intersections found redundant vertices", -1);
    +  zdef_(zadd, Zintersecttot, "   ave. number found per vertex", Zintersect);
    +  zdef_(zmax, Zintersectmax, "   max. found for a vertex", -1);
    +  zdef_(zinc, Zvertexridge, NULL, -1);
    +  zdef_(zadd, Zvertexridgetot, "  ave. number of ridges per tested vertex", Zvertexridge);
    +  zdef_(zmax, Zvertexridgemax, "  max. number of ridges per tested vertex", -1);
    +
    +  zdef_(zdoc, Zdoc10, "memory usage statistics(in bytes)", -1);
    +  zdef_(zadd, Zmemfacets, "for facets and their normals, neighbor and vertex sets", -1);
    +  zdef_(zadd, Zmemvertices, "for vertices and their neighbor sets", -1);
    +  zdef_(zadd, Zmempoints, "for input points, outside and coplanar sets, and qhT",-1);
    +  zdef_(zadd, Zmemridges, "for ridges and their vertex sets", -1);
    +} /* allstat */
    +
    +void qh_allstatI(qhT *qh) {
    +  qh->qhstat.vridges= qh->qhstat.next;
    +  zzdef_(zdoc, Zdoc11, "Voronoi ridge statistics", -1);
    +  zzdef_(zinc, Zridge, "non-simplicial Voronoi vertices for all ridges", -1);
    +  zzdef_(wadd, Wridge, "  ave. distance to ridge", Zridge);
    +  zzdef_(wmax, Wridgemax, "  max. distance to ridge", -1);
    +  zzdef_(zinc, Zridgemid, "bounded ridges", -1);
    +  zzdef_(wadd, Wridgemid, "  ave. distance of midpoint to ridge", Zridgemid);
    +  zzdef_(wmax, Wridgemidmax, "  max. distance of midpoint to ridge", -1);
    +  zzdef_(zinc, Zridgeok, "bounded ridges with ok normal", -1);
    +  zzdef_(wadd, Wridgeok, "  ave. angle to ridge", Zridgeok);
    +  zzdef_(wmax, Wridgeokmax, "  max. angle to ridge", -1);
    +  zzdef_(zinc, Zridge0, "bounded ridges with near-zero normal", -1);
    +  zzdef_(wadd, Wridge0, "  ave. angle to ridge", Zridge0);
    +  zzdef_(wmax, Wridge0max, "  max. angle to ridge", -1);
    +
    +  zdef_(zdoc, Zdoc12, "Triangulation statistics(Qt)", -1);
    +  zdef_(zinc, Ztricoplanar, "non-simplicial facets triangulated", -1);
    +  zdef_(zadd, Ztricoplanartot, "  ave. new facets created(may be deleted)", Ztricoplanar);
    +  zdef_(zmax, Ztricoplanarmax, "  max. new facets created", -1);
    +  zdef_(zinc, Ztrinull, "null new facets deleted(duplicated vertex)", -1);
    +  zdef_(zinc, Ztrimirror, "mirrored pairs of new facets deleted(same vertices)", -1);
    +  zdef_(zinc, Ztridegen, "degenerate new facets in output(same ridge)", -1);
    +} /* allstat */
    +
    +/*---------------------------------
    +
    +  qh_allstatistics()
    +    reset printed flag for all statistics
    +*/
    +void qh_allstatistics(qhT *qh) {
    +  int i;
    +
    +  for(i=ZEND; i--; )
    +    qh->qhstat.printed[i]= False;
    +} /* allstatistics */
    +
    +#if qh_KEEPstatistics
    +/*---------------------------------
    +
    +  qh_collectstatistics()
    +    collect statistics for qh.facet_list
    +
    +*/
    +void qh_collectstatistics(qhT *qh) {
    +  facetT *facet, *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  realT dotproduct, dist;
    +  int sizneighbors, sizridges, sizvertices, i;
    +
    +  qh->old_randomdist= qh->RANDOMdist;
    +  qh->RANDOMdist= False;
    +  zval_(Zmempoints)= qh->num_points * qh->normal_size + sizeof(qhT);
    +  zval_(Zmemfacets)= 0;
    +  zval_(Zmemridges)= 0;
    +  zval_(Zmemvertices)= 0;
    +  zval_(Zangle)= 0;
    +  wval_(Wangle)= 0.0;
    +  zval_(Znumridges)= 0;
    +  zval_(Znumfacets)= 0;
    +  zval_(Znumneighbors)= 0;
    +  zval_(Znumvertices)= 0;
    +  zval_(Znumvneighbors)= 0;
    +  zval_(Znummergetot)= 0;
    +  zval_(Znummergemax)= 0;
    +  zval_(Zvertices)= qh->num_vertices - qh_setsize(qh, qh->del_vertices);
    +  if (qh->MERGING || qh->APPROXhull || qh->JOGGLEmax < REALmax/2)
    +    wmax_(Wmaxoutside, qh->max_outside);
    +  if (qh->MERGING)
    +    wmin_(Wminvertex, qh->min_vertex);
    +  FORALLfacets
    +    facet->seen= False;
    +  if (qh->DELAUNAY) {
    +    FORALLfacets {
    +      if (facet->upperdelaunay != qh->UPPERdelaunay)
    +        facet->seen= True; /* remove from angle statistics */
    +    }
    +  }
    +  FORALLfacets {
    +    if (facet->visible && qh->NEWfacets)
    +      continue;
    +    sizvertices= qh_setsize(qh, facet->vertices);
    +    sizneighbors= qh_setsize(qh, facet->neighbors);
    +    sizridges= qh_setsize(qh, facet->ridges);
    +    zinc_(Znumfacets);
    +    zadd_(Znumvertices, sizvertices);
    +    zmax_(Zmaxvertices, sizvertices);
    +    zadd_(Znumneighbors, sizneighbors);
    +    zmax_(Zmaxneighbors, sizneighbors);
    +    zadd_(Znummergetot, facet->nummerge);
    +    i= facet->nummerge; /* avoid warnings */
    +    zmax_(Znummergemax, i);
    +    if (!facet->simplicial) {
    +      if (sizvertices == qh->hull_dim) {
    +        zinc_(Znowsimplicial);
    +      }else {
    +        zinc_(Znonsimplicial);
    +      }
    +    }
    +    if (sizridges) {
    +      zadd_(Znumridges, sizridges);
    +      zmax_(Zmaxridges, sizridges);
    +    }
    +    zadd_(Zmemfacets, sizeof(facetT) + qh->normal_size + 2*sizeof(setT)
    +       + SETelemsize * (sizneighbors + sizvertices));
    +    if (facet->ridges) {
    +      zadd_(Zmemridges,
    +         sizeof(setT) + SETelemsize * sizridges + sizridges *
    +         (sizeof(ridgeT) + sizeof(setT) + SETelemsize * (qh->hull_dim-1))/2);
    +    }
    +    if (facet->outsideset)
    +      zadd_(Zmempoints, sizeof(setT) + SETelemsize * qh_setsize(qh, facet->outsideset));
    +    if (facet->coplanarset)
    +      zadd_(Zmempoints, sizeof(setT) + SETelemsize * qh_setsize(qh, facet->coplanarset));
    +    if (facet->seen) /* Delaunay upper envelope */
    +      continue;
    +    facet->seen= True;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor == qh_DUPLICATEridge || neighbor == qh_MERGEridge
    +          || neighbor->seen || !facet->normal || !neighbor->normal)
    +        continue;
    +      dotproduct= qh_getangle(qh, facet->normal, neighbor->normal);
    +      zinc_(Zangle);
    +      wadd_(Wangle, dotproduct);
    +      wmax_(Wanglemax, dotproduct)
    +      wmin_(Wanglemin, dotproduct)
    +    }
    +    if (facet->normal) {
    +      FOREACHvertex_(facet->vertices) {
    +        zinc_(Zdiststat);
    +        qh_distplane(qh, vertex->point, facet, &dist);
    +        wmax_(Wvertexmax, dist);
    +        wmin_(Wvertexmin, dist);
    +      }
    +    }
    +  }
    +  FORALLvertices {
    +    if (vertex->deleted)
    +      continue;
    +    zadd_(Zmemvertices, sizeof(vertexT));
    +    if (vertex->neighbors) {
    +      sizneighbors= qh_setsize(qh, vertex->neighbors);
    +      zadd_(Znumvneighbors, sizneighbors);
    +      zmax_(Zmaxvneighbors, sizneighbors);
    +      zadd_(Zmemvertices, sizeof(vertexT) + SETelemsize * sizneighbors);
    +    }
    +  }
    +  qh->RANDOMdist= qh->old_randomdist;
    +} /* collectstatistics */
    +#endif /* qh_KEEPstatistics */
    +
    +/*---------------------------------
    +
    +  qh_initstatistics(qh)
    +    initialize statistics
    +
    +  notes:
    +  NOerrors -- qh_initstatistics can not use qh_errexit(), qh_fprintf, or qh.ferr
    +  On first call, only qhmem.ferr is defined.  qh_memalloc is not setup.
    +  Also invoked by QhullQh().
    +*/
    +void qh_initstatistics(qhT *qh) {
    +  int i;
    +  realT realx;
    +  int intx;
    +
    +  qh->qhstat.next= 0;
    +  qh_allstatA(qh);
    +  qh_allstatB(qh);
    +  qh_allstatC(qh);
    +  qh_allstatD(qh);
    +  qh_allstatE(qh);
    +  qh_allstatE2(qh);
    +  qh_allstatF(qh);
    +  qh_allstatG(qh);
    +  qh_allstatH(qh);
    +  qh_allstatI(qh);
    +  if (qh->qhstat.next > (int)sizeof(qh->qhstat.id)) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6184, "qhull error (qh_initstatistics): increase size of qhstat.id[].\n\
    +      qhstat.next %d should be <= sizeof(qh->qhstat.id) %d\n", qh->qhstat.next, (int)sizeof(qh->qhstat.id));
    +#if 0 /* for locating error, Znumridges should be duplicated */
    +    for(i=0; i < ZEND; i++) {
    +      int j;
    +      for(j=i+1; j < ZEND; j++) {
    +        if (qh->qhstat.id[i] == qh->qhstat.id[j]) {
    +          qh_fprintf(qh, qh->qhmem.ferr, 6185, "qhull error (qh_initstatistics): duplicated statistic %d at indices %d and %d\n",
    +              qh->qhstat.id[i], i, j);
    +        }
    +      }
    +    }
    +#endif
    +    qh_exit(qh_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  qh->qhstat.init[zinc].i= 0;
    +  qh->qhstat.init[zadd].i= 0;
    +  qh->qhstat.init[zmin].i= INT_MAX;
    +  qh->qhstat.init[zmax].i= INT_MIN;
    +  qh->qhstat.init[wadd].r= 0;
    +  qh->qhstat.init[wmin].r= REALmax;
    +  qh->qhstat.init[wmax].r= -REALmax;
    +  for(i=0; i < ZEND; i++) {
    +    if (qh->qhstat.type[i] > ZTYPEreal) {
    +      realx= qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].r;
    +      qh->qhstat.stats[i].r= realx;
    +    }else if (qh->qhstat.type[i] != zdoc) {
    +      intx= qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].i;
    +      qh->qhstat.stats[i].i= intx;
    +    }
    +  }
    +} /* initstatistics */
    +
    +/*---------------------------------
    +
    +  qh_newstats(qh, )
    +    returns True if statistics for zdoc
    +
    +  returns:
    +    next zdoc
    +*/
    +boolT qh_newstats(qhT *qh, int idx, int *nextindex) {
    +  boolT isnew= False;
    +  int start, i;
    +
    +  if (qh->qhstat.type[qh->qhstat.id[idx]] == zdoc)
    +    start= idx+1;
    +  else
    +    start= idx;
    +  for(i= start; i < qh->qhstat.next && qh->qhstat.type[qh->qhstat.id[i]] != zdoc; i++) {
    +    if (!qh_nostatistic(qh, qh->qhstat.id[i]) && !qh->qhstat.printed[qh->qhstat.id[i]])
    +        isnew= True;
    +  }
    +  *nextindex= i;
    +  return isnew;
    +} /* newstats */
    +
    +/*---------------------------------
    +
    +  qh_nostatistic(qh, index )
    +    true if no statistic to print
    +*/
    +boolT qh_nostatistic(qhT *qh, int i) {
    +
    +  if ((qh->qhstat.type[i] > ZTYPEreal
    +       &&qh->qhstat.stats[i].r == qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].r)
    +      || (qh->qhstat.type[i] < ZTYPEreal
    +          &&qh->qhstat.stats[i].i == qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].i))
    +    return True;
    +  return False;
    +} /* nostatistic */
    +
    +#if qh_KEEPstatistics
    +/*---------------------------------
    +
    +  qh_printallstatistics(qh, fp, string )
    +    print all statistics with header 'string'
    +*/
    +void qh_printallstatistics(qhT *qh, FILE *fp, const char *string) {
    +
    +  qh_allstatistics(qh);
    +  qh_collectstatistics(qh);
    +  qh_printstatistics(qh, fp, string);
    +  qh_memstatistics(qh, fp);
    +}
    +
    +
    +/*---------------------------------
    +
    +  qh_printstatistics(qh, fp, string )
    +    print statistics to a file with header 'string'
    +    skips statistics with qhstat.printed[] (reset with qh_allstatistics)
    +
    +  see:
    +    qh_printallstatistics()
    +*/
    +void qh_printstatistics(qhT *qh, FILE *fp, const char *string) {
    +  int i, k;
    +  realT ave;
    +
    +  if (qh->num_points != qh->num_vertices) {
    +    wval_(Wpbalance)= 0;
    +    wval_(Wpbalance2)= 0;
    +  }else
    +    wval_(Wpbalance2)= qh_stddev(zval_(Zpbalance), wval_(Wpbalance),
    +                                 wval_(Wpbalance2), &ave);
    +  wval_(Wnewbalance2)= qh_stddev(zval_(Zprocessed), wval_(Wnewbalance),
    +                                 wval_(Wnewbalance2), &ave);
    +  qh_fprintf(qh, fp, 9350, "\n\
    +%s\n\
    + qhull invoked by: %s | %s\n%s with options:\n%s\n", string, qh->rbox_command,
    +     qh->qhull_command, qh_version, qh->qhull_options);
    +  qh_fprintf(qh, fp, 9351, "\nprecision constants:\n\
    + %6.2g max. abs. coordinate in the (transformed) input('Qbd:n')\n\
    + %6.2g max. roundoff error for distance computation('En')\n\
    + %6.2g max. roundoff error for angle computations\n\
    + %6.2g min. distance for outside points ('Wn')\n\
    + %6.2g min. distance for visible facets ('Vn')\n\
    + %6.2g max. distance for coplanar facets ('Un')\n\
    + %6.2g max. facet width for recomputing centrum and area\n\
    +",
    +  qh->MAXabs_coord, qh->DISTround, qh->ANGLEround, qh->MINoutside,
    +        qh->MINvisible, qh->MAXcoplanar, qh->WIDEfacet);
    +  if (qh->KEEPnearinside)
    +    qh_fprintf(qh, fp, 9352, "\
    + %6.2g max. distance for near-inside points\n", qh->NEARinside);
    +  if (qh->premerge_cos < REALmax/2) qh_fprintf(qh, fp, 9353, "\
    + %6.2g max. cosine for pre-merge angle\n", qh->premerge_cos);
    +  if (qh->PREmerge) qh_fprintf(qh, fp, 9354, "\
    + %6.2g radius of pre-merge centrum\n", qh->premerge_centrum);
    +  if (qh->postmerge_cos < REALmax/2) qh_fprintf(qh, fp, 9355, "\
    + %6.2g max. cosine for post-merge angle\n", qh->postmerge_cos);
    +  if (qh->POSTmerge) qh_fprintf(qh, fp, 9356, "\
    + %6.2g radius of post-merge centrum\n", qh->postmerge_centrum);
    +  qh_fprintf(qh, fp, 9357, "\
    + %6.2g max. distance for merging two simplicial facets\n\
    + %6.2g max. roundoff error for arithmetic operations\n\
    + %6.2g min. denominator for divisions\n\
    +  zero diagonal for Gauss: ", qh->ONEmerge, REALepsilon, qh->MINdenom);
    +  for(k=0; k < qh->hull_dim; k++)
    +    qh_fprintf(qh, fp, 9358, "%6.2e ", qh->NEARzero[k]);
    +  qh_fprintf(qh, fp, 9359, "\n\n");
    +  for(i=0 ; i < qh->qhstat.next; )
    +    qh_printstats(qh, fp, i, &i);
    +} /* printstatistics */
    +#endif /* qh_KEEPstatistics */
    +
    +/*---------------------------------
    +
    +  qh_printstatlevel(qh, fp, id )
    +    print level information for a statistic
    +
    +  notes:
    +    nop if id >= ZEND, printed, or same as initial value
    +*/
    +void qh_printstatlevel(qhT *qh, FILE *fp, int id) {
    +#define NULLfield "       "
    +
    +  if (id >= ZEND || qh->qhstat.printed[id])
    +    return;
    +  if (qh->qhstat.type[id] == zdoc) {
    +    qh_fprintf(qh, fp, 9360, "%s\n", qh->qhstat.doc[id]);
    +    return;
    +  }
    +  if (qh_nostatistic(qh, id) || !qh->qhstat.doc[id])
    +    return;
    +  qh->qhstat.printed[id]= True;
    +  if (qh->qhstat.count[id] != -1
    +      && qh->qhstat.stats[(unsigned char)(qh->qhstat.count[id])].i == 0)
    +    qh_fprintf(qh, fp, 9361, " *0 cnt*");
    +  else if (qh->qhstat.type[id] >= ZTYPEreal && qh->qhstat.count[id] == -1)
    +    qh_fprintf(qh, fp, 9362, "%7.2g", qh->qhstat.stats[id].r);
    +  else if (qh->qhstat.type[id] >= ZTYPEreal && qh->qhstat.count[id] != -1)
    +    qh_fprintf(qh, fp, 9363, "%7.2g", qh->qhstat.stats[id].r/ qh->qhstat.stats[(unsigned char)(qh->qhstat.count[id])].i);
    +  else if (qh->qhstat.type[id] < ZTYPEreal && qh->qhstat.count[id] == -1)
    +    qh_fprintf(qh, fp, 9364, "%7d", qh->qhstat.stats[id].i);
    +  else if (qh->qhstat.type[id] < ZTYPEreal && qh->qhstat.count[id] != -1)
    +    qh_fprintf(qh, fp, 9365, "%7.3g", (realT) qh->qhstat.stats[id].i / qh->qhstat.stats[(unsigned char)(qh->qhstat.count[id])].i);
    +  qh_fprintf(qh, fp, 9366, " %s\n", qh->qhstat.doc[id]);
    +} /* printstatlevel */
    +
    +
    +/*---------------------------------
    +
    +  qh_printstats(qh, fp, index, nextindex )
    +    print statistics for a zdoc group
    +
    +  returns:
    +    next zdoc if non-null
    +*/
    +void qh_printstats(qhT *qh, FILE *fp, int idx, int *nextindex) {
    +  int j, nexti;
    +
    +  if (qh_newstats(qh, idx, &nexti)) {
    +    qh_fprintf(qh, fp, 9367, "\n");
    +    for (j=idx; jqhstat.id[j]);
    +  }
    +  if (nextindex)
    +    *nextindex= nexti;
    +} /* printstats */
    +
    +#if qh_KEEPstatistics
    +
    +/*---------------------------------
    +
    +  qh_stddev(num, tot, tot2, ave )
    +    compute the standard deviation and average from statistics
    +
    +    tot2 is the sum of the squares
    +  notes:
    +    computes r.m.s.:
    +      (x-ave)^2
    +      == x^2 - 2x tot/num +   (tot/num)^2
    +      == tot2 - 2 tot tot/num + tot tot/num
    +      == tot2 - tot ave
    +*/
    +realT qh_stddev(int num, realT tot, realT tot2, realT *ave) {
    +  realT stddev;
    +
    +  *ave= tot/num;
    +  stddev= sqrt(tot2/num - *ave * *ave);
    +  return stddev;
    +} /* stddev */
    +
    +#endif /* qh_KEEPstatistics */
    +
    +#if !qh_KEEPstatistics
    +void    qh_collectstatistics(qhT *qh) {}
    +void    qh_printallstatistics(qhT *qh, FILE *fp, char *string) {};
    +void    qh_printstatistics(qhT *qh, FILE *fp, char *string) {}
    +#endif
    +
    diff --git a/xs/src/qhull/src/libqhull_r/stat_r.h b/xs/src/qhull/src/libqhull_r/stat_r.h
    new file mode 100644
    index 0000000000..75e7d10578
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/stat_r.h
    @@ -0,0 +1,533 @@
    +/*
      ---------------------------------
    +
    +   stat_r.h
    +     contains all statistics that are collected for qhull
    +
    +   see qh-stat_r.htm and stat_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/stat_r.h#5 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +
    +   recompile qhull if you change this file
    +
    +   Integer statistics are Z* while real statistics are W*.
    +
    +   define MAYdebugx to call a routine at every statistic event
    +
    +*/
    +
    +#ifndef qhDEFstat
    +#define qhDEFstat 1
    +
    +/* Depends on realT.  Do not include libqhull_r to avoid circular dependency */
    +
    +#ifndef DEFqhT
    +#define DEFqhT 1
    +typedef struct qhT qhT;         /* Defined by libqhull_r.h */
    +#endif
    +
    +#ifndef DEFqhstatT
    +#define DEFqhstatT 1
    +typedef struct qhstatT qhstatT; /* Defined here */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_KEEPstatistics
    +    0 turns off statistic gathering (except zzdef/zzinc/zzadd/zzval/wwval)
    +*/
    +#ifndef qh_KEEPstatistics
    +#define qh_KEEPstatistics 1
    +#endif
    +
    +/*---------------------------------
    +
    +  Zxxx for integers, Wxxx for reals
    +
    +  notes:
    +    be sure that all statistics are defined in stat_r.c
    +      otherwise initialization may core dump
    +    can pick up all statistics by:
    +      grep '[zw].*_[(][ZW]' *.c >z.x
    +    remove trailers with query">-
    +    remove leaders with  query-replace-regexp [ ^I]+  (
    +*/
    +#if qh_KEEPstatistics
    +enum qh_statistics {     /* alphabetical after Z/W */
    +    Zacoplanar,
    +    Wacoplanarmax,
    +    Wacoplanartot,
    +    Zangle,
    +    Wangle,
    +    Wanglemax,
    +    Wanglemin,
    +    Zangletests,
    +    Wareatot,
    +    Wareamax,
    +    Wareamin,
    +    Zavoidold,
    +    Wavoidoldmax,
    +    Wavoidoldtot,
    +    Zback0,
    +    Zbestcentrum,
    +    Zbestdist,
    +    Zbestlower,
    +    Zbestlowerall,
    +    Zbestloweralln,
    +    Zbestlowerv,
    +    Zcentrumtests,
    +    Zcheckpart,
    +    Zcomputefurthest,
    +    Zconcave,
    +    Wconcavemax,
    +    Wconcavetot,
    +    Zconcaveridges,
    +    Zconcaveridge,
    +    Zcoplanar,
    +    Wcoplanarmax,
    +    Wcoplanartot,
    +    Zcoplanarangle,
    +    Zcoplanarcentrum,
    +    Zcoplanarhorizon,
    +    Zcoplanarinside,
    +    Zcoplanarpart,
    +    Zcoplanarridges,
    +    Wcpu,
    +    Zcyclefacetmax,
    +    Zcyclefacettot,
    +    Zcyclehorizon,
    +    Zcyclevertex,
    +    Zdegen,
    +    Wdegenmax,
    +    Wdegentot,
    +    Zdegenvertex,
    +    Zdelfacetdup,
    +    Zdelridge,
    +    Zdelvertextot,
    +    Zdelvertexmax,
    +    Zdetsimplex,
    +    Zdistcheck,
    +    Zdistconvex,
    +    Zdistgood,
    +    Zdistio,
    +    Zdistplane,
    +    Zdiststat,
    +    Zdistvertex,
    +    Zdistzero,
    +    Zdoc1,
    +    Zdoc2,
    +    Zdoc3,
    +    Zdoc4,
    +    Zdoc5,
    +    Zdoc6,
    +    Zdoc7,
    +    Zdoc8,
    +    Zdoc9,
    +    Zdoc10,
    +    Zdoc11,
    +    Zdoc12,
    +    Zdropdegen,
    +    Zdropneighbor,
    +    Zdupflip,
    +    Zduplicate,
    +    Wduplicatemax,
    +    Wduplicatetot,
    +    Zdupridge,
    +    Zdupsame,
    +    Zflipped,
    +    Wflippedmax,
    +    Wflippedtot,
    +    Zflippedfacets,
    +    Zfindbest,
    +    Zfindbestmax,
    +    Zfindbesttot,
    +    Zfindcoplanar,
    +    Zfindfail,
    +    Zfindhorizon,
    +    Zfindhorizonmax,
    +    Zfindhorizontot,
    +    Zfindjump,
    +    Zfindnew,
    +    Zfindnewmax,
    +    Zfindnewtot,
    +    Zfindnewjump,
    +    Zfindnewsharp,
    +    Zgauss0,
    +    Zgoodfacet,
    +    Zhashlookup,
    +    Zhashridge,
    +    Zhashridgetest,
    +    Zhashtests,
    +    Zinsidevisible,
    +    Zintersect,
    +    Zintersectfail,
    +    Zintersectmax,
    +    Zintersectnum,
    +    Zintersecttot,
    +    Zmaxneighbors,
    +    Wmaxout,
    +    Wmaxoutside,
    +    Zmaxridges,
    +    Zmaxvertex,
    +    Zmaxvertices,
    +    Zmaxvneighbors,
    +    Zmemfacets,
    +    Zmempoints,
    +    Zmemridges,
    +    Zmemvertices,
    +    Zmergeflipdup,
    +    Zmergehorizon,
    +    Zmergeinittot,
    +    Zmergeinitmax,
    +    Zmergeinittot2,
    +    Zmergeintohorizon,
    +    Zmergenew,
    +    Zmergesettot,
    +    Zmergesetmax,
    +    Zmergesettot2,
    +    Zmergesimplex,
    +    Zmergevertex,
    +    Wmindenom,
    +    Wminvertex,
    +    Zminnorm,
    +    Zmultiridge,
    +    Znearlysingular,
    +    Zneighbor,
    +    Wnewbalance,
    +    Wnewbalance2,
    +    Znewfacettot,
    +    Znewfacetmax,
    +    Znewvertex,
    +    Wnewvertex,
    +    Wnewvertexmax,
    +    Znoarea,
    +    Znonsimplicial,
    +    Znowsimplicial,
    +    Znotgood,
    +    Znotgoodnew,
    +    Znotmax,
    +    Znumfacets,
    +    Znummergemax,
    +    Znummergetot,
    +    Znumneighbors,
    +    Znumridges,
    +    Znumvertices,
    +    Znumvisibility,
    +    Znumvneighbors,
    +    Zonehorizon,
    +    Zpartangle,
    +    Zpartcoplanar,
    +    Zpartflip,
    +    Zparthorizon,
    +    Zpartinside,
    +    Zpartition,
    +    Zpartitionall,
    +    Zpartnear,
    +    Zpbalance,
    +    Wpbalance,
    +    Wpbalance2,
    +    Zpostfacets,
    +    Zpremergetot,
    +    Zprocessed,
    +    Zremvertex,
    +    Zremvertexdel,
    +    Zrenameall,
    +    Zrenamepinch,
    +    Zrenameshare,
    +    Zretry,
    +    Wretrymax,
    +    Zridge,
    +    Wridge,
    +    Wridgemax,
    +    Zridge0,
    +    Wridge0,
    +    Wridge0max,
    +    Zridgemid,
    +    Wridgemid,
    +    Wridgemidmax,
    +    Zridgeok,
    +    Wridgeok,
    +    Wridgeokmax,
    +    Zsearchpoints,
    +    Zsetplane,
    +    Ztestvneighbor,
    +    Ztotcheck,
    +    Ztothorizon,
    +    Ztotmerge,
    +    Ztotpartcoplanar,
    +    Ztotpartition,
    +    Ztotridges,
    +    Ztotvertices,
    +    Ztotvisible,
    +    Ztricoplanar,
    +    Ztricoplanarmax,
    +    Ztricoplanartot,
    +    Ztridegen,
    +    Ztrimirror,
    +    Ztrinull,
    +    Wvertexmax,
    +    Wvertexmin,
    +    Zvertexridge,
    +    Zvertexridgetot,
    +    Zvertexridgemax,
    +    Zvertices,
    +    Zvisfacettot,
    +    Zvisfacetmax,
    +    Zvisit,
    +    Zvisit2max,
    +    Zvisvertextot,
    +    Zvisvertexmax,
    +    Zvvisit,
    +    Zvvisit2max,
    +    Zwidefacet,
    +    Zwidevertices,
    +    ZEND};
    +
    +/*---------------------------------
    +
    +  Zxxx/Wxxx statistics that remain defined if qh_KEEPstatistics=0
    +
    +  notes:
    +    be sure to use zzdef, zzinc, etc. with these statistics (no double checking!)
    +*/
    +#else
    +enum qh_statistics {     /* for zzdef etc. macros */
    +  Zback0,
    +  Zbestdist,
    +  Zcentrumtests,
    +  Zcheckpart,
    +  Zconcaveridges,
    +  Zcoplanarhorizon,
    +  Zcoplanarpart,
    +  Zcoplanarridges,
    +  Zcyclefacettot,
    +  Zcyclehorizon,
    +  Zdelvertextot,
    +  Zdistcheck,
    +  Zdistconvex,
    +  Zdistzero,
    +  Zdoc1,
    +  Zdoc2,
    +  Zdoc3,
    +  Zdoc11,
    +  Zflippedfacets,
    +  Zgauss0,
    +  Zminnorm,
    +  Zmultiridge,
    +  Znearlysingular,
    +  Wnewvertexmax,
    +  Znumvisibility,
    +  Zpartcoplanar,
    +  Zpartition,
    +  Zpartitionall,
    +  Zprocessed,
    +  Zretry,
    +  Zridge,
    +  Wridge,
    +  Wridgemax,
    +  Zridge0,
    +  Wridge0,
    +  Wridge0max,
    +  Zridgemid,
    +  Wridgemid,
    +  Wridgemidmax,
    +  Zridgeok,
    +  Wridgeok,
    +  Wridgeokmax,
    +  Zsetplane,
    +  Ztotcheck,
    +  Ztotmerge,
    +    ZEND};
    +#endif
    +
    +/*---------------------------------
    +
    +  ztype
    +    the type of a statistic sets its initial value.
    +
    +  notes:
    +    The type should be the same as the macro for collecting the statistic
    +*/
    +enum ztypes {zdoc,zinc,zadd,zmax,zmin,ZTYPEreal,wadd,wmax,wmin,ZTYPEend};
    +
    +/*========== macros and constants =============*/
    +
    +/*----------------------------------
    +
    +  MAYdebugx
    +    define as maydebug() to be called frequently for error trapping
    +*/
    +#define MAYdebugx
    +
    +/*----------------------------------
    +
    +  zzdef_, zdef_( type, name, doc, -1)
    +    define a statistic (assumes 'qhstat.next= 0;')
    +
    +  zdef_( type, name, doc, count)
    +    define an averaged statistic
    +    printed as name/count
    +*/
    +#define zzdef_(stype,name,string,cnt) qh->qhstat.id[qh->qhstat.next++]=name; \
    +   qh->qhstat.doc[name]= string; qh->qhstat.count[name]= cnt; qh->qhstat.type[name]= stype
    +#if qh_KEEPstatistics
    +#define zdef_(stype,name,string,cnt) qh->qhstat.id[qh->qhstat.next++]=name; \
    +   qh->qhstat.doc[name]= string; qh->qhstat.count[name]= cnt; qh->qhstat.type[name]= stype
    +#else
    +#define zdef_(type,name,doc,count)
    +#endif
    +
    +/*----------------------------------
    +
    +  zzinc_( name ), zinc_( name)
    +    increment an integer statistic
    +*/
    +#define zzinc_(id) {MAYdebugx; qh->qhstat.stats[id].i++;}
    +#if qh_KEEPstatistics
    +#define zinc_(id) {MAYdebugx; qh->qhstat.stats[id].i++;}
    +#else
    +#define zinc_(id) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zzadd_( name, value ), zadd_( name, value ), wadd_( name, value )
    +    add value to an integer or real statistic
    +*/
    +#define zzadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].i += (val);}
    +#define wwadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].r += (val);}
    +#if qh_KEEPstatistics
    +#define zadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].i += (val);}
    +#define wadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].r += (val);}
    +#else
    +#define zadd_(id, val) {}
    +#define wadd_(id, val) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zzval_( name ), zval_( name ), wwval_( name )
    +    set or return value of a statistic
    +*/
    +#define zzval_(id) ((qh->qhstat.stats[id]).i)
    +#define wwval_(id) ((qh->qhstat.stats[id]).r)
    +#if qh_KEEPstatistics
    +#define zval_(id) ((qh->qhstat.stats[id]).i)
    +#define wval_(id) ((qh->qhstat.stats[id]).r)
    +#else
    +#define zval_(id) qh->qhstat.tempi
    +#define wval_(id) qh->qhstat.tempr
    +#endif
    +
    +/*----------------------------------
    +
    +  zmax_( id, val ), wmax_( id, value )
    +    maximize id with val
    +*/
    +#define wwmax_(id, val) {MAYdebugx; maximize_(qh->qhstat.stats[id].r,(val));}
    +#if qh_KEEPstatistics
    +#define zmax_(id, val) {MAYdebugx; maximize_(qh->qhstat.stats[id].i,(val));}
    +#define wmax_(id, val) {MAYdebugx; maximize_(qh->qhstat.stats[id].r,(val));}
    +#else
    +#define zmax_(id, val) {}
    +#define wmax_(id, val) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zmin_( id, val ), wmin_( id, value )
    +    minimize id with val
    +*/
    +#if qh_KEEPstatistics
    +#define zmin_(id, val) {MAYdebugx; minimize_(qh->qhstat.stats[id].i,(val));}
    +#define wmin_(id, val) {MAYdebugx; minimize_(qh->qhstat.stats[id].r,(val));}
    +#else
    +#define zmin_(id, val) {}
    +#define wmin_(id, val) {}
    +#endif
    +
    +/*================== stat_r.h types ==============*/
    +
    +
    +/*----------------------------------
    +
    +  intrealT
    +    union of integer and real, used for statistics
    +*/
    +typedef union intrealT intrealT;    /* union of int and realT */
    +union intrealT {
    +    int i;
    +    realT r;
    +};
    +
    +/*----------------------------------
    +
    +  qhstat
    +    Data structure for statistics, similar to qh and qhrbox
    +
    +    Allocated as part of qhT (libqhull_r.h)
    +*/
    +
    +struct qhstatT {
    +  intrealT   stats[ZEND];     /* integer and real statistics */
    +  unsigned   char id[ZEND+10]; /* id's in print order */
    +  const char *doc[ZEND];       /* array of documentation strings */
    +  short int  count[ZEND];     /* -1 if none, else index of count to use */
    +  char       type[ZEND];      /* type, see ztypes above */
    +  char       printed[ZEND];   /* true, if statistic has been printed */
    +  intrealT   init[ZTYPEend];  /* initial values by types, set initstatistics */
    +
    +  int        next;            /* next index for zdef_ */
    +  int        precision;       /* index for precision problems */
    +  int        vridges;         /* index for Voronoi ridges */
    +  int        tempi;
    +  realT      tempr;
    +};
    +
    +/*========== function prototypes ===========*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_allstatA(qhT *qh);
    +void    qh_allstatB(qhT *qh);
    +void    qh_allstatC(qhT *qh);
    +void    qh_allstatD(qhT *qh);
    +void    qh_allstatE(qhT *qh);
    +void    qh_allstatE2(qhT *qh);
    +void    qh_allstatF(qhT *qh);
    +void    qh_allstatG(qhT *qh);
    +void    qh_allstatH(qhT *qh);
    +void    qh_allstatI(qhT *qh);
    +void    qh_allstatistics(qhT *qh);
    +void    qh_collectstatistics(qhT *qh);
    +void    qh_initstatistics(qhT *qh);
    +boolT   qh_newstats(qhT *qh, int idx, int *nextindex);
    +boolT   qh_nostatistic(qhT *qh, int i);
    +void    qh_printallstatistics(qhT *qh, FILE *fp, const char *string);
    +void    qh_printstatistics(qhT *qh, FILE *fp, const char *string);
    +void    qh_printstatlevel(qhT *qh, FILE *fp, int id);
    +void    qh_printstats(qhT *qh, FILE *fp, int idx, int *nextindex);
    +realT   qh_stddev(int num, realT tot, realT tot2, realT *ave);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif   /* qhDEFstat */
    diff --git a/xs/src/qhull/src/libqhull_r/user_r.c b/xs/src/qhull/src/libqhull_r/user_r.c
    new file mode 100644
    index 0000000000..bf7ed1d8d6
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/user_r.c
    @@ -0,0 +1,527 @@
    +/*
      ---------------------------------
    +
    +   user.c
    +   user redefinable functions
    +
    +   see user2_r.c for qh_fprintf, qh_malloc, qh_free
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   see libqhull_r.h for data structures, macros, and user-callable functions.
    +
    +   see user_eg.c, user_eg2.c, and unix.c for examples.
    +
    +   see user.h for user-definable constants
    +
    +      use qh_NOmem in mem_r.h to turn off memory management
    +      use qh_NOmerge in user.h to turn off facet merging
    +      set qh_KEEPstatistics in user.h to 0 to turn off statistics
    +
    +   This is unsupported software.  You're welcome to make changes,
    +   but you're on your own if something goes wrong.  Use 'Tc' to
    +   check frequently.  Usually qhull will report an error if
    +   a data structure becomes inconsistent.  If so, it also reports
    +   the last point added to the hull, e.g., 102.  You can then trace
    +   the execution of qhull with "T4P102".
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +
    +   Qhull-template is a template for calling qhull from within your application
    +
    +   if you recompile and load this module, then user.o will not be loaded
    +   from qhull.a
    +
    +   you can add additional quick allocation sizes in qh_user_memsizes
    +
    +   if the other functions here are redefined to not use qh_print...,
    +   then io.o will not be loaded from qhull.a.  See user_eg_r.c for an
    +   example.  We recommend keeping io.o for the extra debugging
    +   information it supplies.
    +*/
    +
    +#include "qhull_ra.h"
    +
    +#include 
    +
    +/*---------------------------------
    +
    +  Qhull-template
    +    Template for calling qhull from inside your program
    +
    +  returns:
    +    exit code(see qh_ERR... in libqhull_r.h)
    +    all memory freed
    +
    +  notes:
    +    This can be called any number of times.
    +
    +*/
    +#if 0
    +{
    +  int dim;                  /* dimension of points */
    +  int numpoints;            /* number of points */
    +  coordT *points;           /* array of coordinates for each point */
    +  boolT ismalloc;           /* True if qhull should free points in qh_freeqhull() or reallocation */
    +  char flags[]= "qhull Tv"; /* option flags for qhull, see qh_opt.htm */
    +  FILE *outfile= stdout;    /* output from qh_produce_output(qh)
    +                               use NULL to skip qh_produce_output(qh) */
    +  FILE *errfile= stderr;    /* error messages from qhull code */
    +  int exitcode;             /* 0 if no error from qhull */
    +  facetT *facet;            /* set by FORALLfacets */
    +  int curlong, totlong;     /* memory remaining after qh_memfreeshort */
    +
    +  qhT qh_qh;                /* Qhull's data structure.  First argument of most calls */
    +  qhT *qh= &qh_qh;          /* Alternatively -- qhT *qh= (qhT*)malloc(sizeof(qhT)) */
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  qh_zero(qh, errfile);
    +
    +  /* initialize dim, numpoints, points[], ismalloc here */
    +  exitcode= qh_new_qhull(qh, dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh->facet_list' contains the convex hull */
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +  }
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf(qh, errfile, 7068, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n", totlong, curlong);
    +}
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_new_qhull(qh, dim, numpoints, points, ismalloc, qhull_cmd, outfile, errfile )
    +    Run qhull and return results in qh.
    +    Returns exitcode (0 if no errors).
    +    Before first call, either call qh_zero(qh, errfile), or set qh to all zero.
    +
    +  notes:
    +    do not modify points until finished with results.
    +      The qhull data structure contains pointers into the points array.
    +    do not call qhull functions before qh_new_qhull().
    +      The qhull data structure is not initialized until qh_new_qhull().
    +    do not call qh_init_A (global_r.c)
    +
    +    Default errfile is stderr, outfile may be null
    +    qhull_cmd must start with "qhull "
    +    projects points to a new point array for Delaunay triangulations ('d' and 'v')
    +    transforms points into a new point array for halfspace intersection ('H')
    +
    +  see:
    +    Qhull-template at the beginning of this file.
    +    An example of using qh_new_qhull is user_eg_r.c
    +*/
    +int qh_new_qhull(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc,
    +                char *qhull_cmd, FILE *outfile, FILE *errfile) {
    +  /* gcc may issue a "might be clobbered" warning for dim, points, and ismalloc [-Wclobbered].
    +     These parameters are not referenced after a longjmp() and hence not clobbered.
    +     See http://stackoverflow.com/questions/7721854/what-sense-do-these-clobbered-variable-warnings-make */
    +  int exitcode, hulldim;
    +  boolT new_ismalloc;
    +  coordT *new_points;
    +
    +  if(!errfile){
    +    errfile= stderr;
    +  }
    +  if (!qh->qhmem.ferr) {
    +    qh_meminit(qh, errfile);
    +  } else {
    +    qh_memcheck(qh);
    +  }
    +  if (strncmp(qhull_cmd, "qhull ", (size_t)6)) {
    +    qh_fprintf(qh, errfile, 6186, "qhull error (qh_new_qhull): start qhull_cmd argument with \"qhull \"\n");
    +    return qh_ERRinput;
    +  }
    +  qh_initqhull_start(qh, NULL, outfile, errfile);
    +  trace1((qh, qh->ferr, 1044, "qh_new_qhull: build new Qhull for %d %d-d points with %s\n", numpoints, dim, qhull_cmd));
    +  exitcode = setjmp(qh->errexit);
    +  if (!exitcode)
    +  {
    +    qh->NOerrexit = False;
    +    qh_initflags(qh, qhull_cmd);
    +    if (qh->DELAUNAY)
    +      qh->PROJECTdelaunay= True;
    +    if (qh->HALFspace) {
    +      /* points is an array of halfspaces,
    +         the last coordinate of each halfspace is its offset */
    +      hulldim= dim-1;
    +      qh_setfeasible(qh, hulldim);
    +      new_points= qh_sethalfspace_all(qh, dim, numpoints, points, qh->feasible_point);
    +      new_ismalloc= True;
    +      if (ismalloc)
    +        qh_free(points);
    +    }else {
    +      hulldim= dim;
    +      new_points= points;
    +      new_ismalloc= ismalloc;
    +    }
    +    qh_init_B(qh, new_points, numpoints, hulldim, new_ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    if (outfile) {
    +      qh_produce_output(qh);
    +    }else {
    +      qh_prepare_output(qh);
    +    }
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +  }
    +  qh->NOerrexit = True;
    +  return exitcode;
    +} /* new_qhull */
    +
    +/*---------------------------------
    +
    +  qh_errexit(qh, exitcode, facet, ridge )
    +    report and exit from an error
    +    report facet and ridge if non-NULL
    +    reports useful information such as last point processed
    +    set qh.FORCEoutput to print neighborhood of facet
    +
    +  see:
    +    qh_errexit2() in libqhull_r.c for printing 2 facets
    +
    +  design:
    +    check for error within error processing
    +    compute qh.hulltime
    +    print facet and ridge (if any)
    +    report commandString, options, qh.furthest_id
    +    print summary and statistics (including precision statistics)
    +    if qh_ERRsingular
    +      print help text for singular data set
    +    exit program via long jump (if defined) or exit()
    +*/
    +void qh_errexit(qhT *qh, int exitcode, facetT *facet, ridgeT *ridge) {
    +
    +  if (qh->ERREXITcalled) {
    +    qh_fprintf(qh, qh->ferr, 8126, "\nqhull error while processing previous error.  Exit program\n");
    +    qh_exit(qh_ERRqhull);
    +  }
    +  qh->ERREXITcalled= True;
    +  if (!qh->QHULLfinished)
    +    qh->hulltime= qh_CPUclock - qh->hulltime;
    +  qh_errprint(qh, "ERRONEOUS", facet, NULL, ridge, NULL);
    +  qh_fprintf(qh, qh->ferr, 8127, "\nWhile executing: %s | %s\n", qh->rbox_command, qh->qhull_command);
    +  qh_fprintf(qh, qh->ferr, 8128, "Options selected for Qhull %s:\n%s\n", qh_version, qh->qhull_options);
    +  if (qh->furthest_id >= 0) {
    +    qh_fprintf(qh, qh->ferr, 8129, "Last point added to hull was p%d.", qh->furthest_id);
    +    if (zzval_(Ztotmerge))
    +      qh_fprintf(qh, qh->ferr, 8130, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    if (qh->QHULLfinished)
    +      qh_fprintf(qh, qh->ferr, 8131, "\nQhull has finished constructing the hull.");
    +    else if (qh->POSTmerging)
    +      qh_fprintf(qh, qh->ferr, 8132, "\nQhull has started post-merging.");
    +    qh_fprintf(qh, qh->ferr, 8133, "\n");
    +  }
    +  if (qh->FORCEoutput && (qh->QHULLfinished || (!facet && !ridge)))
    +    qh_produce_output(qh);
    +  else if (exitcode != qh_ERRinput) {
    +    if (exitcode != qh_ERRsingular && zzval_(Zsetplane) > qh->hull_dim+1) {
    +      qh_fprintf(qh, qh->ferr, 8134, "\nAt error exit:\n");
    +      qh_printsummary(qh, qh->ferr);
    +      if (qh->PRINTstatistics) {
    +        qh_collectstatistics(qh);
    +        qh_printstatistics(qh, qh->ferr, "at error exit");
    +        qh_memstatistics(qh, qh->ferr);
    +      }
    +    }
    +    if (qh->PRINTprecision)
    +      qh_printstats(qh, qh->ferr, qh->qhstat.precision, NULL);
    +  }
    +  if (!exitcode)
    +    exitcode= qh_ERRqhull;
    +  else if (exitcode == qh_ERRsingular)
    +    qh_printhelp_singular(qh, qh->ferr);
    +  else if (exitcode == qh_ERRprec && !qh->PREmerge)
    +    qh_printhelp_degenerate(qh, qh->ferr);
    +  if (qh->NOerrexit) {
    +    qh_fprintf(qh, qh->ferr, 6187, "qhull error while ending program, or qh->NOerrexit not cleared after setjmp(). Exit program with error.\n");
    +    qh_exit(qh_ERRqhull);
    +  }
    +  qh->ERREXITcalled= False;
    +  qh->NOerrexit= True;
    +  qh->ALLOWrestart= False;  /* longjmp will undo qh_build_withrestart */
    +  longjmp(qh->errexit, exitcode);
    +} /* errexit */
    +
    +
    +/*---------------------------------
    +
    +  qh_errprint(qh, fp, string, atfacet, otherfacet, atridge, atvertex )
    +    prints out the information of facets and ridges to fp
    +    also prints neighbors and geomview output
    +
    +  notes:
    +    except for string, any parameter may be NULL
    +*/
    +void qh_errprint(qhT *qh, const char *string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex) {
    +  int i;
    +
    +  if (atfacet) {
    +    qh_fprintf(qh, qh->ferr, 8135, "%s FACET:\n", string);
    +    qh_printfacet(qh, qh->ferr, atfacet);
    +  }
    +  if (otherfacet) {
    +    qh_fprintf(qh, qh->ferr, 8136, "%s OTHER FACET:\n", string);
    +    qh_printfacet(qh, qh->ferr, otherfacet);
    +  }
    +  if (atridge) {
    +    qh_fprintf(qh, qh->ferr, 8137, "%s RIDGE:\n", string);
    +    qh_printridge(qh, qh->ferr, atridge);
    +    if (atridge->top && atridge->top != atfacet && atridge->top != otherfacet)
    +      qh_printfacet(qh, qh->ferr, atridge->top);
    +    if (atridge->bottom
    +        && atridge->bottom != atfacet && atridge->bottom != otherfacet)
    +      qh_printfacet(qh, qh->ferr, atridge->bottom);
    +    if (!atfacet)
    +      atfacet= atridge->top;
    +    if (!otherfacet)
    +      otherfacet= otherfacet_(atridge, atfacet);
    +  }
    +  if (atvertex) {
    +    qh_fprintf(qh, qh->ferr, 8138, "%s VERTEX:\n", string);
    +    qh_printvertex(qh, qh->ferr, atvertex);
    +  }
    +  if (qh->fout && qh->FORCEoutput && atfacet && !qh->QHULLfinished && !qh->IStracing) {
    +    qh_fprintf(qh, qh->ferr, 8139, "ERRONEOUS and NEIGHBORING FACETS to output\n");
    +    for (i=0; i < qh_PRINTEND; i++)  /* use fout for geomview output */
    +      qh_printneighborhood(qh, qh->fout, qh->PRINTout[i], atfacet, otherfacet,
    +                            !qh_ALL);
    +  }
    +} /* errprint */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetlist(qh, fp, facetlist, facets, printall )
    +    print all fields for a facet list and/or set of facets to fp
    +    if !printall,
    +      only prints good facets
    +
    +  notes:
    +    also prints all vertices
    +*/
    +void qh_printfacetlist(qhT *qh, facetT *facetlist, setT *facets, boolT printall) {
    +  facetT *facet, **facetp;
    +
    +  qh_printbegin(qh, qh->ferr, qh_PRINTfacets, facetlist, facets, printall);
    +  FORALLfacet_(facetlist)
    +    qh_printafacet(qh, qh->ferr, qh_PRINTfacets, facet, printall);
    +  FOREACHfacet_(facets)
    +    qh_printafacet(qh, qh->ferr, qh_PRINTfacets, facet, printall);
    +  qh_printend(qh, qh->ferr, qh_PRINTfacets, facetlist, facets, printall);
    +} /* printfacetlist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhelp_degenerate(qh, fp )
    +    prints descriptive message for precision error
    +
    +  notes:
    +    no message if qh_QUICKhelp
    +*/
    +void qh_printhelp_degenerate(qhT *qh, FILE *fp) {
    +
    +  if (qh->MERGEexact || qh->PREmerge || qh->JOGGLEmax < REALmax/2)
    +    qh_fprintf(qh, fp, 9368, "\n\
    +A Qhull error has occurred.  Qhull should have corrected the above\n\
    +precision error.  Please send the input and all of the output to\n\
    +qhull_bug@qhull.org\n");
    +  else if (!qh_QUICKhelp) {
    +    qh_fprintf(qh, fp, 9369, "\n\
    +Precision problems were detected during construction of the convex hull.\n\
    +This occurs because convex hull algorithms assume that calculations are\n\
    +exact, but floating-point arithmetic has roundoff errors.\n\
    +\n\
    +To correct for precision problems, do not use 'Q0'.  By default, Qhull\n\
    +selects 'C-0' or 'Qx' and merges non-convex facets.  With option 'QJ',\n\
    +Qhull joggles the input to prevent precision problems.  See \"Imprecision\n\
    +in Qhull\" (qh-impre.htm).\n\
    +\n\
    +If you use 'Q0', the output may include\n\
    +coplanar ridges, concave ridges, and flipped facets.  In 4-d and higher,\n\
    +Qhull may produce a ridge with four neighbors or two facets with the same \n\
    +vertices.  Qhull reports these events when they occur.  It stops when a\n\
    +concave ridge, flipped facet, or duplicate facet occurs.\n");
    +#if REALfloat
    +    qh_fprintf(qh, fp, 9370, "\
    +\n\
    +Qhull is currently using single precision arithmetic.  The following\n\
    +will probably remove the precision problems:\n\
    +  - recompile qhull for realT precision(#define REALfloat 0 in user.h).\n");
    +#endif
    +    if (qh->DELAUNAY && !qh->SCALElast && qh->MAXabs_coord > 1e4)
    +      qh_fprintf(qh, fp, 9371, "\
    +\n\
    +When computing the Delaunay triangulation of coordinates > 1.0,\n\
    +  - use 'Qbb' to scale the last coordinate to [0,m] (max previous coordinate)\n");
    +    if (qh->DELAUNAY && !qh->ATinfinity)
    +      qh_fprintf(qh, fp, 9372, "\
    +When computing the Delaunay triangulation:\n\
    +  - use 'Qz' to add a point at-infinity.  This reduces precision problems.\n");
    +
    +    qh_fprintf(qh, fp, 9373, "\
    +\n\
    +If you need triangular output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft'.  It triangulates non-simplicial facets with added points.\n\
    +\n\
    +If you must use 'Q0',\n\
    +try one or more of the following options.  They can not guarantee an output.\n\
    +  - use 'QbB' to scale the input to a cube.\n\
    +  - use 'Po' to produce output and prevent partitioning for flipped facets\n\
    +  - use 'V0' to set min. distance to visible facet as 0 instead of roundoff\n\
    +  - use 'En' to specify a maximum roundoff error less than %2.2g.\n\
    +  - options 'Qf', 'Qbb', and 'QR0' may also help\n",
    +               qh->DISTround);
    +    qh_fprintf(qh, fp, 9374, "\
    +\n\
    +To guarantee simplicial output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft' to triangulate the output by adding points\n\
    +  - use exact arithmetic (see \"Imprecision in Qhull\", qh-impre.htm)\n\
    +");
    +  }
    +} /* printhelp_degenerate */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhelp_narrowhull(qh, minangle )
    +    Warn about a narrow hull
    +
    +  notes:
    +    Alternatively, reduce qh_WARNnarrow in user.h
    +
    +*/
    +void qh_printhelp_narrowhull(qhT *qh, FILE *fp, realT minangle) {
    +
    +    qh_fprintf(qh, fp, 9375, "qhull precision warning: \n\
    +The initial hull is narrow (cosine of min. angle is %.16f).\n\
    +Is the input lower dimensional (e.g., on a plane in 3-d)?  Qhull may\n\
    +produce a wide facet.  Options 'QbB' (scale to unit box) or 'Qbb' (scale\n\
    +last coordinate) may remove this warning.  Use 'Pp' to skip this warning.\n\
    +See 'Limitations' in qh-impre.htm.\n",
    +          -minangle);   /* convert from angle between normals to angle between facets */
    +} /* printhelp_narrowhull */
    +
    +/*---------------------------------
    +
    +  qh_printhelp_singular(qh, fp )
    +    prints descriptive message for singular input
    +*/
    +void qh_printhelp_singular(qhT *qh, FILE *fp) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  realT min, max, *coord, dist;
    +  int i,k;
    +
    +  qh_fprintf(qh, fp, 9376, "\n\
    +The input to qhull appears to be less than %d dimensional, or a\n\
    +computation has overflowed.\n\n\
    +Qhull could not construct a clearly convex simplex from points:\n",
    +           qh->hull_dim);
    +  qh_printvertexlist(qh, fp, "", qh->facet_list, NULL, qh_ALL);
    +  if (!qh_QUICKhelp)
    +    qh_fprintf(qh, fp, 9377, "\n\
    +The center point is coplanar with a facet, or a vertex is coplanar\n\
    +with a neighboring facet.  The maximum round off error for\n\
    +computing distances is %2.2g.  The center point, facets and distances\n\
    +to the center point are as follows:\n\n", qh->DISTround);
    +  qh_printpointid(qh, fp, "center point", qh->hull_dim, qh->interior_point, qh_IDunknown);
    +  qh_fprintf(qh, fp, 9378, "\n");
    +  FORALLfacets {
    +    qh_fprintf(qh, fp, 9379, "facet");
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(qh, fp, 9380, " p%d", qh_pointid(qh, vertex->point));
    +    zinc_(Zdistio);
    +    qh_distplane(qh, qh->interior_point, facet, &dist);
    +    qh_fprintf(qh, fp, 9381, " distance= %4.2g\n", dist);
    +  }
    +  if (!qh_QUICKhelp) {
    +    if (qh->HALFspace)
    +      qh_fprintf(qh, fp, 9382, "\n\
    +These points are the dual of the given halfspaces.  They indicate that\n\
    +the intersection is degenerate.\n");
    +    qh_fprintf(qh, fp, 9383,"\n\
    +These points either have a maximum or minimum x-coordinate, or\n\
    +they maximize the determinant for k coordinates.  Trial points\n\
    +are first selected from points that maximize a coordinate.\n");
    +    if (qh->hull_dim >= qh_INITIALmax)
    +      qh_fprintf(qh, fp, 9384, "\n\
    +Because of the high dimension, the min x-coordinate and max-coordinate\n\
    +points are used if the determinant is non-zero.  Option 'Qs' will\n\
    +do a better, though much slower, job.  Instead of 'Qs', you can change\n\
    +the points by randomly rotating the input with 'QR0'.\n");
    +  }
    +  qh_fprintf(qh, fp, 9385, "\nThe min and max coordinates for each dimension are:\n");
    +  for (k=0; k < qh->hull_dim; k++) {
    +    min= REALmax;
    +    max= -REALmin;
    +    for (i=qh->num_points, coord= qh->first_point+k; i--; coord += qh->hull_dim) {
    +      maximize_(max, *coord);
    +      minimize_(min, *coord);
    +    }
    +    qh_fprintf(qh, fp, 9386, "  %d:  %8.4g  %8.4g  difference= %4.4g\n", k, min, max, max-min);
    +  }
    +  if (!qh_QUICKhelp) {
    +    qh_fprintf(qh, fp, 9387, "\n\
    +If the input should be full dimensional, you have several options that\n\
    +may determine an initial simplex:\n\
    +  - use 'QJ'  to joggle the input and make it full dimensional\n\
    +  - use 'QbB' to scale the points to the unit cube\n\
    +  - use 'QR0' to randomly rotate the input for different maximum points\n\
    +  - use 'Qs'  to search all points for the initial simplex\n\
    +  - use 'En'  to specify a maximum roundoff error less than %2.2g.\n\
    +  - trace execution with 'T3' to see the determinant for each point.\n",
    +                     qh->DISTround);
    +#if REALfloat
    +    qh_fprintf(qh, fp, 9388, "\
    +  - recompile qhull for realT precision(#define REALfloat 0 in libqhull_r.h).\n");
    +#endif
    +    qh_fprintf(qh, fp, 9389, "\n\
    +If the input is lower dimensional:\n\
    +  - use 'QJ' to joggle the input and make it full dimensional\n\
    +  - use 'Qbk:0Bk:0' to delete coordinate k from the input.  You should\n\
    +    pick the coordinate with the least range.  The hull will have the\n\
    +    correct topology.\n\
    +  - determine the flat containing the points, rotate the points\n\
    +    into a coordinate plane, and delete the other coordinates.\n\
    +  - add one or more points to make the input full dimensional.\n\
    +");
    +  }
    +} /* printhelp_singular */
    +
    +/*---------------------------------
    +
    +  qh_user_memsizes(qh)
    +    allocate up to 10 additional, quick allocation sizes
    +
    +  notes:
    +    increase maximum number of allocations in qh_initqhull_mem()
    +*/
    +void qh_user_memsizes(qhT *qh) {
    +
    +  QHULL_UNUSED(qh)
    +  /* qh_memsize(qh, size); */
    +} /* user_memsizes */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/user_r.h b/xs/src/qhull/src/libqhull_r/user_r.h
    new file mode 100644
    index 0000000000..7cca65a804
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/user_r.h
    @@ -0,0 +1,882 @@
    +/*
      ---------------------------------
    +
    +   user.h
    +   user redefinable constants
    +
    +   for each source file, user_r.h is included first
    +
    +   see qh-user_r.htm.  see COPYING for copyright information.
    +
    +   See user_r.c for sample code.
    +
    +   before reading any code, review libqhull_r.h for data structure definitions
    +
    +Sections:
    +   ============= qhull library constants ======================
    +   ============= data types and configuration macros ==========
    +   ============= performance related constants ================
    +   ============= memory constants =============================
    +   ============= joggle constants =============================
    +   ============= conditional compilation ======================
    +   ============= -merge constants- ============================
    +
    +Code flags --
    +  NOerrors -- the code does not call qh_errexit()
    +  WARN64 -- the code may be incompatible with 64-bit pointers
    +
    +*/
    +
    +#include 
    +
    +#ifndef qhDEFuser
    +#define qhDEFuser 1
    +
    +/* Derived from Qt's corelib/global/qglobal.h */
    +#if !defined(SAG_COM) && !defined(__CYGWIN__) && (defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__))
    +#   define QHULL_OS_WIN
    +#elif defined(__MWERKS__) && defined(__INTEL__) /* Metrowerks discontinued before the release of Intel Macs */
    +#   define QHULL_OS_WIN
    +#endif
    +
    +/*============================================================*/
    +/*============= qhull library constants ======================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  FILENAMElen -- max length for TI and TO filenames
    +
    +*/
    +
    +#define qh_FILENAMElen 500
    +
    +/*----------------------------------
    +
    +  msgcode -- Unique message codes for qh_fprintf
    +
    +  If add new messages, assign these values and increment in user.h and user_r.h
    +  See QhullError.h for 10000 errors.
    +
    +  def counters =  [27, 1048, 2059, 3026, 4068, 5003,
    +     6273, 7081, 8147, 9411, 10000, 11029]
    +
    +  See: qh_ERR* [libqhull_r.h]
    +*/
    +
    +#define MSG_TRACE0 0
    +#define MSG_TRACE1 1000
    +#define MSG_TRACE2 2000
    +#define MSG_TRACE3 3000
    +#define MSG_TRACE4 4000
    +#define MSG_TRACE5 5000
    +#define MSG_ERROR  6000   /* errors written to qh.ferr */
    +#define MSG_WARNING 7000
    +#define MSG_STDERR  8000  /* log messages Written to qh.ferr */
    +#define MSG_OUTPUT  9000
    +#define MSG_QHULL_ERROR 10000 /* errors thrown by QhullError.cpp (QHULLlastError is in QhullError.h) */
    +#define MSG_FIXUP  11000  /* FIXUP QH11... */
    +#define MSG_MAXLEN  3000 /* qh_printhelp_degenerate() in user.c */
    +
    +
    +/*----------------------------------
    +
    +  qh_OPTIONline -- max length of an option line 'FO'
    +*/
    +#define qh_OPTIONline 80
    +
    +/*============================================================*/
    +/*============= data types and configuration macros ==========*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  realT
    +    set the size of floating point numbers
    +
    +  qh_REALdigits
    +    maximimum number of significant digits
    +
    +  qh_REAL_1, qh_REAL_2n, qh_REAL_3n
    +    format strings for printf
    +
    +  qh_REALmax, qh_REALmin
    +    maximum and minimum (near zero) values
    +
    +  qh_REALepsilon
    +    machine roundoff.  Maximum roundoff error for addition and multiplication.
    +
    +  notes:
    +   Select whether to store floating point numbers in single precision (float)
    +   or double precision (double).
    +
    +   Use 'float' to save about 8% in time and 25% in space.  This is particularly
    +   helpful if high-d where convex hulls are space limited.  Using 'float' also
    +   reduces the printed size of Qhull's output since numbers have 8 digits of
    +   precision.
    +
    +   Use 'double' when greater arithmetic precision is needed.  This is needed
    +   for Delaunay triangulations and Voronoi diagrams when you are not merging
    +   facets.
    +
    +   If 'double' gives insufficient precision, your data probably includes
    +   degeneracies.  If so you should use facet merging (done by default)
    +   or exact arithmetic (see imprecision section of manual, qh-impre.htm).
    +   You may also use option 'Po' to force output despite precision errors.
    +
    +   You may use 'long double', but many format statements need to be changed
    +   and you may need a 'long double' square root routine.  S. Grundmann
    +   (sg@eeiwzb.et.tu-dresden.de) has done this.  He reports that the code runs
    +   much slower with little gain in precision.
    +
    +   WARNING: on some machines,    int f(){realT a= REALmax;return (a == REALmax);}
    +      returns False.  Use (a > REALmax/2) instead of (a == REALmax).
    +
    +   REALfloat =   1      all numbers are 'float' type
    +             =   0      all numbers are 'double' type
    +*/
    +#define REALfloat 1
    +
    +#if (REALfloat == 1)
    +#define realT float
    +#define REALmax FLT_MAX
    +#define REALmin FLT_MIN
    +#define REALepsilon FLT_EPSILON
    +#define qh_REALdigits 8   /* maximum number of significant digits */
    +#define qh_REAL_1 "%6.8g "
    +#define qh_REAL_2n "%6.8g %6.8g\n"
    +#define qh_REAL_3n "%6.8g %6.8g %6.8g\n"
    +
    +#elif (REALfloat == 0)
    +#define realT double
    +#define REALmax DBL_MAX
    +#define REALmin DBL_MIN
    +#define REALepsilon DBL_EPSILON
    +#define qh_REALdigits 16    /* maximum number of significant digits */
    +#define qh_REAL_1 "%6.16g "
    +#define qh_REAL_2n "%6.16g %6.16g\n"
    +#define qh_REAL_3n "%6.16g %6.16g %6.16g\n"
    +
    +#else
    +#error unknown float option
    +#endif
    +
    +/*----------------------------------
    +
    +  countT
    +    The type for counts and identifiers (e.g., the number of points, vertex identifiers)
    +    Currently used by C++ code-only.  Decided against using it for setT because most sets are small.
    +
    +    Defined as 'int' for C-code compatibility and QH11026
    +
    +    FIXUP QH11026 countT may be defined as a unsigned value, but several code issues need to be solved first.  See countT in Changes.txt
    +*/
    +
    +#ifndef DEFcountT
    +#define DEFcountT 1
    +typedef int countT;
    +#endif
    +#define COUNTmax 0x7fffffff
    +
    +
    +/*----------------------------------
    +
    +  qh_CPUclock
    +    define the clock() function for reporting the total time spent by Qhull
    +    returns CPU ticks as a 'long int'
    +    qh_CPUclock is only used for reporting the total time spent by Qhull
    +
    +  qh_SECticks
    +    the number of clock ticks per second
    +
    +  notes:
    +    looks for CLOCKS_PER_SEC, CLOCKS_PER_SECOND, or assumes microseconds
    +    to define a custom clock, set qh_CLOCKtype to 0
    +
    +    if your system does not use clock() to return CPU ticks, replace
    +    qh_CPUclock with the corresponding function.  It is converted
    +    to 'unsigned long' to prevent wrap-around during long runs.  By default,
    +     defines clock_t as 'long'
    +
    +   Set qh_CLOCKtype to
    +
    +     1          for CLOCKS_PER_SEC, CLOCKS_PER_SECOND, or microsecond
    +                Note:  may fail if more than 1 hour elapsed time
    +
    +     2          use qh_clock() with POSIX times() (see global_r.c)
    +*/
    +#define qh_CLOCKtype 1  /* change to the desired number */
    +
    +#if (qh_CLOCKtype == 1)
    +
    +#if defined(CLOCKS_PER_SECOND)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLOCKS_PER_SECOND
    +
    +#elif defined(CLOCKS_PER_SEC)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLOCKS_PER_SEC
    +
    +#elif defined(CLK_TCK)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLK_TCK
    +
    +#else
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks 1E6
    +#endif
    +
    +#elif (qh_CLOCKtype == 2)
    +#define qh_CPUclock    qh_clock()  /* return CPU clock */
    +#define qh_SECticks 100
    +
    +#else /* qh_CLOCKtype == ? */
    +#error unknown clock option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_RANDOMtype, qh_RANDOMmax, qh_RANDOMseed
    +    define random number generator
    +
    +    qh_RANDOMint generates a random integer between 0 and qh_RANDOMmax.
    +    qh_RANDOMseed sets the random number seed for qh_RANDOMint
    +
    +  Set qh_RANDOMtype (default 5) to:
    +    1       for random() with 31 bits (UCB)
    +    2       for rand() with RAND_MAX or 15 bits (system 5)
    +    3       for rand() with 31 bits (Sun)
    +    4       for lrand48() with 31 bits (Solaris)
    +    5       for qh_rand(qh) with 31 bits (included with Qhull, requires 'qh')
    +
    +  notes:
    +    Random numbers are used by rbox to generate point sets.  Random
    +    numbers are used by Qhull to rotate the input ('QRn' option),
    +    simulate a randomized algorithm ('Qr' option), and to simulate
    +    roundoff errors ('Rn' option).
    +
    +    Random number generators differ between systems.  Most systems provide
    +    rand() but the period varies.  The period of rand() is not critical
    +    since qhull does not normally use random numbers.
    +
    +    The default generator is Park & Miller's minimal standard random
    +    number generator [CACM 31:1195 '88].  It is included with Qhull.
    +
    +    If qh_RANDOMmax is wrong, qhull will report a warning and Geomview
    +    output will likely be invisible.
    +*/
    +#define qh_RANDOMtype 5   /* *** change to the desired number *** */
    +
    +#if (qh_RANDOMtype == 1)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, random()/MAX */
    +#define qh_RANDOMint random()
    +#define qh_RANDOMseed_(qh, seed) srandom(seed);
    +
    +#elif (qh_RANDOMtype == 2)
    +#ifdef RAND_MAX
    +#define qh_RANDOMmax ((realT)RAND_MAX)
    +#else
    +#define qh_RANDOMmax ((realT)32767)   /* 15 bits (System 5) */
    +#endif
    +#define qh_RANDOMint  rand()
    +#define qh_RANDOMseed_(qh, seed) srand((unsigned)seed);
    +
    +#elif (qh_RANDOMtype == 3)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, Sun */
    +#define qh_RANDOMint  rand()
    +#define qh_RANDOMseed_(qh, seed) srand((unsigned)seed);
    +
    +#elif (qh_RANDOMtype == 4)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, lrand38()/MAX */
    +#define qh_RANDOMint lrand48()
    +#define qh_RANDOMseed_(qh, seed) srand48(seed);
    +
    +#elif (qh_RANDOMtype == 5)  /* 'qh' is an implicit parameter */
    +#define qh_RANDOMmax ((realT)2147483646UL)  /* 31 bits, qh_rand/MAX */
    +#define qh_RANDOMint qh_rand(qh)
    +#define qh_RANDOMseed_(qh, seed) qh_srand(qh, seed);
    +/* unlike rand(), never returns 0 */
    +
    +#else
    +#error: unknown random option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_ORIENTclock
    +    0 for inward pointing normals by Geomview convention
    +*/
    +#define qh_ORIENTclock 0
    +
    +
    +/*============================================================*/
    +/*============= joggle constants =============================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +qh_JOGGLEdefault
    +default qh.JOGGLEmax is qh.DISTround * qh_JOGGLEdefault
    +
    +notes:
    +rbox s r 100 | qhull QJ1e-15 QR0 generates 90% faults at distround 7e-16
    +rbox s r 100 | qhull QJ1e-14 QR0 generates 70% faults
    +rbox s r 100 | qhull QJ1e-13 QR0 generates 35% faults
    +rbox s r 100 | qhull QJ1e-12 QR0 generates 8% faults
    +rbox s r 100 | qhull QJ1e-11 QR0 generates 1% faults
    +rbox s r 100 | qhull QJ1e-10 QR0 generates 0% faults
    +rbox 1000 W0 | qhull QJ1e-12 QR0 generates 86% faults
    +rbox 1000 W0 | qhull QJ1e-11 QR0 generates 20% faults
    +rbox 1000 W0 | qhull QJ1e-10 QR0 generates 2% faults
    +the later have about 20 points per facet, each of which may interfere
    +
    +pick a value large enough to avoid retries on most inputs
    +*/
    +#define qh_JOGGLEdefault 30000.0
    +
    +/*----------------------------------
    +
    +qh_JOGGLEincrease
    +factor to increase qh.JOGGLEmax on qh_JOGGLEretry or qh_JOGGLEagain
    +*/
    +#define qh_JOGGLEincrease 10.0
    +
    +/*----------------------------------
    +
    +qh_JOGGLEretry
    +if ZZretry = qh_JOGGLEretry, increase qh.JOGGLEmax
    +
    +notes:
    +try twice at the original value in case of bad luck the first time
    +*/
    +#define qh_JOGGLEretry 2
    +
    +/*----------------------------------
    +
    +qh_JOGGLEagain
    +every following qh_JOGGLEagain, increase qh.JOGGLEmax
    +
    +notes:
    +1 is OK since it's already failed qh_JOGGLEretry times
    +*/
    +#define qh_JOGGLEagain 1
    +
    +/*----------------------------------
    +
    +qh_JOGGLEmaxincrease
    +maximum qh.JOGGLEmax due to qh_JOGGLEincrease
    +relative to qh.MAXwidth
    +
    +notes:
    +qh.joggleinput will retry at this value until qh_JOGGLEmaxretry
    +*/
    +#define qh_JOGGLEmaxincrease 1e-2
    +
    +/*----------------------------------
    +
    +qh_JOGGLEmaxretry
    +stop after qh_JOGGLEmaxretry attempts
    +*/
    +#define qh_JOGGLEmaxretry 100
    +
    +/*============================================================*/
    +/*============= performance related constants ================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  qh_HASHfactor
    +    total hash slots / used hash slots.  Must be at least 1.1.
    +
    +  notes:
    +    =2 for at worst 50% occupancy for qh.hash_table and normally 25% occupancy
    +*/
    +#define qh_HASHfactor 2
    +
    +/*----------------------------------
    +
    +  qh_VERIFYdirect
    +    with 'Tv' verify all points against all facets if op count is smaller
    +
    +  notes:
    +    if greater, calls qh_check_bestdist() instead
    +*/
    +#define qh_VERIFYdirect 1000000
    +
    +/*----------------------------------
    +
    +  qh_INITIALsearch
    +     if qh_INITIALmax, search points up to this dimension
    +*/
    +#define qh_INITIALsearch 6
    +
    +/*----------------------------------
    +
    +  qh_INITIALmax
    +    if dim >= qh_INITIALmax, use min/max coordinate points for initial simplex
    +
    +  notes:
    +    from points with non-zero determinants
    +    use option 'Qs' to override (much slower)
    +*/
    +#define qh_INITIALmax 8
    +
    +/*============================================================*/
    +/*============= memory constants =============================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  qh_MEMalign
    +    memory alignment for qh_meminitbuffers() in global_r.c
    +
    +  notes:
    +    to avoid bus errors, memory allocation must consider alignment requirements.
    +    malloc() automatically takes care of alignment.   Since mem_r.c manages
    +    its own memory, we need to explicitly specify alignment in
    +    qh_meminitbuffers().
    +
    +    A safe choice is sizeof(double).  sizeof(float) may be used if doubles
    +    do not occur in data structures and pointers are the same size.  Be careful
    +    of machines (e.g., DEC Alpha) with large pointers.
    +
    +    If using gcc, best alignment is [fmax_() is defined in geom_r.h]
    +              #define qh_MEMalign fmax_(__alignof__(realT),__alignof__(void *))
    +*/
    +#define qh_MEMalign ((int)(fmax_(sizeof(realT), sizeof(void *))))
    +
    +/*----------------------------------
    +
    +  qh_MEMbufsize
    +    size of additional memory buffers
    +
    +  notes:
    +    used for qh_meminitbuffers() in global_r.c
    +*/
    +#define qh_MEMbufsize 0x10000       /* allocate 64K memory buffers */
    +
    +/*----------------------------------
    +
    +  qh_MEMinitbuf
    +    size of initial memory buffer
    +
    +  notes:
    +    use for qh_meminitbuffers() in global_r.c
    +*/
    +#define qh_MEMinitbuf 0x20000      /* initially allocate 128K buffer */
    +
    +/*----------------------------------
    +
    +  qh_INFINITE
    +    on output, indicates Voronoi center at infinity
    +*/
    +#define qh_INFINITE  -10.101
    +
    +/*----------------------------------
    +
    +  qh_DEFAULTbox
    +    default box size (Geomview expects 0.5)
    +
    +  qh_DEFAULTbox
    +    default box size for integer coorindate (rbox only)
    +*/
    +#define qh_DEFAULTbox 0.5
    +#define qh_DEFAULTzbox 1e6
    +
    +/*============================================================*/
    +/*============= conditional compilation ======================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  __cplusplus
    +    defined by C++ compilers
    +
    +  __MSC_VER
    +    defined by Microsoft Visual C++
    +
    +  __MWERKS__ && __INTEL__
    +    defined by Metrowerks when compiling for Windows (not Intel-based Macintosh)
    +
    +  __MWERKS__ && __POWERPC__
    +    defined by Metrowerks when compiling for PowerPC-based Macintosh
    +
    +  __STDC__
    +    defined for strict ANSI C
    +*/
    +
    +/*----------------------------------
    +
    +  qh_COMPUTEfurthest
    +    compute furthest distance to an outside point instead of storing it with the facet
    +    =1 to compute furthest
    +
    +  notes:
    +    computing furthest saves memory but costs time
    +      about 40% more distance tests for partitioning
    +      removes facet->furthestdist
    +*/
    +#define qh_COMPUTEfurthest 0
    +
    +/*----------------------------------
    +
    +  qh_KEEPstatistics
    +    =0 removes most of statistic gathering and reporting
    +
    +  notes:
    +    if 0, code size is reduced by about 4%.
    +*/
    +#define qh_KEEPstatistics 1
    +
    +/*----------------------------------
    +
    +  qh_MAXoutside
    +    record outer plane for each facet
    +    =1 to record facet->maxoutside
    +
    +  notes:
    +    this takes a realT per facet and slightly slows down qhull
    +    it produces better outer planes for geomview output
    +*/
    +#define qh_MAXoutside 1
    +
    +/*----------------------------------
    +
    +  qh_NOmerge
    +    disables facet merging if defined
    +
    +  notes:
    +    This saves about 10% space.
    +
    +    Unless 'Q0'
    +      qh_NOmerge sets 'QJ' to avoid precision errors
    +
    +    #define qh_NOmerge
    +
    +  see:
    +    qh_NOmem in mem_r.c
    +
    +    see user_r.c/user_eg.c for removing io_r.o
    +*/
    +
    +/*----------------------------------
    +
    +  qh_NOtrace
    +    no tracing if defined
    +
    +  notes:
    +    This saves about 5% space.
    +
    +    #define qh_NOtrace
    +*/
    +
    +#if 0  /* sample code */
    +    exitcode= qh_new_qhull(qhT *qh, dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +    qh_freeqhull(qhT *qh, !qh_ALL); /* frees long memory used by second call */
    +    qh_memfreeshort(qhT *qh, &curlong, &totlong);  /* frees short memory and memory allocator */
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_QUICKhelp
    +    =1 to use abbreviated help messages, e.g., for degenerate inputs
    +*/
    +#define qh_QUICKhelp    0
    +
    +/*============================================================*/
    +/*============= -merge constants- ============================*/
    +/*============================================================*/
    +/*
    +   These constants effect facet merging.  You probably will not need
    +   to modify them.  They effect the performance of facet merging.
    +*/
    +
    +/*----------------------------------
    +
    +  qh_DIMmergeVertex
    +    max dimension for vertex merging (it is not effective in high-d)
    +*/
    +#define qh_DIMmergeVertex 6
    +
    +/*----------------------------------
    +
    +  qh_DIMreduceBuild
    +     max dimension for vertex reduction during build (slow in high-d)
    +*/
    +#define qh_DIMreduceBuild 5
    +
    +/*----------------------------------
    +
    +  qh_BESTcentrum
    +     if > 2*dim+n vertices, qh_findbestneighbor() tests centrums (faster)
    +     else, qh_findbestneighbor() tests all vertices (much better merges)
    +
    +  qh_BESTcentrum2
    +     if qh_BESTcentrum2 * DIM3 + BESTcentrum < #vertices tests centrums
    +*/
    +#define qh_BESTcentrum 20
    +#define qh_BESTcentrum2 2
    +
    +/*----------------------------------
    +
    +  qh_BESTnonconvex
    +    if > dim+n neighbors, qh_findbestneighbor() tests nonconvex ridges.
    +
    +  notes:
    +    It is needed because qh_findbestneighbor is slow for large facets
    +*/
    +#define qh_BESTnonconvex 15
    +
    +/*----------------------------------
    +
    +  qh_MAXnewmerges
    +    if >n newmerges, qh_merge_nonconvex() calls qh_reducevertices_centrums.
    +
    +  notes:
    +    It is needed because postmerge can merge many facets at once
    +*/
    +#define qh_MAXnewmerges 2
    +
    +/*----------------------------------
    +
    +  qh_MAXnewcentrum
    +    if <= dim+n vertices (n approximates the number of merges),
    +      reset the centrum in qh_updatetested() and qh_mergecycle_facets()
    +
    +  notes:
    +    needed to reduce cost and because centrums may move too much if
    +    many vertices in high-d
    +*/
    +#define qh_MAXnewcentrum 5
    +
    +/*----------------------------------
    +
    +  qh_COPLANARratio
    +    for 3-d+ merging, qh.MINvisible is n*premerge_centrum
    +
    +  notes:
    +    for non-merging, it's DISTround
    +*/
    +#define qh_COPLANARratio 3
    +
    +/*----------------------------------
    +
    +  qh_DISToutside
    +    When is a point clearly outside of a facet?
    +    Stops search in qh_findbestnew or qh_partitionall
    +    qh_findbest uses qh.MINoutside since since it is only called if no merges.
    +
    +  notes:
    +    'Qf' always searches for best facet
    +    if !qh.MERGING, same as qh.MINoutside.
    +    if qh_USEfindbestnew, increase value since neighboring facets may be ill-behaved
    +      [Note: Zdelvertextot occurs normally with interior points]
    +            RBOX 1000 s Z1 G1e-13 t1001188774 | QHULL Tv
    +    When there is a sharp edge, need to move points to a
    +    clearly good facet; otherwise may be lost in another partitioning.
    +    if too big then O(n^2) behavior for partitioning in cone
    +    if very small then important points not processed
    +    Needed in qh_partitionall for
    +      RBOX 1000 s Z1 G1e-13 t1001032651 | QHULL Tv
    +    Needed in qh_findbestnew for many instances of
    +      RBOX 1000 s Z1 G1e-13 t | QHULL Tv
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_DISToutside ((qh_USEfindbestnew ? 2 : 1) * \
    +     fmax_((qh->MERGING ? 2 : 1)*qh->MINoutside, qh->max_outside))
    +
    +/*----------------------------------
    +
    +  qh_RATIOnearinside
    +    ratio of qh.NEARinside to qh.ONEmerge for retaining inside points for
    +    qh_check_maxout().
    +
    +  notes:
    +    This is overkill since do not know the correct value.
    +    It effects whether 'Qc' reports all coplanar points
    +    Not used for 'd' since non-extreme points are coplanar
    +*/
    +#define qh_RATIOnearinside 5
    +
    +/*----------------------------------
    +
    +  qh_SEARCHdist
    +    When is a facet coplanar with the best facet?
    +    qh_findbesthorizon: all coplanar facets of the best facet need to be searched.
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_SEARCHdist ((qh_USEfindbestnew ? 2 : 1) * \
    +      (qh->max_outside + 2 * qh->DISTround + fmax_( qh->MINvisible, qh->MAXcoplanar)));
    +
    +/*----------------------------------
    +
    +  qh_USEfindbestnew
    +     Always use qh_findbestnew for qh_partitionpoint, otherwise use
    +     qh_findbestnew if merged new facet or sharpnewfacets.
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_USEfindbestnew (zzval_(Ztotmerge) > 50)
    +
    +/*----------------------------------
    +
    +  qh_WIDEcoplanar
    +    n*MAXcoplanar or n*MINvisible for a WIDEfacet
    +
    +    if vertex is further than qh.WIDEfacet from the hyperplane
    +    then its ridges are not counted in computing the area, and
    +    the facet's centrum is frozen.
    +
    +  notes:
    +   qh.WIDEfacet= max(qh.MAXoutside,qh_WIDEcoplanar*qh.MAXcoplanar,
    +      qh_WIDEcoplanar * qh.MINvisible);
    +*/
    +#define qh_WIDEcoplanar 6
    +
    +/*----------------------------------
    +
    +  qh_WIDEduplicate
    +    Merge ratio for errexit from qh_forcedmerges due to duplicate ridge
    +    Override with option Q12 no-wide-duplicate
    +
    +    Notes:
    +      Merging a duplicate ridge can lead to very wide facets.
    +      A future release of qhull will avoid duplicate ridges by removing duplicate sub-ridges from the horizon
    +*/
    +#define qh_WIDEduplicate 100
    +
    +/*----------------------------------
    +
    +  qh_MAXnarrow
    +    max. cosine in initial hull that sets qh.NARROWhull
    +
    +  notes:
    +    If qh.NARROWhull, the initial partition does not make
    +    coplanar points.  If narrow, a coplanar point can be
    +    coplanar to two facets of opposite orientations and
    +    distant from the exact convex hull.
    +
    +    Conservative estimate.  Don't actually see problems until it is -1.0
    +*/
    +#define qh_MAXnarrow -0.99999999
    +
    +/*----------------------------------
    +
    +  qh_WARNnarrow
    +    max. cosine in initial hull to warn about qh.NARROWhull
    +
    +  notes:
    +    this is a conservative estimate.
    +    Don't actually see problems until it is -1.0.  See qh-impre.htm
    +*/
    +#define qh_WARNnarrow -0.999999999999999
    +
    +/*----------------------------------
    +
    +  qh_ZEROdelaunay
    +    a zero Delaunay facet occurs for input sites coplanar with their convex hull
    +    the last normal coefficient of a zero Delaunay facet is within
    +        qh_ZEROdelaunay * qh.ANGLEround of 0
    +
    +  notes:
    +    qh_ZEROdelaunay does not allow for joggled input ('QJ').
    +
    +    You can avoid zero Delaunay facets by surrounding the input with a box.
    +
    +    Use option 'PDk:-n' to explicitly define zero Delaunay facets
    +      k= dimension of input sites (e.g., 3 for 3-d Delaunay triangulation)
    +      n= the cutoff for zero Delaunay facets (e.g., 'PD3:-1e-12')
    +*/
    +#define qh_ZEROdelaunay 2
    +
    +/*============================================================*/
    +/*============= Microsoft DevStudio ==========================*/
    +/*============================================================*/
    +
    +/*
    +   Finding Memory Leaks Using the CRT Library
    +   https://msdn.microsoft.com/en-us/library/x98tx3cf(v=vs.100).aspx
    +
    +   Reports enabled in qh_lib_check for Debug window and stderr
    +
    +   From 2005=>msvcr80d, 2010=>msvcr100d, 2012=>msvcr110d
    +
    +   Watch: {,,msvcr80d.dll}_crtBreakAlloc  Value from {n} in the leak report
    +   _CrtSetBreakAlloc(689); // qh_lib_check() [global_r.c]
    +
    +   Examples
    +     http://free-cad.sourceforge.net/SrcDocu/d2/d7f/MemDebug_8cpp_source.html
    +     https://github.com/illlust/Game/blob/master/library/MemoryLeak.cpp
    +*/
    +#if 0   /* off (0) by default for QHULL_CRTDBG */
    +#define QHULL_CRTDBG
    +#endif
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)
    +#define _CRTDBG_MAP_ALLOC
    +#include 
    +#include 
    +#endif
    +
    +#endif /* qh_DEFuser */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/usermem_r.c b/xs/src/qhull/src/libqhull_r/usermem_r.c
    new file mode 100644
    index 0000000000..3297b03185
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/usermem_r.c
    @@ -0,0 +1,94 @@
    +/*
      ---------------------------------
    +
    +   usermem_r.c
    +   qh_exit(), qh_free(), and qh_malloc()
    +
    +   See README.txt.
    +
    +   If you redefine one of these functions you must redefine all of them.
    +   If you recompile and load this file, then usermem.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull_r.h for data structures, macros, and user-callable functions.
    +   See user_r.c for qhull-related, redefinable functions
    +   see user_r.h for user-definable constants
    +   See userprintf_r.c for qh_fprintf and userprintf_rbox_r.c for qh_fprintf_rbox
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull_r.h"
    +
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +  qh_exit( exitcode )
    +    exit program
    +
    +  notes:
    +    qh_exit() is called when qh_errexit() and longjmp() are not available.
    +
    +    This is the only use of exit() in Qhull
    +    To replace qh_exit with 'throw', see libqhullcpp/usermem_r-cpp.cpp
    +*/
    +void qh_exit(int exitcode) {
    +    exit(exitcode);
    +} /* exit */
    +
    +/*---------------------------------
    +
    +  qh_fprintf_stderr( msgcode, format, list of args )
    +    fprintf to stderr with msgcode (non-zero)
    +
    +  notes:
    +    qh_fprintf_stderr() is called when qh->ferr is not defined, usually due to an initialization error
    +    
    +    It is typically followed by qh_errexit().
    +
    +    Redefine this function to avoid using stderr
    +
    +    Use qh_fprintf [userprintf_r.c] for normal printing
    +*/
    +void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    va_start(args, fmt);
    +    if(msgcode)
    +      fprintf(stderr, "QH%.4d ", msgcode);
    +    vfprintf(stderr, fmt, args);
    +    va_end(args);
    +} /* fprintf_stderr */
    +
    +/*---------------------------------
    +
    +  qh_free(qhT *qh, mem )
    +    free memory
    +
    +  notes:
    +    same as free()
    +    No calls to qh_errexit() 
    +*/
    +void qh_free(void *mem) {
    +    free(mem);
    +} /* free */
    +
    +/*---------------------------------
    +
    +    qh_malloc( mem )
    +      allocate memory
    +
    +    notes:
    +      same as malloc()
    +*/
    +void *qh_malloc(size_t size) {
    +    return malloc(size);
    +} /* malloc */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/userprintf_r.c b/xs/src/qhull/src/libqhull_r/userprintf_r.c
    new file mode 100644
    index 0000000000..6004491a1c
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/userprintf_r.c
    @@ -0,0 +1,65 @@
    +/*
      ---------------------------------
    +
    +   userprintf_r.c
    +   qh_fprintf()
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   If you recompile and load this file, then userprintf_r.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull_r.h for data structures, macros, and user-callable functions.
    +   See user_r.c for qhull-related, redefinable functions
    +   see user_r.h for user-definable constants
    +   See usermem_r.c for qh_exit(), qh_free(), and qh_malloc()
    +   see Qhull.cpp and RboxPoints.cpp for examples.
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +   qh_fprintf(qh, fp, msgcode, format, list of args )
    +     print arguments to *fp according to format
    +     Use qh_fprintf_rbox() for rboxlib_r.c
    +
    +   notes:
    +     same as fprintf()
    +     fgets() is not trapped like fprintf()
    +     exit qh_fprintf via qh_errexit()
    +     may be called for errors in qh_initstatistics and qh_meminit
    +*/
    +
    +void qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    if (!fp) {
    +        if(!qh){
    +            qh_fprintf_stderr(6241, "userprintf_r.c: fp and qh not defined for qh_fprintf '%s'", fmt);
    +            qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +        }
    +        /* could use qh->qhmem.ferr, but probably better to be cautious */
    +        qh_fprintf_stderr(6232, "Qhull internal error (userprintf_r.c): fp is 0.  Wrong qh_fprintf called.\n");
    +        qh_errexit(qh, 6232, NULL, NULL);
    +    }
    +    va_start(args, fmt);
    +    if (qh && qh->ANNOTATEoutput) {
    +      fprintf(fp, "[QH%.4d]", msgcode);
    +    }else if (msgcode >= MSG_ERROR && msgcode < MSG_STDERR ) {
    +      fprintf(fp, "QH%.4d ", msgcode);
    +    }
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +
    +    /* Place debugging traps here. Use with option 'Tn' */
    +
    +} /* qh_fprintf */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/userprintf_rbox_r.c b/xs/src/qhull/src/libqhull_r/userprintf_rbox_r.c
    new file mode 100644
    index 0000000000..1e721a22ae
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/userprintf_rbox_r.c
    @@ -0,0 +1,53 @@
    +/*
      ---------------------------------
    +
    +   userprintf_rbox_r.c
    +   qh_fprintf_rbox()
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   If you recompile and load this file, then userprintf_rbox_r.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull_r.h for data structures, macros, and user-callable functions.
    +   See user_r.c for qhull-related, redefinable functions
    +   see user_r.h for user-definable constants
    +   See usermem_r.c for qh_exit(), qh_free(), and qh_malloc()
    +   see Qhull.cpp and RboxPoints.cpp for examples.
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +   qh_fprintf_rbox(qh, fp, msgcode, format, list of args )
    +     print arguments to *fp according to format
    +     Use qh_fprintf_rbox() for rboxlib_r.c
    +
    +   notes:
    +     same as fprintf()
    +     fgets() is not trapped like fprintf()
    +     exit qh_fprintf_rbox via qh_errexit_rbox()
    +*/
    +
    +void qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    if (!fp) {
    +        qh_fprintf_stderr(6231, "Qhull internal error (userprintf_rbox_r.c): fp is 0.  Wrong qh_fprintf_rbox called.\n");
    +        qh_errexit_rbox(qh, 6231);
    +    }
    +    if (msgcode >= MSG_ERROR && msgcode < MSG_STDERR)
    +      fprintf(fp, "QH%.4d ", msgcode);
    +    va_start(args, fmt);
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +} /* qh_fprintf_rbox */
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/Coordinates.cpp b/xs/src/qhull/src/libqhullcpp/Coordinates.cpp
    new file mode 100644
    index 0000000000..806b438aba
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/Coordinates.cpp
    @@ -0,0 +1,198 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/Coordinates.cpp#4 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/Coordinates.h"
    +
    +#include "libqhullcpp/functionObjects.h"
    +#include "libqhullcpp/QhullError.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//! Coordinates -- vector of coordT (normally double)
    +
    +#//!\name Constructor
    +
    +#//!\name Element access
    +
    +// Inefficient without result-value-optimization or implicitly shared object
    +Coordinates Coordinates::
    +mid(countT idx, countT length) const
    +{
    +    countT newLength= length;
    +    if(length<0 || idx+length > count()){
    +        newLength= count()-idx;
    +    }
    +    Coordinates result;
    +    if(newLength>0){
    +        std::copy(begin()+idx, begin()+(idx+newLength), std::back_inserter(result));
    +    }
    +    return result;
    +}//mid
    +
    +coordT Coordinates::
    +value(countT idx, const coordT &defaultValue) const
    +{
    +    return ((idx < 0 || idx >= count()) ? defaultValue : (*this)[idx]);
    +}//value
    +
    +#//!\name GetSet
    +
    +Coordinates Coordinates::
    +operator+(const Coordinates &other) const
    +{
    +    Coordinates result(*this);
    +    std::copy(other.begin(), other.end(), std::back_inserter(result));
    +    return result;
    +}//operator+
    +
    +Coordinates & Coordinates::
    +operator+=(const Coordinates &other)
    +{
    +    if(&other==this){
    +        Coordinates clone(other);
    +        std::copy(clone.begin(), clone.end(), std::back_inserter(*this));
    +    }else{
    +        std::copy(other.begin(), other.end(), std::back_inserter(*this));
    +    }
    +    return *this;
    +}//operator+=
    +
    +#//!\name Read-write
    +
    +void Coordinates::
    +append(int pointDimension, coordT *c)
    +{
    +    if(c){
    +        coordT *p= c;
    +        for(int i= 0; i(i-begin())); // WARN64 coordinate index
    +            }
    +            ++i;
    +        }
    +    }
    +    return -1;
    +}//indexOf
    +
    +countT Coordinates::
    +lastIndexOf(const coordT &t, countT from) const
    +{
    +    if(from<0){
    +        from += count();
    +    }else if(from>=count()){
    +        from= count()-1;
    +    }
    +    if(from>=0){
    +        const_iterator i= begin()+from+1;
    +        while(i-- != constBegin()){
    +            if(*i==t){
    +                return (static_cast(i-begin())); // WARN64 coordinate index
    +            }
    +        }
    +    }
    +    return -1;
    +}//lastIndexOf
    +
    +void Coordinates::
    +removeAll(const coordT &t)
    +{
    +    MutableCoordinatesIterator i(*this);
    +    while(i.findNext(t)){
    +        i.remove();
    +    }
    +}//removeAll
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::istream;
    +using std::ostream;
    +using std::string;
    +using std::ws;
    +using orgQhull::Coordinates;
    +
    +ostream &
    +operator<<(ostream &os, const Coordinates &cs)
    +{
    +    Coordinates::const_iterator c= cs.begin();
    +    for(countT i=cs.count(); i--; ){
    +        os << *c++ << " ";
    +    }
    +    return os;
    +}//operator<<
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/Coordinates.h b/xs/src/qhull/src/libqhullcpp/Coordinates.h
    new file mode 100644
    index 0000000000..df8bd11386
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/Coordinates.h
    @@ -0,0 +1,303 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/Coordinates.h#6 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHCOORDINATES_H
    +#define QHCOORDINATES_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullIterator.h"
    +
    +#include  // ptrdiff_t, size_t
    +#include 
    +// Requires STL vector class.  Can use with another vector class such as QList.
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! An std::vector of point coordinates independent of dimension
    +    //! Used by PointCoordinates for RboxPoints and by Qhull for feasiblePoint
    +    //! A QhullPoint refers to previously allocated coordinates
    +    class Coordinates;
    +    class MutableCoordinatesIterator;
    +
    +class Coordinates {
    +
    +private:
    +#//!\name Fields
    +    std::vector coordinate_array;
    +
    +public:
    +#//!\name Subtypes
    +
    +    class const_iterator;
    +    class iterator;
    +    typedef iterator Iterator;
    +    typedef const_iterator ConstIterator;
    +
    +    typedef coordT              value_type;
    +    typedef const value_type   *const_pointer;
    +    typedef const value_type &  const_reference;
    +    typedef value_type *        pointer;
    +    typedef value_type &        reference;
    +    typedef ptrdiff_t           difference_type;
    +    typedef countT              size_type;
    +
    +#//!\name Construct
    +                        Coordinates() {};
    +    explicit            Coordinates(const std::vector &other) : coordinate_array(other) {}
    +                        Coordinates(const Coordinates &other) : coordinate_array(other.coordinate_array) {}
    +    Coordinates &       operator=(const Coordinates &other) { coordinate_array= other.coordinate_array; return *this; }
    +    Coordinates &       operator=(const std::vector &other) { coordinate_array= other; return *this; }
    +                        ~Coordinates() {}
    +
    +#//!\name Conversion
    +
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const { return coordinate_array; }
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList       toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +    countT              count() const { return static_cast(size()); }
    +    coordT *            data() { return isEmpty() ? 0 : &at(0); }
    +    const coordT *      data() const { return const_cast(isEmpty() ? 0 : &at(0)); }
    +    bool                isEmpty() const { return coordinate_array.empty(); }
    +    bool                operator==(const Coordinates &other) const  { return coordinate_array==other.coordinate_array; }
    +    bool                operator!=(const Coordinates &other) const  { return coordinate_array!=other.coordinate_array; }
    +    size_t              size() const { return coordinate_array.size(); }
    +
    +#//!\name Element access
    +    coordT &            at(countT idx) { return coordinate_array.at(idx); }
    +    const coordT &      at(countT idx) const { return coordinate_array.at(idx); }
    +    coordT &            back() { return coordinate_array.back(); }
    +    const coordT &      back() const { return coordinate_array.back(); }
    +    coordT &            first() { return front(); }
    +    const coordT &      first() const { return front(); }
    +    coordT &            front() { return coordinate_array.front(); }
    +    const coordT &      front() const { return coordinate_array.front(); }
    +    coordT &            last() { return back(); }
    +    const coordT &      last() const { return back(); }
    +    Coordinates         mid(countT idx, countT length= -1) const; //!<\todo countT -1 indicates
    +    coordT &            operator[](countT idx) { return coordinate_array.operator[](idx); }
    +    const coordT &      operator[](countT idx) const { return coordinate_array.operator[](idx); }
    +    coordT              value(countT idx, const coordT &defaultValue) const;
    +
    +#//!\name Iterator
    +    iterator            begin() { return iterator(coordinate_array.begin()); }
    +    const_iterator      begin() const { return const_iterator(coordinate_array.begin()); }
    +    const_iterator      constBegin() const { return begin(); }
    +    const_iterator      constEnd() const { return end(); }
    +    iterator            end() { return iterator(coordinate_array.end()); }
    +    const_iterator      end() const { return const_iterator(coordinate_array.end()); }
    +
    +#//!\name GetSet
    +    Coordinates         operator+(const Coordinates &other) const;
    +
    +#//!\name Modify
    +    void                append(int pointDimension, coordT *c);
    +    void                append(const coordT &c) { push_back(c); }
    +    void                clear() { coordinate_array.clear(); }
    +    iterator            erase(iterator idx) { return iterator(coordinate_array.erase(idx.base())); }
    +    iterator            erase(iterator beginIterator, iterator endIterator) { return iterator(coordinate_array.erase(beginIterator.base(), endIterator.base())); }
    +    void                insert(countT before, const coordT &c) { insert(begin()+before, c); }
    +    iterator            insert(iterator before, const coordT &c) { return iterator(coordinate_array.insert(before.base(), c)); }
    +    void                move(countT from, countT to) { insert(to, takeAt(from)); }
    +    Coordinates &       operator+=(const Coordinates &other);
    +    Coordinates &       operator+=(const coordT &c) { append(c); return *this; }
    +    Coordinates &       operator<<(const Coordinates &other) { return *this += other; }
    +    Coordinates &       operator<<(const coordT &c) { return *this += c; }
    +    void                pop_back() { coordinate_array.pop_back(); }
    +    void                pop_front() { removeFirst(); }
    +    void                prepend(const coordT &c) { insert(begin(), c); }
    +    void                push_back(const coordT &c) { coordinate_array.push_back(c); }
    +    void                push_front(const coordT &c) { insert(begin(), c); }
    +                        //removeAll below
    +    void                removeAt(countT idx) { erase(begin()+idx); }
    +    void                removeFirst() { erase(begin()); }
    +    void                removeLast() { erase(--end()); }
    +    void                replace(countT idx, const coordT &c) { (*this)[idx]= c; }
    +    void                reserve(countT i) { coordinate_array.reserve(i); }
    +    void                swap(countT idx, countT other);
    +    coordT              takeAt(countT idx);
    +    coordT              takeFirst() { return takeAt(0); }
    +    coordT              takeLast();
    +
    +#//!\name Search
    +    bool                contains(const coordT &t) const;
    +    countT              count(const coordT &t) const;
    +    countT              indexOf(const coordT &t, countT from = 0) const;
    +    countT              lastIndexOf(const coordT &t, countT from = -1) const;
    +    void                removeAll(const coordT &t);
    +
    +#//!\name Coordinates::iterator -- from QhullPoints, forwarding to coordinate_array
    +    // before const_iterator for conversion with comparison operators
    +    // Reviewed corelib/tools/qlist.h and corelib/tools/qvector.h w/o QT_STRICT_ITERATORS
    +    class iterator {
    +
    +    private:
    +        std::vector::iterator i;
    +        friend class const_iterator;
    +
    +    public:
    +        typedef std::random_access_iterator_tag  iterator_category;
    +        typedef coordT      value_type;
    +        typedef value_type *pointer;
    +        typedef value_type &reference;
    +        typedef ptrdiff_t   difference_type;
    +
    +                        iterator() {}
    +                        iterator(const iterator &other) { i= other.i; }
    +        explicit        iterator(const std::vector::iterator &vi) { i= vi; }
    +        iterator &      operator=(const iterator &other) { i= other.i; return *this; }
    +        std::vector::iterator &base() { return i; }
    +        coordT &        operator*() const { return *i; }
    +        // No operator->() when the base type is double
    +        coordT &        operator[](countT idx) const { return i[idx]; }
    +
    +        bool            operator==(const iterator &other) const { return i==other.i; }
    +        bool            operator!=(const iterator &other) const { return i!=other.i; }
    +        bool            operator<(const iterator &other) const { return i(const iterator &other) const { return i>other.i; }
    +        bool            operator>=(const iterator &other) const { return i>=other.i; }
    +              // reinterpret_cast to break circular dependency
    +        bool            operator==(const Coordinates::const_iterator &other) const { return *this==reinterpret_cast(other); }
    +        bool            operator!=(const Coordinates::const_iterator &other) const { return *this!=reinterpret_cast(other); }
    +        bool            operator<(const Coordinates::const_iterator &other) const { return *this(other); }
    +        bool            operator<=(const Coordinates::const_iterator &other) const { return *this<=reinterpret_cast(other); }
    +        bool            operator>(const Coordinates::const_iterator &other) const { return *this>reinterpret_cast(other); }
    +        bool            operator>=(const Coordinates::const_iterator &other) const { return *this>=reinterpret_cast(other); }
    +
    +        iterator &      operator++() { ++i; return *this; }
    +        iterator        operator++(int) { return iterator(i++); }
    +        iterator &      operator--() { --i; return *this; }
    +        iterator        operator--(int) { return iterator(i--); }
    +        iterator &      operator+=(countT idx) { i += idx; return *this; }
    +        iterator &      operator-=(countT idx) { i -= idx; return *this; }
    +        iterator        operator+(countT idx) const { return iterator(i+idx); }
    +        iterator        operator-(countT idx) const { return iterator(i-idx); }
    +        difference_type operator-(iterator other) const { return i-other.i; }
    +    };//Coordinates::iterator
    +
    +#//!\name Coordinates::const_iterator
    +    class const_iterator {
    +
    +    private:
    +        std::vector::const_iterator i;
    +
    +    public:
    +        typedef std::random_access_iterator_tag  iterator_category;
    +        typedef coordT            value_type;
    +        typedef const value_type *pointer;
    +        typedef const value_type &reference;
    +        typedef ptrdiff_t         difference_type;
    +
    +                        const_iterator() {}
    +                        const_iterator(const const_iterator &other) { i= other.i; }
    +                        const_iterator(const iterator &o) : i(o.i) {}
    +        explicit        const_iterator(const std::vector::const_iterator &vi) { i= vi; }
    +        const_iterator &operator=(const const_iterator &other) { i= other.i; return *this; }
    +        const coordT &  operator*() const { return *i; }
    +        // No operator->() when the base type is double
    +        const coordT &  operator[](countT idx) const { return i[idx]; }
    +
    +        bool            operator==(const const_iterator &other) const { return i==other.i; }
    +        bool            operator!=(const const_iterator &other) const { return i!=other.i; }
    +        bool            operator<(const const_iterator &other) const { return i(const const_iterator &other) const { return i>other.i; }
    +        bool            operator>=(const const_iterator &other) const { return i>=other.i; }
    +
    +        const_iterator & operator++() { ++i; return *this; } 
    +        const_iterator  operator++(int) { return const_iterator(i++); }
    +        const_iterator & operator--() { --i; return *this; }
    +        const_iterator  operator--(int) { return const_iterator(i--); }
    +        const_iterator & operator+=(countT idx) { i += idx; return *this; }
    +        const_iterator & operator-=(countT idx) { i -= idx; return *this; }
    +        const_iterator  operator+(countT idx) const { return const_iterator(i+idx); }
    +        const_iterator  operator-(countT idx) const { return const_iterator(i-idx); }
    +        difference_type operator-(const_iterator other) const { return i-other.i; }
    +    };//Coordinates::const_iterator
    +
    +};//Coordinates
    +
    +//class CoordinatesIterator
    +//QHULL_DECLARE_SEQUENTIAL_ITERATOR(Coordinates, coordT)
    +
    +class CoordinatesIterator
    +{
    +    typedef Coordinates::const_iterator const_iterator;
    +
    +private:
    +    const Coordinates * c;
    +    const_iterator      i;
    +
    +public:
    +                        CoordinatesIterator(const Coordinates &container): c(&container), i(c->constBegin()) {}
    +    CoordinatesIterator &operator=(const Coordinates &container) { c= &container; i= c->constBegin(); return *this; }
    +                        ~CoordinatesIterator() {}
    +
    +    bool                findNext(const coordT &t) { while (i != c->constEnd()) if(*i++ == t){ return true;} return false; }
    +    bool                findPrevious(const coordT &t) { while (i != c->constBegin())if (*(--i) == t){ return true;} return false;  }
    +    bool                hasNext() const { return i != c->constEnd(); }
    +    bool                hasPrevious() const { return i != c->constBegin(); }
    +    const coordT &      next() { return *i++; }
    +    const coordT &      previous() { return *--i; }
    +    const coordT &      peekNext() const { return *i; }
    +    const coordT &      peekPrevious() const { const_iterator p= i; return *--p; }
    +    void                toFront() { i= c->constBegin(); }
    +    void                toBack() { i= c->constEnd(); }
    +};//CoordinatesIterator
    +
    +//class MutableCoordinatesIterator
    +//QHULL_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR(Coordinates, coordT)
    +class MutableCoordinatesIterator
    +{
    +    typedef Coordinates::iterator iterator;
    +    typedef Coordinates::const_iterator const_iterator;
    +
    +private:
    +    Coordinates *       c;
    +    iterator            i;
    +    iterator            n;
    +    bool                item_exists() const { return const_iterator(n) != c->constEnd(); }
    +
    +public:
    +                        MutableCoordinatesIterator(Coordinates &container) : c(&container) { i= c->begin(); n= c->end(); }
    +    MutableCoordinatesIterator &operator=(Coordinates &container) { c= &container; i= c->begin(); n= c->end(); return *this; }
    +                        ~MutableCoordinatesIterator() {}
    +
    +    bool                findNext(const coordT &t) { while(c->constEnd()!=const_iterator(n= i)){ if(*i++==t){ return true;}} return false; }
    +    bool                findPrevious(const coordT &t) { while(c->constBegin()!=const_iterator(i)){ if(*(n= --i)== t){ return true;}} n= c->end(); return false;  }
    +    bool                hasNext() const { return (c->constEnd()!=const_iterator(i)); }
    +    bool                hasPrevious() const { return (c->constBegin()!=const_iterator(i)); }
    +    void                insert(const coordT &t) { n= i= c->insert(i, t); ++i; }
    +    coordT &            next() { n= i++; return *n; }
    +    coordT &            peekNext() const { return *i; }
    +    coordT &            peekPrevious() const { iterator p= i; return *--p; }
    +    coordT &            previous() { n= --i; return *n; }
    +    void                remove() { if(c->constEnd()!=const_iterator(n)){ i= c->erase(n); n= c->end();} }
    +    void                setValue(const coordT &t) const { if(c->constEnd()!=const_iterator(n)){ *n= t;} }
    +    void                toFront() { i= c->begin(); n= c->end(); }
    +    void                toBack() { i= c->end(); n= i; }
    +    coordT &            value() { QHULL_ASSERT(item_exists()); return *n; }
    +    const coordT &      value() const { QHULL_ASSERT(item_exists()); return *n; }
    +};//MutableCoordinatesIterator
    +
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::Coordinates &c);
    +
    +#endif // QHCOORDINATES_H
    diff --git a/xs/src/qhull/src/libqhullcpp/PointCoordinates.cpp b/xs/src/qhull/src/libqhullcpp/PointCoordinates.cpp
    new file mode 100644
    index 0000000000..a5b71e901d
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/PointCoordinates.cpp
    @@ -0,0 +1,348 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/PointCoordinates.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/PointCoordinates.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullPoint.h"
    +
    +#include 
    +#include 
    +
    +using std::istream;
    +using std::string;
    +using std::ws;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//! PointCoordinates -- vector of PointCoordinates
    +
    +#//!\name Constructors
    +
    +PointCoordinates::
    +PointCoordinates()
    +: QhullPoints()
    +, point_coordinates()
    +, describe_points()
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const std::string &aComment)
    +: QhullPoints()
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(int pointDimension, const std::string &aComment)
    +: QhullPoints()
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +}
    +
    +//! Qhull and QhullQh constructors are the same
    +PointCoordinates::
    +PointCoordinates(const Qhull &q)
    +: QhullPoints(q)
    +, point_coordinates()
    +, describe_points()
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const Qhull &q, const std::string &aComment)
    +: QhullPoints(q)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const Qhull &q, int pointDimension, const std::string &aComment)
    +: QhullPoints(q)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const Qhull &q, int pointDimension, const std::string &aComment, countT coordinatesCount, const coordT *c)
    +: QhullPoints(q)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +    append(coordinatesCount, c);
    +}
    +
    +PointCoordinates::
    +PointCoordinates(QhullQh *qqh)
    +: QhullPoints(qqh)
    +, point_coordinates()
    +, describe_points()
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(QhullQh *qqh, const std::string &aComment)
    +: QhullPoints(qqh)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(QhullQh *qqh, int pointDimension, const std::string &aComment)
    +: QhullPoints(qqh)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +}
    +
    +PointCoordinates::
    +PointCoordinates(QhullQh *qqh, int pointDimension, const std::string &aComment, countT coordinatesCount, const coordT *c)
    +: QhullPoints(qqh)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +    append(coordinatesCount, c);
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const PointCoordinates &other)
    +: QhullPoints(other)
    +, point_coordinates(other.point_coordinates)
    +, describe_points(other.describe_points)
    +{
    +    makeValid();  // Update point_first and point_end
    +}
    +
    +PointCoordinates & PointCoordinates::
    +operator=(const PointCoordinates &other)
    +{
    +    QhullPoints::operator=(other);
    +    point_coordinates= other.point_coordinates;
    +    describe_points= other.describe_points;
    +    makeValid(); // Update point_first and point_end
    +    return *this;
    +}//operator=
    +
    +PointCoordinates::
    +~PointCoordinates()
    +{ }
    +
    +#//!\name GetSet
    +
    +void PointCoordinates::
    +checkValid() const
    +{
    +    if(getCoordinates().data()!=data()
    +    || getCoordinates().count()!=coordinateCount()){
    +        throw QhullError(10060, "Qhull error: first point (%x) is not PointCoordinates.data() or count (%d) is not PointCoordinates.count (%d)", coordinateCount(), getCoordinates().count(), 0.0, data());
    +    }
    +}//checkValid
    +
    +void PointCoordinates::
    +setDimension(int i)
    +{
    +    if(i<0){
    +        throw QhullError(10062, "Qhull error: can not set PointCoordinates dimension to %d", i);
    +    }
    +    int currentDimension=QhullPoints::dimension();
    +    if(currentDimension!=0 && i!=currentDimension){
    +        throw QhullError(10063, "Qhull error: can not change PointCoordinates dimension (from %d to %d)", currentDimension, i);
    +    }
    +    QhullPoints::setDimension(i);
    +}//setDimension
    +
    +#//!\name Foreach
    +
    +Coordinates::ConstIterator PointCoordinates::
    +beginCoordinates(countT pointIndex) const
    +{
    +    return point_coordinates.begin()+indexOffset(pointIndex);
    +}
    +
    +Coordinates::Iterator PointCoordinates::
    +beginCoordinates(countT pointIndex)
    +{
    +    return point_coordinates.begin()+indexOffset(pointIndex);
    +}
    +
    +#//!\name Methods
    +
    +void PointCoordinates::
    +append(countT coordinatesCount, const coordT *c)
    +{
    +    if(coordinatesCount<=0){
    +        return;
    +    }
    +    if(includesCoordinates(c)){
    +        throw QhullError(10065, "Qhull error: can not append a subset of PointCoordinates to itself.  The coordinates for point %d may move.", indexOf(c, QhullError::NOthrow));
    +    }
    +    reserveCoordinates(coordinatesCount);
    +    std::copy(c, c+coordinatesCount, std::back_inserter(point_coordinates));
    +    makeValid();
    +}//append coordT
    +
    +void PointCoordinates::
    +append(const PointCoordinates &other)
    +{
    +    setDimension(other.dimension());
    +    append(other.coordinateCount(), other.data());
    +}//append PointCoordinates
    +
    +void PointCoordinates::
    +append(const QhullPoint &p)
    +{
    +    setDimension(p.dimension());
    +    append(p.dimension(), p.coordinates());
    +}//append QhullPoint
    +
    +void PointCoordinates::
    +appendComment(const std::string &s){
    +    if(char c= s[0] && describe_points.empty()){
    +        if(c=='-' || isdigit(c)){
    +            throw QhullError(10028, "Qhull argument error: comments can not start with a number or minus, %s", 0, 0, 0.0, s.c_str());
    +        }
    +    }
    +    describe_points += s;
    +}//appendComment
    +
    +//! Read PointCoordinates from istream.  First two numbers are dimension and count.  A non-digit starts a rboxCommand.
    +//! Overwrites describe_points.  See qh_readpoints [io.c]
    +void PointCoordinates::
    +appendPoints(istream &in)
    +{
    +    int inDimension;
    +    countT inCount;
    +    in >> ws >> inDimension >> ws;
    +    if(!in.good()){
    +        in.clear();
    +        string remainder;
    +        getline(in, remainder);
    +        throw QhullError(10005, "Qhull error: input did not start with dimension or count -- %s", 0, 0, 0, remainder.c_str());
    +    }
    +    char c= (char)in.peek();
    +    if(c!='-' && !isdigit(c)){         // Comments start with a non-digit
    +        getline(in, describe_points);
    +        in >> ws;
    +    }
    +    in >> inCount >> ws;
    +    if(!in.good()){
    +        in.clear();
    +        string remainder;
    +        getline(in, remainder);
    +        throw QhullError(10009, "Qhull error: input did not start with dimension and count -- %d %s", inDimension, 0, 0, remainder.c_str());
    +    }
    +    c= (char)in.peek();
    +    if(c!='-' && !isdigit(c)){         // Comments start with a non-digit
    +        getline(in, describe_points);
    +        in >> ws;
    +    }
    +    if(inCount> p >> ws;
    +        if(in.fail()){
    +            in.clear();
    +            string remainder;
    +            getline(in, remainder);
    +            throw QhullError(10008, "Qhull error: failed to read coordinate %d  of point %d\n   %s", coordinatesCount % inDimension, coordinatesCount/inDimension, 0, remainder.c_str());
    +        }else{
    +            point_coordinates.push_back(p);
    +            coordinatesCount++;
    +        }
    +    }
    +    if(coordinatesCount != inCount*inDimension){
    +        if(coordinatesCount%inDimension==0){
    +            throw QhullError(10006, "Qhull error: expected %d %d-d PointCoordinates but read %i PointCoordinates", int(inCount), inDimension, 0.0, int(coordinatesCount/inDimension));
    +        }else{
    +            throw QhullError(10012, "Qhull error: expected %d %d-d PointCoordinates but read %i PointCoordinates plus %f extra coordinates", inCount, inDimension, float(coordinatesCount%inDimension), coordinatesCount/inDimension);
    +        }
    +    }
    +    makeValid();
    +}//appendPoints istream
    +
    +PointCoordinates PointCoordinates::
    +operator+(const PointCoordinates &other) const
    +{
    +    PointCoordinates pc= *this;
    +    pc << other;
    +    return pc;
    +}//operator+
    +
    +void PointCoordinates::
    +reserveCoordinates(countT newCoordinates)
    +{
    +    // vector::reserve is not const
    +    point_coordinates.reserve((countT)point_coordinates.size()+newCoordinates); // WARN64
    +    makeValid();
    +}//reserveCoordinates
    +
    +#//!\name Helpers
    +
    +countT PointCoordinates::
    +indexOffset(countT i) const {
    +    countT n= i*dimension();
    +    countT coordinatesCount= point_coordinates.count();
    +    if(i<0 || n>coordinatesCount){
    +        throw QhullError(10061, "Qhull error: point_coordinates is too short (%d) for point %d", coordinatesCount, i);
    +    }
    +    return n;
    +}
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +
    +using orgQhull::Coordinates;
    +using orgQhull::PointCoordinates;
    +
    +ostream&
    +operator<<(ostream &os, const PointCoordinates &p)
    +{
    +    p.checkValid();
    +    countT count= p.count();
    +    int dimension= p.dimension();
    +    string comment= p.comment();
    +    if(comment.empty()){
    +        os << dimension << endl;
    +    }else{
    +        os << dimension << " " << comment << endl;
    +    }
    +    os << count << endl;
    +    Coordinates::ConstIterator c= p.beginCoordinates();
    +    for(countT i=0; i
    +#include 
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! QhullPoints with Coordinates and description
    +    //! Inherited by RboxPoints
    +    class PointCoordinates;
    +
    +class PointCoordinates : public QhullPoints {
    +
    +private:
    +#//!\name Fields
    +    Coordinates         point_coordinates;      //! std::vector of point coordinates
    +                                                //! may have extraCoordinates()
    +    std::string         describe_points;          //! Comment describing PointCoordinates
    +
    +public:
    +#//!\name Construct
    +    //! QhullPoint, PointCoordinates, and QhullPoints have similar constructors
    +    //! If Qhull/QhullQh is not initialized, then dimension()==0                        PointCoordinates();
    +                        PointCoordinates();
    +    explicit            PointCoordinates(const std::string &aComment);
    +                        PointCoordinates(int pointDimension, const std::string &aComment);
    +                        //! Qhull/QhullQh used for dimension() and QhullPoint equality
    +    explicit            PointCoordinates(const Qhull &q);
    +                        PointCoordinates(const Qhull &q, const std::string &aComment);
    +                        PointCoordinates(const Qhull &q, int pointDimension, const std::string &aComment);
    +                        PointCoordinates(const Qhull &q, int pointDimension, const std::string &aComment, countT coordinatesCount, const coordT *c); // may be invalid
    +                        //! Use append() and appendPoints() for Coordinates and vector
    +    explicit            PointCoordinates(QhullQh *qqh);
    +                        PointCoordinates(QhullQh *qqh, const std::string &aComment);
    +                        PointCoordinates(QhullQh *qqh, int pointDimension, const std::string &aComment);
    +                        PointCoordinates(QhullQh *qqh, int pointDimension, const std::string &aComment, countT coordinatesCount, const coordT *c); // may be invalid
    +                        //! Use append() and appendPoints() for Coordinates and vector
    +                        PointCoordinates(const PointCoordinates &other);
    +    PointCoordinates &  operator=(const PointCoordinates &other);
    +                        ~PointCoordinates();
    +
    +#//!\name Convert
    +    //! QhullPoints coordinates, constData, data, count, size
    +#ifndef QHULL_NO_STL
    +    void                append(const std::vector &otherCoordinates) { if(!otherCoordinates.empty()){ append((int)otherCoordinates.size(), &otherCoordinates[0]); } }
    +    std::vector toStdVector() const { return point_coordinates.toStdVector(); }
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    void                append(const QList &pointCoordinates) { if(!pointCoordinates.isEmpty()){ append(pointCoordinates.count(), &pointCoordinates[0]); } }
    +    QList       toQList() const { return point_coordinates.toQList(); }
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +    //! See QhullPoints for coordinates, coordinateCount, dimension, empty, isEmpty, ==, !=
    +    void                checkValid() const;
    +    std::string         comment() const { return describe_points; }
    +    void                makeValid() { defineAs(point_coordinates.count(), point_coordinates.data()); }
    +    const Coordinates & getCoordinates() const { return point_coordinates; }
    +    void                setComment(const std::string &s) { describe_points= s; }
    +    void                setDimension(int i);
    +
    +private:
    +    //! disable QhullPoints.defineAs()
    +    void                defineAs(countT coordinatesCount, coordT *c) { QhullPoints::defineAs(coordinatesCount, c); }
    +public:
    +
    +#//!\name ElementAccess
    +    //! See QhullPoints for at, back, first, front, last, mid, [], value
    +
    +#//!\name Foreach
    +    //! See QhullPoints for begin, constBegin, end
    +    Coordinates::ConstIterator  beginCoordinates() const { return point_coordinates.begin(); }
    +    Coordinates::Iterator       beginCoordinates() { return point_coordinates.begin(); }
    +    Coordinates::ConstIterator  beginCoordinates(countT pointIndex) const;
    +    Coordinates::Iterator       beginCoordinates(countT pointIndex);
    +    Coordinates::ConstIterator  endCoordinates() const { return point_coordinates.end(); }
    +    Coordinates::Iterator       endCoordinates() { return point_coordinates.end(); }
    +
    +#//!\name Search
    +    //! See QhullPoints for contains, count, indexOf, lastIndexOf
    +
    +#//!\name GetSet
    +    PointCoordinates    operator+(const PointCoordinates &other) const;
    +
    +#//!\name Modify
    +    //FIXUP QH11001: Add clear() and other modify operators from Coordinates.h.  Include QhullPoint::operator=()
    +    void                append(countT coordinatesCount, const coordT *c);  //! Dimension previously defined
    +    void                append(const coordT &c) { append(1, &c); } //! Dimension previously defined
    +    void                append(const QhullPoint &p);
    +    //! See convert for std::vector and QList
    +    void                append(const Coordinates &c) { append(c.count(), c.data()); }
    +    void                append(const PointCoordinates &other);
    +    void                appendComment(const std::string &s);
    +    void                appendPoints(std::istream &in);
    +    PointCoordinates &  operator+=(const PointCoordinates &other) { append(other); return *this; }
    +    PointCoordinates &  operator+=(const coordT &c) { append(c); return *this; }
    +    PointCoordinates &  operator+=(const QhullPoint &p) { append(p); return *this; }
    +    PointCoordinates &  operator<<(const PointCoordinates &other) { return *this += other; }
    +    PointCoordinates &  operator<<(const coordT &c) { return *this += c; }
    +    PointCoordinates &  operator<<(const QhullPoint &p) { return *this += p; }
    +    // reserve() is non-const
    +    void                reserveCoordinates(countT newCoordinates);
    +
    +#//!\name Helpers
    +private:
    +    int                 indexOffset(int i) const;
    +
    +};//PointCoordinates
    +
    +// No references to QhullPoint.  Prevents use of QHULL_DECLARE_SEQUENTIAL_ITERATOR(PointCoordinates, QhullPoint)
    +class PointCoordinatesIterator
    +{
    +    typedef PointCoordinates::const_iterator const_iterator;
    +
    +private:
    +    const PointCoordinates *c;
    +    const_iterator      i;
    +
    +public:
    +                        PointCoordinatesIterator(const PointCoordinates &container) : c(&container), i(c->constBegin()) {}
    +                        PointCoordinatesIterator &operator=(const PointCoordinates &container) { c = &container; i = c->constBegin(); return *this; }
    +
    +    void                toFront() { i = c->constBegin(); }
    +    void                toBack() { i = c->constEnd(); }
    +    bool                hasNext() const { return i != c->constEnd(); }
    +    const QhullPoint    next() { return *i++; }
    +    const QhullPoint    peekNext() const { return *i; }
    +    bool                hasPrevious() const { return i != c->constBegin(); }
    +    const QhullPoint    previous() { return *--i; }
    +    const QhullPoint    peekPrevious() const { const_iterator p = i; return *--p; }
    +    bool                findNext(const QhullPoint &t) { while(i != c->constEnd()){ if (*i++ == t) return true;} return false; }
    +    bool                findPrevious(const QhullPoint &t) { while(i != c->constBegin()){ if (*(--i) == t) return true;} return false;  }
    +};//CoordinatesIterator
    +
    +// FIXUP QH11002:  Add MutablePointCoordinatesIterator after adding modify operators
    +\
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &          operator<<(std::ostream &os, const orgQhull::PointCoordinates &p);
    +
    +#endif // QHPOINTCOORDINATES_H
    diff --git a/xs/src/qhull/src/libqhullcpp/Qhull.cpp b/xs/src/qhull/src/libqhullcpp/Qhull.cpp
    new file mode 100644
    index 0000000000..7124a15cdc
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/Qhull.cpp
    @@ -0,0 +1,352 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/Qhull.cpp#4 $$Change: 2078 $
    +** $DateTime: 2016/02/07 16:53:56 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! Qhull -- invoke qhull from C++
    +#//! Compile libqhull_r and Qhull together due to use of setjmp/longjmp()
    +
    +#include "libqhullcpp/Qhull.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullQh.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +
    +#include 
    +
    +using std::cerr;
    +using std::string;
    +using std::vector;
    +using std::ostream;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Global variables
    +
    +const char s_unsupported_options[]=" Fd TI ";
    +const char s_not_output_options[]= " Fd TI A C d E H P Qb QbB Qbb Qc Qf Qg Qi Qm QJ Qr QR Qs Qt Qv Qx Qz Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 Q10 Q11 R Tc TC TM TP TR Tv TV TW U v V W ";
    +
    +#//!\name Constructor, destructor, etc.
    +Qhull::
    +Qhull()
    +: qh_qh(0)
    +, origin_point()
    +, run_called(false)
    +, feasible_point()
    +{
    +    allocateQhullQh();
    +}//Qhull
    +
    +//! Invokes Qhull on rboxPoints
    +//! Same as runQhull()
    +//! For rbox commands, see http://www.qhull.org/html/rbox.htm or html/rbox.htm
    +//! For qhull commands, see http://www.qhull.org/html/qhull.htm or html/qhull.htm
    +Qhull::
    +Qhull(const RboxPoints &rboxPoints, const char *qhullCommand2)
    +: qh_qh(0)
    +, origin_point()
    +, run_called(false)
    +, feasible_point()
    +{
    +    allocateQhullQh();
    +    runQhull(rboxPoints, qhullCommand2);
    +}//Qhull rbox
    +
    +//! Invokes Qhull on a set of input points
    +//! Same as runQhull()
    +//! For qhull commands, see http://www.qhull.org/html/qhull.htm or html/qhull.htm
    +Qhull::
    +Qhull(const char *inputComment2, int pointDimension, int pointCount, const realT *pointCoordinates, const char *qhullCommand2)
    +: qh_qh(0)
    +, origin_point()
    +, run_called(false)
    +, feasible_point()
    +{
    +    allocateQhullQh();
    +    runQhull(inputComment2, pointDimension, pointCount, pointCoordinates, qhullCommand2);
    +}//Qhull points
    +
    +void Qhull::
    +allocateQhullQh()
    +{
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +    qh_qh= new QhullQh;
    +    void *p= qh_qh;
    +    void *p2= static_cast(qh_qh);
    +    char *s= static_cast(p);
    +    char *s2= static_cast(p2);
    +    if(s!=s2){
    +        throw QhullError(10074, "Qhull error: QhullQh at a different address than base type QhT (%d bytes).  Please report compiler to qhull.org", int(s2-s));
    +    }
    +}//allocateQhullQh
    +
    +Qhull::
    +~Qhull() throw()
    +{
    +    // Except for cerr, does not throw errors
    +    if(qh_qh->hasQhullMessage()){
    +        cerr<< "\nQhull output at end\n"; //FIXUP QH11005: where should error and log messages go on ~Qhull?
    +        cerr<< qh_qh->qhullMessage();
    +        qh_qh->clearQhullMessage();
    +    }
    +    delete qh_qh;
    +    qh_qh= 0;
    +}//~Qhull
    +
    +#//!\name GetSet
    +
    +void Qhull::
    +checkIfQhullInitialized()
    +{
    +    if(!initialized()){ // qh_initqhull_buffers() not called
    +        throw QhullError(10023, "Qhull error: checkIfQhullInitialized failed.  Call runQhull() first.");
    +    }
    +}//checkIfQhullInitialized
    +
    +//! Return feasiblePoint for halfspace intersection
    +//! If called before runQhull(), then it returns the value from setFeasiblePoint.  qh.feasible_string overrides this value if it is defined.
    +Coordinates Qhull::
    +feasiblePoint() const
    +{
    +    Coordinates result;
    +    if(qh_qh->feasible_point){
    +        result.append(qh_qh->hull_dim, qh_qh->feasible_point);
    +    }else{
    +        result= feasible_point;
    +    }
    +    return result;
    +}//feasiblePoint
    +
    +//! Return origin point for qh.input_dim
    +QhullPoint Qhull::
    +inputOrigin()
    +{
    +    QhullPoint result= origin();
    +    result.setDimension(qh_qh->input_dim);
    +    return result;
    +}//inputOrigin
    +
    +#//!\name GetValue
    +
    +double Qhull::
    +area(){
    +    checkIfQhullInitialized();
    +    if(!qh_qh->hasAreaVolume){
    +        QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +            qh_getarea(qh_qh, qh_qh->facet_list);
    +        }
    +        qh_qh->NOerrexit= true;
    +        qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    }
    +    return qh_qh->totarea;
    +}//area
    +
    +double Qhull::
    +volume(){
    +    checkIfQhullInitialized();
    +    if(!qh_qh->hasAreaVolume){
    +        QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +            qh_getarea(qh_qh, qh_qh->facet_list);
    +        }
    +        qh_qh->NOerrexit= true;
    +        qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    }
    +    return qh_qh->totvol;
    +}//volume
    +
    +#//!\name Foreach
    +
    +//! Define QhullVertex::neighborFacets().
    +//! Automatically called if merging facets or computing the Voronoi diagram.
    +//! Noop if called multiple times.
    +void Qhull::
    +defineVertexNeighborFacets(){
    +    checkIfQhullInitialized();
    +    if(!qh_qh->hasAreaVolume){
    +        QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +            qh_vertexneighbors(qh_qh);
    +        }
    +        qh_qh->NOerrexit= true;
    +        qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    }
    +}//defineVertexNeighborFacets
    +
    +QhullFacetList Qhull::
    +facetList() const{
    +    return QhullFacetList(beginFacet(), endFacet());
    +}//facetList
    +
    +QhullPoints Qhull::
    +points() const
    +{
    +    return QhullPoints(qh_qh, qh_qh->hull_dim, qh_qh->num_points*qh_qh->hull_dim, qh_qh->first_point);
    +}//points
    +
    +QhullPointSet Qhull::
    +otherPoints() const
    +{
    +    return QhullPointSet(qh_qh, qh_qh->other_points);
    +}//otherPoints
    +
    +//! Return vertices of the convex hull.
    +QhullVertexList Qhull::
    +vertexList() const{
    +    return QhullVertexList(beginVertex(), endVertex());
    +}//vertexList
    +
    +#//!\name Methods
    +
    +void Qhull::
    +outputQhull()
    +{
    +    checkIfQhullInitialized();
    +    QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +        qh_produce_output2(qh_qh);
    +    }
    +    qh_qh->NOerrexit= true;
    +    qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +}//outputQhull
    +
    +void Qhull::
    +outputQhull(const char *outputflags)
    +{
    +    checkIfQhullInitialized();
    +    string cmd(" "); // qh_checkflags skips first word
    +    cmd += outputflags;
    +    char *command= const_cast(cmd.c_str());
    +    QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +        qh_clear_outputflags(qh_qh);
    +        char *s = qh_qh->qhull_command + strlen(qh_qh->qhull_command) + 1; //space
    +        strncat(qh_qh->qhull_command, command, sizeof(qh_qh->qhull_command)-strlen(qh_qh->qhull_command)-1);
    +        qh_checkflags(qh_qh, command, const_cast(s_not_output_options));
    +        qh_initflags(qh_qh, s);
    +        qh_initqhull_outputflags(qh_qh);
    +        if(qh_qh->KEEPminArea < REALmax/2
    +           || (0 != qh_qh->KEEParea + qh_qh->KEEPmerge + qh_qh->GOODvertex
    +                    + qh_qh->GOODthreshold + qh_qh->GOODpoint + qh_qh->SPLITthresholds)){
    +            facetT *facet;
    +            qh_qh->ONLYgood= False;
    +            FORALLfacet_(qh_qh->facet_list) {
    +                facet->good= True;
    +            }
    +            qh_prepare_output(qh_qh);
    +        }
    +        qh_produce_output2(qh_qh);
    +        if(qh_qh->VERIFYoutput && !qh_qh->STOPpoint && !qh_qh->STOPcone){
    +            qh_check_points(qh_qh);
    +        }
    +    }
    +    qh_qh->NOerrexit= true;
    +    qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +}//outputQhull
    +
    +//! For qhull commands, see http://www.qhull.org/html/qhull.htm or html/qhull.htm
    +void Qhull::
    +runQhull(const RboxPoints &rboxPoints, const char *qhullCommand2)
    +{
    +    runQhull(rboxPoints.comment().c_str(), rboxPoints.dimension(), rboxPoints.count(), &*rboxPoints.coordinates(), qhullCommand2);
    +}//runQhull, RboxPoints
    +
    +//! pointCoordinates is a array of points, input sites ('d' or 'v'), or halfspaces with offset last ('H')
    +//! Derived from qh_new_qhull [user.c]
    +//! For rbox commands, see http://www.qhull.org/html/rbox.htm or html/rbox.htm
    +//! For qhull commands, see http://www.qhull.org/html/qhull.htm or html/qhull.htm
    +void Qhull::
    +runQhull(const char *inputComment, int pointDimension, int pointCount, const realT *pointCoordinates, const char *qhullCommand)
    +{
    +  /* gcc may issue a "might be clobbered" warning for pointDimension and pointCoordinates [-Wclobbered].
    +     These parameters are not referenced after a longjmp() and hence not clobbered.
    +     See http://stackoverflow.com/questions/7721854/what-sense-do-these-clobbered-variable-warnings-make */
    +    if(run_called){
    +        throw QhullError(10027, "Qhull error: runQhull called twice.  Only one call allowed.");
    +    }
    +    run_called= true;
    +    string s("qhull ");
    +    s += qhullCommand;
    +    char *command= const_cast(s.c_str());
    +    /************* Expansion of QH_TRY_ for debugging
    +    int QH_TRY_status;
    +    if(qh_qh->NOerrexit){
    +        qh_qh->NOerrexit= False;
    +        QH_TRY_status= setjmp(qh_qh->errexit);
    +    }else{
    +        QH_TRY_status= QH_TRY_ERROR;
    +    }
    +    if(!QH_TRY_status){
    +    *************/
    +    QH_TRY_(qh_qh){ // no object creation -- destructors are skipped on longjmp()
    +        qh_checkflags(qh_qh, command, const_cast(s_unsupported_options));
    +        qh_initflags(qh_qh, command);
    +        *qh_qh->rbox_command= '\0';
    +        strncat( qh_qh->rbox_command, inputComment, sizeof(qh_qh->rbox_command)-1);
    +        if(qh_qh->DELAUNAY){
    +            qh_qh->PROJECTdelaunay= True;   // qh_init_B() calls qh_projectinput()
    +        }
    +        pointT *newPoints= const_cast(pointCoordinates);
    +        int newDimension= pointDimension;
    +        int newIsMalloc= False;
    +        if(qh_qh->HALFspace){
    +            --newDimension;
    +            initializeFeasiblePoint(newDimension);
    +            newPoints= qh_sethalfspace_all(qh_qh, pointDimension, pointCount, newPoints, qh_qh->feasible_point);
    +            newIsMalloc= True;
    +        }
    +        qh_init_B(qh_qh, newPoints, pointCount, newDimension, newIsMalloc);
    +        qh_qhull(qh_qh);
    +        qh_check_output(qh_qh);
    +        qh_prepare_output(qh_qh);
    +        if(qh_qh->VERIFYoutput && !qh_qh->STOPpoint && !qh_qh->STOPcone){
    +            qh_check_points(qh_qh);
    +        }
    +    }
    +    qh_qh->NOerrexit= true;
    +    for(int k= qh_qh->hull_dim; k--; ){  // Do not move into QH_TRY block.  It may throw an error
    +        origin_point << 0.0;
    +    }
    +    qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +}//runQhull
    +
    +#//!\name Helpers -- be careful of allocating C++ objects due to setjmp/longjmp() error handling by qh_... routines
    +
    +//! initialize qh.feasible_point for half-space intersection
    +//! Sets from qh.feasible_string if available, otherwise from Qhull::feasible_point
    +//! called only once from runQhull(), otherwise it leaks memory (the same as qh_setFeasible)
    +void Qhull::
    +initializeFeasiblePoint(int hulldim)
    +{
    +    if(qh_qh->feasible_string){
    +        qh_setfeasible(qh_qh, hulldim);
    +    }else{
    +        if(feasible_point.isEmpty()){
    +            qh_fprintf(qh_qh, qh_qh->ferr, 6209, "qhull error: missing feasible point for halfspace intersection.  Use option 'Hn,n' or Qhull::setFeasiblePoint before runQhull()\n");
    +            qh_errexit(qh_qh, qh_ERRmem, NULL, NULL);
    +        }
    +        if(feasible_point.size()!=(size_t)hulldim){
    +            qh_fprintf(qh_qh, qh_qh->ferr, 6210, "qhull error: dimension of feasiblePoint should be %d.  It is %u", hulldim, feasible_point.size());
    +            qh_errexit(qh_qh, qh_ERRmem, NULL, NULL);
    +        }
    +        if (!(qh_qh->feasible_point= (coordT*)qh_malloc(hulldim * sizeof(coordT)))) {
    +            qh_fprintf(qh_qh, qh_qh->ferr, 6202, "qhull error: insufficient memory for feasible point\n");
    +            qh_errexit(qh_qh, qh_ERRmem, NULL, NULL);
    +        }
    +        coordT *t= qh_qh->feasible_point;
    +        // No qh_... routines after here -- longjmp() ignores destructor
    +        for(Coordinates::ConstIterator p=feasible_point.begin(); p.  It could be rewritten for another vector class such as QList
    +   #define QHULL_USES_QT
    +      Supply conversions to QT
    +      qhulltest requires QT.  It is defined in RoadTest.h
    +
    +  #define QHULL_ASSERT
    +      Defined by QhullError.h
    +      It invokes assert()
    +*/
    +
    +#//!\name Used here
    +    class QhullFacetList;
    +    class QhullPoints;
    +    class QhullQh;
    +    class RboxPoints;
    +
    +#//!\name Defined here
    +    class Qhull;
    +
    +//! Interface to Qhull from C++
    +class Qhull {
    +
    +private:
    +#//!\name Members and friends
    +    QhullQh *           qh_qh;          //! qhT for this instance
    +    Coordinates         origin_point;   //! origin for qh_qh->hull_dim.  Set by runQhull()
    +    bool                run_called;     //! True at start of runQhull.  Errors if call again.
    +    Coordinates         feasible_point;  //! feasible point for half-space intersection (alternative to qh.feasible_string for qh.feasible_point)
    +
    +public:
    +#//!\name Constructors
    +                        Qhull();      //!< call runQhull() next
    +                        Qhull(const RboxPoints &rboxPoints, const char *qhullCommand2);
    +                        Qhull(const char *inputComment2, int pointDimension, int pointCount, const realT *pointCoordinates, const char *qhullCommand2);
    +                        ~Qhull() throw();
    +private:                //! Disable copy constructor and assignment.  Qhull owns QhullQh.
    +                        Qhull(const Qhull &);
    +    Qhull &             operator=(const Qhull &);
    +
    +private:
    +    void                allocateQhullQh();
    +
    +public:
    +
    +#//!\name GetSet
    +    void                checkIfQhullInitialized();
    +    int                 dimension() const { return qh_qh->input_dim; } //!< Dimension of input and result
    +    void                disableOutputStream() { qh_qh->disableOutputStream(); }
    +    void                enableOutputStream() { qh_qh->enableOutputStream(); }
    +    countT              facetCount() const { return qh_qh->num_facets; }
    +    Coordinates         feasiblePoint() const; 
    +    int                 hullDimension() const { return qh_qh->hull_dim; } //!< Dimension of the computed hull
    +    bool                hasOutputStream() const { return qh_qh->hasOutputStream(); }
    +    bool                initialized() const { return (qh_qh->hull_dim>0); }
    +    const char *        inputComment() const { return qh_qh->rbox_command; }
    +    QhullPoint          inputOrigin();
    +                        //! non-const due to QhullPoint
    +    QhullPoint          origin() { QHULL_ASSERT(initialized()); return QhullPoint(qh_qh, origin_point.data()); }
    +    QhullQh *           qh() const { return qh_qh; };
    +    const char *        qhullCommand() const { return qh_qh->qhull_command; }
    +    const char *        rboxCommand() const { return qh_qh->rbox_command; }
    +    int                 rotateRandom() const { return qh_qh->ROTATErandom; } //!< Return QRn for repeating QR0 runs
    +    void                setFeasiblePoint(const Coordinates &c) { feasible_point= c; } //!< Sets qh.feasible_point via initializeFeasiblePoint
    +    countT              vertexCount() const { return qh_qh->num_vertices; }
    +
    +#//!\name Delegated to QhullQh
    +    double              angleEpsilon() const { return qh_qh->angleEpsilon(); } //!< Epsilon for hyperplane angle equality
    +    void                appendQhullMessage(const std::string &s) { qh_qh->appendQhullMessage(s); }
    +    void                clearQhullMessage() { qh_qh->clearQhullMessage(); }
    +    double              distanceEpsilon() const { return qh_qh->distanceEpsilon(); } //!< Epsilon for distance to hyperplane
    +    double              factorEpsilon() const { return qh_qh->factorEpsilon(); }  //!< Factor for angleEpsilon and distanceEpsilon
    +    std::string         qhullMessage() const { return qh_qh->qhullMessage(); }
    +    bool                hasQhullMessage() const { return qh_qh->hasQhullMessage(); }
    +    int                 qhullStatus() const { return qh_qh->qhullStatus(); }
    +    void                setErrorStream(std::ostream *os) { qh_qh->setErrorStream(os); }
    +    void                setFactorEpsilon(double a) { qh_qh->setFactorEpsilon(a); }
    +    void                setOutputStream(std::ostream *os) { qh_qh->setOutputStream(os); }
    +
    +#//!\name ForEach
    +    QhullFacet          beginFacet() const { return QhullFacet(qh_qh, qh_qh->facet_list); }
    +    QhullVertex         beginVertex() const { return QhullVertex(qh_qh, qh_qh->vertex_list); }
    +    void                defineVertexNeighborFacets(); //!< Automatically called if merging facets or Voronoi diagram
    +    QhullFacet          endFacet() const { return QhullFacet(qh_qh, qh_qh->facet_tail); }
    +    QhullVertex         endVertex() const { return QhullVertex(qh_qh, qh_qh->vertex_tail); }
    +    QhullFacetList      facetList() const;
    +    QhullFacet          firstFacet() const { return beginFacet(); }
    +    QhullVertex         firstVertex() const { return beginVertex(); }
    +    QhullPoints         points() const;
    +    QhullPointSet       otherPoints() const;
    +                        //! Same as points().coordinates()
    +    coordT *            pointCoordinateBegin() const { return qh_qh->first_point; }
    +    coordT *            pointCoordinateEnd() const { return qh_qh->first_point + qh_qh->num_points*qh_qh->hull_dim; }
    +    QhullVertexList     vertexList() const;
    +
    +#//!\name Methods
    +    double              area();
    +    void                outputQhull();
    +    void                outputQhull(const char * outputflags);
    +    void                runQhull(const RboxPoints &rboxPoints, const char *qhullCommand2);
    +    void                runQhull(const char *inputComment2, int pointDimension, int pointCount, const realT *pointCoordinates, const char *qhullCommand2);
    +    double              volume();
    +
    +#//!\name Helpers
    +private:
    +    void                initializeFeasiblePoint(int hulldim);
    +};//Qhull
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLCPP_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullError.h b/xs/src/qhull/src/libqhullcpp/QhullError.h
    new file mode 100644
    index 0000000000..08d50aa0ff
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullError.h
    @@ -0,0 +1,62 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullError.h#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLERROR_H
    +#define QHULLERROR_H
    +
    +#include "libqhullcpp/RoadError.h"
    +// No dependencies on libqhull
    +
    +#ifndef QHULL_ASSERT
    +#define QHULL_ASSERT assert
    +#include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! QhullError -- std::exception class for Qhull
    +    class QhullError;
    +
    +class QhullError : public RoadError {
    +
    +public:
    +#//!\name Constants
    +    enum {
    +        QHULLfirstError= 10000, //MSG_QHULL_ERROR in Qhull's user.h
    +        QHULLlastError= 10078,
    +        NOthrow= 1 //! For flag to indexOf()
    +    };
    +
    +#//!\name Constructors
    +    // default constructors
    +    QhullError() : RoadError() {};
    +    QhullError(const QhullError &other) : RoadError(other) {}
    +    QhullError(int code, const std::string &message) : RoadError(code, message) {};
    +    QhullError(int code, const char *fmt) : RoadError(code, fmt) {};
    +    QhullError(int code, const char *fmt, int d) : RoadError(code, fmt, d) {};
    +    QhullError(int code, const char *fmt, int d, int d2) : RoadError(code, fmt, d, d2) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f) : RoadError(code, fmt, d, d2, f) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, const char *s) : RoadError(code, fmt, d, d2, f, s) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, const void *x) : RoadError(code, fmt, d, d2, f, x) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, int i) : RoadError(code, fmt, d, d2, f, i) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, long long i) : RoadError(code, fmt, d, d2, f, i) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, double e) : RoadError(code, fmt, d, d2, f, e) {};
    +    QhullError &operator=(const QhullError &other) { this->RoadError::operator=(other); return *this; }
    +    ~QhullError() throw() {}
    +
    +};//class QhullError
    +
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +inline std::ostream &operator<<(std::ostream &os, const orgQhull::QhullError &e) { return os << e.what(); }
    +
    +#endif // QHULLERROR_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacet.cpp b/xs/src/qhull/src/libqhullcpp/QhullFacet.cpp
    new file mode 100644
    index 0000000000..40d3828a4c
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacet.cpp
    @@ -0,0 +1,519 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacet.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullFacet -- Qhull's facet structure, facetT, as a C++ class
    +
    +#include "libqhullcpp/QhullFacet.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullSet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullPointSet.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullVertex.h"
    +
    +#include 
    +
    +using std::endl;
    +using std::ostream;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class objects
    +facetT QhullFacet::
    +s_empty_facet= {0,0,0,0,{0},
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0};
    +
    +#//!\name Constructors
    +
    +QhullFacet::
    +QhullFacet(const Qhull &q) 
    +: qh_facet(&s_empty_facet)
    +, qh_qh(q.qh())
    +{
    +}
    +
    +QhullFacet::
    +QhullFacet(const Qhull &q, facetT *f) 
    +: qh_facet(f ? f : &s_empty_facet)
    +, qh_qh(q.qh())
    +{
    +}
    +
    +#//!\name GetSet
    +
    +//! Return voronoi center or facet centrum.  Derived from qh_printcenter [io_r.c]
    +//! if printFormat=qh_PRINTtriangles and qh.DELAUNAY, returns centrum of a Delaunay facet
    +//! Sets center if needed
    +//! Code duplicated for PrintCenter and getCenter
    +//! Returns QhullPoint() if none or qh_INFINITE
    +QhullPoint QhullFacet::
    +getCenter(qh_PRINT printFormat)
    +{
    +    if(!qh_qh){
    +        // returns QhullPoint()
    +    }else if(qh_qh->CENTERtype==qh_ASvoronoi){
    +        if(!qh_facet->normal || !qh_facet->upperdelaunay || !qh_qh->ATinfinity){
    +            if(!qh_facet->center){
    +                QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +                    qh_facet->center= qh_facetcenter(qh_qh, qh_facet->vertices);
    +                }
    +                qh_qh->NOerrexit= true;
    +                qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +            }
    +            return QhullPoint(qh_qh, qh_qh->hull_dim-1, qh_facet->center);
    +        }
    +    }else if(qh_qh->CENTERtype==qh_AScentrum){
    +        volatile int numCoords= qh_qh->hull_dim;
    +        if(printFormat==qh_PRINTtriangles && qh_qh->DELAUNAY){
    +            numCoords--;
    +        }
    +        if(!qh_facet->center){
    +            QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +                qh_facet->center= qh_getcentrum(qh_qh, getFacetT());
    +            }
    +            qh_qh->NOerrexit= true;
    +            qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +        }
    +        return QhullPoint(qh_qh, numCoords, qh_facet->center);
    +    }
    +    return QhullPoint();
    + }//getCenter
    +
    +//! Return innerplane clearly below the vertices
    +//! from io_r.c[qh_PRINTinner]
    +QhullHyperplane QhullFacet::
    +innerplane() const{
    +    QhullHyperplane h;
    +    if(qh_qh){
    +        realT inner;
    +        // Does not error, TRY_QHULL_ not needed
    +        qh_outerinner(qh_qh, const_cast(getFacetT()), NULL, &inner);
    +        h= hyperplane();
    +        h.setOffset(h.offset()-inner); //inner is negative
    +    }
    +    return h;
    +}//innerplane
    +
    +//! Return outerplane clearly above all points
    +//! from io_r.c[qh_PRINTouter]
    +QhullHyperplane QhullFacet::
    +outerplane() const{
    +    QhullHyperplane h;
    +    if(qh_qh){
    +        realT outer;
    +        // Does not error, TRY_QHULL_ not needed
    +        qh_outerinner(qh_qh, const_cast(getFacetT()), &outer, NULL);
    +        h= hyperplane();
    +        h.setOffset(h.offset()-outer); //outer is positive
    +    }
    +    return h;
    +}//outerplane
    +
    +//! Set by qh_triangulate for option 'Qt'.
    +//! Errors if tricoplanar and facetArea() or qh_getarea() called first.
    +QhullFacet QhullFacet::
    +tricoplanarOwner() const
    +{
    +    if(qh_facet->tricoplanar){
    +        if(qh_facet->isarea){
    +            throw QhullError(10018, "Qhull error: facetArea() or qh_getarea() previously called.  triCoplanarOwner() is not available.");
    +        }
    +        return QhullFacet(qh_qh, qh_facet->f.triowner);
    +    }
    +    return QhullFacet(qh_qh); 
    +}//tricoplanarOwner
    +
    +QhullPoint QhullFacet::
    +voronoiVertex()
    +{
    +    if(qh_qh && qh_qh->CENTERtype!=qh_ASvoronoi){
    +          throw QhullError(10052, "Error: QhullFacet.voronoiVertex() requires option 'v' (qh_ASvoronoi)");
    +    }
    +    return getCenter();
    +}//voronoiVertex
    +
    +#//!\name Value
    +
    +//! Disables tricoplanarOwner()
    +double QhullFacet::
    +facetArea()
    +{
    +    if(qh_qh && !qh_facet->isarea){
    +        QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +            qh_facet->f.area= qh_facetarea(qh_qh, qh_facet);
    +            qh_facet->isarea= True;
    +        }
    +        qh_qh->NOerrexit= true;
    +        qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    }
    +    return qh_facet->f.area;
    +}//facetArea
    +
    +#//!\name Foreach
    +
    +QhullPointSet QhullFacet::
    +coplanarPoints() const
    +{
    +    return QhullPointSet(qh_qh, qh_facet->coplanarset);
    +}//coplanarPoints
    +
    +QhullFacetSet QhullFacet::
    +neighborFacets() const
    +{
    +    return QhullFacetSet(qh_qh, qh_facet->neighbors);
    +}//neighborFacets
    +
    +QhullPointSet QhullFacet::
    +outsidePoints() const
    +{
    +    return QhullPointSet(qh_qh, qh_facet->outsideset);
    +}//outsidePoints
    +
    +QhullRidgeSet QhullFacet::
    +ridges() const
    +{
    +    return QhullRidgeSet(qh_qh, qh_facet->ridges);
    +}//ridges
    +
    +QhullVertexSet QhullFacet::
    +vertices() const
    +{
    +    return QhullVertexSet(qh_qh, qh_facet->vertices);
    +}//vertices
    +
    +}//namespace orgQhull
    +
    +#//!\name operator<<
    +
    +using std::ostream;
    +
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetSet;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullPointSet;
    +using orgQhull::QhullRidge;
    +using orgQhull::QhullRidgeSet;
    +using orgQhull::QhullSetBase;
    +using orgQhull::QhullVertexSet;
    +
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintFacet &pr)
    +{
    +    os << pr.message;
    +    QhullFacet f= *pr.facet;
    +    if(f.getFacetT()==0){ // Special values from set iterator
    +        os << " NULLfacet" << endl;
    +        return os;
    +    }
    +    if(f.getFacetT()==qh_MERGEridge){
    +        os << " MERGEridge" << endl;
    +        return os;
    +    }
    +    if(f.getFacetT()==qh_DUPLICATEridge){
    +        os << " DUPLICATEridge" << endl;
    +        return os;
    +    }
    +    os << f.printHeader();
    +    if(!f.ridges().isEmpty()){
    +        os << f.printRidges();
    +    }
    +    return os;
    +}//operator<< PrintFacet
    +
    +//! Print Voronoi center or facet centrum to stream.  Same as qh_printcenter [_r.]
    +//! Code duplicated for PrintCenter and getCenter
    +//! Sets center if needed
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintCenter &pr)
    +{
    +    facetT *f= pr.facet->getFacetT();
    +    if(pr.facet->qh()->CENTERtype!=qh_ASvoronoi && pr.facet->qh()->CENTERtype!=qh_AScentrum){
    +        return os;
    +    }
    +    if (pr.message){
    +        os << pr.message;
    +    }
    +    int numCoords;
    +    if(pr.facet->qh()->CENTERtype==qh_ASvoronoi){
    +        numCoords= pr.facet->qh()->hull_dim-1;
    +        if(!f->normal || !f->upperdelaunay || !pr.facet->qh()->ATinfinity){
    +            if(!f->center){
    +                f->center= qh_facetcenter(pr.facet->qh(), f->vertices);
    +            }
    +            for(int k=0; kcenter[k] << " "; // FIXUP QH11010 qh_REAL_1
    +            }
    +        }else{
    +            for(int k=0; kqh()->hull_dim;
    +        if(pr.print_format==qh_PRINTtriangles && pr.facet->qh()->DELAUNAY){
    +            numCoords--;
    +        }
    +        if(!f->center){
    +            f->center= qh_getcentrum(pr.facet->qh(), f);
    +        }
    +        for(int k=0; kcenter[k] << " "; // FIXUP QH11010 qh_REAL_1
    +        }
    +    }
    +    if(pr.print_format==qh_PRINTgeom && numCoords==2){
    +        os << " 0";
    +    }
    +    os << endl;
    +    return os;
    +}//operator<< PrintCenter
    +
    +//! Print flags for facet to stream.  Space prefix.  From qh_printfacetheader [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintFlags &p)
    +{
    +    const facetT *f= p.facet->getFacetT();
    +    if(p.message){
    +        os << p.message;
    +    }
    +
    +    os << (p.facet->isTopOrient() ? " top" : " bottom");
    +    if(p.facet->isSimplicial()){
    +        os << " simplicial";
    +    }
    +    if(p.facet->isTriCoplanar()){
    +        os << " tricoplanar";
    +    }
    +    if(p.facet->isUpperDelaunay()){
    +        os << " upperDelaunay";
    +    }
    +    if(f->visible){
    +        os << " visible";
    +    }
    +    if(f->newfacet){
    +        os << " new";
    +    }
    +    if(f->tested){
    +        os << " tested";
    +    }
    +    if(!f->good){
    +        os << " notG";
    +    }
    +    if(f->seen){
    +        os << " seen";
    +    }
    +    if(f->coplanar){
    +        os << " coplanar";
    +    }
    +    if(f->mergehorizon){
    +        os << " mergehorizon";
    +    }
    +    if(f->keepcentrum){
    +        os << " keepcentrum";
    +    }
    +    if(f->dupridge){
    +        os << " dupridge";
    +    }
    +    if(f->mergeridge && !f->mergeridge2){
    +        os << " mergeridge1";
    +    }
    +    if(f->mergeridge2){
    +        os << " mergeridge2";
    +    }
    +    if(f->newmerge){
    +        os << " newmerge";
    +    }
    +    if(f->flipped){
    +        os << " flipped";
    +    }
    +    if(f->notfurthest){
    +        os << " notfurthest";
    +    }
    +    if(f->degenerate){
    +        os << " degenerate";
    +    }
    +    if(f->redundant){
    +        os << " redundant";
    +    }
    +    os << endl;
    +    return os;
    +}//operator<< PrintFlags
    +
    +//! Print header for facet to stream. Space prefix.  From qh_printfacetheader [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintHeader &pr)
    +{
    +    QhullFacet facet= *pr.facet;
    +    facetT *f= facet.getFacetT();
    +    os << "- f" << facet.id() << endl;
    +    os << facet.printFlags("    - flags:");
    +    if(f->isarea){
    +        os << "    - area: " << f->f.area << endl; //FIXUP QH11010 2.2g
    +    }else if(pr.facet->qh()->NEWfacets && f->visible && f->f.replace){
    +        os << "    - replacement: f" << f->f.replace->id << endl;
    +    }else if(f->newfacet){
    +        if(f->f.samecycle && f->f.samecycle != f){
    +            os << "    - shares same visible/horizon as f" << f->f.samecycle->id << endl;
    +        }
    +    }else if(f->tricoplanar /* !isarea */){
    +        if(f->f.triowner){
    +            os << "    - owner of normal & centrum is facet f" << f->f.triowner->id << endl;
    +        }
    +    }else if(f->f.newcycle){
    +        os << "    - was horizon to f" << f->f.newcycle->id << endl;
    +    }
    +    if(f->nummerge){
    +        os << "    - merges: " << f->nummerge << endl;
    +    }
    +    os << facet.hyperplane().print("    - normal: ", "\n    - offset: "); // FIXUP QH11010 %10.7g
    +    if(pr.facet->qh()->CENTERtype==qh_ASvoronoi || f->center){
    +        os << facet.printCenter(qh_PRINTfacets, "    - center: ");
    +    }
    +#if qh_MAXoutside
    +    if(f->maxoutside > pr.facet->qh()->DISTround){
    +        os << "    - maxoutside: " << f->maxoutside << endl; //FIXUP QH11010 %10.7g
    +    }
    +#endif
    +    QhullPointSet ps= facet.outsidePoints();
    +    if(!ps.isEmpty()){
    +        QhullPoint furthest= ps.last();
    +        if (ps.size() < 6) {
    +            os << "    - outside set(furthest p" << furthest.id() << "):" << endl;
    +            for(QhullPointSet::iterator i=ps.begin(); i!=ps.end(); ++i){
    +                QhullPoint p= *i;
    +                os << p.print("     ");
    +            }
    +        }else if(ps.size()<21){
    +            os << ps.print("    - outside set:");
    +        }else{
    +            os << "    - outside set:  " << ps.size() << " points.";
    +            os << furthest.print("  Furthest");
    +        }
    +#if !qh_COMPUTEfurthest
    +        os << "    - furthest distance= " << f->furthestdist << endl; //FIXUP QH11010 %2.2g
    +#endif
    +    }
    +    QhullPointSet cs= facet.coplanarPoints();
    +    if(!cs.isEmpty()){
    +        QhullPoint furthest= cs.last();
    +        if (cs.size() < 6) {
    +            os << "    - coplanar set(furthest p" << furthest.id() << "):" << endl;
    +            for(QhullPointSet::iterator i=cs.begin(); i!=cs.end(); ++i){
    +                QhullPoint p= *i;
    +                os << p.print("     ");
    +            }
    +        }else if(cs.size()<21){
    +            os << cs.print("    - coplanar set:");
    +        }else{
    +            os << "    - coplanar set:  " << cs.size() << " points.";
    +            os << furthest.print("  Furthest");
    +        }
    +        // FIXUP QH11027 Can/should zinc_(Zdistio) be called from C++ interface
    +        double d= facet.distance(furthest);
    +        os << "      furthest distance= " << d << endl; //FIXUP QH11010 %2.2g
    +    }
    +    QhullVertexSet vs= facet.vertices();
    +    if(!vs.isEmpty()){
    +        os << vs.print("    - vertices:");
    +    }
    +    QhullFacetSet fs= facet.neighborFacets();
    +    fs.selectAll();
    +    if(!fs.isEmpty()){
    +        os << fs.printIdentifiers("    - neighboring facets:");
    +    }
    +    return os;
    +}//operator<< PrintHeader
    +
    +
    +//! Print ridges of facet to stream.  Same as qh_printfacetridges [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintRidges &pr)
    +{
    +    const QhullFacet facet= *pr.facet;
    +    facetT *f= facet.getFacetT();
    +    QhullRidgeSet rs= facet.ridges();
    +    if(!rs.isEmpty()){
    +        if(f->visible && pr.facet->qh()->NEWfacets){
    +            os << "    - ridges(ids may be garbage):";
    +            for(QhullRidgeSet::iterator i=rs.begin(); i!=rs.end(); ++i){
    +                QhullRidge r= *i;
    +                os << " r" << r.id();
    +            }
    +            os << endl;
    +        }else{
    +            os << "    - ridges:" << endl;
    +        }
    +
    +        // Keep track of printed ridges
    +        for(QhullRidgeSet::iterator i=rs.begin(); i!=rs.end(); ++i){
    +            QhullRidge r= *i;
    +            r.getRidgeT()->seen= false;
    +        }
    +        int ridgeCount= 0;
    +        if(facet.dimension()==3){
    +            for(QhullRidge r= rs.first(); !r.getRidgeT()->seen; r= r.nextRidge3d(facet)){
    +                r.getRidgeT()->seen= true;
    +                os << r.print("");
    +                ++ridgeCount;
    +                if(!r.hasNextRidge3d(facet)){
    +                    break;
    +                }
    +            }
    +        }else {
    +            QhullFacetSet ns(facet.neighborFacets());
    +            for(QhullFacetSet::iterator i=ns.begin(); i!=ns.end(); ++i){
    +                QhullFacet neighbor= *i;
    +                QhullRidgeSet nrs(neighbor.ridges());
    +                for(QhullRidgeSet::iterator j=nrs.begin(); j!=nrs.end(); ++j){
    +                    QhullRidge r= *j;
    +                    if(r.otherFacet(neighbor)==facet){
    +                        r.getRidgeT()->seen= true;
    +                        os << r.print("");
    +                        ridgeCount++;
    +                    }
    +                }
    +            }
    +        }
    +        if(ridgeCount!=rs.count()){
    +            os << "     - all ridges:";
    +            for(QhullRidgeSet::iterator i=rs.begin(); i!=rs.end(); ++i){
    +                QhullRidge r= *i;
    +                os << " r" << r.id();
    +            }
    +            os << endl;
    +        }
    +        for(QhullRidgeSet::iterator i=rs.begin(); i!=rs.end(); ++i){
    +            QhullRidge r= *i;
    +            if(!r.getRidgeT()->seen){
    +                os << r.print("");
    +            }
    +        }
    +    }
    +    return os;
    +}//operator<< PrintRidges
    +
    +// "No conversion" error if defined inline
    +ostream &
    +operator<<(ostream &os, QhullFacet &f)
    +{
    +    os << f.print("");
    +    return os;
    +}//<< QhullFacet
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacet.h b/xs/src/qhull/src/libqhullcpp/QhullFacet.h
    new file mode 100644
    index 0000000000..ae4f008fd2
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacet.h
    @@ -0,0 +1,151 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacet.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLFACET_H
    +#define QHULLFACET_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullHyperplane.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullSet.h"
    +#include "libqhullcpp/QhullPointSet.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Coordinates;
    +    class Qhull;
    +    class QhullFacetSet;
    +    class QhullRidge;
    +    class QhullVertex;
    +    class QhullVertexSet;
    +
    +#//!\name Defined here
    +    class QhullFacet;
    +    typedef QhullSet  QhullRidgeSet;
    +
    +//! A QhullFacet is the C++ equivalent to Qhull's facetT*
    +class QhullFacet {
    +
    +#//!\name Defined here
    +public:
    +    typedef facetT *   base_type;  // for QhullVertexSet
    +
    +private:
    +#//!\name Fields -- no additions (QhullFacetSet of facetT*)
    +    facetT *            qh_facet;  //!< Corresponding facetT, may be 0 for corner cases (e.g., *facetSet.end()==0) and tricoplanarOwner()
    +    QhullQh *           qh_qh;     //!< QhullQh/qhT for facetT, may be 0
    +
    +#//!\name Class objects
    +    static facetT       s_empty_facet; // needed for shallow copy
    +
    +public:
    +#//!\name Constructors
    +                        QhullFacet() : qh_facet(&s_empty_facet), qh_qh(0) {}
    +    explicit            QhullFacet(const Qhull &q);
    +                        QhullFacet(const Qhull &q, facetT *f);
    +    explicit            QhullFacet(QhullQh *qqh) : qh_facet(&s_empty_facet), qh_qh(qqh) {}
    +                        QhullFacet(QhullQh *qqh, facetT *f) : qh_facet(f ? f : &s_empty_facet), qh_qh(qqh) {}
    +                        // Creates an alias.  Does not copy QhullFacet.  Needed for return by value and parameter passing
    +                        QhullFacet(const QhullFacet &other) : qh_facet(other.qh_facet ? other.qh_facet : &s_empty_facet), qh_qh(other.qh_qh) {}
    +                        // Creates an alias.  Does not copy QhullFacet.  Needed for vector
    +    QhullFacet &        operator=(const QhullFacet &other) { qh_facet= other.qh_facet ? other.qh_facet : &s_empty_facet; qh_qh= other.qh_qh; return *this; }
    +                        ~QhullFacet() {}
    +
    +
    +#//!\name GetSet
    +    int                 dimension() const { return (qh_qh ? qh_qh->hull_dim : 0); }
    +    QhullPoint          getCenter() { return getCenter(qh_PRINTpoints); }
    +    QhullPoint          getCenter(qh_PRINT printFormat);
    +    facetT *            getBaseT() const { return getFacetT(); } //!< For QhullSet
    +                        // Do not define facetT().  It conflicts with return type facetT*
    +    facetT *            getFacetT() const { return qh_facet; }
    +    QhullHyperplane     hyperplane() const { return QhullHyperplane(qh_qh, dimension(), qh_facet->normal, qh_facet->offset); }
    +    countT              id() const { return (qh_facet ? qh_facet->id : (int)qh_IDunknown); }
    +    QhullHyperplane     innerplane() const;
    +    bool                isValid() const { return qh_qh && qh_facet && qh_facet != &s_empty_facet; }
    +    bool                isGood() const { return qh_facet && qh_facet->good; }
    +    bool                isSimplicial() const { return qh_facet && qh_facet->simplicial; }
    +    bool                isTopOrient() const { return qh_facet && qh_facet->toporient; }
    +    bool                isTriCoplanar() const { return qh_facet && qh_facet->tricoplanar; }
    +    bool                isUpperDelaunay() const { return qh_facet && qh_facet->upperdelaunay; }
    +    QhullFacet          next() const { return QhullFacet(qh_qh, qh_facet->next); }
    +    bool                operator==(const QhullFacet &other) const { return qh_facet==other.qh_facet; }
    +    bool                operator!=(const QhullFacet &other) const { return !operator==(other); }
    +    QhullHyperplane     outerplane() const;
    +    QhullFacet          previous() const { return QhullFacet(qh_qh, qh_facet->previous); }
    +    QhullQh *           qh() const { return qh_qh; }
    +    QhullFacet          tricoplanarOwner() const;
    +    QhullPoint          voronoiVertex();
    +
    +#//!\name value
    +    //! Undefined if c.size() != dimension()
    +    double              distance(const Coordinates &c) const { return distance(c.data()); }
    +    double              distance(const pointT *p) const { return distance(QhullPoint(qh_qh, const_cast(p))); }
    +    double              distance(const QhullPoint &p) const { return hyperplane().distance(p); }
    +    double              facetArea();
    +
    +#//!\name foreach
    +    // Can not inline.  Otherwise circular reference
    +    QhullPointSet       coplanarPoints() const;
    +    QhullFacetSet       neighborFacets() const;
    +    QhullPointSet       outsidePoints() const;
    +    QhullRidgeSet       ridges() const;
    +    QhullVertexSet      vertices() const;
    +
    +#//!\name IO
    +    struct PrintCenter{
    +        QhullFacet *    facet;  // non-const due to facet.center()
    +        const char *    message;
    +        qh_PRINT        print_format;
    +                        PrintCenter(QhullFacet &f, qh_PRINT printFormat, const char * s) : facet(&f), message(s), print_format(printFormat){}
    +    };//PrintCenter
    +    PrintCenter         printCenter(qh_PRINT printFormat, const char *message) { return PrintCenter(*this, printFormat, message); }
    +
    +    struct PrintFacet{
    +        QhullFacet *    facet;  // non-const due to f->center()
    +        const char *    message;
    +        explicit        PrintFacet(QhullFacet &f, const char * s) : facet(&f), message(s) {}
    +    };//PrintFacet
    +    PrintFacet          print(const char *message) { return PrintFacet(*this, message); }
    +
    +    struct PrintFlags{
    +        const QhullFacet *facet;
    +        const char *    message;
    +                        PrintFlags(const QhullFacet &f, const char *s) : facet(&f), message(s) {}
    +    };//PrintFlags
    +    PrintFlags          printFlags(const char *message) const { return PrintFlags(*this, message); }
    +
    +    struct PrintHeader{
    +        QhullFacet *    facet;  // non-const due to f->center()
    +                        PrintHeader(QhullFacet &f) : facet(&f) {}
    +    };//PrintHeader
    +    PrintHeader         printHeader() { return PrintHeader(*this); }
    +
    +    struct PrintRidges{
    +        const QhullFacet *facet;
    +                        PrintRidges(QhullFacet &f) : facet(&f) {}
    +    };//PrintRidges
    +    PrintRidges         printRidges() { return PrintRidges(*this); }
    +
    +};//class QhullFacet
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintFacet &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintCenter &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintFlags &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintHeader &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintRidges &pr);
    +std::ostream &operator<<(std::ostream &os, orgQhull::QhullFacet &f); // non-const due to qh_getcenter()
    +
    +#endif // QHULLFACET_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacetList.cpp b/xs/src/qhull/src/libqhullcpp/QhullFacetList.cpp
    new file mode 100644
    index 0000000000..9e6ddfe9ec
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacetList.cpp
    @@ -0,0 +1,174 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacetList.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullFacetList -- Qhull's linked facets, as a C++ class
    +
    +#include "libqhullcpp/QhullFacetList.h"
    +
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullVertex.h"
    +
    +using std::string;
    +using std::vector;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructors
    +
    +QhullFacetList::
    +QhullFacetList(const Qhull &q, facetT *b, facetT *e ) 
    +: QhullLinkedList(QhullFacet(q, b), QhullFacet(q, e))
    +, select_all(false)
    +{
    +}
    +
    +#//!\name Conversions
    +
    +// See qt_qhull.cpp for QList conversions
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullFacetList::
    +toStdVector() const
    +{
    +    QhullLinkedListIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        QhullFacet f= i.next();
    +        if(isSelectAll() || f.isGood()){
    +            vs.push_back(f);
    +        }
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#ifndef QHULL_NO_STL
    +//! Same as PrintVertices
    +std::vector QhullFacetList::
    +vertices_toStdVector() const
    +{
    +    std::vector vs;
    +    QhullVertexSet qvs(qh(), first().getFacetT(), 0, isSelectAll());
    +
    +    for(QhullVertexSet::iterator i=qvs.begin(); i!=qvs.end(); ++i){
    +        vs.push_back(*i);
    +    }
    +    return vs;
    +}//vertices_toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +bool QhullFacetList::
    +contains(const QhullFacet &facet) const
    +{
    +    if(isSelectAll()){
    +        return QhullLinkedList::contains(facet);
    +    }
    +    for(QhullFacetList::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f==facet && f.isGood()){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//contains
    +
    +int QhullFacetList::
    +count() const
    +{
    +    if(isSelectAll()){
    +        return QhullLinkedList::count();
    +    }
    +    int counter= 0;
    +    for(QhullFacetList::const_iterator i=begin(); i != end(); ++i){
    +        if((*i).isGood()){
    +            counter++;
    +        }
    +    }
    +    return counter;
    +}//count
    +
    +int QhullFacetList::
    +count(const QhullFacet &facet) const
    +{
    +    if(isSelectAll()){
    +        return QhullLinkedList::count(facet);
    +    }
    +    int counter= 0;
    +    for(QhullFacetList::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f==facet && f.isGood()){
    +            counter++;
    +        }
    +    }
    +    return counter;
    +}//count
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetList;
    +using orgQhull::QhullVertex;
    +using orgQhull::QhullVertexSet;
    +
    +ostream &
    +operator<<(ostream &os, const QhullFacetList::PrintFacetList &pr)
    +{
    +    os << pr.print_message;
    +    QhullFacetList fs= *pr.facet_list;
    +    os << "Vertices for " << fs.count() << " facets" << endl;
    +    os << fs.printVertices();
    +    os << fs.printFacets();
    +    return os;
    +}//operator<<
    +
    +//! Print facet list to stream.  From qh_printafacet [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullFacetList::PrintFacets &pr)
    +{
    +    for(QhullFacetList::const_iterator i= pr.facet_list->begin(); i != pr.facet_list->end(); ++i){
    +        QhullFacet f= *i;
    +        if(pr.facet_list->isSelectAll() || f.isGood()){
    +            os << f.print("");
    +        }
    +    }
    +    return os;
    +}//printFacets
    +
    +//! Print vertices of good faces in facet list to stream.  From qh_printvertexlist [io_r.c]
    +//! Same as vertices_toStdVector
    +ostream &
    +operator<<(ostream &os, const QhullFacetList::PrintVertices &pr)
    +{
    +    QhullVertexSet vs(pr.facet_list->qh(), pr.facet_list->first().getFacetT(), NULL, pr.facet_list->isSelectAll());
    +    for(QhullVertexSet::iterator i=vs.begin(); i!=vs.end(); ++i){
    +        QhullVertex v= *i;
    +        os << v.print("");
    +    }
    +    return os;
    +}//printVertices
    +
    +std::ostream &
    +operator<<(ostream &os, const QhullFacetList &fs)
    +{
    +    os << fs.printFacets();
    +    return os;
    +}//QhullFacetList
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacetList.h b/xs/src/qhull/src/libqhullcpp/QhullFacetList.h
    new file mode 100644
    index 0000000000..e61e568403
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacetList.h
    @@ -0,0 +1,106 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacetList.h#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLFACETLIST_H
    +#define QHULLFACETLIST_H
    +
    +#include "libqhullcpp/QhullLinkedList.h"
    +#include "libqhullcpp/QhullFacet.h"
    +
    +#include 
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +    class QhullFacet;
    +    class QhullQh;
    +
    +#//!\name Defined here
    +    //! QhullFacetList -- List of QhullFacet/facetT, as a C++ class.  
    +    //!\see QhullFacetSet.h
    +    class QhullFacetList;
    +    //! QhullFacetListIterator -- if(f.isGood()){ ... }
    +    typedef QhullLinkedListIterator QhullFacetListIterator;
    +
    +class QhullFacetList : public QhullLinkedList {
    +
    +#//!\name  Fields
    +private:
    +    bool                select_all;   //! True if include bad facets.  Default is false.
    +
    +#//!\name Constructors
    +public:
    +                        QhullFacetList(const Qhull &q, facetT *b, facetT *e);
    +                        QhullFacetList(QhullQh *qqh, facetT *b, facetT *e);
    +                        QhullFacetList(QhullFacet b, QhullFacet e) : QhullLinkedList(b, e), select_all(false) {}
    +                        //Copy constructor copies pointer but not contents.  Needed for return by value and parameter passing.
    +                        QhullFacetList(const QhullFacetList &other) : QhullLinkedList(*other.begin(), *other.end()), select_all(other.select_all) {}
    +    QhullFacetList &    operator=(const QhullFacetList &other) { QhullLinkedList::operator =(other); select_all= other.select_all; return *this; }
    +                        ~QhullFacetList() {}
    +
    +private:                //!Disable default constructor.  See QhullLinkedList
    +                    QhullFacetList();
    +public:
    +
    +#//!\name Conversion
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +    std::vector vertices_toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList   toQList() const;
    +    QList  vertices_toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +                        //! Filtered by facet.isGood().  May be 0 when !isEmpty().
    +    countT              count() const;
    +    bool                contains(const QhullFacet &f) const;
    +    countT              count(const QhullFacet &f) const;
    +    bool                isSelectAll() const { return select_all; }
    +    QhullQh *           qh() const { return first().qh(); }
    +    void                selectAll() { select_all= true; }
    +    void                selectGood() { select_all= false; }
    +                        //!< operator==() does not depend on isGood()
    +
    +#//!\name IO
    +    struct PrintFacetList{
    +        const QhullFacetList *facet_list;
    +        const char *    print_message;   //!< non-null message
    +                        PrintFacetList(const QhullFacetList &fl, const char *message) : facet_list(&fl), print_message(message) {}
    +    };//PrintFacetList
    +    PrintFacetList      print(const char *message) const  { return PrintFacetList(*this, message); }
    +
    +    struct PrintFacets{
    +        const QhullFacetList *facet_list;
    +                        PrintFacets(const QhullFacetList &fl) : facet_list(&fl) {}
    +    };//PrintFacets
    +    PrintFacets         printFacets() const { return PrintFacets(*this); }
    +
    +    struct PrintVertices{
    +        const QhullFacetList *facet_list;
    +                        PrintVertices(const QhullFacetList &fl) : facet_list(&fl) {}
    +    };//PrintVertices
    +    PrintVertices       printVertices() const { return PrintVertices(*this); }
    +};//class QhullFacetList
    +
    +}//namespace orgQhull
    +
    +#//!\name == Global namespace =========================================
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetList::PrintFacetList &p);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetList::PrintFacets &p);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetList::PrintVertices &p);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetList &fs);
    +
    +#endif // QHULLFACETLIST_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacetSet.cpp b/xs/src/qhull/src/libqhullcpp/QhullFacetSet.cpp
    new file mode 100644
    index 0000000000..d30c21e26a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacetSet.cpp
    @@ -0,0 +1,147 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacetSet.cpp#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullFacetSet -- Qhull's linked facets, as a C++ class
    +
    +#include "libqhullcpp/QhullFacetSet.h"
    +
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullVertex.h"
    +
    +#ifndef QHULL_NO_STL
    +using std::vector;
    +#endif
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Conversions
    +
    +// See qt-qhull.cpp for QList conversions
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullFacetSet::
    +toStdVector() const
    +{
    +    QhullSetIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        QhullFacet f= i.next();
    +        if(isSelectAll() || f.isGood()){
    +            vs.push_back(f);
    +        }
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +bool QhullFacetSet::
    +contains(const QhullFacet &facet) const
    +{
    +    if(isSelectAll()){
    +        return QhullSet::contains(facet);
    +    }
    +    for(QhullFacetSet::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f==facet && f.isGood()){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//contains
    +
    +int QhullFacetSet::
    +count() const
    +{
    +    if(isSelectAll()){
    +        return QhullSet::count();
    +    }
    +    int counter= 0;
    +    for(QhullFacetSet::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f.isGood()){
    +            counter++;
    +        }
    +    }
    +    return counter;
    +}//count
    +
    +int QhullFacetSet::
    +count(const QhullFacet &facet) const
    +{
    +    if(isSelectAll()){
    +        return QhullSet::count(facet);
    +    }
    +    int counter= 0;
    +    for(QhullFacetSet::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f==facet && f.isGood()){
    +            counter++;
    +        }
    +    }
    +    return counter;
    +}//count
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetSet;
    +
    +ostream &
    +operator<<(ostream &os, const QhullFacetSet &fs)
    +{
    +    os << fs.print("");
    +    return os;
    +}//<begin(); i!=p.facet_set->end(); ++i){
    +        const QhullFacet f= *i;
    +        if(f.getFacetT()==qh_MERGEridge){
    +            os << " MERGE";
    +        }else if(f.getFacetT()==qh_DUPLICATEridge){
    +            os << " DUP";
    +        }else if(p.facet_set->isSelectAll() || f.isGood()){
    +            os << " f" << f.id();
    +        }
    +    }
    +    os << endl;
    +    return os;
    +}//<
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +
    +#//!\name Defined here
    +    //! QhullFacetSet -- a set of Qhull facets, as a C++ class.  See QhullFacetList.h
    +    class QhullFacetSet;
    +    typedef QhullSetIterator QhullFacetSetIterator;
    +
    +class QhullFacetSet : public QhullSet {
    +
    +#//!\name Defined here
    +public:
    +    typedef facetT *   base_type;  // for QhullVertexSet
    +
    +private:
    +#//!\name Fields
    +    bool                select_all;   //! True if include bad facets.  Default is false.
    +
    +public:
    +#//!\name Constructor
    +                        //Conversion from setT* is not type-safe.  Implicit conversion for void* to T
    +                        QhullFacetSet(const Qhull &q, setT *s) : QhullSet(q, s), select_all(false) {}
    +                        QhullFacetSet(QhullQh *qqh, setT *s) : QhullSet(qqh, s), select_all(false) {}
    +                        //!Copy constructor copies pointers but not contents.  Needed for return by value and parameter passing.
    +                        QhullFacetSet(const QhullFacetSet &other) : QhullSet(other), select_all(other.select_all) {}
    +                        //!Assignment copies pointers but not contents.
    +    QhullFacetSet &     operator=(const QhullFacetSet &other) { QhullSet::operator=(other); select_all= other.select_all; return *this; }
    +
    +private:
    +                        //!Disable default constructor.  See QhullSetBase
    +                        QhullFacetSet();
    +public:
    +
    +#//!\name Conversion
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList   toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +                        //! Filtered by facet.isGood().  May be 0 when !isEmpty().
    +    countT              count() const;
    +    bool                contains(const QhullFacet &f) const;
    +    countT              count(const QhullFacet &f) const;
    +    bool                isSelectAll() const { return select_all; }
    +                        //! operator==() does not depend on isGood()
    +    void                selectAll() { select_all= true; }
    +    void                selectGood() { select_all= false; }
    +
    +#//!\name IO
    +    // Not same as QhullFacetList#IO.  A QhullFacetSet is a component of a QhullFacetList.
    +
    +    struct PrintFacetSet{
    +        const QhullFacetSet *facet_set;
    +        const char *    print_message;  //!< non-null message
    +                        PrintFacetSet(const char *message, const QhullFacetSet *s) : facet_set(s), print_message(message) {}
    +    };//PrintFacetSet
    +    const PrintFacetSet print(const char *message) const { return PrintFacetSet(message, this); }
    +
    +    struct PrintIdentifiers{
    +        const QhullFacetSet *facet_set;
    +        const char *    print_message;  //!< non-null message
    +                        PrintIdentifiers(const char *message, const QhullFacetSet *s) : facet_set(s), print_message(message) {}
    +    };//PrintIdentifiers
    +    PrintIdentifiers    printIdentifiers(const char *message) const { return PrintIdentifiers(message, this); }
    +
    +};//class QhullFacetSet
    +
    +}//namespace orgQhull
    +
    +#//!\name == Global namespace =========================================
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetSet &fs);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetSet::PrintFacetSet &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetSet::PrintIdentifiers &p);
    +
    +#endif // QHULLFACETSET_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullHyperplane.cpp b/xs/src/qhull/src/libqhullcpp/QhullHyperplane.cpp
    new file mode 100644
    index 0000000000..ed5cc4bae1
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullHyperplane.cpp
    @@ -0,0 +1,187 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullHyperplane.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/QhullHyperplane.h"
    +
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullPoint.h"
    +
    +#include 
    +
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructors
    +
    +QhullHyperplane::
    +QhullHyperplane(const Qhull &q) 
    +: hyperplane_coordinates(0)
    +, qh_qh(q.qh())
    +, hyperplane_offset(0.0)
    +, hyperplane_dimension(0)
    +{
    +}
    +
    +QhullHyperplane::
    +QhullHyperplane(const Qhull &q, int hyperplaneDimension, coordT *c, coordT hyperplaneOffset) 
    +: hyperplane_coordinates(c)
    +, qh_qh(q.qh())
    +, hyperplane_offset(hyperplaneOffset)
    +, hyperplane_dimension(hyperplaneDimension)
    +{
    +}
    +
    +#//!\name Conversions
    +
    +// See qt-qhull.cpp for QList conversions
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullHyperplane::
    +toStdVector() const
    +{
    +    QhullHyperplaneIterator i(*this);
    +    std::vector fs;
    +    while(i.hasNext()){
    +        fs.push_back(i.next());
    +    }
    +    fs.push_back(hyperplane_offset);
    +    return fs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +//! Return true if equal
    +//! If qh_qh defined, tests qh.distanceEpsilon and qh.angleEpsilon
    +//! otherwise, tests equal coordinates and offset
    +bool QhullHyperplane::
    +operator==(const QhullHyperplane &other) const
    +{
    +    if(hyperplane_dimension!=other.hyperplane_dimension || !hyperplane_coordinates || !other.hyperplane_coordinates){
    +        return false;
    +    }
    +    double d= fabs(hyperplane_offset-other.hyperplane_offset);
    +    if(d > (qh_qh ? qh_qh->distanceEpsilon() : 0.0)){
    +        return false;
    +    }
    +    double angle= hyperplaneAngle(other);
    +
    +    double a= fabs(angle-1.0);
    +    if(a > (qh_qh ? qh_qh->angleEpsilon() : 0.0)){
    +        return false;
    +    }
    +    return true;
    +}//operator==
    +
    +#//!\name Methods
    +
    +//! Return distance from point to hyperplane.
    +//!   If greater than zero, the point is above the facet (i.e., outside).
    +// qh_distplane [geom_r.c], QhullFacet::distance, and QhullHyperplane::distance are copies
    +//    Does not support RANDOMdist or logging
    +double QhullHyperplane::
    +distance(const QhullPoint &p) const
    +{
    +    const coordT *point= p.coordinates();
    +    int dim= p.dimension();
    +    QHULL_ASSERT(dim==dimension());
    +    const coordT *normal= coordinates();
    +    double dist;
    +
    +    switch (dim){
    +  case 2:
    +      dist= offset() + point[0] * normal[0] + point[1] * normal[1];
    +      break;
    +  case 3:
    +      dist= offset() + point[0] * normal[0] + point[1] * normal[1] + point[2] * normal[2];
    +      break;
    +  case 4:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3];
    +      break;
    +  case 5:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4];
    +      break;
    +  case 6:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5];
    +      break;
    +  case 7:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6];
    +      break;
    +  case 8:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6]+point[7]*normal[7];
    +      break;
    +  default:
    +      dist= offset();
    +      for (int k=dim; k--; )
    +          dist += *point++ * *normal++;
    +      break;
    +    }
    +    return dist;
    +}//distance
    +
    +double QhullHyperplane::
    +hyperplaneAngle(const QhullHyperplane &other) const
    +{
    +    volatile realT result= 0.0;
    +    QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +        result= qh_getangle(qh_qh, hyperplane_coordinates, other.hyperplane_coordinates);
    +    }
    +    qh_qh->NOerrexit= true;
    +    qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    return result;
    +}//hyperplaneAngle
    +
    +double QhullHyperplane::
    +norm() const {
    +    double d= 0.0;
    +    const coordT *c= coordinates();
    +    for (int k=dimension(); k--; ){
    +        d += *c * *c;
    +        ++c;
    +    }
    +    return sqrt(d);
    +}//norm
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::ostream;
    +using orgQhull::QhullHyperplane;
    +
    +#//!\name GetSet<<
    +
    +ostream &
    +operator<<(ostream &os, const QhullHyperplane &p)
    +{
    +    os << p.print("");
    +    return os;
    +}
    +
    +ostream &
    +operator<<(ostream &os, const QhullHyperplane::PrintHyperplane &pr)
    +{
    +    os << pr.print_message;
    +    QhullHyperplane p= *pr.hyperplane;
    +    const realT *c= p.coordinates();
    +    for(int k=p.dimension(); k--; ){
    +        realT r= *c++;
    +        if(pr.print_message){
    +            os << " " << r; // FIXUP QH11010 %8.4g
    +        }else{
    +            os << " " << r; // FIXUP QH11010 qh_REAL_1
    +        }
    +    }
    +    os << pr.hyperplane_offset_message << " " << p.offset();
    +    os << std::endl;
    +    return os;
    +}//PrintHyperplane
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullHyperplane.h b/xs/src/qhull/src/libqhullcpp/QhullHyperplane.h
    new file mode 100644
    index 0000000000..2868ce5c99
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullHyperplane.h
    @@ -0,0 +1,123 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullHyperplane.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHHYPERPLANE_H
    +#define QHHYPERPLANE_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullIterator.h"
    +#include "libqhullcpp/QhullQh.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +    class QhullPoint;
    +
    +#//!\name Defined here
    +    //! QhullHyperplane as an offset, dimension, and pointer to coordinates
    +    class QhullHyperplane;
    +    //! Java-style iterator for QhullHyperplane coordinates
    +    class QhullHyperplaneIterator;
    +
    +class QhullHyperplane { // Similar to QhullPoint
    +public:
    +#//!\name Subtypes
    +    typedef const coordT *                  iterator;
    +    typedef const coordT *                  const_iterator;
    +    typedef QhullHyperplane::iterator       Iterator;
    +    typedef QhullHyperplane::const_iterator ConstIterator;
    +
    +private:
    +#//!\name Fields
    +    coordT *            hyperplane_coordinates;  //!< Normal to hyperplane.   facetT.normal is normalized to 1.0
    +    QhullQh *           qh_qh;                  //!< qhT for distanceEpsilon() in operator==
    +    coordT              hyperplane_offset;      //!< Distance from hyperplane to origin
    +    int                 hyperplane_dimension;   //!< Dimension of hyperplane
    +
    +#//!\name Construct
    +public:
    +                        QhullHyperplane() : hyperplane_coordinates(0), qh_qh(0), hyperplane_offset(0.0), hyperplane_dimension(0) {}
    +    explicit            QhullHyperplane(const Qhull &q);
    +                        QhullHyperplane(const Qhull &q, int hyperplaneDimension, coordT *c, coordT hyperplaneOffset);
    +    explicit            QhullHyperplane(QhullQh *qqh) : hyperplane_coordinates(0), qh_qh(qqh), hyperplane_offset(0.0), hyperplane_dimension(0) {}
    +                        QhullHyperplane(QhullQh *qqh, int hyperplaneDimension, coordT *c, coordT hyperplaneOffset) : hyperplane_coordinates(c), qh_qh(qqh), hyperplane_offset(hyperplaneOffset), hyperplane_dimension(hyperplaneDimension) {}
    +                        // Creates an alias.  Does not copy the hyperplane's coordinates.  Needed for return by value and parameter passing.
    +                        QhullHyperplane(const QhullHyperplane &other)  : hyperplane_coordinates(other.hyperplane_coordinates), qh_qh(other.qh_qh), hyperplane_offset(other.hyperplane_offset), hyperplane_dimension(other.hyperplane_dimension) {}
    +                        // Creates an alias.  Does not copy the hyperplane's coordinates.  Needed for vector
    +    QhullHyperplane &   operator=(const QhullHyperplane &other) { hyperplane_coordinates= other.hyperplane_coordinates; qh_qh= other.qh_qh; hyperplane_offset= other.hyperplane_offset; hyperplane_dimension= other.hyperplane_dimension; return *this; }
    +                        ~QhullHyperplane() {}
    +
    +#//!\name Conversions --
    +//! Includes offset at end
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList       toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +public:
    +    const coordT *      coordinates() const { return hyperplane_coordinates; }
    +    coordT *            coordinates() { return hyperplane_coordinates; }
    +    void                defineAs(int hyperplaneDimension, coordT *c, coordT hyperplaneOffset) { QHULL_ASSERT(hyperplaneDimension>=0); hyperplane_coordinates= c; hyperplane_dimension= hyperplaneDimension; hyperplane_offset= hyperplaneOffset; }
    +    //! Creates an alias to other using the same qh_qh
    +    void                defineAs(QhullHyperplane &other) { hyperplane_coordinates= other.coordinates(); hyperplane_dimension= other.dimension();  hyperplane_offset= other.offset(); }
    +    int                 dimension() const { return hyperplane_dimension; }
    +    bool                isValid() const { return hyperplane_coordinates!=0 && hyperplane_dimension>0; }
    +    coordT              offset() const { return hyperplane_offset; }
    +    bool                operator==(const QhullHyperplane &other) const;
    +    bool                operator!=(const QhullHyperplane &other) const { return !operator==(other); }
    +    const coordT &      operator[](int idx) const { QHULL_ASSERT(idx>=0 && idx=0 && idx
    +#include 
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! Only QHULL_DECLARE_SEQUENTIAL_ITERATOR is used in libqhullcpp.  The others need further development
    +    //! QHULL_DECLARE_SEQUENTIAL_ITERATOR(C) -- Declare a Java-style iterator
    +    //! QHULL_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR(C) -- Declare a mutable Java-style iterator
    +    //! QHULL_DECLARE_SET_ITERATOR(C) -- Declare a set iterator
    +    //! QHULL_DECLARE_MUTABLE_SET_ITERATOR(C) -- Declare a mutable set iterator
    +    //! Derived from Qt/core/tools/qiterator.h and qset_r.h/FOREACHsetelement_()
    +
    +// Stores C* as done in Mutable...  Assumes the container is not deleted.
    +// C::const_iterator is an STL-style iterator that returns T&
    +#define QHULL_DECLARE_SEQUENTIAL_ITERATOR(C, T) \
    +    \
    +    class C##Iterator \
    +    { \
    +        typedef C::const_iterator const_iterator; \
    +        const C *c; \
    +        const_iterator i; \
    +        public: \
    +        inline C##Iterator(const C &container) \
    +        : c(&container), i(c->constBegin()) {} \
    +        inline C##Iterator &operator=(const C &container) \
    +        { c = &container; i = c->constBegin(); return *this; } \
    +        inline void toFront() { i = c->constBegin(); } \
    +        inline void toBack() { i = c->constEnd(); } \
    +        inline bool hasNext() const { return i != c->constEnd(); } \
    +        inline const T &next() { return *i++; } \
    +        inline const T &peekNext() const { return *i; } \
    +        inline bool hasPrevious() const { return i != c->constBegin(); } \
    +        inline const T &previous() { return *--i; } \
    +        inline const T &peekPrevious() const { const_iterator p = i; return *--p; } \
    +        inline bool findNext(const T &t) \
    +        { while (i != c->constEnd()) if (*i++ == t) return true; return false; } \
    +        inline bool findPrevious(const T &t) \
    +        { while (i != c->constBegin()) if (*(--i) == t) return true; \
    +        return false;  } \
    +    };//C##Iterator
    +
    +// Remove setShareable() from Q_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR
    +// Uses QHULL_ASSERT (assert.h)
    +// Duplicated in MutablePointIterator without insert or remove
    +// Not used in libqhullcpp.  See Coordinates.h
    +#define QHULL_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR(C, T) \
    +    class Mutable##C##Iterator \
    +    { \
    +        typedef C::iterator iterator; \
    +        typedef C::const_iterator const_iterator; \
    +        C *c; \
    +        iterator i, n; \
    +        inline bool item_exists() const { return const_iterator(n) != c->constEnd(); } \
    +        public: \
    +        inline Mutable##C##Iterator(C &container) \
    +        : c(&container) \
    +        { i = c->begin(); n = c->end(); } \
    +        inline ~Mutable##C##Iterator() \
    +        {} \
    +        inline Mutable##C##Iterator &operator=(C &container) \
    +        { c = &container; \
    +        i = c->begin(); n = c->end(); return *this; } \
    +        inline void toFront() { i = c->begin(); n = c->end(); } \
    +        inline void toBack() { i = c->end(); n = i; } \
    +        inline bool hasNext() const { return c->constEnd() != const_iterator(i); } \
    +        inline T &next() { n = i++; return *n; } \
    +        inline T &peekNext() const { return *i; } \
    +        inline bool hasPrevious() const { return c->constBegin() != const_iterator(i); } \
    +        inline T &previous() { n = --i; return *n; } \
    +        inline T &peekPrevious() const { iterator p = i; return *--p; } \
    +        inline void remove() \
    +        { if (c->constEnd() != const_iterator(n)) { i = c->erase(n); n = c->end(); } } \
    +        inline void setValue(const T &t) const { if (c->constEnd() != const_iterator(n)) *n = t; } \
    +        inline T &value() { QHULL_ASSERT(item_exists()); return *n; } \
    +        inline const T &value() const { QHULL_ASSERT(item_exists()); return *n; } \
    +        inline void insert(const T &t) { n = i = c->insert(i, t); ++i; } \
    +        inline bool findNext(const T &t) \
    +        { while (c->constEnd() != const_iterator(n = i)) if (*i++ == t) return true; return false; } \
    +        inline bool findPrevious(const T &t) \
    +        { while (c->constBegin() != const_iterator(i)) if (*(n = --i) == t) return true; \
    +        n = c->end(); return false;  } \
    +    };//Mutable##C##Iterator
    +
    +// Not used in libqhullcpp.
    +#define QHULL_DECLARE_SET_ITERATOR(C) \
    +\
    +    template  \
    +    class Qhull##C##Iterator \
    +    { \
    +        typedef typename Qhull##C::const_iterator const_iterator; \
    +        Qhull##C c; \
    +        const_iterator i; \
    +    public: \
    +        inline Qhull##C##Iterator(const Qhull##C &container) \
    +        : c(container), i(c.constBegin()) {} \
    +        inline Qhull##C##Iterator &operator=(const Qhull##C &container) \
    +        { c = container; i = c.constBegin(); return *this; } \
    +        inline void toFront() { i = c.constBegin(); } \
    +        inline void toBack() { i = c.constEnd(); } \
    +        inline bool hasNext() const { return i != c.constEnd(); } \
    +        inline const T &next() { return *i++; } \
    +        inline const T &peekNext() const { return *i; } \
    +        inline bool hasPrevious() const { return i != c.constBegin(); } \
    +        inline const T &previous() { return *--i; } \
    +        inline const T &peekPrevious() const { const_iterator p = i; return *--p; } \
    +        inline bool findNext(const T &t) \
    +        { while (i != c.constEnd()) if (*i++ == t) return true; return false; } \
    +        inline bool findPrevious(const T &t) \
    +        { while (i != c.constBegin()) if (*(--i) == t) return true; \
    +        return false;  } \
    +    };//Qhull##C##Iterator
    +
    +// Not used in libqhullcpp.
    +#define QHULL_DECLARE_MUTABLE_SET_ITERATOR(C) \
    +\
    +template  \
    +class QhullMutable##C##Iterator \
    +{ \
    +    typedef typename Qhull##C::iterator iterator; \
    +    typedef typename Qhull##C::const_iterator const_iterator; \
    +    Qhull##C *c; \
    +    iterator i, n; \
    +    inline bool item_exists() const { return const_iterator(n) != c->constEnd(); } \
    +public: \
    +    inline Mutable##C##Iterator(Qhull##C &container) \
    +        : c(&container) \
    +    { c->setSharable(false); i = c->begin(); n = c->end(); } \
    +    inline ~Mutable##C##Iterator() \
    +    { c->setSharable(true); } \
    +    inline Mutable##C##Iterator &operator=(Qhull##C &container) \
    +    { c->setSharable(true); c = &container; c->setSharable(false); \
    +      i = c->begin(); n = c->end(); return *this; } \
    +    inline void toFront() { i = c->begin(); n = c->end(); } \
    +    inline void toBack() { i = c->end(); n = i; } \
    +    inline bool hasNext() const { return c->constEnd() != const_iterator(i); } \
    +    inline T &next() { n = i++; return *n; } \
    +    inline T &peekNext() const { return *i; } \
    +    inline bool hasPrevious() const { return c->constBegin() != const_iterator(i); } \
    +    inline T &previous() { n = --i; return *n; } \
    +    inline T &peekPrevious() const { iterator p = i; return *--p; } \
    +    inline void remove() \
    +    { if (c->constEnd() != const_iterator(n)) { i = c->erase(n); n = c->end(); } } \
    +    inline void setValue(const T &t) const { if (c->constEnd() != const_iterator(n)) *n = t; } \
    +    inline T &value() { Q_ASSERT(item_exists()); return *n; } \
    +    inline const T &value() const { Q_ASSERT(item_exists()); return *n; } \
    +    inline void insert(const T &t) { n = i = c->insert(i, t); ++i; } \
    +    inline bool findNext(const T &t) \
    +    { while (c->constEnd() != const_iterator(n = i)) if (*i++ == t) return true; return false; } \
    +    inline bool findPrevious(const T &t) \
    +    { while (c->constBegin() != const_iterator(i)) if (*(n = --i) == t) return true; \
    +      n = c->end(); return false;  } \
    +};//QhullMutable##C##Iterator
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLITERATOR_H
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullLinkedList.h b/xs/src/qhull/src/libqhullcpp/QhullLinkedList.h
    new file mode 100644
    index 0000000000..d4caf52c18
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullLinkedList.h
    @@ -0,0 +1,388 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullLinkedList.h#7 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLLINKEDLIST_H
    +#define QHULLLINKEDLIST_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +
    +#include   // ptrdiff_t, size_t
    +
    +#ifdef QHULL_USES_QT
    +#include 
    +#endif
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#include 
    +#include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! QhullLinkedList -- A linked list modeled on QLinkedList.
    +    //!   T is an opaque type with T(B *b), b=t.getBaseT(), t=t.next(), and t=t.prev().  The end node is a sentinel.
    +    //!   QhullQh/qhT owns the contents.
    +    //!   QhullLinkedList does not define erase(), clear(), removeFirst(), removeLast(), pop_back(), pop_front(), fromStdList()
    +    //!   Derived from Qt/core/tools/qlinkedlist.h and libqhull_r.h/FORALLfacets_()
    +    //! QhullLinkedList::const_iterator -- STL-style iterator
    +    //! QhullLinkedList::iterator -- STL-style iterator
    +    //! QhullLinkedListIterator -- Java-style iterator
    +    //!   Derived from Qt/core/tools/qiterator.h
    +    //!   Works with Qt's foreach keyword [Qt/src/corelib/global/qglobal.h]
    +
    +template 
    +class QhullLinkedList
    +{
    +#//!\name Defined here
    +public:
    +    class const_iterator;
    +    class iterator;
    +    typedef const_iterator  ConstIterator;
    +    typedef iterator    Iterator;
    +    typedef ptrdiff_t   difference_type;
    +    typedef countT      size_type;
    +    typedef T           value_type;
    +    typedef const value_type *const_pointer;
    +    typedef const value_type &const_reference;
    +    typedef value_type *pointer;
    +    typedef value_type &reference;
    +
    +#//!\name Fields
    +private:
    +    T                   begin_node;
    +    T                   end_node;     //! Sentinel node at end of list
    +
    +#//!\name Constructors
    +public:
    +                        QhullLinkedList(T b, T e) : begin_node(b), end_node(e) {}
    +                        //! Copy constructor copies begin_node and end_node, but not the list elements.  Needed for return by value and parameter passing.
    +                        QhullLinkedList(const QhullLinkedList &other) : begin_node(other.begin_node), end_node(other.end_node) {}
    +                        //! Copy assignment copies begin_node and end_node, but not the list elements.
    +                        QhullLinkedList & operator=(const QhullLinkedList &other) { begin_node= other.begin_node; end_node= other.end_node; return *this; }
    +                        ~QhullLinkedList() {}
    +
    +private:
    +                        //!disabled since a sentinel must be allocated as the private type
    +                        QhullLinkedList() {}
    +
    +public:
    +
    +#//!\name Conversions
    +#ifndef QHULL_NO_STL
    +    std::vector      toStdVector() const;
    +#endif
    +#ifdef QHULL_USES_QT
    +    QList            toQList() const;
    +#endif
    +
    +#//!\name GetSet
    +    countT              count() const;
    +                        //count(t) under #//!\name Search
    +    bool                isEmpty() const { return (begin_node==end_node); }
    +    bool                operator==(const QhullLinkedList &o) const;
    +    bool                operator!=(const QhullLinkedList &o) const { return !operator==(o); }
    +    size_t              size() const { return count(); }
    +
    +#//!\name Element access
    +    //! For back() and last(), return T instead of T& (T is computed)
    +    const T             back() const { return last(); }
    +    T                   back() { return last(); }
    +    const T &           first() const { QHULL_ASSERT(!isEmpty()); return begin_node; }
    +    T &                 first() { QHULL_ASSERT(!isEmpty()); return begin_node; }
    +    const T &           front() const { return first(); }
    +    T &                 front() { return first(); }
    +    const T             last() const { QHULL_ASSERT(!isEmpty()); return *--end(); }
    +    T                   last() { QHULL_ASSERT(!isEmpty()); return *--end(); }
    +
    +#//!\name Modify -- Allocation of opaque types not implemented.
    +
    +#//!\name Search
    +    bool                contains(const T &t) const;
    +    countT              count(const T &t) const;
    +
    +#//!\name Iterator
    +    iterator            begin() { return begin_node; }
    +    const_iterator      begin() const { return begin_node; }
    +    const_iterator      constBegin() const { return begin_node; }
    +    const_iterator      constEnd() const { return end_node; }
    +    iterator            end() { return end_node; }
    +    const_iterator      end() const { return end_node; }
    +
    +    class iterator {
    +
    +    private:
    +        T               i;
    +        friend class const_iterator;
    +
    +    public:
    +        typedef std::bidirectional_iterator_tag  iterator_category;
    +        typedef T           value_type;
    +        typedef value_type *pointer;
    +        typedef value_type &reference;
    +        typedef ptrdiff_t   difference_type;
    +
    +                        iterator() : i() {}
    +                        iterator(const T &t) : i(t) {}  //!< Automatic conversion to iterator
    +                        iterator(const iterator &o) : i(o.i) {}
    +        iterator &      operator=(const iterator &o) { i= o.i; return *this; }
    +
    +        const T &       operator*() const { return i; }
    +        T &             operator*() { return i; }
    +        // Do not define operator[]
    +        const T *       operator->() const { return &i; }
    +        T *             operator->() { return &i; }
    +        bool            operator==(const iterator &o) const { return i == o.i; }
    +        bool            operator!=(const iterator &o) const { return !operator==(o); }
    +        bool            operator==(const const_iterator &o) const { return i==reinterpret_cast(o).i; }
    +        bool            operator!=(const const_iterator &o) const { return !operator==(o); }
    +        iterator &      operator++() { i= i.next(); return *this; }
    +        iterator        operator++(int) { iterator o= i; i= i.next(); return o; }
    +        iterator &      operator--() { i= i.previous(); return *this; }
    +        iterator        operator--(int) { iterator o= i; i= i.previous(); return o; }
    +        iterator        operator+(int j) const;
    +        iterator        operator-(int j) const { return operator+(-j); }
    +        iterator &      operator+=(int j) { return (*this= *this + j); }
    +        iterator &      operator-=(int j) { return (*this= *this - j); }
    +    };//QhullLinkedList::iterator
    +
    +    class const_iterator {
    +
    +    private:
    +        T               i;
    +
    +    public:
    +        typedef std::bidirectional_iterator_tag  iterator_category;
    +        typedef T                 value_type;
    +        typedef const value_type *pointer;
    +        typedef const value_type &reference;
    +        typedef ptrdiff_t         difference_type;
    +
    +                        const_iterator() : i() {}
    +                        const_iterator(const T &t) : i(t) {}  //!< Automatic conversion to const_iterator
    +                        const_iterator(const iterator &o) : i(o.i) {}
    +                        const_iterator(const const_iterator &o) : i(o.i) {}
    +        const_iterator &operator=(const const_iterator &o) { i= o.i; return *this; }
    +
    +        const T &       operator*() const { return i; }
    +        const T *       operator->() const { return i; }
    +        bool            operator==(const const_iterator &o) const { return i == o.i; }
    +        bool            operator!=(const const_iterator &o) const { return !operator==(o); }
    +                        // No comparisons or iterator diff
    +        const_iterator &operator++() { i= i.next(); return *this; }
    +        const_iterator  operator++(int) { const_iterator o= i; i= i.next(); return o; }
    +        const_iterator &operator--() { i= i.previous(); return *this; }
    +        const_iterator  operator--(int) { const_iterator o= i; i= i.previous(); return o; }
    +        const_iterator  operator+(int j) const;
    +        const_iterator  operator-(int j) const { return operator+(-j); }
    +        const_iterator &operator+=(int j) { return (*this= *this + j); }
    +        const_iterator &operator-=(int j) { return (*this= *this - j); }
    +    };//QhullLinkedList::const_iterator
    +
    +};//QhullLinkedList
    +
    +template 
    +class QhullLinkedListIterator // FIXUP QH11016 define QhullMutableLinkedListIterator
    +{
    +    typedef typename QhullLinkedList::const_iterator const_iterator;
    +    const QhullLinkedList *c;
    +    const_iterator      i;
    +
    +public:
    +                        QhullLinkedListIterator(const QhullLinkedList &container) : c(&container), i(c->constBegin()) {}
    +    QhullLinkedListIterator & operator=(const QhullLinkedList &container) { c= &container; i= c->constBegin(); return *this; }
    +    bool                findNext(const T &t);
    +    bool                findPrevious(const T &t);
    +    bool                hasNext() const { return i != c->constEnd(); }
    +    bool                hasPrevious() const { return i != c->constBegin(); }
    +    T                   next() { return *i++; }
    +    T                   peekNext() const { return *i; }
    +    T                   peekPrevious() const { const_iterator p= i; return *--p; }
    +    T                   previous() { return *--i; }
    +    void                toFront() { i= c->constBegin(); }
    +    void                toBack() { i= c->constEnd(); }
    +};//QhullLinkedListIterator
    +
    +#//!\name == Definitions =========================================
    +
    +#//!\name Conversion
    +
    +#ifndef QHULL_NO_STL
    +template 
    +std::vector QhullLinkedList::
    +toStdVector() const
    +{
    +    std::vector tmp;
    +    std::copy(constBegin(), constEnd(), std::back_inserter(tmp));
    +    return tmp;
    +}//toStdVector
    +#endif
    +
    +#ifdef QHULL_USES_QT
    +template 
    +QList  QhullLinkedList::
    +toQList() const
    +{
    +    QhullLinkedListIterator i(*this);
    +    QList ls;
    +    while(i.hasNext()){
    +        ls.append(i.next());
    +    }
    +    return ls;
    +}//toQList
    +#endif
    +
    +#//!\name GetSet
    +
    +template 
    +countT QhullLinkedList::
    +count() const
    +{
    +    const_iterator i= begin_node;
    +    countT c= 0;
    +    while(i != end_node){
    +        c++;
    +        i++;
    +    }
    +    return c;
    +}//count
    +
    +#//!\name Search
    +
    +template 
    +bool QhullLinkedList::
    +contains(const T &t) const
    +{
    +    const_iterator i= begin_node;
    +    while(i != end_node){
    +        if(i==t){
    +            return true;
    +        }
    +        i++;
    +    }
    +    return false;
    +}//contains
    +
    +template 
    +countT QhullLinkedList::
    +count(const T &t) const
    +{
    +    const_iterator i= begin_node;
    +    countT c= 0;
    +    while(i != end_node){
    +        if(i==t){
    +            c++;
    +        }
    +        i++;
    +    }
    +    return c;
    +}//count
    +
    +template 
    +bool QhullLinkedList::
    +operator==(const QhullLinkedList &l) const
    +{
    +    if(begin_node==l.begin_node){
    +        return (end_node==l.end_node);
    +    }
    +    T i= begin_node;
    +    T il= l.begin_node;
    +    while(i != end_node){
    +        if(i != il){
    +            return false;
    +        }
    +        i= static_cast(i.next());
    +        il= static_cast(il.next());
    +    }
    +    if(il != l.end_node){
    +        return false;
    +    }
    +    return true;
    +}//operator==
    +
    +#//!\name Iterator
    +
    +template 
    +typename QhullLinkedList::iterator  QhullLinkedList::iterator::
    +operator+(int j) const
    +{
    +    T n= i;
    +    if(j>0){
    +        while(j--){
    +            n= n.next();
    +        }
    +    }else{
    +        while(j++){
    +            n= n.previous();
    +        }
    +    }
    +    return iterator(n);
    +}//operator+
    +
    +template 
    +typename QhullLinkedList::const_iterator  QhullLinkedList::const_iterator::
    +operator+(int j) const
    +{
    +    T n= i;
    +    if(j>0){
    +        while(j--){
    +            n= n.next();
    +        }
    +    }else{
    +        while(j++){
    +            n= n.previous();
    +        }
    +    }
    +    return const_iterator(n);
    +}//operator+
    +
    +#//!\name QhullLinkedListIterator
    +
    +template 
    +bool QhullLinkedListIterator::
    +findNext(const T &t)
    +{
    +    while(i != c->constEnd()){
    +        if (*i++ == t){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findNext
    +
    +template 
    +bool QhullLinkedListIterator::
    +findPrevious(const T &t)
    +{
    +    while(i!=c->constBegin()){
    +        if(*(--i)==t){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findNext
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +template 
    +std::ostream &
    +operator<<(std::ostream &os, const orgQhull::QhullLinkedList &qs)
    +{
    +    typename orgQhull::QhullLinkedList::const_iterator i;
    +    for(i= qs.begin(); i != qs.end(); ++i){
    +        os << *i;
    +    }
    +    return os;
    +}//operator<<
    +
    +#endif // QHULLLINKEDLIST_H
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullPoint.cpp b/xs/src/qhull/src/libqhullcpp/QhullPoint.cpp
    new file mode 100644
    index 0000000000..f5e9124609
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullPoint.cpp
    @@ -0,0 +1,203 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullPoint.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/QhullPoint.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructors
    +
    +
    +QhullPoint::
    +QhullPoint(const Qhull &q) 
    +: point_coordinates(0)
    +, qh_qh(q.qh())
    +, point_dimension(q.hullDimension())
    +{
    +}//QhullPoint
    +
    +QhullPoint::
    +QhullPoint(const Qhull &q, coordT *c) 
    +: point_coordinates(c)
    +, qh_qh(q.qh())
    +, point_dimension(q.hullDimension())
    +{
    +    QHULL_ASSERT(q.hullDimension()>0);
    +}//QhullPoint dim, coordT
    +
    +QhullPoint::
    +QhullPoint(const Qhull &q, int pointDimension, coordT *c) 
    +: point_coordinates(c)
    +, qh_qh(q.qh())
    +, point_dimension(pointDimension)
    +{
    +}//QhullPoint dim, coordT
    +
    +//! QhullPoint of Coordinates with point_dimension==c.count()
    +QhullPoint::
    +QhullPoint(const Qhull &q, Coordinates &c) 
    +: point_coordinates(c.data())
    +, qh_qh(q.qh())
    +, point_dimension(c.count())
    +{
    +}//QhullPoint Coordinates
    +
    +#//!\name Conversions
    +
    +// See qt-qhull.cpp for QList conversion
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullPoint::
    +toStdVector() const
    +{
    +    QhullPointIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        vs.push_back(i.next());
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +//! QhullPoint is equal if it has the same address and dimension
    +//! If !qh_qh, returns true if dimension and coordinates are equal
    +//! If qh_qh, returns true if the distance between points is less than qh_qh->distanceEpsilon()
    +//!\todo Compares distance with distance-to-hyperplane (distanceEpsilon).   Is that correct?
    +bool QhullPoint::
    +operator==(const QhullPoint &other) const
    +{
    +    if(point_dimension!=other.point_dimension){
    +        return false;
    +    }
    +    const coordT *c= point_coordinates;
    +    const coordT *c2= other.point_coordinates;
    +    if(c==c2){
    +        return true;
    +    }
    +    if(!c || !c2){
    +        return false;
    +    }
    +    if(!qh_qh || qh_qh->hull_dim==0){
    +        for(int k= point_dimension; k--; ){
    +            if(*c++ != *c2++){
    +                return false;
    +            }
    +        }
    +        return true;
    +    }
    +    double dist2= 0.0;
    +    for(int k= point_dimension; k--; ){
    +        double diff= *c++ - *c2++;
    +        dist2 += diff*diff;
    +    }
    +    dist2= sqrt(dist2);
    +    return (dist2 < qh_qh->distanceEpsilon());
    +}//operator==
    +
    +#//!\name Methods
    +
    +//! Return distance between two points.
    +double QhullPoint::
    +distance(const QhullPoint &p) const
    +{
    +    const coordT *c= point_coordinates;
    +    const coordT *c2= p.point_coordinates;
    +    int dim= point_dimension;
    +    if(dim!=p.point_dimension){
    +        throw QhullError(10075, "QhullPoint error: Expecting dimension %d for distance().  Got %d", dim, p.point_dimension);
    +    }
    +    if(!c || !c2){
    +        throw QhullError(10076, "QhullPoint error: Cannot compute distance() for undefined point");
    +    }
    +    double dist;
    +
    +    switch(dim){
    +  case 2:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]);
    +      break;
    +  case 3:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]);
    +      break;
    +  case 4:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]);
    +      break;
    +  case 5:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]) + (c[4]-c2[4])*(c[4]-c2[4]);
    +      break;
    +  case 6:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]) + (c[4]-c2[4])*(c[4]-c2[4]) + (c[5]-c2[5])*(c[5]-c2[5]);
    +      break;
    +  case 7:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]) + (c[4]-c2[4])*(c[4]-c2[4]) + (c[5]-c2[5])*(c[5]-c2[5]) + (c[6]-c2[6])*(c[6]-c2[6]);
    +      break;
    +  case 8:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]) + (c[4]-c2[4])*(c[4]-c2[4]) + (c[5]-c2[5])*(c[5]-c2[5]) + (c[6]-c2[6])*(c[6]-c2[6]) + (c[7]-c2[7])*(c[7]-c2[7]);
    +      break;
    +  default:
    +      dist= 0.0;
    +      for(int k=dim; k--; ){
    +          dist += (*c - *c2) * (*c - *c2);
    +          ++c;
    +          ++c2;
    +      }
    +      break;
    +    }
    +    return sqrt(dist);
    +}//distance
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::ostream;
    +using orgQhull::QhullPoint;
    +
    +//! Same as qh_printpointid [io.c]
    +ostream &
    +operator<<(ostream &os, const QhullPoint::PrintPoint &pr)
    +{
    +    QhullPoint p= *pr.point; 
    +    countT i= p.id();
    +    if(pr.point_message){
    +        if(*pr.point_message){
    +            os << pr.point_message << " ";
    +        }
    +        if(pr.with_identifier && (i!=qh_IDunknown) && (i!=qh_IDnone)){
    +            os << "p" << i << ": ";
    +        }
    +    }
    +    const realT *c= p.coordinates();
    +    for(int k=p.dimension(); k--; ){
    +        realT r= *c++;
    +        if(pr.point_message){
    +            os << " " << r; // FIXUP QH11010 %8.4g
    +        }else{
    +            os << " " << r; // FIXUP QH11010 qh_REAL_1
    +        }
    +    }
    +    os << std::endl;
    +    return os;
    +}//printPoint
    +
    +ostream & 
    +operator<<(ostream &os, const QhullPoint &p)
    +{
    +    os << p.print(""); 
    +    return os;
    +}//operator<<
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullPoint.h b/xs/src/qhull/src/libqhullcpp/QhullPoint.h
    new file mode 100644
    index 0000000000..17f94ab364
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullPoint.h
    @@ -0,0 +1,136 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullPoint.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHPOINT_H
    +#define QHPOINT_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullIterator.h"
    +#include "libqhullcpp/QhullQh.h"
    +#include "libqhullcpp/Coordinates.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    class QhullPoint;  //!<  QhullPoint as a pointer and dimension to shared memory
    +    class QhullPointIterator; //!< Java-style iterator for QhullPoint coordinates
    +
    +#//!\name Used here
    +    class Qhull;
    +
    +//! A QhullPoint is a dimension and an array of coordinates.
    +//! With Qhull/QhullQh, a QhullPoint has an identifier.  Point equality is relative to qh.distanceEpsilon
    +class QhullPoint {
    +
    +#//!\name Iterators
    +public:
    +    typedef coordT *                    base_type;  // for QhullPointSet
    +    typedef const coordT *              iterator;
    +    typedef const coordT *              const_iterator;
    +    typedef QhullPoint::iterator        Iterator;
    +    typedef QhullPoint::const_iterator  ConstIterator;
    +
    +#//!\name Fields
    +protected: // For QhullPoints::iterator, QhullPoints::const_iterator
    +    coordT *            point_coordinates;  //!< Pointer to first coordinate,   0 if undefined
    +    QhullQh *           qh_qh;              //!< qhT for this instance of Qhull.  0 if undefined.
    +                                            //!< operator==() returns true if points within sqrt(qh_qh->distanceEpsilon())
    +                                            //!< If !qh_qh, id() is -3, and operator==() requires equal coordinates
    +    int                 point_dimension;    //!< Default dimension is qh_qh->hull_dim
    +public:
    +
    +#//!\name Constructors
    +    //! QhullPoint, PointCoordinates, and QhullPoints have similar constructors
    +    //! If Qhull/QhullQh is not initialized, then QhullPoint.dimension() is zero unless explicitly set
    +    //! Cannot define QhullPoints(int pointDimension) since it is ambiguous with QhullPoints(QhullQh *qqh)
    +                        QhullPoint() : point_coordinates(0), qh_qh(0), point_dimension(0) {}
    +                        QhullPoint(int pointDimension, coordT *c) : point_coordinates(c), qh_qh(0), point_dimension(pointDimension) { QHULL_ASSERT(pointDimension>0); }
    +    explicit            QhullPoint(const Qhull &q);
    +                        QhullPoint(const Qhull &q, coordT *c);
    +                        QhullPoint(const Qhull &q, Coordinates &c);
    +                        QhullPoint(const Qhull &q, int pointDimension, coordT *c);
    +    explicit            QhullPoint(QhullQh *qqh) : point_coordinates(0), qh_qh(qqh), point_dimension(qqh->hull_dim) {}
    +                        QhullPoint(QhullQh *qqh, coordT *c) : point_coordinates(c), qh_qh(qqh), point_dimension(qqh->hull_dim) { QHULL_ASSERT(qqh->hull_dim>0); }
    +                        QhullPoint(QhullQh *qqh, Coordinates &c) : point_coordinates(c.data()), qh_qh(qqh), point_dimension(c.count()) {}
    +                        QhullPoint(QhullQh *qqh, int pointDimension, coordT *c) : point_coordinates(c), qh_qh(qqh), point_dimension(pointDimension) {}
    +                        //! Creates an alias.  Does not make a deep copy of the point.  Needed for return by value and parameter passing.
    +                        QhullPoint(const QhullPoint &other) : point_coordinates(other.point_coordinates), qh_qh(other.qh_qh), point_dimension(other.point_dimension) {}
    +                        //! Creates an alias.  Does not make a deep copy of the point.  Needed for vector
    +    QhullPoint &        operator=(const QhullPoint &other) { point_coordinates= other.point_coordinates; qh_qh= other.qh_qh; point_dimension= other.point_dimension; return *this; }
    +                        ~QhullPoint() {}
    +
    +
    +#//!\name Conversions
    +
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList       toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +public:
    +    const coordT *      coordinates() const { return point_coordinates; }  //!< 0 if undefined
    +    coordT *            coordinates() { return point_coordinates; }        //!< 0 if undefined
    +    void                defineAs(coordT *c) { QHULL_ASSERT(point_dimension>0); point_coordinates= c; }
    +    void                defineAs(int pointDimension, coordT *c) { QHULL_ASSERT(pointDimension>=0); point_coordinates= c; point_dimension= pointDimension; }
    +    void                defineAs(QhullPoint &other) { point_coordinates= other.point_coordinates; qh_qh= other.qh_qh; point_dimension= other.point_dimension; }
    +    int                 dimension() const { return point_dimension; }
    +    coordT *            getBaseT() const { return point_coordinates; } // for QhullPointSet
    +    countT              id() const { return qh_pointid(qh_qh, point_coordinates); } // NOerrors
    +    bool                isValid() const { return (point_coordinates!=0 && point_dimension>0); };
    +    bool                operator==(const QhullPoint &other) const;
    +    bool                operator!=(const QhullPoint &other) const { return ! operator==(other); }
    +    const coordT &      operator[](int idx) const { QHULL_ASSERT(point_coordinates!=0 && idx>=0 && idx=0 && idx
    +#include 
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +// Implemented via QhullSet.h
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullPointSet;
    +using orgQhull::QhullPointSetIterator;
    +
    +ostream &
    +operator<<(ostream &os, const QhullPointSet::PrintIdentifiers &pr)
    +{
    +    os << pr.print_message;
    +    const QhullPointSet s= *pr.point_set;
    +    QhullPointSetIterator i(s);
    +    while(i.hasNext()){
    +        if(i.hasPrevious()){
    +            os << " ";
    +        }
    +        const QhullPoint point= i.next();
    +        countT id= point.id();
    +        os << "p" << id;
    +
    +    }
    +    os << endl;
    +    return os;
    +}//PrintIdentifiers
    +
    +ostream &
    +operator<<(ostream &os, const QhullPointSet::PrintPointSet &pr)
    +{
    +    os << pr.print_message;
    +    const QhullPointSet s= *pr.point_set;
    +    for(QhullPointSet::const_iterator i=s.begin(); i != s.end(); ++i){
    +        const QhullPoint point= *i;
    +        os << point;
    +    }
    +    return os;
    +}//printPointSet
    +
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullPointSet.h b/xs/src/qhull/src/libqhullcpp/QhullPointSet.h
    new file mode 100644
    index 0000000000..8562e170ea
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullPointSet.h
    @@ -0,0 +1,77 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullPointSet.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLPOINTSET_H
    +#define QHULLPOINTSET_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullSet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +    class QhullPoint;
    +
    +#//!\name Defined here
    +    //! QhullPointSet -- a set of coordinate pointers with input dimension
    +    // with const_iterator and iterator
    +    class QhullPointSet;
    +
    +class QhullPointSet : public QhullSet {
    +
    +private:
    +#//!\name Fields
    +    // no fields
    +public:
    +
    +#//!\name Construct
    +                        QhullPointSet(const Qhull &q, setT *s) : QhullSet(q, s) {}
    +                        //Conversion from setT* is not type-safe.  Implicit conversion for void* to T
    +                        QhullPointSet(QhullQh *qqh, setT *s) : QhullSet(qqh, s) {}
    +                        //Copy constructor copies pointer but not contents.  Needed for return by value and parameter passing.
    +                        QhullPointSet(const QhullPointSet &other) : QhullSet(other) {}
    +                        //!Assignment copies pointers but not contents.
    +    QhullPointSet &     operator=(const QhullPointSet &other) { QhullSet::operator=(other); return *this; }
    +                        ~QhullPointSet() {}
    +
    +                        //!Default constructor disabled.
    +private:
    +                        QhullPointSet();
    +public:
    +
    +#//!\name IO
    +    struct PrintIdentifiers{
    +        const QhullPointSet *point_set;
    +        const char *    print_message; //!< non-null message
    +        PrintIdentifiers(const char *message, const QhullPointSet *s) : point_set(s), print_message(message) {}
    +    };//PrintIdentifiers
    +    PrintIdentifiers printIdentifiers(const char *message) const { return PrintIdentifiers(message, this); }
    +
    +    struct PrintPointSet{
    +        const QhullPointSet *point_set;
    +        const char *    print_message;  //!< non-null message
    +        PrintPointSet(const char *message, const QhullPointSet &s) : point_set(&s), print_message(message) {}
    +    };//PrintPointSet
    +    PrintPointSet       print(const char *message) const { return PrintPointSet(message, *this); }
    +
    +};//QhullPointSet
    +
    +typedef QhullSetIterator  QhullPointSetIterator;
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullPointSet::PrintIdentifiers &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullPointSet::PrintPointSet &pr);
    +
    +#endif // QHULLPOINTSET_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullPoints.cpp b/xs/src/qhull/src/libqhullcpp/QhullPoints.cpp
    new file mode 100644
    index 0000000000..2320b5007a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullPoints.cpp
    @@ -0,0 +1,320 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullPoints.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/QhullPoints.h"
    +
    +#include "libqhullcpp/Qhull.h"
    +
    +#include 
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#endif
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructors
    +
    +QhullPoints::
    +QhullPoints(const Qhull &q)
    +: point_first(0)
    +, point_end(0)
    +, qh_qh(q.qh())
    +, point_dimension(q.hullDimension())
    +{
    +}//QhullPoints Qhull
    +
    +QhullPoints::
    +QhullPoints(const Qhull &q, countT coordinateCount2, coordT *c)
    +: point_first(c)
    +, point_end(c+coordinateCount2)
    +, qh_qh(q.qh())
    +, point_dimension(q.hullDimension())
    +{
    +    QHULL_ASSERT(q.hullDimension());
    +    QHULL_ASSERT(coordinateCount2>=0);
    +}//QhullPoints Qhull dim
    +
    +QhullPoints::
    +QhullPoints(const Qhull &q, int pointDimension, countT coordinateCount2, coordT *c)
    +: point_first(c)
    +, point_end(c+coordinateCount2)
    +, qh_qh(q.qh())
    +, point_dimension(pointDimension)
    +{
    +    QHULL_ASSERT(pointDimension>=0);
    +    QHULL_ASSERT(coordinateCount2>=0);
    +}//QhullPoints Qhull dim coordT
    +
    +QhullPoints::
    +QhullPoints(QhullQh *qqh, int pointDimension, countT coordinateCount2, coordT *c)
    +: point_first(c)
    +, point_end(c+coordinateCount2)
    +, qh_qh(qqh)
    +, point_dimension(pointDimension)
    +{
    +    QHULL_ASSERT(pointDimension>=0);
    +    QHULL_ASSERT(coordinateCount2>=0);
    +}//QhullPoints QhullQh dim coordT
    +
    +#//!\name Conversions
    +// See qt-qhull.cpp for QList conversion
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullPoints::
    +toStdVector() const
    +{
    +    QhullPointsIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        vs.push_back(i.next());
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +countT QhullPoints::
    +extraCoordinatesCount() const
    +{
    +    if(point_dimension>0){
    +        return (countT)((point_end-point_first)%(size_t)point_dimension);
    +    }
    +    return 0;
    +}//extraCoordinatesCount
    +
    +//! QhullPoints is equal if the same address, or if the coordinates are identical
    +//! Use QhullPoint.operator==() for DISTround equality
    +bool QhullPoints::
    +operator==(const QhullPoints &other) const
    +{
    +    if((point_end-point_first) != (other.point_end-other.point_first)){
    +        return false;
    +    }
    +    if(point_dimension!=other.point_dimension){
    +        return false;
    +    }
    +    if(point_first==other.point_first){
    +        return true;
    +    }
    +    if(!qh_qh || qh_qh->hull_dim==0){
    +        const coordT *c= point_first;
    +        const coordT *c2= other.point_first;
    +        while(chull_dim : 0);
    +    point_first= 0;
    +    point_end= 0;
    +}//resetQhullQh
    +
    +QhullPoint QhullPoints::
    +value(countT idx) const
    +{
    +    QhullPoint p(qh_qh);
    +    if(idx>=0 && idx=0 && idx=n){
    +        n= 0;
    +    }else if(length<0 || idx+length>=n){
    +        n -= idx;
    +    }else{
    +        n -= idx+length;
    +    }
    +    return QhullPoints(qh_qh, point_dimension, n*point_dimension, point_first+idx*point_dimension);
    +}//mid
    +
    +#//!\name QhullPointsIterator
    +
    +bool QhullPointsIterator::
    +findNext(const QhullPoint &p)
    +{
    +    while(i!=ps->constEnd()){
    +        if(*i++ == p){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findNext
    +
    +bool QhullPointsIterator::
    +findPrevious(const QhullPoint &p)
    +{
    +    while(i!=ps->constBegin()){
    +        if(*--i == p){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findPrevious
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::ostream;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullPoints;
    +using orgQhull::QhullPointsIterator;
    +
    +ostream &
    +operator<<(ostream &os, const QhullPoints &p)
    +{
    +    QhullPointsIterator i(p);
    +    while(i.hasNext()){
    +        os << i.next();
    +    }
    +    return os;
    +}//operator<  // ptrdiff_t, size_t
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    class QhullPoints;          //!< One or more points Coordinate pointers with dimension and iterators
    +    class QhullPointsIterator;  //!< Java-style iterator
    +
    +//! QhullPoints are an array of QhullPoint as pointers into an array of coordinates.
    +//! For Qhull/QhullQh, QhullPoints must use hull_dim.  Can change QhullPoint to input_dim if needed for Delaunay input site
    +class QhullPoints {
    +
    +private:
    +#//!\name Fields
    +    coordT *            point_first; //!< First coordinate of an array of points of point_dimension
    +    coordT *            point_end;   //!< End of point coordinates (end>=first).  Trailing coordinates ignored
    +    QhullQh *           qh_qh;       //!< Maybe initialized NULL to allow ownership by RboxPoints
    +                                     //!< qh_qh used for QhullPoint() and qh_qh->hull_dim in constructor
    +    int                 point_dimension;  //!< Dimension, >=0
    +
    +public:
    +#//!\name Subtypes
    +    class const_iterator;
    +    class iterator;
    +    typedef QhullPoints::const_iterator ConstIterator;
    +    typedef QhullPoints::iterator       Iterator;
    +
    +#//!\name Construct
    +    //! QhullPoint, PointCoordinates, and QhullPoints have similar constructors
    +    //! If Qhull/QhullQh is not initialized, then QhullPoints.dimension() is zero unless explicitly set
    +    //! Cannot define QhullPoints(int pointDimension) since it is ambiguous with QhullPoints(QhullQh *qqh)
    +                        QhullPoints() : point_first(0), point_end(0), qh_qh(0), point_dimension(0) { }
    +                        QhullPoints(int pointDimension, countT coordinateCount2, coordT *c) : point_first(c), point_end(c+coordinateCount2), qh_qh(0), point_dimension(pointDimension) { QHULL_ASSERT(pointDimension>=0); }
    +    explicit            QhullPoints(const Qhull &q);
    +                        QhullPoints(const Qhull &q, countT coordinateCount2, coordT *c);
    +                        QhullPoints(const Qhull &q, int pointDimension, countT coordinateCount2, coordT *c);
    +    explicit            QhullPoints(QhullQh *qqh) : point_first(0), point_end(0), qh_qh(qqh), point_dimension(qqh ? qqh->hull_dim : 0) { }
    +                        QhullPoints(QhullQh *qqh, countT coordinateCount2, coordT *c) : point_first(c), point_end(c+coordinateCount2), qh_qh(qqh), point_dimension(qqh ? qqh->hull_dim : 0) { QHULL_ASSERT(qqh && qqh->hull_dim>0); }
    +                        QhullPoints(QhullQh *qqh, int pointDimension, countT coordinateCount2, coordT *c);
    +                        //! Copy constructor copies pointers but not contents.  Needed for return by value and parameter passing.
    +                        QhullPoints(const QhullPoints &other)  : point_first(other.point_first), point_end(other.point_end), qh_qh(other.qh_qh), point_dimension(other.point_dimension) {}
    +    QhullPoints &       operator=(const QhullPoints &other) { point_first= other.point_first; point_end= other.point_end; qh_qh= other.qh_qh; point_dimension= other.point_dimension; return *this; }
    +                        ~QhullPoints() {}
    +
    +public:
    +
    +#//!\name Conversion
    +
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList   toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +    // Constructs QhullPoint.  Cannot return reference.
    +    const QhullPoint    at(countT idx) const { /* point_first==0 caught by point_end assert */ coordT *p= point_first+idx*point_dimension; QHULL_ASSERT(p=0 && coordinatesCount>=0 && c!=0); point_first= c; point_end= c+coordinatesCount; point_dimension= pointDimension; }
    +    void                defineAs(countT coordinatesCount, coordT *c) { QHULL_ASSERT((point_dimension>0 && coordinatesCount>=0 && c!=0) || (c==0 && coordinatesCount==0)); point_first= c; point_end= c+coordinatesCount; }
    +    void                defineAs(const QhullPoints &other) { point_first= other.point_first; point_end= other.point_end; qh_qh= other.qh_qh; point_dimension= other.point_dimension; }
    +    int                 dimension() const { return point_dimension; }
    +    ConstIterator       end() const { return ConstIterator(qh_qh, point_dimension, point_end); }
    +    Iterator            end() { return Iterator(qh_qh, point_dimension, point_end); }
    +    coordT *            extraCoordinates() const { return extraCoordinatesCount() ? (point_end-extraCoordinatesCount()) : 0; }
    +    countT              extraCoordinatesCount() const;  // WARN64
    +    // Constructs QhullPoint.  Cannot return reference.
    +    const QhullPoint    first() const { return QhullPoint(qh_qh, point_dimension, point_first); }
    +    QhullPoint          first() { return QhullPoint(qh_qh, point_dimension, point_first); }
    +    // Constructs QhullPoint.  Cannot return reference.
    +    const QhullPoint    front() const { return first(); }
    +    QhullPoint          front() { return first(); }
    +    bool                includesCoordinates(const coordT *c) const { return c>=point_first && c(other)); return *this; }
    +
    +        // Need 'const QhullPoint' to maintain const
    +        const QhullPoint & operator*() const { return *this; }
    +        QhullPoint &    operator*() { return *this; }
    +        const QhullPoint * operator->() const { return this; }
    +        QhullPoint *    operator->() { return this; }
    +        // value instead of reference since advancePoint() modifies self
    +        QhullPoint      operator[](countT idx) const { QhullPoint result= *this; result.advancePoint(idx); return result; }
    +        bool            operator==(const iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return (point_coordinates==o.point_coordinates && point_dimension==o.point_dimension); }
    +        bool            operator!=(const iterator &o) const { return !operator==(o); }
    +        bool            operator<(const iterator &o) const  { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates < o.point_coordinates; }
    +        bool            operator<=(const iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates <= o.point_coordinates; }
    +        bool            operator>(const iterator &o) const  { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates > o.point_coordinates; }
    +        bool            operator>=(const iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates >= o.point_coordinates; }
    +        // reinterpret_cast to break circular dependency
    +        bool            operator==(const QhullPoints::const_iterator &o) const { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return (point_coordinates==reinterpret_cast(o).point_coordinates && point_dimension==reinterpret_cast(o).point_dimension); }
    +        bool            operator!=(const QhullPoints::const_iterator &o) const { return !operator==(reinterpret_cast(o)); }
    +        bool            operator<(const QhullPoints::const_iterator &o) const  { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return point_coordinates < reinterpret_cast(o).point_coordinates; }
    +        bool            operator<=(const QhullPoints::const_iterator &o) const { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return point_coordinates <= reinterpret_cast(o).point_coordinates; }
    +        bool            operator>(const QhullPoints::const_iterator &o) const  { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return point_coordinates > reinterpret_cast(o).point_coordinates; }
    +        bool            operator>=(const QhullPoints::const_iterator &o) const { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return point_coordinates >= reinterpret_cast(o).point_coordinates; }
    +        iterator &      operator++() { advancePoint(1); return *this; }
    +        iterator        operator++(int) { iterator n= *this; operator++(); return iterator(n); }
    +        iterator &      operator--() { advancePoint(-1); return *this; }
    +        iterator        operator--(int) { iterator n= *this; operator--(); return iterator(n); }
    +        iterator &      operator+=(countT idx) { advancePoint(idx); return *this; }
    +        iterator &      operator-=(countT idx) { advancePoint(-idx); return *this; }
    +        iterator        operator+(countT idx) const { iterator n= *this; n.advancePoint(idx); return n; }
    +        iterator        operator-(countT idx) const { iterator n= *this; n.advancePoint(-idx); return n; }
    +        difference_type operator-(iterator o) const { QHULL_ASSERT(qh_qh==o.qh_qh && point_dimension==o.point_dimension); return (point_dimension ? (point_coordinates-o.point_coordinates)/point_dimension : 0); }
    +    };//QhullPoints::iterator
    +
    +#//!\name QhullPoints::const_iterator
    +    //!\todo FIXUP QH11018 const_iterator same as iterator.  SHould have a common definition
    +    class const_iterator : public QhullPoint {
    +
    +    public:
    +        typedef std::random_access_iterator_tag  iterator_category;
    +        typedef QhullPoint          value_type;
    +        typedef const value_type *  pointer;
    +        typedef const value_type &  reference;
    +        typedef ptrdiff_t           difference_type;
    +
    +                        const_iterator(const QhullPoints::iterator &o) : QhullPoint(*o) {}
    +        explicit        const_iterator(const QhullPoints &ps) : QhullPoint(ps.qh(), ps.dimension(), ps.coordinates()) {}
    +                        const_iterator(const int pointDimension, coordT *c): QhullPoint(pointDimension, c) {}
    +                        const_iterator(const Qhull &q, coordT *c): QhullPoint(q, c) {}
    +                        const_iterator(const Qhull &q, int pointDimension, coordT *c): QhullPoint(q, pointDimension, c) {}
    +                        const_iterator(QhullQh *qqh, coordT *c): QhullPoint(qqh, c) {}
    +                        const_iterator(QhullQh *qqh, int pointDimension, coordT *c): QhullPoint(qqh, pointDimension, c) {}
    +                        const_iterator(const const_iterator &o) : QhullPoint(*o) {}
    +        const_iterator &operator=(const const_iterator &o) { defineAs(const_cast(o)); return *this; }
    +
    +        // value/non-const since advancePoint(1), etc. modifies self
    +        const QhullPoint & operator*() const { return *this; }
    +        const QhullPoint * operator->() const { return this; }
    +        // value instead of reference since advancePoint() modifies self
    +        const QhullPoint operator[](countT idx) const { QhullPoint n= *this; n.advancePoint(idx); return n; }
    +        bool            operator==(const const_iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return (point_coordinates==o.point_coordinates && point_dimension==o.point_dimension); }
    +        bool            operator!=(const const_iterator &o) const { return ! operator==(o); }
    +        bool            operator<(const const_iterator &o) const  { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates < o.point_coordinates; }
    +        bool            operator<=(const const_iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates <= o.point_coordinates; }
    +        bool            operator>(const const_iterator &o) const  { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates > o.point_coordinates; }
    +        bool            operator>=(const const_iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates >= o.point_coordinates; }
    +        const_iterator &operator++() { advancePoint(1); return *this; }
    +        const_iterator  operator++(int) { const_iterator n= *this; operator++(); return const_iterator(n); }
    +        const_iterator &operator--() { advancePoint(-1); return *this; }
    +        const_iterator  operator--(int) { const_iterator n= *this; operator--(); return const_iterator(n); }
    +        const_iterator &operator+=(countT idx) { advancePoint(idx); return *this; }
    +        const_iterator &operator-=(countT idx) { advancePoint(-idx); return *this; }
    +        const_iterator  operator+(countT idx) const { const_iterator n= *this; n.advancePoint(idx); return n; }
    +        const_iterator  operator-(countT idx) const { const_iterator n= *this; n.advancePoint(-idx); return n; }
    +        difference_type operator-(const_iterator o) const { QHULL_ASSERT(qh_qh==o.qh_qh && point_dimension==o.point_dimension); return (point_dimension ? (point_coordinates-o.point_coordinates)/point_dimension : 0); }
    +    };//QhullPoints::const_iterator
    +
    +#//!\name IO
    +    struct PrintPoints{
    +        const QhullPoints  *points;
    +        const char *    point_message;
    +        bool            with_identifier;
    +        PrintPoints(const char *message, bool withIdentifier, const QhullPoints &ps) : points(&ps), point_message(message), with_identifier(withIdentifier) {}
    +    };//PrintPoints
    +    PrintPoints          print(const char *message) const { return PrintPoints(message, false, *this); }
    +    PrintPoints          printWithIdentifier(const char *message) const { return PrintPoints(message, true, *this); }
    +};//QhullPoints
    +
    +// Instead of QHULL_DECLARE_SEQUENTIAL_ITERATOR because next(),etc would return a reference to a temporary
    +class QhullPointsIterator
    +{
    +    typedef QhullPoints::const_iterator const_iterator;
    +
    +#//!\name Fields
    +private:
    +    const QhullPoints  *ps;
    +    const_iterator      i;
    +
    +public:
    +                        QhullPointsIterator(const QhullPoints &other) : ps(&other), i(ps->constBegin()) {}
    +    QhullPointsIterator &operator=(const QhullPoints &other) { ps = &other; i = ps->constBegin(); return *this; }
    +
    +    bool                findNext(const QhullPoint &t);
    +    bool                findPrevious(const QhullPoint &t);
    +    bool                hasNext() const { return i != ps->constEnd(); }
    +    bool                hasPrevious() const { return i != ps->constBegin(); }
    +    QhullPoint          next() { return *i++; }
    +    QhullPoint          peekNext() const { return *i; }
    +    QhullPoint          peekPrevious() const { const_iterator p = i; return *--p; }
    +    QhullPoint          previous() { return *--i; }
    +    void                toBack() { i = ps->constEnd(); }
    +    void                toFront() { i = ps->constBegin(); }
    +};//QhullPointsIterator
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &          operator<<(std::ostream &os, const orgQhull::QhullPoints &p);
    +std::ostream &          operator<<(std::ostream &os, const orgQhull::QhullPoints::PrintPoints &pr);
    +
    +#endif // QHULLPOINTS_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullQh.cpp b/xs/src/qhull/src/libqhullcpp/QhullQh.cpp
    new file mode 100644
    index 0000000000..3635337001
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullQh.cpp
    @@ -0,0 +1,237 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullQh.cpp#5 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullQh -- Qhull's global data structure, qhT, as a C++ class
    +
    +
    +#include "libqhullcpp/QhullQh.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullStat.h"
    +
    +#include 
    +#include 
    +
    +#include 
    +
    +using std::cerr;
    +using std::string;
    +using std::vector;
    +using std::ostream;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Global variables
    +const double QhullQh::
    +default_factor_epsilon= 1.0;
    +
    +#//!\name Constructor, destructor, etc.
    +
    +//! Derived from qh_new_qhull[user.c]
    +QhullQh::
    +QhullQh()
    +: qhull_status(qh_ERRnone)
    +, qhull_message()
    +, error_stream(0)
    +, output_stream(0)
    +, factor_epsilon(QhullQh::default_factor_epsilon)
    +, use_output_stream(false)
    +{
    +    // NOerrors: TRY_QHULL_ not needed since these routines do not call qh_errexit()
    +    qh_meminit(this, NULL);
    +    qh_initstatistics(this);
    +    qh_initqhull_start2(this, NULL, NULL, qh_FILEstderr);  // Initialize qhT
    +    this->ISqhullQh= True;
    +}//QhullQh
    +
    +QhullQh::
    +~QhullQh()
    +{
    +    checkAndFreeQhullMemory();
    +}//~QhullQh
    +
    +#//!\name Methods
    +
    +//! Check memory for internal consistency
    +//! Free global memory used by qh_initbuild and qh_buildhull
    +//! Zero the qhT data structure, except for memory (qhmemT) and statistics (qhstatT)
    +//! Check and free short memory (e.g., facetT)
    +//! Zero the qhmemT data structure
    +void QhullQh::
    +checkAndFreeQhullMemory()
    +{
    +#ifdef qh_NOmem
    +    qh_freeqhull(this, qh_ALL);
    +#else
    +    qh_memcheck(this);
    +    qh_freeqhull(this, !qh_ALL);
    +    countT curlong;
    +    countT totlong;
    +    qh_memfreeshort(this, &curlong, &totlong);
    +    if (curlong || totlong)
    +        throw QhullError(10026, "Qhull error: qhull did not free %d bytes of long memory (%d pieces).", totlong, curlong);
    +#endif
    +}//checkAndFreeQhullMemory
    +
    +#//!\name Messaging
    +
    +void QhullQh::
    +appendQhullMessage(const string &s)
    +{
    +    if(output_stream && use_output_stream && this->USEstdout){ 
    +        *output_stream << s;
    +    }else if(error_stream){
    +        *error_stream << s;
    +    }else{
    +        qhull_message += s;
    +    }
    +}//appendQhullMessage
    +
    +//! clearQhullMessage does not throw errors (~Qhull)
    +void QhullQh::
    +clearQhullMessage()
    +{
    +    qhull_status= qh_ERRnone;
    +    qhull_message.clear();
    +    RoadError::clearGlobalLog();
    +}//clearQhullMessage
    +
    +//! hasQhullMessage does not throw errors (~Qhull)
    +bool QhullQh::
    +hasQhullMessage() const
    +{
    +    return (!qhull_message.empty() || qhull_status!=qh_ERRnone);
    +    //FIXUP QH11006 -- inconsistent usage with Rbox.  hasRboxMessage just tests rbox_status.  No appendRboxMessage()
    +}
    +
    +void QhullQh::
    +maybeThrowQhullMessage(int exitCode)
    +{
    +    if(!NOerrexit){
    +        if(qhull_message.size()>0){
    +            qhull_message.append("\n");
    +        }
    +        if(exitCode || qhull_status==qh_ERRnone){
    +            qhull_status= 10073;
    +        }else{
    +            qhull_message.append("QH10073: ");
    +        }
    +        qhull_message.append("Cannot call maybeThrowQhullMessage() from QH_TRY_().  Or missing 'qh->NOerrexit=true;' after QH_TRY_(){...}.");
    +    }
    +    if(qhull_status==qh_ERRnone){
    +        qhull_status= exitCode;
    +    }
    +    if(qhull_status!=qh_ERRnone){
    +        QhullError e(qhull_status, qhull_message);
    +        clearQhullMessage();
    +        throw e; // FIXUP QH11007: copy constructor is expensive if logging
    +    }
    +}//maybeThrowQhullMessage
    +
    +void QhullQh::
    +maybeThrowQhullMessage(int exitCode, int noThrow)  throw()
    +{
    +    QHULL_UNUSED(noThrow);
    +
    +    if(qhull_status==qh_ERRnone){
    +        qhull_status= exitCode;
    +    }
    +    if(qhull_status!=qh_ERRnone){
    +        QhullError e(qhull_status, qhull_message);
    +        e.logErrorLastResort();
    +    }
    +}//maybeThrowQhullMessage
    +
    +//! qhullMessage does not throw errors (~Qhull)
    +std::string QhullQh::
    +qhullMessage() const
    +{
    +    if(qhull_message.empty() && qhull_status!=qh_ERRnone){
    +        return "qhull: no message for error.  Check cerr or error stream\n";
    +    }else{
    +        return qhull_message;
    +    }
    +}//qhullMessage
    +
    +int QhullQh::
    +qhullStatus() const
    +{
    +    return qhull_status;
    +}//qhullStatus
    +
    +void QhullQh::
    +setErrorStream(ostream *os)
    +{
    +    error_stream= os;
    +}//setErrorStream
    +
    +//! Updates use_output_stream
    +void QhullQh::
    +setOutputStream(ostream *os)
    +{
    +    output_stream= os;
    +    use_output_stream= (os!=0);
    +}//setOutputStream
    +
    +}//namespace orgQhull
    +
    +/*---------------------------------
    +
    +  qh_fprintf(qhT *qh, fp, msgcode, format, list of args )
    +    replaces qh_fprintf() in userprintf_r.c
    +
    +notes:
    +    only called from libqhull
    +    same as fprintf() and RboxPoints.qh_fprintf_rbox()
    +    fgets() is not trapped like fprintf()
    +    Do not throw errors from here.  Use qh_errexit;
    +*/
    +extern "C"
    +void qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    using namespace orgQhull;
    +
    +    if(!qh->ISqhullQh){
    +        qh_fprintf_stderr(10025, "Qhull error: qh_fprintf called from a Qhull instance without QhullQh defined\n");
    +        qh_exit(10025);
    +    }
    +    QhullQh *qhullQh= static_cast(qh);
    +    va_start(args, fmt);
    +    if(msgcode=MSG_ERROR && msgcodeqhull_statusqhull_status>=MSG_WARNING){
    +                qhullQh->qhull_status= msgcode;
    +            }
    +        }
    +        char newMessage[MSG_MAXLEN];
    +        // RoadError will add the message tag
    +        vsnprintf(newMessage, sizeof(newMessage), fmt, args);
    +        qhullQh->appendQhullMessage(newMessage);
    +        va_end(args);
    +        return;
    +    }
    +    if(qhullQh->output_stream && qhullQh->use_output_stream){
    +        char newMessage[MSG_MAXLEN];
    +        vsnprintf(newMessage, sizeof(newMessage), fmt, args);
    +        *qhullQh->output_stream << newMessage;
    +        va_end(args);
    +        return;
    +    }
    +    // FIXUP QH11008: how do users trap messages and handle input?  A callback?
    +    char newMessage[MSG_MAXLEN];
    +    vsnprintf(newMessage, sizeof(newMessage), fmt, args);
    +    qhullQh->appendQhullMessage(newMessage);
    +    va_end(args);
    +} /* qh_fprintf */
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullQh.h b/xs/src/qhull/src/libqhullcpp/QhullQh.h
    new file mode 100644
    index 0000000000..c3b277ff0f
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullQh.h
    @@ -0,0 +1,110 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullQh.h#2 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLQH_H
    +#define QHULLQH_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +
    +#include 
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  /* interaction between '_setjmp' and C++ object destruction is non-portable */
    +/* setjmp should not be implemented with 'catch' */
    +#endif
    +
    +//! Use QH_TRY_ or QH_TRY_NOTHROW_ to call a libqhull_r routine that may invoke qh_errexit()
    +//! QH_TRY_(qh){...} qh->NOerrexit=true;
    +//! No object creation -- longjmp() skips object destructors
    +//! To test for error when done -- qh->maybeThrowQhullMessage(QH_TRY_status);
    +//! Use the same compiler for QH_TRY_, libqhullcpp, and libqhull_r.  setjmp() is not portable between compilers.
    +
    +#define QH_TRY_ERROR 10071
    +
    +#define QH_TRY_(qh) \
    +    int QH_TRY_status; \
    +    if(qh->NOerrexit){ \
    +        qh->NOerrexit= False; \
    +        QH_TRY_status= setjmp(qh->errexit); \
    +    }else{ \
    +        throw QhullError(QH_TRY_ERROR, "Cannot invoke QH_TRY_() from inside a QH_TRY_.  Or missing 'qh->NOerrexit=true' after previously called QH_TRY_(qh){...}"); \
    +    } \
    +    if(!QH_TRY_status)
    +
    +#define QH_TRY_NO_THROW_(qh) \
    +    int QH_TRY_status; \
    +    if(qh->NOerrexit){ \
    +        qh->NOerrexit= False; \
    +        QH_TRY_status= setjmp(qh->errexit); \
    +    }else{ \
    +        QH_TRY_status= QH_TRY_ERROR; \
    +    } \
    +    if(!QH_TRY_status)
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! QhullQh -- Qhull's global data structure, qhT, as a C++ class
    +    class QhullQh;
    +
    +//! POD type equivalent to qhT.  No virtual members
    +class QhullQh : public qhT {
    +
    +#//!\name Constants
    +
    +#//!\name Fields
    +private:
    +    int                 qhull_status;   //!< qh_ERRnone if valid
    +    std::string         qhull_message;  //!< Returned messages from libqhull_r
    +    std::ostream *      error_stream;   //!< overrides errorMessage, use appendQhullMessage()
    +    std::ostream *      output_stream;  //!< send output to stream
    +    double              factor_epsilon; //!< Factor to increase ANGLEround and DISTround for hyperplane equality
    +    bool                use_output_stream; //!< True if using output_stream
    +
    +    friend void         ::qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +
    +    static const double default_factor_epsilon;  //!< Default factor_epsilon is 1.0, never updated
    +
    +#//!\name Constructors
    +public:
    +                        QhullQh();
    +                        ~QhullQh();
    +private:
    +                        //!disable copy constructor and assignment
    +                        QhullQh(const QhullQh &);
    +    QhullQh &           operator=(const QhullQh &);
    +public:
    +
    +#//!\name GetSet
    +    double              factorEpsilon() const { return factor_epsilon; }
    +    void                setFactorEpsilon(double a) { factor_epsilon= a; }
    +    void                disableOutputStream() { use_output_stream= false; }
    +    void                enableOutputStream() { use_output_stream= true; }
    +
    +#//!\name Messaging
    +    void                appendQhullMessage(const std::string &s);
    +    void                clearQhullMessage();
    +    std::string         qhullMessage() const;
    +    bool                hasOutputStream() const { return use_output_stream; }
    +    bool                hasQhullMessage() const;
    +    void                maybeThrowQhullMessage(int exitCode);
    +    void                maybeThrowQhullMessage(int exitCode, int noThrow) throw();
    +    int                 qhullStatus() const;
    +    void                setErrorStream(std::ostream *os);
    +    void                setOutputStream(std::ostream *os);
    +
    +#//!\name Methods
    +    double              angleEpsilon() const { return this->ANGLEround*factor_epsilon; } //!< Epsilon for hyperplane angle equality
    +    void                checkAndFreeQhullMemory();
    +    double              distanceEpsilon() const { return this->DISTround*factor_epsilon; } //!< Epsilon for distance to hyperplane
    +
    +};//class QhullQh
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLQH_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullRidge.cpp b/xs/src/qhull/src/libqhullcpp/QhullRidge.cpp
    new file mode 100644
    index 0000000000..7a01812805
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullRidge.cpp
    @@ -0,0 +1,124 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullRidge.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullRidge -- Qhull's ridge structure, ridgeT, as a C++ class
    +
    +#include "libqhullcpp/QhullRidge.h"
    +
    +#include "libqhullcpp/QhullSets.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class objects
    +ridgeT QhullRidge::
    +s_empty_ridge= {0,0,0,0,0,
    +                0,0};
    +
    +#//!\name Constructors
    +
    +QhullRidge::QhullRidge(const Qhull &q)
    +: qh_ridge(&s_empty_ridge)
    +, qh_qh(q.qh())
    +{
    +}//Default
    +
    +QhullRidge::QhullRidge(const Qhull &q, ridgeT *r)
    +: qh_ridge(r ? r : &s_empty_ridge)
    +, qh_qh(q.qh())
    +{
    +}//ridgeT
    +
    +#//!\name foreach
    +
    +//! Return True if nextRidge3d
    +//! Simplicial facets may have incomplete ridgeSets
    +//! Does not use qh_errexit()
    +bool QhullRidge::
    +hasNextRidge3d(const QhullFacet &f) const
    +{
    +    if(!qh_qh){
    +        return false;
    +    }
    +    vertexT *v= 0;
    +    // Does not call qh_errexit(), TRY_QHULL_ not needed
    +    ridgeT *ridge= qh_nextridge3d(getRidgeT(), f.getFacetT(), &v);
    +    return (ridge!=0);
    +}//hasNextRidge3d
    +
    +//! Return next ridge and optional vertex for a 3d facet and ridge
    +//! Does not use qh_errexit()
    +QhullRidge QhullRidge::
    +nextRidge3d(const QhullFacet &f, QhullVertex *nextVertex) const
    +{
    +    vertexT *v= 0;
    +    ridgeT *ridge= 0;
    +    if(qh_qh){
    +        // Does not call qh_errexit(), TRY_QHULL_ not needed
    +        ridge= qh_nextridge3d(getRidgeT(), f.getFacetT(), &v);
    +        if(!ridge){
    +            throw QhullError(10030, "Qhull error nextRidge3d:  missing next ridge for facet %d ridge %d.  Does facet contain ridge?", f.id(), id());
    +        }
    +    }
    +    if(nextVertex!=0){
    +        *nextVertex= QhullVertex(qh_qh, v);
    +    }
    +    return QhullRidge(qh_qh, ridge);
    +}//nextRidge3d
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullRidge;
    +using orgQhull::QhullVertex;
    +
    +ostream &
    +operator<<(ostream &os, const QhullRidge &r)
    +{
    +    os << r.print("");
    +    return os;
    +}//<< QhullRidge
    +
    +//! Duplicate of qh_printridge [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullRidge::PrintRidge &pr)
    +{
    +    if(*pr.print_message){
    +        os << pr.print_message << " ";
    +    }else{
    +        os << "     - ";
    +    }
    +    QhullRidge r= *pr.ridge;
    +    os << "r" << r.id();
    +    if(r.getRidgeT()->tested){
    +        os << " tested";
    +    }
    +    if(r.getRidgeT()->nonconvex){
    +        os << " nonconvex";
    +    }
    +    os << endl;
    +    os << r.vertices().print("           vertices:");
    +    if(r.getRidgeT()->top && r.getRidgeT()->bottom){
    +        os << "           between f" << r.topFacet().id() << " and f" << r.bottomFacet().id() << endl;
    +    }else if(r.getRidgeT()->top){
    +        os << "           top f" << r.topFacet().id() << endl;
    +    }else if(r.getRidgeT()->bottom){
    +        os << "           bottom f" << r.bottomFacet().id() << endl;
    +    }
    +
    +    return os;
    +}//<< PrintRidge
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullRidge.h b/xs/src/qhull/src/libqhullcpp/QhullRidge.h
    new file mode 100644
    index 0000000000..924340fb09
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullRidge.h
    @@ -0,0 +1,112 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullRidge.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLRIDGE_H
    +#define QHULLRIDGE_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullSet.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/QhullVertexSet.h"
    +#include "libqhullcpp/QhullFacet.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +    class QhullVertex;
    +    class QhullVertexSet;
    +    class QhullFacet;
    +
    +#//!\name Defined here
    +    //! QhullRidge -- Qhull's ridge structure, ridgeT [libqhull.h], as a C++ class
    +    class QhullRidge;
    +    typedef QhullSet  QhullRidgeSet;
    +    typedef QhullSetIterator  QhullRidgeSetIterator;
    +    // see QhullSets.h for QhullRidgeSet and QhullRidgeSetIterator -- avoids circular references
    +
    +/************************
    +a ridge is hull_dim-1 simplex between two neighboring facets.  If the
    +facets are non-simplicial, there may be more than one ridge between
    +two facets.  E.G. a 4-d hypercube has two triangles between each pair
    +of neighboring facets.
    +
    +topological information:
    +    vertices            a set of vertices
    +    top,bottom          neighboring facets with orientation
    +
    +geometric information:
    +    tested              True if ridge is clearly convex
    +    nonconvex           True if ridge is non-convex
    +*/
    +
    +class QhullRidge {
    +
    +#//!\name Defined here
    +public:
    +    typedef ridgeT *   base_type;  // for QhullRidgeSet
    +
    +#//!\name Fields
    +private:
    +    ridgeT *            qh_ridge;  //!< Corresponding ridgeT, never 0
    +    QhullQh *           qh_qh;     //!< QhullQh/qhT for ridgeT, may be 0
    +
    +#//!\name Class objects
    +    static ridgeT       s_empty_ridge;
    +
    +public:
    +#//!\name Constants
    +
    +#//!\name Constructors
    +                        QhullRidge() : qh_ridge(&s_empty_ridge), qh_qh(0) {}
    +    explicit            QhullRidge(const Qhull &q);
    +                        QhullRidge(const Qhull &q, ridgeT *r);
    +    explicit            QhullRidge(QhullQh *qqh) : qh_ridge(&s_empty_ridge), qh_qh(qqh) {}
    +                        QhullRidge(QhullQh *qqh, ridgeT *r) : qh_ridge(r ? r : &s_empty_ridge), qh_qh(qqh) {}
    +                        // Creates an alias.  Does not copy QhullRidge.  Needed for return by value and parameter passing
    +                        QhullRidge(const QhullRidge &other) : qh_ridge(other.qh_ridge), qh_qh(other.qh_qh) {}
    +                        // Creates an alias.  Does not copy QhullRidge.  Needed for vector
    +    QhullRidge &        operator=(const QhullRidge &other) { qh_ridge= other.qh_ridge; qh_qh= other.qh_qh; return *this; }
    +                        ~QhullRidge() {}
    +
    +#//!\name GetSet
    +    QhullFacet          bottomFacet() const { return QhullFacet(qh_qh, qh_ridge->bottom); }
    +    int                 dimension() const { return ((qh_qh && qh_qh->hull_dim) ? qh_qh->hull_dim-1 : 0); }
    +    ridgeT *            getBaseT() const { return getRidgeT(); } //!< For QhullSet
    +    ridgeT *            getRidgeT() const { return qh_ridge; }
    +    countT              id() const { return qh_ridge->id; }
    +    bool                isValid() const { return (qh_qh && qh_ridge != &s_empty_ridge); }
    +    bool                operator==(const QhullRidge &other) const { return qh_ridge==other.qh_ridge; }
    +    bool                operator!=(const QhullRidge &other) const { return !operator==(other); }
    +    QhullFacet          otherFacet(const QhullFacet &f) const { return QhullFacet(qh_qh, (qh_ridge->top==f.getFacetT() ? qh_ridge->bottom : qh_ridge->top)); }
    +    QhullFacet          topFacet() const { return QhullFacet(qh_qh, qh_ridge->top); }
    +
    +#//!\name foreach
    +    bool                hasNextRidge3d(const QhullFacet &f) const;
    +    QhullRidge          nextRidge3d(const QhullFacet &f) const { return nextRidge3d(f, 0); }
    +    QhullRidge          nextRidge3d(const QhullFacet &f, QhullVertex *nextVertex) const;
    +    QhullVertexSet      vertices() const { return QhullVertexSet(qh_qh, qh_ridge->vertices); }
    +
    +#//!\name IO
    +
    +    struct PrintRidge{
    +        const QhullRidge *ridge;
    +        const char *    print_message;    //!< non-null message
    +                        PrintRidge(const char *message, const QhullRidge &r) : ridge(&r), print_message(message) {}
    +    };//PrintRidge
    +    PrintRidge          print(const char* message) const { return PrintRidge(message, *this); }
    +};//class QhullRidge
    +
    +}//namespace orgQhull
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullRidge &r);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullRidge::PrintRidge &pr);
    +
    +#endif // QHULLRIDGE_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullSet.cpp b/xs/src/qhull/src/libqhullcpp/QhullSet.cpp
    new file mode 100644
    index 0000000000..dfdc3c51f3
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullSet.cpp
    @@ -0,0 +1,62 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullSet.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullSet -- Qhull's set structure, setT, as a C++ class
    +
    +#include "libqhullcpp/QhullSet.h"
    +
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullError.h"
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class objects
    +
    +setT QhullSetBase::
    +s_empty_set;
    +
    +#//!\name Constructors
    +
    +QhullSetBase::
    +QhullSetBase(const Qhull &q, setT *s) 
    +: qh_set(s ? s : &s_empty_set)
    +, qh_qh(q.qh())
    +{
    +}
    +
    +#//!\name Class methods
    +
    +// Same code for qh_setsize [qset_r.c] and QhullSetBase::count [static]
    +countT QhullSetBase::
    +count(const setT *set)
    +{
    +    countT size;
    +    const setelemT *sizep;
    +
    +    if (!set){
    +        return(0);
    +    }
    +    sizep= SETsizeaddr_(set);
    +    if ((size= sizep->i)) {
    +        size--;
    +        if (size > set->maxsize) {
    +            // FIXUP QH11022 How to add additional output to a error? -- qh_setprint(qhmem.ferr, "set: ", set);
    +            throw QhullError(10032, "QhullSet internal error: current set size %d is greater than maximum size %d\n",
    +                size, set->maxsize);
    +        }
    +    }else{
    +        size= set->maxsize;
    +    }
    +    return size;
    +}//count
    +
    +}//namespace orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullSet.h b/xs/src/qhull/src/libqhullcpp/QhullSet.h
    new file mode 100644
    index 0000000000..afb6b51d9f
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullSet.h
    @@ -0,0 +1,462 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullSet.h#6 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QhullSet_H
    +#define QhullSet_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullQh.h"
    +
    +#include   // ptrdiff_t, size_t
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#endif
    +
    +#ifdef QHULL_USES_QT
    + #include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +
    +#//!\name Defined here
    +    class QhullSetBase;  //! Base class for QhullSet
    +    //! QhullSet defined below
    +    //! QhullSetIterator defined below
    +    //! \see QhullPointSet, QhullLinkedList
    +
    +//! QhullSetBase is a wrapper for Qhull's setT of void* pointers
    +//! \see libqhull_r/qset.h
    +class QhullSetBase {
    +
    +private:
    +#//!\name Fields --
    +    setT *              qh_set;
    +    QhullQh *           qh_qh;             //! Provides access to setT memory allocator
    +
    +#//!\name Class objects
    +    static setT         s_empty_set;  //! Used if setT* is NULL
    +
    +public:
    +#//!\name Constructors
    +                        QhullSetBase(const Qhull &q, setT *s);
    +                        QhullSetBase(QhullQh *qqh, setT *s) : qh_set(s ? s : &s_empty_set), qh_qh(qqh) {}
    +                        //! Copy constructor copies the pointer but not the set.  Needed for return by value and parameter passing.
    +                        QhullSetBase(const QhullSetBase &other) : qh_set(other.qh_set), qh_qh(other.qh_qh) {}
    +    QhullSetBase &      operator=(const QhullSetBase &other) { qh_set= other.qh_set; qh_qh= other.qh_qh; return *this; }
    +                        ~QhullSetBase() {}
    +
    +private:
    +                        //!disabled since memory allocation for QhullSet not defined
    +                        QhullSetBase() {}
    +public:
    +
    +#//!\name GetSet
    +    countT              count() const { return QhullSetBase::count(qh_set); }
    +    void                defineAs(setT *s) { qh_set= s ? s : &s_empty_set; } //!< Not type-safe since setT may contain any type
    +    void                forceEmpty() { qh_set= &s_empty_set; }
    +    setT *              getSetT() const { return qh_set; }
    +    bool                isEmpty() const { return SETempty_(qh_set); }
    +    QhullQh *           qh() const { return qh_qh; }
    +    setT **             referenceSetT() { return &qh_set; }
    +    size_t              size() const { return QhullSetBase::count(qh_set); }
    +
    +#//!\name Element
    +protected:
    +    void **             beginPointer() const { return &qh_set->e[0].p; }
    +    void **             elementPointer(countT idx) const { QHULL_ASSERT(idx>=0 && idxmaxsize); return &SETelem_(qh_set, idx); }
    +                        //! Always points to 0
    +    void **             endPointer() const { return qh_setendpointer(qh_set); }
    +
    +#//!\name Class methods
    +public:
    +    static countT       count(const setT *set);
    +    //s may be null
    +    static bool         isEmpty(const setT *s) { return SETempty_(s); }
    +};//QhullSetBase
    +
    +
    +//! QhullSet -- A read-only wrapper to Qhull's collection class, setT.
    +//!  QhullSet is similar to STL's  and Qt's QVector
    +//!  QhullSet is unrelated to STL and Qt's set and map types (e.g., QSet and QMap)
    +//!  T is a Qhull type that defines 'base_type' and getBaseT() (e.g., QhullFacet with base_type 'facetT *'
    +//!  A QhullSet does not own its contents -- erase(), clear(), removeFirst(), removeLast(), pop_back(), pop_front(), fromStdList() not defined
    +//!  QhullSetIterator is faster than STL-style iterator/const_iterator
    +//!  Qhull's FOREACHelement_() [qset_r.h] maybe more efficient than QhullSet.  It uses a NULL terminator instead of an end pointer.  STL requires an end pointer.
    +//!  Derived from QhullLinkedList.h and Qt/core/tools/qlist.h w/o QT_STRICT_ITERATORS
    +template 
    +class QhullSet : public QhullSetBase {
    +
    +private:
    +#//!\name Fields -- see QhullSetBase
    +
    +#//!\name Class objects
    +    static setT         s_empty_set;  //! Workaround for no setT allocator.  Used if setT* is NULL
    +
    +public:
    +#//!\name Defined here
    +    class iterator;
    +    class const_iterator;
    +    typedef typename QhullSet::iterator Iterator;
    +    typedef typename QhullSet::const_iterator ConstIterator;
    +
    +#//!\name Constructors
    +                        QhullSet(const Qhull &q, setT *s) : QhullSetBase(q, s) { }
    +                        QhullSet(QhullQh *qqh, setT *s) : QhullSetBase(qqh, s) { }
    +                        //Conversion from setT* is not type-safe.  Implicit conversion for void* to T
    +                        //Copy constructor copies pointer but not contents.  Needed for return by value.
    +                        QhullSet(const QhullSet &other) : QhullSetBase(other) {}
    +    QhullSet &       operator=(const QhullSet &other) { QhullSetBase::operator=(other); return *this; }
    +                        ~QhullSet() {}
    +
    +private:
    +                        //!Disable default constructor.  See QhullSetBase
    +                        QhullSet();
    +public:
    +
    +#//!\name Conversion
    +
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif
    +#ifdef QHULL_USES_QT
    +    QList toQList() const;
    +#endif
    +
    +#//!\name GetSet -- see QhullSetBase for count(), empty(), isEmpty(), size()
    +    using QhullSetBase::count;
    +    using QhullSetBase::isEmpty;
    +    // operator== defined for QhullSets of the same type
    +    bool                operator==(const QhullSet &other) const { return qh_setequal(getSetT(), other.getSetT()); }
    +    bool                operator!=(const QhullSet &other) const { return !operator==(other); }
    +
    +#//!\name Element access
    +    // Constructs T.  Cannot return reference.
    +    const T             at(countT idx) const { return operator[](idx); }
    +    // Constructs T.  Cannot return reference.
    +    const T             back() const { return last(); }
    +    T                   back() { return last(); }
    +    //! end element is NULL
    +    const typename T::base_type * constData() const { return reinterpret_cast(beginPointer()); }
    +    typename T::base_type *     data() { return reinterpret_cast(beginPointer()); }
    +    const typename T::base_type *data() const { return reinterpret_cast(beginPointer()); }
    +    typename T::base_type *     endData() { return reinterpret_cast(endPointer()); }
    +    const typename T::base_type * endData() const { return reinterpret_cast(endPointer()); }
    +    // Constructs T.  Cannot return reference.
    +    const T             first() const { QHULL_ASSERT(!isEmpty()); return T(qh(), *data()); }
    +    T                   first() { QHULL_ASSERT(!isEmpty()); return T(qh(), *data()); }
    +    // Constructs T.  Cannot return reference.
    +    const T             front() const { return first(); }
    +    T                   front() { return first(); }
    +    // Constructs T.  Cannot return reference.
    +    const T             last() const { QHULL_ASSERT(!isEmpty()); return T(qh(), *(endData()-1)); }
    +    T                   last() { QHULL_ASSERT(!isEmpty()); return T(qh(), *(endData()-1)); }
    +    // mid() not available.  No setT constructor
    +    // Constructs T.  Cannot return reference.
    +    const T             operator[](countT idx) const { const typename T::base_type *p= reinterpret_cast(elementPointer(idx)); QHULL_ASSERT(idx>=0 && p < endData()); return T(qh(), *p); }
    +    T                   operator[](countT idx) { typename T::base_type *p= reinterpret_cast(elementPointer(idx)); QHULL_ASSERT(idx>=0 && p < endData()); return T(qh(), *p); }
    +    const T             second() const { return operator[](1); }
    +    T                   second() { return operator[](1); }
    +    T                   value(countT idx) const;
    +    T                   value(countT idx, const T &defaultValue) const;
    +
    +#//!\name Read-write -- Not available, no setT constructor
    +
    +#//!\name iterator
    +    iterator            begin() { return iterator(qh(), reinterpret_cast(beginPointer())); }
    +    const_iterator      begin() const { return const_iterator(qh(), data()); }
    +    const_iterator      constBegin() const { return const_iterator(qh(), data()); }
    +    const_iterator      constEnd() const { return const_iterator(qh(), endData()); }
    +    iterator            end() { return iterator(qh(), endData()); }
    +    const_iterator      end() const { return const_iterator(qh(), endData()); }
    +
    +#//!\name Search
    +    bool                contains(const T &t) const;
    +    countT              count(const T &t) const;
    +    countT              indexOf(const T &t) const { /* no qh_qh */ return qh_setindex(getSetT(), t.getBaseT()); }
    +    countT              lastIndexOf(const T &t) const;
    +
    +    // before const_iterator for conversion with comparison operators
    +    class iterator {
    +        friend class const_iterator;
    +    private:
    +        typename T::base_type *  i;  // e.g., facetT**, first for debugger
    +        QhullQh *       qh_qh;
    +
    +    public:
    +        typedef ptrdiff_t       difference_type;
    +        typedef std::bidirectional_iterator_tag  iterator_category;
    +        typedef T               value_type;
    +
    +                        iterator(QhullQh *qqh, typename T::base_type *p) : i(p), qh_qh(qqh) {}
    +                        iterator(const iterator &o) : i(o.i), qh_qh(o.qh_qh) {}
    +        iterator &      operator=(const iterator &o) { i= o.i; qh_qh= o.qh_qh; return *this; }
    +
    +        // Constructs T.  Cannot return reference.  
    +        T               operator*() const { return T(qh_qh, *i); }
    +        //operator->() n/a, value-type
    +        // Constructs T.  Cannot return reference.  
    +        T               operator[](countT idx) const { return T(qh_qh, *(i+idx)); } //!< No error checking
    +        bool            operator==(const iterator &o) const { return i == o.i; }
    +        bool            operator!=(const iterator &o) const { return !operator==(o); }
    +        bool            operator==(const const_iterator &o) const { return (i==reinterpret_cast(o).i); }
    +        bool            operator!=(const const_iterator &o) const { return !operator==(o); }
    +
    +        //! Assumes same point set
    +        countT          operator-(const iterator &o) const { return (countT)(i-o.i); } //WARN64
    +        bool            operator>(const iterator &o) const { return i>o.i; }
    +        bool            operator<=(const iterator &o) const { return !operator>(o); }
    +        bool            operator<(const iterator &o) const { return i=(const iterator &o) const { return !operator<(o); }
    +        bool            operator>(const const_iterator &o) const { return (i > reinterpret_cast(o).i); }
    +        bool            operator<=(const const_iterator &o) const { return !operator>(o); }
    +        bool            operator<(const const_iterator &o) const { return (i < reinterpret_cast(o).i); }
    +        bool            operator>=(const const_iterator &o) const { return !operator<(o); }
    +
    +        //! No error checking
    +        iterator &      operator++() { ++i; return *this; }
    +        iterator        operator++(int) { iterator o= *this; ++i; return o; }
    +        iterator &      operator--() { --i; return *this; }
    +        iterator        operator--(int) { iterator o= *this; --i; return o; }
    +        iterator        operator+(countT j) const { return iterator(qh_qh, i+j); }
    +        iterator        operator-(countT j) const { return operator+(-j); }
    +        iterator &      operator+=(countT j) { i += j; return *this; }
    +        iterator &      operator-=(countT j) { i -= j; return *this; }
    +    };//QhullPointSet::iterator
    +
    +    class const_iterator {
    +    private:
    +        const typename T::base_type *  i;  // e.g., const facetT**, first for debugger
    +        QhullQh *       qh_qh;
    +
    +    public:
    +        typedef ptrdiff_t       difference_type;
    +        typedef std::random_access_iterator_tag  iterator_category;
    +        typedef T               value_type;
    +
    +                        const_iterator(QhullQh *qqh, const typename T::base_type * p) : i(p), qh_qh(qqh) {}
    +                        const_iterator(const const_iterator &o) : i(o.i), qh_qh(o.qh_qh) {}
    +                        const_iterator(const iterator &o) : i(o.i), qh_qh(o.qh_qh) {}
    +        const_iterator &operator=(const const_iterator &o) { i= o.i; qh_qh= o.qh_qh; return *this; }
    +
    +        // Constructs T.  Cannot return reference.  Retaining 'const T' return type for consistency with QList/QVector
    +        const T         operator*() const { return T(qh_qh, *i); }
    +        const T         operator[](countT idx) const { return T(qh_qh, *(i+idx)); }  //!< No error checking
    +        //operator->() n/a, value-type
    +        bool            operator==(const const_iterator &o) const { return i == o.i; }
    +        bool            operator!=(const const_iterator &o) const { return !operator==(o); }
    +
    +        //! Assumes same point set
    +        countT          operator-(const const_iterator &o) { return (countT)(i-o.i); } //WARN64
    +        bool            operator>(const const_iterator &o) const { return i>o.i; }
    +        bool            operator<=(const const_iterator &o) const { return !operator>(o); }
    +        bool            operator<(const const_iterator &o) const { return i=(const const_iterator &o) const { return !operator<(o); }
    +
    +        //!< No error checking
    +        const_iterator &operator++() { ++i; return *this; }
    +        const_iterator  operator++(int) { const_iterator o= *this; ++i; return o; }
    +        const_iterator &operator--() { --i; return *this; }
    +        const_iterator  operator--(int) { const_iterator o= *this; --i; return o; }
    +        const_iterator  operator+(int j) const { return const_iterator(qh_qh, i+j); }
    +        const_iterator  operator-(int j) const { return operator+(-j); }
    +        const_iterator &operator+=(int j) { i += j; return *this; }
    +        const_iterator &operator-=(int j) { i -= j; return *this; }
    +    };//QhullPointSet::const_iterator
    +
    +};//class QhullSet
    +
    +
    +//! Faster then interator/const_iterator due to T::base_type
    +template 
    +class QhullSetIterator {
    +
    +#//!\name Subtypes
    +    typedef typename QhullSet::const_iterator const_iterator;
    +
    +private:
    +#//!\name Fields
    +    const typename T::base_type *  i;  // e.g., facetT**, first for debugger
    +    const typename T::base_type *  begin_i;  // must be initialized after i
    +    const typename T::base_type *  end_i;
    +    QhullQh *                qh_qh;
    +
    +public:
    +#//!\name Constructors
    +                        QhullSetIterator(const QhullSet &s) : i(s.data()), begin_i(i), end_i(s.endData()), qh_qh(s.qh()) {}
    +                        QhullSetIterator(const QhullSetIterator &o) : i(o.i), begin_i(o.begin_i), end_i(o.end_i), qh_qh(o.qh_qh) {}
    +    QhullSetIterator &operator=(const QhullSetIterator &o) { i= o.i; begin_i= o.begin_i; end_i= o.end_i; qh_qh= o.qh_qh; return *this; }
    +
    +#//!\name ReadOnly
    +    countT              countRemaining() { return (countT)(end_i-i); } // WARN64
    +
    +#//!\name Search
    +    bool                findNext(const T &t);
    +    bool                findPrevious(const T &t);
    +
    +#//!\name Foreach
    +    bool                hasNext() const { return i != end_i; }
    +    bool                hasPrevious() const { return i != begin_i; }
    +    T                   next() { return T(qh_qh, *i++); }
    +    T                   peekNext() const { return T(qh_qh, *i); }
    +    T                   peekPrevious() const { const typename T::base_type *p = i; return T(qh_qh, *--p); }
    +    T                   previous() { return T(qh_qh, *--i); }
    +    void                toBack() { i = end_i; }
    +    void                toFront() { i = begin_i; }
    +};//class QhullSetIterator
    +
    +#//!\name == Definitions =========================================
    +
    +#//!\name Conversions
    +
    +// See qt-qhull.cpp for QList conversion
    +
    +#ifndef QHULL_NO_STL
    +template 
    +std::vector QhullSet::
    +toStdVector() const
    +{
    +	typename QhullSet::const_iterator i = begin();
    +	typename QhullSet::const_iterator e = end();
    +    std::vector vs;
    +    while(i!=e){
    +        vs.push_back(*i++);
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#ifdef QHULL_USES_QT
    +template 
    +QList QhullSet::
    +toQList() const
    +{
    +    QhullSet::const_iterator i= begin();
    +    QhullSet::const_iterator e= end();
    +    QList vs;
    +    while(i!=e){
    +        vs.append(*i++);
    +    }
    +    return vs;
    +}//toQList
    +#endif
    +
    +#//!\name Element
    +
    +template 
    +T QhullSet::
    +value(countT idx) const
    +{
    +    // Avoid call to qh_setsize() and assert in elementPointer()
    +    const typename T::base_type *p= reinterpret_cast(&SETelem_(getSetT(), idx));
    +    return (idx>=0 && p
    +T QhullSet::
    +value(countT idx, const T &defaultValue) const
    +{
    +    // Avoid call to qh_setsize() and assert in elementPointer()
    +    const typename T::base_type *p= reinterpret_cast(&SETelem_(getSetT(), idx));
    +    return (idx>=0 && p
    +bool QhullSet::
    +contains(const T &t) const
    +{
    +    setT *s= getSetT();
    +    void *p= t.getBaseT();  // contains() is not inline for better error reporting
    +    int result= qh_setin(s, p);
    +    return result!=0;
    +}//contains
    +
    +template 
    +countT QhullSet::
    +count(const T &t) const
    +{
    +    countT n= 0;
    +    const typename T::base_type *i= data();
    +    const typename T::base_type *e= endData();
    +    typename T::base_type p= t.getBaseT();
    +    while(i
    +countT QhullSet::
    +lastIndexOf(const T &t) const
    +{
    +    const typename T::base_type *b= data();
    +    const typename T::base_type *i= endData();
    +    typename T::base_type p= t.getBaseT();
    +    while(--i>=b){
    +        if(*i==p){
    +            break;
    +        }
    +    }
    +    return (countT)(i-b); // WARN64
    +}//lastIndexOf
    +
    +#//!\name QhullSetIterator
    +
    +template 
    +bool QhullSetIterator::
    +findNext(const T &t)
    +{
    +    typename T::base_type p= t.getBaseT();
    +    while(i!=end_i){
    +        if(*(++i)==p){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findNext
    +
    +template 
    +bool QhullSetIterator::
    +findPrevious(const T &t)
    +{
    +    typename T::base_type p= t.getBaseT();
    +    while(i!=begin_i){
    +        if(*(--i)==p){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findPrevious
    +
    +}//namespace orgQhull
    +
    +
    +#//!\name == Global namespace =========================================
    +
    +template 
    +std::ostream &
    +operator<<(std::ostream &os, const orgQhull::QhullSet &qs)
    +{
    +    const typename T::base_type *i= qs.data();
    +    const typename T::base_type *e= qs.endData();
    +    while(i!=e){
    +        os << T(qs.qh(), *i++);
    +    }
    +    return os;
    +}//operator<<
    +
    +#endif // QhullSet_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullSets.h b/xs/src/qhull/src/libqhullcpp/QhullSets.h
    new file mode 100644
    index 0000000000..d0f200cbcf
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullSets.h
    @@ -0,0 +1,27 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullSets.h#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLSETS_H
    +#define QHULLSETS_H
    +
    +#include "libqhullcpp/QhullSet.h"
    +
    +namespace orgQhull {
    +
    +    //See: QhullFacetSet.h
    +    //See: QhullPointSet.h
    +    //See: QhullVertexSet.h
    +
    +    // Avoid circular references between QhullFacet, QhullRidge, and QhullVertex
    +    class QhullRidge;
    +    typedef QhullSet  QhullRidgeSet;
    +    typedef QhullSetIterator  QhullRidgeSetIterator;
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLSETS_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullStat.cpp b/xs/src/qhull/src/libqhullcpp/QhullStat.cpp
    new file mode 100644
    index 0000000000..c4fe6c4918
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullStat.cpp
    @@ -0,0 +1,42 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullStat.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullStat -- Qhull's global data structure, statT, as a C++ class
    +
    +#include "libqhullcpp/QhullStat.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +
    +#include 
    +#include 
    +
    +using std::cerr;
    +using std::string;
    +using std::vector;
    +using std::ostream;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructor, destructor, etc.
    +
    +//! If qh_QHpointer==0, invoke with placement new on qh_stat;
    +QhullStat::
    +QhullStat()
    +{
    +}//QhullStat
    +
    +QhullStat::
    +~QhullStat()
    +{
    +}//~QhullStat
    +
    +}//namespace orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullStat.h b/xs/src/qhull/src/libqhullcpp/QhullStat.h
    new file mode 100644
    index 0000000000..54bde8fc79
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullStat.h
    @@ -0,0 +1,49 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullStat.h#2 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLSTAT_H
    +#define QHULLSTAT_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name defined here
    +    //! QhullStat -- Qhull's statistics, qhstatT, as a C++ class
    +    //! Statistics defined with zzdef_() control Qhull's behavior, summarize its result, and report precision problems.
    +    class QhullStat;
    +
    +class QhullStat : public qhstatT {
    +
    +private:
    +#//!\name Fields (empty) -- POD type equivalent to qhstatT.  No data or virtual members
    +
    +public:
    +#//!\name Constants
    +
    +#//!\name class methods
    +
    +#//!\name constructor, assignment, destructor, invariant
    +                        QhullStat();
    +                        ~QhullStat();
    +
    +private:
    +    //!disable copy constructor and assignment
    +                        QhullStat(const QhullStat &);
    +    QhullStat &         operator=(const QhullStat &);
    +public:
    +
    +#//!\name Access
    +};//class QhullStat
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLSTAT_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullVertex.cpp b/xs/src/qhull/src/libqhullcpp/QhullVertex.cpp
    new file mode 100644
    index 0000000000..fd7aef0893
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullVertex.cpp
    @@ -0,0 +1,112 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullVertex.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullVertex -- Qhull's vertex structure, vertexT, as a C++ class
    +
    +#include "libqhullcpp/QhullVertex.h"
    +
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullFacet.h"
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class objects
    +vertexT QhullVertex::
    +s_empty_vertex= {0,0,0,0,0,
    +                 0,0,0,0,0,
    +                 0};
    +
    +#//!\name Constructors
    +
    +QhullVertex::QhullVertex(const Qhull &q)
    +: qh_vertex(&s_empty_vertex)
    +, qh_qh(q.qh())
    +{
    +}//Default
    +
    +QhullVertex::QhullVertex(const Qhull &q, vertexT *v)
    +: qh_vertex(v ? v : &s_empty_vertex)
    +, qh_qh(q.qh())
    +{
    +}//vertexT
    +
    +#//!\name foreach
    +
    +//! Return neighboring facets for a vertex
    +//! If neither merging nor Voronoi diagram, requires Qhull::defineVertexNeighborFacets() beforehand.
    +QhullFacetSet QhullVertex::
    +neighborFacets() const
    +{
    +    if(!neighborFacetsDefined()){
    +        throw QhullError(10034, "Qhull error: neighboring facets of vertex %d not defined.  Please call Qhull::defineVertexNeighborFacets() beforehand.", id());
    +    }
    +    return QhullFacetSet(qh_qh, qh_vertex->neighbors);
    +}//neighborFacets
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using std::string;
    +using std::vector;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetSet;
    +using orgQhull::QhullFacetSetIterator;
    +using orgQhull::QhullVertex;
    +
    +//! Duplicate of qh_printvertex [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullVertex::PrintVertex &pr)
    +{
    +    QhullVertex v= *pr.vertex;
    +    QhullPoint p= v.point();
    +    if(*pr.print_message){
    +        os << pr.print_message << " ";
    +    }else{
    +        os << "- ";
    +    }
    +    os << "p" << p.id() << " (v" << v.id() << "): ";
    +    const realT *c= p.coordinates();
    +    for(int k= p.dimension(); k--; ){
    +        os << " " << *c++; // FIXUP QH11010 %5.2g
    +    }
    +    if(v.getVertexT()->deleted){
    +        os << " deleted";
    +    }
    +    if(v.getVertexT()->delridge){
    +        os << " ridgedeleted";
    +    }
    +    os << endl;
    +    if(v.neighborFacetsDefined()){
    +        QhullFacetSetIterator i= v.neighborFacets();
    +        if(i.hasNext()){
    +            os << " neighborFacets:";
    +            countT count= 0;
    +            while(i.hasNext()){
    +                if(++count % 100 == 0){
    +                    os << endl << "     ";
    +                }
    +                QhullFacet f= i.next();
    +                os << " f" << f.id();
    +            }
    +            os << endl;
    +        }
    +    }
    +    return os;
    +}//<< PrintVertex
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullVertex.h b/xs/src/qhull/src/libqhullcpp/QhullVertex.h
    new file mode 100644
    index 0000000000..0137766db5
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullVertex.h
    @@ -0,0 +1,104 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullVertex.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLVERTEX_H
    +#define QHULLVERTEX_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullLinkedList.h"
    +#include "libqhullcpp/QhullSet.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class QhullFacetSet;
    +
    +#//!\name Defined here
    +    //! QhullVertex -- Qhull's vertex structure, vertexT [libqhull_r.h], as a C++ class
    +    class QhullVertex;
    +    typedef QhullLinkedList QhullVertexList;
    +    typedef QhullLinkedListIterator QhullVertexListIterator;
    +
    +
    +/*********************
    +  topological information:
    +    next,previous       doubly-linked list of all vertices
    +    neighborFacets           set of adjacent facets (only if qh.VERTEXneighbors)
    +
    +  geometric information:
    +    point               array of DIM coordinates
    +*/
    +
    +class QhullVertex {
    +
    +#//!\name Defined here
    +public:
    +    typedef vertexT *   base_type;  // for QhullVertexSet
    +
    +private:
    +#//!\name Fields
    +    vertexT *           qh_vertex;  //!< Corresponding vertexT, never 0
    +    QhullQh *           qh_qh;      //!< QhullQh/qhT for vertexT, may be 0
    +
    +#//!\name Class objects
    +    static vertexT      s_empty_vertex;  // needed for shallow copy
    +
    +public:
    +#//!\name Constants
    +
    +#//!\name Constructors
    +                        QhullVertex() : qh_vertex(&s_empty_vertex), qh_qh(0) {}
    +    explicit            QhullVertex(const Qhull &q);
    +                        QhullVertex(const Qhull &q, vertexT *v);
    +    explicit            QhullVertex(QhullQh *qqh) : qh_vertex(&s_empty_vertex), qh_qh(qqh) {}
    +                        QhullVertex(QhullQh *qqh, vertexT *v) : qh_vertex(v ? v : &s_empty_vertex), qh_qh(qqh) {}
    +                        // Creates an alias.  Does not copy QhullVertex.  Needed for return by value and parameter passing
    +                        QhullVertex(const QhullVertex &other) : qh_vertex(other.qh_vertex), qh_qh(other.qh_qh) {}
    +                        // Creates an alias.  Does not copy QhullVertex.  Needed for vector
    +    QhullVertex &       operator=(const QhullVertex &other) { qh_vertex= other.qh_vertex; qh_qh= other.qh_qh; return *this; }
    +                        ~QhullVertex() {}
    +
    +#//!\name GetSet
    +    int                 dimension() const { return (qh_qh ? qh_qh->hull_dim : 0); }
    +    vertexT *           getBaseT() const { return getVertexT(); } //!< For QhullSet
    +    vertexT *           getVertexT() const { return qh_vertex; }
    +    countT              id() const { return qh_vertex->id; }
    +    bool                isValid() const { return (qh_qh && qh_vertex != &s_empty_vertex); }
    +                        //! True if defineVertexNeighborFacets() already called.  Auotomatically set for facet merging, Voronoi diagrams
    +    bool                neighborFacetsDefined() const { return qh_vertex->neighbors != 0; }
    +    QhullVertex         next() const { return QhullVertex(qh_qh, qh_vertex->next); }
    +    bool                operator==(const QhullVertex &other) const { return qh_vertex==other.qh_vertex; }
    +    bool                operator!=(const QhullVertex &other) const { return !operator==(other); }
    +    QhullPoint          point() const { return QhullPoint(qh_qh, qh_vertex->point); }
    +    QhullVertex         previous() const { return QhullVertex(qh_qh, qh_vertex->previous); }
    +    QhullQh *           qh() const { return qh_qh; }
    +
    +#//!\name foreach
    +    //See also QhullVertexList
    +    QhullFacetSet       neighborFacets() const;
    +
    +#//!\name IO
    +    struct PrintVertex{
    +        const QhullVertex *vertex;
    +        const char *    print_message;    //!< non-null message
    +                        PrintVertex(const char *message, const QhullVertex &v) : vertex(&v), print_message(message) {}
    +    };//PrintVertex
    +    PrintVertex         print(const char *message) const { return PrintVertex(message, *this); }
    +};//class QhullVertex
    +
    +}//namespace orgQhull
    +
    +#//!\name GLobal
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertex::PrintVertex &pr);
    +inline std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertex &v) { os << v.print(""); return os; }
    +
    +#endif // QHULLVERTEX_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullVertexSet.cpp b/xs/src/qhull/src/libqhullcpp/QhullVertexSet.cpp
    new file mode 100644
    index 0000000000..00ba62d196
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullVertexSet.cpp
    @@ -0,0 +1,161 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullVertexSet.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullVertexSet -- Qhull's linked Vertexs, as a C++ class
    +
    +#include "libqhullcpp/QhullVertexSet.h"
    +
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::string;
    +using std::vector;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  /* interaction between '_setjmp' and C++ object destruction is non-portable */
    +                                    /* setjmp should not be implemented with 'catch' */
    +#endif
    +
    +namespace orgQhull {
    +
    +QhullVertexSet::
    +QhullVertexSet(const Qhull &q, facetT *facetlist, setT *facetset, bool allfacets)
    +: QhullSet(q.qh(), 0)
    +, qhsettemp_defined(false)
    +{
    +    QH_TRY_(q.qh()){ // no object creation -- destructors skipped on longjmp()
    +        setT *vertices= qh_facetvertices(q.qh(), facetlist, facetset, allfacets);
    +        defineAs(vertices);
    +        qhsettemp_defined= true;
    +    }
    +    q.qh()->NOerrexit= true;
    +    q.qh()->maybeThrowQhullMessage(QH_TRY_status);
    +}//QhullVertexSet facetlist facetset
    +
    +//! Return tempory QhullVertexSet of vertices for a list and/or a set of facets
    +//! Sets qhsettemp_defined (disallows copy constructor and assignment to prevent double-free)
    +QhullVertexSet::
    +QhullVertexSet(QhullQh *qqh, facetT *facetlist, setT *facetset, bool allfacets)
    +: QhullSet(qqh, 0)
    +, qhsettemp_defined(false)
    +{
    +    QH_TRY_(qh()){ // no object creation -- destructors skipped on longjmp()
    +        setT *vertices= qh_facetvertices(qh(), facetlist, facetset, allfacets);
    +        defineAs(vertices);
    +        qhsettemp_defined= true;
    +    }
    +    qh()->NOerrexit= true;
    +    qh()->maybeThrowQhullMessage(QH_TRY_status);
    +}//QhullVertexSet facetlist facetset
    +
    +//! Copy constructor for argument passing and returning a result
    +//! Only copies a pointer to the set.
    +//! Throws an error if qhsettemp_defined, otherwise have a double-free
    +//!\todo Convert QhullVertexSet to a shared pointer with reference counting
    +QhullVertexSet::
    +QhullVertexSet(const QhullVertexSet &other)
    +: QhullSet(other)
    +, qhsettemp_defined(false)
    +{
    +    if(other.qhsettemp_defined){
    +        throw QhullError(10077, "QhullVertexSet: Cannot use copy constructor since qhsettemp_defined (e.g., QhullVertexSet for a set and/or list of QhFacet).  Contains %d vertices", other.count());
    +    }
    +}//copy constructor
    +
    +//! Copy assignment only copies a pointer to the set.
    +//! Throws an error if qhsettemp_defined, otherwise have a double-free
    +QhullVertexSet & QhullVertexSet::
    +operator=(const QhullVertexSet &other)
    +{
    +    QhullSet::operator=(other);
    +    qhsettemp_defined= false;
    +    if(other.qhsettemp_defined){
    +        throw QhullError(10078, "QhullVertexSet: Cannot use copy constructor since qhsettemp_defined (e.g., QhullVertexSet for a set and/or list of QhFacet).  Contains %d vertices", other.count());
    +    }
    +    return *this;
    +}//assignment
    +
    +void QhullVertexSet::
    +freeQhSetTemp()
    +{
    +    if(qhsettemp_defined){
    +        qhsettemp_defined= false;
    +        QH_TRY_(qh()){ // no object creation -- destructors skipped on longjmp()
    +            qh_settempfree(qh(), referenceSetT()); // errors if not top of tempstack or if qhmem corrupted
    +        }
    +        qh()->NOerrexit= true;
    +        qh()->maybeThrowQhullMessage(QH_TRY_status, QhullError::NOthrow);
    +    }
    +}//freeQhSetTemp
    +
    +QhullVertexSet::
    +~QhullVertexSet()
    +{
    +    freeQhSetTemp();
    +}//~QhullVertexSet
    +
    +//FIXUP -- Move conditional, QhullVertexSet code to QhullVertexSet.cpp
    +#ifndef QHULL_NO_STL
    +std::vector QhullVertexSet::
    +toStdVector() const
    +{
    +    QhullSetIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        QhullVertex v= i.next();
    +        vs.push_back(v);
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullVertex;
    +using orgQhull::QhullVertexSet;
    +using orgQhull::QhullVertexSetIterator;
    +
    +//! Print Vertex identifiers to stream.  Space prefix.  From qh_printVertexheader [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullVertexSet::PrintIdentifiers &pr)
    +{
    +    os << pr.print_message;
    +    for(QhullVertexSet::const_iterator i= pr.vertex_set->begin(); i!=pr.vertex_set->end(); ++i){
    +        const QhullVertex v= *i;
    +        os << " v" << v.id();
    +    }
    +    os << endl;
    +    return os;
    +}//<
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +
    +#//!\name Defined here
    +    //! QhullVertexSet -- a set of Qhull Vertices, as a C++ class.
    +    //! See Qhull
    +    class QhullVertexSet;
    +    typedef QhullSetIterator QhullVertexSetIterator;
    +
    +class QhullVertexSet : public QhullSet {
    +
    +private:
    +#//!\name Fields
    +    bool                qhsettemp_defined;  //! Set was allocated with qh_settemp()
    +
    +public:
    +#//!\name Constructor
    +                        QhullVertexSet(const Qhull &q, setT *s) : QhullSet(q, s), qhsettemp_defined(false) {}
    +                        QhullVertexSet(const Qhull &q, facetT *facetlist, setT *facetset, bool allfacets);
    +                        //Conversion from setT* is not type-safe.  Implicit conversion for void* to T
    +                        QhullVertexSet(QhullQh *qqh, setT *s) : QhullSet(qqh, s), qhsettemp_defined(false) {}
    +                        QhullVertexSet(QhullQh *qqh, facetT *facetlist, setT *facetset, bool allfacets);
    +                        //Copy constructor and assignment copies pointer but not contents.  Throws error if qhsettemp_defined.  Needed for return by value.
    +                        QhullVertexSet(const QhullVertexSet &other);
    +    QhullVertexSet &    operator=(const QhullVertexSet &other);
    +                        ~QhullVertexSet();
    +
    +private:                //!Default constructor disabled.  Will implement allocation later
    +                        QhullVertexSet();
    +public:
    +
    +#//!\name Destructor
    +    void                freeQhSetTemp();
    +
    +#//!\name Conversion
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList   toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name IO
    +    struct PrintVertexSet{
    +        const QhullVertexSet *vertex_set;
    +        const char *    print_message;     //!< non-null message
    +                        
    +                        PrintVertexSet(const char *message, const QhullVertexSet *s) : vertex_set(s), print_message(message) {}
    +    };//PrintVertexSet
    +    const PrintVertexSet print(const char *message) const { return PrintVertexSet(message, this); }
    +
    +    struct PrintIdentifiers{
    +        const QhullVertexSet *vertex_set;
    +        const char *    print_message;    //!< non-null message
    +                        PrintIdentifiers(const char *message, const QhullVertexSet *s) : vertex_set(s), print_message(message) {}
    +    };//PrintIdentifiers
    +    PrintIdentifiers    printIdentifiers(const char *message) const { return PrintIdentifiers(message, this); }
    +
    +};//class QhullVertexSet
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertexSet::PrintVertexSet &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertexSet::PrintIdentifiers &p);
    +inline std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertexSet &vs) { os << vs.print(""); return os; }
    +
    +#endif // QHULLVERTEXSET_H
    diff --git a/xs/src/qhull/src/libqhullcpp/RboxPoints.cpp b/xs/src/qhull/src/libqhullcpp/RboxPoints.cpp
    new file mode 100644
    index 0000000000..d7acd9fce0
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RboxPoints.cpp
    @@ -0,0 +1,224 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RboxPoints.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/RboxPoints.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +
    +#include 
    +
    +using std::cerr;
    +using std::endl;
    +using std::istream;
    +using std::ostream;
    +using std::ostringstream;
    +using std::string;
    +using std::vector;
    +using std::ws;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//! RboxPoints -- generate random PointCoordinates for qhull (rbox)
    +
    +
    +#//!\name Constructors
    +RboxPoints::
    +RboxPoints()
    +: PointCoordinates("rbox")
    +, rbox_new_count(0)
    +, rbox_status(qh_ERRnone)
    +, rbox_message()
    +{
    +    allocateQhullQh();
    +}
    +
    +//! Allocate and generate points according to rboxCommand
    +//! For rbox commands, see http://www.qhull.org/html/rbox.htm or html/rbox.htm
    +//! Same as appendPoints()
    +RboxPoints::
    +RboxPoints(const char *rboxCommand)
    +: PointCoordinates("rbox")
    +, rbox_new_count(0)
    +, rbox_status(qh_ERRnone)
    +, rbox_message()
    +{
    +    allocateQhullQh();
    +    // rbox arguments added to comment() via qh_rboxpoints > qh_fprintf_rbox
    +    appendPoints(rboxCommand);
    +}
    +
    +RboxPoints::
    +~RboxPoints()
    +{
    +    delete qh();
    +    resetQhullQh(0);
    +}
    +
    +// RboxPoints and qh_rboxpoints has several fields in qhT (rbox_errexit..cpp_object)
    +// It shares last_random with qh_rand and qh_srand
    +// The other fields are unused
    +void RboxPoints::
    +allocateQhullQh()
    +{
    +    QHULL_LIB_CHECK /* Check for compatible library */
    +    resetQhullQh(new QhullQh);
    +}//allocateQhullQh
    +
    +#//!\name Messaging
    +
    +void RboxPoints::
    +clearRboxMessage()
    +{
    +    rbox_status= qh_ERRnone;
    +    rbox_message.clear();
    +}//clearRboxMessage
    +
    +std::string RboxPoints::
    +rboxMessage() const
    +{
    +    if(rbox_status!=qh_ERRnone){
    +        return rbox_message;
    +    }
    +    if(isEmpty()){
    +        return "rbox warning: no points generated\n";
    +    }
    +    return "rbox: OK\n";
    +}//rboxMessage
    +
    +int RboxPoints::
    +rboxStatus() const
    +{
    +    return rbox_status;
    +}
    +
    +bool RboxPoints::
    +hasRboxMessage() const
    +{
    +    return (rbox_status!=qh_ERRnone);
    +}
    +
    +#//!\name Methods
    +
    +//! Appends points as defined by rboxCommand
    +//! Appends rboxCommand to comment
    +//! For rbox commands, see http://www.qhull.org/html/rbox.htm or html/rbox.htm
    +void RboxPoints::
    +appendPoints(const char *rboxCommand)
    +{
    +    string s("rbox ");
    +    s += rboxCommand;
    +    char *command= const_cast(s.c_str());
    +    if(qh()->cpp_object){
    +        throw QhullError(10001, "Qhull error: conflicting user of cpp_object for RboxPoints::appendPoints() or corrupted qh_qh");
    +    }
    +    if(extraCoordinatesCount()!=0){
    +        throw QhullError(10067, "Qhull error: Extra coordinates (%d) prior to calling RboxPoints::appendPoints.  Was %s", extraCoordinatesCount(), 0, 0.0, comment().c_str());
    +    }
    +    countT previousCount= count();
    +    qh()->cpp_object= this;           // for qh_fprintf_rbox()
    +    int status= qh_rboxpoints(qh(), command);
    +    qh()->cpp_object= 0;
    +    if(rbox_status==qh_ERRnone){
    +        rbox_status= status;
    +    }
    +    if(rbox_status!=qh_ERRnone){
    +        throw QhullError(rbox_status, rbox_message);
    +    }
    +    if(extraCoordinatesCount()!=0){
    +        throw QhullError(10002, "Qhull error: extra coordinates (%d) for PointCoordinates (%x)", extraCoordinatesCount(), 0, 0.0, coordinates());
    +    }
    +    if(previousCount+newCount()!=count()){
    +        throw QhullError(10068, "Qhull error: rbox specified %d points but got %d points for command '%s'", newCount(), count()-previousCount, 0.0, comment().c_str());
    +    }
    +}//appendPoints
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +/*---------------------------------
    +
    +  qh_fprintf_rbox(qh, fp, msgcode, format, list of args )
    +    fp is ignored (replaces qh_fprintf_rbox() in userprintf_rbox.c)
    +    cpp_object == RboxPoints
    +
    +notes:
    +    only called from qh_rboxpoints()
    +    same as fprintf() and Qhull.qh_fprintf()
    +    fgets() is not trapped like fprintf()
    +    Do not throw errors from here.  Use qh_errexit_rbox;
    +    A similar technique can be used for qh_fprintf to capture all of its output
    +*/
    +extern "C"
    +void qh_fprintf_rbox(qhT *qh, FILE*, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    using namespace orgQhull;
    +
    +    if(!qh->cpp_object){
    +        qh_errexit_rbox(qh, 10072);
    +    }
    +    RboxPoints *out= reinterpret_cast(qh->cpp_object);
    +    va_start(args, fmt);
    +    if(msgcoderbox_message += newMessage;
    +        if(out->rbox_statusrbox_status>=MSG_STDERR){
    +            out->rbox_status= msgcode;
    +        }
    +        va_end(args);
    +        return;
    +    }
    +    switch(msgcode){
    +    case 9391:
    +    case 9392:
    +        out->rbox_message += "RboxPoints error: options 'h', 'n' not supported.\n";
    +        qh_errexit_rbox(qh, 10010);
    +        /* never returns */
    +    case 9393:  // FIXUP QH11026 countT vs. int
    +        {
    +            int dimension= va_arg(args, int);
    +            string command(va_arg(args, char*));
    +            countT count= va_arg(args, countT);
    +            out->setDimension(dimension);
    +            out->appendComment(" \"");
    +            out->appendComment(command.substr(command.find(' ')+1));
    +            out->appendComment("\"");
    +            out->setNewCount(count);
    +            out->reservePoints();
    +        }
    +        break;
    +    case 9407:
    +        *out << va_arg(args, int);
    +        // fall through
    +    case 9405:
    +        *out << va_arg(args, int);
    +        // fall through
    +    case 9403:
    +        *out << va_arg(args, int);
    +        break;
    +    case 9408:
    +        *out << va_arg(args, double);
    +        // fall through
    +    case 9406:
    +        *out << va_arg(args, double);
    +        // fall through
    +    case 9404:
    +        *out << va_arg(args, double);
    +        break;
    +    }
    +    va_end(args);
    +} /* qh_fprintf_rbox */
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/RboxPoints.h b/xs/src/qhull/src/libqhullcpp/RboxPoints.h
    new file mode 100644
    index 0000000000..e8ec72b14a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RboxPoints.h
    @@ -0,0 +1,69 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RboxPoints.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef RBOXPOINTS_H
    +#define RBOXPOINTS_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/PointCoordinates.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! RboxPoints -- generate random PointCoordinates for Qhull
    +    class RboxPoints;
    +
    +class RboxPoints : public PointCoordinates {
    +
    +private:
    +#//!\name Fields and friends
    +                        //! PointCoordinates.qh() is owned by RboxPoints
    +    countT              rbox_new_count;     //! Number of points for PointCoordinates
    +    int                 rbox_status;    //! error status from rboxpoints.  qh_ERRnone if none.
    +    std::string         rbox_message;   //! stderr from rboxpoints
    +
    +    // '::' is required for friend references
    +    friend void ::qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +
    +public:
    +#//!\name Construct
    +                        RboxPoints();
    +    explicit            RboxPoints(const char *rboxCommand);
    +                        ~RboxPoints();
    +private:                // Disable copy constructor and assignment.  RboxPoints owns QhullQh.
    +                        RboxPoints(const RboxPoints &);
    +                        RboxPoints &operator=(const RboxPoints &);
    +private:
    +    void                allocateQhullQh();
    +
    +public:
    +#//!\name GetSet
    +    void                clearRboxMessage();
    +    countT              newCount() const { return rbox_new_count; }
    +    std::string         rboxMessage() const;
    +    int                 rboxStatus() const;
    +    bool                hasRboxMessage() const;
    +    void                setNewCount(countT pointCount) { QHULL_ASSERT(pointCount>=0); rbox_new_count= pointCount; }
    +
    +#//!\name Modify
    +    void                appendPoints(const char* rboxCommand);
    +    using               PointCoordinates::appendPoints;
    +    void                reservePoints() { reserveCoordinates((count()+newCount())*dimension()); }
    +};//class RboxPoints
    +
    +}//namespace orgQhull
    +
    +#endif // RBOXPOINTS_H
    diff --git a/xs/src/qhull/src/libqhullcpp/RoadError.cpp b/xs/src/qhull/src/libqhullcpp/RoadError.cpp
    new file mode 100644
    index 0000000000..1d41ec1bc1
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RoadError.cpp
    @@ -0,0 +1,158 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RoadError.cpp#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! RoadError -- All exceptions thrown by Qhull are RoadErrors
    +#//! Do not throw RoadError's from destructors.  Use e.logError() instead.
    +
    +#include "libqhullcpp/RoadError.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +using std::cerr;
    +using std::cout;
    +using std::string;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class fields
    +
    +//! Identifies error messages from Qhull and Road for web searches.
    +//! See QhullError.h#QHULLlastError and user.h#MSG_ERROR
    +const char * RoadError::
    +ROADtag= "QH";
    +
    +std::ostringstream RoadError::
    +global_log;
    +
    +#//!\name Constructor
    +
    +RoadError::
    +RoadError()
    +: error_code(0)
    +, log_event()
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(const RoadError &other)
    +: error_code(other.error_code)
    +, log_event(other.log_event)
    +, error_message(other.error_message)
    +{
    +}//copy construct
    +
    +RoadError::
    +RoadError(int code, const std::string &message)
    +: error_code(code)
    +, log_event(message.c_str())
    +, error_message(log_event.toString(ROADtag, error_code))
    +{
    +    log_event.cstr_1= error_message.c_str(); // overwrites initial value
    +}
    +
    +RoadError::
    +RoadError(int code, const char *fmt)
    +: error_code(code)
    +, log_event(fmt)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d)
    +: error_code(code)
    +, log_event(fmt, d)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2)
    +: error_code(code)
    +, log_event(fmt, d, d2)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f)
    +: error_code(code)
    +, log_event(fmt, d, d2, f)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, const char *s)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, s)
    +, error_message(log_event.toString(ROADtag, code)) // char * may go out of scope
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, const void *x)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, x)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, int i)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, i)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, long long i)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, i)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, double e)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, e)
    +, error_message()
    +{ }
    +
    +RoadError & RoadError::
    +operator=(const RoadError &other)
    +{
    +    error_code= other.error_code;
    +    error_message= other.error_message;
    +    log_event= other.log_event;
    +    return *this;
    +}//operator=
    +
    +#//!\name Virtual
    +const char * RoadError::
    +what() const throw()
    +{
    +    if(error_message.empty()){
    +        error_message= log_event.toString(ROADtag, error_code);
    +    }
    +    return error_message.c_str();
    +}//what
    +
    +#//!\name Updates
    +
    +//! Log error instead of throwing it.
    +//! Not reentrant, so avoid using it if possible
    +//!\todo Redesign with a thread-local stream or a reentrant ostringstream
    +void RoadError::
    +logErrorLastResort() const
    +{
    +    global_log << what() << endl;
    +}//logError
    +
    +
    +}//namespace orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/RoadError.h b/xs/src/qhull/src/libqhullcpp/RoadError.h
    new file mode 100644
    index 0000000000..1c9f6cdd5a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RoadError.h
    @@ -0,0 +1,88 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RoadError.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef ROADERROR_H
    +#define ROADERROR_H
    +
    +#include "libqhull_r/user_r.h"  /* for QHULL_CRTDBG */
    +#include "libqhullcpp/RoadLogEvent.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +using std::endl;
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! RoadError -- Report and log errors
    +    //!  See discussion in Saylan, G., "Practical C++ error handling in hybrid environments," Dr. Dobb's Journal, p. 50-55, March 2007.
    +    //!   He uses an auto_ptr to track a stringstream.  It constructs a string on the fly.  RoadError uses the copy constructor to transform RoadLogEvent into a string
    +    class RoadError;
    +
    +class RoadError : public std::exception {
    +
    +private:
    +#//!\name Fields
    +    int                 error_code;  //! Non-zero code (not logged), maybe returned as program status
    +    RoadLogEvent        log_event;   //! Format string w/ arguments
    +    mutable std::string error_message;  //! Formated error message.  Must be after log_event.
    +
    +#//!\name Class fields
    +    static const char  *  ROADtag;
    +    static std::ostringstream  global_log; //!< May be replaced with any ostream object
    +                                    //!< Not reentrant -- only used by RoadError::logErrorLastResort()
    +
    +public:
    +#//!\name Constants
    +
    +#//!\name Constructors
    +    RoadError();
    +    RoadError(const RoadError &other);  //! Called on throw, generates error_message
    +    RoadError(int code, const std::string &message);
    +    RoadError(int code, const char *fmt);
    +    RoadError(int code, const char *fmt, int d);
    +    RoadError(int code, const char *fmt, int d, int d2);
    +    RoadError(int code, const char *fmt, int d, int d2, float f);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, const char *s);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, const void *x);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, int i);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, long long i);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, double e);
    +
    +    RoadError &         operator=(const RoadError &other);
    +                        ~RoadError() throw() {};
    +
    +#//!\name Class methods
    +
    +    static void         clearGlobalLog() { global_log.seekp(0); }
    +    static bool         emptyGlobalLog() { return global_log.tellp()<=0; }
    +    static const char  *stringGlobalLog() { return global_log.str().c_str(); }
    +
    +#//!\name Virtual
    +    virtual const char *what() const throw();
    +
    +#//!\name GetSet
    +    bool                isValid() const { return log_event.isValid(); }
    +    int                 errorCode() const { return error_code; };
    +   // FIXUP QH11021 should RoadError provide errorMessage().  Currently what()
    +    RoadLogEvent        roadLogEvent() const { return log_event; };
    +
    +#//!\name Update
    +    void                logErrorLastResort() const;
    +};//class RoadError
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +inline std::ostream &   operator<<(std::ostream &os, const orgQhull::RoadError &e) { return os << e.what(); }
    +
    +#endif // ROADERROR_H
    diff --git a/xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp b/xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp
    new file mode 100644
    index 0000000000..9a9cf5960a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp
    @@ -0,0 +1,122 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RoadLogEvent.cpp#3 $$Change: 2066 $
    +** $Date: 2016/01/18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! RoadLogEvent -- All exceptions thrown by Qhull are RoadErrors
    +
    +#include "libqhullcpp/RoadLogEvent.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::string;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Conversion
    +string RoadLogEvent::
    +toString(const char *tag, int code) const
    +{
    +    ostringstream os;
    +    if(tag && code){
    +        os << tag << code;
    +        if(format_string){
    +            os << " ";
    +        }
    +    }
    +    if(!format_string){
    +        return os.str();
    +    }
    +    const char *s= format_string;
    +    int dCount= 0;  // Count of %d
    +    int fCount= 0;  // Count of %f
    +    char extraCode= '\0';
    +    while(*s){
    +        if(*s!='%'){
    +            os << *s++;
    +        }else{
    +            char c= *++s;
    +            s++;
    +            switch(c){
    +            case 'd':
    +                if(++dCount>2){
    +                    os << " ERROR_three_%d_in_format ";
    +                }else if(dCount==2){
    +                    os << int_2;
    +                }else{
    +                    os << int_1;
    +                }
    +                break;
    +            case 'e':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << double_1;
    +                }
    +                break;
    +            case 'f':
    +                if(++fCount>1){
    +                    os << " ERROR_two_%f_in_format ";
    +                }else{
    +                    os << float_1;
    +                }
    +                break;
    +            case 'i':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << int64_1;
    +                }
    +                break;
    +            case 's':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << cstr_1;
    +                }
    +                break;
    +            case 'u':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << "0x" << std::hex << int64_1 << std::dec;
    +                }
    +                break;
    +            case 'x':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << void_1;
    +                }
    +                break;
    +            case '%':
    +                os << c;
    +                break;
    +            default:
    +                os << " ERROR_%" << c << "_not_defined_in_format";
    +                break;
    +            }
    +        }
    +    }
    +    if(s[-1]!='\n'){
    +        os << endl;
    +    }
    +    return os.str();
    +}//toString
    +
    +#//!\name Class helpers (static)
    +
    +//! True if this char is the first extra code
    +bool RoadLogEvent::
    +firstExtraCode(std::ostream &os, char c, char *extraCode){
    +    if(*extraCode){
    +        os << " ERROR_%" << *extraCode << "_and_%" << c << "_in_format ";
    +        return false;
    +    }
    +    *extraCode= c;
    +    return true;
    +}//firstExtraCode
    +
    +}//namespace orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/RoadLogEvent.h b/xs/src/qhull/src/libqhullcpp/RoadLogEvent.h
    new file mode 100644
    index 0000000000..7c4cfba0de
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RoadLogEvent.h
    @@ -0,0 +1,77 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RoadLogEvent.h#1 $$Change: 1981 $
    +** $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef ROADLOGEVENT_H
    +#define ROADLOGEVENT_H
    +
    +#include 
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! RoadLogEvent -- Record an event for the RoadLog
    +    struct RoadLogEvent;
    +
    +struct RoadLogEvent {
    +
    +public:
    +#//!\name Fields
    +    const char *    format_string; //! Format string (a literal with format codes, for logging)
    +    int             int_1;       //! Integer argument (%d, for logging)
    +    int             int_2;       //! Integer argument (%d, for logging)
    +    float           float_1;     //! Float argument (%f, for logging)
    +    union {                      //! One additional argument (for logging)
    +        const char *cstr_1;      //!   Cstr argument (%s) -- type checked at construct-time
    +        const void *void_1;      //!   Void* argument (%x) -- Use upper-case codes for object types
    +        long long   int64_1;     //!   signed int64 (%i).  Ambiguous if unsigned is also defined.
    +        double      double_1;    //!   Double argument (%e)
    +    };
    +
    +#//!\name Constants
    +
    +#//!\name Constructors
    +    RoadLogEvent() : format_string(0), int_1(0), int_2(0), float_1(0), int64_1(0) {};
    +    explicit RoadLogEvent(const char *fmt) : format_string(fmt), int_1(0), int_2(0), float_1(0), int64_1(0) {};
    +    RoadLogEvent(const char *fmt, int d) : format_string(fmt), int_1(d), int_2(0), float_1(0), int64_1(0) {};
    +    RoadLogEvent(const char *fmt, int d, int d2) : format_string(fmt), int_1(d), int_2(d2), float_1(0), int64_1(0) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f) : format_string(fmt), int_1(d), int_2(d2), float_1(f), int64_1(0) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, const char *s) : format_string(fmt), int_1(d), int_2(d2), float_1(f), cstr_1(s) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, const void *x) : format_string(fmt), int_1(d), int_2(d2), float_1(f), void_1(x) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, int i) : format_string(fmt), int_1(d), int_2(d2), float_1(f), int64_1(i) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, long long i) : format_string(fmt), int_1(d), int_2(d2), float_1(f), int64_1(i) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, double g) : format_string(fmt), int_1(d), int_2(d2), float_1(f), double_1(g) {};
    +    ~RoadLogEvent() {};
    +    //! Default copy constructor and assignment
    +
    +#//!\name GetSet
    +    bool                isValid() const { return format_string!=0; }
    +    int                 int1() const { return int_1; };
    +    int                 int2() const { return int_2; };
    +    float               float1() const { return float_1; };
    +    const char *        format() const { return format_string; };
    +    const char *        cstr1() const { return cstr_1; };
    +    const void *        void1() const { return void_1; };
    +    long long           int64() const { return int64_1; };
    +    double              double1() const { return double_1; };
    +
    +#//!\name Conversion
    +
    +    std::string        toString(const char* tag, int code) const;
    +
    +private:
    +#//!\name Class helpers
    +    static bool         firstExtraCode(std::ostream &os, char c, char *extraCode);
    +
    +
    +};//class RoadLogEvent
    +
    +}//namespace orgQhull
    +
    +#endif // ROADLOGEVENT_H
    diff --git a/xs/src/qhull/src/libqhullcpp/functionObjects.h b/xs/src/qhull/src/libqhullcpp/functionObjects.h
    new file mode 100644
    index 0000000000..3645c0713a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/functionObjects.h
    @@ -0,0 +1,67 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/functionObjects.h#1 $$Change: 1981 $
    +** $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHFUNCTIONOBJECTS_H
    +#define QHFUNCTIONOBJECTS_H
    +
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +
    +    //! Sum of absolute values of the elements in a container
    +    class AbsoluteSumOf;
    +    //! Sum of the elements in a container
    +    class SumOf;
    +    //! Sum of squares of the elements in a container
    +    class SumSquaresOf;
    +
    +#//!\name Class
    +
    +//! Absolute sum of the elements in a container
    +class AbsoluteSumOf
    +{
    +private:
    +    double sum;
    +public:
    +    inline AbsoluteSumOf() : sum(0.0) {}
    +    inline void operator()(double v) { sum += fabs(v); }
    +    inline operator double() { return sum; }
    +};//AbsoluteSumOf
    +
    +//! Sum of the elements in a container
    +class SumOf
    +{
    +private:
    +    double sum;
    +public:
    +    inline SumOf() : sum(0.0) {}
    +    inline void operator()(double v) { sum += v; }
    +    inline operator double() { return sum; }
    +};//SumOf
    +
    +
    +//! Sum of squares of the elements in a container
    +class SumSquaresOf
    +{
    +private:
    +    double sum;
    +public:
    +    inline SumSquaresOf() : sum(0.0) {}
    +    inline void operator()(double v) { sum += v*v; }
    +    inline operator double() { return sum; }
    +};//SumSquaresOf
    +
    +
    +}//orgQhull
    +
    +
    +#endif //QHFUNCTIONOBJECTS_H
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/libqhullcpp.pro b/xs/src/qhull/src/libqhullcpp/libqhullcpp.pro
    new file mode 100644
    index 0000000000..89b967bef2
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/libqhullcpp.pro
    @@ -0,0 +1,71 @@
    +# -------------------------------------------------
    +# libqhullcpp.pro -- Qt project for Qhull cpp shared library
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +DESTDIR = ../../lib
    +TEMPLATE = lib
    +# Do not create libqhullcpp as a shared library.  Qhull C++ classes may change layout and size. 
    +CONFIG += staticlib warn_on
    +CONFIG -= qt rtti
    +build_pass:CONFIG(debug, debug|release):{
    +   TARGET = qhullcpp_d
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +   TARGET = qhullcpp
    +   OBJECTS_DIR = Release
    +}
    +MOC_DIR = moc
    +
    +INCLUDEPATH += ../../src
    +INCLUDEPATH += $$PWD # for MOC_DIR
    +
    +CONFIG += qhull_warn_shadow qhull_warn_conversion
    +
    +SOURCES += ../libqhullcpp/Coordinates.cpp
    +SOURCES += ../libqhullcpp/PointCoordinates.cpp
    +SOURCES += ../libqhullcpp/Qhull.cpp
    +SOURCES += ../libqhullcpp/QhullFacet.cpp
    +SOURCES += ../libqhullcpp/QhullFacetList.cpp
    +SOURCES += ../libqhullcpp/QhullFacetSet.cpp
    +SOURCES += ../libqhullcpp/QhullHyperplane.cpp
    +SOURCES += ../libqhullcpp/QhullPoint.cpp
    +SOURCES += ../libqhullcpp/QhullPoints.cpp
    +SOURCES += ../libqhullcpp/QhullPointSet.cpp
    +SOURCES += ../libqhullcpp/QhullQh.cpp
    +SOURCES += ../libqhullcpp/QhullRidge.cpp
    +SOURCES += ../libqhullcpp/QhullSet.cpp
    +SOURCES += ../libqhullcpp/QhullStat.cpp
    +SOURCES += ../libqhullcpp/QhullVertex.cpp
    +SOURCES += ../libqhullcpp/QhullVertexSet.cpp
    +SOURCES += ../libqhullcpp/RboxPoints.cpp
    +SOURCES += ../libqhullcpp/RoadError.cpp
    +SOURCES += ../libqhullcpp/RoadLogEvent.cpp
    +
    +HEADERS += ../libqhullcpp/Coordinates.h
    +HEADERS += ../libqhullcpp/functionObjects.h
    +HEADERS += ../libqhullcpp/PointCoordinates.h
    +HEADERS += ../libqhullcpp/Qhull.h
    +HEADERS += ../libqhullcpp/QhullError.h
    +HEADERS += ../libqhullcpp/QhullFacet.h
    +HEADERS += ../libqhullcpp/QhullFacetList.h
    +HEADERS += ../libqhullcpp/QhullFacetSet.h
    +HEADERS += ../libqhullcpp/QhullHyperplane.h
    +HEADERS += ../libqhullcpp/QhullIterator.h
    +HEADERS += ../libqhullcpp/QhullLinkedList.h
    +HEADERS += ../libqhullcpp/QhullPoint.h
    +HEADERS += ../libqhullcpp/QhullPoints.h
    +HEADERS += ../libqhullcpp/QhullPointSet.h
    +HEADERS += ../libqhullcpp/QhullQh.h
    +HEADERS += ../libqhullcpp/QhullRidge.h
    +HEADERS += ../libqhullcpp/QhullSet.h
    +HEADERS += ../libqhullcpp/QhullSets.h
    +HEADERS += ../libqhullcpp/QhullStat.h
    +HEADERS += ../libqhullcpp/QhullVertex.h
    +HEADERS += ../libqhullcpp/QhullVertexSet.h
    +HEADERS += ../libqhullcpp/RboxPoints.h
    +HEADERS += ../libqhullcpp/RoadError.h
    +HEADERS += ../libqhullcpp/RoadLogEvent.h
    diff --git a/xs/src/qhull/src/libqhullcpp/qt-qhull.cpp b/xs/src/qhull/src/libqhullcpp/qt-qhull.cpp
    new file mode 100644
    index 0000000000..895f591a85
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/qt-qhull.cpp
    @@ -0,0 +1,130 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/qt-qhull.cpp#1 $$Change: 1981 $
    +** $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include 
    +#include "qhulltest/RoadTest.h"
    +
    +#ifndef QHULL_USES_QT
    +#define QHULL_USES_QT 1
    +#endif
    +
    +#include "Coordinates.h"
    +#include "QhullFacetList.h"
    +#include "QhullFacetSet.h"
    +#include "QhullHyperplane.h"
    +#include "QhullPoint.h"
    +#include "QhullPoints.h"
    +#include "QhullPointSet.h"
    +#include "QhullVertex.h"
    +#include "QhullVertexSet.h"
    +
    +namespace orgQhull {
    +
    +#//!\name Conversions
    +
    +QList Coordinates::
    +toQList() const
    +{
    +    CoordinatesIterator i(*this);
    +    QList cs;
    +    while(i.hasNext()){
    +        cs.append(i.next());
    +    }
    +    return cs;
    +}//toQList
    +
    +QList QhullFacetList::
    +toQList() const
    +{
    +    QhullLinkedListIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        QhullFacet f= i.next();
    +        if(isSelectAll() || f.isGood()){
    +            vs.append(f);
    +        }
    +    }
    +    return vs;
    +}//toQList
    +
    +//! Same as PrintVertices
    +QList QhullFacetList::
    +vertices_toQList() const
    +{
    +    QList vs;
    +    QhullVertexSet qvs(qh(), first().getFacetT(), NULL, isSelectAll());
    +    for(QhullVertexSet::iterator i=qvs.begin(); i!=qvs.end(); ++i){
    +        vs.push_back(*i);
    +    }
    +    return vs;
    +}//vertices_toQList
    +
    +QList QhullFacetSet::
    +toQList() const
    +{
    +    QhullSetIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        QhullFacet f= i.next();
    +        if(isSelectAll() || f.isGood()){
    +            vs.append(f);
    +        }
    +    }
    +    return vs;
    +}//toQList
    +
    +#ifdef QHULL_USES_QT
    +QList QhullHyperplane::
    +toQList() const
    +{
    +    QhullHyperplaneIterator i(*this);
    +    QList fs;
    +    while(i.hasNext()){
    +        fs.append(i.next());
    +    }
    +    fs.append(hyperplane_offset);
    +    return fs;
    +}//toQList
    +#endif //QHULL_USES_QT
    +
    +QList QhullPoint::
    +toQList() const
    +{
    +    QhullPointIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        vs.append(i.next());
    +    }
    +    return vs;
    +}//toQList
    +
    +QList QhullPoints::
    +toQList() const
    +{
    +    QhullPointsIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        vs.append(i.next());
    +    }
    +    return vs;
    +}//toQList
    +
    +/******
    +QList QhullPointSet::
    +toQList() const
    +{
    +    QhullPointSetIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        vs.append(i.next());
    +    }
    +    return vs;
    +}//toQList
    +*/
    +}//orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/usermem_r-cpp.cpp b/xs/src/qhull/src/libqhullcpp/usermem_r-cpp.cpp
    new file mode 100644
    index 0000000000..bb9534d098
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/usermem_r-cpp.cpp
    @@ -0,0 +1,93 @@
    +/*
      ---------------------------------
    +
    +   usermem_r-cpp.cpp
    +
    +   Redefine qh_exit() as 'throw std::runtime_error("QH10003 ...")'
    +
    +   This file is not included in the Qhull builds.
    +
    +   qhull_r calls qh_exit() when qh_errexit() is not available.  For example,
    +   it calls qh_exit() if you linked the wrong qhull library.
    +
    +   The C++ interface avoids most of the calls to qh_exit().
    +
    +   If needed, include usermem_r-cpp.o before libqhullstatic_r.a.  You may need to
    +   override duplicate symbol errors (e.g. /FORCE:MULTIPLE for DevStudio).  It
    +   may produce a warning about throwing an error from C code.
    +*/
    +
    +extern "C" {
    +    void    qh_exit(int exitcode);
    +}
    +
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +  qh_exit( exitcode )
    +    exit program
    +
    +  notes:
    +    same as exit()
    +*/
    +void qh_exit(int exitcode) {
    +    exitcode= exitcode;
    +    throw std::runtime_error("QH10003 Qhull error.  See stderr or errfile.");
    +} /* exit */
    +
    +/*---------------------------------
    +
    +  qh_fprintf_stderr( msgcode, format, list of args )
    +    fprintf to stderr with msgcode (non-zero)
    +
    +  notes:
    +    qh_fprintf_stderr() is called when qh->ferr is not defined, usually due to an initialization error
    +
    +    It is typically followed by qh_errexit().
    +
    +    Redefine this function to avoid using stderr
    +
    +    Use qh_fprintf [userprintf_r.c] for normal printing
    +*/
    +void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    va_start(args, fmt);
    +    if(msgcode)
    +      fprintf(stderr, "QH%.4d ", msgcode);
    +    vfprintf(stderr, fmt, args);
    +    va_end(args);
    +} /* fprintf_stderr */
    +
    +/*---------------------------------
    +
    +  qh_free(qhT *qh, mem )
    +    free memory
    +
    +  notes:
    +    same as free()
    +    No calls to qh_errexit()
    +*/
    +void qh_free(void *mem) {
    +    free(mem);
    +} /* free */
    +
    +/*---------------------------------
    +
    +    qh_malloc( mem )
    +      allocate memory
    +
    +    notes:
    +      same as malloc()
    +*/
    +void *qh_malloc(size_t size) {
    +    return malloc(size);
    +} /* malloc */
    +
    +
    diff --git a/xs/src/qhull/src/libqhullstatic/libqhullstatic.pro b/xs/src/qhull/src/libqhullstatic/libqhullstatic.pro
    new file mode 100644
    index 0000000000..1a516db73c
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullstatic/libqhullstatic.pro
    @@ -0,0 +1,19 @@
    +# -------------------------------------------------
    +# libqhullstatic.pro -- Qt project for Qhull static library
    +#   Built with qh_QHpointer=0.  See libqhullp.pro
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +include(../qhull-libqhull-src.pri)
    +
    +DESTDIR = ../../lib
    +TEMPLATE = lib
    +CONFIG += staticlib warn_on
    +CONFIG -= qt
    +build_pass:CONFIG(debug, debug|release):{
    +    TARGET = qhullstatic_d
    +    OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +    TARGET = qhullstatic
    +    OBJECTS_DIR = Release
    +}
    diff --git a/xs/src/qhull/src/libqhullstatic_r/libqhullstatic_r.pro b/xs/src/qhull/src/libqhullstatic_r/libqhullstatic_r.pro
    new file mode 100644
    index 0000000000..2f5bf4d076
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullstatic_r/libqhullstatic_r.pro
    @@ -0,0 +1,21 @@
    +# -------------------------------------------------
    +# libqhullstatic_r.pro -- Qt project for Qhull static library
    +#
    +# It uses reeentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +DESTDIR = ../../lib
    +TEMPLATE = lib
    +CONFIG += staticlib warn_on
    +CONFIG -= qt
    +build_pass:CONFIG(debug, debug|release):{
    +    TARGET = qhullstatic_rd
    +    OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +    TARGET = qhullstatic_r
    +    OBJECTS_DIR = Release
    +}
    +
    +include(../qhull-libqhull-src_r.pri)
    diff --git a/xs/src/qhull/src/qconvex/qconvex.c b/xs/src/qhull/src/qconvex/qconvex.c
    new file mode 100644
    index 0000000000..41bd666da1
    --- /dev/null
    +++ b/xs/src/qhull/src/qconvex/qconvex.c
    @@ -0,0 +1,326 @@
    +/*
      ---------------------------------
    +
    +   qconvex.c
    +      compute convex hulls using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull/libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qconvex
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qconvex.htm */
    +char hidden_options[]=" d v H Qbb Qf Qg Qm Qr Qu Qv Qx Qz TR E V Fp Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qconvex- compute the convex hull\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar points with nearest facet\n\
    +    Qi   - keep interior points with nearest facet\n\
    +\n\
    +Qhull control options:\n\
    +    Qbk:n   - scale coord k so that low bound is n\n\
    +      QBk:n - scale coord k so that upper bound is n (QBk is %2.2g)\n\
    +    QbB  - scale input to unit cube centered at the origin\n\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    QGn  - good facet if visible from point n, -n for not visible\n\
    +    QVn  - good facet if it includes point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Un   - max distance below plane for a new, coplanar point\n\
    +    Wn   - min facet width for outside point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each facet\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    n    - normals with offsets\n\
    +    o    - OFF file format (dim, points and facets; Voronoi regions)\n\
    +    p    - point coordinates \n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each facet\n\
    +    FA   - compute total area and volume for option 's'\n\
    +    Fc   - count plus coplanar points for each facet\n\
    +           use 'Qc' (default) for coplanar and 'Qi' for interior\n\
    +    FC   - centrum for each facet\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - inner plane for each facet\n\
    +    FI   - ID for each facet\n\
    +    Fm   - merge count for each facet (511 max)\n\
    +    Fn   - count plus neighboring facets for each facet\n\
    +    FN   - count plus neighboring facets for each point\n\
    +    Fo   - outer plane (or max_outside) for each facet\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest vertex for each coplanar point\n\
    +    FQ   - command used for qconvex\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                      for output: #vertices, #facets,\n\
    +                                  #coplanar points, #non-simplicial facets\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0) \n\
    +                    #real (2) tot area, tot volume\n\
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)\n\
    +    Fv   - count plus vertices for each facet\n\
    +    FV   - average of vertices (a feasible point for 'H')\n\
    +    Fx   - extreme points (in order for 2-d)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview output (2-d, 3-d, and 4-d)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qconvex- compute the convex hull.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qconvex.htm):\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each facet\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates (includes coplanar points if 'Qc')\n\
    +    Fx   - extreme points (convex hull vertices)\n\
    +    FA   - report total area and volume\n\
    +    FS   - compute total area and volume\n\
    +    o    - OFF format (dim, n, points, facets)\n\
    +    G    - Geomview output (2-d, 3-d, and 4-d)\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    QVn  - print facets that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox c D2 | qconvex s n                    rbox c D2 | qconvex i\n\
    +    rbox c D2 | qconvex o                      rbox 1000 s | qconvex s Tv FA\n\
    +    rbox c d D2 | qconvex s Qc Fx              rbox y 1000 W0 | qconvex s n\n\
    +    rbox y 1000 W0 | qconvex s QJ              rbox d G1 D12 | qconvex QR0 FA Pp\n\
    +    rbox c D7 | qconvex FA TF1000\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + incidences     mathematica    normals        OFF_format     points\n\
    + summary        facet_dump\n\
    +\n\
    + Farea          FArea_total    Fcoplanars     FCentrums      Fd_cdd_in\n\
    + FD_cdd_out     FFacet_xridge  Finner         FIDs           Fmerges\n\
    + Fneighbors     FNeigh_vertex  Fouter         FOptions       FPoint_near\n\
    + FQhull         Fsummary       FSize          Fvertices      FVertex_ave\n\
    + Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   PFacet_area_keep Pgood        PGood_neighbors\n\
    + PMerge_keep    Poutput_forced Pprecision_not\n\
    +\n\
    + QbBound 0:0.5  QbB_scale_box  Qcoplanar      QGood_point    Qinterior\n\
    + QJoggle        Qrandom        QRotate        Qsearch_1st    Qtriangulate\n\
    + QVertex_good\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version, qh_DEFAULTbox,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh NOerrexit = False;
    +    qh_checkflags(qh qhull_command, hidden_options);
    +    qh_initflags(qh qhull_command);
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option("Qxact_merge", NULL, NULL);
    +      qh MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh_ALL);
    +#else
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qconvex/qconvex.pro b/xs/src/qhull/src/qconvex/qconvex.pro
    new file mode 100644
    index 0000000000..1bf631bff6
    --- /dev/null
    +++ b/xs/src/qhull/src/qconvex/qconvex.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qconvex.pro -- Qt project file for qconvex.exe
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = qconvex
    +
    +SOURCES += qconvex.c
    diff --git a/xs/src/qhull/src/qconvex/qconvex_r.c b/xs/src/qhull/src/qconvex/qconvex_r.c
    new file mode 100644
    index 0000000000..abf68ce37e
    --- /dev/null
    +++ b/xs/src/qhull/src/qconvex/qconvex_r.c
    @@ -0,0 +1,328 @@
    +/*
      ---------------------------------
    +
    +   qconvex.c
    +      compute convex hulls using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qconvex
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qconvex.htm */
    +char hidden_options[]=" d v H Qbb Qf Qg Qm Qr Qu Qv Qx Qz TR E V Fp Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qconvex- compute the convex hull\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar points with nearest facet\n\
    +    Qi   - keep interior points with nearest facet\n\
    +\n\
    +Qhull control options:\n\
    +    Qbk:n   - scale coord k so that low bound is n\n\
    +      QBk:n - scale coord k so that upper bound is n (QBk is %2.2g)\n\
    +    QbB  - scale input to unit cube centered at the origin\n\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    QGn  - good facet if visible from point n, -n for not visible\n\
    +    QVn  - good facet if it includes point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Un   - max distance below plane for a new, coplanar point\n\
    +    Wn   - min facet width for outside point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each facet\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    n    - normals with offsets\n\
    +    o    - OFF file format (dim, points and facets; Voronoi regions)\n\
    +    p    - point coordinates \n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each facet\n\
    +    FA   - compute total area and volume for option 's'\n\
    +    Fc   - count plus coplanar points for each facet\n\
    +           use 'Qc' (default) for coplanar and 'Qi' for interior\n\
    +    FC   - centrum for each facet\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - inner plane for each facet\n\
    +    FI   - ID for each facet\n\
    +    Fm   - merge count for each facet (511 max)\n\
    +    Fn   - count plus neighboring facets for each facet\n\
    +    FN   - count plus neighboring facets for each point\n\
    +    Fo   - outer plane (or max_outside) for each facet\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest vertex for each coplanar point\n\
    +    FQ   - command used for qconvex\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                      for output: #vertices, #facets,\n\
    +                                  #coplanar points, #non-simplicial facets\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0) \n\
    +                    #real (2) tot area, tot volume\n\
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)\n\
    +    Fv   - count plus vertices for each facet\n\
    +    FV   - average of vertices (a feasible point for 'H')\n\
    +    Fx   - extreme points (in order for 2-d)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview output (2-d, 3-d, and 4-d)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qconvex- compute the convex hull.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qconvex.htm):\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each facet\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates (includes coplanar points if 'Qc')\n\
    +    Fx   - extreme points (convex hull vertices)\n\
    +    FA   - report total area and volume\n\
    +    FS   - compute total area and volume\n\
    +    o    - OFF format (dim, n, points, facets)\n\
    +    G    - Geomview output (2-d, 3-d, and 4-d)\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    QVn  - print facets that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox c D2 | qconvex s n                    rbox c D2 | qconvex i\n\
    +    rbox c D2 | qconvex o                      rbox 1000 s | qconvex s Tv FA\n\
    +    rbox c d D2 | qconvex s Qc Fx              rbox y 1000 W0 | qconvex s n\n\
    +    rbox y 1000 W0 | qconvex s QJ              rbox d G1 D12 | qconvex QR0 FA Pp\n\
    +    rbox c D7 | qconvex FA TF1000\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + incidences     mathematica    normals        OFF_format     points\n\
    + summary        facet_dump\n\
    +\n\
    + Farea          FArea_total    Fcoplanars     FCentrums      Fd_cdd_in\n\
    + FD_cdd_out     FFacet_xridge  Finner         FIDs           Fmerges\n\
    + Fneighbors     FNeigh_vertex  Fouter         FOptions       FPoint_near\n\
    + FQhull         Fsummary       FSize          Fvertices      FVertex_ave\n\
    + Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   PFacet_area_keep Pgood        PGood_neighbors\n\
    + PMerge_keep    Poutput_forced Pprecision_not\n\
    +\n\
    + QbBound 0:0.5  QbB_scale_box  Qcoplanar      QGood_point    Qinterior\n\
    + QJoggle        Qrandom        QRotate        Qsearch_1st    Qtriangulate\n\
    + QVertex_good\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version, qh_DEFAULTbox,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_checkflags(qh, qh->qhull_command, hidden_options);
    +    qh_initflags(qh, qh->qhull_command);
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option(qh, "Qxact_merge", NULL, NULL);
    +      qh->MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qdelaunay/qdelaun.c b/xs/src/qhull/src/qdelaunay/qdelaun.c
    new file mode 100644
    index 0000000000..9011d9fcc0
    --- /dev/null
    +++ b/xs/src/qhull/src/qdelaunay/qdelaun.c
    @@ -0,0 +1,315 @@
    +/*
      ---------------------------------
    +
    +   qdelaun.c
    +     compute Delaunay triangulations and furthest-point Delaunay
    +     triangulations using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull/libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qdelau_f.htm and qdelaun.htm */
    +char hidden_options[]=" d n v H U Qb QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V FC Fi Fo Ft Fp FV Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qdelaunay- compute the Delaunay triangulation\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qu   - compute furthest-site Delaunay triangulation\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +\n\
    +Qhull control options:\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    Qz   - add point-at-infinity to Delaunay triangulation\n\
    +    QGn  - print Delaunay region if visible from point n, -n if not\n\
    +    QVn  - print Delaunay regions that include point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Wn   - min facet width for outside point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each Delaunay region\n\
    +    m    - Mathematica output (2-d only, lifted to a paraboloid)\n\
    +    o    - OFF format (dim, points, and facets as a paraboloid)\n\
    +    p    - point coordinates (lifted to a paraboloid)\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each Delaunay region\n\
    +    FA   - compute total area for option 's'\n\
    +    Fc   - count plus coincident points for each Delaunay region\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    FI   - ID of each Delaunay region\n\
    +    Fm   - merge count for each Delaunay region (511 max)\n\
    +    FM   - Maple output (2-d only, lifted to a paraboloid)\n\
    +    Fn   - count plus neighboring region for each Delaunay region\n\
    +    FN   - count plus neighboring region for each point\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest point and distance for each coincident point\n\
    +    FQ   - command used for qdelaunay\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                    for output: #vertices, #Delaunay regions,\n\
    +                                #coincident points, #non-simplicial regions\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0)\n\
    +                    #real (2), tot area, 0\n\
    +    Fv   - count plus vertices for each Delaunay region\n\
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d and 3-d)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc     - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +    Gt   - transparent outer ridges to view 3-d Delaunay\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest Delaunay regions by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good Delaunay regions (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep Delaunay regions whose area is at least n\n\
    +    PG   - print neighbors of good regions (needs 'QGn' or 'QVn')\n\
    +    PMn  - keep n Delaunay regions with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qdelaunay- compute the Delaunay triangulation.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qdelaun.htm):\n\
    +    Qu   - furthest-site Delaunay triangulation\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each Delaunay region\n\
    +    Fx   - extreme points (vertices of the convex hull)\n\
    +    o    - OFF format (shows the points lifted to a paraboloid)\n\
    +    G    - Geomview output (2-d and 3-d points lifted to a paraboloid)\n\
    +    m    - Mathematica output (2-d inputs lifted to a paraboloid)\n\
    +    QVn  - print Delaunay regions that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox c P0 D2 | qdelaunay s o          rbox c P0 D2 | qdelaunay i\n\
    +    rbox c P0 D2 | qdelaunay Fv           rbox c P0 D2 | qdelaunay s Qu Fv\n\
    +    rbox c G1 d D2 | qdelaunay s i        rbox c G1 d D2 | qdelaunay Qt\n\
    +    rbox M3,4 z 100 D2 | qdelaunay s      rbox M3,4 z 100 D2 | qdelaunay s Qt\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + incidences     mathematica    OFF_format     points_lifted  summary\n\
    + facet_dump\n\
    +\n\
    + Farea          FArea_total    Fcoincident    Fd_cdd_in      FD_cdd_out\n\
    + FF_dump_xridge FIDs           Fmerges        Fneighbors     FNeigh_vertex\n\
    + FOptions       FPoint_near    FQdelaun       Fsummary       FSize\n\
    + Fvertices      Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    + Gtransparent\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QGood_point    QJoggle        Qsearch_1st    Qtriangulate   QupperDelaunay\n\
    + QVertex_good   Qzinfinite\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh NOerrexit = False;
    +    qh_option("delaunay  Qbbound-last", NULL, NULL);
    +    qh DELAUNAY= True;     /* 'd'   */
    +    qh SCALElast= True;    /* 'Qbb' */
    +    qh KEEPcoplanar= True; /* 'Qc', to keep coplanars in 'p' */
    +    qh_checkflags(qh qhull_command, hidden_options);
    +    qh_initflags(qh qhull_command);
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option("Qxact_merge", NULL, NULL);
    +      qh MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh_ALL);
    +#else
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qdelaunay/qdelaun_r.c b/xs/src/qhull/src/qdelaunay/qdelaun_r.c
    new file mode 100644
    index 0000000000..0854b8bb97
    --- /dev/null
    +++ b/xs/src/qhull/src/qdelaunay/qdelaun_r.c
    @@ -0,0 +1,317 @@
    +/*
      ---------------------------------
    +
    +   qdelaun.c
    +     compute Delaunay triangulations and furthest-point Delaunay
    +     triangulations using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qdelau_f.htm and qdelaun.htm */
    +char hidden_options[]=" d n v H U Qb QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V FC Fi Fo Ft Fp FV Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qdelaunay- compute the Delaunay triangulation\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qu   - compute furthest-site Delaunay triangulation\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +\n\
    +Qhull control options:\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    Qz   - add point-at-infinity to Delaunay triangulation\n\
    +    QGn  - print Delaunay region if visible from point n, -n if not\n\
    +    QVn  - print Delaunay regions that include point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Wn   - min facet width for outside point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each Delaunay region\n\
    +    m    - Mathematica output (2-d only, lifted to a paraboloid)\n\
    +    o    - OFF format (dim, points, and facets as a paraboloid)\n\
    +    p    - point coordinates (lifted to a paraboloid)\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each Delaunay region\n\
    +    FA   - compute total area for option 's'\n\
    +    Fc   - count plus coincident points for each Delaunay region\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    FI   - ID of each Delaunay region\n\
    +    Fm   - merge count for each Delaunay region (511 max)\n\
    +    FM   - Maple output (2-d only, lifted to a paraboloid)\n\
    +    Fn   - count plus neighboring region for each Delaunay region\n\
    +    FN   - count plus neighboring region for each point\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest point and distance for each coincident point\n\
    +    FQ   - command used for qdelaunay\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                    for output: #vertices, #Delaunay regions,\n\
    +                                #coincident points, #non-simplicial regions\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0)\n\
    +                    #real (2), tot area, 0\n\
    +    Fv   - count plus vertices for each Delaunay region\n\
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d and 3-d)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc     - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +    Gt   - transparent outer ridges to view 3-d Delaunay\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest Delaunay regions by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good Delaunay regions (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep Delaunay regions whose area is at least n\n\
    +    PG   - print neighbors of good regions (needs 'QGn' or 'QVn')\n\
    +    PMn  - keep n Delaunay regions with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qdelaunay- compute the Delaunay triangulation.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qdelaun.htm):\n\
    +    Qu   - furthest-site Delaunay triangulation\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each Delaunay region\n\
    +    Fx   - extreme points (vertices of the convex hull)\n\
    +    o    - OFF format (shows the points lifted to a paraboloid)\n\
    +    G    - Geomview output (2-d and 3-d points lifted to a paraboloid)\n\
    +    m    - Mathematica output (2-d inputs lifted to a paraboloid)\n\
    +    QVn  - print Delaunay regions that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox c P0 D2 | qdelaunay s o          rbox c P0 D2 | qdelaunay i\n\
    +    rbox c P0 D2 | qdelaunay Fv           rbox c P0 D2 | qdelaunay s Qu Fv\n\
    +    rbox c G1 d D2 | qdelaunay s i        rbox c G1 d D2 | qdelaunay Qt\n\
    +    rbox M3,4 z 100 D2 | qdelaunay s      rbox M3,4 z 100 D2 | qdelaunay s Qt\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + incidences     mathematica    OFF_format     points_lifted  summary\n\
    + facet_dump\n\
    +\n\
    + Farea          FArea_total    Fcoincident    Fd_cdd_in      FD_cdd_out\n\
    + FF_dump_xridge FIDs           Fmerges        Fneighbors     FNeigh_vertex\n\
    + FOptions       FPoint_near    FQdelaun       Fsummary       FSize\n\
    + Fvertices      Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    + Gtransparent\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QGood_point    QJoggle        Qsearch_1st    Qtriangulate   QupperDelaunay\n\
    + QVertex_good   Qzinfinite\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_option(qh, "delaunay  Qbbound-last", NULL, NULL);
    +    qh->DELAUNAY= True;     /* 'd'   */
    +    qh->SCALElast= True;    /* 'Qbb' */
    +    qh->KEEPcoplanar= True; /* 'Qc', to keep coplanars in 'p' */
    +    qh_checkflags(qh, qh->qhull_command, hidden_options);
    +    qh_initflags(qh, qh->qhull_command);
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option(qh, "Qxact_merge", NULL, NULL);
    +      qh->MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qdelaunay/qdelaunay.pro b/xs/src/qhull/src/qdelaunay/qdelaunay.pro
    new file mode 100644
    index 0000000000..138ac0589d
    --- /dev/null
    +++ b/xs/src/qhull/src/qdelaunay/qdelaunay.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qdelaunay.pro -- Qt project file for qvoronoi.exe
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = qdelaunay
    +
    +SOURCES += qdelaun.c
    diff --git a/xs/src/qhull/src/qhalf/qhalf.c b/xs/src/qhull/src/qhalf/qhalf.c
    new file mode 100644
    index 0000000000..4a5889ed78
    --- /dev/null
    +++ b/xs/src/qhull/src/qhalf/qhalf.c
    @@ -0,0 +1,316 @@
    +/*
      ---------------------------------
    +
    +   qhalf.c
    +     compute the intersection of halfspaces about a point
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull/libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qhalf.htm */
    +char hidden_options[]=" d n v Qbb QbB Qf Qg Qm Qr QR Qv Qx Qz TR E V Fa FA FC FD FS Ft FV Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qhalf- compute the intersection of halfspaces about a point\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    optional interior point: dimension, 1, coordinates\n\
    +    first lines: dimension+1 and number of halfspaces\n\
    +    other lines: halfspace coefficients followed by offset\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Hn,n - specify coordinates of interior point\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar halfspaces\n\
    +    Qi   - keep other redundant halfspaces\n\
    +\n\
    +Qhull control options:\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    Qs   - search all halfspaces for the initial simplex\n\
    +    QGn  - print intersection if visible to halfspace n, -n for not\n\
    +    QVn  - print intersections for halfspace n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and redundancy\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when halfspace n added to intersection\n\
    +    TMn  - turn on tracing at merge n\n\
    +    TWn  - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding halfspace n, -n for before (see TCn)\n\
    +    TCn  - stop qhull after building cone for halfspace n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Un   - max distance below plane for a new, coplanar halfspace\n\
    +    Wn   - min facet width for outside halfspace (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (dual convex hull)\n\
    +    i    - non-redundant halfspaces incident to each intersection\n\
    +    m    - Mathematica output (dual convex hull)\n\
    +    o    - OFF format (dual convex hull: dimension, points, and facets)\n\
    +    p    - vertex coordinates of dual convex hull (coplanars if 'Qc' or 'Qi')\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fc   - count plus redundant halfspaces for each intersection\n\
    +         -   Qc (default) for coplanar and Qi for other redundant\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    FI   - ID of each intersection\n\
    +    Fm   - merge count for each intersection (511 max)\n\
    +    FM   - Maple output (dual convex hull)\n\
    +    Fn   - count plus neighboring intersections for each intersection\n\
    +    FN   - count plus intersections for each non-redundant halfspace\n\
    +    FO   - options and precision constants\n\
    +    Fp   - dim, count, and intersection coordinates\n\
    +    FP   - nearest halfspace and distance for each redundant halfspace\n\
    +    FQ   - command used for qhalf\n\
    +    Fs   - summary: #int (8), dim, #halfspaces, #non-redundant, #intersections\n\
    +                      for output: #non-redundant, #intersections, #coplanar\n\
    +                                  halfspaces, #non-simplicial intersections\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    Fv   - count plus non-redundant halfspaces for each intersection\n\
    +    Fx   - non-redundant halfspaces\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview output (2-d, 3-d and 4-d; dual convex hull)\n\
    +    Ga   - all points (i.e., transformed halfspaces) as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices (i.e., non-redundant halfspaces) as spheres\n\
    +    Gi   - inner planes (i.e., halfspace intersections) only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets (i.e., intersections) by area\n\
    +    Pdk:n- drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n- drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qhalf- halfspace intersection about a point.  Qhull %s\n\
    +    input (stdin): [dim, 1, interior point], dim+1, n, coefficients+offset\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qhalf.htm):\n\
    +    Hn,n - specify coordinates of interior point\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and redundancy\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    Fp   - intersection coordinates\n\
    +    Fv   - non-redundant halfspaces incident to each intersection\n\
    +    Fx   - non-redundant halfspaces\n\
    +    o    - OFF file format (dual convex hull)\n\
    +    G    - Geomview output (dual convex hull)\n\
    +    m    - Mathematica output (dual convex hull)\n\
    +    QVn  - print intersections for halfspace n, -n if not\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox d | qconvex FQ n | qhalf s H0,0,0 Fp\n\
    +    rbox c | qconvex FQ FV n | qhalf s i\n\
    +    rbox c | qconvex FQ FV n | qhalf s o\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper_case options take an argument.\n\
    +\n\
    + incidences     Geomview       mathematica    OFF_format     point_dual\n\
    + summary        facet_dump\n\
    +\n\
    + Fc_redundant   Fd_cdd_in      FF_dump_xridge FIDs           Fmerges\n\
    + Fneighbors     FN_intersect   FOptions       Fp_coordinates FP_nearest\n\
    + FQhalf         Fsummary       Fv_halfspace   FMaple         Fx_non_redundant\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + Qbk:0Bk:0_drop Qcoplanar      QG_half_good   Qi_redundant   QJoggle\n\
    + Qsearch_1st    Qtriangulate   QVertex_good\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +        qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh NOerrexit = False;
    +    qh_option("Halfspace", NULL, NULL);
    +    qh HALFspace= True;    /* 'H'   */
    +    qh_checkflags(qh qhull_command, hidden_options);
    +    qh_initflags(qh qhull_command);
    +    if (qh SCALEinput) {
    +      fprintf(qh ferr, "\
    +qhull error: options 'Qbk:n' and 'QBk:n' are not used with qhalf.\n\
    +             Use 'Qbk:0Bk:0 to drop dimension k.\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option("Qxact_merge", NULL, NULL);
    +      qh MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh_ALL);
    +#else
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qhalf/qhalf.pro b/xs/src/qhull/src/qhalf/qhalf.pro
    new file mode 100644
    index 0000000000..ebad387893
    --- /dev/null
    +++ b/xs/src/qhull/src/qhalf/qhalf.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qhalf.pro -- Qt project file for qconvex.exe
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = qhalf
    +
    +SOURCES += qhalf.c
    diff --git a/xs/src/qhull/src/qhalf/qhalf_r.c b/xs/src/qhull/src/qhalf/qhalf_r.c
    new file mode 100644
    index 0000000000..c49d777f95
    --- /dev/null
    +++ b/xs/src/qhull/src/qhalf/qhalf_r.c
    @@ -0,0 +1,318 @@
    +/*
      ---------------------------------
    +
    +   qhalf.c
    +     compute the intersection of halfspaces about a point
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qhalf.htm */
    +char hidden_options[]=" d n v Qbb QbB Qf Qg Qm Qr QR Qv Qx Qz TR E V Fa FA FC FD FS Ft FV Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qhalf- compute the intersection of halfspaces about a point\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    optional interior point: dimension, 1, coordinates\n\
    +    first lines: dimension+1 and number of halfspaces\n\
    +    other lines: halfspace coefficients followed by offset\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Hn,n - specify coordinates of interior point\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar halfspaces\n\
    +    Qi   - keep other redundant halfspaces\n\
    +\n\
    +Qhull control options:\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    Qs   - search all halfspaces for the initial simplex\n\
    +    QGn  - print intersection if visible to halfspace n, -n for not\n\
    +    QVn  - print intersections for halfspace n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and redundancy\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when halfspace n added to intersection\n\
    +    TMn  - turn on tracing at merge n\n\
    +    TWn  - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding halfspace n, -n for before (see TCn)\n\
    +    TCn  - stop qhull after building cone for halfspace n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Un   - max distance below plane for a new, coplanar halfspace\n\
    +    Wn   - min facet width for outside halfspace (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (dual convex hull)\n\
    +    i    - non-redundant halfspaces incident to each intersection\n\
    +    m    - Mathematica output (dual convex hull)\n\
    +    o    - OFF format (dual convex hull: dimension, points, and facets)\n\
    +    p    - vertex coordinates of dual convex hull (coplanars if 'Qc' or 'Qi')\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fc   - count plus redundant halfspaces for each intersection\n\
    +         -   Qc (default) for coplanar and Qi for other redundant\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    FI   - ID of each intersection\n\
    +    Fm   - merge count for each intersection (511 max)\n\
    +    FM   - Maple output (dual convex hull)\n\
    +    Fn   - count plus neighboring intersections for each intersection\n\
    +    FN   - count plus intersections for each non-redundant halfspace\n\
    +    FO   - options and precision constants\n\
    +    Fp   - dim, count, and intersection coordinates\n\
    +    FP   - nearest halfspace and distance for each redundant halfspace\n\
    +    FQ   - command used for qhalf\n\
    +    Fs   - summary: #int (8), dim, #halfspaces, #non-redundant, #intersections\n\
    +                      for output: #non-redundant, #intersections, #coplanar\n\
    +                                  halfspaces, #non-simplicial intersections\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    Fv   - count plus non-redundant halfspaces for each intersection\n\
    +    Fx   - non-redundant halfspaces\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview output (2-d, 3-d and 4-d; dual convex hull)\n\
    +    Ga   - all points (i.e., transformed halfspaces) as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices (i.e., non-redundant halfspaces) as spheres\n\
    +    Gi   - inner planes (i.e., halfspace intersections) only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets (i.e., intersections) by area\n\
    +    Pdk:n- drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n- drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qhalf- halfspace intersection about a point.  Qhull %s\n\
    +    input (stdin): [dim, 1, interior point], dim+1, n, coefficients+offset\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qhalf.htm):\n\
    +    Hn,n - specify coordinates of interior point\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and redundancy\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    Fp   - intersection coordinates\n\
    +    Fv   - non-redundant halfspaces incident to each intersection\n\
    +    Fx   - non-redundant halfspaces\n\
    +    o    - OFF file format (dual convex hull)\n\
    +    G    - Geomview output (dual convex hull)\n\
    +    m    - Mathematica output (dual convex hull)\n\
    +    QVn  - print intersections for halfspace n, -n if not\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox d | qconvex FQ n | qhalf s H0,0,0 Fp\n\
    +    rbox c | qconvex FQ FV n | qhalf s i\n\
    +    rbox c | qconvex FQ FV n | qhalf s o\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper_case options take an argument.\n\
    +\n\
    + incidences     Geomview       mathematica    OFF_format     point_dual\n\
    + summary        facet_dump\n\
    +\n\
    + Fc_redundant   Fd_cdd_in      FF_dump_xridge FIDs           Fmerges\n\
    + Fneighbors     FN_intersect   FOptions       Fp_coordinates FP_nearest\n\
    + FQhalf         Fsummary       Fv_halfspace   FMaple         Fx_non_redundant\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + Qbk:0Bk:0_drop Qcoplanar      QG_half_good   Qi_redundant   QJoggle\n\
    + Qsearch_1st    Qtriangulate   QVertex_good\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +        qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_option(qh, "Halfspace", NULL, NULL);
    +    qh->HALFspace= True;    /* 'H'   */
    +    qh_checkflags(qh, qh->qhull_command, hidden_options);
    +    qh_initflags(qh, qh->qhull_command);
    +    if (qh->SCALEinput) {
    +      fprintf(qh->ferr, "\
    +qhull error: options 'Qbk:n' and 'QBk:n' are not used with qhalf.\n\
    +             Use 'Qbk:0Bk:0 to drop dimension k.\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option(qh, "Qxact_merge", NULL, NULL);
    +      qh->MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qhull-all.pro b/xs/src/qhull/src/qhull-all.pro
    new file mode 100644
    index 0000000000..1d3a0ac6f3
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-all.pro
    @@ -0,0 +1,94 @@
    +# -------------------------------------------------
    +# qhull-all.pro -- Qt project to build executables and static libraries
    +#
    +# To build with Qt on mingw
    +#   Download Qt SDK, install Perl
    +#   /c/qt/2010.05/qt> ./configure -static -platform win32-g++ -fast -no-qt3support
    +#
    +# To build DevStudio sln and proj files (Qhull ships with cmake derived files)
    +# qmake is in Qt's bin directory
    +# mkdir -p build && cd build && qmake -tp vc -r ../src/qhull-all.pro
    +# Additional Library Directories -- C:\qt\Qt5.2.0\5.2.0\msvc2012_64\lib
    +# libqhullcpp and libqhullstatic refered to $(QTDIR) but apparently didn't retrieve (should be %QTDIR%?)
    +# libqhull_r also needs ..\..\lib
    +# Need to change build/x64/Debug/*.lib to lib/ (or copy libs by hand, each time)
    +# Additional Build Dependencies
    +# See README.txt -- Need to add Build Dependencies, disable rtti, rename targets to qhull.dll, qhull6_p.dll and qhull6_pd.dll
    +# -------------------------------------------------
    +
    +TEMPLATE = subdirs
    +CONFIG += ordered
    +
    +SUBDIRS += libqhull_r      #shared library with reentrant code
    +SUBDIRS += libqhullstatic  #static library
    +SUBDIRS += libqhullstatic_r #static library with reentrant code
    +SUBDIRS += libqhullcpp     #static library for C++ interface with libqhullstatic_r
    +
    +SUBDIRS += qhull           #qhull program linked to libqhullstatic_r
    +SUBDIRS += rbox         
    +SUBDIRS += qconvex         #qhull programs linked to libqhullstatic
    +SUBDIRS += qdelaunay
    +SUBDIRS += qhalf
    +SUBDIRS += qvoronoi
    +
    +SUBDIRS += user_eg         #user programs linked to libqhull_r
    +SUBDIRS += user_eg2  
    +SUBDIRS += user_eg3        #user program with libqhullcpp and libqhullstatic_r
    +
    +SUBDIRS += qhulltest       #C++ test program with Qt, libqhullcpp, and libqhullstatic_r
    +SUBDIRS += testqset        #test program for qset.c with mem.c
    +SUBDIRS += testqset_r      #test program for qset_r.c with mem_r.c
    +                           #See eg/q_test for qhull tests
    +
    +OTHER_FILES += Changes.txt
    +OTHER_FILES += CMakeLists.txt
    +OTHER_FILES += Make-config.sh
    +OTHER_FILES += ../Announce.txt
    +OTHER_FILES += ../CMakeLists.txt
    +OTHER_FILES += ../COPYING.txt
    +OTHER_FILES += ../File_id.diz
    +OTHER_FILES += ../index.htm
    +OTHER_FILES += ../Makefile
    +OTHER_FILES += ../README.txt
    +OTHER_FILES += ../REGISTER.txt
    +OTHER_FILES += ../eg/q_eg
    +OTHER_FILES += ../eg/q_egtest
    +OTHER_FILES += ../eg/q_test
    +OTHER_FILES += ../html/index.htm
    +OTHER_FILES += ../html/qconvex.htm
    +OTHER_FILES += ../html/qdelau_f.htm
    +OTHER_FILES += ../html/qdelaun.htm
    +OTHER_FILES += ../html/qhalf.htm
    +OTHER_FILES += ../html/qh-code.htm
    +OTHER_FILES += ../html/qh-eg.htm
    +OTHER_FILES += ../html/qh-faq.htm
    +OTHER_FILES += ../html/qh-get.htm
    +OTHER_FILES += ../html/qh-impre.htm
    +OTHER_FILES += ../html/qh-optc.htm
    +OTHER_FILES += ../html/qh-optf.htm
    +OTHER_FILES += ../html/qh-optg.htm
    +OTHER_FILES += ../html/qh-opto.htm
    +OTHER_FILES += ../html/qh-optp.htm
    +OTHER_FILES += ../html/qh-optq.htm
    +OTHER_FILES += ../html/qh-optt.htm
    +OTHER_FILES += ../html/qh-quick.htm
    +OTHER_FILES += ../html/qhull.htm
    +OTHER_FILES += ../html/qhull.man
    +OTHER_FILES += ../html/qhull.txt
    +OTHER_FILES += ../html/qhull-cpp.xml
    +OTHER_FILES += ../html/qvoron_f.htm
    +OTHER_FILES += ../html/qvoronoi.htm
    +OTHER_FILES += ../html/rbox.htm
    +OTHER_FILES += ../html/rbox.man
    +OTHER_FILES += ../html/rbox.txt
    +OTHER_FILES += ../src/libqhull/Makefile
    +OTHER_FILES += ../src/libqhull_r/Makefile
    +OTHER_FILES += ../src/libqhull_r/qhull_r-exports.def
    +OTHER_FILES += ../src/qconvex/qconvex_r.c
    +OTHER_FILES += ../src/qdelaunay/qdelaun_r.c
    +OTHER_FILES += ../src/qhalf/qhalf_r.c
    +OTHER_FILES += ../src/qhull/rbox_r.c
    +OTHER_FILES += ../src/qvoronoi/qvoronoi_r.c
    +OTHER_FILES += ../src/qhull/unix.c
    +OTHER_FILES += ../src/user_eg/user_eg.c
    +OTHER_FILES += ../src/user_eg2/user_eg2.c
    diff --git a/xs/src/qhull/src/qhull-app-c.pri b/xs/src/qhull/src/qhull-app-c.pri
    new file mode 100644
    index 0000000000..05e5a00f28
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-c.pri
    @@ -0,0 +1,24 @@
    +# -------------------------------------------------
    +# qhull-app-c.pri -- Qt include project for C qhull applications linked to libqhull
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhullstatic_d
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhullstatic
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +INCLUDEPATH += ..
    +CONFIG += qhull_warn_conversion
    +
    diff --git a/xs/src/qhull/src/qhull-app-c_r.pri b/xs/src/qhull/src/qhull-app-c_r.pri
    new file mode 100644
    index 0000000000..9c2ef5600b
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-c_r.pri
    @@ -0,0 +1,26 @@
    +# -------------------------------------------------
    +# qhull-app-c_r.pri -- Qt include project for C qhull applications linked to qhullstatic_r
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhullstatic_rd
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhullstatic_r
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +INCLUDEPATH += ..
    +CONFIG += qhull_warn_conversion
    +
    diff --git a/xs/src/qhull/src/qhull-app-cpp.pri b/xs/src/qhull/src/qhull-app-cpp.pri
    new file mode 100644
    index 0000000000..a6f17d8ec4
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-cpp.pri
    @@ -0,0 +1,23 @@
    +# -------------------------------------------------
    +# qhull-app-cpp.pri -- Qt include project for qhull as C++ classes
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= rtti
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhullcpp_d
    +   LIBS += -lqhullstatic_rd  # Must be last, otherwise qh_fprintf,etc. are loaded from here instead of qhullcpp-d.lib
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhullcpp
    +   LIBS += -lqhullstatic_r  # Must be last, otherwise qh_fprintf,etc. are loaded from here instead of qhullcpp.lib
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +INCLUDEPATH += ../../src # "libqhull_r/qhull_a.h"
    diff --git a/xs/src/qhull/src/qhull-app-shared.pri b/xs/src/qhull/src/qhull-app-shared.pri
    new file mode 100644
    index 0000000000..1f4026a6aa
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-shared.pri
    @@ -0,0 +1,27 @@
    +# -------------------------------------------------
    +# qhull-app-shared.pri -- Deprecated Qt include project for C qhull applications linked with libqhull (shared library)
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhull_d
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhull
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +win32-msvc* : DEFINES += qh_dllimport # libqhull/user.h
    +
    +INCLUDEPATH += ../libqhull
    +CONFIG += qhull_warn_conversion
    +
    +
    diff --git a/xs/src/qhull/src/qhull-app-shared_r.pri b/xs/src/qhull/src/qhull-app-shared_r.pri
    new file mode 100644
    index 0000000000..e55c1a65f8
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-shared_r.pri
    @@ -0,0 +1,29 @@
    +# -------------------------------------------------
    +# qhull-app-shared_r.pri -- Qt include project for C qhull applications linked with libqhull_r (shared library)
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhull_rd
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhull_r
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +win32-msvc* : DEFINES += qh_dllimport # libqhull_r/user.h
    +
    +INCLUDEPATH += ..
    +CONFIG += qhull_warn_conversion
    +
    +
    diff --git a/xs/src/qhull/src/qhull-libqhull-src.pri b/xs/src/qhull/src/qhull-libqhull-src.pri
    new file mode 100644
    index 0000000000..e7aff3f781
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-libqhull-src.pri
    @@ -0,0 +1,39 @@
    +# -------------------------------------------------
    +# qhull-libqhull-src.pri -- Qt include project for libqhull sources and headers
    +#   libqhull.pro, libqhullp.pro, and libqhulldll.pro are the same for SOURCES and HEADERS
    +# -------------------------------------------------
    +
    +# Order object files by frequency of execution.  Small files at end.
    +# Current directory is caller
    +
    +# libqhull/libqhull.pro and ../qhull-libqhull-src.pri have the same SOURCES and HEADERS
    +SOURCES += ../libqhull/global.c
    +SOURCES += ../libqhull/stat.c
    +SOURCES += ../libqhull/geom2.c
    +SOURCES += ../libqhull/poly2.c
    +SOURCES += ../libqhull/merge.c
    +SOURCES += ../libqhull/libqhull.c
    +SOURCES += ../libqhull/geom.c
    +SOURCES += ../libqhull/poly.c
    +SOURCES += ../libqhull/qset.c
    +SOURCES += ../libqhull/mem.c
    +SOURCES += ../libqhull/random.c
    +SOURCES += ../libqhull/usermem.c
    +SOURCES += ../libqhull/userprintf.c
    +SOURCES += ../libqhull/io.c
    +SOURCES += ../libqhull/user.c
    +SOURCES += ../libqhull/rboxlib.c
    +SOURCES += ../libqhull/userprintf_rbox.c
    +
    +# [2014] qmake locates the headers in the shadow build directory not the src directory
    +HEADERS += ../libqhull/geom.h
    +HEADERS += ../libqhull/io.h
    +HEADERS += ../libqhull/libqhull.h
    +HEADERS += ../libqhull/mem.h
    +HEADERS += ../libqhull/merge.h
    +HEADERS += ../libqhull/poly.h
    +HEADERS += ../libqhull/random.h
    +HEADERS += ../libqhull/qhull_a.h
    +HEADERS += ../libqhull/qset.h
    +HEADERS += ../libqhull/stat.h
    +HEADERS += ../libqhull/user.h
    diff --git a/xs/src/qhull/src/qhull-libqhull-src_r.pri b/xs/src/qhull/src/qhull-libqhull-src_r.pri
    new file mode 100644
    index 0000000000..3b53291b1b
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-libqhull-src_r.pri
    @@ -0,0 +1,39 @@
    +# -------------------------------------------------
    +# qhull-libqhull-src_r.pri -- Qt include project for libqhull_r sources and headers
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +# Order object files by frequency of execution.  Small files at end.
    +# Current directory is caller
    +
    +# libqhull_r/libqhull_r.pro and ../qhull-libqhull-src_r.pri have the same SOURCES and HEADERS
    +SOURCES += ../libqhull_r/global_r.c
    +SOURCES += ../libqhull_r/stat_r.c
    +SOURCES += ../libqhull_r/geom2_r.c
    +SOURCES += ../libqhull_r/poly2_r.c
    +SOURCES += ../libqhull_r/merge_r.c
    +SOURCES += ../libqhull_r/libqhull_r.c
    +SOURCES += ../libqhull_r/geom_r.c
    +SOURCES += ../libqhull_r/poly_r.c
    +SOURCES += ../libqhull_r/qset_r.c
    +SOURCES += ../libqhull_r/mem_r.c
    +SOURCES += ../libqhull_r/random_r.c
    +SOURCES += ../libqhull_r/usermem_r.c
    +SOURCES += ../libqhull_r/userprintf_r.c
    +SOURCES += ../libqhull_r/io_r.c
    +SOURCES += ../libqhull_r/user_r.c
    +SOURCES += ../libqhull_r/rboxlib_r.c
    +SOURCES += ../libqhull_r/userprintf_rbox_r.c
    +
    +HEADERS += ../libqhull_r/geom_r.h
    +HEADERS += ../libqhull_r/io_r.h
    +HEADERS += ../libqhull_r/libqhull_r.h
    +HEADERS += ../libqhull_r/mem_r.h
    +HEADERS += ../libqhull_r/merge_r.h
    +HEADERS += ../libqhull_r/poly_r.h
    +HEADERS += ../libqhull_r/random_r.h
    +HEADERS += ../libqhull_r/qhull_ra.h
    +HEADERS += ../libqhull_r/qset_r.h
    +HEADERS += ../libqhull_r/stat_r.h
    +HEADERS += ../libqhull_r/user_r.h
    diff --git a/xs/src/qhull/src/qhull-warn.pri b/xs/src/qhull/src/qhull-warn.pri
    new file mode 100644
    index 0000000000..7d0e7fa2f4
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-warn.pri
    @@ -0,0 +1,57 @@
    +# -------------------------------------------------
    +# qhull-warn.pri -- Qt project warnings for warn_on
    +#   CONFIG += qhull_warn_all        # Qhull compiles with all warnings except for qhull_warn_shadow and qhull_warn_conversion
    +#   CONFIG += qhull_warn_conversion # Warn in Qt and Qhull about conversion errors
    +#   CONFIG += qhull_warn_error      # Turn warnings into errors
    +#   CONFIG += qhull_warn_shadow     # Warn in Qt about shadowing of functions and fields
    +# -------------------------------------------------
    +
    +# [apr'11] VERSION works erratically for msvc builds
    +# VERSION = 7.2.0
    +qhull_SOVERSION = 7
    +
    +# Uncomment to report warnings as errors
    +#CONFIG += qhull_warn_error
    +
    +*g++{
    +    qhull_warn_error{
    +        QMAKE_CFLAGS_WARN_ON += -Werror
    +        QMAKE_CXXFLAGS_WARN_ON += -Werror
    +    }
    +
    +    QMAKE_CFLAGS_WARN_ON += -Wcast-qual -Wextra -Wshadow -Wwrite-strings
    +
    +    QMAKE_CXXFLAGS_WARN_ON += -Wcast-qual -Wextra -Wwrite-strings
    +    QMAKE_CXXFLAGS_WARN_ON += -Wno-sign-conversion
    +
    +    qhull_warn_shadow{
    +        QMAKE_CXXFLAGS_WARN_ON += -Wshadow     # Shadowing occurs in Qt, e.g., nested foreach
    +    }
    +
    +    qhull_warn_conversion{
    +        QMAKE_CFLAGS_WARN_ON += -Wno-sign-conversion   # libqhullstatic has many size_t vs. int warnings
    +        QMAKE_CFLAGS_WARN_ON += -Wconversion           # libqhullstatic has no workaround for bit-field conversions
    +        QMAKE_CXXFLAGS_WARN_ON += -Wconversion         # Qt has conversion errors for qbitarray and qpalette
    +    }
    +
    +    qhull_warn_all{
    +        QMAKE_CFLAGS_WARN_ON += -Waddress -Warray-bounds -Wchar-subscripts -Wclobbered -Wcomment -Wempty-body
    +        QMAKE_CFLAGS_WARN_ON += -Wformat -Wignored-qualifiers -Wimplicit-function-declaration -Wimplicit-int
    +        QMAKE_CFLAGS_WARN_ON += -Wmain -Wmissing-braces -Wmissing-field-initializers -Wmissing-parameter-type
    +        QMAKE_CFLAGS_WARN_ON += -Wnonnull -Wold-style-declaration -Woverride-init -Wparentheses
    +        QMAKE_CFLAGS_WARN_ON += -Wpointer-sign -Wreturn-type -Wsequence-point -Wsign-compare
    +        QMAKE_CFLAGS_WARN_ON += -Wsign-compare -Wstrict-aliasing -Wstrict-overflow=1 -Wswitch
    +        QMAKE_CFLAGS_WARN_ON += -Wtrigraphs -Wtype-limits -Wuninitialized -Wuninitialized
    +        QMAKE_CFLAGS_WARN_ON += -Wunknown-pragmas -Wunused-function -Wunused-label -Wunused-parameter
    +        QMAKE_CFLAGS_WARN_ON += -Wunused-value -Wunused-variable -Wvolatile-register-var
    +
    +        QMAKE_CXXFLAGS_WARN_ON += -Waddress -Warray-bounds -Wc++0x-compat -Wchar-subscripts
    +        QMAKE_CXXFLAGS_WARN_ON += -Wclobbered -Wcomment -Wempty-body -Wenum-compare
    +        QMAKE_CXXFLAGS_WARN_ON += -Wformat -Wignored-qualifiers -Wmain -Wmissing-braces
    +        QMAKE_CXXFLAGS_WARN_ON += -Wmissing-field-initializers -Wparentheses -Wreorder -Wreturn-type
    +        QMAKE_CXXFLAGS_WARN_ON += -Wsequence-point -Wsign-compare -Wsign-compare -Wstrict-aliasing
    +        QMAKE_CXXFLAGS_WARN_ON += -Wstrict-overflow=1 -Wswitch -Wtrigraphs -Wtype-limits
    +        QMAKE_CXXFLAGS_WARN_ON += -Wuninitialized -Wunknown-pragmas -Wunused-function -Wunused-label
    +        QMAKE_CXXFLAGS_WARN_ON += -Wunused-parameter -Wunused-value -Wunused-variable -Wvolatile-register-var
    +    }
    +}
    diff --git a/xs/src/qhull/src/qhull/qhull.pro b/xs/src/qhull/src/qhull/qhull.pro
    new file mode 100644
    index 0000000000..8393728567
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull/qhull.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qhull.pro -- Qt project file for qhull.exe with libqhullstatic_r
    +# -------------------------------------------------
    +
    +include(../qhull-app-c_r.pri)
    +
    +TARGET = qhull
    +
    +SOURCES += unix_r.c
    diff --git a/xs/src/qhull/src/qhull/unix.c b/xs/src/qhull/src/qhull/unix.c
    new file mode 100644
    index 0000000000..892a819c31
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull/unix.c
    @@ -0,0 +1,372 @@
    +/*
      ---------------------------------
    +
    +   unix.c
    +     command line interface to qhull
    +         includes SIOUX interface for Macintoshes
    +
    +   see qh-qhull.htm
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/qhull/unix.c#4 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +*/
    +
    +#include "libqhull/libqhull.h"
    +#include "libqhull/qset.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  see:
    +    concise prompt below
    +*/
    +char qh_prompta[]= "\n\
    +qhull- compute convex hulls and related structures.\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +    halfspaces:  use dim plus one and put offset after coefficients.\n\
    +                 May be preceded by a single interior point ('H').\n\
    +\n\
    +options:\n\
    +    d    - Delaunay triangulation by lifting points to a paraboloid\n\
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)\n\
    +    v    - Voronoi diagram (dual of the Delaunay triangulation)\n\
    +    v Qu - furthest-site Voronoi diagram\n\
    +    Hn,n,... - halfspace intersection about point [n,n,0,...]\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar points with nearest facet\n\
    +    Qi   - keep interior points with nearest facet\n\
    +\n\
    +Qhull control options:\n\
    +    Qbk:n   - scale coord k so that low bound is n\n\
    +      QBk:n - scale coord k so that upper bound is n (QBk is %2.2g)\n\
    +    QbB  - scale input to unit cube centered at the origin\n\
    +    Qbb  - scale last coordinate to [0,m] for Delaunay triangulations\n\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qf   - partition point to furthest outside facet\n\
    +    Qg   - only build good facets (needs 'QGn', 'QVn', or 'PdD')\n\
    +    Qm   - only process points that would increase max_outside\n\
    +    Qr   - process random outside points instead of furthest ones\n\
    +    Qs   - search all points for the initial simplex\n\
    +    Qu   - for 'd' or 'v', compute upper hull without point at-infinity\n\
    +              returns furthest-site Delaunay triangulation\n\
    +    Qv   - test vertex neighbors for convexity\n\
    +    Qx   - exact pre-merges (skips coplanar and angle-coplanar facets)\n\
    +    Qz   - add point-at-infinity to Delaunay triangulation\n\
    +    QGn  - good facet if visible from point n, -n for not visible\n\
    +    QVn  - good facet if it includes point n, -n if not\n\
    +    Q0   - turn off default premerge with 'C-0'/'Qx'\n\
    +    Q1     - sort merges by type instead of angle\n\
    +    Q2   - merge all non-convex at once instead of independent sets\n\
    +    Q3   - do not merge redundant vertices\n\
    +    Q4   - avoid old->new merges\n\
    +    Q5   - do not correct outer planes at end of qhull\n\
    +    Q6   - do not pre-merge concave or coplanar facets\n\
    +    Q7   - depth-first processing instead of breadth-first\n\
    +    Q8   - do not process near-inside points\n\
    +    Q9   - process furthest of furthest points\n\
    +    Q10  - no special processing for narrow distributions\n\
    +    Q11  - copy normals and recompute centrums for tricoplanar facets\n\
    +    Q12  - no error on wide merge due to duplicate ridge\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Topts- Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Ta   - annotate output with message codes\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TRn  - rerun qhull n times.  Use with 'QJn'\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    En   - max roundoff error for distance computation\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Vn   - min distance above plane for a visible facet (default 3C-n or En)\n\
    +    Un   - max distance below plane for a new, coplanar point (default Vn)\n\
    +    Wn   - min facet width for outside point (before roundoff, default 2Vn)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each facet\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    o    - OFF format (dim, points and facets; Voronoi regions)\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates or Voronoi vertices (coplanar points if 'Qc')\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each facet\n\
    +    FA   - compute total area and volume for option 's'\n\
    +    Fc   - count plus coplanar points for each facet\n\
    +           use 'Qc' (default) for coplanar and 'Qi' for interior\n\
    +    FC   - centrum or Voronoi center for each facet\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - inner plane for each facet\n\
    +           for 'v', separating hyperplanes for bounded Voronoi regions\n\
    +    FI   - ID of each facet\n\
    +    Fm   - merge count for each facet (511 max)\n\
    +    FM   - Maple output (2-d and 3-d)\n\
    +    Fn   - count plus neighboring facets for each facet\n\
    +    FN   - count plus neighboring facets for each point\n\
    +    Fo   - outer plane (or max_outside) for each facet\n\
    +           for 'v', separating hyperplanes for unbounded Voronoi regions\n\
    +    FO   - options and precision constants\n\
    +    Fp   - dim, count, and intersection coordinates (halfspace only)\n\
    +    FP   - nearest vertex and distance for each coplanar point\n\
    +    FQ   - command used for qhull\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                      output: #vertices, #facets, #coplanars, #nonsimplicial\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0)\n\
    +                    #real (2) tot area, tot volume\n\
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)\n\
    +    Fv   - count plus vertices for each facet\n\
    +           for 'v', Voronoi diagram as Voronoi vertices for pairs of sites\n\
    +    FV   - average of vertices (a feasible point for 'H')\n\
    +    Fx   - extreme points (in order for 2-d)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d, 3-d, and 4-d; 2-d Voronoi)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +    Gt   - for 3-d 'd', transparent outer ridges\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qhull- compute convex hulls and related structures.  Qhull %s\n\
    +    input (stdin): dimension, n, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +    halfspace: use dim+1 and put offsets after coefficients\n\
    +\n\
    +options (qh-quick.htm):\n\
    +    d    - Delaunay triangulation by lifting points to a paraboloid\n\
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)\n\
    +    v    - Voronoi diagram as the dual of the Delaunay triangulation\n\
    +    v Qu - furthest-site Voronoi diagram\n\
    +    H1,1 - Halfspace intersection about [1,1,0,...] via polar duality\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of each option\n\
    +    -V   - version\n\
    +\n\
    +Output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each facet\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates (if 'Qc', includes coplanar points)\n\
    +           if 'v', Voronoi vertices\n\
    +    Fp   - halfspace intersections\n\
    +    Fx   - extreme points (convex hull vertices)\n\
    +    FA   - compute total area and volume\n\
    +    o    - OFF format (if 'v', outputs Voronoi regions)\n\
    +    G    - Geomview output (2-d, 3-d and 4-d)\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    QVn  - print facets that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox D4 | qhull Tv                        rbox 1000 s | qhull Tv s FA\n\
    +    rbox 10 D2 | qhull d QJ s i TO result     rbox 10 D2 | qhull v Qbb Qt p\n\
    +    rbox 10 D2 | qhull d Qu QJ m              rbox 10 D2 | qhull v Qu QJ o\n\
    +    rbox c d D2 | qhull Qc s f Fx | more      rbox c | qhull FV n | qhull H Fp\n\
    +    rbox d D12 | qhull QR0 FA                 rbox c D7 | qhull FA TF1000\n\
    +    rbox y 1000 W0 | qhull                    rbox c | qhull n\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + delaunay       voronoi        Geomview       Halfspace      facet_dump\n\
    + incidences     mathematica    normals        OFF_format     points\n\
    + summary\n\
    +\n\
    + Farea          FArea-total    Fcoplanars     FCentrums      Fd-cdd-in\n\
    + FD-cdd-out     FF-dump-xridge Finner         FIDs           Fmerges\n\
    + Fneighbors     FNeigh-vertex  Fouter         FOptions       Fpoint-intersect\n\
    + FPoint_near    FQhull         Fsummary       FSize          Ftriangles\n\
    + Fvertices      Fvoronoi       FVertex-ave    Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    + Gtransparent\n\
    +\n\
    + PArea-keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge-keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QbBound 0:0.5  Qbk:0Bk:0_drop QbB-scale-box  Qbb-scale-last Qcoplanar\n\
    + Qfurthest      Qgood_only     QGood_point    Qinterior      Qmax_out\n\
    + QJoggle        Qrandom        QRotate        Qsearch_1st    Qtriangulate\n\
    + QupperDelaunay QVertex_good   Qvneighbors    Qxact_merge    Qzinfinite\n\
    +\n\
    + Q0_no_premerge Q1_no_angle    Q2_no_independ Q3_no_redundant Q4_no_old\n\
    + Q5_no_check_out Q6_no_concave Q7_depth_first Q8_no_near_in  Q9_pick_furthest\n\
    + Q10_no_narrow  Q11_trinormals Q12_no_wide_dup\n\
    +\n\
    + T4_trace       Tannotate      Tcheck_often   Tstatistics    Tverify\n\
    + Tz_stdout      TFacet_log     TInput_file    TPoint_trace   TMerge_trace\n\
    + TOutput_file   TRerun         TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Error_round    Random_dist    Visible_min\n\
    + Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version, qh_DEFAULTbox,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh_initflags(qh qhull_command);
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull( qh_ALL);
    +#else
    +  qh_freeqhull( !qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qhull/unix_r.c b/xs/src/qhull/src/qhull/unix_r.c
    new file mode 100644
    index 0000000000..3f999f7fa9
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull/unix_r.c
    @@ -0,0 +1,374 @@
    +/*
      ---------------------------------
    +
    +   unix.c
    +     command line interface to qhull
    +         includes SIOUX interface for Macintoshes
    +
    +   see qh-qhull.htm
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/qhull/unix_r.c#6 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  see:
    +    concise prompt below
    +*/
    +char qh_prompta[]= "\n\
    +qhull- compute convex hulls and related structures.\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +    halfspaces:  use dim plus one and put offset after coefficients.\n\
    +                 May be preceded by a single interior point ('H').\n\
    +\n\
    +options:\n\
    +    d    - Delaunay triangulation by lifting points to a paraboloid\n\
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)\n\
    +    v    - Voronoi diagram (dual of the Delaunay triangulation)\n\
    +    v Qu - furthest-site Voronoi diagram\n\
    +    Hn,n,... - halfspace intersection about point [n,n,0,...]\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar points with nearest facet\n\
    +    Qi   - keep interior points with nearest facet\n\
    +\n\
    +Qhull control options:\n\
    +    Qbk:n   - scale coord k so that low bound is n\n\
    +      QBk:n - scale coord k so that upper bound is n (QBk is %2.2g)\n\
    +    QbB  - scale input to unit cube centered at the origin\n\
    +    Qbb  - scale last coordinate to [0,m] for Delaunay triangulations\n\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qf   - partition point to furthest outside facet\n\
    +    Qg   - only build good facets (needs 'QGn', 'QVn', or 'PdD')\n\
    +    Qm   - only process points that would increase max_outside\n\
    +    Qr   - process random outside points instead of furthest ones\n\
    +    Qs   - search all points for the initial simplex\n\
    +    Qu   - for 'd' or 'v', compute upper hull without point at-infinity\n\
    +              returns furthest-site Delaunay triangulation\n\
    +    Qv   - test vertex neighbors for convexity\n\
    +    Qx   - exact pre-merges (skips coplanar and angle-coplanar facets)\n\
    +    Qz   - add point-at-infinity to Delaunay triangulation\n\
    +    QGn  - good facet if visible from point n, -n for not visible\n\
    +    QVn  - good facet if it includes point n, -n if not\n\
    +    Q0   - turn off default premerge with 'C-0'/'Qx'\n\
    +    Q1     - sort merges by type instead of angle\n\
    +    Q2   - merge all non-convex at once instead of independent sets\n\
    +    Q3   - do not merge redundant vertices\n\
    +    Q4   - avoid old->new merges\n\
    +    Q5   - do not correct outer planes at end of qhull\n\
    +    Q6   - do not pre-merge concave or coplanar facets\n\
    +    Q7   - depth-first processing instead of breadth-first\n\
    +    Q8   - do not process near-inside points\n\
    +    Q9   - process furthest of furthest points\n\
    +    Q10  - no special processing for narrow distributions\n\
    +    Q11  - copy normals and recompute centrums for tricoplanar facets\n\
    +    Q12  - no error on wide merge due to duplicate ridge\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Topts- Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Ta   - annotate output with message codes\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TRn  - rerun qhull n times.  Use with 'QJn'\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    En   - max roundoff error for distance computation\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Vn   - min distance above plane for a visible facet (default 3C-n or En)\n\
    +    Un   - max distance below plane for a new, coplanar point (default Vn)\n\
    +    Wn   - min facet width for outside point (before roundoff, default 2Vn)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each facet\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    o    - OFF format (dim, points and facets; Voronoi regions)\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates or Voronoi vertices (coplanar points if 'Qc')\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each facet\n\
    +    FA   - compute total area and volume for option 's'\n\
    +    Fc   - count plus coplanar points for each facet\n\
    +           use 'Qc' (default) for coplanar and 'Qi' for interior\n\
    +    FC   - centrum or Voronoi center for each facet\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - inner plane for each facet\n\
    +           for 'v', separating hyperplanes for bounded Voronoi regions\n\
    +    FI   - ID of each facet\n\
    +    Fm   - merge count for each facet (511 max)\n\
    +    FM   - Maple output (2-d and 3-d)\n\
    +    Fn   - count plus neighboring facets for each facet\n\
    +    FN   - count plus neighboring facets for each point\n\
    +    Fo   - outer plane (or max_outside) for each facet\n\
    +           for 'v', separating hyperplanes for unbounded Voronoi regions\n\
    +    FO   - options and precision constants\n\
    +    Fp   - dim, count, and intersection coordinates (halfspace only)\n\
    +    FP   - nearest vertex and distance for each coplanar point\n\
    +    FQ   - command used for qhull\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                      output: #vertices, #facets, #coplanars, #nonsimplicial\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0)\n\
    +                    #real (2) tot area, tot volume\n\
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)\n\
    +    Fv   - count plus vertices for each facet\n\
    +           for 'v', Voronoi diagram as Voronoi vertices for pairs of sites\n\
    +    FV   - average of vertices (a feasible point for 'H')\n\
    +    Fx   - extreme points (in order for 2-d)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d, 3-d, and 4-d; 2-d Voronoi)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +    Gt   - for 3-d 'd', transparent outer ridges\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qhull- compute convex hulls and related structures.  Qhull %s\n\
    +    input (stdin): dimension, n, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +    halfspace: use dim+1 and put offsets after coefficients\n\
    +\n\
    +options (qh-quick.htm):\n\
    +    d    - Delaunay triangulation by lifting points to a paraboloid\n\
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)\n\
    +    v    - Voronoi diagram as the dual of the Delaunay triangulation\n\
    +    v Qu - furthest-site Voronoi diagram\n\
    +    H1,1 - Halfspace intersection about [1,1,0,...] via polar duality\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of each option\n\
    +    -V   - version\n\
    +\n\
    +Output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each facet\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates (if 'Qc', includes coplanar points)\n\
    +           if 'v', Voronoi vertices\n\
    +    Fp   - halfspace intersections\n\
    +    Fx   - extreme points (convex hull vertices)\n\
    +    FA   - compute total area and volume\n\
    +    o    - OFF format (if 'v', outputs Voronoi regions)\n\
    +    G    - Geomview output (2-d, 3-d and 4-d)\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    QVn  - print facets that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox D4 | qhull Tv                        rbox 1000 s | qhull Tv s FA\n\
    +    rbox 10 D2 | qhull d QJ s i TO result     rbox 10 D2 | qhull v Qbb Qt p\n\
    +    rbox 10 D2 | qhull d Qu QJ m              rbox 10 D2 | qhull v Qu QJ o\n\
    +    rbox c d D2 | qhull Qc s f Fx | more      rbox c | qhull FV n | qhull H Fp\n\
    +    rbox d D12 | qhull QR0 FA                 rbox c D7 | qhull FA TF1000\n\
    +    rbox y 1000 W0 | qhull                    rbox c | qhull n\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + delaunay       voronoi        Geomview       Halfspace      facet_dump\n\
    + incidences     mathematica    normals        OFF_format     points\n\
    + summary\n\
    +\n\
    + Farea          FArea-total    Fcoplanars     FCentrums      Fd-cdd-in\n\
    + FD-cdd-out     FF-dump-xridge Finner         FIDs           Fmerges\n\
    + Fneighbors     FNeigh-vertex  Fouter         FOptions       Fpoint-intersect\n\
    + FPoint_near    FQhull         Fsummary       FSize          Ftriangles\n\
    + Fvertices      Fvoronoi       FVertex-ave    Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    + Gtransparent\n\
    +\n\
    + PArea-keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge-keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QbBound 0:0.5  Qbk:0Bk:0_drop QbB-scale-box  Qbb-scale-last Qcoplanar\n\
    + Qfurthest      Qgood_only     QGood_point    Qinterior      Qmax_out\n\
    + QJoggle        Qrandom        QRotate        Qsearch_1st    Qtriangulate\n\
    + QupperDelaunay QVertex_good   Qvneighbors    Qxact_merge    Qzinfinite\n\
    +\n\
    + Q0_no_premerge Q1_no_angle    Q2_no_independ Q3_no_redundant Q4_no_old\n\
    + Q5_no_check_out Q6_no_concave Q7_depth_first Q8_no_near_in  Q9_pick_furthest\n\
    + Q10_no_narrow  Q11_trinormals Q12_no_wide_dup\n\
    +\n\
    + T4_trace       Tannotate      Tcheck_often   Tstatistics    Tverify\n\
    + Tz_stdout      TFacet_log     TInput_file    TPoint_trace   TMerge_trace\n\
    + TOutput_file   TRerun         TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Error_round    Random_dist    Visible_min\n\
    + Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version, qh_DEFAULTbox,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_initflags(qh, qh->qhull_command);
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qhulltest/Coordinates_test.cpp b/xs/src/qhull/src/qhulltest/Coordinates_test.cpp
    new file mode 100644
    index 0000000000..3e8658a5bd
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/Coordinates_test.cpp
    @@ -0,0 +1,539 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/Coordinates_test.cpp#2 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/Coordinates.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class Coordinates_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void t_construct();
    +    void t_convert();
    +    void t_element();
    +    void t_readonly();
    +    void t_operator();
    +    void t_const_iterator();
    +    void t_iterator();
    +    void t_coord_iterator();
    +    void t_mutable_coord_iterator();
    +    void t_readwrite();
    +    void t_search();
    +    void t_io();
    +};//Coordinates_test
    +
    +void
    +add_Coordinates_test()
    +{
    +    new Coordinates_test();  // RoadTest::s_testcases
    +}
    +
    +void Coordinates_test::
    +t_construct()
    +{
    +    Coordinates c;
    +    QCOMPARE(c.size(), 0U);
    +    QVERIFY(c.isEmpty());
    +    c << 1.0;
    +    QCOMPARE(c.count(), 1);
    +    Coordinates c2(c);
    +    c2 << 2.0;
    +    QCOMPARE(c2.count(), 2);
    +    Coordinates c3;
    +    c3 = c2;
    +    QCOMPARE(c3.count(), 2);
    +    QCOMPARE(c3[0]+c3[1], 3.0);
    +    QVERIFY(c2==c3);
    +    std::vector vc;
    +    vc.push_back(3.0);
    +    vc.push_back(4.0);
    +    Coordinates c4(vc);
    +    QCOMPARE(c4[0]+c4[1], 7.0);
    +    Coordinates c5(c3);
    +    QVERIFY(c5==c3);
    +    c5= vc;
    +    QVERIFY(c5!=c3);
    +    QVERIFY(c5==c4);
    +}//t_construct
    +
    +void Coordinates_test::
    +t_convert()
    +{
    +    Coordinates c;
    +    c << 1.0 << 3.0;
    +    QCOMPARE(c.data()[1], 3.0);
    +    coordT *c2= c.data();
    +    const coordT *c3= c.data();
    +    QCOMPARE(c2, c3);
    +    std::vector vc= c.toStdVector();
    +    QCOMPARE((size_t)vc.size(), c.size());
    +    for(int k= (int)vc.size(); k--; ){
    +        QCOMPARE(vc[k], c[k]);
    +    }
    +    QList qc= c.toQList();
    +    QCOMPARE(qc.count(), c.count());
    +    for(int k= qc.count(); k--; ){
    +        QCOMPARE(qc[k], c[k]);
    +    }
    +    Coordinates c4;
    +    c4= std::vector(2, 0.0);
    +    QCOMPARE(c4.back(), 0.0);
    +    Coordinates c5(std::vector(2, 0.0));
    +    QCOMPARE(c4.size(), c5.size());
    +    QVERIFY(c4==c5);
    +}//t_convert
    +
    +void Coordinates_test::
    +t_element()
    +{
    +    Coordinates c;
    +    c << 1.0 << -2.0;
    +    c.at(1)= -3;
    +    QCOMPARE(c.at(1), -3.0);
    +    QCOMPARE(c.back(), -3.0);
    +    QCOMPARE(c.front(), 1.0);
    +    c[1]= -2.0;
    +    QCOMPARE(c[1],-2.0);
    +    QCOMPARE(c.first(), 1.0);
    +    c.first()= 2.0;
    +    QCOMPARE(c.first(), 2.0);
    +    QCOMPARE(c.last(), -2.0);
    +    c.last()= 0.0;
    +    QCOMPARE(c.first()+c.last(), 2.0);
    +    coordT *c4= &c.first();
    +    const coordT *c5= &c.first();
    +    QCOMPARE(c4, c5);
    +    coordT *c6= &c.last();
    +    const coordT *c7= &c.last();
    +    QCOMPARE(c6, c7);
    +    Coordinates c2= c.mid(1);
    +    QCOMPARE(c2.count(), 1);
    +    c << 3.0;
    +    Coordinates c3= c.mid(1,1);
    +    QCOMPARE(c2, c3);
    +    QCOMPARE(c3.value(-1, -1.0), -1.0);
    +    QCOMPARE(c3.value(3, 4.0), 4.0);
    +    QCOMPARE(c.value(2, 4.0), 3.0);
    +}//t_element
    +
    +void Coordinates_test::
    +t_readonly()
    +{
    +    Coordinates c;
    +    QCOMPARE(c.size(), 0u);
    +    QCOMPARE(c.count(), 0);
    +    QVERIFY(c.isEmpty());
    +    c << 1.0 << -2.0;
    +    QCOMPARE(c.size(), 2u);
    +    QCOMPARE(c.count(), 2);
    +    QVERIFY(!c.isEmpty());
    +}//t_readonly
    +
    +void Coordinates_test::
    +t_operator()
    +{
    +    Coordinates c;
    +    Coordinates c2(c);
    +    QVERIFY(c==c2);
    +    QVERIFY(!(c!=c2));
    +    c << 1.0;
    +    QVERIFY(!(c==c2));
    +    QVERIFY(c!=c2);
    +    c2 << 1.0;
    +    QVERIFY(c==c2);
    +    QVERIFY(!(c!=c2));
    +    c[0]= 0.0;
    +    QVERIFY(c!=c2);
    +    Coordinates c3= c+c2;
    +    QCOMPARE(c3.count(), 2);
    +    QCOMPARE(c3[0], 0.0);
    +    QCOMPARE(c3[1], 1.0);
    +    c3 += c3;
    +    QCOMPARE(c3.count(), 4);
    +    QCOMPARE(c3[2], 0.0);
    +    QCOMPARE(c3[3], 1.0);
    +    c3 += c2;
    +    QCOMPARE(c3[4], 1.0);
    +    c3 += 5.0;
    +    QCOMPARE(c3.count(), 6);
    +    QCOMPARE(c3[5], 5.0);
    +    // << checked above
    +}//t_operator
    +
    +void Coordinates_test::
    +t_const_iterator()
    +{
    +    Coordinates c;
    +    QCOMPARE(c.begin(), c.end());
    +    // begin and end checked elsewhere
    +    c << 1.0 << 3.0;
    +    Coordinates::const_iterator i= c.begin();
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(i[1], 3.0);
    +    // i[1]= -3.0; // compiler error
    +    // operator-> is not applicable to double
    +    QCOMPARE(*i++, 1.0);
    +    QCOMPARE(*i, 3.0);
    +    QCOMPARE(*i--, 3.0);
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(*(i+1), 3.0);
    +    QCOMPARE(*++i, 3.0);
    +    QCOMPARE(*(i-1), 1.0);
    +    QCOMPARE(*--i, 1.0);
    +    QVERIFY(i==c.begin());
    +    QVERIFY(i==c.constBegin());
    +    QVERIFY(i!=c.end());
    +    QVERIFY(i!=c.constEnd());
    +    QVERIFY(i=c.begin());
    +    QVERIFY(i+1<=c.end());
    +    QVERIFY(i+1>c.begin());
    +    Coordinates::iterator i2= c.begin();
    +    Coordinates::const_iterator i3(i2);
    +    QCOMPARE(*i3, 1.0);
    +    QCOMPARE(i3[1], 3.0);
    +}//t_const_iterator
    +
    +void Coordinates_test::
    +t_iterator()
    +{
    +    Coordinates c;
    +    QCOMPARE(c.begin(), c.end());
    +    // begin and end checked elsewhere
    +    c << 1.0 << 3.0;
    +    Coordinates::iterator i= c.begin();
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(i[1], 3.0);
    +    *i= -1.0;
    +    QCOMPARE(*i, -1.0);
    +    i[1]= -3.0;
    +    QCOMPARE(i[1], -3.0);
    +    *i= 1.0;
    +    // operator-> is not applicable to double
    +    QCOMPARE(*i++, 1.0);
    +    QCOMPARE(*i, -3.0);
    +    *i= 3.0;
    +    QCOMPARE(*i--, 3.0);
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(*(i+1), 3.0);
    +    QCOMPARE(*++i, 3.0);
    +    QCOMPARE(*(i-1), 1.0);
    +    QCOMPARE(*--i, 1.0);
    +    QVERIFY(i==c.begin());
    +    QVERIFY(i==c.constBegin());
    +    QVERIFY(i!=c.end());
    +    QVERIFY(i!=c.constEnd());
    +    QVERIFY(i=c.begin());
    +    QVERIFY(i+1<=c.end());
    +    QVERIFY(i+1>c.begin());
    +}//t_iterator
    +
    +void Coordinates_test::
    +t_coord_iterator()
    +{
    +    Coordinates c;
    +    c << 1.0 << 3.0;
    +    CoordinatesIterator i(c);
    +    CoordinatesIterator i2= c;
    +    QVERIFY(i.findNext(1.0));
    +    QVERIFY(!i.findNext(2.0));
    +    QVERIFY(!i.findNext(3.0));
    +    QVERIFY(i.findPrevious(3.0));
    +    QVERIFY(!i.findPrevious(2.0));
    +    QVERIFY(!i.findPrevious(1.0));
    +    QVERIFY(i2.findNext(3.0));
    +    QVERIFY(i2.findPrevious(3.0));
    +    QVERIFY(i2.findNext(3.0));
    +    QVERIFY(i2.findPrevious(1.0));
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    i2.toFront();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(i.hasPrevious());
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    Coordinates c2;
    +    i2= c2;
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    i2.toBack();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekPrevious(), 3.0);
    +    QCOMPARE(i.previous(), 3.0);
    +    QCOMPARE(i.previous(), 1.0);
    +    QVERIFY(!i.hasPrevious());
    +    QCOMPARE(i.peekNext(), 1.0);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), 1.0);
    +    QCOMPARE(i.peekNext(), 3.0);
    +    QCOMPARE(i.next(), 3.0);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), 1.0);
    +}//t_coord_iterator
    +
    +void Coordinates_test::
    +t_mutable_coord_iterator()
    +{
    +    // Same tests as CoordinatesIterator
    +    Coordinates c;
    +    c << 1.0 << 3.0;
    +    MutableCoordinatesIterator i(c);
    +    MutableCoordinatesIterator i2= c;
    +    QVERIFY(i.findNext(1.0));
    +    QVERIFY(!i.findNext(2.0));
    +    QVERIFY(!i.findNext(3.0));
    +    QVERIFY(i.findPrevious(3.0));
    +    QVERIFY(!i.findPrevious(2.0));
    +    QVERIFY(!i.findPrevious(1.0));
    +    QVERIFY(i2.findNext(3.0));
    +    QVERIFY(i2.findPrevious(3.0));
    +    QVERIFY(i2.findNext(3.0));
    +    QVERIFY(i2.findPrevious(1.0));
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    i2.toFront();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(i.hasPrevious());
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    Coordinates c2;
    +    i2= c2;
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    i2.toBack();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekPrevious(), 3.0);
    +    QCOMPARE(i.peekPrevious(), 3.0);
    +    QCOMPARE(i.previous(), 3.0);
    +    QCOMPARE(i.previous(), 1.0);
    +    QVERIFY(!i.hasPrevious());
    +    QCOMPARE(i.peekNext(), 1.0);
    +    QCOMPARE(i.next(), 1.0);
    +    QCOMPARE(i.peekNext(), 3.0);
    +    QCOMPARE(i.next(), 3.0);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), 1.0);
    +
    +    // Mutable tests
    +    i.toFront();
    +    i.peekNext()= -1.0;
    +    QCOMPARE(i.peekNext(), -1.0);
    +    QCOMPARE((i.next()= 1.0), 1.0);
    +    QCOMPARE(i.peekPrevious(), 1.0);
    +    i.remove();
    +    QCOMPARE(c.count(), 1);
    +    i.remove();
    +    QCOMPARE(c.count(), 1);
    +    QCOMPARE(i.peekNext(), 3.0);
    +    i.insert(1.0);
    +    i.insert(2.0);
    +    QCOMPARE(c.count(), 3);
    +    QCOMPARE(i.peekNext(), 3.0);
    +    QCOMPARE(i.peekPrevious(), 2.0);
    +    i.peekPrevious()= -2.0;
    +    QCOMPARE(i.peekPrevious(), -2.0);
    +    QCOMPARE((i.previous()= 2.0), 2.0);
    +    QCOMPARE(i.peekNext(), 2.0);
    +    i.toBack();
    +    i.remove();
    +    QCOMPARE(c.count(), 3); // unchanged
    +    i.toFront();
    +    i.remove();
    +    QCOMPARE(c.count(), 3); // unchanged
    +    QCOMPARE(i.peekNext(), 1.0);
    +    i.remove();
    +    QCOMPARE(c.count(), 3); // unchanged
    +    i.insert(0.0);
    +    QCOMPARE(c.count(), 4);
    +    QCOMPARE(i.value(), 0.0);
    +    QCOMPARE(i.peekPrevious(), 0.0);
    +    i.setValue(-10.0);
    +    QCOMPARE(c.count(), 4); // unchanged
    +    QCOMPARE(i.peekNext(), 1.0);
    +    QCOMPARE(i.peekPrevious(), -10.0);
    +    i.findNext(1.0);
    +    i.setValue(-1.0);
    +    QCOMPARE(i.peekPrevious(), -1.0);
    +    i.setValue(1.0);
    +    QCOMPARE(i.peekPrevious(), 1.0);
    +    QCOMPARE(i.value(), 1.0);
    +    i.findPrevious(1.0);
    +    i.setValue(-1.0);
    +    QCOMPARE(i.peekNext(), -1.0);
    +    i.toBack();
    +    QCOMPARE(i.previous(), 3.0);
    +    i.setValue(-3.0);
    +    QCOMPARE(i.peekNext(), -3.0);
    +    double d= i.value();
    +    QCOMPARE(d, -3.0);
    +    QCOMPARE(i.previous(), 2.0);
    +}//t_mutable_coord_iterator
    +
    +void Coordinates_test::
    +t_readwrite()
    +{
    +    Coordinates c;
    +    c.clear();
    +    QCOMPARE(c.count(), 0);
    +    c << 1.0 << 3.0;
    +    c.clear();
    +    QCOMPARE(c.count(), 0);
    +    coordT c2[4]= { 0.0, 1.0, 2.0, 3.0};
    +    c.append(4, c2);
    +    QCOMPARE(c.count(), 4);
    +    QCOMPARE(c[0], 0.0);
    +    QCOMPARE(c[1], 1.0);
    +    QCOMPARE(c[3], 3.0);
    +    c.clear();
    +    c << 1.0 << 3.0;
    +    c.erase(c.begin(), c.end());
    +    QCOMPARE(c.count(), 0);
    +    c << 1.0 << 0.0;
    +    Coordinates::iterator i= c.erase(c.begin());
    +    QCOMPARE(*i, 0.0);
    +    i= c.insert(c.end(), 1.0);
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(c.count(), 2);
    +    c.pop_back();
    +    QCOMPARE(c.count(), 1);   // 0
    +    QCOMPARE(c[0], 0.0);
    +    c.push_back(2.0);
    +    QCOMPARE(c.count(), 2);
    +    c.append(3.0);
    +    QCOMPARE(c.count(), 3);   // 0, 2, 3
    +    QCOMPARE(c[2], 3.0);
    +    c.insert(0, 4.0);
    +    QCOMPARE(c[0], 4.0);
    +    QCOMPARE(c[3], 3.0);
    +    c.insert(c.count(), 5.0);
    +    QCOMPARE(c.count(), 5);   // 4, 0, 2, 3, 5
    +    QCOMPARE(c[4], 5.0);
    +    c.move(4, 0);
    +    QCOMPARE(c.count(), 5);   // 5, 4, 0, 2, 3
    +    QCOMPARE(c[0], 5.0);
    +    c.pop_front();
    +    QCOMPARE(c.count(), 4);
    +    QCOMPARE(c[0], 4.0);
    +    c.prepend(6.0);
    +    QCOMPARE(c.count(), 5);   // 6, 4, 0, 2, 3
    +    QCOMPARE(c[0], 6.0);
    +    c.push_front(7.0);
    +    QCOMPARE(c.count(), 6);
    +    QCOMPARE(c[0], 7.0);
    +    c.removeAt(1);
    +    QCOMPARE(c.count(), 5);   // 7, 4, 0, 2, 3
    +    QCOMPARE(c[1], 4.0);
    +    c.removeFirst();
    +    QCOMPARE(c.count(), 4);   // 4, 0, 2, 3
    +    QCOMPARE(c[0], 4.0);
    +    c.removeLast();
    +    QCOMPARE(c.count(), 3);
    +    QCOMPARE(c.last(), 2.0);
    +    c.replace(2, 8.0);
    +    QCOMPARE(c.count(), 3);   // 4, 0, 8
    +    QCOMPARE(c[2], 8.0);
    +    c.swap(0, 2);
    +    QCOMPARE(c[2], 4.0);
    +    double d= c.takeAt(2);
    +    QCOMPARE(c.count(), 2);   // 8, 0
    +    QCOMPARE(d, 4.0);
    +    double d2= c.takeFirst();
    +    QCOMPARE(c.count(), 1);   // 0
    +    QCOMPARE(d2, 8.0);
    +    double d3= c.takeLast();
    +    QVERIFY(c.isEmpty()); \
    +    QCOMPARE(d3, 0.0);
    +}//t_readwrite
    +
    +void Coordinates_test::
    +t_search()
    +{
    +    Coordinates c;
    +    c << 1.0 << 3.0 << 1.0;
    +    QVERIFY(c.contains(1.0));
    +    QVERIFY(c.contains(3.0));
    +    QVERIFY(!c.contains(0.0));
    +    QCOMPARE(c.count(1.0), 2);
    +    QCOMPARE(c.count(3.0), 1);
    +    QCOMPARE(c.count(0.0), 0);
    +    QCOMPARE(c.indexOf(1.0), 0);
    +    QCOMPARE(c.indexOf(3.0), 1);
    +    QCOMPARE(c.indexOf(1.0, -1), 2);
    +    QCOMPARE(c.indexOf(3.0, -1), -1);
    +    QCOMPARE(c.indexOf(3.0, -2), 1);
    +    QCOMPARE(c.indexOf(1.0, -3), 0);
    +    QCOMPARE(c.indexOf(1.0, -4), 0);
    +    QCOMPARE(c.indexOf(1.0, 1), 2);
    +    QCOMPARE(c.indexOf(3.0, 2), -1);
    +    QCOMPARE(c.indexOf(1.0, 2), 2);
    +    QCOMPARE(c.indexOf(1.0, 3), -1);
    +    QCOMPARE(c.indexOf(1.0, 4), -1);
    +    QCOMPARE(c.lastIndexOf(1.0), 2);
    +    QCOMPARE(c.lastIndexOf(3.0), 1);
    +    QCOMPARE(c.lastIndexOf(1.0, -1), 2);
    +    QCOMPARE(c.lastIndexOf(3.0, -1), 1);
    +    QCOMPARE(c.lastIndexOf(3.0, -2), 1);
    +    QCOMPARE(c.lastIndexOf(1.0, -3), 0);
    +    QCOMPARE(c.lastIndexOf(1.0, -4), -1);
    +    QCOMPARE(c.lastIndexOf(1.0, 1), 0);
    +    QCOMPARE(c.lastIndexOf(3.0, 2), 1);
    +    QCOMPARE(c.lastIndexOf(1.0, 2), 2);
    +    QCOMPARE(c.lastIndexOf(1.0, 3), 2);
    +    QCOMPARE(c.lastIndexOf(1.0, 4), 2);
    +    c.removeAll(3.0);
    +    QCOMPARE(c.count(), 2);
    +    c.removeAll(4.0);
    +    QCOMPARE(c.count(), 2);
    +    c.removeAll(1.0);
    +    QCOMPARE(c.count(), 0);
    +    c.removeAll(4.0);
    +    QCOMPARE(c.count(), 0);
    +}//t_search
    +
    +void Coordinates_test::
    +t_io()
    +{
    +    Coordinates c;
    +    c << 1.0 << 2.0 << 3.0;
    +    ostringstream os;
    +    os << "Coordinates 1-2-3\n" << c;
    +    cout << os.str();
    +    QString s= QString::fromStdString(os.str());
    +    QCOMPARE(s.count("2"), 2);
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/Coordinates_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/PointCoordinates_test.cpp b/xs/src/qhull/src/qhulltest/PointCoordinates_test.cpp
    new file mode 100644
    index 0000000000..09285954df
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/PointCoordinates_test.cpp
    @@ -0,0 +1,478 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/PointCoordinates_test.cpp#2 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/PointCoordinates.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +using std::stringstream;
    +
    +namespace orgQhull {
    +
    +class PointCoordinates_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void t_construct_q();
    +    void t_construct_qh();
    +    void t_convert();
    +    void t_getset();
    +    void t_element();
    +    void t_foreach();
    +    void t_search();
    +    void t_modify();
    +    void t_append_points();
    +    void t_coord_iterator();
    +    void t_io();
    +};//PointCoordinates_test
    +
    +void
    +add_PointCoordinates_test()
    +{
    +    new PointCoordinates_test();  // RoadTest::s_testcases
    +}
    +
    +void PointCoordinates_test::
    +t_construct_q()
    +{
    +    Qhull q;
    +    PointCoordinates pc(q);
    +    QCOMPARE(pc.size(), 0U);
    +    QCOMPARE(pc.coordinateCount(), 0);
    +    QCOMPARE(pc.dimension(), 0);
    +    QCOMPARE(pc.coordinates(), (coordT *)0);
    +    QVERIFY(pc.isEmpty());
    +    pc.checkValid();
    +    PointCoordinates pc7(q, 2, "test explicit dimension");
    +    QCOMPARE(pc7.dimension(), 2);
    +    QCOMPARE(pc7.count(), 0);
    +    QVERIFY(pc7.isEmpty());
    +    QCOMPARE(pc7.comment(), std::string("test explicit dimension"));
    +    pc7.checkValid();
    +    PointCoordinates pc2(q, "Test pc2");
    +    QCOMPARE(pc2.count(), 0);
    +    QVERIFY(pc2.isEmpty());
    +    QCOMPARE(pc2.comment(), std::string("Test pc2"));
    +    pc2.checkValid();
    +    PointCoordinates pc3(q, 3, "Test 3-d pc3");
    +    QCOMPARE(pc3.dimension(), 3);
    +    QVERIFY(pc3.isEmpty());
    +    pc3.checkValid();
    +    coordT c[]= { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 };
    +    PointCoordinates pc4(q, 2, "Test 2-d pc4", 6, c);
    +    QCOMPARE(pc4.dimension(), 2);
    +    QCOMPARE(pc4.count(), 3);
    +    QCOMPARE(pc4.size(), 3u);
    +    QVERIFY(!pc4.isEmpty());
    +    pc4.checkValid();
    +    QhullPoint p= pc4[2];
    +    QCOMPARE(p[1], 5.0);
    +    // QhullPoint refers to PointCoordinates
    +    p[1] += 1.0;
    +    QCOMPARE(pc4[2][1], 6.0);
    +    PointCoordinates pc5(q, 4, "Test 4-d pc5 with insufficient coordinates", 6, c);
    +    QCOMPARE(pc5.dimension(), 4);
    +    QCOMPARE(pc5.count(), 1);
    +    QCOMPARE(pc5.extraCoordinatesCount(), 2);
    +    QCOMPARE(pc5.extraCoordinates()[1], 5.0);
    +    QVERIFY(!pc5.isEmpty());;
    +    std::vector vc;
    +    vc.push_back(3.0);
    +    vc.push_back(4.0);
    +    vc.push_back(5.0);
    +    vc.push_back(6.0);
    +    vc.push_back(7.0);
    +    vc.push_back(9.0);
    +    pc5.append(2, &vc[3]); // Copy of vc[]
    +    pc5.checkValid();
    +    QhullPoint p5(q, 4, &vc[1]);
    +    QCOMPARE(pc5[1], p5);
    +    PointCoordinates pc6(pc5); // Makes copy of point_coordinates
    +    QCOMPARE(pc6[1], p5);
    +    QVERIFY(pc6==pc5);
    +    QhullPoint p6= pc5[1];  // Refers to pc5.coordinates
    +    pc5[1][0] += 1.0;
    +    QCOMPARE(pc5[1], p6);
    +    QVERIFY(pc5[1]!=p5);
    +    QVERIFY(pc6!=pc5);
    +    pc6= pc5;
    +    QVERIFY(pc6==pc5);
    +    PointCoordinates pc8(q);
    +    pc6= pc8;
    +    QVERIFY(pc6!=pc5);
    +    QVERIFY(pc6.isEmpty());
    +}//t_construct_q
    +
    +void PointCoordinates_test::
    +t_construct_qh()
    +{
    +    QhullQh qh;
    +    PointCoordinates pc(&qh);
    +    QCOMPARE(pc.size(), 0U);
    +    QCOMPARE(pc.coordinateCount(), 0);
    +    QCOMPARE(pc.dimension(), 0);
    +    QCOMPARE(pc.coordinates(), (coordT *)0);
    +    QVERIFY(pc.isEmpty());
    +    pc.checkValid();
    +    PointCoordinates pc7(&qh, 2, "test explicit dimension");
    +    QCOMPARE(pc7.dimension(), 2);
    +    QCOMPARE(pc7.count(), 0);
    +    QVERIFY(pc7.isEmpty());
    +    QCOMPARE(pc7.comment(), std::string("test explicit dimension"));
    +    pc7.checkValid();
    +    PointCoordinates pc2(&qh, "Test pc2");
    +    QCOMPARE(pc2.count(), 0);
    +    QVERIFY(pc2.isEmpty());
    +    QCOMPARE(pc2.comment(), std::string("Test pc2"));
    +    pc2.checkValid();
    +    PointCoordinates pc3(&qh, 3, "Test 3-d pc3");
    +    QCOMPARE(pc3.dimension(), 3);
    +    QVERIFY(pc3.isEmpty());
    +    pc3.checkValid();
    +    coordT c[]= { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 };
    +    PointCoordinates pc4(&qh, 2, "Test 2-d pc4", 6, c);
    +    QCOMPARE(pc4.dimension(), 2);
    +    QCOMPARE(pc4.count(), 3);
    +    QCOMPARE(pc4.size(), 3u);
    +    QVERIFY(!pc4.isEmpty());
    +    pc4.checkValid();
    +    QhullPoint p= pc4[2];
    +    QCOMPARE(p[1], 5.0);
    +    // QhullPoint refers to PointCoordinates
    +    p[1] += 1.0;
    +    QCOMPARE(pc4[2][1], 6.0);
    +    PointCoordinates pc5(&qh, 4, "Test 4-d pc5 with insufficient coordinates", 6, c);
    +    QCOMPARE(pc5.dimension(), 4);
    +    QCOMPARE(pc5.count(), 1);
    +    QCOMPARE(pc5.extraCoordinatesCount(), 2);
    +    QCOMPARE(pc5.extraCoordinates()[1], 5.0);
    +    QVERIFY(!pc5.isEmpty());;
    +    std::vector vc;
    +    vc.push_back(3.0);
    +    vc.push_back(4.0);
    +    vc.push_back(5.0);
    +    vc.push_back(6.0);
    +    vc.push_back(7.0);
    +    vc.push_back(9.0);
    +    pc5.append(2, &vc[3]); // Copy of vc[]
    +    pc5.checkValid();
    +    QhullPoint p5(&qh, 4, &vc[1]);
    +    QCOMPARE(pc5[1], p5);
    +    PointCoordinates pc6(pc5); // Makes copy of point_coordinates
    +    QCOMPARE(pc6[1], p5);
    +    QVERIFY(pc6==pc5);
    +    QhullPoint p6= pc5[1];  // Refers to pc5.coordinates
    +    pc5[1][0] += 1.0;
    +    QCOMPARE(pc5[1], p6);
    +    QVERIFY(pc5[1]!=p5);
    +    QVERIFY(pc6!=pc5);
    +    pc6= pc5;
    +    QVERIFY(pc6==pc5);
    +    PointCoordinates pc8(&qh);
    +    pc6= pc8;
    +    QVERIFY(pc6!=pc5);
    +    QVERIFY(pc6.isEmpty());
    +}//t_construct_qh
    +
    +void PointCoordinates_test::
    +t_convert()
    +{
    +    Qhull q;
    +    //defineAs tested above
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates ps(q, 3, "two 3-d points", 6, c);
    +    QCOMPARE(ps.dimension(), 3);
    +    QCOMPARE(ps.size(), 2u);
    +    const coordT *c2= ps.constData();
    +    QVERIFY(c!=c2);
    +    QCOMPARE(c[0], c2[0]);
    +    const coordT *c3= ps.data();
    +    QCOMPARE(c3, c2);
    +    coordT *c4= ps.data();
    +    QCOMPARE(c4, c2);
    +    std::vector vs= ps.toStdVector();
    +    QCOMPARE(vs.size(), 6u);
    +    QCOMPARE(vs[5], 5.0);
    +    QList qs= ps.toQList();
    +    QCOMPARE(qs.size(), 6);
    +    QCOMPARE(qs[5], 5.0);
    +}//t_convert
    +
    +void PointCoordinates_test::
    +t_getset()
    +{
    +    // See t_construct() for test of coordinates, coordinateCount, dimension, empty, isEmpty, ==, !=
    +    // See t_construct() for test of checkValid, comment, setDimension
    +    Qhull q;
    +    PointCoordinates pc(q, "Coordinates c");
    +    pc.setComment("New comment");
    +    QCOMPARE(pc.comment(), std::string("New comment"));
    +    pc.checkValid();
    +    pc.makeValid();  // A no-op
    +    pc.checkValid();
    +    Coordinates cs= pc.getCoordinates();
    +    QVERIFY(cs.isEmpty());
    +    PointCoordinates pc2(pc);
    +    pc.setDimension(3);
    +    QVERIFY(pc2!=pc);
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    pc.append(6, c);
    +    pc.checkValid();
    +    pc.makeValid();  // A no-op
    +    QhullPoint p= pc[0];
    +    QCOMPARE(p[2], 2.0);
    +    try{
    +        pc.setDimension(2);
    +        QFAIL("setDimension(2) did not fail for 3-d.");
    +    }catch (const std::exception &e) {
    +        const char *s= e.what();
    +        cout << "INFO   : Caught " << s;
    +    }
    +}//t_getset
    +
    +void PointCoordinates_test::
    +t_element()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc(q, 2, "2-d points", 6, c);
    +    QhullPoint p= pc.at(0);
    +    QCOMPARE(p, pc[0]);
    +    QCOMPARE(p, pc.first());
    +    QCOMPARE(p, pc.value(0));
    +    p= pc.back();
    +    QCOMPARE(p, pc[2]);
    +    QCOMPARE(p, pc.last());
    +    QCOMPARE(p, pc.value(2));
    +    QhullPoints ps= pc.mid(1, 2);
    +    QCOMPARE(ps[1], p);
    +}//t_element
    +
    +void PointCoordinates_test::
    +t_foreach()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc(q, 2, "2-d points", 6, c);
    +    QhullPoints::Iterator i= pc.begin();
    +    QhullPoint p= pc[0];
    +    QCOMPARE(*i, p);
    +    QCOMPARE((*i)[0], 0.0);
    +    QhullPoint p3= pc[2];
    +    i= pc.end();
    +    QCOMPARE(i[-1], p3);
    +    const PointCoordinates pc2(q, 2, "2-d points", 6, c);
    +    QhullPoints::ConstIterator i2= pc.begin();
    +    const QhullPoint p0= pc2[0];
    +    QCOMPARE(*i2, p0);
    +    QCOMPARE((*i2)[0], 0.0);
    +    QhullPoints::ConstIterator i3= i2;
    +    QCOMPARE(i3, i2);
    +    QCOMPARE((*i3)[0], 0.0);
    +    i3= pc.constEnd();
    +    --i3;
    +    QhullPoint p2= pc2[2];
    +    QCOMPARE(*i3, p2);
    +    i= pc.end();
    +    QVERIFY(i-1==i3);
    +    i2= pc2.end();
    +    QVERIFY(i2-1!=i3);
    +    QCOMPARE(*(i2-1), *i3);
    +    foreach(QhullPoint p3, pc){ //Qt only
    +        QVERIFY(p3[0]>=0.0);
    +        QVERIFY(p3[0]<=5.0);
    +    }
    +    Coordinates::ConstIterator i4= pc.beginCoordinates();
    +    QCOMPARE(*i4, 0.0);
    +    Coordinates::Iterator i5= pc.beginCoordinates();
    +    QCOMPARE(*i5, 0.0);
    +    i4= pc.beginCoordinates(1);
    +    QCOMPARE(*i4, 2.0);
    +    i5= pc.beginCoordinates(1);
    +    QCOMPARE(*i5, 2.0);
    +    i4= pc.endCoordinates();
    +    QCOMPARE(*--i4, 5.0);
    +    i5= pc.endCoordinates();
    +    QCOMPARE(*--i5, 5.0);
    +}//t_foreach
    +
    +void PointCoordinates_test::
    +t_search()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc(q, 2, "2-d points", 6, c);
    +    QhullPoint p0= pc[0];
    +    QhullPoint p2= pc[2];
    +    QVERIFY(pc.contains(p0));
    +    QVERIFY(pc.contains(p2));
    +    QCOMPARE(pc.count(p0), 1);
    +    QCOMPARE(pc.indexOf(p2), 2);
    +    QCOMPARE(pc.lastIndexOf(p0), 0);
    +}//t_search
    +
    +void PointCoordinates_test::
    +t_modify()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc(q, 2, "2-d points", 6, c);
    +    coordT c3[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc5(q, 2, "test explicit dimension");
    +    pc5.append(6, c3); // 0-5
    +    QVERIFY(pc5==pc);
    +    PointCoordinates pc2(q, 2, "2-d");
    +    coordT c2[]= {6.0, 7.0, 8.0, 9.0, 10.0, 11.0};
    +    pc2.append(6, c2);
    +    QCOMPARE(pc2.count(), 3);
    +    pc2.append(14.0);
    +    QCOMPARE(pc2.count(), 3);
    +    QCOMPARE(pc2.extraCoordinatesCount(), 1);
    +    pc2.append(15.0); // 6-11, 14,15
    +    QCOMPARE(pc2.count(), 4);
    +    QCOMPARE(pc2.extraCoordinatesCount(), 0);
    +    QhullPoint p(pc[0]);
    +    pc2.append(p); // 6-11, 14,15, 0,1
    +    QCOMPARE(pc2.count(), 5);
    +    QCOMPARE(pc2.extraCoordinatesCount(), 0);
    +    QCOMPARE(pc2.lastIndexOf(p), 4);
    +    pc.append(pc2); // Invalidates p
    +    QCOMPARE(pc.count(), 8); // 0-11, 14,15, 0,1
    +    QCOMPARE(pc.extraCoordinatesCount(), 0);
    +    QCOMPARE(pc.lastIndexOf(pc[0]), 7);
    +    pc.appendComment(" operators");
    +    QCOMPARE(pc.comment(), std::string("2-d points operators"));
    +    pc.checkValid();
    +    // see t_append_points for appendPoints
    +    PointCoordinates pc3= pc+pc2;
    +    pc3.checkValid();
    +    QCOMPARE(pc3.count(), 13);
    +    QCOMPARE(pc3[6][0], 14.0);
    +    QCOMPARE(pc3[8][0], 6.0);
    +    pc3 += pc;
    +    QCOMPARE(pc3.count(), 21);
    +    QCOMPARE(pc3[14][0], 2.0);
    +    pc3 += 12.0;
    +    pc3 += 14.0;
    +    QCOMPARE(pc3.count(), 22);
    +    QCOMPARE(pc3.last()[0], 12.0);
    +    // QhullPoint p3= pc3.first(); // += throws error because append may move the data
    +    QhullPoint p3= pc2.first();
    +    pc3 += p3;
    +    QCOMPARE(pc3.count(), 23);
    +    QCOMPARE(pc3.last()[0], 6.0);
    +    pc3 << pc;
    +    QCOMPARE(pc3.count(), 31);
    +    QCOMPARE(pc3.last()[0], 0.0);
    +    pc3 << 12.0 << 14.0;
    +    QCOMPARE(pc3.count(), 32);
    +    QCOMPARE(pc3.last()[0], 12.0);
    +    PointCoordinates pc4(pc3);
    +    pc4.reserveCoordinates(100);
    +    QVERIFY(pc3==pc4);
    +}//t_modify
    +
    +void PointCoordinates_test::
    +t_append_points()
    +{
    +    Qhull q;
    +    PointCoordinates pc(q, 2, "stringstream");
    +    stringstream s("2 3 1 2 3 4 5 6");
    +    pc.appendPoints(s);
    +    QCOMPARE(pc.count(), 3);
    +}//t_append_points
    +
    +void PointCoordinates_test::
    +t_coord_iterator()
    +{
    +    Qhull q;
    +    PointCoordinates c(q, 2, "2-d");
    +    c << 0.0 << 1.0 << 2.0 << 3.0 << 4.0 << 5.0;
    +    PointCoordinatesIterator i(c);
    +    QhullPoint p0(c[0]);
    +    QhullPoint p1(c[1]);
    +    QhullPoint p2(c[2]);
    +    coordT c2[] = {-1.0, -2.0};
    +    QhullPoint p3(q, 2, c2);
    +    PointCoordinatesIterator i2= c;
    +    QVERIFY(i.findNext(p1));
    +    QVERIFY(!i.findNext(p1));
    +    QVERIFY(!i.findNext(p2));
    +    QVERIFY(!i.findNext(p3));
    +    QVERIFY(i.findPrevious(p2));
    +    QVERIFY(!i.findPrevious(p2));
    +    QVERIFY(!i.findPrevious(p0));
    +    QVERIFY(!i.findPrevious(p3));
    +    QVERIFY(i2.findNext(p2));
    +    QVERIFY(i2.findPrevious(p0));
    +    QVERIFY(i2.findNext(p1));
    +    QVERIFY(i2.findPrevious(p0));
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    i2.toFront();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(i.hasPrevious());
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    PointCoordinates c3(q);
    +    PointCoordinatesIterator i3= c3;
    +    QVERIFY(!i3.hasNext());
    +    QVERIFY(!i3.hasPrevious());
    +    i3.toBack();
    +    QVERIFY(!i3.hasNext());
    +    QVERIFY(!i3.hasPrevious());
    +    QCOMPARE(i.peekPrevious(), p2);
    +    QCOMPARE(i.previous(), p2);
    +    QCOMPARE(i.previous(), p1);
    +    QCOMPARE(i.previous(), p0);
    +    QVERIFY(!i.hasPrevious());
    +    QCOMPARE(i.peekNext(), p0);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), p0);
    +    QCOMPARE(i.peekNext(), p1);
    +    QCOMPARE(i.next(), p1);
    +    QCOMPARE(i.next(), p2);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), p0);
    +}//t_coord_iterator
    +
    +void PointCoordinates_test::
    +t_io()
    +{
    +    Qhull q;
    +    PointCoordinates c(q);
    +    ostringstream os;
    +    os << "PointCoordinates 0-d\n" << c;
    +    c.setDimension(2);
    +    c << 1.0 << 2.0 << 3.0 << 1.0 << 2.0 << 3.0;
    +    os << "PointCoordinates 1,2 3,1 2,3\n" << c;
    +    cout << os.str();
    +    QString s= QString::fromStdString(os.str());
    +    QCOMPARE(s.count("0"), 3);
    +    QCOMPARE(s.count("2"), 5);
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/PointCoordinates_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullFacetList_test.cpp b/xs/src/qhull/src/qhulltest/QhullFacetList_test.cpp
    new file mode 100644
    index 0000000000..5a09d01da9
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullFacetList_test.cpp
    @@ -0,0 +1,196 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullFacetList_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/QhullVertexSet.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullFacetList_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct_qh();
    +    void t_construct_q();
    +    void t_convert();
    +    void t_readonly();
    +    void t_foreach();
    +    void t_io();
    +};//QhullFacetList_test
    +
    +void
    +add_QhullFacetList_test()
    +{
    +    new QhullFacetList_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullFacetList_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullFacetList_test::
    +t_construct_qh()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacetList fs2= q.facetList();
    +    QVERIFY(!fs2.isEmpty());
    +    QCOMPARE(fs2.count(),6);
    +    QhullFacetList fs3(q.endFacet(), q.endFacet());
    +    QVERIFY(fs3.isEmpty());
    +    QhullFacetList fs4(q.endFacet().previous(), q.endFacet());
    +    QCOMPARE(fs4.count(), 1);
    +    QhullFacetList fs5(q.beginFacet(), q.endFacet());
    +    QCOMPARE(fs2.count(), fs5.count());
    +    QVERIFY(fs2==fs5);
    +    QhullFacetList fs6= fs2; // copy constructor
    +    QVERIFY(fs6==fs2);
    +    std::vector fv= fs2.toStdVector();
    +    QCOMPARE(fv.size(), 6u);
    +}//t_construct_qh
    +
    +void QhullFacetList_test::
    +t_construct_q()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacetList fs2= q.facetList();
    +    QVERIFY(!fs2.isEmpty());
    +    QCOMPARE(fs2.count(),6);
    +    QhullFacetList fs3(q.endFacet(), q.endFacet());
    +    QVERIFY(fs3.isEmpty());
    +    QhullFacetList fs4(q.endFacet().previous(), q.endFacet());
    +    QCOMPARE(fs4.count(), 1);
    +    QhullFacetList fs5(q.beginFacet(), q.endFacet());
    +    QCOMPARE(fs2.count(), fs5.count());
    +    QVERIFY(fs2==fs5);
    +    QhullFacetList fs6= fs2; // copy constructor
    +    QVERIFY(fs6==fs2);
    +    std::vector fv= fs2.toStdVector();
    +    QCOMPARE(fv.size(), 6u);
    +}//t_construct_q
    +
    +void QhullFacetList_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0 QV2");  // rotated unit cube
    +    QhullFacetList fs2= q.facetList();
    +    QVERIFY(!fs2.isSelectAll());
    +    QVERIFY(!fs2.isEmpty());
    +    QCOMPARE(fs2.count(),3);
    +    std::vector fv= fs2.toStdVector();
    +    QCOMPARE(fv.size(), 3u);
    +    QList fv2= fs2.toQList();
    +    QCOMPARE(fv2.size(), 3);
    +    std::vector fv5= fs2.vertices_toStdVector();
    +    QCOMPARE(fv5.size(), 7u);
    +    QList fv6= fs2.vertices_toQList();
    +    QCOMPARE(fv6.size(), 7);
    +    fs2.selectAll();
    +    QVERIFY(fs2.isSelectAll());
    +    std::vector fv3= fs2.toStdVector();
    +    QCOMPARE(fv3.size(), 6u);
    +    QList fv4= fs2.toQList();
    +    QCOMPARE(fv4.size(), 6);
    +    std::vector fv7= fs2.vertices_toStdVector();
    +    QCOMPARE(fv7.size(), 8u);
    +    QList fv8= fs2.vertices_toQList();
    +    QCOMPARE(fv8.size(), 8);
    +}//t_convert
    +
    +//! Spot check properties and read-only.  See QhullLinkedList_test
    +void QhullFacetList_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QV0");  // good facets are adjacent to point 0
    +    QhullFacetList fs= q.facetList();
    +    QVERIFY(!fs.isSelectAll());
    +    QCOMPARE(fs.count(), 3);
    +    QCOMPARE(fs.first(), q.firstFacet());
    +    fs.selectAll();
    +    QVERIFY(fs.isSelectAll());
    +    QCOMPARE(fs.count(), 6);
    +    fs.selectGood();
    +    QVERIFY(!fs.isSelectAll());
    +    QCOMPARE(fs.count(), 3);
    +    fs.selectAll();
    +    QVERIFY(fs.isSelectAll());
    +    QCOMPARE(fs.count(), 6);
    +    QhullFacet f= fs.first();
    +    QhullFacet f2= fs.last();
    +    fs.selectAll();
    +    QVERIFY(fs.contains(f));
    +    QVERIFY(fs.contains(f2));
    +    QVERIFY(f.isGood());
    +    QVERIFY(!f2.isGood());
    +    fs.selectGood();
    +    QVERIFY(fs.contains(f));
    +    QVERIFY(!fs.contains(f2));
    +}//t_readonly
    +
    +void QhullFacetList_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");
    +    // Spot check predicates and accessors.  See QhullLinkedList_test
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullFacetList fs= q.facetList();
    +    QVERIFY(fs.contains(q.firstFacet()));
    +    QhullFacet f= q.firstFacet().next();
    +    QVERIFY(fs.contains(f));
    +    QCOMPARE(fs.first(), *fs.begin());
    +    QCOMPARE(*(fs.end()-1), fs.last());
    +    QCOMPARE(fs.first(), q.firstFacet());
    +    QCOMPARE(*fs.begin(), q.beginFacet());
    +    QCOMPARE(*fs.end(), q.endFacet());
    +}//t_foreach
    +
    +void QhullFacetList_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0 QV0");   // good facets are adjacent to point 0
    +        QhullFacetList fs= q.facetList();
    +        ostringstream os;
    +        os << fs.print("Show all of FacetList\n");
    +        os << "\nFacets only\n" << fs;
    +        os << "\nVertices only\n" << fs.printVertices();
    +        cout << os.str();
    +        QString facets= QString::fromStdString(os.str());
    +        QCOMPARE(facets.count("(v"), 2*7+12*3*2);
    +        QCOMPARE(facets.count(QRegExp("f\\d")), 2*3*7 + 13*3*2);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullFacetList_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullFacetSet_test.cpp b/xs/src/qhull/src/qhulltest/QhullFacetSet_test.cpp
    new file mode 100644
    index 0000000000..a7fe123a28
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullFacetSet_test.cpp
    @@ -0,0 +1,153 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullFacetSet_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullFacetSet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_readonly();
    +    void t_foreach();
    +    void t_io();
    +};//QhullFacetSet_test
    +
    +void
    +add_QhullFacetSet_test()
    +{
    +    new QhullFacetSet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullFacetSet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullFacetSet_test::
    +t_construct()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacet f= q.firstFacet();
    +    QhullFacetSet fs2= f.neighborFacets();
    +    QVERIFY(!fs2.isEmpty());
    +    QCOMPARE(fs2.count(),4);
    +    QhullFacetSet fs4= fs2; // copy constructor
    +    QVERIFY(fs4==fs2);
    +    QhullFacetSet fs3(q, q.qh()->facet_mergeset);
    +    QVERIFY(fs3.isEmpty());
    +}//t_construct
    +
    +void QhullFacetSet_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q2(rcube,"QR0 QV2");  // rotated unit cube
    +    QhullFacet f2= q2.firstFacet();
    +    QhullFacetSet fs2= f2.neighborFacets();
    +    QVERIFY(!fs2.isSelectAll());
    +    QCOMPARE(fs2.count(),2);
    +    std::vector fv= fs2.toStdVector();
    +    QCOMPARE(fv.size(), 2u);
    +    QList fv2= fs2.toQList();
    +    QCOMPARE(fv2.size(), 2);
    +    fs2.selectAll();
    +    QVERIFY(fs2.isSelectAll());
    +    std::vector fv3= fs2.toStdVector();
    +    QCOMPARE(fv3.size(), 4u);
    +    QList fv4= fs2.toQList();
    +    QCOMPARE(fv4.size(), 4);
    +}//t_convert
    +
    +//! Spot check properties and read-only.  See QhullSet_test
    +void QhullFacetSet_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QV0");  // good facets are adjacent to point 0
    +    QhullFacetSet fs= q.firstFacet().neighborFacets();
    +    QVERIFY(!fs.isSelectAll());
    +    QCOMPARE(fs.count(), 2);
    +    fs.selectAll();
    +    QVERIFY(fs.isSelectAll());
    +    QCOMPARE(fs.count(), 4);
    +    fs.selectGood();
    +    QVERIFY(!fs.isSelectAll());
    +    QCOMPARE(fs.count(), 2);
    +    QhullFacet f= fs.first();
    +    QhullFacet f2= fs.last();
    +    fs.selectAll();
    +    QVERIFY(fs.contains(f));
    +    QVERIFY(fs.contains(f2));
    +    QVERIFY(f.isGood());
    +    QVERIFY(!f2.isGood());
    +    fs.selectGood();
    +    QVERIFY(fs.contains(f));
    +    QVERIFY(!fs.contains(f2));
    +}//t_readonly
    +
    +void QhullFacetSet_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");
    +    // Spot check predicates and accessors.  See QhullLinkedList_test
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacetSet fs= q.firstFacet().neighborFacets();
    +    QVERIFY(!fs.contains(q.firstFacet()));
    +    QVERIFY(fs.contains(fs.first()));
    +    QhullFacet f= q.firstFacet().next();
    +    if(!fs.contains(f)){  // check if 'f' is the facet opposite firstFacet()
    +        f= f.next();
    +    }
    +    QVERIFY(fs.contains(f));
    +    QCOMPARE(fs.first(), *fs.begin());
    +    QCOMPARE(*(fs.end()-1), fs.last());
    +}//t_foreach
    +
    +void QhullFacetSet_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0 QV0");   // good facets are adjacent to point 0
    +        QhullFacetSet fs= q.firstFacet().neighborFacets();
    +        ostringstream os;
    +        os << fs.print("Neighbors of first facet with point 0");
    +        os << fs.printIdentifiers("\nFacet identifiers: ");
    +        cout << os.str();
    +        QString facets= QString::fromStdString(os.str());
    +        QCOMPARE(facets.count(QRegExp(" f[0-9]")), 2+13*2);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullFacetSet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullFacet_test.cpp b/xs/src/qhull/src/qhulltest/QhullFacet_test.cpp
    new file mode 100644
    index 0000000000..271f63753c
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullFacet_test.cpp
    @@ -0,0 +1,283 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullFacet_test.cpp#4 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/Coordinates.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullPointSet.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullFacet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct_qh();
    +    void t_constructConvert();
    +    void t_getSet();
    +    void t_value();
    +    void t_foreach();
    +    void t_io();
    +};//QhullFacet_test
    +
    +void
    +add_QhullFacet_test()
    +{
    +    new QhullFacet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullFacet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullFacet_test::
    +t_construct_qh()
    +{
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    QhullQh qh;
    +    QhullFacet f(&qh);
    +    QVERIFY(!f.isValid());
    +    QCOMPARE(f.dimension(),0);
    +}//t_construct_qh
    +
    +void QhullFacet_test::
    +t_constructConvert()
    +{
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    Qhull q2;
    +    QhullFacet f(q2);
    +    QVERIFY(!f.isValid());
    +    QCOMPARE(f.dimension(),0);
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullFacet f2(q.beginFacet());
    +    QCOMPARE(f2.dimension(),3);
    +    f= f2; // copy assignment
    +    QVERIFY(f.isValid());
    +    QCOMPARE(f.dimension(),3);
    +    QhullFacet f5= f2;
    +    QVERIFY(f5==f2);
    +    QVERIFY(f5==f);
    +    QhullFacet f3(q, f2.getFacetT());
    +    QCOMPARE(f,f3);
    +    QhullFacet f4(q, f2.getBaseT());
    +    QCOMPARE(f,f4);
    +}//t_constructConvert
    +
    +void QhullFacet_test::
    +t_getSet()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        cout << " rbox c | qhull Qt QR0 QR" << q.rotateRandom() << "   distanceEpsilon " << q.distanceEpsilon() << endl;
    +        QCOMPARE(q.facetCount(), 12);
    +        QCOMPARE(q.vertexCount(), 8);
    +        QhullFacetListIterator i(q.facetList());
    +        while(i.hasNext()){
    +            const QhullFacet f= i.next();
    +            cout << f.id() << endl;
    +            QCOMPARE(f.dimension(),3);
    +            QVERIFY(f.id()>0 && f.id()<=39);
    +            QVERIFY(f.isValid());
    +            if(i.hasNext()){
    +                QCOMPARE(f.next(), i.peekNext());
    +                QVERIFY(f.next()!=f);
    +            }
    +            QVERIFY(i.hasPrevious());
    +            QCOMPARE(f, i.peekPrevious());
    +        }
    +
    +        // test tricoplanarOwner
    +        QhullFacet facet = q.beginFacet();
    +        QhullFacet tricoplanarOwner = facet.tricoplanarOwner();
    +        int tricoplanarCount= 0;
    +        i.toFront();
    +        while(i.hasNext()){
    +            const QhullFacet f= i.next();
    +            if(f.tricoplanarOwner()==tricoplanarOwner){
    +                tricoplanarCount++;
    +            }
    +        }
    +        QCOMPARE(tricoplanarCount, 2);
    +        int tricoplanarCount2= 0;
    +        foreach (QhullFacet f, q.facetList()){  // Qt only
    +            QhullHyperplane h= f.hyperplane();
    +            cout << "Hyperplane: " << h;
    +            QCOMPARE(h.count(), 3);
    +            QCOMPARE(h.offset(), -0.5);
    +            double n= h.norm();
    +            QCOMPARE(n, 1.0);
    +            QhullHyperplane hi= f.innerplane();
    +            QCOMPARE(hi.count(), 3);
    +            double innerOffset= hi.offset()+0.5;
    +            cout << "InnerPlane: " << hi << "   innerOffset+0.5 " << innerOffset << endl;
    +            QVERIFY(innerOffset >= 0.0-(2*q.distanceEpsilon())); // A guessed epsilon.  It needs to account for roundoff due to rotation of the vertices
    +            QhullHyperplane ho= f.outerplane();
    +            QCOMPARE(ho.count(), 3);
    +            double outerOffset= ho.offset()+0.5;
    +            cout << "OuterPlane: " << ho << "   outerOffset+0.5 " << outerOffset << endl;
    +            QVERIFY(outerOffset <= 0.0+(2*q.distanceEpsilon())); // A guessed epsilon.  It needs to account for roundoff due to rotation of the vertices
    +            QVERIFY(outerOffset-innerOffset < 1e-7);
    +            for(int k= 0; k<3; k++){
    +                QVERIFY(ho[k]==hi[k]);
    +                QVERIFY(ho[k]==h[k]);
    +            }
    +            QhullPoint center= f.getCenter();
    +            cout << "Center: " << center;
    +            double d= f.distance(center);
    +            QVERIFY(d < innerOffset-outerOffset);
    +            QhullPoint center2= f.getCenter(qh_PRINTcentrums);
    +            QCOMPARE(center, center2);
    +            if(f.tricoplanarOwner()==tricoplanarOwner){
    +                tricoplanarCount2++;
    +            }
    +            cout << endl;
    +        }
    +        QCOMPARE(tricoplanarCount2, 2);
    +        Qhull q2(rcube,"d Qz Qt QR0");  // 3-d triangulation of Delaunay triangulation (the cube)
    +        cout << " rbox c | qhull d Qz Qt QR0 QR" << q2.rotateRandom() << "   distanceEpsilon " << q2.distanceEpsilon() << endl;
    +        QhullFacet f2= q2.firstFacet();
    +        QhullPoint center3= f2.getCenter(qh_PRINTtriangles);
    +        QCOMPARE(center3.dimension(), 3);
    +        QhullPoint center4= f2.getCenter();
    +        QCOMPARE(center4.dimension(), 4);
    +        for(int k= 0; k<3; k++){
    +            QVERIFY(center4[k]==center3[k]);
    +        }
    +        Qhull q3(rcube,"v Qz QR0");  // Voronoi diagram of a cube (one vertex)
    +        cout << " rbox c | qhull v Qz QR0 QR" << q3.rotateRandom() << "   distanceEpsilon " << q3.distanceEpsilon() << endl;
    +
    +        q3.setFactorEpsilon(400); // Voronoi vertices are not necessarily within distance episilon
    +        QhullPoint origin= q3.inputOrigin();
    +        int voronoiCount= 0;
    +        foreach(QhullFacet f, q3.facetList()){ //Qt only
    +            if(f.isGood()){
    +                ++voronoiCount;
    +                QhullPoint p= f.voronoiVertex();
    +                cout << p.print("Voronoi vertex: ")
    +                    << " Is it within " << q3.factorEpsilon() << " * distanceEpsilon (" << q3.distanceEpsilon() << ") of the origin?" << endl;
    +                QCOMPARE(p, origin);
    +            }
    +        }
    +        QCOMPARE(voronoiCount, 1);
    +    }
    +}//t_getSet
    +
    +void QhullFacet_test::
    +t_value()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        coordT c[]= {0.0, 0.0, 0.0};
    +        foreach (QhullFacet f, q.facetList()){  // Qt only
    +            double d= f.distance(q.origin());
    +            QCOMPARE(d, -0.5);
    +            double d0= f.distance(c);
    +            QCOMPARE(d0, -0.5);
    +            double facetArea= f.facetArea();
    +            QCOMPARE(facetArea, 1.0);
    +            #if qh_MAXoutside
    +                double maxoutside= f.getFacetT()->maxoutside;
    +                QVERIFY(maxoutside<1e-7);
    +            #endif
    +        }
    +    }
    +}//t_value
    +
    +void QhullFacet_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c W0 300");  // cube plus 300 points on its surface
    +    {
    +        Qhull q(rcube, "QR0 Qc"); // keep coplanars, thick facet, and rotate the cube
    +        int coplanarCount= 0;
    +        foreach(const QhullFacet f, q.facetList()){
    +            QhullPointSet coplanars= f.coplanarPoints();
    +            coplanarCount += coplanars.count();
    +            QhullFacetSet neighbors= f.neighborFacets();
    +            QCOMPARE(neighbors.count(), 4);
    +            QhullPointSet outsides= f.outsidePoints();
    +            QCOMPARE(outsides.count(), 0);
    +            QhullRidgeSet ridges= f.ridges();
    +            QCOMPARE(ridges.count(), 4);
    +            QhullVertexSet vertices= f.vertices();
    +            QCOMPARE(vertices.count(), 4);
    +            int ridgeCount= 0;
    +            QhullRidge r= ridges.first();
    +            for(int r0= r.id(); ridgeCount==0 || r.id()!=r0; r= r.nextRidge3d(f)){
    +                ++ridgeCount;
    +                if(!r.hasNextRidge3d(f)){
    +                    QFAIL("Unexpected simplicial facet.  They only have ridges to non-simplicial neighbors.");
    +                }
    +            }
    +            QCOMPARE(ridgeCount, 4);
    +        }
    +        QCOMPARE(coplanarCount, 300);
    +    }
    +}//t_foreach
    +
    +void QhullFacet_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullFacet f= q.beginFacet();
    +        cout << f;
    +        ostringstream os;
    +        os << f.print("\nWith a message\n");
    +        os << "\nPrint header for the same facet\n";
    +        os << f.printHeader();
    +        os << "\nPrint each component\n";
    +        os << f.printFlags("    - flags:");
    +        os << f.printCenter(qh_PRINTfacets, "    - center: ");
    +        os << f.printRidges();
    +        cout << os.str();
    +        ostringstream os2;
    +        os2 << f;
    +        QString facetString2= QString::fromStdString(os2.str());
    +        facetString2.replace(QRegExp("\\s\\s+"), " ");
    +        ostringstream os3;
    +        q.qh()->setOutputStream(&os3);
    +        q.outputQhull("f");
    +        QString facetsString= QString::fromStdString(os3.str());
    +        QString facetString3= facetsString.mid(facetsString.indexOf("- f1\n"));
    +        facetString3= facetString3.left(facetString3.indexOf("\n- f")+1);
    +        facetString3.replace(QRegExp("\\s\\s+"), " ");
    +        QCOMPARE(facetString2, facetString3);
    +    }
    +}//t_io
    +
    +// toQhullFacet is static_cast only
    +
    +}//orgQhull
    +
    +#include "moc/QhullFacet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullHyperplane_test.cpp b/xs/src/qhull/src/qhulltest/QhullHyperplane_test.cpp
    new file mode 100644
    index 0000000000..d016989a97
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullHyperplane_test.cpp
    @@ -0,0 +1,429 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullHyperplane_test.cpp#4 $$Change: 2064 $
    +** $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullHyperplane.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#include 
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullHyperplane_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_construct_qh();
    +    void t_convert();
    +    void t_readonly();
    +    void t_define();
    +    void t_value();
    +    void t_operator();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_qhullHyperplane_iterator();
    +    void t_io();
    +};//QhullHyperplane_test
    +
    +void
    +add_QhullHyperplane_test()
    +{
    +    new QhullHyperplane_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullHyperplane_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullHyperplane_test::
    +t_construct()
    +{
    +    QhullHyperplane h4;
    +    QVERIFY(!h4.isValid());
    +    QCOMPARE(h4.dimension(), 0);
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullHyperplane h(q);
    +    QVERIFY(!h.isValid());
    +    QCOMPARE(h.dimension(), 0);
    +    QCOMPARE(h.coordinates(),static_cast(0));
    +    QhullFacet f= q.firstFacet();
    +    QhullHyperplane h2(f.hyperplane());
    +    QVERIFY(h2.isValid());
    +    QCOMPARE(h2.dimension(), 3);
    +    h= h2;
    +    QCOMPARE(h, h2);
    +    QhullHyperplane h3(q, h2.dimension(), h2.coordinates(), h2.offset());
    +    QCOMPARE(h2, h3);
    +    QhullHyperplane h5= h2; // copy constructor
    +    QVERIFY(h5==h2);
    +}//t_construct
    +
    +void QhullHyperplane_test::
    +t_construct_qh()
    +{
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullFacet f= q.firstFacet();
    +    QhullHyperplane h2(f.hyperplane());
    +    QVERIFY(h2.isValid());
    +    QCOMPARE(h2.dimension(), 3);
    +    // h= h2;  // copy assignment disabled, ambiguous
    +    QhullHyperplane h3(q.qh(), h2.dimension(), h2.coordinates(), h2.offset());
    +    QCOMPARE(h2, h3);
    +    QhullHyperplane h5= h2; // copy constructor
    +    QVERIFY(h5==h2);
    +}//t_construct_qh
    +
    +void QhullHyperplane_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullHyperplane h= q.firstFacet().hyperplane();
    +    std::vector fs= h.toStdVector();
    +    QCOMPARE(fs.size(), 4u);
    +    double offset= fs.back();
    +    fs.pop_back();
    +    QCOMPARE(offset, -0.5);
    +
    +    double squareNorm= inner_product(fs.begin(), fs.end(), fs.begin(), 0.0);
    +    QCOMPARE(squareNorm, 1.0);
    +    QList qs= h.toQList();
    +    QCOMPARE(qs.size(), 4);
    +    double offset2= qs.takeLast();
    +    QCOMPARE(offset2, -0.5);
    +    double squareNorm2= std::inner_product(qs.begin(), qs.end(), qs.begin(), 0.0);
    +    QCOMPARE(squareNorm2, 1.0);
    +}//t_convert
    +
    +void QhullHyperplane_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QhullFacetList fs= q.facetList();
    +        QhullFacetListIterator i(fs);
    +        while(i.hasNext()){
    +            QhullFacet f= i.next();
    +            QhullHyperplane h= f.hyperplane();
    +            int id= f.id();
    +            cout << "h" << id << endl;
    +            QVERIFY(h.isValid());
    +            QCOMPARE(h.dimension(),3);
    +            const coordT *c= h.coordinates();
    +            coordT *c2= h.coordinates();
    +            QCOMPARE(c, c2);
    +            const coordT *c3= h.begin();
    +            QCOMPARE(c, c3);
    +            QCOMPARE(h.offset(), -0.5);
    +            int j= h.end()-h.begin();
    +            QCOMPARE(j, 3);
    +            double squareNorm= std::inner_product(h.begin(), h.end(), h.begin(), 0.0);
    +            QCOMPARE(squareNorm, 1.0);
    +        }
    +        QhullHyperplane h2= fs.first().hyperplane();
    +        QhullHyperplane h3= fs.last().hyperplane();
    +        QVERIFY(h2!=h3);
    +        QVERIFY(h3.coordinates()!=h2.coordinates());
    +    }
    +}//t_readonly
    +
    +void QhullHyperplane_test::
    +t_define()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QhullFacetList fs= q.facetList();
    +        QhullHyperplane h= fs.first().hyperplane();
    +        QhullHyperplane h2= h;
    +        QVERIFY(h==h2);
    +        QhullHyperplane h3= fs.last().hyperplane();
    +        QVERIFY(h2!=h3);
    +
    +        QhullHyperplane h4= h3;
    +        h4.defineAs(h2);
    +        QVERIFY(h2==h4);
    +        QhullHyperplane p5= h3;
    +        p5.defineAs(h2.dimension(), h2.coordinates(), h2.offset());
    +        QVERIFY(h2==p5);
    +        QhullHyperplane h6= h3;
    +        h6.setCoordinates(h2.coordinates());
    +        QCOMPARE(h2.coordinates(), h6.coordinates());
    +        h6.setOffset(h2.offset());
    +        QCOMPARE(h2.offset(), h6.offset());
    +        QVERIFY(h2==h6);
    +        h6.setDimension(2);
    +        QCOMPARE(h6.dimension(), 2);
    +        QVERIFY(h2!=h6);
    +    }
    +}//t_define
    +
    +void QhullHyperplane_test::
    +t_value()
    +{
    +    RboxPoints rcube("c G1");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullFacet f= q.firstFacet();
    +    QhullFacet f2= f.neighborFacets().at(0);
    +    const QhullHyperplane h= f.hyperplane();
    +    const QhullHyperplane h2= f2.hyperplane();   // At right angles
    +    double dist= h.distance(q.origin());
    +    QCOMPARE(dist, -1.0);
    +    double norm= h.norm();
    +    QCOMPARE(norm, 1.0);
    +    double angle= h.hyperplaneAngle(h2);
    +    cout << "angle " << angle << endl;
    +    QCOMPARE(angle+1.0, 1.0); // qFuzzyCompare does not work for 0.0
    +    QVERIFY(h==h);
    +    QVERIFY(h!=h2);
    +}//t_value
    +
    +void QhullHyperplane_test::
    +t_operator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    const QhullHyperplane h= q.firstFacet().hyperplane();
    +    //operator== and operator!= tested elsewhere
    +    const coordT *c= h.coordinates();
    +    for(int k=h.dimension(); k--; ){
    +        QCOMPARE(c[k], h[k]);
    +    }
    +    //h[0]= 10.0; // compiler error, const
    +    QhullHyperplane h2= q.firstFacet().hyperplane();
    +    h2[0]= 10.0;  // Overwrites Hyperplane coordinate!
    +    QCOMPARE(h2[0], 10.0);
    +}//t_operator
    +
    +void QhullHyperplane_test::
    +t_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullHyperplane h= q.firstFacet().hyperplane();
    +        QCOMPARE(h.count(), 3);
    +        QCOMPARE(h.size(), 3u);
    +        QhullHyperplane::Iterator i= h.begin();
    +        QhullHyperplane::iterator i2= h.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= h.begin();
    +        QVERIFY(i==i2);
    +        i2= h.end();
    +        QVERIFY(i!=i2);
    +        double d3= *i;
    +        i2--;
    +        double d2= *i2;
    +        QCOMPARE(d3, h[0]);
    +        QCOMPARE(d2, h[2]);
    +        QhullHyperplane::Iterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3), h[1]);
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        QhullHyperplane::ConstIterator i4= h.begin();
    +        QVERIFY(i==i4); // iterator COMP const_iterator
    +        QVERIFY(i<=i4);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4==i); // const_iterator COMP iterator
    +        QVERIFY(i4<=i);
    +        QVERIFY(i4>=i);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4<=i);
    +        QVERIFY(i2!=i4);
    +        QVERIFY(i2>i4);
    +        QVERIFY(i2>=i4);
    +        QVERIFY(i4!=i2);
    +        QVERIFY(i4i);
    +        QVERIFY(i4>=i);
    +
    +        i= h.begin();
    +        i2= h.begin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, h[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, h.begin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2 += 3, h.end());
    +        QCOMPARE(i2 -= 3, h.begin());
    +        QCOMPARE(i2+0, h.begin());
    +        QCOMPARE(i2+3, h.end());
    +        i2 += 3;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-3;
    +        QCOMPARE(i, h.begin());
    +        QCOMPARE(i2-i, 3);
    +
    +        //h.begin end tested above
    +
    +        // QhullHyperplane is const-only
    +    }
    +}//t_iterator
    +
    +void QhullHyperplane_test::
    +t_const_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullHyperplane h= q.firstFacet().hyperplane();
    +        QhullHyperplane::ConstIterator i= h.begin();
    +        QhullHyperplane::const_iterator i2= h.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= h.begin();
    +        QVERIFY(i==i2);
    +        i2= h.end();
    +        QVERIFY(i!=i2);
    +        double d3= *i;
    +        i2--;
    +        double d2= *i2;
    +        QCOMPARE(d3, h[0]);
    +        QCOMPARE(d2, h[2]);
    +        QhullHyperplane::ConstIterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3), h[1]);
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        // See t_iterator for const_iterator COMP iterator
    +
    +        i= h.begin();
    +        i2= h.constBegin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, h[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, h.constBegin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2+=3, h.constEnd());
    +        QCOMPARE(i2-=3, h.constBegin());
    +        QCOMPARE(i2+0, h.constBegin());
    +        QCOMPARE(i2+3, h.constEnd());
    +        i2 += 3;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-3;
    +        QCOMPARE(i, h.constBegin());
    +        QCOMPARE(i2-i, 3);
    +
    +        // QhullHyperplane is const-only
    +    }
    +}//t_const_iterator
    +
    +void QhullHyperplane_test::
    +t_qhullHyperplane_iterator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullHyperplane h = q.firstFacet().hyperplane();
    +    QhullHyperplaneIterator i2(h);
    +    QCOMPARE(h.dimension(), 3);
    +    QhullHyperplaneIterator i= h;
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    i.toFront();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    // i at front, i2 at end/back, 3 coordinates
    +    QCOMPARE(i.peekNext(), h[0]);
    +    QCOMPARE(i2.peekPrevious(), h[2]);
    +    QCOMPARE(i2.previous(), h[2]);
    +    QCOMPARE(i2.previous(), h[1]);
    +    QCOMPARE(i2.previous(), h[0]);
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), h[0]);
    +    // i.peekNext()= 1.0; // compiler error, i is const
    +    QCOMPARE(i.next(), h[0]);
    +    QCOMPARE(i.peekNext(), h[1]);
    +    QCOMPARE(i.next(), h[1]);
    +    QCOMPARE(i.next(), h[2]);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), h[0]);
    +}//t_qhullHyperplane_iterator
    +
    +void QhullHyperplane_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullHyperplane h= q.firstFacet().hyperplane();
    +        ostringstream os;
    +        os << "Hyperplane:\n";
    +        os << h;
    +        os << h.print("message");
    +        os << h.print(" and a message ", " offset ");
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count("1"), 3);
    +        // QCOMPARE(s.count(QRegExp("f\\d")), 3*7 + 13*3*2);
    +    }
    +}//t_io
    +
    +
    +}//orgQhull
    +
    +#include "moc/QhullHyperplane_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullLinkedList_test.cpp b/xs/src/qhull/src/qhulltest/QhullLinkedList_test.cpp
    new file mode 100644
    index 0000000000..e0cde4050f
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullLinkedList_test.cpp
    @@ -0,0 +1,330 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullLinkedList_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h"
    +
    +#include "libqhullcpp/QhullLinkedList.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +namespace orgQhull {
    +
    +class QhullLinkedList_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_element();
    +    void t_search();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_QhullLinkedList_iterator();
    +    void t_io();
    +};//QhullLinkedList_test
    +
    +void
    +add_QhullLinkedList_test()
    +{
    +    new QhullLinkedList_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullLinkedList_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullLinkedList_test::
    +t_construct()
    +{
    +    // QhullLinkedList vs; //private (compiler error).  No memory allocation
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q.facetCount(), 12);
    +        QhullVertexList vs = QhullVertexList(q.beginVertex(), q.endVertex());
    +        QCOMPARE(vs.count(), 8);
    +        QCOMPARE(vs.size(), 8u);
    +        QVERIFY(!vs.isEmpty());
    +        QhullVertexList vs2 = q.vertexList();
    +        QCOMPARE(vs2.count(), 8);
    +        QCOMPARE(vs2.size(),8u);
    +        QVERIFY(!vs2.isEmpty());
    +        QVERIFY(vs==vs2);
    +        // vs= vs2; // disabled.  Would not copy the vertices
    +        QhullVertexList vs3= vs2; // copy constructor
    +        QVERIFY(vs3==vs2);
    +    }
    +}//t_construct
    +
    +void QhullLinkedList_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q.facetCount(), 12);
    +        QhullVertexList vs = q.vertexList();
    +        QCOMPARE(vs.size(), 8u);
    +        QVERIFY(!vs.isEmpty());
    +        std::vector vs2= vs.toStdVector();
    +        QCOMPARE(vs2.size(), vs.size());
    +        QhullVertexList::Iterator i= vs.begin();
    +        for(int k= 0; k<(int)vs2.size(); k++){
    +            QCOMPARE(vs2[k], *i++);
    +        }
    +        QList vs3= vs.toQList();
    +        QCOMPARE(vs3.count(), vs.count());
    +        i= vs.begin();
    +        for(int k= 0; k
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullPointSet.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +
    +namespace orgQhull {
    +
    +class QhullPointSet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_element();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_search();
    +    void t_pointset_iterator();
    +    void t_io();
    +};//QhullPointSet_test
    +
    +void
    +add_QhullPointSet_test()
    +{
    +    new QhullPointSet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullPointSet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullPointSet_test::
    +t_construct()
    +{
    +    // Default constructor is disallowed (i.e., private)
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    int coplanarCount= 0;
    +    foreach(QhullFacet f, q.facetList()){
    +        QhullPointSet ps(q, f.getFacetT()->outsideset);
    +        QVERIFY(ps.isEmpty());
    +        QCOMPARE(ps.count(), 0);
    +        QCOMPARE(ps.size(), 0u);
    +        QhullPointSet ps2(q.qh(), f.getFacetT()->coplanarset);
    +        QVERIFY(!ps2.isEmpty());
    +        coplanarCount += ps2.count();
    +        QCOMPARE(ps2.count(), (int)ps2.size());
    +        QhullPointSet ps3(ps2);
    +        QVERIFY(!ps3.isEmpty());
    +        QCOMPARE(ps3.count(), ps2.count());
    +        QVERIFY(ps3==ps2);
    +        QVERIFY(ps3!=ps);
    +        QhullPointSet ps4= ps3;
    +        QVERIFY(ps4==ps2);
    +    }
    +    QCOMPARE(coplanarCount, 1000);
    +}//t_construct
    +
    +void QhullPointSet_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=1);   // Sometimes no coplanar points
    +    std::vector vs= ps.toStdVector();
    +    QCOMPARE(vs.size(), ps.size());
    +    QhullPoint p= ps[0];
    +    QhullPoint p2= vs[0];
    +    QCOMPARE(p, p2);
    +    QList qs= ps.toQList();
    +    QCOMPARE(qs.size(), static_cast(ps.size()));
    +    QhullPoint p3= qs[0];
    +    QCOMPARE(p3, p);
    +}//t_convert
    +
    +// readonly tested in t_construct
    +//   empty, isEmpty, ==, !=, size
    +
    +void QhullPointSet_test::
    +t_element()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPoint p= ps[0];
    +    QCOMPARE(p, ps[0]);
    +    QhullPoint p2= ps[ps.count()-1];
    +    QCOMPARE(ps.at(1), ps[1]);
    +    QCOMPARE(ps.second(), ps[1]);
    +    QCOMPARE(ps.first(), p);
    +    QCOMPARE(ps.front(), ps.first());
    +    QCOMPARE(ps.last(), p2);
    +    QCOMPARE(ps.back(), ps.last());
    +    QhullPoint p8(q);
    +    QCOMPARE(ps.value(2), ps[2]);
    +    QCOMPARE(ps.value(-1), p8);
    +    QCOMPARE(ps.value(ps.count()), p8);
    +    QCOMPARE(ps.value(ps.count(), p), p);
    +    QVERIFY(ps.value(1, p)!=p);
    +    QhullPointSet ps8= f.coplanarPoints();
    +    QhullPointSet::Iterator i= ps8.begin();
    +    foreach(QhullPoint p9, ps){  // Qt only
    +        QCOMPARE(p9.dimension(), 3);
    +        QCOMPARE(p9, *i++);
    +    }
    +}//t_element
    +
    +void QhullPointSet_test::
    +t_iterator()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPointSet::Iterator i= ps.begin();
    +    QhullPointSet::iterator i2= ps.begin();
    +    QVERIFY(i==i2);
    +    QVERIFY(i>=i2);
    +    QVERIFY(i<=i2);
    +    i= ps.begin();
    +    QVERIFY(i==i2);
    +    i2= ps.end();
    +    QVERIFY(i!=i2);
    +    QhullPoint p= *i;
    +    QCOMPARE(p.dimension(), q.dimension());
    +    QCOMPARE(p, ps[0]);
    +    i2--;
    +    QhullPoint p2= *i2;
    +    QCOMPARE(p2.dimension(), q.dimension());
    +    QCOMPARE(p2, ps.last());
    +    QhullPointSet::Iterator i5(i2);
    +    QCOMPARE(*i2, *i5);
    +    QhullPointSet::Iterator i3= i+1;
    +    QVERIFY(i!=i3);
    +    QCOMPARE(i[1], *i3);
    +    (i3= i)++;
    +    QCOMPARE((*i3)[0], ps[1][0]);
    +    QCOMPARE((*i3).dimension(), 3);
    +
    +    QVERIFY(i==i);
    +    QVERIFY(i!=i3);
    +    QVERIFY(ii);
    +    QVERIFY(i3>=i);
    +
    +    QhullPointSet::ConstIterator i4= ps.begin();
    +    QVERIFY(i==i4); // iterator COMP const_iterator
    +    QVERIFY(i<=i4);
    +    QVERIFY(i>=i4);
    +    QVERIFY(i4==i); // const_iterator COMP iterator
    +    QVERIFY(i4<=i);
    +    QVERIFY(i4>=i);
    +    QVERIFY(i>=i4);
    +    QVERIFY(i4<=i);
    +    QVERIFY(i2!=i4);
    +    QVERIFY(i2>i4);
    +    QVERIFY(i2>=i4);
    +    QVERIFY(i4!=i2);
    +    QVERIFY(i4i);
    +    QVERIFY(i4>=i);
    +    i4= ps.constBegin();
    +    QVERIFY(i==i4); // iterator COMP const_iterator
    +    QCOMPARE(i4+ps.count(), ps.constEnd());
    +
    +    i= ps.begin();
    +    i2= ps.begin();
    +    QCOMPARE(i, i2++);
    +    QCOMPARE(*i2, ps[1]);
    +    QCOMPARE(++i, i2);
    +    QCOMPARE(i, i2--);
    +    QCOMPARE(i2, ps.begin());
    +    QCOMPARE(--i, i2);
    +    QCOMPARE(i2+=ps.count(), ps.end());
    +    QCOMPARE(i2-=ps.count(), ps.begin());
    +    QCOMPARE(i2+0, ps.begin());
    +    QCOMPARE(i2+ps.count(), ps.end());
    +    i2 += ps.count();
    +    i= i2-0;
    +    QCOMPARE(i, i2);
    +    i= i2-ps.count();
    +    QCOMPARE(i, ps.begin());
    +    QCOMPARE(i2-i, ps.count());
    +
    +    //ps.begin end tested above
    +
    +    // QhullPointSet is const-only
    +}//t_iterator
    +
    +void QhullPointSet_test::
    +t_const_iterator()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPointSet::ConstIterator i= ps.begin();
    +    QhullPointSet::const_iterator i2= ps.begin();
    +    QVERIFY(i==i2);
    +    QVERIFY(i>=i2);
    +    QVERIFY(i<=i2);
    +
    +    // See t_iterator for const_iterator COMP iterator
    +
    +    i= ps.begin();
    +    QVERIFY(i==i2);
    +    i2= ps.end();
    +    QVERIFY(i!=i2);
    +    QhullPoint p= *i; // QhullPoint is the base class for QhullPointSet::iterator
    +    QCOMPARE(p.dimension(), q.dimension());
    +    QCOMPARE(p, ps[0]);
    +    i2--;
    +    QhullPoint p2= *i2;
    +    QCOMPARE(p2.dimension(), q.dimension());
    +    QCOMPARE(p2, ps.last());
    +    QhullPointSet::ConstIterator i5(i2);
    +    QCOMPARE(*i2, *i5);
    +
    +
    +    QhullPointSet::ConstIterator i3= i+1;
    +    QVERIFY(i!=i3);
    +    QCOMPARE(i[1], *i3);
    +
    +    QVERIFY(i==i);
    +    QVERIFY(i!=i3);
    +    QVERIFY(ii);
    +    QVERIFY(i3>=i);
    +
    +    // QhullPointSet is const-only
    +}//t_const_iterator
    +
    +
    +void QhullPointSet_test::
    +t_search()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPoint p= ps.first();
    +    QhullPoint p2= ps.last();
    +    QVERIFY(ps.contains(p));
    +    QVERIFY(ps.contains(p2));
    +    QVERIFY(p!=p2);
    +    QhullPoint p3= ps[2];
    +    QVERIFY(ps.contains(p3));
    +    QVERIFY(p!=p3);
    +    QCOMPARE(ps.indexOf(p), 0);
    +    QCOMPARE(ps.indexOf(p2), ps.count()-1);
    +    QCOMPARE(ps.indexOf(p3), 2);
    +    QhullPoint p4(q);
    +    QCOMPARE(ps.indexOf(p4), -1);
    +    QCOMPARE(ps.lastIndexOf(p), 0);
    +    QCOMPARE(ps.lastIndexOf(p2), ps.count()-1);
    +    QCOMPARE(ps.lastIndexOf(p3), 2);
    +    QCOMPARE(ps.lastIndexOf(p4), -1);
    +}//t_search
    +
    +void QhullPointSet_test::
    +t_pointset_iterator()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps2= f.outsidePoints();
    +    QVERIFY(ps2.count()==0); // No outside points after constructing the convex hull
    +    QhullPointSetIterator i2= ps2;
    +    QCOMPARE(i2.countRemaining(), 0);
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    i2.toBack();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPointSetIterator i(ps);
    +    i2= ps;
    +    QCOMPARE(i2.countRemaining(), ps.count());
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    QCOMPARE(i2.countRemaining(), 0);
    +    i.toFront();
    +    QCOMPARE(i.countRemaining(), ps.count());
    +    QCOMPARE(i2.countRemaining(), 0);
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    QhullPoint p= ps[0];
    +    QhullPoint p2(ps[0]);
    +    QCOMPARE(p, p2);
    +    QVERIFY(p==p2);
    +    QhullPoint p3(ps.last());
    + // p2[0]= 0.0;
    +    QVERIFY(p==p2);
    +    QCOMPARE(i2.peekPrevious(), p3);
    +    QCOMPARE(i2.previous(), p3);
    +    QCOMPARE(i2.previous(), ps[ps.count()-2]);
    +    QVERIFY(i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), p);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), p);
    +    QCOMPARE(i.countRemaining(), ps.count()-1);
    +    QhullPoint p4= i.peekNext();
    +    QVERIFY(p4!=p3);
    +    QCOMPARE(i.next(), p4);
    +    QVERIFY(i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), p);
    +}//t_pointset_iterator
    +
    +void QhullPointSet_test::
    +t_io()
    +{
    +    ostringstream os;
    +    RboxPoints rcube("c W0 120");
    +    Qhull q(rcube,"Qc");  // cube with 100 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    os << "QhullPointSet from coplanarPoints\n" << ps << endl;
    +    os << ps.print("\nWith message\n");
    +    os << ps.printIdentifiers("\nCoplanar points: ");
    +    os << "\nAs a point set:\n";
    +    os << ps;
    +    cout << os.str();
    +    QString s= QString::fromStdString(os.str());
    +    QCOMPARE(s.count(" 0.5\n"), 3*ps.count());
    +    QCOMPARE(s.count("p"), ps.count()+4);
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullPointSet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullPoint_test.cpp b/xs/src/qhull/src/qhulltest/QhullPoint_test.cpp
    new file mode 100644
    index 0000000000..1528086a15
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullPoint_test.cpp
    @@ -0,0 +1,437 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullPoint_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/Coordinates.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullPoint_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_readonly();
    +    void t_define();
    +    void t_operator();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_qhullpoint_iterator();
    +    void t_method();
    +    void t_io();
    +};//QhullPoint_test
    +
    +void
    +add_QhullPoint_test()
    +{
    +    new QhullPoint_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each test
    +void QhullPoint_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullPoint_test::
    +t_construct()
    +{
    +    QhullPoint p12;
    +    QVERIFY(!p12.isValid());
    +    QCOMPARE(p12.coordinates(), (coordT *)0);
    +    QCOMPARE(p12.dimension(), 0);
    +    QCOMPARE(p12.qh(), (QhullQh *)0);
    +    QCOMPARE(p12.id(), -3);
    +    QCOMPARE(p12.begin(), p12.end());
    +    QCOMPARE(p12.constBegin(), p12.constEnd());
    +
    +    RboxPoints rcube("c");
    +    Qhull q(rcube, "Qt QR0");  // triangulation of rotated unit cube
    +    QhullPoint p(q);
    +    QVERIFY(!p.isValid());
    +    QCOMPARE(p.dimension(),3);
    +    QCOMPARE(p.coordinates(),static_cast(0));
    +    QhullPoint p7(q.qh());
    +    QCOMPARE(p, p7);
    +
    +    // copy constructor and copy assignment
    +    QhullVertex v2(q.beginVertex());
    +    QhullPoint p2(v2.point());
    +    QVERIFY(p2.isValid());
    +    QCOMPARE(p2.dimension(),3);
    +    QVERIFY(p2!=p12);
    +    p= p2;
    +    QCOMPARE(p, p2);
    +
    +    QhullPoint p3(q, p2.dimension(), p2.coordinates());
    +    QCOMPARE(p3, p2);
    +    QhullPoint p8(q, p2.coordinates()); // Qhull defines dimension
    +    QCOMPARE(p8, p2);
    +    QhullPoint p9(q.qh(), p2.dimension(), p2.coordinates());
    +    QCOMPARE(p9, p2);
    +    QhullPoint p10(q.qh(), p2.coordinates()); // Qhull defines dimension
    +    QCOMPARE(p10, p2);
    +
    +    Coordinates c;
    +    c << 0.0 << 0.0 << 0.0;
    +    QhullPoint p6(q, c);
    +    QCOMPARE(p6, q.origin());
    +    QhullPoint p11(q.qh(), c);
    +    QCOMPARE(p11, q.origin());
    +
    +    QhullPoint p5= p2; // copy constructor
    +    QVERIFY(p5==p2);
    +}//t_construct
    +
    +void QhullPoint_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullVertex v= q.firstVertex();
    +    QhullPoint p= v.point();
    +    std::vector vs= p.toStdVector();
    +    QCOMPARE(vs.size(), 3u);
    +    for(int k=3; k--; ){
    +        QCOMPARE(vs[k], p[k]);
    +    }
    +    QList qs= p.toQList();
    +    QCOMPARE(qs.size(), 3);
    +    for(int k=3; k--; ){
    +        QCOMPARE(qs[k], p[k]);
    +    }
    +}//t_convert
    +
    +void QhullPoint_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QhullVertexList vs= q.vertexList();
    +        cout << "Point ids in 'rbox c'\n";
    +        QhullVertexListIterator i(vs);
    +        while(i.hasNext()){
    +            QhullPoint p= i.next().point();
    +            int id= p.id();
    +            cout << "p" << id << endl;
    +            QVERIFY(p.isValid());
    +            QCOMPARE(p.dimension(),3);
    +            QCOMPARE(id, p.id());
    +            QVERIFY(p.id()>=0 && p.id()<9);
    +            const coordT *c= p.coordinates();
    +            coordT *c2= p.coordinates();
    +            QCOMPARE(c, c2);
    +            QCOMPARE(p.dimension(), 3);
    +            QCOMPARE(q.qh(), p.qh());
    +        }
    +        QhullPoint p2= vs.first().point();
    +        QhullPoint p3= vs.last().point();
    +        QVERIFY(p2!=p3);
    +        QVERIFY(p3.coordinates()!=p2.coordinates());
    +    }
    +}//t_readonly
    +
    +void QhullPoint_test::
    +t_define()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QhullVertexList vs= q.vertexList();
    +        QhullPoint p= vs.first().point();
    +        QhullPoint p2= p;
    +        QVERIFY(p==p2);
    +        QhullPoint p3= vs.last().point();
    +        QVERIFY(p2!=p3);
    +        int idx= (p3.coordinates()-p2.coordinates())/p2.dimension();
    +        QVERIFY(idx>-8 && idx<8);
    +        p2.advancePoint(idx);
    +        QVERIFY(p2==p3);
    +        p2.advancePoint(-idx);
    +        QVERIFY(p2==p);
    +        p2.advancePoint(0);
    +        QVERIFY(p2==p);
    +
    +        QhullPoint p4= p3;
    +        QVERIFY(p4==p3);
    +        p4.defineAs(p2);
    +        QVERIFY(p2==p4);
    +        QhullPoint p5= p3;
    +        p5.defineAs(p2.dimension(), p2.coordinates());
    +        QVERIFY(p2==p5);
    +        QhullPoint p6= p3;
    +        p6.setCoordinates(p2.coordinates());
    +        QCOMPARE(p2.coordinates(), p6.coordinates());
    +        QVERIFY(p2==p6);
    +        p6.setDimension(2);
    +        QCOMPARE(p6.dimension(), 2);
    +        QVERIFY(p2!=p6);
    +    }
    +}//t_define
    +
    +void QhullPoint_test::
    +t_operator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    const QhullPoint p= q.firstVertex().point();
    +    //operator== and operator!= tested elsewhere
    +    const coordT *c= p.coordinates();
    +    for(int k=p.dimension(); k--; ){
    +        QCOMPARE(c[k], p[k]);
    +    }
    +    //p[0]= 10.0; // compiler error, const
    +    QhullPoint p2= q.firstVertex().point();
    +    p2[0]= 10.0;  // Overwrites point coordinate
    +    QCOMPARE(p2[0], 10.0);
    +}//t_operator
    +
    +void QhullPoint_test::
    +t_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullPoint p2(q);
    +        QCOMPARE(p2.begin(), p2.end());
    +
    +        QhullPoint p= q.firstVertex().point();
    +        QhullPoint::Iterator i= p.begin();
    +        QhullPoint::iterator i2= p.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= p.begin();
    +        QVERIFY(i==i2);
    +        i2= p.end();
    +        QVERIFY(i!=i2);
    +        double d3= *i;
    +        i2--;
    +        double d2= *i2;
    +        QCOMPARE(d3, p[0]);
    +        QCOMPARE(d2, p[2]);
    +        QhullPoint::Iterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3), p[1]);
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        QhullPoint::ConstIterator i4= p.begin();
    +        QVERIFY(i==i4); // iterator COMP const_iterator
    +        QVERIFY(i<=i4);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4==i); // const_iterator COMP iterator
    +        QVERIFY(i4<=i);
    +        QVERIFY(i4>=i);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4<=i);
    +        QVERIFY(i2!=i4);
    +        QVERIFY(i2>i4);
    +        QVERIFY(i2>=i4);
    +        QVERIFY(i4!=i2);
    +        QVERIFY(i4i);
    +        QVERIFY(i4>=i);
    +
    +        i= p.begin();
    +        i2= p.begin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, p[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, p.begin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2 += 3, p.end());
    +        QCOMPARE(i2 -= 3, p.begin());
    +        QCOMPARE(i2+0, p.begin());
    +        QCOMPARE(i2+3, p.end());
    +        i2 += 3;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-3;
    +        QCOMPARE(i, p.begin());
    +        QCOMPARE(i2-i, 3);
    +
    +        //p.begin end tested above
    +
    +        // QhullPoint is const-only
    +    }
    +}//t_iterator
    +
    +void QhullPoint_test::
    +t_const_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullPoint p= q.firstVertex().point();
    +        QhullPoint::ConstIterator i= p.begin();
    +        QhullPoint::const_iterator i2= p.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= p.begin();
    +        QVERIFY(i==i2);
    +        i2= p.end();
    +        QVERIFY(i!=i2);
    +        double d3= *i;
    +        i2--;
    +        double d2= *i2;
    +        QCOMPARE(d3, p[0]);
    +        QCOMPARE(d2, p[2]);
    +        QhullPoint::ConstIterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3), p[1]);
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        // See t_iterator for const_iterator COMP iterator
    +
    +        i= p.begin();
    +        i2= p.constBegin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, p[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, p.constBegin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2+=3, p.constEnd());
    +        QCOMPARE(i2-=3, p.constBegin());
    +        QCOMPARE(i2+0, p.constBegin());
    +        QCOMPARE(i2+3, p.constEnd());
    +        i2 += 3;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-3;
    +        QCOMPARE(i, p.constBegin());
    +        QCOMPARE(i2-i, 3);
    +
    +        // QhullPoint is const-only
    +    }
    +}//t_const_iterator
    +
    +void QhullPoint_test::
    +t_qhullpoint_iterator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +
    +    QhullPoint p2(q);
    +    QhullPointIterator i= p2;
    +    QCOMPARE(p2.dimension(), 3);
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    QhullPoint p = q.firstVertex().point();
    +    QhullPointIterator i2(p);
    +    QCOMPARE(p.dimension(), 3);
    +    i= p;
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    i.toFront();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    // i at front, i2 at end/back, 3 coordinates
    +    QCOMPARE(i.peekNext(), p[0]);
    +    QCOMPARE(i2.peekPrevious(), p[2]);
    +    QCOMPARE(i2.previous(), p[2]);
    +    QCOMPARE(i2.previous(), p[1]);
    +    QCOMPARE(i2.previous(), p[0]);
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), p[0]);
    +    // i.peekNext()= 1.0; // compiler error, i is const
    +    QCOMPARE(i.next(), p[0]);
    +    QCOMPARE(i.peekNext(), p[1]);
    +    QCOMPARE(i.next(), p[1]);
    +    QCOMPARE(i.next(), p[2]);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), p[0]);
    +}//t_qhullpoint_iterator
    +
    +void QhullPoint_test::
    +t_method()
    +{
    +    // advancePoint tested above
    +    RboxPoints rcube("c");
    +    Qhull q(rcube, "");
    +    QhullPoint p = q.firstVertex().point();
    +    double dist= p.distance(q.origin());
    +    QCOMPARE(dist, sqrt(double(2.0+1.0))/2); // half diagonal of unit cube
    +}//t_qhullpoint_iterator
    +
    +void QhullPoint_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullPoint p= q.beginVertex().point();
    +        ostringstream os;
    +        os << "Point:\n";
    +        os << p;
    +        os << "Point w/ print:\n";
    +        os << p.print(" message ");
    +        os << p.printWithIdentifier(" Point with id and a message ");
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count("p"), 2);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullPoint_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullPoints_test.cpp b/xs/src/qhull/src/qhulltest/QhullPoints_test.cpp
    new file mode 100644
    index 0000000000..c2d8347e28
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullPoints_test.cpp
    @@ -0,0 +1,561 @@
    +/****************************************************************************
    +**
    +** Copyright (p) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullPoints_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled header
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullPoints.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +
    +namespace orgQhull {
    +
    +class QhullPoints_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct_q();
    +    void t_construct_qh();
    +    void t_convert();
    +    void t_getset();
    +    void t_element();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_search();
    +    void t_points_iterator();
    +    void t_io();
    +};//QhullPoints_test
    +
    +void
    +add_QhullPoints_test()
    +{
    +    new QhullPoints_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullPoints_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullPoints_test::
    +t_construct_q()
    +{
    +    Qhull q;
    +    QhullPoints ps(q);
    +    QCOMPARE(ps.dimension(), 0);
    +    QVERIFY(ps.isEmpty());
    +    QCOMPARE(ps.count(), 0);
    +    QCOMPARE(ps.size(), 0u);
    +    QCOMPARE(ps.coordinateCount(), 0);
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps2(q);
    +    ps2.defineAs(2, 6, c);
    +    QCOMPARE(ps2.dimension(), 2);
    +    QVERIFY(!ps2.isEmpty());
    +    QCOMPARE(ps2.count(), 3);
    +    QCOMPARE(ps2.size(), 3u);
    +    QCOMPARE(ps2.coordinates(), c);
    +    QhullPoints ps3(q, 2, 6, c);
    +    QCOMPARE(ps3.dimension(), 2);
    +    QVERIFY(!ps3.isEmpty());
    +    QCOMPARE(ps3.coordinates(), ps2.coordinates());
    +    QVERIFY(ps3==ps2);
    +    QVERIFY(ps3!=ps);
    +    QhullPoints ps4= ps3;
    +    QVERIFY(ps4==ps3);
    +    // ps4= ps3; //compiler error
    +    QhullPoints ps5(ps4);
    +    QVERIFY(ps5==ps4);
    +    QVERIFY(!(ps5!=ps4));
    +    coordT c2[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps6(q, 2, 6, c2);
    +    QVERIFY(ps6==ps2);
    +
    +    RboxPoints rbox("c D2");
    +    Qhull q2(rbox, "");
    +    QhullPoints ps8(q2);
    +    QCOMPARE(ps8.dimension(), 2);
    +    QCOMPARE(ps8.count(), 0);
    +    QCOMPARE(ps8.size(), 0u);
    +    QCOMPARE(ps8.coordinateCount(), 0);
    +    coordT c3[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps9(q2, 6, c3);
    +    QCOMPARE(ps9.dimension(), 2);
    +    QCOMPARE(ps9.coordinateCount(), 6);
    +    QCOMPARE(ps9.count(), 3);
    +    QCOMPARE(ps9.coordinates(), c3);
    +    QCOMPARE(ps9, ps2);  // DISTround
    +    c3[1]= 1.0+1e-17;
    +    QCOMPARE(ps9, ps2);  // DISTround
    +    c3[1]= 1.0+1e-15;
    +    QVERIFY(ps9!=ps2);  // DISTround
    +
    +    ps9.defineAs(6, c2);
    +    QCOMPARE(ps9.dimension(), 2);
    +    QCOMPARE(ps9.coordinateCount(), 6);
    +    QCOMPARE(ps9.count(), 3);
    +    QCOMPARE(ps9.coordinates(), c2);
    +}//t_construct_q
    +
    +void QhullPoints_test::
    +t_construct_qh()
    +{
    +    Qhull q;
    +    QhullQh *qh= q.qh();
    +    QhullPoints ps(qh);
    +    QCOMPARE(ps.dimension(), 0);
    +    QVERIFY(ps.isEmpty());
    +    QCOMPARE(ps.count(), 0);
    +    QCOMPARE(ps.size(), 0u);
    +    QCOMPARE(ps.coordinateCount(), 0);
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps2(qh);
    +    ps2.defineAs(2, 6, c);
    +    QCOMPARE(ps2.dimension(), 2);
    +    QVERIFY(!ps2.isEmpty());
    +    QCOMPARE(ps2.count(), 3);
    +    QCOMPARE(ps2.size(), 3u);
    +    QCOMPARE(ps2.coordinates(), c);
    +    QhullPoints ps3(qh, 2, 6, c);
    +    QCOMPARE(ps3.dimension(), 2);
    +    QVERIFY(!ps3.isEmpty());
    +    QCOMPARE(ps3.coordinates(), ps2.coordinates());
    +    QVERIFY(ps3==ps2);
    +    QVERIFY(ps3!=ps);
    +    QhullPoints ps4= ps3;
    +    QVERIFY(ps4==ps3);
    +    // ps4= ps3; //compiler error
    +    QhullPoints ps5(ps4);
    +    QVERIFY(ps5==ps4);
    +    QVERIFY(!(ps5!=ps4));
    +    coordT c2[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps6(qh, 2, 6, c2);
    +    QVERIFY(ps6==ps2);
    +
    +    RboxPoints rbox("c D2");
    +    Qhull q2(rbox, "");
    +    QhullPoints ps8(q2.qh());
    +    QCOMPARE(ps8.dimension(), 2);
    +    QCOMPARE(ps8.count(), 0);
    +    QCOMPARE(ps8.size(), 0u);
    +    QCOMPARE(ps8.coordinateCount(), 0);
    +    coordT c3[]= {10.0, 11.0, 12.0, 13.0, 14.0, 15.0};
    +    QhullPoints ps9(q2.qh(), 6, c3);
    +    QCOMPARE(ps9.dimension(), 2);
    +    QCOMPARE(ps9.coordinateCount(), 6);
    +    QCOMPARE(ps9.count(), 3);
    +    QCOMPARE(ps9.coordinates(), c3);
    +    ps9.defineAs(6, c2);
    +    QCOMPARE(ps9.dimension(), 2);
    +    QCOMPARE(ps9.coordinateCount(), 6);
    +    QCOMPARE(ps9.count(), 3);
    +    QCOMPARE(ps9.coordinates(), c2);
    +}//t_construct_qh
    +
    +void QhullPoints_test::
    +t_convert()
    +{
    +    Qhull q;
    +    //defineAs tested above
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps(q, 3, 6, c);
    +    QCOMPARE(ps.dimension(), 3);
    +    QCOMPARE(ps.size(), 2u);
    +    const coordT *c2= ps.constData();
    +    QCOMPARE(c, c2);
    +    const coordT *c3= ps.data();
    +    QCOMPARE(c, c3);
    +    coordT *c4= ps.data();
    +    QCOMPARE(c, c4);
    +    std::vector vs= ps.toStdVector();
    +    QCOMPARE(vs.size(), 2u);
    +    QhullPoint p= vs[1];
    +    QCOMPARE(p[2], 5.0);
    +    QList qs= ps.toQList();
    +    QCOMPARE(qs.size(), 2);
    +    QhullPoint p2= qs[1];
    +    QCOMPARE(p2[2], 5.0);
    +}//t_convert
    +
    +void QhullPoints_test::
    +t_getset()
    +{
    +    Qhull q;
    +    //See t_construct for coordinates, count, defineAs, dimension, isempty, ==, !=, size
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps(q, 3, 6, c);
    +    QhullPoints ps2(q, 3, 6, c);
    +    QCOMPARE(ps2.dimension(), 3);
    +    QCOMPARE(ps2.coordinates(), c);
    +    QCOMPARE(ps2.count(), 2);
    +    QCOMPARE(ps2.coordinateCount(), 6);
    +    coordT c2[]= {-1.0, -2.0, -3.0, -4.0, -5.0, -6.0};
    +    ps2.defineAs(6, c2);
    +    QCOMPARE(ps2.coordinates(), c2);
    +    QCOMPARE(ps2.count(), 2);
    +    QCOMPARE(ps2.size(), 2u);
    +    QCOMPARE(ps2.dimension(), 3);
    +    QVERIFY(!ps2.isEmpty());
    +    QVERIFY(ps!=ps2);
    +    // ps2= ps; // assignment not available, compiler error
    +    ps2.defineAs(ps);
    +    QVERIFY(ps==ps2);
    +    ps2.setDimension(2);
    +    QCOMPARE(ps2.dimension(), 2);
    +    QCOMPARE(ps2.coordinates(), c);
    +    QVERIFY(!ps2.isEmpty());
    +    QCOMPARE(ps2.count(), 3);
    +    QCOMPARE(ps2.size(), 3u);
    +    QVERIFY(ps!=ps2);
    +    QhullPoints ps3(ps2);
    +    ps3.setDimension(3);
    +    ps3.defineAs(5, c2);
    +    QCOMPARE(ps3.count(), 1);
    +    QCOMPARE(ps3.extraCoordinatesCount(), 2);
    +    QCOMPARE(ps3.extraCoordinates()[0], -4.0);
    +    QVERIFY(ps3.includesCoordinates(ps3.data()));
    +    QVERIFY(ps3.includesCoordinates(ps3.data()+ps3.count()-1));
    +    QVERIFY(!ps3.includesCoordinates(ps3.data()-1));
    +    QVERIFY(!ps3.includesCoordinates(ps3.data()+ps3.coordinateCount()));
    +}//t_getset
    +
    +
    +void QhullPoints_test::
    +t_element()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps(q, 2, 6, c);
    +    QCOMPARE(ps.count(), 3);
    +    QhullPoint p(q, 2, c);
    +    QCOMPARE(ps[0], p);
    +    QCOMPARE(ps.at(1), ps[1]);
    +    QCOMPARE(ps.first(), p);
    +    QCOMPARE(ps.front(), ps.first());
    +    QCOMPARE(ps.last(), ps.at(2));
    +    QCOMPARE(ps.back(), ps.last());
    +    QhullPoints ps2= ps.mid(2);
    +    QCOMPARE(ps2.count(), 1);
    +    QhullPoints ps3= ps.mid(3);
    +    QVERIFY(ps3.isEmpty());
    +    QhullPoints ps4= ps.mid(10);
    +    QVERIFY(ps4.isEmpty());
    +    QhullPoints ps5= ps.mid(-1);
    +    QVERIFY(ps5.isEmpty());
    +    QhullPoints ps6= ps.mid(1, 1);
    +    QCOMPARE(ps6.count(), 1);
    +    QCOMPARE(ps6[0], ps[1]);
    +    QhullPoints ps7= ps.mid(1, 10);
    +    QCOMPARE(ps7.count(), 2);
    +    QCOMPARE(ps7[1], ps[2]);
    +    QhullPoint p8(q);
    +    QCOMPARE(ps.value(2), ps[2]);
    +    QCOMPARE(ps.value(-1), p8);
    +    QCOMPARE(ps.value(3), p8);
    +    QCOMPARE(ps.value(3, p), p);
    +    QVERIFY(ps.value(1, p)!=p);
    +    foreach(QhullPoint p9, ps){  // Qt only
    +        QCOMPARE(p9.dimension(), 2);
    +        QVERIFY(p9[0]==0.0 || p9[0]==2.0 || p9[0]==4.0);
    +    }
    +}//t_element
    +
    +void QhullPoints_test::
    +t_iterator()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0};
    +    QhullPoints ps(q, 1, 3, c);
    +    QCOMPARE(ps.dimension(), 1);
    +    QhullPoints::Iterator i(ps);
    +    QhullPoints::iterator i2= ps.begin();
    +    QVERIFY(i==i2);
    +    QVERIFY(i>=i2);
    +    QVERIFY(i<=i2);
    +    i= ps.begin();
    +    QVERIFY(i==i2);
    +    i2= ps.end();
    +    QVERIFY(i!=i2);
    +    QhullPoint p(i); // QhullPoint is the base class for QhullPoints::iterator
    +    QCOMPARE(p.dimension(), ps.dimension());
    +    QCOMPARE(p.coordinates(), ps.coordinates());
    +    i2--;
    +    QhullPoint p2= *i2;
    +    QCOMPARE(p[0], 0.0);
    +    QCOMPARE(p2[0], 2.0);
    +    QhullPoints::Iterator i5(i2);
    +    QCOMPARE(*i2, *i5);
    +    coordT c3[]= {0.0, -1.0, -2.0};
    +    QhullPoints::Iterator i3(q, 1, c3);
    +    QVERIFY(i!=i3);
    +    QCOMPARE(*i, *i3);
    +
    +    (i3= i)++;
    +    QCOMPARE((*i3)[0], 1.0);
    +    QCOMPARE(i3->dimension(), 1);
    +    QCOMPARE(i3[0][0], 1.0);
    +    QCOMPARE(i3[0], ps[1]);
    +
    +    QVERIFY(i==i);
    +    QVERIFY(i!=i2);
    +    QVERIFY(ii);
    +    QVERIFY(i2>=i);
    +
    +    QhullPoints::ConstIterator i4(q, 1, c);
    +    QVERIFY(i==i4); // iterator COMP const_iterator
    +    QVERIFY(i<=i4);
    +    QVERIFY(i>=i4);
    +    QVERIFY(i4==i); // const_iterator COMP iterator
    +    QVERIFY(i4<=i);
    +    QVERIFY(i4>=i);
    +    QVERIFY(i>=i4);
    +    QVERIFY(i4<=i);
    +    QVERIFY(i2!=i4);
    +    QVERIFY(i2>i4);
    +    QVERIFY(i2>=i4);
    +    QVERIFY(i4!=i2);
    +    QVERIFY(i4i);
    +    QVERIFY(i4>=i);
    +
    +    i= ps.begin();
    +    i2= ps.begin();
    +    QCOMPARE(i, i2++);
    +    QCOMPARE(*i2, ps[1]);
    +    QCOMPARE(++i, i2);
    +    QCOMPARE(i, i2--);
    +    QCOMPARE(i2, ps.begin());
    +    QCOMPARE(--i, i2);
    +    QCOMPARE(i2+=3, ps.end());
    +    QCOMPARE(i2-=3, ps.begin());
    +    QCOMPARE(i2+0, ps.begin());
    +    QCOMPARE(i2+3, ps.end());
    +    i2 += 3;
    +    i= i2-0;
    +    QCOMPARE(i, i2);
    +    i= i2-3;
    +    QCOMPARE(i, ps.begin());
    +    QCOMPARE(i2-i, 3);
    +
    +    //ps.begin end tested above
    +
    +    // QhullPoints is const-only
    +}//t_iterator
    +
    +void QhullPoints_test::
    +t_const_iterator()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0};
    +    const QhullPoints ps(q, 1, 3, c);
    +    QhullPoints::ConstIterator i(ps);
    +    QhullPoints::const_iterator i2= ps.begin();
    +    QVERIFY(i==i2);
    +    QVERIFY(i>=i2);
    +    QVERIFY(i<=i2);
    +    i= ps.begin();
    +    QVERIFY(i==i2);
    +    i2= ps.end();
    +    QVERIFY(i!=i2);
    +    QhullPoint p(i);
    +    QCOMPARE(p.dimension(), ps.dimension());
    +    QCOMPARE(p.coordinates(), ps.coordinates());
    +    i2--;
    +    QhullPoint p2= *i2;
    +    QCOMPARE(p[0], 0.0);
    +    QCOMPARE(p2[0], 2.0);
    +    QhullPoints::ConstIterator i5(i2);
    +    QCOMPARE(*i2, *i5);
    +    coordT c3[]= {0.0, -1.0, -2.0};
    +    QhullPoints::ConstIterator i3(q, 1, c3);
    +    QVERIFY(i!=i3);
    +    QCOMPARE(*i, *i3);
    +
    +    (i3= i)++;
    +    QCOMPARE((*i3)[0], 1.0);
    +    QCOMPARE(i3->dimension(), 1);
    +    QCOMPARE(i3[0][0], 1.0);
    +    QCOMPARE(i3[0][0], 1.0);
    +    QCOMPARE(i3[0], ps[1]);
    +
    +    QVERIFY(i==i);
    +    QVERIFY(i!=i2);
    +    QVERIFY(ii);
    +    QVERIFY(i2>=i);
    +
    +    // See t_iterator for const_iterator COMP iterator
    +
    +    i= ps.begin();
    +    i2= ps.constBegin();
    +    QCOMPARE(i, i2++);
    +    QCOMPARE(*i2, ps[1]);
    +    QCOMPARE(++i, i2);
    +    QCOMPARE(i, i2--);
    +    QCOMPARE(i2, ps.constBegin());
    +    QCOMPARE(--i, i2);
    +    QCOMPARE(i2+=3, ps.constEnd());
    +    QCOMPARE(i2-=3, ps.constBegin());
    +    QCOMPARE(i2+0, ps.constBegin());
    +    QCOMPARE(i2+3, ps.constEnd());
    +    i2 += 3;
    +    i= i2-0;
    +    QCOMPARE(i, i2);
    +    i= i2-3;
    +    QCOMPARE(i, ps.constBegin());
    +    QCOMPARE(i2-i, 3);
    +
    +    // QhullPoints is const-only
    +}//t_const_iterator
    +
    +
    +void QhullPoints_test::
    +t_search()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 0, 1};
    +    QhullPoints ps(q, 2, 8, c); //2-d array of 4 points
    +    QhullPoint p= ps.first();
    +    QhullPoint p2= ps.last();
    +    QVERIFY(ps.contains(p));
    +    QVERIFY(ps.contains(p2));
    +    QVERIFY(p==p2);
    +    QhullPoint p5= ps[2];
    +    QVERIFY(p!=p5);
    +    QVERIFY(ps.contains(p5));
    +    coordT c2[]= {0.0, 1.0, 2.0, 3.0};
    +    QhullPoint p3(q, 2, c2); //2-d point
    +    QVERIFY(ps.contains(p3));
    +    QhullPoint p4(q, 3, c2); //3-d point
    +    QVERIFY(!ps.contains(p4));
    +    p4.defineAs(2, c); //2-d point
    +    QVERIFY(ps.contains(p4));
    +    p4.defineAs(2, c+1); //2-d point
    +    QVERIFY(!ps.contains(p4));
    +    QhullPoint p6(q, 2, c2+2); //2-d point
    +    QCOMPARE(ps.count(p), 2);
    +    QCOMPARE(ps.count(p2), 2);
    +    QCOMPARE(ps.count(p3), 2);
    +    QCOMPARE(ps.count(p4), 0);
    +    QCOMPARE(ps.count(p6), 1);
    +    QCOMPARE(ps.indexOf(&ps[0][0]), 0);
    +    //QCOMPARE(ps.indexOf(ps.end()), -1); //ps.end() is a QhullPoint which may match
    +    QCOMPARE(ps.indexOf(0), -1);
    +    QCOMPARE(ps.indexOf(&ps[3][0]), 3);
    +    QCOMPARE(ps.indexOf(&ps[3][1], QhullError::NOthrow), 3);
    +    QCOMPARE(ps.indexOf(ps.data()+ps.coordinateCount(), QhullError::NOthrow), -1);
    +    QCOMPARE(ps.indexOf(p), 0);
    +    QCOMPARE(ps.indexOf(p2), 0);
    +    QCOMPARE(ps.indexOf(p3), 0);
    +    QCOMPARE(ps.indexOf(p4), -1);
    +    QCOMPARE(ps.indexOf(p5), 2);
    +    QCOMPARE(ps.indexOf(p6), 1);
    +    QCOMPARE(ps.lastIndexOf(p), 3);
    +    QCOMPARE(ps.lastIndexOf(p4), -1);
    +    QCOMPARE(ps.lastIndexOf(p6), 1);
    +    QhullPoints ps3(q);
    +    QCOMPARE(ps3.indexOf(ps3.data()), -1);
    +    QCOMPARE(ps3.indexOf(ps3.data()+1, QhullError::NOthrow), -1);
    +    QCOMPARE(ps3.indexOf(p), -1);
    +    QCOMPARE(ps3.lastIndexOf(p), -1);
    +    QhullPoints ps4(q, 2, 0, c);
    +    QCOMPARE(ps4.indexOf(p), -1);
    +    QCOMPARE(ps4.lastIndexOf(p), -1);
    +}//t_search
    +
    +void QhullPoints_test::
    +t_points_iterator()
    +{
    +    Qhull q;
    +    coordT c2[]= {0.0};
    +    QhullPoints ps2(q, 0, 0, c2); // 0-dimensional
    +    QhullPointsIterator i2= ps2;
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    i2.toBack();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps(q, 3, 6, c); // 3-dimensional
    +    QhullPointsIterator i(ps);
    +    i2= ps;
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    i.toFront();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    QhullPoint p= ps[0];
    +    QhullPoint p2(ps[0]);
    +    QCOMPARE(p, p2);
    +    QVERIFY(p==p2);
    +    QhullPoint p3(ps[1]);
    + // p2[0]= 0.0;
    +    QVERIFY(p==p2);
    +    QCOMPARE(i2.peekPrevious(), p3);
    +    QCOMPARE(i2.previous(), p3);
    +    QCOMPARE(i2.previous(), p);
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), p);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), p);
    +    QCOMPARE(i.peekNext(), p3);
    +    QCOMPARE(i.next(), p3);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), p);
    +}//t_points_iterator
    +
    +void QhullPoints_test::
    +t_io()
    +{
    +    Qhull q;
    +    QhullPoints ps(q);
    +    ostringstream os;
    +    os << "Empty QhullPoints\n" << ps << endl;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps2(q, 3, 6, c); // 3-dimensional explicit
    +    os << "QhullPoints from c[]\n" << ps2 << endl;
    +
    +    RboxPoints rcube("c");
    +    Qhull q2(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullPoints ps3= q2.points();
    +    os << "QhullPoints\n" << ps3;
    +    os << ps3.print("message\n");
    +    os << ps3.printWithIdentifier("w/ identifiers\n");
    +    cout << os.str();
    +    QString s= QString::fromStdString(os.str());
    +    QCOMPARE(s.count("p"), 8+1);
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullPoints_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullRidge_test.cpp b/xs/src/qhull/src/qhulltest/QhullRidge_test.cpp
    new file mode 100644
    index 0000000000..420a7f06d3
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullRidge_test.cpp
    @@ -0,0 +1,159 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullRidge_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullRidge_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_getSet();
    +    void t_foreach();
    +    void t_io();
    +};//QhullRidge_test
    +
    +void
    +add_QhullRidge_test()
    +{
    +    new QhullRidge_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullRidge_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullRidge_test::
    +t_construct()
    +{
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // triangulation of rotated unit cube
    +    QhullRidge r(q);
    +    QVERIFY(!r.isValid());
    +    QCOMPARE(r.dimension(),2);
    +    QhullFacet f(q.firstFacet());
    +    QhullRidgeSet rs(f.ridges());
    +    QVERIFY(!rs.isEmpty()); // Simplicial facets do not have ridges()
    +    QhullRidge r2(rs.first());
    +    QCOMPARE(r2.dimension(), 2); // One dimension lower than the facet
    +    r= r2;
    +    QVERIFY(r.isValid());
    +    QCOMPARE(r.dimension(), 2);
    +    QhullRidge r3(q, r2.getRidgeT());
    +    QCOMPARE(r,r3);
    +    QhullRidge r4(q, r2.getBaseT());
    +    QCOMPARE(r,r4);
    +    QhullRidge r5= r2; // copy constructor
    +    QVERIFY(r5==r2);
    +    QVERIFY(r5==r);
    +}//t_construct
    +
    +void QhullRidge_test::
    +t_getSet()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q.facetCount(), 6);
    +        QCOMPARE(q.vertexCount(), 8);
    +        QhullFacet f(q.firstFacet());
    +        QhullRidgeSet rs= f.ridges();
    +        QhullRidgeSetIterator i(rs);
    +        while(i.hasNext()){
    +            const QhullRidge r= i.next();
    +            cout << r.id() << endl;
    +            QVERIFY(r.bottomFacet()!=r.topFacet());
    +            QCOMPARE(r.dimension(), 2); // Ridge one-dimension less than facet
    +            QVERIFY(r.id()>=0 && r.id()<9*27);
    +            QVERIFY(r.isValid());
    +            QVERIFY(r==r);
    +            QVERIFY(r==i.peekPrevious());
    +            QCOMPARE(r.otherFacet(r.bottomFacet()),r.topFacet());
    +            QCOMPARE(r.otherFacet(r.topFacet()),r.bottomFacet());
    +        }
    +    }
    +}//t_getSet
    +
    +void QhullRidge_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");  // cube
    +    {
    +        Qhull q(rcube, "QR0"); // rotated cube
    +        QhullFacet f(q.firstFacet());
    +        foreach (const QhullRidge &r, f.ridges()){  // Qt only
    +            QhullVertexSet vs= r.vertices();
    +            QCOMPARE(vs.count(), 2);
    +            foreach (const QhullVertex &v, vs){  // Qt only
    +                QVERIFY(f.vertices().contains(v));
    +            }
    +        }
    +        QhullRidgeSet rs= f.ridges();
    +        QhullRidge r= rs.first();
    +        QhullRidge r2= r;
    +        QList vs;
    +        int count= 0;
    +        while(!count || r2!=r){
    +            ++count;
    +            QhullVertex v(q);
    +            QVERIFY2(r2.hasNextRidge3d(f),"A cube should only have non-simplicial facets.");
    +            QhullRidge r3= r2.nextRidge3d(f, &v);
    +            QVERIFY(!vs.contains(v));
    +            vs << v;
    +            r2= r2.nextRidge3d(f);
    +            QCOMPARE(r3, r2);
    +        }
    +        QCOMPARE(vs.count(), rs.count());
    +        QCOMPARE(count, rs.count());
    +    }
    +}//t_foreach
    +
    +void QhullRidge_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullFacet f(q.firstFacet());
    +        QhullRidgeSet rs= f.ridges();
    +        QhullRidge r= rs.first();
    +        ostringstream os;
    +        os << "Ridges\n" << rs << "Ridge\n" << r;
    +        os << r.print("\nRidge with message");
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count(" r"), 6);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullRidge_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullSet_test.cpp b/xs/src/qhull/src/qhulltest/QhullSet_test.cpp
    new file mode 100644
    index 0000000000..87fcf4acf2
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullSet_test.cpp
    @@ -0,0 +1,434 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullSet_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +
    +namespace orgQhull {
    +
    +class QhullSet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_qhullsetbase();
    +    void t_convert();
    +    void t_element();
    +    void t_search();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_qhullset_iterator();
    +    void t_io();
    +};//QhullSet_test
    +
    +void
    +add_QhullSet_test()
    +{
    +    new QhullSet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullSet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +// Test QhullFacetSet and QhullSet.
    +// Use QhullRidgeSet to test methods overloaded by QhullFacetSet
    +
    +void QhullSet_test::
    +t_qhullsetbase()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // triangulation of rotated unit cube
    +        // Fake an empty set.  Default constructor not defined.  No memory allocation.
    +        QhullFacet f4 = q.beginFacet();
    +        QhullFacetSet fs = f4.neighborFacets();
    +        fs.defineAs(q.qh()->other_points); // Force an empty set
    +        QVERIFY(fs.isEmpty());
    +        QCOMPARE(fs.count(), 0);
    +        QCOMPARE(fs.size(), 0u);
    +        QCOMPARE(fs.begin(), fs.end()); // beginPointer(), endPointer()
    +        QVERIFY(QhullSetBase::isEmpty(fs.getSetT()));
    +
    +        QhullRidgeSet rs = f4.ridges();
    +        QVERIFY(!rs.isEmpty());
    +        QCOMPARE(rs.count(), 4);
    +        QCOMPARE(rs.size(), 4u);
    +        QVERIFY(rs.begin()!=rs.end());
    +        QVERIFY(!QhullSetBase::isEmpty(rs.getSetT()));
    +        QhullRidgeSet rs2= rs; // copy constructor
    +        // rs= rs2; // disabled.  Would not copy ridges
    +        QCOMPARE(rs2, rs);
    +
    +        QCOMPARE(q.facetCount(), 6);
    +        QhullFacet f = q.beginFacet();
    +        QhullFacetSet fs2 = f.neighborFacets();
    +        QCOMPARE(fs2.count(), 4);
    +        QCOMPARE(fs2.size(), 4u);
    +        QVERIFY(!fs2.isEmpty());
    +        QVERIFY(!QhullSetBase::isEmpty(fs2.getSetT()));
    +        QVERIFY(fs!=fs2);
    +        setT *s= fs2.getSetT();
    +        fs.defineAs(s);
    +        QVERIFY(fs==fs2);
    +        QCOMPARE(fs[1], fs2[1]); // elementPointer
    +        QhullFacetSet fs3(fs2);
    +        QVERIFY(fs3==fs);
    +        // fs= fs2; // disabled.  Would not copy facets
    +        QhullFacetSet fs4= fs2; // copy constructor
    +        QVERIFY(fs4==fs2);
    +    }
    +}//t_qhullsetbase
    +
    +// constructors tested by t_qhullsetbase
    +
    +void QhullSet_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullFacet f= q.firstFacet();
    +        f= f.next();
    +        QhullRidgeSet rs= f.ridges();
    +        QCOMPARE(rs.count(),4);
    +        std::vector rv= rs.toStdVector();
    +        QCOMPARE(rv.size(), 4u);
    +        QList rv2= rs.toQList();
    +        QCOMPARE(rv2.size(), 4);
    +        std::vector::iterator i= rv.begin();
    +        foreach(QhullRidge r, rv2){  // Qt only
    +            QhullRidge r2= *i++;
    +            QCOMPARE(r, r2);
    +        }
    +
    +        Qhull q2(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q2.facetCount(), 12);
    +        QhullFacet f2 = q2.beginFacet();
    +        QhullFacetSet fs = f2.neighborFacets();
    +        QCOMPARE(fs.size(), 3U);
    +        std::vector vs= fs.toStdVector();
    +        QCOMPARE(vs.size(), fs.size());
    +        for(int k= fs.count(); k--; ){
    +            QCOMPARE(vs[k], fs[k]);
    +        }
    +        QList qv= fs.toQList();
    +        QCOMPARE(qv.count(), fs.count());
    +        for(int k= fs.count(); k--; ){
    +            QCOMPARE(qv[k], fs[k]);
    +        }
    +    }
    +}//t_convert
    +
    +//ReadOnly (count, isEmpty) tested by t_convert
    +//  operator== tested by t_search
    +
    +void QhullSet_test::
    +t_element()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacet f = q.beginFacet();
    +    QhullFacetSet fs = f.neighborFacets();
    +
    +    QCOMPARE(fs.at(1), fs[1]);
    +    QCOMPARE(fs.first(), fs[0]);
    +    QCOMPARE(fs.front(), fs.first());
    +    QCOMPARE(fs.last(), fs.at(3));
    +    QCOMPARE(fs.back(), fs.last());
    +    facetT **d = fs.data();
    +    facetT * const *d2= fs.data();
    +    facetT * const *d3= fs.constData();
    +    QVERIFY(d==d2);
    +    QVERIFY(d2==d3);
    +    QCOMPARE(QhullFacet(q, *d), fs.first());
    +    QhullFacetSet::iterator i(q.qh(), d+4);
    +    QCOMPARE(i, fs.end());
    +    QCOMPARE(d[4], static_cast(0));
    +    QhullFacet f4(q, d[4]);
    +    QVERIFY(!f4.isValid());
    +    QCOMPARE(fs.second(), fs[1]);
    +    const QhullFacet f2= fs.second();
    +    QVERIFY(f2==fs[1]);
    +    const QhullFacet f3= fs[1];
    +    QCOMPARE(f2, f3);
    +
    +    QCOMPARE(fs.value(2), fs[2]);
    +    QCOMPARE(fs.value(-1), QhullFacet());
    +    QCOMPARE(fs.value(10), QhullFacet());
    +    QCOMPARE(fs.value(2, f), fs[2]);
    +    QCOMPARE(fs.value(4, f), f);
    +    // mid() not available (read-only)
    +}//t_element
    +
    +void QhullSet_test::
    +t_search()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacet f = q.beginFacet();
    +    QhullFacetSet fs = f.neighborFacets();
    +    QhullFacet f2= *fs.begin();
    +    QhullFacet f3= fs.last();
    +    QVERIFY(fs.contains(f2));
    +    QVERIFY(fs.contains(f3));
    +    QVERIFY(!fs.contains(f));
    +
    +    QhullFacetSet fs2= f2.neighborFacets();
    +    QVERIFY(fs==fs);
    +    QVERIFY(fs!=fs2);
    +    QCOMPARE(fs.count(f2), 1);
    +    QCOMPARE(fs.count(f3), 1);
    +    QCOMPARE(fs.count(f), 0);
    +    QCOMPARE(fs.indexOf(f2), 0);
    +    QCOMPARE(fs.indexOf(f3), 3);
    +    QCOMPARE(fs.indexOf(f), -1);
    +    QCOMPARE(fs.lastIndexOf(f2), 0);
    +    QCOMPARE(fs.lastIndexOf(f3), 3);
    +    QCOMPARE(fs.lastIndexOf(f), -1);
    +}//t_search
    +
    +void QhullSet_test::
    +t_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullFacet f = q.beginFacet();
    +        QhullFacetSet fs = f.neighborFacets();
    +        QhullFacetSet::Iterator i= fs.begin();
    +        QhullFacetSet::iterator i2= fs.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= fs.begin();
    +        QVERIFY(i==i2);
    +        i2= fs.end();
    +        QVERIFY(i!=i2);
    +        QhullFacet f3(*i);
    +        i2--;
    +        QhullFacet f2= *i2;
    +        QCOMPARE(f3.id(), fs[0].id());
    +        QCOMPARE(f2.id(), fs[3].id());
    +        QhullFacetSet::Iterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3).id(), fs[1].id());
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        QhullFacetSet::ConstIterator i4= fs.begin();
    +        QVERIFY(i==i4); // iterator COMP const_iterator
    +        QVERIFY(i<=i4);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4==i); // const_iterator COMP iterator
    +        QVERIFY(i4<=i);
    +        QVERIFY(i4>=i);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4<=i);
    +        QVERIFY(i2!=i4);
    +        QVERIFY(i2>i4);
    +        QVERIFY(i2>=i4);
    +        QVERIFY(i4!=i2);
    +        QVERIFY(i4i);
    +        QVERIFY(i4>=i);
    +
    +        i= fs.begin();
    +        i2= fs.begin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, fs[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, fs.begin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2 += 4, fs.end());
    +        QCOMPARE(i2 -= 4, fs.begin());
    +        QCOMPARE(i2+0, fs.begin());
    +        QCOMPARE(i2+4, fs.end());
    +        i2 += 4;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-4;
    +        QCOMPARE(i, fs.begin());
    +        QCOMPARE(i2-i, 4);
    +
    +        //fs.begin end tested above
    +
    +        // QhullFacetSet is const-only
    +    }
    +}//t_iterator
    +
    +void QhullSet_test::
    +t_const_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullFacet f = q.beginFacet();
    +        QhullFacetSet fs = f.neighborFacets();
    +        QhullFacetSet::ConstIterator i= fs.begin();
    +        QhullFacetSet::const_iterator i2= fs.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= fs.begin();
    +        QVERIFY(i==i2);
    +        i2= fs.end();
    +        QVERIFY(i!=i2);
    +        QhullFacet f3(*i);
    +        i2--;
    +        QhullFacet f2= *i2;
    +        QCOMPARE(f3.id(), fs[0].id());
    +        QCOMPARE(f2.id(), fs[3].id());
    +        QhullFacetSet::ConstIterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3).id(), fs[1].id());
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        // See t_iterator for const_iterator COMP iterator
    +
    +        i= fs.begin();
    +        i2= fs.constBegin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, fs[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, fs.constBegin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2+=4, fs.constEnd());
    +        QCOMPARE(i2-=4, fs.constBegin());
    +        QCOMPARE(i2+0, fs.constBegin());
    +        QCOMPARE(i2+4, fs.constEnd());
    +        i2 += 4;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-4;
    +        QCOMPARE(i, fs.constBegin());
    +        QCOMPARE(i2-i, 4);
    +
    +        // QhullFacetSet is const-only
    +    }
    +}//t_const_iterator
    +
    +void QhullSet_test::
    +t_qhullset_iterator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    // Fake an empty set.  Default constructor not defined.  No memory allocation.
    +    QhullFacet f = q.beginFacet();
    +    QhullFacetSet fs = f.neighborFacets();
    +    fs.defineAs(q.qh()->other_points);
    +    QhullFacetSetIterator i(fs);
    +    QCOMPARE(fs.count(), 0);
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    QhullFacet f2 = q.beginFacet();
    +    QhullFacetSet fs2 = f2.neighborFacets();
    +    QhullFacetSetIterator i2(fs2);
    +    QCOMPARE(fs2.count(), 4);
    +    i= fs2;
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    i.toFront();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    // i at front, i2 at end/back, 4 neighbors
    +    QhullFacetSet fs3 = f2.neighborFacets(); // same as fs2
    +    QhullFacet f3(fs2[0]);
    +    QhullFacet f4= fs3[0];
    +    QCOMPARE(f3, f4);
    +    QVERIFY(f3==f4);
    +    QhullFacet f5(fs3[1]);
    +    QVERIFY(f4!=f5);
    +    QhullFacet f6(fs3[2]);
    +    QhullFacet f7(fs3[3]);
    +    QCOMPARE(i2.peekPrevious(), f7);
    +    QCOMPARE(i2.previous(), f7);
    +    QCOMPARE(i2.previous(), f6);
    +    QCOMPARE(i2.previous(), f5);
    +    QCOMPARE(i2.previous(), f4);
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), f4);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), f4);
    +    QCOMPARE(i.peekNext(), f5);
    +    QCOMPARE(i.next(), f5);
    +    QCOMPARE(i.next(), f6);
    +    QCOMPARE(i.next(), f7);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), f4);
    +}//t_qhullset_iterator
    +
    +void QhullSet_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    // Fake an empty set.  Default constructor not defined.  No memory allocation.
    +    QhullFacet f= q.beginFacet();
    +    QhullFacetSet fs= f.neighborFacets();
    +    fs.defineAs(q.qh()->other_points);
    +    cout << "INFO:     empty set" << fs << std::endl;
    +    QhullFacet f2= q.beginFacet();
    +    QhullFacetSet fs2= f2.neighborFacets();
    +    cout << "INFO:   Neighboring facets\n";
    +    cout << fs2 << std::endl;
    +
    +    QhullRidgeSet rs= f.ridges();
    +    cout << "INFO:   Ridges for a facet\n";
    +    cout << rs << std::endl;
    +}//t_io
    +
    +}//namespace orgQhull
    +
    +#include "moc/QhullSet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullVertexSet_test.cpp b/xs/src/qhull/src/qhulltest/QhullVertexSet_test.cpp
    new file mode 100644
    index 0000000000..41caacd290
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullVertexSet_test.cpp
    @@ -0,0 +1,152 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullVertexSet_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullVertexSet.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullVertexSet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_readonly();
    +    void t_foreach();
    +    void t_io();
    +};//QhullVertexSet_test
    +
    +void
    +add_QhullVertexSet_test()
    +{
    +    new QhullVertexSet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullVertexSet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullVertexSet_test::
    +t_construct()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    cout << "INFO   : Cube rotated by QR" << q.rotateRandom() << std::endl;
    +    QhullFacet f= q.firstFacet();
    +    QhullVertexSet vs= f.vertices();
    +    QVERIFY(!vs.isEmpty());
    +    QCOMPARE(vs.count(),4);
    +    QhullVertexSet vs4= vs; // copy constructor
    +    QVERIFY(vs4==vs);
    +    QhullVertexSet vs3(q, q.qh()->del_vertices);
    +    QVERIFY(vs3.isEmpty());
    +}//t_construct
    +
    +void QhullVertexSet_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0 QV2");  // rotated unit cube with "good" facets adjacent to point 0
    +    cout << "INFO   : Cube rotated by QR" << q.rotateRandom() << std::endl;
    +    QhullFacet f= q.firstFacet();
    +    QhullVertexSet vs2= f.vertices();
    +    QCOMPARE(vs2.count(),4);
    +    std::vector fv= vs2.toStdVector();
    +    QCOMPARE(fv.size(), 4u);
    +    QList fv2= vs2.toQList();
    +    QCOMPARE(fv2.size(), 4);
    +    std::vector fv3= vs2.toStdVector();
    +    QCOMPARE(fv3.size(), 4u);
    +    QList fv4= vs2.toQList();
    +    QCOMPARE(fv4.size(), 4);
    +}//t_convert
    +
    +//! Spot check properties and read-only.  See QhullSet_test
    +void QhullVertexSet_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QV0");  // good facets are adjacent to point 0
    +    QhullVertexSet vs= q.firstFacet().vertices();
    +    QCOMPARE(vs.count(), 4);
    +    QCOMPARE(vs.count(), 4);
    +    QhullVertex v= vs.first();
    +    QhullVertex v2= vs.last();
    +    QVERIFY(vs.contains(v));
    +    QVERIFY(vs.contains(v2));
    +}//t_readonly
    +
    +void QhullVertexSet_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");
    +    // Spot check predicates and accessors.  See QhullLinkedList_test
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    cout << "INFO   : Cube rotated by QR" << q.rotateRandom() << std::endl;
    +    QhullVertexSet vs= q.firstFacet().vertices();
    +    QVERIFY(vs.contains(vs.first()));
    +    QVERIFY(vs.contains(vs.last()));
    +    QCOMPARE(vs.first(), *vs.begin());
    +    QCOMPARE(*(vs.end()-1), vs.last());
    +}//t_foreach
    +
    +void QhullVertexSet_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0 QV0");   // good facets are adjacent to point 0
    +        cout << "INFO   : Cube rotated by QR" << q.rotateRandom() << std::endl;
    +        QhullVertexSet vs= q.firstFacet().vertices();
    +        ostringstream os;
    +        os << vs.print("Vertices of first facet with point 0");
    +        os << vs.printIdentifiers("\nVertex identifiers: ");
    +        cout<< os.str();
    +        QString vertices= QString::fromStdString(os.str());
    +        QCOMPARE(vertices.count(QRegExp(" v[0-9]")), 4);
    +    }
    +}//t_io
    +
    +#ifdef QHULL_USES_QT
    +QList QhullVertexSet::
    +toQList() const
    +{
    +    QhullSetIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        QhullVertex v= i.next();
    +        vs.append(v);
    +    }
    +    return vs;
    +}//toQList
    +#endif //QHULL_USES_QT
    +
    +}//orgQhull
    +
    +#include "moc/QhullVertexSet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullVertex_test.cpp b/xs/src/qhull/src/qhulltest/QhullVertex_test.cpp
    new file mode 100644
    index 0000000000..fb6ec9640a
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullVertex_test.cpp
    @@ -0,0 +1,184 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullVertex_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Coordinates.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullVertexSet.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullVertex_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_constructConvert();
    +    void t_getSet();
    +    void t_foreach();
    +    void t_io();
    +};//QhullVertex_test
    +
    +void
    +add_QhullVertex_test()
    +{
    +    new QhullVertex_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullVertex_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullVertex_test::
    +t_constructConvert()
    +{
    +    QhullVertex v6;
    +    QVERIFY(!v6.isValid());
    +    QCOMPARE(v6.dimension(),0);
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullVertex v(q);
    +    QVERIFY(!v.isValid());
    +    QCOMPARE(v.dimension(),3);
    +    QhullVertex v2(q.beginVertex());
    +    QCOMPARE(v2.dimension(),3);
    +    v= v2;  // copy assignment
    +    QVERIFY(v.isValid());
    +    QCOMPARE(v.dimension(),3);
    +    QhullVertex v5= v2; // copy constructor
    +    QVERIFY(v5==v2);
    +    QVERIFY(v5==v);
    +    QhullVertex v3(q, v2.getVertexT());
    +    QCOMPARE(v,v3);
    +    QhullVertex v4(q, v2.getBaseT());
    +    QCOMPARE(v,v4);
    +}//t_constructConvert
    +
    +void QhullVertex_test::
    +t_getSet()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q.facetCount(), 12);
    +        QCOMPARE(q.vertexCount(), 8);
    +
    +        // Also spot-test QhullVertexList.  See QhullLinkedList_test.cpp
    +        QhullVertexList vs= q.vertexList();
    +        QhullVertexListIterator i(vs);
    +        while(i.hasNext()){
    +            const QhullVertex v= i.next();
    +            cout << v.id() << endl;
    +            QCOMPARE(v.dimension(),3);
    +            QVERIFY(v.id()>=0 && v.id()<9);
    +            QVERIFY(v.isValid());
    +            if(i.hasNext()){
    +                QCOMPARE(v.next(), i.peekNext());
    +                QVERIFY(v.next()!=v);
    +                QVERIFY(v.next().previous()==v);
    +            }
    +            QVERIFY(i.hasPrevious());
    +            QCOMPARE(v, i.peekPrevious());
    +        }
    +
    +        // test point()
    +        foreach (QhullVertex v, q.vertexList()){  // Qt only
    +            QhullPoint p= v.point();
    +            int j= p.id();
    +            cout << "Point " << j << ":\n" << p << endl;
    +            QVERIFY(j>=0 && j<8);
    +        }
    +    }
    +}//t_getSet
    +
    +void QhullVertex_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c W0 300");  // 300 points on surface of cube
    +    {
    +        Qhull q(rcube, "QR0 Qc"); // keep coplanars, thick facet, and rotate the cube
    +        foreach (QhullVertex v, q.vertexList()){  // Qt only
    +            QhullFacetSet fs= v.neighborFacets();
    +            QCOMPARE(fs.count(), 3);
    +            foreach (QhullFacet f, fs){  // Qt only
    +                QVERIFY(f.vertices().contains(v));
    +            }
    +        }
    +    }
    +}//t_foreach
    +
    +void QhullVertex_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullVertex v= q.beginVertex();
    +        ostringstream os;
    +        os << "Vertex and vertices:\n";
    +        os << v;
    +        QhullVertexSet vs= q.firstFacet().vertices();
    +        os << vs;
    +        os << "\nVertex and vertices with message:\n";
    +        os << v.print("Vertex");
    +        os << vs.print("\nVertices:");
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count("(v"), 10);
    +        QCOMPARE(s.count(": f"), 2);
    +    }
    +    RboxPoints r10("10 D3");  // Without QhullVertex::facetNeighbors
    +    {
    +        Qhull q(r10, "");
    +        QhullVertex v= q.beginVertex();
    +        ostringstream os;
    +        os << "\nTry again with simplicial facets.  No neighboring facets listed for vertices.\n";
    +        os << "Vertex and vertices:\n";
    +        os << v;
    +        q.defineVertexNeighborFacets();
    +        os << "This time with neighborFacets() defined for all vertices:\n";
    +        os << v;
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count(": f"), 1);
    +
    +        Qhull q2(r10, "v"); // Voronoi diagram
    +        QhullVertex v2= q2.beginVertex();
    +        ostringstream os2;
    +        os2 << "\nTry again with Voronoi diagram of simplicial facets.  Neighboring facets automatically defined for vertices.\n";
    +        os2 << "Vertex and vertices:\n";
    +        os2 << v2;
    +        cout << os2.str();
    +        QString s2= QString::fromStdString(os2.str());
    +        QCOMPARE(s2.count(": f"), 1);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullVertex_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/Qhull_test.cpp b/xs/src/qhull/src/qhulltest/Qhull_test.cpp
    new file mode 100644
    index 0000000000..cc3918a050
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/Qhull_test.cpp
    @@ -0,0 +1,360 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/Qhull_test.cpp#4 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +//! Test C++ interface to Qhull
    +//! See eg/q_test for tests of Qhull commands
    +class Qhull_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_attribute();
    +    void t_message();
    +    void t_getSet();
    +    void t_getQh();
    +    void t_getValue();
    +    void t_foreach();
    +    void t_modify();
    +};//Qhull_test
    +
    +void
    +add_Qhull_test()
    +{
    +    new Qhull_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void Qhull_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void Qhull_test::
    +t_construct()
    +{
    +    {
    +        Qhull q;
    +        QCOMPARE(q.dimension(),0);
    +        QVERIFY(q.qh()!=0);
    +        QCOMPARE(QString(q.qhullCommand()),QString(""));
    +        QCOMPARE(QString(q.rboxCommand()),QString(""));
    +        try{
    +            QCOMPARE(q.area(),0.0);
    +            QFAIL("area() did not fail.");
    +        }catch (const std::exception &e) {
    +            cout << "INFO   : Caught " << e.what();
    +        }
    +    }
    +    {
    +        RboxPoints rbox("10000");
    +        Qhull q(rbox, "QR0"); // Random points in a randomly rotated cube.
    +        QCOMPARE(q.dimension(),3);
    +        QVERIFY(q.volume() < 1.0);
    +        QVERIFY(q.volume() > 0.99);
    +    }
    +    {
    +        double points[] = {
    +            0, 0,
    +            1, 0,
    +            1, 1
    +        };
    +        Qhull q("triangle", 2, 3, points, "");
    +        QCOMPARE(q.dimension(),2);
    +        QCOMPARE(q.facetCount(),3);
    +        QCOMPARE(q.vertexCount(),3);
    +        QCOMPARE(q.dimension(),2);
    +        QCOMPARE(q.area(), 2.0+sqrt(2.0)); // length of boundary
    +        QCOMPARE(q.volume(), 0.5);        // the 2-d area
    +    }
    +}//t_construct
    +
    +void Qhull_test::
    +t_attribute()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        double normals[] = {
    +            0,  -1, -0.5,
    +           -1,   0, -0.5,
    +            1,   0, -0.5,
    +            0,   1, -0.5
    +        };
    +        Qhull q;
    +        Coordinates feasible;
    +        feasible << 0.0 << 0.0;
    +        q.setFeasiblePoint(feasible);
    +        Coordinates c(std::vector(2, 0.0));
    +        QVERIFY(q.feasiblePoint()==c);
    +        q.setOutputStream(&cout);
    +        q.runQhull("normals of square", 3, 4, normals, "H"); // halfspace intersect
    +        QVERIFY(q.feasiblePoint()==c); // from qh.feasible_point after runQhull()
    +        QCOMPARE(q.facetList().count(), 4); // Vertices of square
    +        cout << "Expecting summary of halfspace intersection\n";
    +        q.outputQhull();
    +        q.qh()->disableOutputStream();  // Same as q.disableOutputStream()
    +        cout << "Expecting no output from qh_fprintf() in Qhull.cpp\n";
    +        q.outputQhull();
    +    }
    +}//t_attribute
    +
    +//! No QhullMessage for errors outside of qhull
    +void Qhull_test::
    +t_message()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        QCOMPARE(q.qhullMessage(), string(""));
    +        QCOMPARE(q.qhullStatus(), qh_ERRnone);
    +        QVERIFY(!q.hasQhullMessage());
    +        try{
    +            q.runQhull(rcube, "Fd");
    +            QFAIL("runQhull Fd did not fail.");
    +        }catch (const std::exception &e) {
    +            const char *s= e.what();
    +            cout << "INFO   : Caught " << s;
    +            QCOMPARE(QString::fromStdString(s).left(6), QString("QH6029"));
    +            // FIXUP QH11025 -- review decision to clearQhullMessage at QhullError()            // Cleared when copied to QhullError
    +            QVERIFY(!q.hasQhullMessage());
    +            // QCOMPARE(q.qhullMessage(), QString::fromStdString(s).remove(0, 7));
    +            // QCOMPARE(q.qhullStatus(), 6029);
    +            q.clearQhullMessage();
    +            QVERIFY(!q.hasQhullMessage());
    +        }
    +        q.appendQhullMessage("Append 1");
    +        QVERIFY(q.hasQhullMessage());
    +        QCOMPARE(QString::fromStdString(q.qhullMessage()), QString("Append 1"));
    +        q.appendQhullMessage("\nAppend 2\n");
    +        QCOMPARE(QString::fromStdString(q.qhullMessage()), QString("Append 1\nAppend 2\n"));
    +        q.clearQhullMessage();
    +        QVERIFY(!q.hasQhullMessage());
    +        QCOMPARE(QString::fromStdString(q.qhullMessage()), QString(""));
    +    }
    +    {
    +        cout << "INFO   : Error stream without output stream\n";
    +        Qhull q;
    +        q.setErrorStream(&cout);
    +        q.setOutputStream(0);
    +        try{
    +            q.runQhull(rcube, "Fd");
    +            QFAIL("runQhull Fd did not fail.");
    +        }catch (const QhullError &e) {
    +            cout << "INFO   : Caught " << e;
    +            QCOMPARE(e.errorCode(), 6029);
    +        }
    +        //FIXUP QH11025 Qhullmessage cleared when QhullError thrown.  Switched to e
    +        //QVERIFY(q.hasQhullMessage());
    +        //QCOMPARE(QString::fromStdString(q.qhullMessage()).left(6), QString("QH6029"));
    +        q.clearQhullMessage();
    +        QVERIFY(!q.hasQhullMessage());
    +    }
    +    {
    +        cout << "INFO   : Error output sent to output stream without error stream\n";
    +        Qhull q;
    +        q.setErrorStream(0);
    +        q.setOutputStream(&cout);
    +        try{
    +            q.runQhull(rcube, "Tz H0");
    +            QFAIL("runQhull TZ did not fail.");
    +        }catch (const std::exception &e) {
    +            const char *s= e.what();
    +            cout << "INFO   : Caught " << s;
    +            QCOMPARE(QString::fromLatin1(s).left(6), QString("QH6023"));
    +        }
    +        //FIXUP QH11025 Qhullmessage cleared when QhullError thrown.  Switched to e
    +        //QVERIFY(q.hasQhullMessage());
    +        //QCOMPARE(QString::fromStdString(q.qhullMessage()).left(17), QString("qhull: no message"));
    +        //QCOMPARE(q.qhullStatus(), 6023);
    +        q.clearQhullMessage();
    +        QVERIFY(!q.hasQhullMessage());
    +    }
    +    {
    +        cout << "INFO   : No error stream or output stream\n";
    +        Qhull q;
    +        q.setErrorStream(0);
    +        q.setOutputStream(0);
    +        try{
    +            q.runQhull(rcube, "Fd");
    +            QFAIL("outputQhull did not fail.");
    +        }catch (const std::exception &e) {
    +            const char *s= e.what();
    +            cout << "INFO   : Caught " << s;
    +            QCOMPARE(QString::fromLatin1(s).left(6), QString("QH6029"));
    +        }
    +        //FIXUP QH11025 Qhullmessage cleared when QhullError thrown.  Switched to e
    +        //QVERIFY(q.hasQhullMessage());
    +        //QCOMPARE(QString::fromStdString(q.qhullMessage()).left(9), QString("qhull err"));
    +        //QCOMPARE(q.qhullStatus(), 6029);
    +        q.clearQhullMessage();
    +        QVERIFY(!q.hasQhullMessage());
    +    }
    +}//t_message
    +
    +void Qhull_test::
    +t_getSet()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        QVERIFY(!q.initialized());
    +        q.runQhull(rcube, "s");
    +        QVERIFY(q.initialized());
    +        QCOMPARE(q.dimension(), 3);
    +        QhullPoint p= q.origin();
    +        QCOMPARE(p.dimension(), 3);
    +        QCOMPARE(p[0]+p[1]+p[2], 0.0);
    +        q.setErrorStream(&cout);
    +        q.outputQhull();
    +    }
    +    {
    +        Qhull q;
    +        q.runQhull(rcube, "");
    +        q.setOutputStream(&cout);
    +        q.outputQhull();
    +    }
    +}//t_getSet
    +
    +void Qhull_test::
    +t_getQh()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        q.runQhull(rcube, "s");
    +        QCOMPARE(QString(q.qhullCommand()), QString("qhull s"));
    +        QCOMPARE(QString(q.rboxCommand()), QString("rbox \"c\""));
    +        QCOMPARE(q.facetCount(), 6);
    +        QCOMPARE(q.vertexCount(), 8);
    +        // Sample fields from Qhull's qhT [libqhull.h]
    +        QCOMPARE(q.qh()->ALLpoints, 0u);
    +        QCOMPARE(q.qh()->GOODpoint, 0);
    +        QCOMPARE(q.qh()->IStracing, 0);
    +        QCOMPARE(q.qh()->MAXcoplanar+1.0, 1.0); // fuzzy compare
    +        QCOMPARE(q.qh()->MERGING, 1u);
    +        QCOMPARE(q.qh()->input_dim, 3);
    +        QCOMPARE(QString(q.qh()->qhull_options).left(8), QString("  run-id"));
    +        QCOMPARE(q.qh()->num_facets, 6);
    +        QCOMPARE(q.qh()->hasTriangulation, 0u);
    +        QCOMPARE(q.qh()->max_outside - q.qh()->min_vertex + 1.0, 1.0); // fuzzy compare
    +        QCOMPARE(*q.qh()->gm_matrix+1.0, 1.0); // fuzzy compare
    +    }
    +}//t_getQh
    +
    +void Qhull_test::
    +t_getValue()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        q.runQhull(rcube, "");
    +        QCOMPARE(q.area(), 6.0);
    +        QCOMPARE(q.volume(), 1.0);
    +    }
    +}//t_getValue
    +
    +void Qhull_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        QCOMPARE(q.beginFacet(),q.endFacet());
    +        QCOMPARE(q.beginVertex(),q.endVertex());
    +        q.runQhull(rcube, "");
    +        QCOMPARE(q.facetList().count(), 6);
    +
    +        // defineVertexNeighborFacets() tested in QhullVertex_test::t_io()
    +
    +        QhullFacetList facets(q.beginFacet(), q.endFacet());
    +        QCOMPARE(facets.count(), 6);
    +        QCOMPARE(q.firstFacet(), q.beginFacet());
    +        QhullVertexList vertices(q.beginVertex(), q.endVertex());
    +        QCOMPARE(vertices.count(), 8);
    +        QCOMPARE(q.firstVertex(), q.beginVertex());
    +        QhullPoints ps= q.points();
    +        QCOMPARE(ps.count(), 8);
    +        QhullPointSet ps2= q.otherPoints();
    +        QCOMPARE(ps2.count(), 0);
    +        // ps2= q.otherPoints(); //disabled, would not copy the points
    +        QCOMPARE(q.facetCount(), 6);
    +        QCOMPARE(q.vertexCount(), 8);
    +        coordT *c= q.pointCoordinateBegin(); // of q.points()
    +        QVERIFY(*c==0.5 || *c==-0.5);
    +        coordT *c3= q.pointCoordinateEnd();
    +        QVERIFY(c3[-1]==0.5 || c3[-1]==-0.5);
    +        QCOMPARE(c3-c, 8*3);
    +        QCOMPARE(q.vertexList().count(), 8);
    +    }
    +}//t_foreach
    +
    +void Qhull_test::
    +t_modify()
    +{
    +    //addPoint() tested in t_foreach
    +    RboxPoints diamond("d");
    +    Qhull q(diamond, "o");
    +    q.setOutputStream(&cout);
    +    cout << "Expecting vertexList and facetList of a 3-d diamond.\n";
    +    q.outputQhull();
    +    cout << "Expecting normals of a 3-d diamond.\n";
    +    q.outputQhull("n");
    +    // runQhull tested in t_attribute(), t_message(), etc.
    +}//t_modify
    +
    +}//orgQhull
    +
    +// Redefine Qhull's usermem_r.c in order to report erroneous calls to qh_exit
    +void qh_exit(int exitcode) {
    +    cout << "FAIL!  : Qhull called qh_exit().  Qhull's error handling not available.\n.. See the corresponding Qhull:qhull_message or setErrorStream().\n";
    +    exit(exitcode);
    +}
    +void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    va_start(args, fmt);
    +    if(msgcode)
    +        fprintf(stderr, "QH%.4d ", msgcode);
    +    vfprintf(stderr, fmt, args);
    +    va_end(args);
    +} /* fprintf_stderr */
    +void qh_free(void *mem) {
    +    free(mem);
    +}
    +void *qh_malloc(size_t size) {
    +    return malloc(size);
    +}
    +
    +#if 0
    +template<> char * QTest::
    +toString(const std::string &s)
    +{
    +    QByteArray ba = s.c_str();
    +    return qstrdup(ba.data());
    +}
    +#endif
    +
    +#include "moc/Qhull_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/RboxPoints_test.cpp b/xs/src/qhull/src/qhulltest/RboxPoints_test.cpp
    new file mode 100644
    index 0000000000..4f4ea984f8
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/RboxPoints_test.cpp
    @@ -0,0 +1,215 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2006-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/RboxPoints_test.cpp#2 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullError.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::string;
    +using std::stringstream;
    +
    +namespace orgQhull {
    +
    +//! Test C++ interface to Rbox
    +//! See eg/q_test for tests of rbox commands
    +class RboxPoints_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void t_construct();
    +    void t_error();
    +    void t_test();
    +    void t_getSet();
    +    void t_foreach();
    +    void t_change();
    +    void t_ostream();
    +};
    +
    +void
    +add_RboxPoints_test()
    +{
    +    new RboxPoints_test();  // RoadTest::s_testcases
    +}
    +
    +void RboxPoints_test::
    +t_construct()
    +{
    +    RboxPoints rp;
    +    QCOMPARE(rp.dimension(), 0);
    +    QCOMPARE(rp.count(), 0);
    +    QVERIFY(QString::fromStdString(rp.comment()) != QString(""));
    +    QVERIFY(rp.isEmpty());
    +    QVERIFY(!rp.hasRboxMessage());
    +    QCOMPARE(rp.rboxStatus(), qh_ERRnone);
    +    QCOMPARE(QString::fromStdString(rp.rboxMessage()), QString("rbox warning: no points generated\n"));
    +
    +    RboxPoints rp2("c"); // 3-d cube
    +    QCOMPARE(rp2.dimension(), 3);
    +    QCOMPARE(rp2.count(), 8);
    +    QCOMPARE(QString::fromStdString(rp2.comment()), QString("rbox \"c\""));
    +    QVERIFY(!rp2.isEmpty());
    +    QVERIFY(!rp2.hasRboxMessage());
    +    QCOMPARE(rp2.rboxStatus(), qh_ERRnone);
    +    QCOMPARE(QString::fromStdString(rp2.rboxMessage()), QString("rbox: OK\n"));
    +}//t_construct
    +
    +void RboxPoints_test::
    +t_error()
    +{
    +    RboxPoints rp;
    +    try{
    +        rp.appendPoints("D0 c");
    +        QFAIL("'D0 c' did not fail.");
    +    }catch (const std::exception &e) {
    +        const char *s= e.what();
    +        cout << "INFO   : Caught " << s;
    +        QCOMPARE(QString(s).left(6), QString("QH6189"));
    +        QVERIFY(rp.hasRboxMessage());
    +        QCOMPARE(QString::fromStdString(rp.rboxMessage()).left(8), QString("rbox err"));
    +        QCOMPARE(rp.rboxStatus(), 6189);
    +        rp.clearRboxMessage();
    +        QVERIFY(!rp.hasRboxMessage());
    +    }
    +    try{
    +        RboxPoints rp2;
    +        rp2.setDimension(-1);
    +        QFAIL("setDimension(-1) did not fail.");
    +    }catch (const RoadError &e) {
    +        const char *s= e.what();
    +        cout << "INFO   : Caught " << s;
    +        QCOMPARE(QString(s).left(7), QString("QH10062"));
    +        QCOMPARE(e.errorCode(), 10062);
    +        QCOMPARE(QString::fromStdString(e.what()), QString(s));
    +        RoadLogEvent logEvent= e.roadLogEvent();
    +        QCOMPARE(logEvent.int1(), -1);
    +    }
    +}//t_error
    +
    +void RboxPoints_test::
    +t_test()
    +{
    +    // isEmpty -- t_construct
    +}//t_test
    +
    +void RboxPoints_test::
    +t_getSet()
    +{
    +    // comment -- t_construct
    +    // count -- t_construct
    +    // dimension -- t_construct
    +
    +    RboxPoints rp;
    +    QCOMPARE(rp.dimension(), 0);
    +    rp.setDimension(2);
    +    QCOMPARE(rp.dimension(), 2);
    +    try{
    +        rp.setDimension(102);
    +        QFAIL("setDimension(102) did not fail.");
    +    }catch (const std::exception &e) {
    +        cout << "INFO   : Caught " << e.what();
    +    }
    +    QCOMPARE(rp.newCount(), 0);
    +    rp.appendPoints("D2 P1 P2");
    +    QCOMPARE(rp.count(), 2);
    +    QCOMPARE(rp.newCount(), 2); // From previous appendPoints();
    +    PointCoordinates pc(rp.qh(), 2, "Test qh() and <<");
    +    pc << 1.0 << 0.0 << 2.0 << 0.0;
    +    QCOMPARE(pc.dimension(), 2);
    +    QCOMPARE(pc.count(), 2);
    +    QVERIFY(rp==pc);
    +    rp.setNewCount(10);  // Normally only used by appendPoints for rbox processing
    +    QCOMPARE(rp.newCount(), 10);
    +    rp.reservePoints();
    +    QVERIFY(rp==pc);
    +}//t_getSet
    +
    +void RboxPoints_test::
    +t_foreach()
    +{
    +    RboxPoints rp("c");
    +    Coordinates::ConstIterator cci= rp.beginCoordinates();
    +    orgQhull::Coordinates::Iterator ci= rp.beginCoordinates();
    +    QCOMPARE(*cci, -0.5);
    +    QCOMPARE(*ci, *cci);
    +    int i=1;
    +    while(++cci
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +
    +namespace orgQhull {
    +
    +#//!\name class variable
    +
    +QList RoadTest::
    +s_testcases;
    +
    +int RoadTest::
    +s_test_count= 0;
    +
    +int RoadTest::
    +s_test_fail= 0;
    +
    +QStringList RoadTest::
    +s_failed_tests;
    +
    +#//!\name Slot
    +
    +//! Executed after each test
    +void RoadTest::
    +cleanup()
    +{
    +    s_test_count++;
    +    if(QTest::currentTestFailed()){
    +        recordFailedTest();
    +    }
    +}//cleanup
    +
    +#//!\name Helper
    +
    +void RoadTest::
    +recordFailedTest()
    +{
    +    s_test_fail++;
    +    QString className= metaObject()->className();
    +    s_failed_tests << className + "::" + QTest::currentTestFunction();
    +}
    +
    +#//!\name class function
    +
    +void RoadTest::
    +deleteTests()
    +{
    +    foreach(RoadTest *testcase, s_testcases){
    +        delete testcase;
    +    }
    +    s_failed_tests.clear();
    +}
    +
    +int RoadTest::
    +runTests(QStringList arguments)
    +{
    +    int result= 0; // assume success
    +
    +    foreach(RoadTest *testcase, s_testcases){
    +        try{
    +            result += QTest::qExec(testcase, arguments);
    +        }catch(const std::exception &e){
    +            cout << "FAIL!  : Threw error ";
    +            cout << e.what() << endl;
    +    s_test_count++;
    +            testcase->recordFailedTest();
    +            // Qt 4.5.2 OK.  In Qt 4.3.3, qtestcase did not clear currentTestObject
    +        }
    +    }
    +    if(s_test_fail){
    +        cout << "Failed " << s_test_fail << " of " << s_test_count << " tests.\n";
    +        cout << s_failed_tests.join("\n").toLocal8Bit().constData() << std::endl;
    +    }else{
    +        cout << "Passed " << s_test_count << " tests.\n";
    +    }
    +    return result;
    +}//runTests
    +
    +}//orgQhull
    +
    +#include "moc/moc_RoadTest.cpp"
    diff --git a/xs/src/qhull/src/qhulltest/RoadTest.h b/xs/src/qhull/src/qhulltest/RoadTest.h
    new file mode 100644
    index 0000000000..adfe0bf8c1
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/RoadTest.h
    @@ -0,0 +1,102 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/RoadTest.h#2 $$Change: 2062 $
    +** $Date: 2016/01/17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef ROADTEST_H
    +#define ROADTEST_H
    +
    +//pre-compiled with RoadTest.h
    +#include     // Qt C++ Framework
    +#include 
    +
    +#define QHULL_USES_QT 1
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +
    +    //! RoadTest -- Generic test for Qt's QTest
    +    class RoadTest;
    +    //! TESTadd_(t) -- Add a RoadTest
    +
    +/** Test Name objects using Qt's QTestLib
    +
    +Template:
    +
    +class Name_test : public RoadTest
    +{
    +    Q_OBJECT
    +#//!\name Test slot
    +private slots:
    +    void t_name();
    +    //Executed before any test
    +    void initTestCase();
    +    void init();          // Each test
    +    //Executed after each test
    +    void cleanup(); //RoadTest::cleanup();
    +    // Executed after last test
    +    void cleanupTestCase();
    +};
    +
    +void
    +add_Name_test()
    +{
    +    new Name_test();  // RoadTest::s_testcases
    +}
    +
    +Send additional output to cout
    +*/
    +
    +class RoadTest : public QObject
    +{
    +    Q_OBJECT
    +
    +#//!\name Class globals
    +protected:
    +    static QList
    +                        s_testcases; ///! List of testcases to execute.  Initialized via add_...()
    +    static int          s_test_count; ///! Total number of tests executed
    +    static int          s_test_fail; ///! Number of failed tests
    +    static QStringList  s_failed_tests; ///! List of failed tests
    +
    +#//!\name Test slots
    +public slots:
    +    void cleanup();
    +
    +public:
    +#//!\name Constructors, etc.
    +                        RoadTest()  { s_testcases.append(this); }
    +    virtual             ~RoadTest() {} // Derived from QObject
    +
    +#//!\name Helper
    +    void                recordFailedTest();
    +
    +
    +#//!\name Class functions
    +    static void         deleteTests();
    +    static int          runTests(QStringList arguments);
    +
    +};//RoadTest
    +
    +#define TESTadd_(t) extern void t(); t();
    +
    +
    +}//orgQhull
    +
    +namespace QTest{
    +
    +template<>
    +inline char *
    +toString(const std::string &s)
    +{
    +    return qstrdup(s.c_str());
    +}
    +
    +}//namespace QTest
    +
    +#endif //ROADTEST_H
    +
    diff --git a/xs/src/qhull/src/qhulltest/qhulltest.cpp b/xs/src/qhull/src/qhulltest/qhulltest.cpp
    new file mode 100644
    index 0000000000..5bfe16e9cf
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/qhulltest.cpp
    @@ -0,0 +1,94 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/qhulltest.cpp#5 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include "libqhull_r/user_r.h"
    +
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/RoadError.h"
    +#include "libqhull_r/qhull_ra.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +
    +namespace orgQhull {
    +
    +void addQhullTests(QStringList &args)
    +{
    +    TESTadd_(add_Qhull_test);
    +
    +    if(args.contains("--all")){
    +        args.removeAll("--all");
    +        // up-to-date
    +        TESTadd_(add_Coordinates_test);
    +        TESTadd_(add_PointCoordinates_test);
    +        TESTadd_(add_QhullFacet_test);
    +        TESTadd_(add_QhullFacetList_test);
    +        TESTadd_(add_QhullFacetSet_test);
    +        TESTadd_(add_QhullHyperplane_test);
    +        TESTadd_(add_QhullLinkedList_test);
    +        TESTadd_(add_QhullPoint_test);
    +        TESTadd_(add_QhullPoints_test);
    +        TESTadd_(add_QhullPointSet_test);
    +        TESTadd_(add_QhullRidge_test);
    +        TESTadd_(add_QhullSet_test);
    +        TESTadd_(add_QhullVertex_test);
    +        TESTadd_(add_QhullVertexSet_test);
    +        TESTadd_(add_RboxPoints_test);
    +        // qhullStat
    +        TESTadd_(add_Qhull_test);
    +    }//--all
    +}//addQhullTests
    +
    +int main(int argc, char *argv[])
    +{
    +
    +    QCoreApplication app(argc, argv);
    +    QStringList args= app.arguments();
    +    bool isAll= args.contains("--all");
    +
    +    QHULL_LIB_CHECK /* Check for compatible library */
    +
    +    addQhullTests(args);
    +    int status=1010;
    +    try{
    +        status= RoadTest::runTests(args);
    +    }catch(const std::exception &e){
    +        cout << "FAIL!  : runTests() did not catch error\n";
    +        cout << e.what() << endl;
    +        if(!RoadError::emptyGlobalLog()){
    +            cout << RoadError::stringGlobalLog() << endl;
    +            RoadError::clearGlobalLog();
    +        }
    +    }
    +    if(!RoadError::emptyGlobalLog()){
    +        cout << RoadError::stringGlobalLog() << endl;
    +        RoadError::clearGlobalLog();
    +    }
    +    if(isAll){
    +        cout << "Finished test of libqhullcpp.  Test libqhull_r with eg/q_test after building libqhull_r/Makefile" << endl;
    +    }else{
    +        cout << "Finished test of one class.  Test all classes with 'qhulltest --all'" << endl;
    +    }
    +    RoadTest::deleteTests();
    +    return status;
    +}
    +
    +}//orgQhull
    +
    +int main(int argc, char *argv[])
    +{
    +    return orgQhull::main(argc, argv); // Needs RoadTest:: for TESTadd_() linkage
    +}
    +
    diff --git a/xs/src/qhull/src/qhulltest/qhulltest.pro b/xs/src/qhull/src/qhulltest/qhulltest.pro
    new file mode 100644
    index 0000000000..0da34d3755
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/qhulltest.pro
    @@ -0,0 +1,36 @@
    +# -------------------------------------------------
    +# qhulltest.pro -- Qt project for qhulltest.exe (QTestLib)
    +# cd $qh/build/qhulltest && qmake -tp vc -r ../../src/qhulltest/qhulltest.pro
    +# -------------------------------------------------
    +
    +include(../qhull-app-cpp.pri)
    +
    +TARGET = qhulltest
    +QT += testlib
    +MOC_DIR = moc
    +INCLUDEPATH += ..  # for MOC_DIR
    +
    +PRECOMPILED_HEADER = RoadTest.h
    +
    +HEADERS += RoadTest.h
    +
    +SOURCES += ../libqhullcpp/qt-qhull.cpp
    +SOURCES += Coordinates_test.cpp
    +SOURCES += PointCoordinates_test.cpp
    +SOURCES += Qhull_test.cpp
    +SOURCES += QhullFacet_test.cpp
    +SOURCES += QhullFacetList_test.cpp
    +SOURCES += QhullFacetSet_test.cpp
    +SOURCES += QhullHyperplane_test.cpp
    +SOURCES += QhullLinkedList_test.cpp
    +SOURCES += QhullPoint_test.cpp
    +SOURCES += QhullPoints_test.cpp
    +SOURCES += QhullPointSet_test.cpp
    +SOURCES += QhullRidge_test.cpp
    +SOURCES += QhullSet_test.cpp
    +SOURCES += qhulltest.cpp
    +SOURCES += QhullVertex_test.cpp
    +SOURCES += QhullVertexSet_test.cpp
    +SOURCES += RboxPoints_test.cpp
    +SOURCES += RoadTest.cpp
    +
    diff --git a/xs/src/qhull/src/qvoronoi/qvoronoi.c b/xs/src/qhull/src/qvoronoi/qvoronoi.c
    new file mode 100644
    index 0000000000..b93d237114
    --- /dev/null
    +++ b/xs/src/qhull/src/qvoronoi/qvoronoi.c
    @@ -0,0 +1,303 @@
    +/*
      ---------------------------------
    +
    +   qvoronoi.c
    +     compute Voronoi diagrams and furthest-point Voronoi
    +     diagrams using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull/libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qvoron_f.htm and qvoronoi.htm
    +   QJ and Qt are deprecated, but allowed for backwards compatibility
    +*/
    +char hidden_options[]=" d n m v H U Qb QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V Fa FA FC Fp FS Ft FV Pv Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qvoronoi- compute the Voronoi diagram\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qu   - compute furthest-site Voronoi diagram\n\
    +\n\
    +Qhull control options:\n\
    +    Qz   - add point-at-infinity to Voronoi diagram\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    QGn  - Voronoi vertices if visible from point n, -n if not\n\
    +    QVn  - Voronoi vertices for input point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - statistics\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Wn   - min facet width for non-coincident point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    s    - summary to stderr\n\
    +    p    - Voronoi vertices\n\
    +    o    - OFF format (dim, Voronoi vertices, and Voronoi regions)\n\
    +    i    - Delaunay regions (use 'Pp' to avoid warning)\n\
    +    f    - facet dump\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fc   - count plus coincident points (by Voronoi vertex)\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - separating hyperplanes for bounded Voronoi regions\n\
    +    FI   - ID for each Voronoi vertex\n\
    +    Fm   - merge count for each Voronoi vertex (511 max)\n\
    +    Fn   - count plus neighboring Voronoi vertices for each Voronoi vertex\n\
    +    FN   - count and Voronoi vertices for each Voronoi region\n\
    +    Fo   - separating hyperplanes for unbounded Voronoi regions\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest point and distance for each coincident point\n\
    +    FQ   - command used for qvoronoi\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                    for output: #Voronoi regions, #Voronoi vertices,\n\
    +                                #coincident points, #non-simplicial regions\n\
    +                    #real (2), max outer plane and min vertex\n\
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites\n\
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d only)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest Voronoi vertices by 'area'\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good Voronoi vertices (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep Voronoi vertices whose 'area' is at least n\n\
    +    PG   - print neighbors of good Voronoi vertices\n\
    +    PMn  - keep n Voronoi vertices with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qvoronoi- compute the Voronoi diagram.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qvoronoi.htm):\n\
    +    Qu   - compute furthest-site Voronoi diagram\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    p    - Voronoi vertices\n\
    +    o    - OFF file format (dim, Voronoi vertices, and Voronoi regions)\n\
    +    FN   - count and Voronoi vertices for each Voronoi region\n\
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites\n\
    +    Fi   - separating hyperplanes for bounded regions, 'Fo' for unbounded\n\
    +    G    - Geomview output (2-d only)\n\
    +    QVn  - Voronoi vertices for input point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +rbox c P0 D2 | qvoronoi s o         rbox c P0 D2 | qvoronoi Fi\n\
    +rbox c P0 D2 | qvoronoi Fo          rbox c P0 D2 | qvoronoi Fv\n\
    +rbox c P0 D2 | qvoronoi s Qu Fv     rbox c P0 D2 | qvoronoi Qu Fo\n\
    +rbox c G1 d D2 | qvoronoi s p       rbox c P0 D2 | qvoronoi s Fv QV0\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + OFF_format     p_vertices     i_delaunay     summary        facet_dump\n\
    +\n\
    + Fcoincident    Fd_cdd_in      FD_cdd_out     FF-dump-xridge Fi_bounded\n\
    + Fxtremes       Fmerges        Fneighbors     FNeigh_region  FOptions\n\
    + Fo_unbounded   FPoint_near    FQvoronoi      Fsummary       Fvoronoi\n\
    + FIDs\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QG_vertex_good Qsearch_1st    Qupper_voronoi QV_point_good  Qzinfinite\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh NOerrexit= False;
    +    qh_option("voronoi  _bbound-last  _coplanar-keep", NULL, NULL);
    +    qh DELAUNAY= True;     /* 'v'   */
    +    qh VORONOI= True;
    +    qh SCALElast= True;    /* 'Qbb' */
    +    qh_checkflags(qh qhull_command, hidden_options);
    +    qh_initflags(qh qhull_command);
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option("_merge-exact", NULL, NULL);
    +      qh MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh_ALL);
    +#else
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qvoronoi/qvoronoi.pro b/xs/src/qhull/src/qvoronoi/qvoronoi.pro
    new file mode 100644
    index 0000000000..4646c84472
    --- /dev/null
    +++ b/xs/src/qhull/src/qvoronoi/qvoronoi.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qvoronoi.pro -- Qt project file for qvoronoi.exe
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = qvoronoi
    +
    +SOURCES += qvoronoi.c
    diff --git a/xs/src/qhull/src/qvoronoi/qvoronoi_r.c b/xs/src/qhull/src/qvoronoi/qvoronoi_r.c
    new file mode 100644
    index 0000000000..6323c8b496
    --- /dev/null
    +++ b/xs/src/qhull/src/qvoronoi/qvoronoi_r.c
    @@ -0,0 +1,305 @@
    +/*
      ---------------------------------
    +
    +   qvoronoi.c
    +     compute Voronoi diagrams and furthest-point Voronoi
    +     diagrams using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qvoron_f.htm and qvoronoi.htm
    +   QJ and Qt are deprecated, but allowed for backwards compatibility
    +*/
    +char hidden_options[]=" d n m v H U Qb QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V Fa FA FC Fp FS Ft FV Pv Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qvoronoi- compute the Voronoi diagram\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qu   - compute furthest-site Voronoi diagram\n\
    +\n\
    +Qhull control options:\n\
    +    Qz   - add point-at-infinity to Voronoi diagram\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    QGn  - Voronoi vertices if visible from point n, -n if not\n\
    +    QVn  - Voronoi vertices for input point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - statistics\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Wn   - min facet width for non-coincident point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    s    - summary to stderr\n\
    +    p    - Voronoi vertices\n\
    +    o    - OFF format (dim, Voronoi vertices, and Voronoi regions)\n\
    +    i    - Delaunay regions (use 'Pp' to avoid warning)\n\
    +    f    - facet dump\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fc   - count plus coincident points (by Voronoi vertex)\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - separating hyperplanes for bounded Voronoi regions\n\
    +    FI   - ID for each Voronoi vertex\n\
    +    Fm   - merge count for each Voronoi vertex (511 max)\n\
    +    Fn   - count plus neighboring Voronoi vertices for each Voronoi vertex\n\
    +    FN   - count and Voronoi vertices for each Voronoi region\n\
    +    Fo   - separating hyperplanes for unbounded Voronoi regions\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest point and distance for each coincident point\n\
    +    FQ   - command used for qvoronoi\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                    for output: #Voronoi regions, #Voronoi vertices,\n\
    +                                #coincident points, #non-simplicial regions\n\
    +                    #real (2), max outer plane and min vertex\n\
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites\n\
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d only)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest Voronoi vertices by 'area'\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good Voronoi vertices (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep Voronoi vertices whose 'area' is at least n\n\
    +    PG   - print neighbors of good Voronoi vertices\n\
    +    PMn  - keep n Voronoi vertices with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qvoronoi- compute the Voronoi diagram.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qvoronoi.htm):\n\
    +    Qu   - compute furthest-site Voronoi diagram\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    p    - Voronoi vertices\n\
    +    o    - OFF file format (dim, Voronoi vertices, and Voronoi regions)\n\
    +    FN   - count and Voronoi vertices for each Voronoi region\n\
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites\n\
    +    Fi   - separating hyperplanes for bounded regions, 'Fo' for unbounded\n\
    +    G    - Geomview output (2-d only)\n\
    +    QVn  - Voronoi vertices for input point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +rbox c P0 D2 | qvoronoi s o         rbox c P0 D2 | qvoronoi Fi\n\
    +rbox c P0 D2 | qvoronoi Fo          rbox c P0 D2 | qvoronoi Fv\n\
    +rbox c P0 D2 | qvoronoi s Qu Fv     rbox c P0 D2 | qvoronoi Qu Fo\n\
    +rbox c G1 d D2 | qvoronoi s p       rbox c P0 D2 | qvoronoi s Fv QV0\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + OFF_format     p_vertices     i_delaunay     summary        facet_dump\n\
    +\n\
    + Fcoincident    Fd_cdd_in      FD_cdd_out     FF-dump-xridge Fi_bounded\n\
    + Fxtremes       Fmerges        Fneighbors     FNeigh_region  FOptions\n\
    + Fo_unbounded   FPoint_near    FQvoronoi      Fsummary       Fvoronoi\n\
    + FIDs\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QG_vertex_good Qsearch_1st    Qupper_voronoi QV_point_good  Qzinfinite\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_option(qh, "voronoi  _bbound-last  _coplanar-keep", NULL, NULL);
    +    qh->DELAUNAY= True;     /* 'v'   */
    +    qh->VORONOI= True;
    +    qh->SCALElast= True;    /* 'Qbb' */
    +    qh_checkflags(qh, qh->qhull_command, hidden_options);
    +    qh_initflags(qh, qh->qhull_command);
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option(qh, "_merge-exact", NULL, NULL);
    +      qh->MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/rbox/rbox.c b/xs/src/qhull/src/rbox/rbox.c
    new file mode 100644
    index 0000000000..d7c51b1aaf
    --- /dev/null
    +++ b/xs/src/qhull/src/rbox/rbox.c
    @@ -0,0 +1,88 @@
    +/*
      ---------------------------------
    +
    +   rbox.c
    +     rbox program for generating input points for qhull.
    +
    +   notes:
    +     50 points generated for 'rbox D4'
    +
    +*/
    +
    +#include "libqhull/libqhull.h"
    +#include "libqhull/random.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#endif
    +
    +char prompt[]= "\n\
    +-rbox- generate various point distributions.  Default is random in cube.\n\
    +\n\
    +args (any order, space separated):                    Version: 2016/01/18\n\
    +  3000    number of random points in cube, lens, spiral, sphere or grid\n\
    +  D3      dimension 3-d\n\
    +  c       add a unit cube to the output ('c G2.0' sets size)\n\
    +  d       add a unit diamond to the output ('d G2.0' sets size)\n\
    +  l       generate a regular 3-d spiral\n\
    +  r       generate a regular polygon, ('r s Z1 G0.1' makes a cone)\n\
    +  s       generate cospherical points\n\
    +  x       generate random points in simplex, may use 'r' or 'Wn'\n\
    +  y       same as 'x', plus simplex\n\
    +  Cn,r,m  add n nearly coincident points within radius r of m points\n\
    +  Pn,m,r  add point [n,m,r] first, pads with 0, maybe repeated\n\
    +\n\
    +  Ln      lens distribution of radius n.  Also 's', 'r', 'G', 'W'.\n\
    +  Mn,m,r  lattice(Mesh) rotated by [n,-m,0], [m,n,0], [0,0,r], ...\n\
    +          '27 M1,0,1' is {0,1,2} x {0,1,2} x {0,1,2}.  Try 'M3,4 z'.\n\
    +  W0.1    random distribution within 0.1 of the cube's or sphere's surface\n\
    +  Z0.5 s  random points in a 0.5 disk projected to a sphere\n\
    +  Z0.5 s G0.6 same as Z0.5 within a 0.6 gap\n\
    +\n\
    +  Bn      bounding box coordinates, default %2.2g\n\
    +  h       output as homogeneous coordinates for cdd\n\
    +  n       remove command line from the first line of output\n\
    +  On      offset coordinates by n\n\
    +  t       use time as the random number seed(default is command line)\n\
    +  tn      use n as the random number seed\n\
    +  z       print integer coordinates, default 'Bn' is %2.2g\n\
    +";
    +
    +/*--------------------------------------------
    +-rbox-  main procedure of rbox application
    +*/
    +int main(int argc, char **argv) {
    +  char *command;
    +  int command_size;
    +  int return_status;
    +
    +  QHULL_LIB_CHECK_RBOX
    +
    +  if (argc == 1) {
    +    printf(prompt, qh_DEFAULTbox, qh_DEFAULTzbox);
    +    return 1;
    +  }
    +  if (argc == 2 && strcmp(argv[1], "D4")==0)
    +    qh_fprintf_stderr(0, "\nStarting the rbox smoketest for qhull.  An immediate failure indicates\nthat non-reentrant rbox was linked to reentrant routines.  An immediate\nfailure of qhull may indicate that qhull was linked to the wrong\nqhull library.  Also try 'rbox D4 | qhull T1'\n");
    +
    +  command_size= qh_argv_to_command_size(argc, argv);
    +  if ((command= (char *)qh_malloc((size_t)command_size))) {
    +    if (!qh_argv_to_command(argc, argv, command, command_size)) {
    +      qh_fprintf_stderr(6264, "rbox internal error: allocated insufficient memory (%d) for arguments\n", command_size);
    +      return_status= qh_ERRinput;
    +    }else{
    +      return_status= qh_rboxpoints(stdout, stderr, command);
    +    }
    +    qh_free(command);
    +  }else {
    +    qh_fprintf_stderr(6265, "rbox error: insufficient memory for %d bytes\n", command_size);
    +    return_status= qh_ERRmem;
    +  }
    +  return return_status;
    +}/*main*/
    +
    diff --git a/xs/src/qhull/src/rbox/rbox.pro b/xs/src/qhull/src/rbox/rbox.pro
    new file mode 100644
    index 0000000000..6c21bdb6df
    --- /dev/null
    +++ b/xs/src/qhull/src/rbox/rbox.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# rbox.pro -- Qt project for rbox.exe with libqhullstatic
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = rbox
    +
    +SOURCES += rbox.c
    diff --git a/xs/src/qhull/src/rbox/rbox_r.c b/xs/src/qhull/src/rbox/rbox_r.c
    new file mode 100644
    index 0000000000..6ec74d914b
    --- /dev/null
    +++ b/xs/src/qhull/src/rbox/rbox_r.c
    @@ -0,0 +1,78 @@
    +
    +/*
      ---------------------------------
    +
    +   rbox.c
    +     rbox program for generating input points for qhull.
    +
    +   notes:
    +     50 points generated for 'rbox D4'
    +
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +#include "libqhull/random_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#endif
    +
    +char prompt[]= "\n\
    +-rbox- generate various point distributions.  Default is random in cube.\n\
    +\n\
    +args (any order, space separated):                    Version: 2016/01/18 r\n\
    +  3000    number of random points in cube, lens, spiral, sphere or grid\n\
    +  D3      dimension 3-d\n\
    +  c       add a unit cube to the output ('c G2.0' sets size)\n\
    +  d       add a unit diamond to the output ('d G2.0' sets size)\n\
    +  l       generate a regular 3-d spiral\n\
    +  r       generate a regular polygon, ('r s Z1 G0.1' makes a cone)\n\
    +  s       generate cospherical points\n\
    +  x       generate random points in simplex, may use 'r' or 'Wn'\n\
    +  y       same as 'x', plus simplex\n\
    +  Cn,r,m  add n nearly coincident points within radius r of m points\n\
    +  Pn,m,r  add point [n,m,r] first, pads with 0, maybe repeated\n\
    +\n\
    +  Ln      lens distribution of radius n.  Also 's', 'r', 'G', 'W'.\n\
    +  Mn,m,r  lattice(Mesh) rotated by [n,-m,0], [m,n,0], [0,0,r], ...\n\
    +          '27 M1,0,1' is {0,1,2} x {0,1,2} x {0,1,2}.  Try 'M3,4 z'.\n\
    +  W0.1    random distribution within 0.1 of the cube's or sphere's surface\n\
    +  Z0.5 s  random points in a 0.5 disk projected to a sphere\n\
    +  Z0.5 s G0.6 same as Z0.5 within a 0.6 gap\n\
    +\n\
    +  Bn      bounding box coordinates, default %2.2g\n\
    +  h       output as homogeneous coordinates for cdd\n\
    +  n       remove command line from the first line of output\n\
    +  On      offset coordinates by n\n\
    +  t       use time as the random number seed(default is command line)\n\
    +  tn      use n as the random number seed\n\
    +  z       print integer coordinates, default 'Bn' is %2.2g\n\
    +";
    +
    +/*--------------------------------------------
    +-rbox-  main procedure of rbox application
    +*/
    +int main(int argc, char **argv) {
    +  int return_status;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK_RBOX
    +
    +  if (argc == 1) {
    +    printf(prompt, qh_DEFAULTbox, qh_DEFAULTzbox);
    +    return 1;
    +  }
    +  if (argc == 2 && strcmp(argv[1], "D4")==0)
    +    qh_fprintf_stderr(0, "\nStarting the rbox smoketest for qhull.  An immediate failure indicates\nthat reentrant rbox was linked to non-reentrant routines.  An immediate\nfailure of qhull may indicate that qhull was linked to the wrong\nqhull library.  Also try 'rbox D4 | qhull T1'\n");
    +
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /*no qh_errexit, sets qh->qhull_command */
    +  return_status= qh_rboxpoints(qh, qh->qhull_command); /* Traps its own errors, qh_errexit_rbox() */
    +  return return_status;
    +}/*main*/
    +
    diff --git a/xs/src/qhull/src/testqset/testqset.c b/xs/src/qhull/src/testqset/testqset.c
    new file mode 100644
    index 0000000000..61057eef9c
    --- /dev/null
    +++ b/xs/src/qhull/src/testqset/testqset.c
    @@ -0,0 +1,891 @@
    +/*
      ---------------------------------
    +
    +   testset.c -- test qset.c and its use of mem.c
    +
    +   The test sets are pointers to int.  Normally a set is a pointer to a type (e.g., facetT, ridgeT, etc.).
    +   For consistency in notation, an "int" is typedef'd to i2T
    +
    +Functions and macros from qset.h.  Counts occurrences in this test.  Does not correspond to thoroughness.
    +    qh_setaddsorted -- 4 tests
    +    qh_setaddnth -- 1 test
    +    qh_setappend -- 7 tests
    +    qh_setappend_set -- 1 test
    +    qh_setappend2ndlast -- 1 test
    +    qh_setcheck -- lots of tests
    +    qh_setcompact -- 7 tests
    +    qh_setcopy -- 3 tests
    +    qh_setdel -- 1 tests
    +    qh_setdellast -- 1 tests
    +    qh_setdelnth -- 2 tests
    +    qh_setdelnthsorted -- 2 tests
    +    qh_setdelsorted -- 1 test
    +    qh_setduplicate -- not testable here
    +    qh_setequal -- 4 tests
    +    qh_setequal_except -- 2 tests
    +    qh_setequal_skip -- 2 tests
    +    qh_setfree -- 11+ tests
    +    qh_setfree2 -- not testable here
    +    qh_setfreelong -- 2 tests
    +    qh_setin -- 3 tests
    +    qh_setindex -- 4 tests
    +    qh_setlarger -- 1 test
    +    qh_setlast -- 2 tests
    +    qh_setnew -- 6 tests
    +    qh_setnew_delnthsorted
    +    qh_setprint -- tested elsewhere
    +    qh_setreplace -- 1 test
    +    qh_setsize -- 9+ tests
    +    qh_settemp -- 2 tests
    +    qh_settempfree -- 1 test
    +    qh_settempfree_all -- 1 test
    +    qh_settemppop -- 1 test
    +    qh_settemppush -- 1 test
    +    qh_settruncate -- 3 tests
    +    qh_setunique -- 3 tests
    +    qh_setzero -- 1 test
    +    FOREACHint_ -- 2 test
    +    FOREACHint4_
    +    FOREACHint_i_ -- 1 test
    +    FOREACHintreverse_
    +    FOREACHintreverse12_
    +    FOREACHsetelement_ -- 1 test
    +    FOREACHsetelement_i_ -- 1 test
    +    FOREACHsetelementreverse_ -- 1 test
    +    FOREACHsetelementreverse12_ -- 1 test
    +    SETelem_ -- 3 tests
    +    SETelemaddr_ -- 2 tests
    +    SETelemt_ -- not tested (generic)
    +    SETempty_ -- 1 test
    +    SETfirst_ -- 4 tests
    +    SETfirstt_ -- 2 tests
    +    SETindex_ -- 2 tests
    +    SETref_ -- 2 tests
    +    SETreturnsize_ -- 2 tests
    +    SETsecond_ -- 1 test
    +    SETsecondt_ -- 2 tests
    +    SETtruncate_ -- 2 tests
    +
    +    Copyright (c) 2012-2015 C.B. Barber. All rights reserved.
    +    $Id: //main/2015/qhull/src/testqset/testqset.c#4 $$Change: 2062 $
    +    $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "libqhull/user.h"  /* QHULL_CRTDBG */
    +#include "libqhull/qset.h"
    +#include "libqhull/mem.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +typedef int i2T;
    +#define MAXerrorCount 100 /* quit after n errors */
    +
    +#define FOREACHint_( ints ) FOREACHsetelement_( i2T, ints, i2)
    +#define FOREACHint4_( ints ) FOREACHsetelement_( i2T, ints, i4)
    +#define FOREACHint_i_( ints ) FOREACHsetelement_i_( i2T, ints, i2)
    +#define FOREACHintreverse_( ints ) FOREACHsetelementreverse_( i2T, ints, i2)
    +#define FOREACHintreverse12_( ints ) FOREACHsetelementreverse12_( i2T, ints, i2)
    +
    +enum {
    +    MAXint= 0x7fffffff,
    +};
    +
    +char prompt[]= "testqset N [M] [T5] -- Test qset.c and mem.c\n\
    +  \n\
    +  If this test fails then qhull will not work.\n\
    +  \n\
    +  Test qsets of 0..N integers with a check every M iterations (default ~log10)\n\
    +  Additional checking and logging if M is 1\n\
    +  \n\
    +  T5 turns on memory logging (qset does not log)\n\
    +  \n\
    +  For example:\n\
    +    testqset 10000\n\
    +";
    +
    +int error_count= 0;  /* Global error_count.  checkSetContents() keeps its own error count.  It exits on too many errors */
    +
    +/* Macros normally defined in geom.h */
    +#define fmax_( a,b )  ( ( a ) < ( b ) ? ( b ) : ( a ) )
    +
    +/* Macros normally defined in user.h */
    +
    +#define realT double
    +#define qh_MEMalign ((int)(fmax_(sizeof(realT), sizeof(void *))))
    +#define qh_MEMbufsize 0x10000       /* allocate 64K memory buffers */
    +#define qh_MEMinitbuf 0x20000      /* initially allocate 128K buffer */
    +
    +/* Macros normally defined in QhullSet.h */
    +
    +
    +/* Functions normally defined in user.h for usermem.c */
    +
    +void    qh_exit(int exitcode);
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +
    +/* Normally defined in user.c */
    +
    +void    qh_errexit(int exitcode, void *f, void *r)
    +{
    +    (void)f; /* unused */
    +    (void)r; /* unused */
    +    qh_exit(exitcode);
    +}
    +
    +/* Normally defined in userprintf.c */
    +
    +void    qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... )
    +{
    +    static int needs_cr= 0;  /* True if qh_fprintf needs a CR */
    +
    +    size_t fmtlen= strlen(fmt);
    +    va_list args;
    +
    +    if (!fp) {
    +        /* Do not use qh_fprintf_stderr.  This is a standalone program */
    +        fprintf(stderr, "QH6232 qh_fprintf: fp not defined for '%s'", fmt);
    +        qh_errexit(6232, NULL, NULL);
    +    }
    +    if(fmtlen>0){
    +        if(fmt[fmtlen-1]=='\n'){
    +            if(needs_cr && fmtlen>1){
    +                fprintf(fp, "\n");
    +            }
    +            needs_cr= 0;
    +        }else{
    +            needs_cr= 1;
    +        }
    +    }
    +    if(msgcode>=6000 && msgcode<7000){
    +        fprintf(fp, "Error TQ%d ", msgcode);
    +    }
    +    va_start(args, fmt);
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +}
    +
    +/* Defined below in order of use */
    +int main(int argc, char **argv);
    +void readOptions(int argc, char **argv, const char *promptstr, int *numInts, int *checkEvery, int *traceLevel);
    +void setupMemory(int tracelevel, int numInts, int **intarray);
    +
    +void testSetappendSettruncate(int numInts, int *intarray, int checkEvery);
    +void testSetdelSetadd(int numInts, int *intarray, int checkEvery);
    +void testSetappendSet(int numInts, int *intarray, int checkEvery);
    +void testSetcompactCopy(int numInts, int *intarray, int checkEvery);
    +void testSetequalInEtc(int numInts, int *intarray, int checkEvery);
    +void testSettemp(int numInts, int *intarray, int checkEvery);
    +void testSetlastEtc(int numInts, int *intarray, int checkEvery);
    +void testSetdelsortedEtc(int numInts, int *intarray, int checkEvery);
    +
    +int log_i(setT *set, const char *s, int i, int numInts, int checkEvery);
    +void checkSetContents(const char *name, setT *set, int count, int rangeA, int rangeB, int rangeC);
    +
    +int main(int argc, char **argv) {
    +    int *intarray= NULL;
    +    int numInts;
    +    int checkEvery= MAXint;
    +    int curlong, totlong;
    +    int traceLevel= 4; /* 4 normally, no tracing since qset does not log.  5 for memory tracing */
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)  /* user.h */
    +    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) );
    +    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
    +#endif
    +
    +    readOptions(argc, argv, prompt, &numInts, &checkEvery, &traceLevel);
    +    setupMemory(traceLevel, numInts, &intarray);
    +
    +    testSetappendSettruncate(numInts, intarray, checkEvery);
    +    testSetdelSetadd(numInts, intarray, checkEvery);
    +    testSetappendSet(numInts, intarray, checkEvery);
    +    testSetcompactCopy(numInts, intarray, checkEvery);
    +    testSetequalInEtc(numInts, intarray, checkEvery);
    +    testSettemp(numInts, intarray, checkEvery);
    +    testSetlastEtc(numInts, intarray, checkEvery);
    +    testSetdelsortedEtc(numInts, intarray, checkEvery);
    +    printf("\n\nNot testing qh_setduplicate and qh_setfree2.\n  These routines use heap-allocated set contents.  See qhull tests.\n");
    +
    +    qh_memstatistics(stdout);
    +    qh_memfreeshort(&curlong, &totlong);
    +    if (curlong || totlong){
    +        qh_fprintf(stderr, 8043, "qh_memfreeshort: did not free %d bytes of long memory(%d pieces)\n", totlong, curlong);
    +        error_count++;
    +    }
    +    if(error_count){
    +        qh_fprintf(stderr, 8012, "testqset: %d errors\n\n", error_count);
    +        exit(1);
    +    }else{
    +        printf("testqset: OK\n\n");
    +    }
    +    return 0;
    +}/*main*/
    +
    +void readOptions(int argc, char **argv, const char *promptstr, int *numInts, int *checkEvery, int *traceLevel)
    +{
    +    long numIntsArg;
    +    long checkEveryArg;
    +    char *endp;
    +    int isTracing= 0;
    +
    +    if (argc < 2 || argc > 4) {
    +        printf("%s", promptstr);
    +        exit(0);
    +    }
    +    numIntsArg= strtol(argv[1], &endp, 10);
    +    if(numIntsArg<1){
    +        qh_fprintf(stderr, 6301, "First argument should be 1 or greater.  Got '%s'\n", argv[1]);
    +        exit(1);
    +    }
    +    if(numIntsArg>MAXint){
    +        qh_fprintf(stderr, 6302, "qset does not currently support 64-bit ints.  Maximum count is %d\n", MAXint);
    +        exit(1);
    +    }
    +    *numInts= (int)numIntsArg;
    +
    +    if(argc==3 && argv[2][0]=='T' && argv[2][1]=='5' ){
    +        isTracing= 1;
    +        *traceLevel= 5;
    +    }
    +    if(argc==4 || (argc==3 && !isTracing)){
    +        checkEveryArg= strtol(argv[2], &endp, 10);
    +        if(checkEveryArg<1){
    +            qh_fprintf(stderr, 6321, "checkEvery argument should be 1 or greater.  Got '%s'\n", argv[2]);
    +            exit(1);
    +        }
    +        if(checkEveryArg>MAXint){
    +            qh_fprintf(stderr, 6322, "qset does not currently support 64-bit ints.  Maximum checkEvery is %d\n", MAXint);
    +            exit(1);
    +        }
    +        if(argc==4){
    +            if(argv[3][0]=='T' && argv[3][1]=='5' ){
    +                isTracing= 1;
    +                *traceLevel= 5;
    +            }else{
    +                qh_fprintf(stderr, 6242, "Optional third argument must be 'T5'.  Got '%s'\n", argv[3]);
    +                exit(1);
    +            }
    +        }
    +        *checkEvery= (int)checkEveryArg;
    +    }
    +}/*readOptions*/
    +
    +void setupMemory(int tracelevel, int numInts, int **intarray)
    +{
    +    int i;
    +    if(numInts<0 || numInts*(int)sizeof(int)<0){
    +        qh_fprintf(stderr, 6303, "qset does not currently support 64-bit ints.  Integer overflow\n");
    +        exit(1);
    +    }
    +    *intarray= qh_malloc(numInts * sizeof(int));
    +    if(!*intarray){
    +        qh_fprintf(stderr, 6304, "Failed to allocate %d bytes of memory\n", numInts * sizeof(int));
    +        exit(1);
    +    }
    +    for(i= 0; i=2){
    +        isCheck= log_i(ints, "n", numInts/2, numInts, checkEvery);
    +        qh_settruncate(ints, numInts/2);
    +        checkSetContents("qh_settruncate by half", ints, numInts/2, 0, -1, -1);
    +    }
    +    isCheck= log_i(ints, "n", 0, numInts, checkEvery);
    +    qh_settruncate(ints, 0);
    +    checkSetContents("qh_settruncate", ints, 0, -1, -1, -1);
    +
    +    qh_fprintf(stderr, 8003, "\n\nTesting qh_setappend2ndlast 0,0..%d.  Test 0", numInts-1);
    +    qh_setfree(&ints);
    +    ints= qh_setnew(4);
    +    qh_setappend(&ints, intarray+0);
    +    for(i= 0; i=2){
    +        isCheck= log_i(ints, "n", numInts/2, numInts, checkEvery);
    +        SETtruncate_(ints, numInts/2);
    +        checkSetContents("SETtruncate_ by half", ints, numInts/2, 0, -1, -1);
    +    }
    +    isCheck= log_i(ints, "n", 0, numInts, checkEvery);
    +    SETtruncate_(ints, 0);
    +    checkSetContents("SETtruncate_", ints, 0, -1, -1, -1);
    +
    +    qh_setfree(&ints);
    +}/*testSetappendSettruncate*/
    +
    +void testSetdelSetadd(int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints=qh_setnew(1);
    +    int i,j,isCheck;
    +
    +    qh_fprintf(stderr, 8003, "\n\nTesting qh_setdelnthsorted and qh_setaddnth 1..%d. Test", numInts-1);
    +    for(j=1; j3){
    +                qh_setdelsorted(ints, intarray+i/2);
    +                checkSetContents("qh_setdelsorted", ints, j-1, 0, i/2+1, -1);
    +                qh_setaddsorted(&ints, intarray+i/2);
    +                checkSetContents("qh_setaddsorted i/2", ints, j, 0, 0, -1);
    +            }
    +            qh_setdellast(ints);
    +            checkSetContents("qh_setdellast", ints, (j ? j-1 : 0), 0, -1, -1);
    +            if(j>0){
    +                qh_setaddsorted(&ints, intarray+j-1);
    +                checkSetContents("qh_setaddsorted j-1", ints, j, 0, -1, -1);
    +            }
    +            if(j>4){
    +                qh_setdelnthsorted(ints, i/2);
    +                if (checkEvery==1)
    +                  checkSetContents("qh_setdelnthsorted", ints, j-1, 0, i/2+1, -1);
    +                /* test qh_setdelnth and move-to-front */
    +                qh_setdelsorted(ints, intarray+i/2+1);
    +                checkSetContents("qh_setdelsorted 2", ints, j-2, 0, i/2+2, -1);
    +                qh_setaddsorted(&ints, intarray+i/2+1);
    +                if (checkEvery==1)
    +                  checkSetContents("qh_setaddsorted i/2+1", ints, j-1, 0, i/2+1, -1);
    +                qh_setaddsorted(&ints, intarray+i/2);
    +                checkSetContents("qh_setaddsorted i/2 again", ints, j, 0, -1, -1);
    +            }
    +            qh_setfree(&ints2);
    +            ints2= qh_setcopy(ints, 0);
    +            qh_setcompact(ints);
    +            qh_setcompact(ints2);
    +            checkSetContents("qh_setcompact", ints, j, 0, 0, -1);
    +            checkSetContents("qh_setcompact 2", ints2, j, 0, 0, -1);
    +            qh_setcompact(ints);
    +            checkSetContents("qh_setcompact 3", ints, j, 0, 0, -1);
    +            qh_setfree(&ints2);
    +        }
    +    }
    +    qh_setfreelong(&ints);
    +    if(ints){
    +        qh_setfree(&ints); /* Was quick memory */
    +    }
    +}/*testSetdelsortedEtc*/
    +
    +void testSetequalInEtc(int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    setT *ints3= NULL;
    +    int i,j,n;
    +
    +    qh_fprintf(stderr, 8019, "\n\nTesting qh_setequal*, qh_setin*, qh_setdel, qh_setdelnth, and qh_setlarger 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                if(qh_setequal(ints, ints2)){
    +                    qh_fprintf(stderr, 6324, "testSetequalInEtc: non-empty set equal to empty set\n", j);
    +                    error_count++;
    +                }
    +                qh_setfree(&ints3);
    +                ints3= qh_setcopy(ints, 0);
    +                checkSetContents("qh_setreplace", ints3, j, 0, -1, -1);
    +                qh_setreplace(ints3, intarray+j/2, intarray+j/2+1);
    +                if(j==1){
    +                    checkSetContents("qh_setreplace 2", ints3, j, j/2+1, -1, -1);
    +                }else if(j==2){
    +                    checkSetContents("qh_setreplace 3", ints3, j, 0, j/2+1, -1);
    +                }else{
    +                    checkSetContents("qh_setreplace 3", ints3, j, 0, j/2+1, j/2+1);
    +                }
    +                if(qh_setequal(ints, ints3)){
    +                    qh_fprintf(stderr, 6325, "testSetequalInEtc: modified set equal to original set at %d/2\n", j);
    +                    error_count++;
    +                }
    +                if(!qh_setequal_except(ints, intarray+j/2, ints3, intarray+j/2+1)){
    +                    qh_fprintf(stderr, 6326, "qh_setequal_except: modified set not equal to original set except modified\n", j);
    +                    error_count++;
    +                }
    +                if(qh_setequal_except(ints, intarray+j/2, ints3, intarray)){
    +                    qh_fprintf(stderr, 6327, "qh_setequal_except: modified set equal to original set with wrong excepts\n", j);
    +                    error_count++;
    +                }
    +                if(!qh_setequal_skip(ints, j/2, ints3, j/2)){
    +                    qh_fprintf(stderr, 6328, "qh_setequal_skip: modified set not equal to original set except modified\n", j);
    +                    error_count++;
    +                }
    +                if(j>2 && qh_setequal_skip(ints, j/2, ints3, 0)){
    +                    qh_fprintf(stderr, 6329, "qh_setequal_skip: modified set equal to original set with wrong excepts\n", j);
    +                    error_count++;
    +                }
    +                if(intarray+j/2+1!=qh_setdel(ints3, intarray+j/2+1)){
    +                    qh_fprintf(stderr, 6330, "qh_setdel: failed to find added element\n", j);
    +                    error_count++;
    +                }
    +                checkSetContents("qh_setdel", ints3, j-1, 0, j-1, (j==1 ? -1 : j/2+1));  /* swaps last element with deleted element */
    +                if(j>3){
    +                    qh_setdelnth(ints3, j/2); /* Delete at the same location as the original replace, for only one out-of-order element */
    +                    checkSetContents("qh_setdelnth", ints3, j-2, 0, j-2, (j==2 ? -1 : j/2+1));
    +                }
    +                if(qh_setin(ints3, intarray+j/2)){
    +                    qh_fprintf(stderr, 6331, "qh_setin: found deleted element\n");
    +                    error_count++;
    +                }
    +                if(j>4 && !qh_setin(ints3, intarray+1)){
    +                    qh_fprintf(stderr, 6332, "qh_setin: did not find second element\n");
    +                    error_count++;
    +                }
    +                if(j>4 && !qh_setin(ints3, intarray+j-2)){
    +                    qh_fprintf(stderr, 6333, "qh_setin: did not find last element\n");
    +                    error_count++;
    +                }
    +                if(-1!=qh_setindex(ints2, intarray)){
    +                    qh_fprintf(stderr, 6334, "qh_setindex: found element in empty set\n");
    +                    error_count++;
    +                }
    +                if(-1!=qh_setindex(ints3, intarray+j/2)){
    +                    qh_fprintf(stderr, 6335, "qh_setindex: found deleted element in set\n");
    +                    error_count++;
    +                }
    +                if(0!=qh_setindex(ints, intarray)){
    +                    qh_fprintf(stderr, 6336, "qh_setindex: did not find first in set\n");
    +                    error_count++;
    +                }
    +                if(j-1!=qh_setindex(ints, intarray+j-1)){
    +                    qh_fprintf(stderr, 6337, "qh_setindex: did not find last in set\n");
    +                    error_count++;
    +                }
    +            }
    +            qh_setfree(&ints2);
    +        }
    +    }
    +    qh_setfree(&ints3);
    +    qh_setfreelong(&ints);
    +    if(ints){
    +        qh_setfree(&ints); /* Was quick memory */
    +    }
    +}/*testSetequalInEtc*/
    +
    +
    +void testSetlastEtc(int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    int i,j,prepend;
    +
    +    qh_fprintf(stderr, 8020, "\n\nTesting qh_setlast, qh_setnew_delnthsorted, qh_setunique, and qh_setzero 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                if(intarray+j-1!=qh_setlast(ints)){
    +                    qh_fprintf(stderr, 6338, "qh_setlast: wrong last element\n");
    +                    error_count++;
    +                }
    +                prepend= (j<100 ? j/4 : 0);
    +                ints2= qh_setnew_delnthsorted(ints, qh_setsize(ints), j/2, prepend);
    +                if(qh_setsize(ints2)!=j+prepend-1){
    +                    qh_fprintf(stderr, 6345, "qh_setnew_delnthsorted: Expecting %d elements, got %d\n", j+prepend-1, qh_setsize(ints2));
    +                    error_count++;
    +                }
    +                /* Define prepended elements.  Otherwise qh_setdelnthsorted may fail */
    +                for(i= 0; i2){
    +                    qh_setzero(ints2, j/2, j-1);  /* max size may be j-1 */
    +                    if(qh_setsize(ints2)!=j-1){
    +                        qh_fprintf(stderr, 6342, "qh_setzero: Expecting %d elements, got %d\n", j, qh_setsize(ints2));
    +                        error_count++;
    +                    }
    +                    qh_setcompact(ints2);
    +                    checkSetContents("qh_setzero", ints2, j/2, 0, -1, -1);
    +                }
    +            }
    +            qh_setfree(&ints2);
    +        }
    +    }
    +    qh_setfreelong(&ints);
    +    if(ints){
    +        qh_setfree(&ints); /* Was quick memory */
    +    }
    +}/*testSetlastEtc*/
    +
    +void testSettemp(int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    setT *ints3= NULL;
    +    int i,j;
    +
    +    qh_fprintf(stderr, 8021, "\n\nTesting qh_settemp* 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                qh_settemppush(ints);
    +                ints3= qh_settemppop();
    +                if(ints!=ints3){
    +                    qh_fprintf(stderr, 6343, "qh_settemppop: didn't pop the push\n");
    +                    error_count++;
    +                }
    +            }
    +            qh_settempfree(&ints2);
    +        }
    +    }
    +    qh_setfreelong(&ints);
    +    if(ints){
    +        qh_setfree(&ints); /* Was quick memory */
    +    }
    +}/*testSettemp*/
    +
    +/* Check that a set contains count elements
    +   Ranges are consecutive (e.g., 1,2,3,...) starting with first, mid, and last
    +   Use -1 for missing ranges
    +   Returns -1 if should check results
    +*/
    +int log_i(setT *set, const char *s, int i, int numInts, int checkEvery)
    +{
    +    int j= i;
    +    int scale= 1;
    +    int e= 0;
    +    int *i2, **i2p;
    +
    +    if(*s || checkEvery==1){
    +        if(i<10){
    +            qh_fprintf(stderr, 8004, " %s%d", s, i);
    +        }else{
    +            if(i==11 && checkEvery==1){
    +                qh_fprintf(stderr, 8005, "\nResults after 10: ");
    +                FOREACHint_(set){
    +                    qh_fprintf(stderr, 8006, " %d", *i2);
    +                }
    +                qh_fprintf(stderr, 8007, " Continue");
    +            }
    +            while((j= j/10)>=1){
    +                scale *= 10;
    +                e++;
    +            }
    +            if(i==numInts-1){
    +                qh_fprintf(stderr, 8008, " %s%d", s, i);
    +            }else if(i==scale){
    +                if(i<=1000){
    +                    qh_fprintf(stderr, 8010, " %s%d", s, i);
    +                }else{
    +                    qh_fprintf(stderr, 8009, " %s1e%d", s, e);
    +                }
    +            }
    +        }
    +    }
    +    if(i<1000 || i%checkEvery==0 || i== scale || i==numInts-1){
    +        return 1;
    +    }
    +    return 0;
    +}/*log_i*/
    +
    +/* Check that a set contains count elements
    +   Ranges are consecutive (e.g., 1,2,3,...) starting with first, mid, and last
    +   Use -1 for missing ranges
    +*/
    +void checkSetContents(const char *name, setT *set, int count, int rangeA, int rangeB, int rangeC)
    +{
    +
    +    i2T *i2, **i2p;
    +    int i2_i, i2_n;
    +    int prev= -1; /* avoid warning */
    +    int i;
    +    int first= -3;
    +    int second= -3;
    +    int rangeCount=1;
    +    int actualSize= 0;
    +
    +    qh_setcheck(set, name, 0);
    +    if(set){
    +        SETreturnsize_(set, actualSize);  /* normally used only when speed is critical */
    +        if(*qh_setendpointer(set)!=NULL){
    +            qh_fprintf(stderr, 6344, "%s: qh_setendpointer(), 0x%x, is not NULL terminator of set 0x%x", name, qh_setendpointer(set), set);
    +            error_count++;
    +        }
    +    }
    +    if(actualSize!=qh_setsize(set)){
    +        qh_fprintf(stderr, 6305, "%s: SETreturnsize_() returned %d while qh_setsize() returns %d\n", name, actualSize, qh_setsize(set));
    +        error_count++;
    +    }else if(actualSize!=count){
    +        qh_fprintf(stderr, 6306, "%s: Expecting %d elements for set.  Got %d elements\n", name, count, actualSize);
    +        error_count++;
    +    }
    +    if(SETempty_(set)){
    +        if(count!=0){
    +            qh_fprintf(stderr, 6307, "%s: Got empty set instead of count %d, rangeA %d, rangeB %d, rangeC %d\n", name, count, rangeA, rangeB, rangeC);
    +            error_count++;
    +        }
    +    }else{
    +        /* Must be first, otherwise trips msvc 8 */
    +        i2T **p= SETaddr_(set, i2T);
    +        if(*p!=SETfirstt_(set, i2T)){
    +            qh_fprintf(stderr, 6309, "%s: SETaddr_(set, i2t) [%p] is not the same as SETfirst_(set) [%p]\n", name, SETaddr_(set, i2T), SETfirst_(set));
    +            error_count++;
    +        }
    +        first= *(int *)SETfirst_(set);
    +        if(SETfirst_(set)!=SETfirstt_(set, i2T)){
    +            qh_fprintf(stderr, 6308, "%s: SETfirst_(set) [%p] is not the same as SETfirstt_(set, i2T [%p]\n", name, SETfirst_(set), SETfirstt_(set, i2T));
    +            error_count++;
    +        }
    +        if(qh_setsize(set)>1){
    +            second= *(int *)SETsecond_(set);
    +            if(SETsecond_(set)!=SETsecondt_(set, i2T)){
    +                qh_fprintf(stderr, 6310, "%s: SETsecond_(set) [%p] is not the same as SETsecondt_(set, i2T) [%p]\n", name, SETsecond_(set), SETsecondt_(set, i2T));
    +                error_count++;
    +            }
    +        }
    +    }
    +    /* Test first run of ints in set*/
    +    i= 0;
    +    FOREACHint_(set){
    +        if(i2!=SETfirst_(set) && *i2!=prev+1){
    +            break;
    +        }
    +        prev= *i2;
    +        if(SETindex_(set, i2)!=i){
    +            qh_fprintf(stderr, 6311, "%s: Expecting SETIndex_(set, pointer-to-%d) to be %d.  Got %d\n", name, *i2, i, SETindex_(set, i2));
    +            error_count++;;
    +        }
    +        if(i2!=SETref_(i2)){
    +            qh_fprintf(stderr, 6312, "%s: SETref_(i2) [%p] does not point to i2 (the %d'th element)\n", name, SETref_(i2), i);
    +            error_count++;;
    +        }
    +        i++;
    +    }
    +    FOREACHint_i_(set){
    +        /* Must be first conditional, otherwise it trips up msvc 8 */
    +        i2T **p= SETelemaddr_(set, i2_i, i2T);
    +        if(i2!=*p){
    +            qh_fprintf(stderr, 6320, "%s: SETelemaddr_(set, %d, i2T) [%p] does not point to i2\n", name, i2_i, SETelemaddr_(set, i2_i, int));
    +            error_count++;;
    +        }
    +        if(i2_i==0){
    +            if(first!=*i2){
    +                qh_fprintf(stderr, 6314, "%s: First element is %d instead of SETfirst %d\n", name, *i2, first);
    +                error_count++;;
    +            }
    +            if(rangeA!=*i2){
    +                qh_fprintf(stderr, 6315, "%s: starts with %d instead of rangeA %d\n", name, *i2, rangeA);
    +                error_count++;;
    +            }
    +            prev= rangeA;
    +        }else{
    +            if(i2_i==1 && second!=*i2){
    +                qh_fprintf(stderr, 6316, "%s: Second element is %d instead of SETsecond %d\n", name, *i2, second);
    +                error_count++;;
    +            }
    +            if(prev+1==*i2){
    +                prev++;
    +            }else{
    +                if(*i2==rangeB){
    +                    prev= rangeB;
    +                    rangeB= -1;
    +                    rangeCount++;
    +                }else if(rangeB==-1 && *i2==rangeC){
    +                    prev= rangeC;
    +                    rangeC= -1;
    +                    rangeCount++;
    +                }else{
    +                    prev++;
    +                    qh_fprintf(stderr, 6317, "%s: Expecting %d'th element to be %d.  Got %d\n", name, i2_i, prev, *i2);
    +                    error_count++;
    +                }
    +            }
    +        }
    +        if(i2!=SETelem_(set, i2_i)){
    +            qh_fprintf(stderr, 6318, "%s: SETelem_(set, %d) [%p] is not i2 [%p] (the %d'th element)\n", name, i2_i, SETelem_(set, i2_i), i2, i2_i);
    +            error_count++;;
    +        }
    +        if(SETelemt_(set, i2_i, i2T)!=SETelem_(set, i2_i)){   /* Normally SETelemt_ is used for generic sets */
    +            qh_fprintf(stderr, 6319, "%s: SETelemt_(set, %d, i2T) [%p] is not SETelem_(set, %d) [%p] (the %d'th element)\n", name, i2_i, SETelemt_(set, i2_i, int), i2_i, SETelem_(set, i2_i), i2_i);
    +            error_count++;;
    +        }
    +    }
    +    if(error_count>=MAXerrorCount){
    +        qh_fprintf(stderr, 8011, "testqset: Stop testing after %d errors\n", error_count);
    +        exit(1);
    +    }
    +}/*checkSetContents*/
    +
    diff --git a/xs/src/qhull/src/testqset/testqset.pro b/xs/src/qhull/src/testqset/testqset.pro
    new file mode 100644
    index 0000000000..3f69048aac
    --- /dev/null
    +++ b/xs/src/qhull/src/testqset/testqset.pro
    @@ -0,0 +1,30 @@
    +# -------------------------------------------------
    +# testqset.pro -- Qt project file for testqset.exe
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +TARGET = testqset
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +CONFIG += qhull_warn_conversion
    +
    +build_pass:CONFIG(debug, debug|release){
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   OBJECTS_DIR = Release
    +}
    +
    +INCLUDEPATH += ..
    +
    +SOURCES += testqset.c
    +SOURCES += ../libqhull/qset.c
    +SOURCES += ../libqhull/mem.c
    +SOURCES += ../libqhull/usermem.c
    +
    +HEADERS += ../libqhull/mem.h
    +HEADERS += ../libqhull/qset.h
    +
    diff --git a/xs/src/qhull/src/testqset_r/testqset_r.c b/xs/src/qhull/src/testqset_r/testqset_r.c
    new file mode 100644
    index 0000000000..9a6d496e40
    --- /dev/null
    +++ b/xs/src/qhull/src/testqset_r/testqset_r.c
    @@ -0,0 +1,890 @@
    +/*
      ---------------------------------
    +
    +   testset.c -- test qset.c and its use of mem.c
    +
    +   The test sets are pointers to int.  Normally a set is a pointer to a type (e.g., facetT, ridgeT, etc.).
    +   For consistency in notation, an "int" is typedef'd to i2T
    +
    +Functions and macros from qset.h.  Counts occurrences in this test.  Does not correspond to thoroughness.
    +    qh_setaddsorted -- 4 tests
    +    qh_setaddnth -- 1 test
    +    qh_setappend -- 7 tests
    +    qh_setappend_set -- 1 test
    +    qh_setappend2ndlast -- 1 test
    +    qh_setcheck -- lots of tests
    +    qh_setcompact -- 7 tests
    +    qh_setcopy -- 3 tests
    +    qh_setdel -- 1 tests
    +    qh_setdellast -- 1 tests
    +    qh_setdelnth -- 2 tests
    +    qh_setdelnthsorted -- 2 tests
    +    qh_setdelsorted -- 1 test
    +    qh_setduplicate -- not testable here
    +    qh_setequal -- 4 tests
    +    qh_setequal_except -- 2 tests
    +    qh_setequal_skip -- 2 tests
    +    qh_setfree -- 11+ tests
    +    qh_setfree2 -- not testable here
    +    qh_setfreelong -- 2 tests
    +    qh_setin -- 3 tests
    +    qh_setindex -- 4 tests
    +    qh_setlarger -- 1 test
    +    qh_setlast -- 2 tests
    +    qh_setnew -- 6 tests
    +    qh_setnew_delnthsorted
    +    qh_setprint -- tested elsewhere
    +    qh_setreplace -- 1 test
    +    qh_setsize -- 9+ tests
    +    qh_settemp -- 2 tests
    +    qh_settempfree -- 1 test
    +    qh_settempfree_all -- 1 test
    +    qh_settemppop -- 1 test
    +    qh_settemppush -- 1 test
    +    qh_settruncate -- 3 tests
    +    qh_setunique -- 3 tests
    +    qh_setzero -- 1 test
    +    FOREACHint_ -- 2 test
    +    FOREACHint4_
    +    FOREACHint_i_ -- 1 test
    +    FOREACHintreverse_
    +    FOREACHintreverse12_
    +    FOREACHsetelement_ -- 1 test
    +    FOREACHsetelement_i_ -- 1 test
    +    FOREACHsetelementreverse_ -- 1 test
    +    FOREACHsetelementreverse12_ -- 1 test
    +    SETelem_ -- 3 tests
    +    SETelemaddr_ -- 2 tests
    +    SETelemt_ -- not tested (generic)
    +    SETempty_ -- 1 test
    +    SETfirst_ -- 4 tests
    +    SETfirstt_ -- 2 tests
    +    SETindex_ -- 2 tests
    +    SETref_ -- 2 tests
    +    SETreturnsize_ -- 2 tests
    +    SETsecond_ -- 1 test
    +    SETsecondt_ -- 2 tests
    +    SETtruncate_ -- 2 tests
    +
    +    Copyright (c) 2012-2015 C.B. Barber. All rights reserved.
    +    $Id: //main/2015/qhull/src/testqset_r/testqset_r.c#5 $$Change: 2064 $
    +    $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "libqhull_r/user_r.h"  /* QHULL_CRTDBG */
    +#include "libqhull_r/qset_r.h"
    +#include "libqhull_r/mem_r.h"
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +typedef int i2T;
    +#define MAXerrorCount 100 /* quit after n errors */
    +
    +#define FOREACHint_( ints ) FOREACHsetelement_( i2T, ints, i2)
    +#define FOREACHint4_( ints ) FOREACHsetelement_( i2T, ints, i4)
    +#define FOREACHint_i_( qh, ints ) FOREACHsetelement_i_( qh, i2T, ints, i2)
    +#define FOREACHintreverse_( qh, ints ) FOREACHsetelementreverse_( qh, i2T, ints, i2)
    +#define FOREACHintreverse12_( ints ) FOREACHsetelementreverse12_( i2T, ints, i2)
    +
    +enum {
    +    MAXint= 0x7fffffff,
    +};
    +
    +char prompt[]= "testqset_r N [M] [T5] -- Test reentrant qset_r.c and mem_r.c\n\
    +  \n\
    +  If this test fails then reentrant Qhull will not work.\n\
    +  \n\
    +  Test qsets of 0..N integers with a check every M iterations (default ~log10)\n\
    +  Additional checking and logging if M is 1\n\
    +  \n\
    +  T5 turns on memory logging (qset does not log)\n\
    +  \n\
    +  For example:\n\
    +    testqset_r 10000\n\
    +";
    +
    +int error_count= 0;  /* Global error_count.  checkSetContents(qh) keeps its own error count.  It exits on too many errors */
    +
    +/* Macros normally defined in geom.h */
    +#define fmax_( a,b )  ( ( a ) < ( b ) ? ( b ) : ( a ) )
    +
    +/* Macros normally defined in QhullSet.h */
    +
    +/* Functions normally defined in user_r.h for usermem_r.c */
    +
    +void    qh_exit(int exitcode);
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +
    +/* Normally defined in user_r.c */
    +
    +void    qh_errexit(qhT *qh, int exitcode, facetT *f, ridgeT *r)
    +{
    +    (void)f; /* unused */
    +    (void)r; /* unused */
    +    (void)qh; /* unused */
    +    qh_exit(exitcode);
    +}
    +
    +/* Normally defined in userprintf.c */
    +
    +void    qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... )
    +{
    +    static int needs_cr= 0;  /* True if qh_fprintf needs a CR. testqset_r is not itself reentrant */
    +
    +    size_t fmtlen= strlen(fmt);
    +    va_list args;
    +
    +    if (!fp) {
    +        /* Do not use qh_fprintf_stderr.  This is a standalone program */
    +        if(!qh)
    +            fprintf(stderr, "QH6241 qh_fprintf: fp and qh not defined for '%s'", fmt);
    +        else
    +            fprintf(stderr, "QH6232 qh_fprintf: fp is 0.  Was wrong qh_fprintf called for '%s'", fmt);
    +        qh_errexit(qh, 6232, NULL, NULL);
    +    }
    +    if(fmtlen>0){
    +        if(fmt[fmtlen-1]=='\n'){
    +            if(needs_cr && fmtlen>1){
    +                fprintf(fp, "\n");
    +            }
    +            needs_cr= 0;
    +        }else{
    +            needs_cr= 1;
    +        }
    +    }
    +    if(msgcode>=6000 && msgcode<7000){
    +        fprintf(fp, "Error TQ%d ", msgcode);
    +    }
    +    va_start(args, fmt);
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +}
    +
    +/* Defined below in order of use */
    +int main(int argc, char **argv);
    +void readOptions(qhT *qh, int argc, char **argv, const char *promptstr, int *numInts, int *checkEvery, int *traceLevel);
    +void setupMemory(qhT *qh, int tracelevel, int numInts, int **intarray);
    +
    +void testSetappendSettruncate(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetdelSetadd(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetappendSet(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetcompactCopy(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetequalInEtc(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSettemp(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetlastEtc(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetdelsortedEtc(qhT *qh, int numInts, int *intarray, int checkEvery);
    +
    +int log_i(qhT *qh, setT *set, const char *s, int i, int numInts, int checkEvery);
    +void checkSetContents(qhT *qh, const char *name, setT *set, int count, int rangeA, int rangeB, int rangeC);
    +
    +int main(int argc, char **argv) {
    +    int *intarray= NULL;
    +    int numInts;
    +    int checkEvery= MAXint;
    +    int curlong, totlong;
    +    int traceLevel= 4; /* 4 normally, no tracing since qset does not log.  Option 'T5' for memory tracing */
    +    qhT qh_qh;
    +    qhT *qh= &qh_qh;
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)  /* user_r.h */
    +    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) );
    +    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
    +#endif
    +
    +    readOptions(qh, argc, argv, prompt, &numInts, &checkEvery, &traceLevel);
    +    setupMemory(qh, traceLevel, numInts, &intarray);
    +
    +    testSetappendSettruncate(qh, numInts, intarray, checkEvery);
    +    testSetdelSetadd(qh, numInts, intarray, checkEvery);
    +    testSetappendSet(qh, numInts, intarray, checkEvery);
    +    testSetcompactCopy(qh, numInts, intarray, checkEvery);
    +    testSetequalInEtc(qh, numInts, intarray, checkEvery);
    +    testSettemp(qh, numInts, intarray, checkEvery);
    +    testSetlastEtc(qh, numInts, intarray, checkEvery);
    +    testSetdelsortedEtc(qh, numInts, intarray, checkEvery);
    +    printf("\n\nNot testing qh_setduplicate and qh_setfree2.\n  These routines use heap-allocated set contents.  See qhull tests.\n");
    +
    +    qh_memstatistics(qh, stdout);
    +    qh_memfreeshort(qh, &curlong, &totlong);
    +    if (curlong || totlong){
    +        qh_fprintf(qh, stderr, 8043, "qh_memfreeshort: did not free %d bytes of long memory(%d pieces)\n", totlong, curlong);
    +        error_count++;
    +    }
    +    if(error_count){
    +        qh_fprintf(qh, stderr, 8012, "testqset: %d errors\n\n", error_count);
    +        exit(1);
    +    }else{
    +        printf("testqset_r: OK\n\n");
    +    }
    +    return 0;
    +}/*main*/
    +
    +void readOptions(qhT *qh, int argc, char **argv, const char *promptstr, int *numInts, int *checkEvery, int *traceLevel)
    +{
    +    long numIntsArg;
    +    long checkEveryArg;
    +    char *endp;
    +    int isTracing= 0;
    +
    +    if (argc < 2 || argc > 4) {
    +        printf("%s", promptstr);
    +        exit(0);
    +    }
    +    numIntsArg= strtol(argv[1], &endp, 10);
    +    if(numIntsArg<1){
    +        qh_fprintf(qh, stderr, 6301, "First argument should be 1 or greater.  Got '%s'\n", argv[1]);
    +        exit(1);
    +    }
    +    if(numIntsArg>MAXint){
    +        qh_fprintf(qh, stderr, 6302, "qset does not currently support 64-bit ints.  Maximum count is %d\n", MAXint);
    +        exit(1);
    +    }
    +    *numInts= (int)numIntsArg;
    +
    +    if(argc==3 && argv[2][0]=='T' && argv[2][1]=='5' ){
    +        isTracing= 1;
    +        *traceLevel= 5;
    +    }
    +    if(argc==4 || (argc==3 && !isTracing)){
    +        checkEveryArg= strtol(argv[2], &endp, 10);
    +        if(checkEveryArg<1){
    +            qh_fprintf(qh, stderr, 6321, "checkEvery argument should be 1 or greater.  Got '%s'\n", argv[2]);
    +            exit(1);
    +        }
    +        if(checkEveryArg>MAXint){
    +            qh_fprintf(qh, stderr, 6322, "qset does not currently support 64-bit ints.  Maximum checkEvery is %d\n", MAXint);
    +            exit(1);
    +        }
    +        if(argc==4){
    +            if(argv[3][0]=='T' && argv[3][1]=='5' ){
    +                isTracing= 1;
    +                *traceLevel= 5;
    +            }else{
    +                qh_fprintf(qh, stderr, 6242, "Optional third argument must be 'T5'.  Got '%s'\n", argv[3]);
    +                exit(1);
    +            }
    +        }
    +        *checkEvery= (int)checkEveryArg;
    +    }
    +}/*readOptions*/
    +
    +void setupMemory(qhT *qh, int tracelevel, int numInts, int **intarray)
    +{
    +    int i;
    +    if(numInts<0 || numInts*(int)sizeof(int)<0){
    +        qh_fprintf(qh, stderr, 6303, "qset does not currently support 64-bit ints.  Integer overflow\n");
    +        exit(1);
    +    }
    +    *intarray= qh_malloc(numInts * sizeof(int));
    +    if(!*intarray){
    +        qh_fprintf(qh, stderr, 6304, "Failed to allocate %d bytes of memory\n", numInts * sizeof(int));
    +        exit(1);
    +    }
    +    for(i= 0; i=2){
    +        isCheck= log_i(qh, ints, "n", numInts/2, numInts, checkEvery);
    +        qh_settruncate(qh, ints, numInts/2);
    +        checkSetContents(qh, "qh_settruncate by half", ints, numInts/2, 0, -1, -1);
    +    }
    +    isCheck= log_i(qh, ints, "n", 0, numInts, checkEvery);
    +    qh_settruncate(qh, ints, 0);
    +    checkSetContents(qh, "qh_settruncate", ints, 0, -1, -1, -1);
    +
    +    qh_fprintf(qh, stderr, 8003, "\n\nTesting qh_setappend2ndlast 0,0..%d.  Test 0", numInts-1);
    +    qh_setfree(qh, &ints);
    +    ints= qh_setnew(qh, 4);
    +    qh_setappend(qh, &ints, intarray+0);
    +    for(i= 0; i=2){
    +        isCheck= log_i(qh, ints, "n", numInts/2, numInts, checkEvery);
    +        SETtruncate_(ints, numInts/2);
    +        checkSetContents(qh, "SETtruncate_ by half", ints, numInts/2, 0, -1, -1);
    +    }
    +    isCheck= log_i(qh, ints, "n", 0, numInts, checkEvery);
    +    SETtruncate_(ints, 0);
    +    checkSetContents(qh, "SETtruncate_", ints, 0, -1, -1, -1);
    +
    +    qh_setfree(qh, &ints);
    +}/*testSetappendSettruncate*/
    +
    +void testSetdelSetadd(qhT *qh, int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints=qh_setnew(qh, 1);
    +    int i,j,isCheck;
    +
    +    qh_fprintf(qh, stderr, 8003, "\n\nTesting qh_setdelnthsorted and qh_setaddnth 1..%d. Test", numInts-1);
    +    for(j=1; j3){
    +                qh_setdelsorted(ints, intarray+i/2);
    +                checkSetContents(qh, "qh_setdelsorted", ints, j-1, 0, i/2+1, -1);
    +                qh_setaddsorted(qh, &ints, intarray+i/2);
    +                checkSetContents(qh, "qh_setaddsorted i/2", ints, j, 0, 0, -1);
    +            }
    +            qh_setdellast(ints);
    +            checkSetContents(qh, "qh_setdellast", ints, (j ? j-1 : 0), 0, -1, -1);
    +            if(j>0){
    +                qh_setaddsorted(qh, &ints, intarray+j-1);
    +                checkSetContents(qh, "qh_setaddsorted j-1", ints, j, 0, -1, -1);
    +            }
    +            if(j>4){
    +                qh_setdelnthsorted(qh, ints, i/2);
    +                if (checkEvery==1)
    +                  checkSetContents(qh, "qh_setdelnthsorted", ints, j-1, 0, i/2+1, -1);
    +                /* test qh_setdelnth and move-to-front */
    +                qh_setdelsorted(ints, intarray+i/2+1);
    +                checkSetContents(qh, "qh_setdelsorted 2", ints, j-2, 0, i/2+2, -1);
    +                qh_setaddsorted(qh, &ints, intarray+i/2+1);
    +                if (checkEvery==1)
    +                  checkSetContents(qh, "qh_setaddsorted i/2+1", ints, j-1, 0, i/2+1, -1);
    +                qh_setaddsorted(qh, &ints, intarray+i/2);
    +                checkSetContents(qh, "qh_setaddsorted i/2 again", ints, j, 0, -1, -1);
    +            }
    +            qh_setfree(qh, &ints2);
    +            ints2= qh_setcopy(qh, ints, 0);
    +            qh_setcompact(qh, ints);
    +            qh_setcompact(qh, ints2);
    +            checkSetContents(qh, "qh_setcompact", ints, j, 0, 0, -1);
    +            checkSetContents(qh, "qh_setcompact 2", ints2, j, 0, 0, -1);
    +            qh_setcompact(qh, ints);
    +            checkSetContents(qh, "qh_setcompact 3", ints, j, 0, 0, -1);
    +            qh_setfree(qh, &ints2);
    +        }
    +    }
    +    qh_setfreelong(qh, &ints);
    +    if(ints){
    +        qh_setfree(qh, &ints); /* Was quick memory */
    +    }
    +}/*testSetdelsortedEtc*/
    +
    +void testSetequalInEtc(qhT *qh, int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    setT *ints3= NULL;
    +    int i,j,n;
    +
    +    qh_fprintf(qh, stderr, 8019, "\n\nTesting qh_setequal*, qh_setin*, qh_setdel, qh_setdelnth, and qh_setlarger 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                if(qh_setequal(ints, ints2)){
    +                    qh_fprintf(qh, stderr, 6324, "testSetequalInEtc: non-empty set equal to empty set\n", j);
    +                    error_count++;
    +                }
    +                qh_setfree(qh, &ints3);
    +                ints3= qh_setcopy(qh, ints, 0);
    +                checkSetContents(qh, "qh_setreplace", ints3, j, 0, -1, -1);
    +                qh_setreplace(qh, ints3, intarray+j/2, intarray+j/2+1);
    +                if(j==1){
    +                    checkSetContents(qh, "qh_setreplace 2", ints3, j, j/2+1, -1, -1);
    +                }else if(j==2){
    +                    checkSetContents(qh, "qh_setreplace 3", ints3, j, 0, j/2+1, -1);
    +                }else{
    +                    checkSetContents(qh, "qh_setreplace 3", ints3, j, 0, j/2+1, j/2+1);
    +                }
    +                if(qh_setequal(ints, ints3)){
    +                    qh_fprintf(qh, stderr, 6325, "testSetequalInEtc: modified set equal to original set at %d/2\n", j);
    +                    error_count++;
    +                }
    +                if(!qh_setequal_except(ints, intarray+j/2, ints3, intarray+j/2+1)){
    +                    qh_fprintf(qh, stderr, 6326, "qh_setequal_except: modified set not equal to original set except modified\n", j);
    +                    error_count++;
    +                }
    +                if(qh_setequal_except(ints, intarray+j/2, ints3, intarray)){
    +                    qh_fprintf(qh, stderr, 6327, "qh_setequal_except: modified set equal to original set with wrong excepts\n", j);
    +                    error_count++;
    +                }
    +                if(!qh_setequal_skip(ints, j/2, ints3, j/2)){
    +                    qh_fprintf(qh, stderr, 6328, "qh_setequal_skip: modified set not equal to original set except modified\n", j);
    +                    error_count++;
    +                }
    +                if(j>2 && qh_setequal_skip(ints, j/2, ints3, 0)){
    +                    qh_fprintf(qh, stderr, 6329, "qh_setequal_skip: modified set equal to original set with wrong excepts\n", j);
    +                    error_count++;
    +                }
    +                if(intarray+j/2+1!=qh_setdel(ints3, intarray+j/2+1)){
    +                    qh_fprintf(qh, stderr, 6330, "qh_setdel: failed to find added element\n", j);
    +                    error_count++;
    +                }
    +                checkSetContents(qh, "qh_setdel", ints3, j-1, 0, j-1, (j==1 ? -1 : j/2+1));  /* swaps last element with deleted element */
    +                if(j>3){
    +                    qh_setdelnth(qh, ints3, j/2); /* Delete at the same location as the original replace, for only one out-of-order element */
    +                    checkSetContents(qh, "qh_setdelnth", ints3, j-2, 0, j-2, (j==2 ? -1 : j/2+1));
    +                }
    +                if(qh_setin(ints3, intarray+j/2)){
    +                    qh_fprintf(qh, stderr, 6331, "qh_setin: found deleted element\n");
    +                    error_count++;
    +                }
    +                if(j>4 && !qh_setin(ints3, intarray+1)){
    +                    qh_fprintf(qh, stderr, 6332, "qh_setin: did not find second element\n");
    +                    error_count++;
    +                }
    +                if(j>4 && !qh_setin(ints3, intarray+j-2)){
    +                    qh_fprintf(qh, stderr, 6333, "qh_setin: did not find last element\n");
    +                    error_count++;
    +                }
    +                if(-1!=qh_setindex(ints2, intarray)){
    +                    qh_fprintf(qh, stderr, 6334, "qh_setindex: found element in empty set\n");
    +                    error_count++;
    +                }
    +                if(-1!=qh_setindex(ints3, intarray+j/2)){
    +                    qh_fprintf(qh, stderr, 6335, "qh_setindex: found deleted element in set\n");
    +                    error_count++;
    +                }
    +                if(0!=qh_setindex(ints, intarray)){
    +                    qh_fprintf(qh, stderr, 6336, "qh_setindex: did not find first in set\n");
    +                    error_count++;
    +                }
    +                if(j-1!=qh_setindex(ints, intarray+j-1)){
    +                    qh_fprintf(qh, stderr, 6337, "qh_setindex: did not find last in set\n");
    +                    error_count++;
    +                }
    +            }
    +            qh_setfree(qh, &ints2);
    +        }
    +    }
    +    qh_setfree(qh, &ints3);
    +    qh_setfreelong(qh, &ints);
    +    if(ints){
    +        qh_setfree(qh, &ints); /* Was quick memory */
    +    }
    +}/*testSetequalInEtc*/
    +
    +
    +void testSetlastEtc(qhT *qh, int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    int i,j,prepend;
    +
    +    qh_fprintf(qh, stderr, 8020, "\n\nTesting qh_setlast, qh_setnew_delnthsorted, qh_setunique, and qh_setzero 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                if(intarray+j-1!=qh_setlast(ints)){
    +                    qh_fprintf(qh, stderr, 6338, "qh_setlast: wrong last element\n");
    +                    error_count++;
    +                }
    +                prepend= (j<100 ? j/4 : 0);
    +                ints2= qh_setnew_delnthsorted(qh, ints, qh_setsize(qh, ints), j/2, prepend);
    +                if(qh_setsize(qh, ints2)!=j+prepend-1){
    +                    qh_fprintf(qh, stderr, 6345, "qh_setnew_delnthsorted: Expecting %d elements, got %d\n", j+prepend-1, qh_setsize(qh, ints2));
    +                    error_count++;
    +                }
    +                /* Define prepended elements.  Otherwise qh_setdelnthsorted may fail */
    +                for(i= 0; i2){
    +                    qh_setzero(qh, ints2, j/2, j-1);  /* max size may be j-1 */
    +                    if(qh_setsize(qh, ints2)!=j-1){
    +                        qh_fprintf(qh, stderr, 6342, "qh_setzero: Expecting %d elements, got %d\n", j, qh_setsize(qh, ints2));
    +                        error_count++;
    +                    }
    +                    qh_setcompact(qh, ints2);
    +                    checkSetContents(qh, "qh_setzero", ints2, j/2, 0, -1, -1);
    +                }
    +            }
    +            qh_setfree(qh, &ints2);
    +        }
    +    }
    +    qh_setfreelong(qh, &ints);
    +    if(ints){
    +        qh_setfree(qh, &ints); /* Was quick memory */
    +    }
    +}/*testSetlastEtc*/
    +
    +void testSettemp(qhT *qh, int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    setT *ints3= NULL;
    +    int i,j;
    +
    +    qh_fprintf(qh, stderr, 8021, "\n\nTesting qh_settemp* 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                qh_settemppush(qh, ints);
    +                ints3= qh_settemppop(qh);
    +                if(ints!=ints3){
    +                    qh_fprintf(qh, stderr, 6343, "qh_settemppop: didn't pop the push\n");
    +                    error_count++;
    +                }
    +            }
    +            qh_settempfree(qh, &ints2);
    +        }
    +    }
    +    qh_setfreelong(qh, &ints);
    +    if(ints){
    +        qh_setfree(qh, &ints); /* Was quick memory */
    +    }
    +}/*testSettemp*/
    +
    +/* Check that a set contains count elements
    +   Ranges are consecutive (e.g., 1,2,3,...) starting with first, mid, and last
    +   Use -1 for missing ranges
    +   Returns -1 if should check results
    +*/
    +int log_i(qhT *qh, setT *set, const char *s, int i, int numInts, int checkEvery)
    +{
    +    int j= i;
    +    int scale= 1;
    +    int e= 0;
    +    int *i2, **i2p;
    +
    +    if(*s || checkEvery==1){
    +        if(i<10){
    +            qh_fprintf(qh, stderr, 8004, " %s%d", s, i);
    +        }else{
    +            if(i==11 && checkEvery==1){
    +                qh_fprintf(qh, stderr, 8005, "\nResults after 10: ");
    +                FOREACHint_(set){
    +                    qh_fprintf(qh, stderr, 8006, " %d", *i2);
    +                }
    +                qh_fprintf(qh, stderr, 8007, " Continue");
    +            }
    +            while((j= j/10)>=1){
    +                scale *= 10;
    +                e++;
    +            }
    +            if(i==numInts-1){
    +                qh_fprintf(qh, stderr, 8008, " %s%d", s, i);
    +            }else if(i==scale){
    +                if(i<=1000){
    +                    qh_fprintf(qh, stderr, 8010, " %s%d", s, i);
    +                }else{
    +                    qh_fprintf(qh, stderr, 8009, " %s1e%d", s, e);
    +                }
    +            }
    +        }
    +    }
    +    if(i<1000 || i%checkEvery==0 || i== scale || i==numInts-1){
    +        return 1;
    +    }
    +    return 0;
    +}/*log_i*/
    +
    +/* Check that a set contains count elements
    +   Ranges are consecutive (e.g., 1,2,3,...) starting with first, mid, and last
    +   Use -1 for missing ranges
    +*/
    +void checkSetContents(qhT *qh, const char *name, setT *set, int count, int rangeA, int rangeB, int rangeC)
    +{
    +
    +    i2T *i2, **i2p;
    +    int i2_i, i2_n;
    +    int prev= -1; /* avoid warning */
    +    int i;
    +    int first= -3;
    +    int second= -3;
    +    int rangeCount=1;
    +    int actualSize= 0;
    +
    +    qh_setcheck(qh, set, name, 0);
    +    if(set){
    +        SETreturnsize_(set, actualSize);  /* normally used only when speed is critical */
    +        if(*qh_setendpointer(set)!=NULL){
    +            qh_fprintf(qh, stderr, 6344, "%s: qh_setendpointer(set), 0x%x, is not NULL terminator of set 0x%x", name, qh_setendpointer(set), set);
    +            error_count++;
    +        }
    +    }
    +    if(actualSize!=qh_setsize(qh, set)){
    +        qh_fprintf(qh, stderr, 6305, "%s: SETreturnsize_(qh) returned %d while qh_setsize(qh) returns %d\n", name, actualSize, qh_setsize(qh, set));
    +        error_count++;
    +    }else if(actualSize!=count){
    +        qh_fprintf(qh, stderr, 6306, "%s: Expecting %d elements for set.  Got %d elements\n", name, count, actualSize);
    +        error_count++;
    +    }
    +    if(SETempty_(set)){
    +        if(count!=0){
    +            qh_fprintf(qh, stderr, 6307, "%s: Got empty set instead of count %d, rangeA %d, rangeB %d, rangeC %d\n", name, count, rangeA, rangeB, rangeC);
    +            error_count++;
    +        }
    +    }else{
    +        /* Must be first, otherwise trips msvc 8 */
    +        i2T **p= SETaddr_(set, i2T);
    +        if(*p!=SETfirstt_(set, i2T)){
    +            qh_fprintf(qh, stderr, 6309, "%s: SETaddr_(set, i2t) [%p] is not the same as SETfirst_(set) [%p]\n", name, SETaddr_(set, i2T), SETfirst_(set));
    +            error_count++;
    +        }
    +        first= *(int *)SETfirst_(set);
    +        if(SETfirst_(set)!=SETfirstt_(set, i2T)){
    +            qh_fprintf(qh, stderr, 6308, "%s: SETfirst_(set) [%p] is not the same as SETfirstt_(set, i2T [%p]\n", name, SETfirst_(set), SETfirstt_(set, i2T));
    +            error_count++;
    +        }
    +        if(qh_setsize(qh, set)>1){
    +            second= *(int *)SETsecond_(set);
    +            if(SETsecond_(set)!=SETsecondt_(set, i2T)){
    +                qh_fprintf(qh, stderr, 6310, "%s: SETsecond_(set) [%p] is not the same as SETsecondt_(set, i2T) [%p]\n", name, SETsecond_(set), SETsecondt_(set, i2T));
    +                error_count++;
    +            }
    +        }
    +    }
    +    /* Test first run of ints in set*/
    +    i= 0;
    +    FOREACHint_(set){
    +        if(i2!=SETfirst_(set) && *i2!=prev+1){
    +            break;
    +        }
    +        prev= *i2;
    +        if(SETindex_(set, i2)!=i){
    +            qh_fprintf(qh, stderr, 6311, "%s: Expecting SETindex_(set, pointer-to-%d) to be %d.  Got %d\n", name, *i2, i, SETindex_(set, i2));
    +            error_count++;;
    +        }
    +        if(i2!=SETref_(i2)){
    +            qh_fprintf(qh, stderr, 6312, "%s: SETref_(i2) [%p] does not point to i2 (the %d'th element)\n", name, SETref_(i2), i);
    +            error_count++;;
    +        }
    +        i++;
    +    }
    +    FOREACHint_i_(qh, set){
    +        /* Must be first conditional, otherwise it trips up msvc 8 */
    +        i2T **p= SETelemaddr_(set, i2_i, i2T);
    +        if(i2!=*p){
    +            qh_fprintf(qh, stderr, 6320, "%s: SETelemaddr_(set, %d, i2T) [%p] does not point to i2\n", name, i2_i, SETelemaddr_(set, i2_i, int));
    +            error_count++;;
    +        }
    +        if(i2_i==0){
    +            if(first!=*i2){
    +                qh_fprintf(qh, stderr, 6314, "%s: First element is %d instead of SETfirst %d\n", name, *i2, first);
    +                error_count++;;
    +            }
    +            if(rangeA!=*i2){
    +                qh_fprintf(qh, stderr, 6315, "%s: starts with %d instead of rangeA %d\n", name, *i2, rangeA);
    +                error_count++;;
    +            }
    +            prev= rangeA;
    +        }else{
    +            if(i2_i==1 && second!=*i2){
    +                qh_fprintf(qh, stderr, 6316, "%s: Second element is %d instead of SETsecond %d\n", name, *i2, second);
    +                error_count++;;
    +            }
    +            if(prev+1==*i2){
    +                prev++;
    +            }else{
    +                if(*i2==rangeB){
    +                    prev= rangeB;
    +                    rangeB= -1;
    +                    rangeCount++;
    +                }else if(rangeB==-1 && *i2==rangeC){
    +                    prev= rangeC;
    +                    rangeC= -1;
    +                    rangeCount++;
    +                }else{
    +                    prev++;
    +                    qh_fprintf(qh, stderr, 6317, "%s: Expecting %d'th element to be %d.  Got %d\n", name, i2_i, prev, *i2);
    +                    error_count++;
    +                }
    +            }
    +        }
    +        if(i2!=SETelem_(set, i2_i)){
    +            qh_fprintf(qh, stderr, 6318, "%s: SETelem_(set, %d) [%p] is not i2 [%p] (the %d'th element)\n", name, i2_i, SETelem_(set, i2_i), i2, i2_i);
    +            error_count++;;
    +        }
    +        if(SETelemt_(set, i2_i, i2T)!=SETelem_(set, i2_i)){   /* Normally SETelemt_ is used for generic sets */
    +            qh_fprintf(qh, stderr, 6319, "%s: SETelemt_(set, %d, i2T) [%p] is not SETelem_(set, %d) [%p] (the %d'th element)\n", name, i2_i, SETelemt_(set, i2_i, int), i2_i, SETelem_(set, i2_i), i2_i);
    +            error_count++;;
    +        }
    +    }
    +    if(error_count>=MAXerrorCount){
    +        qh_fprintf(qh, stderr, 8011, "testqset: Stop testing after %d errors\n", error_count);
    +        exit(1);
    +    }
    +}/*checkSetContents*/
    +
    diff --git a/xs/src/qhull/src/testqset_r/testqset_r.pro b/xs/src/qhull/src/testqset_r/testqset_r.pro
    new file mode 100644
    index 0000000000..951e0624e8
    --- /dev/null
    +++ b/xs/src/qhull/src/testqset_r/testqset_r.pro
    @@ -0,0 +1,30 @@
    +# -------------------------------------------------
    +# testqset_r.pro -- Qt project file for testqset_r.exe
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +TARGET = testqset_r
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +CONFIG += qhull_warn_conversion
    +
    +build_pass:CONFIG(debug, debug|release){
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   OBJECTS_DIR = Release
    +}
    +
    +INCLUDEPATH += ..
    +
    +SOURCES += testqset_r.c
    +SOURCES += ../libqhull_r/qset_r.c
    +SOURCES += ../libqhull_r/mem_r.c
    +SOURCES += ../libqhull_r/usermem_r.c
    +
    +HEADERS += ../libqhull_r/mem_r.h
    +HEADERS += ../libqhull_r/qset_r.h
    +
    diff --git a/xs/src/qhull/src/user_eg/user_eg.c b/xs/src/qhull/src/user_eg/user_eg.c
    new file mode 100644
    index 0000000000..9c5fee51b3
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg/user_eg.c
    @@ -0,0 +1,330 @@
    +/*
      ---------------------------------
    +
    +  user_eg.c
    +  sample code for calling qhull() from an application
    +
    +  call with:
    +
    +     user_eg "cube/diamond options" "delaunay options" "halfspace options"
    +
    +  for example:
    +
    +     user_eg                             # return summaries
    +
    +     user_eg "n" "o" "Fp"                # return normals, OFF, points
    +
    +     user_eg "n Qt" "o" "Fp"             # triangulated cube
    +
    +     user_eg "QR0 p" "QR0 v p" "QR0 Fp"  # rotate input and return points
    +                                         # 'v' returns Voronoi
    +                                         # transform is rotated for halfspaces
    +
    +   main() makes three runs of qhull.
    +
    +     1) compute the convex hull of a cube
    +
    +     2a) compute the Delaunay triangulation of random points
    +
    +     2b) find the Delaunay triangle closest to a point.
    +
    +     3) compute the halfspace intersection of a diamond
    +
    + notes:
    +
    +   For another example, see main() in unix.c and user_eg2.c.
    +   These examples, call qh_qhull() directly.  They allow
    +   tighter control on the code loaded with Qhull.
    +
    +   For a C++ example, see user_eg3/user_eg3_r.cpp
    +
    +   Summaries are sent to stderr if other output formats are used
    +
    +   compiled by 'make bin/user_eg'
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +*/
    +
    +#define qh_QHimport
    +#include "libqhull/qhull_a.h"
    +
    +/*-------------------------------------------------
    +-internal function prototypes
    +*/
    +void print_summary(void);
    +void makecube(coordT *points, int numpoints, int dim);
    +void makeDelaunay(coordT *points, int numpoints, int dim, int seed);
    +void findDelaunay(int dim);
    +void makehalf(coordT *points, int numpoints, int dim);
    +
    +/*-------------------------------------------------
    +-print_summary()
    +*/
    +void print_summary(void) {
    +  facetT *facet;
    +  int k;
    +
    +  printf("\n%d vertices and %d facets with normals:\n",
    +                 qh num_vertices, qh num_facets);
    +  FORALLfacets {
    +    for (k=0; k < qh hull_dim; k++)
    +      printf("%6.2g ", facet->normal[k]);
    +    printf("\n");
    +  }
    +}
    +
    +/*--------------------------------------------------
    +-makecube- set points to vertices of cube
    +  points is numpoints X dim
    +*/
    +void makecube(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jlocate a facet with qh_findbestfacet()
    +*/
    +void findDelaunay(int dim) {
    +  int k;
    +  coordT point[ 100];
    +  boolT isoutside;
    +  realT bestdist;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  for (k= 0; k < dim; k++)
    +    point[k]= 0.5;
    +  qh_setdelaunay(dim+1, 1, point);
    +  facet= qh_findbestfacet(point, qh_ALL, &bestdist, &isoutside);
    +  if (facet->tricoplanar) {
    +    fprintf(stderr, "findDelaunay: not implemented for triangulated, non-simplicial Delaunay regions (tricoplanar facet, f%d).\n",
    +       facet->id);
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    for (k=0; k < dim; k++)
    +      printf("%5.2f ", vertex->point[k]);
    +    printf("\n");
    +  }
    +} /*.findDelaunay.*/
    +
    +/*--------------------------------------------------
    +-makehalf- set points to halfspaces for a (dim)-dimensional diamond
    +  points is numpoints X dim+1
    +
    +  each halfspace consists of dim coefficients followed by an offset
    +*/
    +void makehalf(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; j&1'\n\n");
    +
    +#if qh_QHpointer  /* see user.h */
    +  if (qh_qh){
    +      printf("QH6233: Qhull link error.  The global variable qh_qh was not initialized\n\
    +to NULL by global.c.  Please compile user_eg.c with -Dqh_QHpointer_dllimport\n\
    +as well as -Dqh_QHpointer, or use libqhullstatic, or use a different tool chain.\n\n");
    +      return -1;
    +  }
    +#endif
    +
    +  /*
    +    Run 1: convex hull
    +  */
    +  printf( "\ncompute convex hull of cube after rotating input\n");
    +  sprintf(flags, "qhull s Tcv %s", argc >= 2 ? argv[1] : "");
    +  numpoints= SIZEcube;
    +  makecube(points, numpoints, DIM);
    +  for (i=numpoints; i--; )
    +    rows[i]= points+dim*i;
    +  qh_printmatrix(outfile, "input", rows, numpoints, dim);
    +  exitcode= qh_new_qhull(dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh facet_list' contains the convex hull */
    +    print_summary();
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +  }
    +  qh_freeqhull(!qh_ALL);                   /* free long memory  */
    +  qh_memfreeshort(&curlong, &totlong);    /* free short memory and memory allocator */
    +  if (curlong || totlong)
    +    fprintf(errfile, "qhull internal warning (user_eg, #1): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +  /*
    +    Run 2: Delaunay triangulation
    +  */
    +
    +  printf( "\ncompute %d-d Delaunay triangulation\n", dim);
    +  sprintf(flags, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +  numpoints= SIZEcube;
    +  makeDelaunay(points, numpoints, dim, (int)time(NULL));
    +  for (i=numpoints; i--; )
    +    rows[i]= points+dim*i;
    +  qh_printmatrix(outfile, "input", rows, numpoints, dim);
    +  exitcode= qh_new_qhull(dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh facet_list' contains the convex hull */
    +    /* If you want a Voronoi diagram ('v') and do not request output (i.e., outfile=NULL),
    +       call qh_setvoronoi_all() after qh_new_qhull(). */
    +    print_summary();
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +    printf( "\nfind %d-d Delaunay triangle closest to [0.5, 0.5, ...]\n", dim);
    +    exitcode= setjmp(qh errexit);
    +    if (!exitcode) {
    +      /* Trap Qhull errors in findDelaunay().  Without the setjmp(), Qhull
    +         will exit() after reporting an error */
    +      qh NOerrexit= False;
    +      findDelaunay(DIM);
    +    }
    +    qh NOerrexit= True;
    +  }
    +#if qh_QHpointer  /* see user.h */
    +  {
    +    qhT *oldqhA, *oldqhB;
    +    coordT pointsB[DIM*TOTpoints]; /* array of coordinates for each point */
    +
    +    printf( "\nsave first triangulation and compute a new triangulation\n");
    +    oldqhA= qh_save_qhull();
    +    sprintf(flags, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +    numpoints= SIZEcube;
    +    makeDelaunay(pointsB, numpoints, dim, (int)time(NULL)+1);
    +    for (i=numpoints; i--; )
    +      rows[i]= pointsB+dim*i;
    +    qh_printmatrix(outfile, "input", rows, numpoints, dim);
    +    exitcode= qh_new_qhull(dim, numpoints, pointsB, ismalloc,
    +                      flags, outfile, errfile);
    +    if (!exitcode)
    +      print_summary();
    +    printf( "\nsave second triangulation and restore first one\n");
    +    oldqhB= qh_save_qhull();
    +    qh_restore_qhull(&oldqhA);
    +    print_summary();
    +    printf( "\nfree first triangulation and restore second one.\n");
    +    qh_freeqhull(qh_ALL);               /* free short and long memory used by first call */
    +                                         /* do not use qh_memfreeshort */
    +    qh_restore_qhull(&oldqhB);
    +    print_summary();
    +  }
    +#endif
    +  qh_freeqhull(!qh_ALL);                 /* free long memory */
    +  qh_memfreeshort(&curlong, &totlong);  /* free short memory and memory allocator */
    +  if (curlong || totlong)
    +    fprintf(errfile, "qhull internal warning (user_eg, #2): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +  /*
    +    Run 3: halfspace intersection about the origin
    +  */
    +  printf( "\ncompute halfspace intersection about the origin for a diamond\n");
    +  sprintf(flags, "qhull H0 s Tcv %s", argc >= 4 ? argv[3] : "Fp");
    +  numpoints= SIZEcube;
    +  makehalf(points, numpoints, dim);
    +  for (i=numpoints; i--; )
    +    rows[i]= points+(dim+1)*i;
    +  qh_printmatrix(outfile, "input as halfspace coefficients + offsets", rows, numpoints, dim+1);
    +  /* use qh_sethalfspace_all to transform the halfspaces yourself.
    +     If so, set 'qh feasible_point and do not use option 'Hn,...' [it would retransform the halfspaces]
    +  */
    +  exitcode= qh_new_qhull(dim+1, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode)
    +    print_summary();
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)  /* could also check previous runs */
    +    fprintf(stderr, "qhull internal warning (user_eg, #3): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/user_eg/user_eg.pro b/xs/src/qhull/src/user_eg/user_eg.pro
    new file mode 100644
    index 0000000000..9dda010099
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg/user_eg.pro
    @@ -0,0 +1,11 @@
    +# -------------------------------------------------
    +# user_eg.pro -- Qt project for Qhull demonstration using shared Qhull library
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-app-shared_r.pri)
    +
    +TARGET = user_eg
    +
    +SOURCES += user_eg_r.c
    diff --git a/xs/src/qhull/src/user_eg/user_eg_r.c b/xs/src/qhull/src/user_eg/user_eg_r.c
    new file mode 100644
    index 0000000000..21b0ccf4e9
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg/user_eg_r.c
    @@ -0,0 +1,326 @@
    +/*
      ---------------------------------
    +
    +  user_eg_r.c
    +  sample code for calling qhull() from an application.  Uses reentrant libqhull_r
    +
    +  call with:
    +
    +     user_eg "cube/diamond options" "delaunay options" "halfspace options"
    +
    +  for example:
    +
    +     user_eg                             # return summaries
    +
    +     user_eg "n" "o" "Fp"                # return normals, OFF, points
    +
    +     user_eg "n Qt" "o" "Fp"             # triangulated cube
    +
    +     user_eg "QR0 p" "QR0 v p" "QR0 Fp"  # rotate input and return points
    +                                         # 'v' returns Voronoi
    +                                         # transform is rotated for halfspaces
    +
    +   main() makes three runs of qhull.
    +
    +     1) compute the convex hull of a cube
    +
    +     2a) compute the Delaunay triangulation of random points
    +
    +     2b) find the Delaunay triangle closest to a point.
    +
    +     3) compute the halfspace intersection of a diamond
    +
    + notes:
    +
    +   For another example, see main() in unix_r.c and user_eg2_r.c.
    +   These examples, call qh_qhull() directly.  They allow
    +   tighter control on the code loaded with Qhull.
    +
    +   For a C++ example, see user_eg3/user_eg3_r.cpp
    +
    +   Summaries are sent to stderr if other output formats are used
    +
    +   compiled by 'make bin/user_eg'
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +*/
    +
    +#define qh_QHimport
    +#include "libqhull_r/qhull_ra.h"
    +
    +/*-------------------------------------------------
    +-internal function prototypes
    +*/
    +void print_summary(qhT *qh);
    +void makecube(coordT *points, int numpoints, int dim);
    +void makeDelaunay(qhT *qh, coordT *points, int numpoints, int dim, int seed);
    +void findDelaunay(qhT *qh, int dim);
    +void makehalf(coordT *points, int numpoints, int dim);
    +
    +/*-------------------------------------------------
    +-print_summary(qh)
    +*/
    +void print_summary(qhT *qh) {
    +  facetT *facet;
    +  int k;
    +
    +  printf("\n%d vertices and %d facets with normals:\n",
    +                 qh->num_vertices, qh->num_facets);
    +  FORALLfacets {
    +    for (k=0; k < qh->hull_dim; k++)
    +      printf("%6.2g ", facet->normal[k]);
    +    printf("\n");
    +  }
    +}
    +
    +/*--------------------------------------------------
    +-makecube- set points to vertices of cube
    +  points is numpoints X dim
    +*/
    +void makecube(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jlocate a facet with qh_findbestfacet()
    +*/
    +void findDelaunay(qhT *qh, int dim) {
    +  int k;
    +  coordT point[ 100];
    +  boolT isoutside;
    +  realT bestdist;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  for (k= 0; k < dim; k++)
    +    point[k]= 0.5;
    +  qh_setdelaunay(qh, dim+1, 1, point);
    +  facet= qh_findbestfacet(qh, point, qh_ALL, &bestdist, &isoutside);
    +  if (facet->tricoplanar) {
    +    fprintf(stderr, "findDelaunay: not implemented for triangulated, non-simplicial Delaunay regions (tricoplanar facet, f%d).\n",
    +       facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    for (k=0; k < dim; k++)
    +      printf("%5.2f ", vertex->point[k]);
    +    printf("\n");
    +  }
    +} /*.findDelaunay.*/
    +
    +/*--------------------------------------------------
    +-makehalf- set points to halfspaces for a (dim)-dimensional diamond
    +  points is numpoints X dim+1
    +
    +  each halfspace consists of dim coefficients followed by an offset
    +*/
    +void makehalf(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; j&1'\n\n");
    +
    +  /*
    +    Run 1: convex hull
    +  */
    +  printf( "\ncompute convex hull of cube after rotating input\n");
    +  sprintf(flags, "qhull s Tcv %s", argc >= 2 ? argv[1] : "");
    +  numpoints= SIZEcube;
    +  makecube(points, numpoints, DIM);
    +  for (i=numpoints; i--; )
    +    rows[i]= points+dim*i;
    +  qh_printmatrix(qh, outfile, "input", rows, numpoints, dim);
    +  exitcode= qh_new_qhull(qh, dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh->facet_list' contains the convex hull */
    +    print_summary(qh);
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +  }
    +  qh_freeqhull(qh, !qh_ALL);                   /* free long memory  */
    +  qh_memfreeshort(qh, &curlong, &totlong);    /* free short memory and memory allocator */
    +  if (curlong || totlong)
    +    fprintf(errfile, "qhull internal warning (user_eg, #1): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +  /*
    +    Run 2: Delaunay triangulation, reusing the previous qh/qh_qh
    +  */
    +
    +  printf( "\ncompute %d-d Delaunay triangulation\n", dim);
    +  sprintf(flags, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +  numpoints= SIZEcube;
    +  makeDelaunay(qh, points, numpoints, dim, (int)time(NULL));
    +  for (i=numpoints; i--; )
    +    rows[i]= points+dim*i;
    +  qh_printmatrix(qh, outfile, "input", rows, numpoints, dim);
    +  exitcode= qh_new_qhull(qh, dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh->facet_list' contains the convex hull */
    +    /* If you want a Voronoi diagram ('v') and do not request output (i.e., outfile=NULL),
    +       call qh_setvoronoi_all() after qh_new_qhull(). */
    +    print_summary(qh);
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +    printf( "\nfind %d-d Delaunay triangle closest to [0.5, 0.5, ...]\n", dim);
    +    exitcode= setjmp(qh->errexit);
    +    if (!exitcode) {
    +      /* Trap Qhull errors in findDelaunay().  Without the setjmp(), Qhull
    +         will exit() after reporting an error */
    +      qh->NOerrexit= False;
    +      findDelaunay(qh, DIM);
    +    }
    +    qh->NOerrexit= True;
    +  }
    +  {
    +    coordT pointsB[DIM*TOTpoints]; /* array of coordinates for each point */
    +
    +    qhT qh_qhB;    /* Create a new instance of Qhull (qhB) */
    +    qhT *qhB= &qh_qhB;
    +    qh_zero(qhB, errfile);
    +
    +    printf( "\nCompute a new triangulation as a separate instance of Qhull\n");
    +    sprintf(flags, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +    numpoints= SIZEcube;
    +    makeDelaunay(qhB, pointsB, numpoints, dim, (int)time(NULL)+1);
    +    for (i=numpoints; i--; )
    +      rows[i]= pointsB+dim*i;
    +    qh_printmatrix(qhB, outfile, "input", rows, numpoints, dim);
    +    exitcode= qh_new_qhull(qhB, dim, numpoints, pointsB, ismalloc,
    +                      flags, outfile, errfile);
    +    if (!exitcode)
    +      print_summary(qhB);
    +    printf( "\nFree memory allocated by the new instance of Qhull, and redisplay the old results.\n");
    +    qh_freeqhull(qhB, !qh_ALL);                 /* free long memory */
    +    qh_memfreeshort(qhB, &curlong, &totlong);  /* free short memory and memory allocator */
    +    if (curlong || totlong)
    +        fprintf(errfile, "qhull internal warning (user_eg, #4): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +    printf( "\n\n");
    +    print_summary(qh);  /* The other instance is unchanged */
    +    /* Exiting the block frees qh_qhB */
    +  }
    +  qh_freeqhull(qh, !qh_ALL);                 /* free long memory */
    +  qh_memfreeshort(qh, &curlong, &totlong);  /* free short memory and memory allocator */
    +  if (curlong || totlong)
    +    fprintf(errfile, "qhull internal warning (user_eg, #2): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +  /*
    +    Run 3: halfspace intersection about the origin
    +  */
    +  printf( "\ncompute halfspace intersection about the origin for a diamond\n");
    +  sprintf(flags, "qhull H0 s Tcv %s", argc >= 4 ? argv[3] : "Fp");
    +  numpoints= SIZEcube;
    +  makehalf(points, numpoints, dim);
    +  for (i=numpoints; i--; )
    +    rows[i]= points+(dim+1)*i;
    +  qh_printmatrix(qh, outfile, "input as halfspace coefficients + offsets", rows, numpoints, dim+1);
    +  /* use qh_sethalfspace_all to transform the halfspaces yourself.
    +     If so, set 'qh->feasible_point and do not use option 'Hn,...' [it would retransform the halfspaces]
    +  */
    +  exitcode= qh_new_qhull(qh, dim+1, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode)
    +    print_summary(qh);
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)  /* could also check previous runs */
    +    fprintf(stderr, "qhull internal warning (user_eg, #3): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/user_eg2/user_eg2.c b/xs/src/qhull/src/user_eg2/user_eg2.c
    new file mode 100644
    index 0000000000..a455f025d1
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg2/user_eg2.c
    @@ -0,0 +1,746 @@
    +/*
      ---------------------------------
    +
    +  user_eg2.c
    +
    +  sample code for calling qhull() from an application.
    +
    +  See user_eg.c for a simpler method using qh_new_qhull().
    +  The method used here and in unix.c gives you additional
    +  control over Qhull.
    +
    +  See user_eg3/user_eg3_r.cpp for a C++ example
    +
    +  call with:
    +
    +     user_eg2 "triangulated cube/diamond options" "delaunay options" "halfspace options"
    +
    +  for example:
    +
    +     user_eg2                             # return summaries
    +
    +     user_eg2 "n" "o" "Fp"                # return normals, OFF, points
    +
    +     user_eg2 "QR0 p" "QR0 v p" "QR0 Fp"  # rotate input and return points
    +                                         # 'v' returns Voronoi
    +                                         # transform is rotated for halfspaces
    +
    +   main() makes three runs of qhull.
    +
    +     1) compute the convex hull of a cube, and incrementally add a diamond
    +
    +     2a) compute the Delaunay triangulation of random points, and add points.
    +
    +     2b) find the Delaunay triangle closest to a point.
    +
    +     3) compute the halfspace intersection of a diamond, and add a cube
    +
    + notes:
    +
    +   summaries are sent to stderr if other output formats are used
    +
    +   derived from unix.c and compiled by 'make bin/user_eg2'
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +
    +   If you want to control all output to stdio and input to stdin,
    +   set the #if below to "1" and delete all lines that contain "io.c".
    +   This prevents the loading of io.o.  Qhull will
    +   still write to 'qh ferr' (stderr) for error reporting and tracing.
    +
    +   Defining #if 1, also prevents user.o from being loaded.
    +*/
    +
    +#include "libqhull/qhull_a.h"
    +
    +/*-------------------------------------------------
    +-internal function prototypes
    +*/
    +void print_summary(void);
    +void makecube(coordT *points, int numpoints, int dim);
    +void adddiamond(coordT *points, int numpoints, int numnew, int dim);
    +void makeDelaunay(coordT *points, int numpoints, int dim);
    +void addDelaunay(coordT *points, int numpoints, int numnew, int dim);
    +void findDelaunay(int dim);
    +void makehalf(coordT *points, int numpoints, int dim);
    +void addhalf(coordT *points, int numpoints, int numnew, int dim, coordT *feasible);
    +
    +/*-------------------------------------------------
    +-print_summary()
    +*/
    +void print_summary(void) {
    +  facetT *facet;
    +  int k;
    +
    +  printf("\n%d vertices and %d facets with normals:\n",
    +                 qh num_vertices, qh num_facets);
    +  FORALLfacets {
    +    for (k=0; k < qh hull_dim; k++)
    +      printf("%6.2g ", facet->normal[k]);
    +    printf("\n");
    +  }
    +}
    +
    +/*--------------------------------------------------
    +-makecube- set points to vertices of cube
    +  points is numpoints X dim
    +*/
    +void makecube(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jlocate a facet with qh_findbestfacet()
    +*/
    +void findDelaunay(int dim) {
    +  int k;
    +  coordT point[ 100];
    +  boolT isoutside;
    +  realT bestdist;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  for (k= 0; k < dim-1; k++)
    +    point[k]= 0.5;
    +  qh_setdelaunay(dim, 1, point);
    +  facet= qh_findbestfacet(point, qh_ALL, &bestdist, &isoutside);
    +  if (facet->tricoplanar) {
    +    fprintf(stderr, "findDelaunay: not implemented for triangulated, non-simplicial Delaunay regions (tricoplanar facet, f%d).\n",
    +       facet->id);
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    for (k=0; k < dim-1; k++)
    +      printf("%5.2f ", vertex->point[k]);
    +    printf("\n");
    +  }
    +} /*.findDelaunay.*/
    +
    +/*--------------------------------------------------
    +-makehalf- set points to halfspaces for a (dim)-d diamond
    +  points is numpoints X dim+1
    +
    +  each halfspace consists of dim coefficients followed by an offset
    +*/
    +void makehalf(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; j= 2 ? argv[1] : "");
    +    qh_initflags(options);
    +    printf( "\ncompute triangulated convex hull of cube after rotating input\n");
    +    makecube(array[0], SIZEcube, DIM);
    +    qh_init_B(array[0], SIZEcube, DIM, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_triangulate();  /* requires option 'Q11' if want to add points */
    +    print_summary();
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    printf( "\nadd points in a diamond\n");
    +    adddiamond(array[0], SIZEcube, SIZEdiamond, DIM);
    +    qh_check_output();
    +    print_summary();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +  }
    +  qh NOerrexit= True;
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    fprintf(stderr, "qhull warning (user_eg, run 1): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +
    +  /*
    +    Run 2: Delaunay triangulation
    +  */
    +  qh_init_A(stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM];
    +
    +    strcat(qh rbox_command, "user_eg Delaunay");
    +    sprintf(options, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +    qh_initflags(options);
    +    printf( "\ncompute %d-d Delaunay triangulation\n", DIM-1);
    +    makeDelaunay(array[0], SIZEcube, DIM);
    +    /* Instead of makeDelaunay with qh_setdelaunay, you may
    +       produce a 2-d array of points, set DIM to 2, and set
    +       qh PROJECTdelaunay to True.  qh_init_B will call
    +       qh_projectinput to project the points to the paraboloid
    +       and add a point "at-infinity".
    +    */
    +    qh_init_B(array[0], SIZEcube, DIM, ismalloc);
    +    qh_qhull();
    +    /* If you want Voronoi ('v') without qh_produce_output(), call
    +       qh_setvoronoi_all() after qh_qhull() */
    +    qh_check_output();
    +    print_summary();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    printf( "\nadd points to triangulation\n");
    +    addDelaunay(array[0], SIZEcube, SIZEdiamond, DIM);
    +    qh_check_output();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    printf( "\nfind Delaunay triangle closest to [0.5, 0.5, ...]\n");
    +    findDelaunay(DIM);
    +  }
    +  qh NOerrexit= True;
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    fprintf(stderr, "qhull warning (user_eg, run 2): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +
    +  /*
    +    Run 3: halfspace intersection
    +  */
    +  qh_init_A(stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM+1];  /* +1 for halfspace offset */
    +    pointT *points;
    +
    +    strcat(qh rbox_command, "user_eg halfspaces");
    +    sprintf(options, "qhull H0 s Tcv %s", argc >= 4 ? argv[3] : "");
    +    qh_initflags(options);
    +    printf( "\ncompute halfspace intersection about the origin for a diamond\n");
    +    makehalf(array[0], SIZEcube, DIM);
    +    qh_setfeasible(DIM); /* from io.c, sets qh feasible_point from 'Hn,n' */
    +    /* you may malloc and set qh feasible_point directly.  It is only used for
    +       option 'Fp' */
    +    points= qh_sethalfspace_all( DIM+1, SIZEcube, array[0], qh feasible_point);
    +    qh_init_B(points, SIZEcube, DIM, True); /* qh_freeqhull frees points */
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    printf( "\nadd halfspaces for cube to intersection\n");
    +    addhalf(array[0], SIZEcube, SIZEdiamond, DIM, qh feasible_point);
    +    qh_check_output();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +  }
    +  qh NOerrexit= True;
    +  qh NOerrexit= True;
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    fprintf(stderr, "qhull warning (user_eg, run 3): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +  return exitcode;
    +} /* main */
    +
    +#if 1    /* use 1 to prevent loading of io.o and user.o */
    +/*-------------------------------------------
    +-errexit- return exitcode to system after an error
    +  assumes exitcode non-zero
    +  prints useful information
    +  see qh_errexit2() in libqhull.c for 2 facets
    +*/
    +void qh_errexit(int exitcode, facetT *facet, ridgeT *ridge) {
    +  QHULL_UNUSED(facet);
    +  QHULL_UNUSED(ridge);
    +
    +  if (qh ERREXITcalled) {
    +    fprintf(qh ferr, "qhull error while processing previous error.  Exit program\n");
    +    exit(1);
    +  }
    +  qh ERREXITcalled= True;
    +  if (!qh QHULLfinished)
    +    qh hulltime= (unsigned)clock() - qh hulltime;
    +  fprintf(qh ferr, "\nWhile executing: %s | %s\n", qh rbox_command, qh qhull_command);
    +  fprintf(qh ferr, "Options selected:\n%s\n", qh qhull_options);
    +  if (qh furthest_id >= 0) {
    +    fprintf(qh ferr, "\nLast point added to hull was p%d", qh furthest_id);
    +    if (zzval_(Ztotmerge))
    +      fprintf(qh ferr, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    if (qh QHULLfinished)
    +      fprintf(qh ferr, "\nQhull has finished constructing the hull.");
    +    else if (qh POSTmerging)
    +      fprintf(qh ferr, "\nQhull has started post-merging");
    +    fprintf(qh ferr, "\n\n");
    +  }
    +  if (qh NOerrexit) {
    +    fprintf(qh ferr, "qhull error while ending program.  Exit program\n");
    +    exit(1);
    +  }
    +  if (!exitcode)
    +    exitcode= qh_ERRqhull;
    +  qh NOerrexit= True;
    +  longjmp(qh errexit, exitcode);
    +} /* errexit */
    +
    +
    +/*-------------------------------------------
    +-errprint- prints out the information of the erroneous object
    +    any parameter may be NULL, also prints neighbors and geomview output
    +*/
    +void qh_errprint(const char *string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex) {
    +
    +  fprintf(qh ferr, "%s facets f%d f%d ridge r%d vertex v%d\n",
    +           string, getid_(atfacet), getid_(otherfacet), getid_(atridge),
    +           getid_(atvertex));
    +} /* errprint */
    +
    +
    +void qh_printfacetlist(facetT *facetlist, setT *facets, boolT printall) {
    +  facetT *facet, **facetp;
    +
    +  /* remove these calls to help avoid io.c */
    +  qh_printbegin(qh ferr, qh_PRINTfacets, facetlist, facets, printall);/*io.c*/
    +  FORALLfacet_(facetlist)                                              /*io.c*/
    +    qh_printafacet(qh ferr, qh_PRINTfacets, facet, printall);          /*io.c*/
    +  FOREACHfacet_(facets)                                                /*io.c*/
    +    qh_printafacet(qh ferr, qh_PRINTfacets, facet, printall);          /*io.c*/
    +  qh_printend(qh ferr, qh_PRINTfacets, facetlist, facets, printall);  /*io.c*/
    +
    +  FORALLfacet_(facetlist)
    +    fprintf( qh ferr, "facet f%d\n", facet->id);
    +} /* printfacetlist */
    +
    +/* qh_printhelp_degenerate( fp )
    +    prints descriptive message for precision error
    +
    +  notes:
    +    no message if qh_QUICKhelp
    +*/
    +void qh_printhelp_degenerate(FILE *fp) {
    +
    +  if (qh MERGEexact || qh PREmerge || qh JOGGLEmax < REALmax/2)
    +    qh_fprintf(fp, 9368, "\n\
    +A Qhull error has occurred.  Qhull should have corrected the above\n\
    +precision error.  Please send the input and all of the output to\n\
    +qhull_bug@qhull.org\n");
    +  else if (!qh_QUICKhelp) {
    +    qh_fprintf(fp, 9369, "\n\
    +Precision problems were detected during construction of the convex hull.\n\
    +This occurs because convex hull algorithms assume that calculations are\n\
    +exact, but floating-point arithmetic has roundoff errors.\n\
    +\n\
    +To correct for precision problems, do not use 'Q0'.  By default, Qhull\n\
    +selects 'C-0' or 'Qx' and merges non-convex facets.  With option 'QJ',\n\
    +Qhull joggles the input to prevent precision problems.  See \"Imprecision\n\
    +in Qhull\" (qh-impre.htm).\n\
    +\n\
    +If you use 'Q0', the output may include\n\
    +coplanar ridges, concave ridges, and flipped facets.  In 4-d and higher,\n\
    +Qhull may produce a ridge with four neighbors or two facets with the same \n\
    +vertices.  Qhull reports these events when they occur.  It stops when a\n\
    +concave ridge, flipped facet, or duplicate facet occurs.\n");
    +#if REALfloat
    +    qh_fprintf(fp, 9370, "\
    +\n\
    +Qhull is currently using single precision arithmetic.  The following\n\
    +will probably remove the precision problems:\n\
    +  - recompile qhull for realT precision(#define REALfloat 0 in user.h).\n");
    +#endif
    +    if (qh DELAUNAY && !qh SCALElast && qh MAXabs_coord > 1e4)
    +      qh_fprintf(fp, 9371, "\
    +\n\
    +When computing the Delaunay triangulation of coordinates > 1.0,\n\
    +  - use 'Qbb' to scale the last coordinate to [0,m] (max previous coordinate)\n");
    +    if (qh DELAUNAY && !qh ATinfinity)
    +      qh_fprintf(fp, 9372, "\
    +When computing the Delaunay triangulation:\n\
    +  - use 'Qz' to add a point at-infinity.  This reduces precision problems.\n");
    +
    +    qh_fprintf(fp, 9373, "\
    +\n\
    +If you need triangular output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft'.  It triangulates non-simplicial facets with added points.\n\
    +\n\
    +If you must use 'Q0',\n\
    +try one or more of the following options.  They can not guarantee an output.\n\
    +  - use 'QbB' to scale the input to a cube.\n\
    +  - use 'Po' to produce output and prevent partitioning for flipped facets\n\
    +  - use 'V0' to set min. distance to visible facet as 0 instead of roundoff\n\
    +  - use 'En' to specify a maximum roundoff error less than %2.2g.\n\
    +  - options 'Qf', 'Qbb', and 'QR0' may also help\n",
    +               qh DISTround);
    +    qh_fprintf(fp, 9374, "\
    +\n\
    +To guarantee simplicial output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft' to triangulate the output by adding points\n\
    +  - use exact arithmetic (see \"Imprecision in Qhull\", qh-impre.htm)\n\
    +");
    +  }
    +} /* printhelp_degenerate */
    +
    +
    +/* qh_printhelp_narrowhull( minangle )
    +     Warn about a narrow hull
    +
    +  notes:
    +    Alternatively, reduce qh_WARNnarrow in user.h
    +
    +*/
    +void qh_printhelp_narrowhull(FILE *fp, realT minangle) {
    +
    +    qh_fprintf(fp, 9375, "qhull precision warning: \n\
    +The initial hull is narrow (cosine of min. angle is %.16f).\n\
    +A coplanar point may lead to a wide facet.  Options 'QbB' (scale to unit box)\n\
    +or 'Qbb' (scale last coordinate) may remove this warning.  Use 'Pp' to skip\n\
    +this warning.  See 'Limitations' in qh-impre.htm.\n",
    +          -minangle);   /* convert from angle between normals to angle between facets */
    +} /* printhelp_narrowhull */
    +
    +/* qh_printhelp_singular
    +      prints descriptive message for singular input
    +*/
    +void qh_printhelp_singular(FILE *fp) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  realT min, max, *coord, dist;
    +  int i,k;
    +
    +  qh_fprintf(fp, 9376, "\n\
    +The input to qhull appears to be less than %d dimensional, or a\n\
    +computation has overflowed.\n\n\
    +Qhull could not construct a clearly convex simplex from points:\n",
    +           qh hull_dim);
    +  qh_printvertexlist(fp, "", qh facet_list, NULL, qh_ALL);
    +  if (!qh_QUICKhelp)
    +    qh_fprintf(fp, 9377, "\n\
    +The center point is coplanar with a facet, or a vertex is coplanar\n\
    +with a neighboring facet.  The maximum round off error for\n\
    +computing distances is %2.2g.  The center point, facets and distances\n\
    +to the center point are as follows:\n\n", qh DISTround);
    +  qh_printpointid(fp, "center point", qh hull_dim, qh interior_point, -1);
    +  qh_fprintf(fp, 9378, "\n");
    +  FORALLfacets {
    +    qh_fprintf(fp, 9379, "facet");
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(fp, 9380, " p%d", qh_pointid(vertex->point));
    +    zinc_(Zdistio);
    +    qh_distplane(qh interior_point, facet, &dist);
    +    qh_fprintf(fp, 9381, " distance= %4.2g\n", dist);
    +  }
    +  if (!qh_QUICKhelp) {
    +    if (qh HALFspace)
    +      qh_fprintf(fp, 9382, "\n\
    +These points are the dual of the given halfspaces.  They indicate that\n\
    +the intersection is degenerate.\n");
    +    qh_fprintf(fp, 9383,"\n\
    +These points either have a maximum or minimum x-coordinate, or\n\
    +they maximize the determinant for k coordinates.  Trial points\n\
    +are first selected from points that maximize a coordinate.\n");
    +    if (qh hull_dim >= qh_INITIALmax)
    +      qh_fprintf(fp, 9384, "\n\
    +Because of the high dimension, the min x-coordinate and max-coordinate\n\
    +points are used if the determinant is non-zero.  Option 'Qs' will\n\
    +do a better, though much slower, job.  Instead of 'Qs', you can change\n\
    +the points by randomly rotating the input with 'QR0'.\n");
    +  }
    +  qh_fprintf(fp, 9385, "\nThe min and max coordinates for each dimension are:\n");
    +  for (k=0; k < qh hull_dim; k++) {
    +    min= REALmax;
    +    max= -REALmin;
    +    for (i=qh num_points, coord= qh first_point+k; i--; coord += qh hull_dim) {
    +      maximize_(max, *coord);
    +      minimize_(min, *coord);
    +    }
    +    qh_fprintf(fp, 9386, "  %d:  %8.4g  %8.4g  difference= %4.4g\n", k, min, max, max-min);
    +  }
    +  if (!qh_QUICKhelp) {
    +    qh_fprintf(fp, 9387, "\n\
    +If the input should be full dimensional, you have several options that\n\
    +may determine an initial simplex:\n\
    +  - use 'QJ'  to joggle the input and make it full dimensional\n\
    +  - use 'QbB' to scale the points to the unit cube\n\
    +  - use 'QR0' to randomly rotate the input for different maximum points\n\
    +  - use 'Qs'  to search all points for the initial simplex\n\
    +  - use 'En'  to specify a maximum roundoff error less than %2.2g.\n\
    +  - trace execution with 'T3' to see the determinant for each point.\n",
    +                     qh DISTround);
    +#if REALfloat
    +    qh_fprintf(fp, 9388, "\
    +  - recompile qhull for realT precision(#define REALfloat 0 in libqhull.h).\n");
    +#endif
    +    qh_fprintf(fp, 9389, "\n\
    +If the input is lower dimensional:\n\
    +  - use 'QJ' to joggle the input and make it full dimensional\n\
    +  - use 'Qbk:0Bk:0' to delete coordinate k from the input.  You should\n\
    +    pick the coordinate with the least range.  The hull will have the\n\
    +    correct topology.\n\
    +  - determine the flat containing the points, rotate the points\n\
    +    into a coordinate plane, and delete the other coordinates.\n\
    +  - add one or more points to make the input full dimensional.\n\
    +");
    +    if (qh DELAUNAY && !qh ATinfinity)
    +      qh_fprintf(fp, 9390, "\n\n\
    +This is a Delaunay triangulation and the input is co-circular or co-spherical:\n\
    +  - use 'Qz' to add a point \"at infinity\" (i.e., above the paraboloid)\n\
    +  - or use 'QJ' to joggle the input and avoid co-circular data\n");
    +  }
    +} /* printhelp_singular */
    +
    +
    +/*-----------------------------------------
    +-user_memsizes- allocate up to 10 additional, quick allocation sizes
    +*/
    +void qh_user_memsizes(void) {
    +
    +  /* qh_memsize(size); */
    +} /* user_memsizes */
    +
    +#endif
    diff --git a/xs/src/qhull/src/user_eg2/user_eg2.pro b/xs/src/qhull/src/user_eg2/user_eg2.pro
    new file mode 100644
    index 0000000000..c841bfe134
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg2/user_eg2.pro
    @@ -0,0 +1,11 @@
    +# -------------------------------------------------
    +# user_eg2.pro -- Qt project for Qhull demonstration using the static Qhull library
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-app-c_r.pri)
    +
    +TARGET = user_eg2
    +
    +SOURCES += user_eg2_r.c
    diff --git a/xs/src/qhull/src/user_eg2/user_eg2_r.c b/xs/src/qhull/src/user_eg2/user_eg2_r.c
    new file mode 100644
    index 0000000000..2f8b4e6c76
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg2/user_eg2_r.c
    @@ -0,0 +1,742 @@
    +/*
      ---------------------------------
    +
    +  user_eg2_r.c
    +
    +  sample code for calling qhull() from an application.
    +
    +  See user_eg_r.c for a simpler method using qh_new_qhull().
    +  The method used here and in unix_r.c gives you additional
    +  control over Qhull.
    +
    +  See user_eg3/user_eg3_r.cpp for a C++ example
    +
    +  call with:
    +
    +     user_eg2 "triangulated cube/diamond options" "delaunay options" "halfspace options"
    +
    +  for example:
    +
    +     user_eg2                             # return summaries
    +
    +     user_eg2 "n" "o" "Fp"                # return normals, OFF, points
    +
    +     user_eg2 "QR0 p" "QR0 v p" "QR0 Fp"  # rotate input and return points
    +                                         # 'v' returns Voronoi
    +                                         # transform is rotated for halfspaces
    +
    +   main() makes three runs of qhull.
    +
    +     1) compute the convex hull of a cube, and incrementally add a diamond
    +
    +     2a) compute the Delaunay triangulation of random points, and add points.
    +
    +     2b) find the Delaunay triangle closest to a point.
    +
    +     3) compute the halfspace intersection of a diamond, and add a cube
    +
    + notes:
    +
    +   summaries are sent to stderr if other output formats are used
    +
    +   derived from unix.c and compiled by 'make bin/user_eg2'
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +
    +   If you want to control all output to stdio and input to stdin,
    +   set the #if below to "1" and delete all lines that contain "io.c".
    +   This prevents the loading of io.o.  Qhull will
    +   still write to 'qh->ferr' (stderr) for error reporting and tracing.
    +
    +   Defining #if 1, also prevents user.o from being loaded.
    +*/
    +
    +#include "libqhull_r/qhull_ra.h"
    +
    +/*-------------------------------------------------
    +-internal function prototypes
    +*/
    +void print_summary(qhT *qh);
    +void makecube(coordT *points, int numpoints, int dim);
    +void adddiamond(qhT *qh, coordT *points, int numpoints, int numnew, int dim);
    +void makeDelaunay(qhT *qh, coordT *points, int numpoints, int dim);
    +void addDelaunay(qhT *qh, coordT *points, int numpoints, int numnew, int dim);
    +void findDelaunay(qhT *qh, int dim);
    +void makehalf(coordT *points, int numpoints, int dim);
    +void addhalf(qhT *qh, coordT *points, int numpoints, int numnew, int dim, coordT *feasible);
    +
    +/*-------------------------------------------------
    +-print_summary(qh)
    +*/
    +void print_summary(qhT *qh) {
    +  facetT *facet;
    +  int k;
    +
    +  printf("\n%d vertices and %d facets with normals:\n",
    +                 qh->num_vertices, qh->num_facets);
    +  FORALLfacets {
    +    for (k=0; k < qh->hull_dim; k++)
    +      printf("%6.2g ", facet->normal[k]);
    +    printf("\n");
    +  }
    +}
    +
    +/*--------------------------------------------------
    +-makecube- set points to vertices of cube
    +  points is numpoints X dim
    +*/
    +void makecube(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jfirst_point)  /* in case of 'QRn' */
    +      qh->num_points= numpoints+j+1;
    +    /* qh.num_points sets the size of the points array.  You may
    +       allocate the points elsewhere.  If so, qh_addpoint records
    +       the point's address in qh->other_points
    +    */
    +    for (k=dim; k--; ) {
    +      if (j/2 == k)
    +        point[k]= (j & 1) ? 2.0 : -2.0;
    +      else
    +        point[k]= 0.0;
    +    }
    +    facet= qh_findbestfacet(qh, point, !qh_ALL, &bestdist, &isoutside);
    +    if (isoutside) {
    +      if (!qh_addpoint(qh, point, facet, False))
    +        break;  /* user requested an early exit with 'TVn' or 'TCn' */
    +    }
    +    printf("%d vertices and %d facets\n",
    +                 qh->num_vertices, qh->num_facets);
    +    /* qh_produce_output(); */
    +  }
    +  if (qh->DOcheckmax)
    +    qh_check_maxout(qh);
    +  else if (qh->KEEPnearinside)
    +    qh_nearcoplanar(qh);
    +} /*.adddiamond.*/
    +
    +/*--------------------------------------------------
    +-makeDelaunay- set points for dim-1 Delaunay triangulation of random points
    +  points is numpoints X dim.  Each point is projected to a paraboloid.
    +*/
    +void makeDelaunay(qhT *qh, coordT *points, int numpoints, int dim) {
    +  int j,k, seed;
    +  coordT *point, realr;
    +
    +  seed= (int)time(NULL); /* time_t to int */
    +  printf("seed: %d\n", seed);
    +  qh_RANDOMseed_(qh, seed);
    +  for (j=0; jfirst_point)  /* in case of 'QRn' */
    +      qh->num_points= numpoints+j+1;
    +    /* qh.num_points sets the size of the points array.  You may
    +       allocate the point elsewhere.  If so, qh_addpoint records
    +       the point's address in qh->other_points
    +    */
    +    for (k= 0; k < dim-1; k++) {
    +      realr= qh_RANDOMint;
    +      point[k]= 2.0 * realr/(qh_RANDOMmax+1) - 1.0;
    +    }
    +    qh_setdelaunay(qh, dim, 1, point);
    +    facet= qh_findbestfacet(qh, point, !qh_ALL, &bestdist, &isoutside);
    +    if (isoutside) {
    +      if (!qh_addpoint(qh, point, facet, False))
    +        break;  /* user requested an early exit with 'TVn' or 'TCn' */
    +    }
    +    qh_printpoint(qh, stdout, "added point", point);
    +    printf("%d points, %d extra points, %d vertices, and %d facets in total\n",
    +                  qh->num_points, qh_setsize(qh, qh->other_points),
    +                  qh->num_vertices, qh->num_facets);
    +
    +    /* qh_produce_output(qh); */
    +  }
    +  if (qh->DOcheckmax)
    +    qh_check_maxout(qh);
    +  else if (qh->KEEPnearinside)
    +    qh_nearcoplanar(qh);
    +} /*.addDelaunay.*/
    +
    +/*--------------------------------------------------
    +-findDelaunay- find Delaunay triangle for [0.5,0.5,...]
    +  assumes dim < 100
    +notes:
    +  calls qh_setdelaunay() to project the point to a parabaloid
    +warning:
    +  This is not implemented for tricoplanar facets ('Qt'),
    +  See locate a facet with qh_findbestfacet()
    +*/
    +void findDelaunay(qhT *qh, int dim) {
    +  int k;
    +  coordT point[ 100];
    +  boolT isoutside;
    +  realT bestdist;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  for (k= 0; k < dim-1; k++)
    +    point[k]= 0.5;
    +  qh_setdelaunay(qh, dim, 1, point);
    +  facet= qh_findbestfacet(qh, point, qh_ALL, &bestdist, &isoutside);
    +  if (facet->tricoplanar) {
    +    fprintf(stderr, "findDelaunay: not implemented for triangulated, non-simplicial Delaunay regions (tricoplanar facet, f%d).\n",
    +       facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    for (k=0; k < dim-1; k++)
    +      printf("%5.2f ", vertex->point[k]);
    +    printf("\n");
    +  }
    +} /*.findDelaunay.*/
    +
    +/*--------------------------------------------------
    +-makehalf- set points to halfspaces for a (dim)-d diamond
    +  points is numpoints X dim+1
    +
    +  each halfspace consists of dim coefficients followed by an offset
    +*/
    +void makehalf(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jnum_points, qh_setsize(qh, qh->other_points),
    +                  qh->num_vertices, qh->num_facets);
    +    /* qh_produce_output(qh); */
    +  }
    +  if (qh->DOcheckmax)
    +    qh_check_maxout(qh);
    +  else if (qh->KEEPnearinside)
    +    qh_nearcoplanar(qh);
    +} /*.addhalf.*/
    +
    +#define DIM 3     /* dimension of points, must be < 31 for SIZEcube */
    +#define SIZEcube (1<&1'\n\n");
    +
    +  ismalloc= False;      /* True if qh_freeqhull should 'free(array)' */
    +  /*
    +    Run 1: convex hull
    +  */
    +  qh_init_A(qh, stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh->errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM];
    +
    +    qh->NOerrexit= False;
    +    strcat(qh->rbox_command, "user_eg2 cube example");
    +    sprintf(options, "qhull s Tcv Q11 %s ", argc >= 2 ? argv[1] : "");
    +    qh_initflags(qh, options);
    +    printf( "\ncompute triangulated convex hull of cube after rotating input\n");
    +    makecube(array[0], SIZEcube, DIM);
    +    qh_init_B(qh, array[0], SIZEcube, DIM, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_triangulate(qh);  /* requires option 'Q11' if want to add points */
    +    print_summary(qh);
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    printf( "\nadd points in a diamond\n");
    +    adddiamond(qh, array[0], SIZEcube, SIZEdiamond, DIM);
    +    qh_check_output(qh);
    +    print_summary(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +  }
    +  qh->NOerrexit= True;
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +      fprintf(stderr, "qhull warning (user_eg2, run 1): did not free %d bytes of long memory (%d pieces)\n",
    +          totlong, curlong);
    +  /*
    +    Run 2: Delaunay triangulation
    +  */
    +  qh_init_A(qh, stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh->errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM];
    +
    +    qh->NOerrexit= False;
    +    strcat(qh->rbox_command, "user_eg2 Delaunay example");
    +    sprintf(options, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +    qh_initflags(qh, options);
    +    printf( "\ncompute %d-d Delaunay triangulation\n", DIM-1);
    +    makeDelaunay(qh, array[0], SIZEcube, DIM);
    +    /* Instead of makeDelaunay with qh_setdelaunay, you may
    +       produce a 2-d array of points, set DIM to 2, and set
    +       qh->PROJECTdelaunay to True.  qh_init_B will call
    +       qh_projectinput to project the points to the paraboloid
    +       and add a point "at-infinity".
    +    */
    +    qh_init_B(qh, array[0], SIZEcube, DIM, ismalloc);
    +    qh_qhull(qh);
    +    /* If you want Voronoi ('v') without qh_produce_output(), call
    +       qh_setvoronoi_all() after qh_qhull() */
    +    qh_check_output(qh);
    +    print_summary(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    printf( "\nadd points to triangulation\n");
    +    addDelaunay(qh, array[0], SIZEcube, SIZEdiamond, DIM);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    printf( "\nfind Delaunay triangle closest to [0.5, 0.5, ...]\n");
    +    findDelaunay(qh, DIM);
    +  }
    +  qh->NOerrexit= True;
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong) 
    +      fprintf(stderr, "qhull warning (user_eg2, run 2): did not free %d bytes of long memory (%d pieces)\n",
    +         totlong, curlong);
    +  /*
    +    Run 3: halfspace intersection
    +  */
    +  qh_init_A(qh, stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh->errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM+1];  /* +1 for halfspace offset */
    +    pointT *points;
    +
    +    qh->NOerrexit= False;
    +    strcat(qh->rbox_command, "user_eg2 halfspace example");
    +    sprintf(options, "qhull H0 s Tcv %s", argc >= 4 ? argv[3] : "");
    +    qh_initflags(qh, options);
    +    printf( "\ncompute halfspace intersection about the origin for a diamond\n");
    +    makehalf(array[0], SIZEcube, DIM);
    +    qh_setfeasible(qh, DIM); /* from io.c, sets qh->feasible_point from 'Hn,n' */
    +    /* you may malloc and set qh->feasible_point directly.  It is only used for
    +       option 'Fp' */
    +    points= qh_sethalfspace_all(qh, DIM+1, SIZEcube, array[0], qh->feasible_point);
    +    qh_init_B(qh, points, SIZEcube, DIM, True); /* qh_freeqhull frees points */
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    printf( "\nadd halfspaces for cube to intersection\n");
    +    addhalf(qh, array[0], SIZEcube, SIZEdiamond, DIM, qh->feasible_point);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +  }
    +  qh->NOerrexit= True;
    +  qh->NOerrexit= True;
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +      fprintf(stderr, "qhull warning (user_eg2, run 3): did not free %d bytes of long memory (%d pieces)\n",
    +          totlong, curlong);
    +  return exitcode;
    +} /* main */
    +
    +#if 1    /* use 1 to prevent loading of io.o and user.o */
    +/*-------------------------------------------
    +-errexit- return exitcode to system after an error
    +  assumes exitcode non-zero
    +  prints useful information
    +  see qh_errexit2() in libqhull.c for 2 facets
    +*/
    +void qh_errexit(qhT *qh, int exitcode, facetT *facet, ridgeT *ridge) {
    +  QHULL_UNUSED(facet);
    +  QHULL_UNUSED(ridge);
    +
    +  if (qh->ERREXITcalled) {
    +    fprintf(qh->ferr, "qhull error while processing previous error.  Exit program\n");
    +    exit(1);
    +  }
    +  qh->ERREXITcalled= True;
    +  if (!qh->QHULLfinished)
    +    qh->hulltime= (unsigned)clock() - qh->hulltime;
    +  fprintf(qh->ferr, "\nWhile executing: %s | %s\n", qh->rbox_command, qh->qhull_command);
    +  fprintf(qh->ferr, "Options selected:\n%s\n", qh->qhull_options);
    +  if (qh->furthest_id >= 0) {
    +    fprintf(qh->ferr, "\nLast point added to hull was p%d", qh->furthest_id);
    +    if (zzval_(Ztotmerge))
    +      fprintf(qh->ferr, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    if (qh->QHULLfinished)
    +      fprintf(qh->ferr, "\nQhull has finished constructing the hull.");
    +    else if (qh->POSTmerging)
    +      fprintf(qh->ferr, "\nQhull has started post-merging");
    +    fprintf(qh->ferr, "\n\n");
    +  }
    +  if (qh->NOerrexit) {
    +    fprintf(qh->ferr, "qhull error while ending program.  Exit program\n");
    +    exit(1);
    +  }
    +  if (!exitcode)
    +    exitcode= qh_ERRqhull;
    +  qh->NOerrexit= True;
    +  longjmp(qh->errexit, exitcode);
    +} /* errexit */
    +
    +
    +/*-------------------------------------------
    +-errprint- prints out the information of the erroneous object
    +    any parameter may be NULL, also prints neighbors and geomview output
    +*/
    +void qh_errprint(qhT *qh, const char *string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex) {
    +
    +  fprintf(qh->ferr, "%s facets f%d f%d ridge r%d vertex v%d\n",
    +           string, getid_(atfacet), getid_(otherfacet), getid_(atridge),
    +           getid_(atvertex));
    +} /* errprint */
    +
    +
    +void qh_printfacetlist(qhT *qh, facetT *facetlist, setT *facets, boolT printall) {
    +  facetT *facet, **facetp;
    +
    +  /* remove these calls to help avoid io.c */
    +  qh_printbegin(qh, qh->ferr, qh_PRINTfacets, facetlist, facets, printall);/*io.c*/
    +  FORALLfacet_(facetlist)                                              /*io.c*/
    +    qh_printafacet(qh, qh->ferr, qh_PRINTfacets, facet, printall);          /*io.c*/
    +  FOREACHfacet_(facets)                                                /*io.c*/
    +    qh_printafacet(qh, qh->ferr, qh_PRINTfacets, facet, printall);          /*io.c*/
    +  qh_printend(qh, qh->ferr, qh_PRINTfacets, facetlist, facets, printall);  /*io.c*/
    +
    +  FORALLfacet_(facetlist)
    +    fprintf( qh->ferr, "facet f%d\n", facet->id);
    +} /* printfacetlist */
    +
    +/* qh_printhelp_degenerate( fp )
    +    prints descriptive message for precision error
    +
    +  notes:
    +    no message if qh_QUICKhelp
    +*/
    +void qh_printhelp_degenerate(qhT *qh, FILE *fp) {
    +
    +  if (qh->MERGEexact || qh->PREmerge || qh->JOGGLEmax < REALmax/2)
    +    qh_fprintf(qh, fp, 9368, "\n\
    +A Qhull error has occurred.  Qhull should have corrected the above\n\
    +precision error.  Please send the input and all of the output to\n\
    +qhull_bug@qhull.org\n");
    +  else if (!qh_QUICKhelp) {
    +    qh_fprintf(qh, fp, 9369, "\n\
    +Precision problems were detected during construction of the convex hull.\n\
    +This occurs because convex hull algorithms assume that calculations are\n\
    +exact, but floating-point arithmetic has roundoff errors.\n\
    +\n\
    +To correct for precision problems, do not use 'Q0'.  By default, Qhull\n\
    +selects 'C-0' or 'Qx' and merges non-convex facets.  With option 'QJ',\n\
    +Qhull joggles the input to prevent precision problems.  See \"Imprecision\n\
    +in Qhull\" (qh-impre.htm).\n\
    +\n\
    +If you use 'Q0', the output may include\n\
    +coplanar ridges, concave ridges, and flipped facets.  In 4-d and higher,\n\
    +Qhull may produce a ridge with four neighbors or two facets with the same \n\
    +vertices.  Qhull reports these events when they occur.  It stops when a\n\
    +concave ridge, flipped facet, or duplicate facet occurs.\n");
    +#if REALfloat
    +    qh_fprintf(qh, fp, 9370, "\
    +\n\
    +Qhull is currently using single precision arithmetic.  The following\n\
    +will probably remove the precision problems:\n\
    +  - recompile qhull for realT precision(#define REALfloat 0 in user.h).\n");
    +#endif
    +    if (qh->DELAUNAY && !qh->SCALElast && qh->MAXabs_coord > 1e4)
    +      qh_fprintf(qh, fp, 9371, "\
    +\n\
    +When computing the Delaunay triangulation of coordinates > 1.0,\n\
    +  - use 'Qbb' to scale the last coordinate to [0,m] (max previous coordinate)\n");
    +    if (qh->DELAUNAY && !qh->ATinfinity)
    +      qh_fprintf(qh, fp, 9372, "\
    +When computing the Delaunay triangulation:\n\
    +  - use 'Qz' to add a point at-infinity.  This reduces precision problems.\n");
    +
    +    qh_fprintf(qh, fp, 9373, "\
    +\n\
    +If you need triangular output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft'.  It triangulates non-simplicial facets with added points.\n\
    +\n\
    +If you must use 'Q0',\n\
    +try one or more of the following options.  They can not guarantee an output.\n\
    +  - use 'QbB' to scale the input to a cube.\n\
    +  - use 'Po' to produce output and prevent partitioning for flipped facets\n\
    +  - use 'V0' to set min. distance to visible facet as 0 instead of roundoff\n\
    +  - use 'En' to specify a maximum roundoff error less than %2.2g.\n\
    +  - options 'Qf', 'Qbb', and 'QR0' may also help\n",
    +               qh->DISTround);
    +    qh_fprintf(qh, fp, 9374, "\
    +\n\
    +To guarantee simplicial output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft' to triangulate the output by adding points\n\
    +  - use exact arithmetic (see \"Imprecision in Qhull\", qh-impre.htm)\n\
    +");
    +  }
    +} /* printhelp_degenerate */
    +
    +
    +/* qh_printhelp_narrowhull( minangle )
    +     Warn about a narrow hull
    +
    +  notes:
    +    Alternatively, reduce qh_WARNnarrow in user.h
    +
    +*/
    +void qh_printhelp_narrowhull(qhT *qh, FILE *fp, realT minangle) {
    +
    +    qh_fprintf(qh, fp, 9375, "qhull precision warning: \n\
    +The initial hull is narrow (cosine of min. angle is %.16f).\n\
    +A coplanar point may lead to a wide facet.  Options 'QbB' (scale to unit box)\n\
    +or 'Qbb' (scale last coordinate) may remove this warning.  Use 'Pp' to skip\n\
    +this warning.  See 'Limitations' in qh-impre.htm.\n",
    +          -minangle);   /* convert from angle between normals to angle between facets */
    +} /* printhelp_narrowhull */
    +
    +/* qh_printhelp_singular
    +      prints descriptive message for singular input
    +*/
    +void qh_printhelp_singular(qhT *qh, FILE *fp) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  realT min, max, *coord, dist;
    +  int i,k;
    +
    +  qh_fprintf(qh, fp, 9376, "\n\
    +The input to qhull appears to be less than %d dimensional, or a\n\
    +computation has overflowed.\n\n\
    +Qhull could not construct a clearly convex simplex from points:\n",
    +           qh->hull_dim);
    +  qh_printvertexlist(qh, fp, "", qh->facet_list, NULL, qh_ALL);
    +  if (!qh_QUICKhelp)
    +    qh_fprintf(qh, fp, 9377, "\n\
    +The center point is coplanar with a facet, or a vertex is coplanar\n\
    +with a neighboring facet.  The maximum round off error for\n\
    +computing distances is %2.2g.  The center point, facets and distances\n\
    +to the center point are as follows:\n\n", qh->DISTround);
    +  qh_printpointid(qh, fp, "center point", qh->hull_dim, qh->interior_point, -1);
    +  qh_fprintf(qh, fp, 9378, "\n");
    +  FORALLfacets {
    +    qh_fprintf(qh, fp, 9379, "facet");
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(qh, fp, 9380, " p%d", qh_pointid(qh, vertex->point));
    +    zinc_(Zdistio);
    +    qh_distplane(qh, qh->interior_point, facet, &dist);
    +    qh_fprintf(qh, fp, 9381, " distance= %4.2g\n", dist);
    +  }
    +  if (!qh_QUICKhelp) {
    +    if (qh->HALFspace)
    +      qh_fprintf(qh, fp, 9382, "\n\
    +These points are the dual of the given halfspaces.  They indicate that\n\
    +the intersection is degenerate.\n");
    +    qh_fprintf(qh, fp, 9383,"\n\
    +These points either have a maximum or minimum x-coordinate, or\n\
    +they maximize the determinant for k coordinates.  Trial points\n\
    +are first selected from points that maximize a coordinate.\n");
    +    if (qh->hull_dim >= qh_INITIALmax)
    +      qh_fprintf(qh, fp, 9384, "\n\
    +Because of the high dimension, the min x-coordinate and max-coordinate\n\
    +points are used if the determinant is non-zero.  Option 'Qs' will\n\
    +do a better, though much slower, job.  Instead of 'Qs', you can change\n\
    +the points by randomly rotating the input with 'QR0'.\n");
    +  }
    +  qh_fprintf(qh, fp, 9385, "\nThe min and max coordinates for each dimension are:\n");
    +  for (k=0; k < qh->hull_dim; k++) {
    +    min= REALmax;
    +    max= -REALmin;
    +    for (i=qh->num_points, coord= qh->first_point+k; i--; coord += qh->hull_dim) {
    +      maximize_(max, *coord);
    +      minimize_(min, *coord);
    +    }
    +    qh_fprintf(qh, fp, 9386, "  %d:  %8.4g  %8.4g  difference= %4.4g\n", k, min, max, max-min);
    +  }
    +  if (!qh_QUICKhelp) {
    +    qh_fprintf(qh, fp, 9387, "\n\
    +If the input should be full dimensional, you have several options that\n\
    +may determine an initial simplex:\n\
    +  - use 'QJ'  to joggle the input and make it full dimensional\n\
    +  - use 'QbB' to scale the points to the unit cube\n\
    +  - use 'QR0' to randomly rotate the input for different maximum points\n\
    +  - use 'Qs'  to search all points for the initial simplex\n\
    +  - use 'En'  to specify a maximum roundoff error less than %2.2g.\n\
    +  - trace execution with 'T3' to see the determinant for each point.\n",
    +                     qh->DISTround);
    +#if REALfloat
    +    qh_fprintf(qh, fp, 9388, "\
    +  - recompile qhull for realT precision(#define REALfloat 0 in libqhull.h).\n");
    +#endif
    +    qh_fprintf(qh, fp, 9389, "\n\
    +If the input is lower dimensional:\n\
    +  - use 'QJ' to joggle the input and make it full dimensional\n\
    +  - use 'Qbk:0Bk:0' to delete coordinate k from the input.  You should\n\
    +    pick the coordinate with the least range.  The hull will have the\n\
    +    correct topology.\n\
    +  - determine the flat containing the points, rotate the points\n\
    +    into a coordinate plane, and delete the other coordinates.\n\
    +  - add one or more points to make the input full dimensional.\n\
    +");
    +    if (qh->DELAUNAY && !qh->ATinfinity)
    +      qh_fprintf(qh, fp, 9390, "\n\n\
    +This is a Delaunay triangulation and the input is co-circular or co-spherical:\n\
    +  - use 'Qz' to add a point \"at infinity\" (i.e., above the paraboloid)\n\
    +  - or use 'QJ' to joggle the input and avoid co-circular data\n");
    +  }
    +} /* printhelp_singular */
    +
    +
    +/*-----------------------------------------
    +-user_memsizes- allocate up to 10 additional, quick allocation sizes
    +*/
    +void qh_user_memsizes(qhT *qh) {
    +
    +  QHULL_UNUSED(qh);
    +  /* qh_memsize(qh, size); */
    +} /* user_memsizes */
    +
    +#endif
    diff --git a/xs/src/qhull/src/user_eg3/user_eg3.pro b/xs/src/qhull/src/user_eg3/user_eg3.pro
    new file mode 100644
    index 0000000000..35372fbf92
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg3/user_eg3.pro
    @@ -0,0 +1,12 @@
    +# -------------------------------------------------
    +# user_eg3.pro -- Qt project for cpp demonstration user_eg3.exe
    +#
    +# The C++ interface requires reentrant Qhull.
    +# -------------------------------------------------
    +
    +include(../qhull-app-cpp.pri)
    +
    +TARGET = user_eg3
    +CONFIG -= qt
    +
    +SOURCES += user_eg3_r.cpp
    diff --git a/xs/src/qhull/src/user_eg3/user_eg3_r.cpp b/xs/src/qhull/src/user_eg3/user_eg3_r.cpp
    new file mode 100644
    index 0000000000..5257872ab8
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg3/user_eg3_r.cpp
    @@ -0,0 +1,162 @@
    +#//! user_eg3_r.cpp -- Invoke rbox and qhull from C++
    +
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullQh.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/QhullLinkedList.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#include    /* for printf() of help message */
    +#include 
    +#include 
    +
    +using std::cerr;
    +using std::cin;
    +using std::cout;
    +using std::endl;
    +
    +using orgQhull::Qhull;
    +using orgQhull::QhullError;
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetList;
    +using orgQhull::QhullQh;
    +using orgQhull::RboxPoints;
    +using orgQhull::QhullVertex;
    +using orgQhull::QhullVertexSet;
    +
    +int main(int argc, char **argv);
    +int user_eg3(int argc, char **argv);
    +
    +char prompt[]= "\n\
    +user_eg3 -- demonstrate calling rbox and qhull from C++.\n\
    +\n\
    +user_eg3 is statically linked to reentrant qhull.  If user_eg3\n\
    +fails immediately, it is probably linked to the non-reentrant qhull.\n\
    +Try 'user_eg3 rbox qhull \"T1\"'\n\
    +\n\
    +  eg-100                       Run the example in qh-code.htm\n\
    +  rbox \"200 D4\" ...            Generate points from rbox\n\
    +  qhull \"d p\" ...              Run qhull and produce output\n\
    +  qhull-cout \"o\" ...           Run qhull and produce output to cout\n\
    +  qhull \"T1\" ...               Run qhull with level-1 trace to cerr\n\
    +  facets                       Print facets when done\n\
    +\n\
    +For example\n\
    +  user_eg3 rbox qhull\n\
    +  user_eg3 rbox qhull d\n\
    +  user_eg3 rbox \"10 D2\"  \"2 D2\" qhull  \"s p\" facets\n\
    +\n\
    +";
    +
    +
    +/*--------------------------------------------
    +-user_eg3-  main procedure of user_eg3 application
    +*/
    +int main(int argc, char **argv) {
    +
    +    QHULL_LIB_CHECK
    +
    +    if(argc==1){
    +        cout << prompt;
    +        return 1;
    +    }
    +    try{
    +        return user_eg3(argc, argv);
    +    }catch(QhullError &e){
    +        cerr << e.what() << std::endl;
    +        return e.errorCode();
    +    }
    +}//main
    +
    +int user_eg3(int argc, char **argv)
    +{
    +    if(strcmp(argv[1], "eg-100")==0){
    +        RboxPoints rbox("100");
    +        Qhull q(rbox, "");
    +        QhullFacetList facets= q.facetList();
    +        cout << facets;
    +        return 0;
    +    }
    +    bool printFacets= false;
    +    RboxPoints rbox;
    +    Qhull qhull;
    +    int readingRbox= 0;
    +    int readingQhull= 0;
    +    for(int i=1; i
    Date: Tue, 7 Aug 2018 14:15:37 +0200
    Subject: [PATCH 080/119] Fixed inccorect (excessive) time estimates with the
     MMU 2.0
    
    ---
     xs/src/libslic3r/GCodeTimeEstimator.cpp | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp
    index b51692fc6e..c4ffb572a9 100644
    --- a/xs/src/libslic3r/GCodeTimeEstimator.cpp
    +++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp
    @@ -721,6 +721,8 @@ namespace Slic3r {
             }
     
             _last_st_synchronized_block_id = _blocks.size() - 1;
    +        // The additional time has been consumed (added to the total time), reset it to zero.
    +        set_additional_time(0.);
         }
     
         void GCodeTimeEstimator::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line)
    
    From a06b6716eaab6784fa09a839ac1a384f536cc33c Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Thu, 9 Aug 2018 16:35:28 +0200
    Subject: [PATCH 081/119] First naive implementation of TriangleMesh convex
     hull calculation
    
    ---
     xs/src/libslic3r/TriangleMesh.cpp | 48 +++++++++++++++++++++++++++++++
     xs/src/libslic3r/TriangleMesh.hpp |  1 +
     2 files changed, 49 insertions(+)
    
    diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp
    index 45e4b6f5dc..197b4d488d 100644
    --- a/xs/src/libslic3r/TriangleMesh.cpp
    +++ b/xs/src/libslic3r/TriangleMesh.cpp
    @@ -1,6 +1,9 @@
     #include "TriangleMesh.hpp"
     #include "ClipperUtils.hpp"
     #include "Geometry.hpp"
    +#include "qhull/src/libqhullcpp/Qhull.h"
    +#include "qhull/src/libqhullcpp/QhullFacetList.h"
    +#include "qhull/src/libqhullcpp/QhullVertexSet.h"
     #include 
     #include 
     #include 
    @@ -10,6 +13,7 @@
     #include 
     #include 
     #include 
    +#include 
     
     #include 
     
    @@ -597,6 +601,50 @@ TriangleMesh::bounding_box() const
         return bb;
     }
     
    +
    +TriangleMesh TriangleMesh::convex_hull3d() const
    +{
    +    // qhull's realT is assumed to be a typedef for float - let's better check it first:
    +    static_assert(std::is_same::value, "Internal type realT in the qhull library must be float!");
    +
    +    // Helper struct for qhull:
    +    struct PointForQHull{
    +        PointForQHull(float x_p, float y_p, float z_p) : x(x_p), y(y_p), z(z_p) {}
    +        float x,y,z;
    +        };
    +    std::vector input_verts;
    +
    +    // We will now fill the vector with input points for computation:
    +    stl_facet* facet_ptr = stl.facet_start;
    +    while (facet_ptr < stl.facet_start+stl.stats.number_of_facets) {
    +        for (int j=0;j<3;++j)
    +            input_verts.emplace_back(PointForQHull(facet_ptr->vertex[j].x, facet_ptr->vertex[j].y, facet_ptr->vertex[j].z));
    +        facet_ptr+=1;
    +    }
    +
    +    // The qhull call:
    +    orgQhull::Qhull qhull;
    +    qhull.disableOutputStream(); // we want qhull to be quiet
    +    qhull.runQhull("", 3, input_verts.size(), (const realT*)(input_verts.data()), "Qt" );
    +
    +    // Let's collect results:
    +    Pointf3s vertices;
    +    std::vector facets;
    +    auto facet_list = qhull.facetList().toStdVector();
    +    for (const orgQhull::QhullFacet& facet : facet_list) {   // iterate through facets
    +        for (unsigned char i=0; i<3; ++i) {        // iterate through facet's vertices
    +            orgQhull::QhullPoint p = (facet.vertices())[i].point();
    +            const float* coords = p.coordinates();
    +            Pointf3 vert((float)coords[0], (float)coords[1], (float)coords[2]);
    +            vertices.emplace_back(vert);
    +        }
    +        facets.emplace_back(Point3(vertices.size()-3, vertices.size()-2, vertices.size()-1));
    +    }
    +    TriangleMesh output_mesh(vertices, facets);
    +    output_mesh.repair();
    +    return output_mesh;
    +}
    +
     void
     TriangleMesh::require_shared_vertices()
     {
    diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp
    index c700784a51..14fdba6c35 100644
    --- a/xs/src/libslic3r/TriangleMesh.hpp
    +++ b/xs/src/libslic3r/TriangleMesh.hpp
    @@ -55,6 +55,7 @@ public:
         ExPolygons horizontal_projection() const;
         Polygon convex_hull();
         BoundingBoxf3 bounding_box() const;
    +    TriangleMesh convex_hull3d() const;
         void reset_repair_stats();
         bool needed_repair() const;
         size_t facets_count() const;
    
    From 25a6c7e30e83e6a691d580fdf3645635ed459a06 Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Thu, 9 Aug 2018 16:55:43 +0200
    Subject: [PATCH 082/119] Created a new gizmo for flattening an object
    
    ---
     lib/Slic3r/GUI/Plater.pm         |   3 +-
     xs/src/slic3r/GUI/GLCanvas3D.cpp |  66 ++++++++++++++++++-
     xs/src/slic3r/GUI/GLCanvas3D.hpp |   4 ++
     xs/src/slic3r/GUI/GLGizmo.cpp    | 109 ++++++++++++++++++++++++++++++-
     xs/src/slic3r/GUI/GLGizmo.hpp    |  32 +++++++++
     5 files changed, 211 insertions(+), 3 deletions(-)
    
    diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
    index a0eef72fea..e9cdf1527a 100644
    --- a/lib/Slic3r/GUI/Plater.pm
    +++ b/lib/Slic3r/GUI/Plater.pm
    @@ -141,8 +141,9 @@ sub new {
         
         # callback to react to gizmo rotate
         my $on_gizmo_rotate = sub {
    -        my ($angle_z) = @_;
    +        my ($angle_z, $angle_y) = @_;
             $self->rotate(rad2deg($angle_z), Z, 'absolute');
    +        $self->rotate(rad2deg($angle_y), Y, 'absolute');
         };
     
         # callback to update object's geometry info while using gizmos
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index ab4095e6fe..f1bbba934e 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -1,5 +1,6 @@
     #include "GLCanvas3D.hpp"
     
    +#include "../../admesh/stl.h"
     #include "../../libslic3r/libslic3r.h"
     #include "../../slic3r/GUI/3DScene.hpp"
     #include "../../slic3r/GUI/GLShader.hpp"
    @@ -1154,6 +1155,18 @@ bool GLCanvas3D::Gizmos::init()
     
         m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo));
     
    +    gizmo = new GLGizmoFlatten;
    +    if (gizmo == nullptr)
    +        return false;
    +
    +    if (!gizmo->init()) {
    +        _reset();
    +        return false;
    +    }
    +
    +    m_gizmos.insert(GizmosMap::value_type(Flatten, gizmo));
    +
    +
         return true;
     }
     
    @@ -1387,6 +1400,25 @@ void GLCanvas3D::Gizmos::set_angle_z(float angle_z)
             reinterpret_cast(it->second)->set_angle_z(angle_z);
     }
     
    +Pointf3 GLCanvas3D::Gizmos::get_flattening_normal() const
    +{
    +    if (!m_enabled)
    +        return Pointf3(0.f, 0.f, 0.f);
    +
    +    GizmosMap::const_iterator it = m_gizmos.find(Flatten);
    +    return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_flattening_normal() : Pointf3(0.f, 0.f, 0.f);
    +}
    +
    +void GLCanvas3D::Gizmos::set_flattening_data(std::vector vertices_list)
    +{
    +    if (!m_enabled)
    +        return;
    +
    +    GizmosMap::const_iterator it = m_gizmos.find(Flatten);
    +    if (it != m_gizmos.end())
    +        reinterpret_cast(it->second)->set_flattening_data(vertices_list);
    +}
    +
     void GLCanvas3D::Gizmos::render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const
     {
         if (!m_enabled)
    @@ -2170,6 +2202,27 @@ void GLCanvas3D::update_gizmos_data()
                 {
                     m_gizmos.set_scale(model_instance->scaling_factor);
                     m_gizmos.set_angle_z(model_instance->rotation);
    +
    +                /////////////////////////////////////////////////////////////////////////
    +                // Following block provides convex hull data to the Flatten gizmo
    +                // It is temporary, it should be optimized and moved elsewhere later
    +                TriangleMesh ch = model_object->mesh().convex_hull3d();
    +                stl_facet* facet_ptr = ch.stl.facet_start;
    +                std::vector points;
    +                while (facet_ptr < ch.stl.facet_start+ch.stl.stats.number_of_facets) {
    +                    Pointf3 a = Pointf3(facet_ptr->vertex[1].x - facet_ptr->vertex[0].x, facet_ptr->vertex[1].y - facet_ptr->vertex[0].y, facet_ptr->vertex[1].z - facet_ptr->vertex[0].z);
    +                    Pointf3 b = Pointf3(facet_ptr->vertex[2].x - facet_ptr->vertex[0].x, facet_ptr->vertex[2].y - facet_ptr->vertex[0].y, facet_ptr->vertex[2].z - facet_ptr->vertex[0].z);
    +
    +                    if (0.5 * sqrt(dot(cross(a, b), cross(a,b))) > 50.f) {
    +                        points.emplace_back(Pointf3s());
    +                        for (unsigned int j=0; j<3; ++j)
    +                            points.back().emplace_back(Pointf3(facet_ptr->vertex[j].x, facet_ptr->vertex[j].y, facet_ptr->vertex[j].z));
    +                        points.back().emplace_back(Pointf3(facet_ptr->normal.x, facet_ptr->normal.y, facet_ptr->normal.z));
    +                    }
    +                    facet_ptr+=1;
    +                }
    +                m_gizmos.set_flattening_data(points);
    +                ////////////////////////////////////////////////////////////////////////
                 }
             }
         }
    @@ -2177,6 +2230,7 @@ void GLCanvas3D::update_gizmos_data()
         {
             m_gizmos.set_scale(1.0f);
             m_gizmos.set_angle_z(0.0f);
    +        m_gizmos.set_flattening_data(std::vector());
         }
     }
     
    @@ -2760,6 +2814,16 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 m_gizmos.start_dragging();
                 m_mouse.drag.gizmo_volume_idx = _get_first_selected_volume_id(selected_object_idx);
                 m_dirty = true;
    +
    +            if (m_gizmos.get_current_type() == Gizmos::Flatten) {
    +                // Rotate the object so the normal points downward:
    +                Pointf3 normal = m_gizmos.get_flattening_normal();
    +                if (normal.x != 0.f || normal.y != 0.f || normal.z != 0.f) {
    +                    float angle_z = -atan2(normal.y, normal.x);
    +                    float angle_y = M_PI - atan2(normal.x*cos(angle_z)-normal.y*sin(angle_z), normal.z);
    +                    m_on_gizmo_rotate_callback.call((double)angle_z, (double)angle_y);
    +                }
    +            }
             }
             else
             {
    @@ -3039,7 +3103,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 }
                 case Gizmos::Rotate:
                 {
    -                m_on_gizmo_rotate_callback.call((double)m_gizmos.get_angle_z());
    +                m_on_gizmo_rotate_callback.call((double)m_gizmos.get_angle_z(), 0.);
                     break;
                 }
                 default:
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    index ae20882fc6..09a7de823b 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    @@ -338,6 +338,7 @@ public:
                 Undefined,
                 Scale,
                 Rotate,
    +            Flatten,
                 Num_Types
             };
     
    @@ -382,6 +383,9 @@ public:
             float get_angle_z() const;
             void set_angle_z(float angle_z);
     
    +        void set_flattening_data(std::vector vertices_list);
    +        Pointf3 get_flattening_normal() const;
    +
             void render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const;
             void render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const;
     
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 47b01e8a28..5a09447584 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -110,7 +110,7 @@ int GLGizmoBase::get_hover_id() const
     
     void GLGizmoBase::set_hover_id(int id)
     {
    -    if (id < (int)m_grabbers.size())
    +    //if (id < (int)m_grabbers.size())
             m_hover_id = id;
     }
     
    @@ -502,5 +502,112 @@ void GLGizmoScale::on_render_for_picking(const BoundingBoxf3& box) const
         render_grabbers();
     }
     
    +
    +GLGizmoFlatten::GLGizmoFlatten()
    +    : GLGizmoBase(),
    +      m_normal(Pointf3(0.f, 0.f, 0.f))
    +{}
    +
    +
    +bool GLGizmoFlatten::on_init()
    +{
    +    std::string path = resources_dir() + "/icons/overlay/";
    +
    +    std::string filename = path + "scale_off.png";
    +    if (!m_textures[Off].load_from_file(filename, false))
    +        return false;
    +
    +    filename = path + "scale_hover.png";
    +    if (!m_textures[Hover].load_from_file(filename, false))
    +        return false;
    +
    +    filename = path + "scale_on.png";
    +    if (!m_textures[On].load_from_file(filename, false))
    +        return false;
    +
    +    return true;
    +}
    +
    +void GLGizmoFlatten::on_start_dragging()
    +{
    +    if (m_hover_id != -1)
    +        m_normal = m_planes[m_hover_id].normal;
    +}
    +
    +void GLGizmoFlatten::on_update(const Pointf& mouse_pos)
    +{
    +    /*Pointf center(0.5 * (m_grabbers[1].center.x + m_grabbers[0].center.x), 0.5 * (m_grabbers[3].center.y + m_grabbers[0].center.y));
    +
    +    coordf_t orig_len = length(m_starting_drag_position - center);
    +    coordf_t new_len = length(mouse_pos - center);
    +    coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0;
    +
    +    m_scale = m_starting_scale * (float)ratio;*/
    +}
    +
    +void GLGizmoFlatten::on_render(const BoundingBoxf3& box) const
    +{
    +    bool blending_was_enabled = ::glIsEnabled(GL_BLEND);
    +    ::glEnable(GL_BLEND);
    +    ::glDisable(GL_DEPTH_TEST);
    +
    +    for (int i=0; i<(int)m_planes.size(); ++i) {
    +        if (i == m_hover_id)
    +            ::glColor4f(0.9f, 0.9f, 0.9f, 0.75f);
    +            else
    +                ::glColor4f(0.9f, 0.9f, 0.9f, 0.5f);
    +
    +        ::glBegin(GL_POLYGON);
    +        for (const auto& vertex : m_planes[i].vertices)
    +            ::glVertex3f((GLfloat)vertex.x, (GLfloat)vertex.y, (GLfloat)vertex.z);
    +        ::glEnd();
    +    }
    +
    +    if (!blending_was_enabled)
    +        ::glDisable(GL_BLEND);
    +}
    +
    +void GLGizmoFlatten::on_render_for_picking(const BoundingBoxf3& box) const
    +{
    +    static const GLfloat INV_255 = 1.0f / 255.0f;
    +
    +    ::glDisable(GL_DEPTH_TEST);
    +
    +    for (unsigned int i = 0; i < m_planes.size(); ++i)
    +    {
    +        ::glColor3f(1.f, 1.f, (254.0f - (float)i) * INV_255);
    +        ::glBegin(GL_POLYGON);
    +        for (const auto& vertex : m_planes[i].vertices)
    +            ::glVertex3f((GLfloat)vertex.x, (GLfloat)vertex.y, (GLfloat)vertex.z);
    +        ::glEnd();
    +    }
    +}
    +
    +void GLGizmoFlatten::set_flattening_data(std::vector vertices_list)
    +{
    +    // Each entry in vertices_list describe one polygon that can be laid flat.
    +    // All points but the last one are vertices of the polygon, the last "point" is the outer normal vector.
    +
    +    m_planes.clear();
    +    m_planes.reserve(vertices_list.size());
    +
    +    for (const auto& plane_data : vertices_list) {
    +        m_planes.emplace_back(PlaneData());
    +        for (unsigned int i=0; i vertices;
    +        Pointf3 normal;
    +        float color[3];
    +    };
    +
    +    std::vector m_planes;
    +
    +public:
    +    GLGizmoFlatten();
    +
    +    void set_flattening_data(std::vector vertices_list);
    +    Pointf3 get_flattening_normal() const;
    +
    +protected:
    +    virtual bool on_init();
    +    virtual void on_start_dragging();
    +    virtual void on_update(const Pointf& mouse_pos);
    +    virtual void on_render(const BoundingBoxf3& box) const;
    +    virtual void on_render_for_picking(const BoundingBoxf3& box) const;
    +};
    +
    +
    +
     } // namespace GUI
     } // namespace Slic3r
     
    
    From 09ce6c62ea94c5f3d88eb2a7a0aa9de91c8a728e Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Mon, 13 Aug 2018 14:51:03 +0200
    Subject: [PATCH 083/119] Retraction after ramming is now done without moving
     the head
    
    ---
     xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 9 +++++++--
     1 file changed, 7 insertions(+), 2 deletions(-)
    
    diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
    index 3d0dba07af..4009ebf3a1 100644
    --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
    +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
    @@ -793,11 +793,16 @@ void WipeTowerPrusaMM::toolchange_Unload(
         float turning_point = (!m_left_to_right ? xl : xr );
         float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length/2.f - 15.f; // the 15mm is reserved for the first part after ramming
         writer.suppress_preview()
    -          .load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed
    +          .retract(15.f, 5000.f) // feedrate 5000mm/min = 83mm/s
    +          .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f)
    +          .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f)
    +          .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f)
    +          
    +          /*.load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed
               .load_move_x_advanced(old_x,         -0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed)
               .load_move_x_advanced(turning_point, -0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed)
               .load_move_x_advanced(old_x,         -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed)
    -          .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate
    +          .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/
               .resume_preview();
     
         if (new_temperature != 0 && new_temperature != m_old_temperature ) { 	// Set the extruder temperature, but don't wait.
    
    From 6742735596eb342d8166ce00aa370cf0fba15c1f Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Mon, 13 Aug 2018 16:16:37 +0200
    Subject: [PATCH 084/119] Better fix for minimum z of object to lay on the bed
     after rotations
    
    ---
     xs/src/libslic3r/Model.cpp | 11 +----------
     1 file changed, 1 insertion(+), 10 deletions(-)
    
    diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp
    index bceeea2582..f9936537fa 100644
    --- a/xs/src/libslic3r/Model.cpp
    +++ b/xs/src/libslic3r/Model.cpp
    @@ -771,21 +771,12 @@ void ModelObject::scale(const Pointf3 &versor)
     
     void ModelObject::rotate(float angle, const Axis &axis)
     {
    -    float min_z = FLT_MAX;
         for (ModelVolume *v : this->volumes)
         {
             v->mesh.rotate(angle, axis);
    -        min_z = std::min(min_z, v->mesh.stl.stats.min.z);
         }
     
    -    if (min_z != 0.0f)
    -    {
    -        // translate the object so that its minimum z lays on the bed
    -        for (ModelVolume *v : this->volumes)
    -        {
    -            v->mesh.translate(0.0f, 0.0f, -min_z);
    -        }
    -    }
    +    center_around_origin();
     
         this->origin_translation = Pointf3(0, 0, 0);
         this->invalidate_bounding_box();
    
    From 168d38df2b823b9c3c52fc288b8a688e00ac1d99 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Tue, 14 Aug 2018 09:33:58 +0200
    Subject: [PATCH 085/119] Fixed object sinking into print bed after parts
     import
    
    ---
     lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm | 8 +++++---
     1 file changed, 5 insertions(+), 3 deletions(-)
    
    diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
    index a20a241f79..783c1a9f5b 100644
    --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
    +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm
    @@ -157,7 +157,7 @@ sub new {
                 my ($volume_idx) = @_;
                 $self->reload_tree($volume_idx);
             });
    -        Slic3r::GUI::_3DScene::load_model_object($canvas, $self->{model_object}, 0, [0]);
    +        Slic3r::GUI::_3DScene::load_model_object($canvas, $self->{model_object}, 0, [0]);        
             Slic3r::GUI::_3DScene::set_auto_bed_shape($canvas);
             Slic3r::GUI::_3DScene::set_axes_length($canvas, 2.0 * max(@{ Slic3r::GUI::_3DScene::get_volumes_bounding_box($canvas)->size }));
             $canvas->SetSize([500,700]);
    @@ -377,7 +377,8 @@ sub on_btn_load {
                 }
             }
         }
    -    
    +
    +    $self->{model_object}->center_around_origin if $self->{parts_changed};
         $self->_parts_changed;
     }
     
    @@ -479,7 +480,8 @@ sub on_btn_delete {
             $self->{model_object}->delete_volume($itemData->{volume_id});
             $self->{parts_changed} = 1;
         }
    -    
    +
    +    $self->{model_object}->center_around_origin if $self->{parts_changed};
         $self->_parts_changed;
     }
     
    
    From 93ce0d23b75881e8fd7cf71df4ff3a885d54b6af Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Tue, 14 Aug 2018 13:08:49 +0200
    Subject: [PATCH 086/119] Simple attempt to smooth the lay flat triangles
    
    ---
     xs/src/slic3r/GUI/GLCanvas3D.cpp | 41 +++++++++++++++++++++++++++++---
     1 file changed, 38 insertions(+), 3 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index f1bbba934e..45260544dd 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -2209,14 +2209,49 @@ void GLCanvas3D::update_gizmos_data()
                     TriangleMesh ch = model_object->mesh().convex_hull3d();
                     stl_facet* facet_ptr = ch.stl.facet_start;
                     std::vector points;
    +                const unsigned int k = 20;
    +                const float ratio = 0.2f;
    +                const unsigned int N = 3; // 3 - triangle
    +
                     while (facet_ptr < ch.stl.facet_start+ch.stl.stats.number_of_facets) {
                         Pointf3 a = Pointf3(facet_ptr->vertex[1].x - facet_ptr->vertex[0].x, facet_ptr->vertex[1].y - facet_ptr->vertex[0].y, facet_ptr->vertex[1].z - facet_ptr->vertex[0].z);
                         Pointf3 b = Pointf3(facet_ptr->vertex[2].x - facet_ptr->vertex[0].x, facet_ptr->vertex[2].y - facet_ptr->vertex[0].y, facet_ptr->vertex[2].z - facet_ptr->vertex[0].z);
     
    +
                         if (0.5 * sqrt(dot(cross(a, b), cross(a,b))) > 50.f) {
    -                        points.emplace_back(Pointf3s());
    -                        for (unsigned int j=0; j<3; ++j)
    -                            points.back().emplace_back(Pointf3(facet_ptr->vertex[j].x, facet_ptr->vertex[j].y, facet_ptr->vertex[j].z));
    +                        points.emplace_back(Pointf3s(2*k*N));
    +
    +                        std::vector> neighbours;
    +                        if (k != 0) {
    +                            for (unsigned int j=0; jvertex[j].x, facet_ptr->vertex[j].y, facet_ptr->vertex[j].z);
    +                                neighbours.push_back(std::make_pair((int)(j*2*k-k) < 0 ? (N-1)*2*k+k : j*2*k-k, j*2*k+k));
    +                            }
    +
    +                            for (unsigned int i=0; ivertex[j].x, facet_ptr->vertex[j].y, facet_ptr->vertex[j].z));
    +
                             points.back().emplace_back(Pointf3(facet_ptr->normal.x, facet_ptr->normal.y, facet_ptr->normal.z));
                         }
                         facet_ptr+=1;
    
    From 74e807f89bb03472924563212e7b11878424f077 Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Tue, 14 Aug 2018 16:23:23 +0200
    Subject: [PATCH 087/119] New experimental parameter to adjust initial loading
     speed of the filament from the nozzle
    
    ---
     xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 2 +-
     xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp | 6 ++++--
     xs/src/libslic3r/Print.cpp                  | 2 ++
     xs/src/libslic3r/PrintConfig.cpp            | 8 ++++++++
     xs/src/libslic3r/PrintConfig.hpp            | 2 ++
     xs/src/slic3r/GUI/Preset.cpp                | 4 ++--
     xs/src/slic3r/GUI/Tab.cpp                   | 1 +
     7 files changed, 20 insertions(+), 5 deletions(-)
    
    diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
    index 4009ebf3a1..de1f9a59b6 100644
    --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
    +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
    @@ -793,7 +793,7 @@ void WipeTowerPrusaMM::toolchange_Unload(
         float turning_point = (!m_left_to_right ? xl : xr );
         float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length/2.f - 15.f; // the 15mm is reserved for the first part after ramming
         writer.suppress_preview()
    -          .retract(15.f, 5000.f) // feedrate 5000mm/min = 83mm/s
    +          .retract(15.f, m_filpar[m_current_tool].unloading_speed_start * 60.f) // feedrate 5000mm/min = 83mm/s
               .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f)
               .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f)
               .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f)
    diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
    index e1529bcf49..4b96ce17c0 100644
    --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
    +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
    @@ -66,8 +66,8 @@ public:
     
     	// Set the extruder properties.
     	void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed,
    -                      float unloading_speed, float delay, int cooling_moves, float cooling_initial_speed,
    -                      float cooling_final_speed, std::string ramming_parameters, float nozzle_diameter)
    +                      float unloading_speed, float unloading_speed_start, float delay, int cooling_moves,
    +                      float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float nozzle_diameter)
     	{
             //while (m_filpar.size() < idx+1)   // makes sure the required element is in the vector
             m_filpar.push_back(FilamentParameters());
    @@ -77,6 +77,7 @@ public:
             m_filpar[idx].first_layer_temperature = first_layer_temp;
             m_filpar[idx].loading_speed = loading_speed;
             m_filpar[idx].unloading_speed = unloading_speed;
    +        m_filpar[idx].unloading_speed_start = unloading_speed_start;
             m_filpar[idx].delay = delay;
             m_filpar[idx].cooling_moves = cooling_moves;
             m_filpar[idx].cooling_initial_speed = cooling_initial_speed;
    @@ -217,6 +218,7 @@ private:
             int  			    first_layer_temperature = 0;
             float               loading_speed = 0.f;
             float               unloading_speed = 0.f;
    +        float               unloading_speed_start = 0.f;
             float               delay = 0.f ;
             int                 cooling_moves = 0;
             float               cooling_initial_speed = 0.f;
    diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp
    index 7d2906bcc1..4154378ec2 100644
    --- a/xs/src/libslic3r/Print.cpp
    +++ b/xs/src/libslic3r/Print.cpp
    @@ -201,6 +201,7 @@ bool Print::invalidate_state_by_config_options(const std::vectorconfig.first_layer_temperature.get_at(i),
                 this->config.filament_loading_speed.get_at(i),
                 this->config.filament_unloading_speed.get_at(i),
    +            this->config.filament_unloading_speed_start.get_at(i),
                 this->config.filament_toolchange_delay.get_at(i),
                 this->config.filament_cooling_moves.get_at(i),
                 this->config.filament_cooling_initial_speed.get_at(i),
    diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
    index d8f2d85a63..cb09a9d097 100644
    --- a/xs/src/libslic3r/PrintConfig.cpp
    +++ b/xs/src/libslic3r/PrintConfig.cpp
    @@ -482,6 +482,14 @@ PrintConfigDef::PrintConfigDef()
         def->min = 0;
         def->default_value = new ConfigOptionFloats { 90. };
     
    +    def = this->add("filament_unloading_speed_start", coFloats);
    +    def->label = L("EXPERIMENTAL: Unloading speed at the start");
    +    def->tooltip = L("Speed used for unloading the tip of the filament immediately after ramming. ");
    +    def->sidetext = L("mm/s");
    +    def->cli = "filament-unloading-speed-start=f@";
    +    def->min = 0;
    +    def->default_value = new ConfigOptionFloats { 83. };
    +
         def = this->add("filament_toolchange_delay", coFloats);
         def->label = L("Delay after unloading");
         def->tooltip = L("Time to wait after the filament is unloaded. "
    diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp
    index b18603d877..edf7756e81 100644
    --- a/xs/src/libslic3r/PrintConfig.hpp
    +++ b/xs/src/libslic3r/PrintConfig.hpp
    @@ -530,6 +530,7 @@ public:
         ConfigOptionFloats              filament_loading_speed;
         ConfigOptionFloats              filament_load_time;
         ConfigOptionFloats              filament_unloading_speed;
    +    ConfigOptionFloats              filament_unloading_speed_start;
         ConfigOptionFloats              filament_toolchange_delay;
         ConfigOptionFloats              filament_unload_time;
         ConfigOptionInts                filament_cooling_moves;
    @@ -596,6 +597,7 @@ protected:
             OPT_PTR(filament_loading_speed);
             OPT_PTR(filament_load_time);
             OPT_PTR(filament_unloading_speed);
    +        OPT_PTR(filament_unloading_speed_start);
             OPT_PTR(filament_unload_time);
             OPT_PTR(filament_toolchange_delay);
             OPT_PTR(filament_cooling_moves);
    diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp
    index 9f51f7b978..ba27bea8a4 100644
    --- a/xs/src/slic3r/GUI/Preset.cpp
    +++ b/xs/src/slic3r/GUI/Preset.cpp
    @@ -314,8 +314,8 @@ const std::vector& Preset::filament_options()
         static std::vector s_opts {
             "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
             "extrusion_multiplier", "filament_density", "filament_cost", 
    -        "filament_loading_speed", "filament_load_time", "filament_unloading_speed", "filament_unload_time", "filament_toolchange_delay",
    -        "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters",
    +        "filament_loading_speed", "filament_load_time", "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time",
    +        "filament_toolchange_delay", "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters",
             "filament_minimal_purge_on_wipe_tower", "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature",
             "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time",
             "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition",
    diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp
    index 7c4322c5a0..d3307f1f59 100644
    --- a/xs/src/slic3r/GUI/Tab.cpp
    +++ b/xs/src/slic3r/GUI/Tab.cpp
    @@ -1292,6 +1292,7 @@ void TabFilament::build()
             optgroup = page->new_optgroup(_(L("Toolchange parameters with single extruder MM printers")));
     		optgroup->append_single_option_line("filament_loading_speed");
             optgroup->append_single_option_line("filament_unloading_speed");
    +        optgroup->append_single_option_line("filament_unloading_speed_start");
     		optgroup->append_single_option_line("filament_load_time");
     		optgroup->append_single_option_line("filament_unload_time");
             optgroup->append_single_option_line("filament_toolchange_delay");
    
    From 211790f8c35a7f950fddb95839a7b1009470a6ac Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Tue, 7 Aug 2018 11:15:47 +0200
    Subject: [PATCH 088/119] Added qhull library to xs/src and cmake
    
    ---
     xs/CMakeLists.txt                             |    6 +-
     xs/src/qhull/Announce.txt                     |   47 +
     xs/src/qhull/CMakeLists.txt                   |  128 +
     xs/src/qhull/COPYING.txt                      |   38 +
     xs/src/qhull/README.txt                       |  623 +++
     xs/src/qhull/REGISTER.txt                     |   32 +
     xs/src/qhull/html/index.htm                   |  935 ++++
     .../html/normal_voronoi_knauss_oesterle.jpg   |  Bin 0 -> 23924 bytes
     xs/src/qhull/html/qconvex.htm                 |  630 +++
     xs/src/qhull/html/qdelau_f.htm                |  416 ++
     xs/src/qhull/html/qdelaun.htm                 |  628 +++
     xs/src/qhull/html/qh--4d.gif                  |  Bin 0 -> 4372 bytes
     xs/src/qhull/html/qh--cone.gif                |  Bin 0 -> 2946 bytes
     xs/src/qhull/html/qh--dt.gif                  |  Bin 0 -> 3772 bytes
     xs/src/qhull/html/qh--geom.gif                |  Bin 0 -> 318 bytes
     xs/src/qhull/html/qh--half.gif                |  Bin 0 -> 2537 bytes
     xs/src/qhull/html/qh--rand.gif                |  Bin 0 -> 3875 bytes
     xs/src/qhull/html/qh-code.htm                 | 1062 +++++
     xs/src/qhull/html/qh-eg.htm                   |  693 +++
     xs/src/qhull/html/qh-faq.htm                  | 1547 +++++++
     xs/src/qhull/html/qh-get.htm                  |  106 +
     xs/src/qhull/html/qh-impre.htm                |  826 ++++
     xs/src/qhull/html/qh-optc.htm                 |  292 ++
     xs/src/qhull/html/qh-optf.htm                 |  736 +++
     xs/src/qhull/html/qh-optg.htm                 |  274 ++
     xs/src/qhull/html/qh-opto.htm                 |  353 ++
     xs/src/qhull/html/qh-optp.htm                 |  253 +
     xs/src/qhull/html/qh-optq.htm                 |  731 +++
     xs/src/qhull/html/qh-optt.htm                 |  278 ++
     xs/src/qhull/html/qh-quick.htm                |  495 ++
     xs/src/qhull/html/qhalf.htm                   |  626 +++
     xs/src/qhull/html/qhull-cpp.xml               |  214 +
     xs/src/qhull/html/qhull.htm                   |  473 ++
     xs/src/qhull/html/qhull.man                   | 1008 ++++
     xs/src/qhull/html/qhull.txt                   | 1263 +++++
     xs/src/qhull/html/qvoron_f.htm                |  396 ++
     xs/src/qhull/html/qvoronoi.htm                |  667 +++
     xs/src/qhull/html/rbox.htm                    |  277 ++
     xs/src/qhull/html/rbox.man                    |  176 +
     xs/src/qhull/html/rbox.txt                    |  195 +
     xs/src/qhull/index.htm                        |  284 ++
     xs/src/qhull/origCMakeLists.txt               |  426 ++
     xs/src/qhull/src/Changes.txt                  | 2129 +++++++++
     xs/src/qhull/src/libqhull/DEPRECATED.txt      |   29 +
     xs/src/qhull/src/libqhull/Makefile            |  240 +
     xs/src/qhull/src/libqhull/Mborland            |  206 +
     xs/src/qhull/src/libqhull/geom.c              | 1234 +++++
     xs/src/qhull/src/libqhull/geom.h              |  176 +
     xs/src/qhull/src/libqhull/geom2.c             | 2094 +++++++++
     xs/src/qhull/src/libqhull/global.c            | 2217 +++++++++
     xs/src/qhull/src/libqhull/index.htm           |  264 ++
     xs/src/qhull/src/libqhull/io.c                | 4062 +++++++++++++++++
     xs/src/qhull/src/libqhull/io.h                |  159 +
     xs/src/qhull/src/libqhull/libqhull.c          | 1403 ++++++
     xs/src/qhull/src/libqhull/libqhull.h          | 1140 +++++
     xs/src/qhull/src/libqhull/libqhull.pro        |   67 +
     xs/src/qhull/src/libqhull/mem.c               |  576 +++
     xs/src/qhull/src/libqhull/mem.h               |  222 +
     xs/src/qhull/src/libqhull/merge.c             | 3628 +++++++++++++++
     xs/src/qhull/src/libqhull/merge.h             |  178 +
     xs/src/qhull/src/libqhull/poly.c              | 1205 +++++
     xs/src/qhull/src/libqhull/poly.h              |  296 ++
     xs/src/qhull/src/libqhull/poly2.c             | 3222 +++++++++++++
     xs/src/qhull/src/libqhull/qh-geom.htm         |  295 ++
     xs/src/qhull/src/libqhull/qh-globa.htm        |  165 +
     xs/src/qhull/src/libqhull/qh-io.htm           |  305 ++
     xs/src/qhull/src/libqhull/qh-mem.htm          |  145 +
     xs/src/qhull/src/libqhull/qh-merge.htm        |  366 ++
     xs/src/qhull/src/libqhull/qh-poly.htm         |  485 ++
     xs/src/qhull/src/libqhull/qh-qhull.htm        |  279 ++
     xs/src/qhull/src/libqhull/qh-set.htm          |  308 ++
     xs/src/qhull/src/libqhull/qh-stat.htm         |  163 +
     xs/src/qhull/src/libqhull/qh-user.htm         |  271 ++
     xs/src/qhull/src/libqhull/qhull-exports.def   |  417 ++
     xs/src/qhull/src/libqhull/qhull_a.h           |  150 +
     xs/src/qhull/src/libqhull/qhull_p-exports.def |  418 ++
     xs/src/qhull/src/libqhull/qset.c              | 1340 ++++++
     xs/src/qhull/src/libqhull/qset.h              |  490 ++
     xs/src/qhull/src/libqhull/random.c            |  245 +
     xs/src/qhull/src/libqhull/random.h            |   34 +
     xs/src/qhull/src/libqhull/rboxlib.c           |  870 ++++
     xs/src/qhull/src/libqhull/stat.c              |  717 +++
     xs/src/qhull/src/libqhull/stat.h              |  543 +++
     xs/src/qhull/src/libqhull/user.c              |  538 +++
     xs/src/qhull/src/libqhull/user.h              |  909 ++++
     xs/src/qhull/src/libqhull/usermem.c           |   94 +
     xs/src/qhull/src/libqhull/userprintf.c        |   66 +
     xs/src/qhull/src/libqhull/userprintf_rbox.c   |   53 +
     xs/src/qhull/src/libqhull_r/Makefile          |  240 +
     xs/src/qhull/src/libqhull_r/geom2_r.c         | 2096 +++++++++
     xs/src/qhull/src/libqhull_r/geom_r.c          | 1234 +++++
     xs/src/qhull/src/libqhull_r/geom_r.h          |  184 +
     xs/src/qhull/src/libqhull_r/global_r.c        | 2100 +++++++++
     xs/src/qhull/src/libqhull_r/index.htm         |  266 ++
     xs/src/qhull/src/libqhull_r/io_r.c            | 4062 +++++++++++++++++
     xs/src/qhull/src/libqhull_r/io_r.h            |  167 +
     xs/src/qhull/src/libqhull_r/libqhull_r.c      | 1403 ++++++
     xs/src/qhull/src/libqhull_r/libqhull_r.h      | 1134 +++++
     xs/src/qhull/src/libqhull_r/libqhull_r.pro    |   67 +
     xs/src/qhull/src/libqhull_r/mem_r.c           |  562 +++
     xs/src/qhull/src/libqhull_r/mem_r.h           |  234 +
     xs/src/qhull/src/libqhull_r/merge_r.c         | 3627 +++++++++++++++
     xs/src/qhull/src/libqhull_r/merge_r.h         |  186 +
     xs/src/qhull/src/libqhull_r/poly2_r.c         | 3222 +++++++++++++
     xs/src/qhull/src/libqhull_r/poly_r.c          | 1205 +++++
     xs/src/qhull/src/libqhull_r/poly_r.h          |  303 ++
     xs/src/qhull/src/libqhull_r/qh-geom_r.htm     |  295 ++
     xs/src/qhull/src/libqhull_r/qh-globa_r.htm    |  163 +
     xs/src/qhull/src/libqhull_r/qh-io_r.htm       |  305 ++
     xs/src/qhull/src/libqhull_r/qh-mem_r.htm      |  145 +
     xs/src/qhull/src/libqhull_r/qh-merge_r.htm    |  366 ++
     xs/src/qhull/src/libqhull_r/qh-poly_r.htm     |  485 ++
     xs/src/qhull/src/libqhull_r/qh-qhull_r.htm    |  279 ++
     xs/src/qhull/src/libqhull_r/qh-set_r.htm      |  308 ++
     xs/src/qhull/src/libqhull_r/qh-stat_r.htm     |  161 +
     xs/src/qhull/src/libqhull_r/qh-user_r.htm     |  271 ++
     .../qhull/src/libqhull_r/qhull_r-exports.def  |  404 ++
     xs/src/qhull/src/libqhull_r/qhull_ra.h        |  158 +
     xs/src/qhull/src/libqhull_r/qset_r.c          | 1340 ++++++
     xs/src/qhull/src/libqhull_r/qset_r.h          |  502 ++
     xs/src/qhull/src/libqhull_r/random_r.c        |  247 +
     xs/src/qhull/src/libqhull_r/random_r.h        |   41 +
     xs/src/qhull/src/libqhull_r/rboxlib_r.c       |  842 ++++
     xs/src/qhull/src/libqhull_r/stat_r.c          |  682 +++
     xs/src/qhull/src/libqhull_r/stat_r.h          |  533 +++
     xs/src/qhull/src/libqhull_r/user_r.c          |  527 +++
     xs/src/qhull/src/libqhull_r/user_r.h          |  882 ++++
     xs/src/qhull/src/libqhull_r/usermem_r.c       |   94 +
     xs/src/qhull/src/libqhull_r/userprintf_r.c    |   65 +
     .../qhull/src/libqhull_r/userprintf_rbox_r.c  |   53 +
     xs/src/qhull/src/libqhullcpp/Coordinates.cpp  |  198 +
     xs/src/qhull/src/libqhullcpp/Coordinates.h    |  303 ++
     .../src/libqhullcpp/PointCoordinates.cpp      |  348 ++
     .../qhull/src/libqhullcpp/PointCoordinates.h  |  161 +
     xs/src/qhull/src/libqhullcpp/Qhull.cpp        |  352 ++
     xs/src/qhull/src/libqhullcpp/Qhull.h          |  132 +
     xs/src/qhull/src/libqhullcpp/QhullError.h     |   62 +
     xs/src/qhull/src/libqhullcpp/QhullFacet.cpp   |  519 +++
     xs/src/qhull/src/libqhullcpp/QhullFacet.h     |  151 +
     .../qhull/src/libqhullcpp/QhullFacetList.cpp  |  174 +
     xs/src/qhull/src/libqhullcpp/QhullFacetList.h |  106 +
     .../qhull/src/libqhullcpp/QhullFacetSet.cpp   |  147 +
     xs/src/qhull/src/libqhullcpp/QhullFacetSet.h  |   97 +
     .../qhull/src/libqhullcpp/QhullHyperplane.cpp |  187 +
     .../qhull/src/libqhullcpp/QhullHyperplane.h   |  123 +
     xs/src/qhull/src/libqhullcpp/QhullIterator.h  |  173 +
     .../qhull/src/libqhullcpp/QhullLinkedList.h   |  388 ++
     xs/src/qhull/src/libqhullcpp/QhullPoint.cpp   |  203 +
     xs/src/qhull/src/libqhullcpp/QhullPoint.h     |  136 +
     .../qhull/src/libqhullcpp/QhullPointSet.cpp   |   62 +
     xs/src/qhull/src/libqhullcpp/QhullPointSet.h  |   77 +
     xs/src/qhull/src/libqhullcpp/QhullPoints.cpp  |  320 ++
     xs/src/qhull/src/libqhullcpp/QhullPoints.h    |  266 ++
     xs/src/qhull/src/libqhullcpp/QhullQh.cpp      |  237 +
     xs/src/qhull/src/libqhullcpp/QhullQh.h        |  110 +
     xs/src/qhull/src/libqhullcpp/QhullRidge.cpp   |  124 +
     xs/src/qhull/src/libqhullcpp/QhullRidge.h     |  112 +
     xs/src/qhull/src/libqhullcpp/QhullSet.cpp     |   62 +
     xs/src/qhull/src/libqhullcpp/QhullSet.h       |  462 ++
     xs/src/qhull/src/libqhullcpp/QhullSets.h      |   27 +
     xs/src/qhull/src/libqhullcpp/QhullStat.cpp    |   42 +
     xs/src/qhull/src/libqhullcpp/QhullStat.h      |   49 +
     xs/src/qhull/src/libqhullcpp/QhullVertex.cpp  |  112 +
     xs/src/qhull/src/libqhullcpp/QhullVertex.h    |  104 +
     .../qhull/src/libqhullcpp/QhullVertexSet.cpp  |  161 +
     xs/src/qhull/src/libqhullcpp/QhullVertexSet.h |   86 +
     xs/src/qhull/src/libqhullcpp/RboxPoints.cpp   |  224 +
     xs/src/qhull/src/libqhullcpp/RboxPoints.h     |   69 +
     xs/src/qhull/src/libqhullcpp/RoadError.cpp    |  158 +
     xs/src/qhull/src/libqhullcpp/RoadError.h      |   88 +
     xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp |  122 +
     xs/src/qhull/src/libqhullcpp/RoadLogEvent.h   |   77 +
     .../qhull/src/libqhullcpp/functionObjects.h   |   67 +
     xs/src/qhull/src/libqhullcpp/libqhullcpp.pro  |   71 +
     xs/src/qhull/src/libqhullcpp/qt-qhull.cpp     |  130 +
     .../qhull/src/libqhullcpp/usermem_r-cpp.cpp   |   93 +
     .../src/libqhullstatic/libqhullstatic.pro     |   19 +
     .../src/libqhullstatic_r/libqhullstatic_r.pro |   21 +
     xs/src/qhull/src/qconvex/qconvex.c            |  326 ++
     xs/src/qhull/src/qconvex/qconvex.pro          |    9 +
     xs/src/qhull/src/qconvex/qconvex_r.c          |  328 ++
     xs/src/qhull/src/qdelaunay/qdelaun.c          |  315 ++
     xs/src/qhull/src/qdelaunay/qdelaun_r.c        |  317 ++
     xs/src/qhull/src/qdelaunay/qdelaunay.pro      |    9 +
     xs/src/qhull/src/qhalf/qhalf.c                |  316 ++
     xs/src/qhull/src/qhalf/qhalf.pro              |    9 +
     xs/src/qhull/src/qhalf/qhalf_r.c              |  318 ++
     xs/src/qhull/src/qhull-all.pro                |   94 +
     xs/src/qhull/src/qhull-app-c.pri              |   24 +
     xs/src/qhull/src/qhull-app-c_r.pri            |   26 +
     xs/src/qhull/src/qhull-app-cpp.pri            |   23 +
     xs/src/qhull/src/qhull-app-shared.pri         |   27 +
     xs/src/qhull/src/qhull-app-shared_r.pri       |   29 +
     xs/src/qhull/src/qhull-libqhull-src.pri       |   39 +
     xs/src/qhull/src/qhull-libqhull-src_r.pri     |   39 +
     xs/src/qhull/src/qhull-warn.pri               |   57 +
     xs/src/qhull/src/qhull/qhull.pro              |    9 +
     xs/src/qhull/src/qhull/unix.c                 |  372 ++
     xs/src/qhull/src/qhull/unix_r.c               |  374 ++
     .../qhull/src/qhulltest/Coordinates_test.cpp  |  539 +++
     .../src/qhulltest/PointCoordinates_test.cpp   |  478 ++
     .../src/qhulltest/QhullFacetList_test.cpp     |  196 +
     .../src/qhulltest/QhullFacetSet_test.cpp      |  153 +
     .../qhull/src/qhulltest/QhullFacet_test.cpp   |  283 ++
     .../src/qhulltest/QhullHyperplane_test.cpp    |  429 ++
     .../src/qhulltest/QhullLinkedList_test.cpp    |  330 ++
     .../src/qhulltest/QhullPointSet_test.cpp      |  378 ++
     .../qhull/src/qhulltest/QhullPoint_test.cpp   |  437 ++
     .../qhull/src/qhulltest/QhullPoints_test.cpp  |  561 +++
     .../qhull/src/qhulltest/QhullRidge_test.cpp   |  159 +
     xs/src/qhull/src/qhulltest/QhullSet_test.cpp  |  434 ++
     .../src/qhulltest/QhullVertexSet_test.cpp     |  152 +
     .../qhull/src/qhulltest/QhullVertex_test.cpp  |  184 +
     xs/src/qhull/src/qhulltest/Qhull_test.cpp     |  360 ++
     .../qhull/src/qhulltest/RboxPoints_test.cpp   |  215 +
     xs/src/qhull/src/qhulltest/RoadTest.cpp       |   94 +
     xs/src/qhull/src/qhulltest/RoadTest.h         |  102 +
     xs/src/qhull/src/qhulltest/qhulltest.cpp      |   94 +
     xs/src/qhull/src/qhulltest/qhulltest.pro      |   36 +
     xs/src/qhull/src/qvoronoi/qvoronoi.c          |  303 ++
     xs/src/qhull/src/qvoronoi/qvoronoi.pro        |    9 +
     xs/src/qhull/src/qvoronoi/qvoronoi_r.c        |  305 ++
     xs/src/qhull/src/rbox/rbox.c                  |   88 +
     xs/src/qhull/src/rbox/rbox.pro                |    9 +
     xs/src/qhull/src/rbox/rbox_r.c                |   78 +
     xs/src/qhull/src/testqset/testqset.c          |  891 ++++
     xs/src/qhull/src/testqset/testqset.pro        |   30 +
     xs/src/qhull/src/testqset_r/testqset_r.c      |  890 ++++
     xs/src/qhull/src/testqset_r/testqset_r.pro    |   30 +
     xs/src/qhull/src/user_eg/user_eg.c            |  330 ++
     xs/src/qhull/src/user_eg/user_eg.pro          |   11 +
     xs/src/qhull/src/user_eg/user_eg_r.c          |  326 ++
     xs/src/qhull/src/user_eg2/user_eg2.c          |  746 +++
     xs/src/qhull/src/user_eg2/user_eg2.pro        |   11 +
     xs/src/qhull/src/user_eg2/user_eg2_r.c        |  742 +++
     xs/src/qhull/src/user_eg3/user_eg3.pro        |   12 +
     xs/src/qhull/src/user_eg3/user_eg3_r.cpp      |  162 +
     237 files changed, 104145 insertions(+), 1 deletion(-)
     create mode 100644 xs/src/qhull/Announce.txt
     create mode 100644 xs/src/qhull/CMakeLists.txt
     create mode 100644 xs/src/qhull/COPYING.txt
     create mode 100644 xs/src/qhull/README.txt
     create mode 100644 xs/src/qhull/REGISTER.txt
     create mode 100644 xs/src/qhull/html/index.htm
     create mode 100644 xs/src/qhull/html/normal_voronoi_knauss_oesterle.jpg
     create mode 100644 xs/src/qhull/html/qconvex.htm
     create mode 100644 xs/src/qhull/html/qdelau_f.htm
     create mode 100644 xs/src/qhull/html/qdelaun.htm
     create mode 100644 xs/src/qhull/html/qh--4d.gif
     create mode 100644 xs/src/qhull/html/qh--cone.gif
     create mode 100644 xs/src/qhull/html/qh--dt.gif
     create mode 100644 xs/src/qhull/html/qh--geom.gif
     create mode 100644 xs/src/qhull/html/qh--half.gif
     create mode 100644 xs/src/qhull/html/qh--rand.gif
     create mode 100644 xs/src/qhull/html/qh-code.htm
     create mode 100644 xs/src/qhull/html/qh-eg.htm
     create mode 100644 xs/src/qhull/html/qh-faq.htm
     create mode 100644 xs/src/qhull/html/qh-get.htm
     create mode 100644 xs/src/qhull/html/qh-impre.htm
     create mode 100644 xs/src/qhull/html/qh-optc.htm
     create mode 100644 xs/src/qhull/html/qh-optf.htm
     create mode 100644 xs/src/qhull/html/qh-optg.htm
     create mode 100644 xs/src/qhull/html/qh-opto.htm
     create mode 100644 xs/src/qhull/html/qh-optp.htm
     create mode 100644 xs/src/qhull/html/qh-optq.htm
     create mode 100644 xs/src/qhull/html/qh-optt.htm
     create mode 100644 xs/src/qhull/html/qh-quick.htm
     create mode 100644 xs/src/qhull/html/qhalf.htm
     create mode 100644 xs/src/qhull/html/qhull-cpp.xml
     create mode 100644 xs/src/qhull/html/qhull.htm
     create mode 100644 xs/src/qhull/html/qhull.man
     create mode 100644 xs/src/qhull/html/qhull.txt
     create mode 100644 xs/src/qhull/html/qvoron_f.htm
     create mode 100644 xs/src/qhull/html/qvoronoi.htm
     create mode 100644 xs/src/qhull/html/rbox.htm
     create mode 100644 xs/src/qhull/html/rbox.man
     create mode 100644 xs/src/qhull/html/rbox.txt
     create mode 100644 xs/src/qhull/index.htm
     create mode 100644 xs/src/qhull/origCMakeLists.txt
     create mode 100644 xs/src/qhull/src/Changes.txt
     create mode 100644 xs/src/qhull/src/libqhull/DEPRECATED.txt
     create mode 100644 xs/src/qhull/src/libqhull/Makefile
     create mode 100644 xs/src/qhull/src/libqhull/Mborland
     create mode 100644 xs/src/qhull/src/libqhull/geom.c
     create mode 100644 xs/src/qhull/src/libqhull/geom.h
     create mode 100644 xs/src/qhull/src/libqhull/geom2.c
     create mode 100644 xs/src/qhull/src/libqhull/global.c
     create mode 100644 xs/src/qhull/src/libqhull/index.htm
     create mode 100644 xs/src/qhull/src/libqhull/io.c
     create mode 100644 xs/src/qhull/src/libqhull/io.h
     create mode 100644 xs/src/qhull/src/libqhull/libqhull.c
     create mode 100644 xs/src/qhull/src/libqhull/libqhull.h
     create mode 100644 xs/src/qhull/src/libqhull/libqhull.pro
     create mode 100644 xs/src/qhull/src/libqhull/mem.c
     create mode 100644 xs/src/qhull/src/libqhull/mem.h
     create mode 100644 xs/src/qhull/src/libqhull/merge.c
     create mode 100644 xs/src/qhull/src/libqhull/merge.h
     create mode 100644 xs/src/qhull/src/libqhull/poly.c
     create mode 100644 xs/src/qhull/src/libqhull/poly.h
     create mode 100644 xs/src/qhull/src/libqhull/poly2.c
     create mode 100644 xs/src/qhull/src/libqhull/qh-geom.htm
     create mode 100644 xs/src/qhull/src/libqhull/qh-globa.htm
     create mode 100644 xs/src/qhull/src/libqhull/qh-io.htm
     create mode 100644 xs/src/qhull/src/libqhull/qh-mem.htm
     create mode 100644 xs/src/qhull/src/libqhull/qh-merge.htm
     create mode 100644 xs/src/qhull/src/libqhull/qh-poly.htm
     create mode 100644 xs/src/qhull/src/libqhull/qh-qhull.htm
     create mode 100644 xs/src/qhull/src/libqhull/qh-set.htm
     create mode 100644 xs/src/qhull/src/libqhull/qh-stat.htm
     create mode 100644 xs/src/qhull/src/libqhull/qh-user.htm
     create mode 100644 xs/src/qhull/src/libqhull/qhull-exports.def
     create mode 100644 xs/src/qhull/src/libqhull/qhull_a.h
     create mode 100644 xs/src/qhull/src/libqhull/qhull_p-exports.def
     create mode 100644 xs/src/qhull/src/libqhull/qset.c
     create mode 100644 xs/src/qhull/src/libqhull/qset.h
     create mode 100644 xs/src/qhull/src/libqhull/random.c
     create mode 100644 xs/src/qhull/src/libqhull/random.h
     create mode 100644 xs/src/qhull/src/libqhull/rboxlib.c
     create mode 100644 xs/src/qhull/src/libqhull/stat.c
     create mode 100644 xs/src/qhull/src/libqhull/stat.h
     create mode 100644 xs/src/qhull/src/libqhull/user.c
     create mode 100644 xs/src/qhull/src/libqhull/user.h
     create mode 100644 xs/src/qhull/src/libqhull/usermem.c
     create mode 100644 xs/src/qhull/src/libqhull/userprintf.c
     create mode 100644 xs/src/qhull/src/libqhull/userprintf_rbox.c
     create mode 100644 xs/src/qhull/src/libqhull_r/Makefile
     create mode 100644 xs/src/qhull/src/libqhull_r/geom2_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/geom_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/geom_r.h
     create mode 100644 xs/src/qhull/src/libqhull_r/global_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/index.htm
     create mode 100644 xs/src/qhull/src/libqhull_r/io_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/io_r.h
     create mode 100644 xs/src/qhull/src/libqhull_r/libqhull_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/libqhull_r.h
     create mode 100644 xs/src/qhull/src/libqhull_r/libqhull_r.pro
     create mode 100644 xs/src/qhull/src/libqhull_r/mem_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/mem_r.h
     create mode 100644 xs/src/qhull/src/libqhull_r/merge_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/merge_r.h
     create mode 100644 xs/src/qhull/src/libqhull_r/poly2_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/poly_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/poly_r.h
     create mode 100644 xs/src/qhull/src/libqhull_r/qh-geom_r.htm
     create mode 100644 xs/src/qhull/src/libqhull_r/qh-globa_r.htm
     create mode 100644 xs/src/qhull/src/libqhull_r/qh-io_r.htm
     create mode 100644 xs/src/qhull/src/libqhull_r/qh-mem_r.htm
     create mode 100644 xs/src/qhull/src/libqhull_r/qh-merge_r.htm
     create mode 100644 xs/src/qhull/src/libqhull_r/qh-poly_r.htm
     create mode 100644 xs/src/qhull/src/libqhull_r/qh-qhull_r.htm
     create mode 100644 xs/src/qhull/src/libqhull_r/qh-set_r.htm
     create mode 100644 xs/src/qhull/src/libqhull_r/qh-stat_r.htm
     create mode 100644 xs/src/qhull/src/libqhull_r/qh-user_r.htm
     create mode 100644 xs/src/qhull/src/libqhull_r/qhull_r-exports.def
     create mode 100644 xs/src/qhull/src/libqhull_r/qhull_ra.h
     create mode 100644 xs/src/qhull/src/libqhull_r/qset_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/qset_r.h
     create mode 100644 xs/src/qhull/src/libqhull_r/random_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/random_r.h
     create mode 100644 xs/src/qhull/src/libqhull_r/rboxlib_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/stat_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/stat_r.h
     create mode 100644 xs/src/qhull/src/libqhull_r/user_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/user_r.h
     create mode 100644 xs/src/qhull/src/libqhull_r/usermem_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/userprintf_r.c
     create mode 100644 xs/src/qhull/src/libqhull_r/userprintf_rbox_r.c
     create mode 100644 xs/src/qhull/src/libqhullcpp/Coordinates.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/Coordinates.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/PointCoordinates.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/PointCoordinates.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/Qhull.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/Qhull.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullError.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacet.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacet.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacetList.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacetList.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacetSet.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacetSet.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullHyperplane.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullHyperplane.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullIterator.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullLinkedList.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPoint.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPoint.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPointSet.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPointSet.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPoints.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPoints.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullQh.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullQh.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullRidge.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullRidge.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullSet.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullSet.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullSets.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullStat.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullStat.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullVertex.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullVertex.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullVertexSet.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/QhullVertexSet.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/RboxPoints.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/RboxPoints.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/RoadError.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/RoadError.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/RoadLogEvent.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/functionObjects.h
     create mode 100644 xs/src/qhull/src/libqhullcpp/libqhullcpp.pro
     create mode 100644 xs/src/qhull/src/libqhullcpp/qt-qhull.cpp
     create mode 100644 xs/src/qhull/src/libqhullcpp/usermem_r-cpp.cpp
     create mode 100644 xs/src/qhull/src/libqhullstatic/libqhullstatic.pro
     create mode 100644 xs/src/qhull/src/libqhullstatic_r/libqhullstatic_r.pro
     create mode 100644 xs/src/qhull/src/qconvex/qconvex.c
     create mode 100644 xs/src/qhull/src/qconvex/qconvex.pro
     create mode 100644 xs/src/qhull/src/qconvex/qconvex_r.c
     create mode 100644 xs/src/qhull/src/qdelaunay/qdelaun.c
     create mode 100644 xs/src/qhull/src/qdelaunay/qdelaun_r.c
     create mode 100644 xs/src/qhull/src/qdelaunay/qdelaunay.pro
     create mode 100644 xs/src/qhull/src/qhalf/qhalf.c
     create mode 100644 xs/src/qhull/src/qhalf/qhalf.pro
     create mode 100644 xs/src/qhull/src/qhalf/qhalf_r.c
     create mode 100644 xs/src/qhull/src/qhull-all.pro
     create mode 100644 xs/src/qhull/src/qhull-app-c.pri
     create mode 100644 xs/src/qhull/src/qhull-app-c_r.pri
     create mode 100644 xs/src/qhull/src/qhull-app-cpp.pri
     create mode 100644 xs/src/qhull/src/qhull-app-shared.pri
     create mode 100644 xs/src/qhull/src/qhull-app-shared_r.pri
     create mode 100644 xs/src/qhull/src/qhull-libqhull-src.pri
     create mode 100644 xs/src/qhull/src/qhull-libqhull-src_r.pri
     create mode 100644 xs/src/qhull/src/qhull-warn.pri
     create mode 100644 xs/src/qhull/src/qhull/qhull.pro
     create mode 100644 xs/src/qhull/src/qhull/unix.c
     create mode 100644 xs/src/qhull/src/qhull/unix_r.c
     create mode 100644 xs/src/qhull/src/qhulltest/Coordinates_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/PointCoordinates_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/QhullFacetList_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/QhullFacetSet_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/QhullFacet_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/QhullHyperplane_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/QhullLinkedList_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/QhullPointSet_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/QhullPoint_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/QhullPoints_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/QhullRidge_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/QhullSet_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/QhullVertexSet_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/QhullVertex_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/Qhull_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/RboxPoints_test.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/RoadTest.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/RoadTest.h
     create mode 100644 xs/src/qhull/src/qhulltest/qhulltest.cpp
     create mode 100644 xs/src/qhull/src/qhulltest/qhulltest.pro
     create mode 100644 xs/src/qhull/src/qvoronoi/qvoronoi.c
     create mode 100644 xs/src/qhull/src/qvoronoi/qvoronoi.pro
     create mode 100644 xs/src/qhull/src/qvoronoi/qvoronoi_r.c
     create mode 100644 xs/src/qhull/src/rbox/rbox.c
     create mode 100644 xs/src/qhull/src/rbox/rbox.pro
     create mode 100644 xs/src/qhull/src/rbox/rbox_r.c
     create mode 100644 xs/src/qhull/src/testqset/testqset.c
     create mode 100644 xs/src/qhull/src/testqset/testqset.pro
     create mode 100644 xs/src/qhull/src/testqset_r/testqset_r.c
     create mode 100644 xs/src/qhull/src/testqset_r/testqset_r.pro
     create mode 100644 xs/src/qhull/src/user_eg/user_eg.c
     create mode 100644 xs/src/qhull/src/user_eg/user_eg.pro
     create mode 100644 xs/src/qhull/src/user_eg/user_eg_r.c
     create mode 100644 xs/src/qhull/src/user_eg2/user_eg2.c
     create mode 100644 xs/src/qhull/src/user_eg2/user_eg2.pro
     create mode 100644 xs/src/qhull/src/user_eg2/user_eg2_r.c
     create mode 100644 xs/src/qhull/src/user_eg3/user_eg3.pro
     create mode 100644 xs/src/qhull/src/user_eg3/user_eg3_r.cpp
    
    diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
    index d41b4c13a9..f1609bc14a 100644
    --- a/xs/CMakeLists.txt
    +++ b/xs/CMakeLists.txt
    @@ -459,7 +459,7 @@ if(APPLE)
         # Ignore undefined symbols of the perl interpreter, they will be found in the caller image.
         target_link_libraries(XS "-undefined dynamic_lookup")
     endif()
    -target_link_libraries(XS libslic3r libslic3r_gui admesh miniz clipper nowide polypartition poly2tri semver avrdude)
    +target_link_libraries(XS libslic3r libslic3r_gui admesh miniz clipper nowide polypartition poly2tri semver avrdude qhull)
     if(SLIC3R_PROFILE)
         target_link_libraries(XS Shiny)
     endif()
    @@ -540,6 +540,10 @@ endif()
     
     add_subdirectory(src/avrdude)
     
    +add_subdirectory(src/qhull)
    +include_directories(${LIBDIR}/qhull/src)
    +message(STATUS ${LIBDIR}/qhull/src)
    +
     ## REQUIRED packages
     
     # Find and configure boost
    diff --git a/xs/src/qhull/Announce.txt b/xs/src/qhull/Announce.txt
    new file mode 100644
    index 0000000000..635cff1afb
    --- /dev/null
    +++ b/xs/src/qhull/Announce.txt
    @@ -0,0 +1,47 @@
    +
    + Qhull 2015.2  2016/01/18
    +
    +        http://www.qhull.org
    +        git@github.com:qhull/qhull.git
    +        http://www.geomview.org
    +
    +Qhull computes convex hulls, Delaunay triangulations, Voronoi diagrams,
    +furthest-site Voronoi diagrams, and halfspace intersections about a point.
    +It runs in 2-d, 3-d, 4-d, or higher.  It implements the Quickhull algorithm
    +for computing convex hulls.   Qhull handles round-off errors from floating
    +point arithmetic.  It can approximate a convex hull.
    +
    +The program includes options for hull volume, facet area, partial hulls,
    +input transformations, randomization, tracing, multiple output formats, and
    +execution statistics.  The program can be called from within your application.
    +You can view the results in 2-d, 3-d and 4-d with Geomview.
    +
    +To download Qhull:
    +        http://www.qhull.org/download
    +        git@github.com:qhull/qhull.git
    + 
    +Download qhull-96.ps for:
    +
    +        Barber, C. B., D.P. Dobkin, and H.T. Huhdanpaa, "The
    +        Quickhull Algorithm for Convex Hulls," ACM Trans. on
    +        Mathematical Software, 22(4):469-483, Dec. 1996.
    +        http://www.acm.org/pubs/citations/journals/toms/1996-22-4/p469-barber/
    +        http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.117.405
    +
    +Abstract:
    +
    +The convex hull of a set of points is the smallest convex set that contains
    +the points.  This article presents a practical convex hull algorithm that
    +combines the two-dimensional Quickhull Algorithm with  the general dimension
    +Beneath-Beyond Algorithm.  It is similar to the randomized, incremental
    +algorithms for convex hull and Delaunay triangulation.  We provide empirical
    +evidence that the algorithm runs faster when the input contains non-extreme
    +points, and that it uses less memory.
    +
    +Computational geometry algorithms have traditionally assumed that input sets
    +are well behaved.  When an algorithm is implemented with floating point
    +arithmetic, this assumption can lead to serious errors.  We briefly describe
    +a solution to this problem when computing the convex hull in two, three, or
    +four dimensions.  The output is a set of "thick" facets that contain all
    +possible exact convex hulls of the input.   A variation is effective in five
    +or more dimensions.
    diff --git a/xs/src/qhull/CMakeLists.txt b/xs/src/qhull/CMakeLists.txt
    new file mode 100644
    index 0000000000..d798b018fa
    --- /dev/null
    +++ b/xs/src/qhull/CMakeLists.txt
    @@ -0,0 +1,128 @@
    +
    +# This CMake file is written specifically to integrate qhull library with Slic3rPE
    +# (see https://github.com/prusa3d/Slic3r for more information about the project)
    +#
    +# Only original libraries qhullstatic_r and qhullcpp are included.
    +# They are built as a single statically linked library.
    +#
    +# Created by modification of the original qhull CMakeLists.
    +# Lukas Matena (25.7.2018), lukasmatena@seznam.cz
    +
    +
    +project(qhull)
    +cmake_minimum_required(VERSION 2.6)
    +
    +# Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, qhull-warn.pri
    +set(qhull_VERSION2 "2015.2 2016/01/18")  # not used, See global.c, global_r.c, rbox.c, rbox_r.c
    +set(qhull_VERSION     "7.2.0")  # Advance every release
    +
    +#include(CMakeModules/CheckLFS.cmake)
    +#option(WITH_LFS "Enable Large File Support" ON)
    +#check_lfs(WITH_LFS)
    +
    +
    +message(STATUS "qhull Version: ${qhull_VERSION} (static linking)")
    +
    +
    +set(libqhull_HEADERS
    +    # reentrant qhull HEADERS:
    +        src/libqhull_r/libqhull_r.h
    +        src/libqhull_r/geom_r.h
    +        src/libqhull_r/io_r.h
    +        src/libqhull_r/mem_r.h
    +        src/libqhull_r/merge_r.h
    +        src/libqhull_r/poly_r.h
    +        src/libqhull_r/qhull_ra.h
    +        src/libqhull_r/qset_r.h
    +        src/libqhull_r/random_r.h
    +        src/libqhull_r/stat_r.h
    +        src/libqhull_r/user_r.h
    +
    +    # C++ interface to reentrant Qhull HEADERS:
    +        src/libqhullcpp/Coordinates.h
    +        src/libqhullcpp/functionObjects.h
    +        src/libqhullcpp/PointCoordinates.h
    +        src/libqhullcpp/Qhull.h
    +        src/libqhullcpp/QhullError.h
    +        src/libqhullcpp/QhullFacet.h
    +        src/libqhullcpp/QhullFacetList.h
    +        src/libqhullcpp/QhullFacetSet.h
    +        src/libqhullcpp/QhullHyperplane.h
    +        src/libqhullcpp/QhullIterator.h
    +        src/libqhullcpp/QhullLinkedList.h
    +        src/libqhullcpp/QhullPoint.h
    +        src/libqhullcpp/QhullPoints.h
    +        src/libqhullcpp/QhullPointSet.h
    +        src/libqhullcpp/QhullQh.h
    +        src/libqhullcpp/QhullRidge.h
    +        src/libqhullcpp/QhullSet.h
    +        src/libqhullcpp/QhullSets.h
    +        src/libqhullcpp/QhullStat.h
    +        src/libqhullcpp/QhullVertex.h
    +        src/libqhullcpp/QhullVertexSet.h
    +        src/libqhullcpp/RboxPoints.h
    +        src/libqhullcpp/RoadError.h
    +        src/libqhullcpp/RoadLogEvent.h
    +        src/qhulltest/RoadTest.h
    +)
    +
    +set(libqhull_SOURCES
    +    # reentrant qhull SOURCES:
    +        src/libqhull_r/global_r.c
    +        src/libqhull_r/stat_r.c
    +        src/libqhull_r/geom2_r.c
    +        src/libqhull_r/poly2_r.c
    +        src/libqhull_r/merge_r.c
    +        src/libqhull_r/libqhull_r.c
    +        src/libqhull_r/geom_r.c
    +        src/libqhull_r/poly_r.c
    +        src/libqhull_r/qset_r.c
    +        src/libqhull_r/mem_r.c
    +        src/libqhull_r/random_r.c
    +        src/libqhull_r/usermem_r.c
    +        src/libqhull_r/userprintf_r.c
    +        src/libqhull_r/io_r.c
    +        src/libqhull_r/user_r.c
    +        src/libqhull_r/rboxlib_r.c
    +        src/libqhull_r/userprintf_rbox_r.c
    +
    +    # C++ interface to reentrant Qhull SOURCES:
    +        src/libqhullcpp/Coordinates.cpp
    +        src/libqhullcpp/PointCoordinates.cpp
    +        src/libqhullcpp/Qhull.cpp
    +        src/libqhullcpp/QhullFacet.cpp
    +        src/libqhullcpp/QhullFacetList.cpp
    +        src/libqhullcpp/QhullFacetSet.cpp
    +        src/libqhullcpp/QhullHyperplane.cpp
    +        src/libqhullcpp/QhullPoint.cpp
    +        src/libqhullcpp/QhullPointSet.cpp
    +        src/libqhullcpp/QhullPoints.cpp
    +        src/libqhullcpp/QhullQh.cpp
    +        src/libqhullcpp/QhullRidge.cpp
    +        src/libqhullcpp/QhullSet.cpp
    +        src/libqhullcpp/QhullStat.cpp
    +        src/libqhullcpp/QhullVertex.cpp
    +        src/libqhullcpp/QhullVertexSet.cpp
    +        src/libqhullcpp/RboxPoints.cpp
    +        src/libqhullcpp/RoadError.cpp
    +        src/libqhullcpp/RoadLogEvent.cpp
    +
    +    # headers for both (libqhullr and libqhullcpp:
    +        ${libqhull_HEADERS}
    +)
    +
    +
    +##################################################
    +# combined library (reentrant qhull and qhullcpp) for Slic3r:
    +set(qhull_STATIC qhull)
    +add_library(${qhull_STATIC} STATIC ${libqhull_SOURCES})
    +set_target_properties(${qhull_STATIC} PROPERTIES
    +    VERSION ${qhull_VERSION})
    +
    +if(UNIX)
    +    target_link_libraries(${qhull_STATIC} m)
    +endif(UNIX)
    +##################################################
    +
    +# LIBDIR is defined in the main xs CMake file:
    +target_include_directories(${qhull_STATIC} PRIVATE ${LIBDIR}/qhull/src)
    diff --git a/xs/src/qhull/COPYING.txt b/xs/src/qhull/COPYING.txt
    new file mode 100644
    index 0000000000..2895ec6a32
    --- /dev/null
    +++ b/xs/src/qhull/COPYING.txt
    @@ -0,0 +1,38 @@
    +                    Qhull, Copyright (c) 1993-2015
    +                    
    +                            C.B. Barber
    +                           Arlington, MA 
    +                          
    +                               and
    +
    +       The National Science and Technology Research Center for
    +        Computation and Visualization of Geometric Structures
    +                        (The Geometry Center)
    +                       University of Minnesota
    +
    +                       email: qhull@qhull.org
    +
    +This software includes Qhull from C.B. Barber and The Geometry Center.  
    +Qhull is copyrighted as noted above.  Qhull is free software and may 
    +be obtained via http from www.qhull.org.  It may be freely copied, modified, 
    +and redistributed under the following conditions:
    +
    +1. All copyright notices must remain intact in all files.
    +
    +2. A copy of this text file must be distributed along with any copies 
    +   of Qhull that you redistribute; this includes copies that you have 
    +   modified, or copies of programs or other software products that 
    +   include Qhull.
    +
    +3. If you modify Qhull, you must include a notice giving the
    +   name of the person performing the modification, the date of
    +   modification, and the reason for such modification.
    +
    +4. When distributing modified versions of Qhull, or other software 
    +   products that include Qhull, you must provide notice that the original 
    +   source code may be obtained as noted above.
    +
    +5. There is no warranty or other guarantee of fitness for Qhull, it is 
    +   provided solely "as is".  Bug reports or fixes may be sent to 
    +   qhull_bug@qhull.org; the authors may or may not act on them as 
    +   they desire.
    diff --git a/xs/src/qhull/README.txt b/xs/src/qhull/README.txt
    new file mode 100644
    index 0000000000..f4c7a3b220
    --- /dev/null
    +++ b/xs/src/qhull/README.txt
    @@ -0,0 +1,623 @@
    +This distribution of qhull library is only meant for interfacing qhull with Slic3rPE
    +(https://github.com/prusa3d/Slic3r).
    +
    +The qhull source file was acquired from https://github.com/qhull/qhull at revision
    +f0bd8ceeb84b554d7cdde9bbfae7d3351270478c.
    +
    +No changes to the qhull library were made, except for
    +- setting REALfloat=1 in user_r.h to enforce calculations in floats
    +- modifying CMakeLists.txt (the original was renamed to origCMakeLists.txt)
    +
    +Many thanks to C. Bradford Barber and all contributors.
    +
    +Lukas Matena (lukasmatena@seznam.cz)
    +25.7.2018
    +
    +
    +See original contents of the README file below.
    +
    +======================================================================================
    +======================================================================================
    +======================================================================================
    +
    +
    +Name
    +
    +      qhull, rbox         2015.2       2016/01/18
    +  
    +Convex hull, Delaunay triangulation, Voronoi diagrams, Halfspace intersection
    + 
    +      Documentation:
    +        html/index.htm
    +        
    +
    +      Available from:
    +        
    +        
    +         (git@github.com:qhull/qhull.git)
    + 
    +      News and a paper:
    +        
    +        
    +
    +     Version 1 (simplicial only):
    +        
    +       
    +Purpose
    +
    +  Qhull is a general dimension convex hull program that reads a set 
    +  of points from stdin, and outputs the smallest convex set that contains 
    +  the points to stdout.  It also generates Delaunay triangulations, Voronoi 
    +  diagrams, furthest-site Voronoi diagrams, and halfspace intersections
    +  about a point.
    +
    +  Rbox is a useful tool in generating input for Qhull; it generates 
    +  hypercubes, diamonds, cones, circles, simplices, spirals, 
    +  lattices, and random points.
    +  
    +  Qhull produces graphical output for Geomview.  This helps with
    +  understanding the output. 
    +
    +Environment requirements
    +
    +  Qhull and rbox should run on all 32-bit and 64-bit computers.  Use
    +  an ANSI C or C++ compiler to compile the program.  The software is 
    +  self-contained.  It comes with examples and test scripts.
    +  
    +  Qhull's C++ interface uses the STL.  The C++ test program uses QTestLib 
    +  from the Qt Framework.  Qhull's C++ interface may change without 
    +  notice.  Eventually, it will move into the qhull shared library.
    +  
    +  Qhull is copyrighted software.  Please read COPYING.txt and REGISTER.txt
    +  before using or distributing Qhull.
    +
    +To cite Qhull, please use
    +
    +  Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., "The Quickhull 
    +  algorithm for convex hulls," ACM Trans. on Mathematical Software,
    +  22(4):469-483, Dec 1996, http://www.qhull.org.
    +
    +To modify Qhull, particularly the C++ interface
    +
    +  Qhull is on GitHub 
    +     (http://github.com/qhull/qhull, git@github.com:qhull/qhull.git)
    +  
    +  For internal documentation, see html/qh-code.htm
    +
    +To install Qhull
    +
    +  Qhull is precompiled for Windows 32-bit, otherwise it needs compilation.
    +  
    +  Qhull includes Makefiles for gcc and other targets, CMakeLists.txt for CMake,
    +  .sln/.vcproj/.vcxproj files for Microsoft Visual Studio, and .pro files 
    +  for Qt Creator.  It compiles under Windows with mingw.
    +  
    +  Install and build instructions follow.  
    +  
    +  See the end of this document for a list of distributed files.
    +
    +-----------------
    +Installing Qhull on Windows 10, 8, 7 (32- or 64-bit), Windows XP, and Windows NT
    +
    +  The zip file contains rbox.exe, qhull.exe, qconvex.exe, qdelaunay.exe, 
    +  qhalf.exe, qvoronoi.exe, testqset.exe, user_eg*.exe, documentation files, 
    +  and source files.  Qhull.exe and user-eg3.exe are compiled with the reentrant 
    +  library while the other executables use the non-reentrant library.
    +  
    +  To install Qhull:
    +  - Unzip the files into a directory (e.g., named 'qhull')
    +  - Click on QHULL-GO or open a command window into Qhull's bin directory.
    +  - Test with 'rbox D4 | qhull'
    +    
    +  To uninstall Qhull
    +  - Delete the qhull directory
    +  
    +  To learn about Qhull:
    +  - Execute 'qconvex' for a synopsis and examples.
    +  - Execute 'rbox 10 | qconvex' to compute the convex hull of 10 random points.
    +  - Execute 'rbox 10 | qconvex i TO file' to write results to 'file'.
    +  - Browse the documentation: qhull\html\index.htm
    +  - If an error occurs, Windows sends the error to stdout instead of stderr.
    +    Use 'TO xxx' to send normal output to xxx
    +
    +  To improve the command window
    +  - Double-click the window bar to increase the size of the window
    +  - Right-click the window bar
    +  - Select Properties
    +  - Check QuickEdit Mode
    +    Select text with right-click or Enter
    +    Paste text with right-click
    +  - Change Font to Lucinda Console
    +  - Change Layout to Screen Buffer Height 999, Window Size Height 55
    +  - Change Colors to Screen Background White, Screen Text Black
    +  - Click OK
    +  - Select 'Modify shortcut that started this window', then OK
    +
    +  If you use qhull a lot, install a bash shell such as
    +    MSYS (www.mingw.org/wiki/msys), Road Bash (www.qhull.org/bash), 
    +    or Cygwin (www.cygwin.com).
    +
    +-----------------
    +Installing Qhull on Unix with gcc
    +
    +  To build Qhull, static libraries, shared library, and C++ interface
    +  - Download and extract Qhull (either GitHub, .tgz file, or .zip file)
    +  - make
    +  - export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH
    +
    +  The Makefiles may be edited for other compilers.
    +  If 'testqset' exits with an error, qhull is broken
    +  
    +  A simple Makefile for Qhull is in src/libqhull and src/libqhull_r.
    +  To build the Qhull executables and libqhullstatic
    +  - Extract Qhull from qhull...tgz or qhull...zip
    +  - cd src/libqhull_r  # cd src/libqhull 
    +  - make
    +
    +  
    +-----------------
    +Installing Qhull with CMake 2.6 or later
    +
    +  See CMakeLists.txt for examples and further build instructions
    +  
    +  To build Qhull, static libraries, shared library, and C++ interface
    +  - Download and extract Qhull (either GitHub, .tgz file, or .zip file)
    +  - cd build
    +  - cmake --help  # List build generators
    +  - make -G "" .. && cmake ..  
    +  - cmake ..
    +  - make
    +  - make install
    +
    +  The ".." is important.  It refers to the parent directory (i.e., qhull/)
    +
    +  On Windows, CMake installs to C:/Program Files/qhull.  64-bit generators
    +  have a "Win64" tag.
    +  
    +  If creating a qhull package, please include a pkg-config file based on build/qhull*.pc.in
    +
    +  If cmake fails with "No CMAKE_C_COMPILER could be found"
    +  - cmake was not able to find the build environment specified by -G "..."
    +  
    +-----------------
    +Installing Qhull with Qt
    +
    +  To build Qhull, including its C++ test (qhulltest)
    +  - Download and extract Qhull (either GitHub, .tgz file, or .zip file)
    +  - Load src/qhull-all.pro into QtCreator
    +  - Build
    +
    +-------------------
    +Working with Qhull's C++ interface
    +
    +  See html/qh-code.htm#cpp for calling Qhull from C++ programs
    +  
    +  See html/qh-code.htm#reentrant for converting from Qhull-2012
    +
    +  Examples of using the C++ interface
    +    user_eg3_r.cpp
    +    qhulltest/*_test.cpp
    +
    +  Qhull's C++ interface is likely to change.  Stay current with GitHub.
    +
    +  To clone Qhull's next branch from http://github.com/qhull/qhull
    +    git init
    +    git clone git@github.com:qhull/qhull.git
    +    cd qhull
    +    git checkout next
    +    ...
    +    git pull origin next
    +    
    +  Compile qhullcpp and libqhullstatic_r with the same compiler.  Both libraries
    +  use the C routines setjmp() and longjmp() for error handling.  They must 
    +  be compiled with the same compiler.
    +  
    +-------------------
    +Calling Qhull from C programs
    +
    +  See html/qh-code.htm#library for calling Qhull from C programs
    +
    +  See html/qh-code.htm#reentrant for converting from Qhull-2012
    +
    +  Warning: You will need to understand Qhull's data structures and read the 
    +  code.  Most users will find it easier to call Qhull as an external command.
    +
    +  The new, reentrant 'C' code (src/libqhull_r), passes a pointer to qhT 
    +  to most Qhull routines.  This allows multiple instances of Qhull to run 
    +  at the same time.  It simplifies the C++ interface.
    +
    +  The non-reentrant 'C' code (src/libqhull) looks unusual.  It refers to 
    +  Qhull's global data structure, qhT, through a 'qh' macro (e.g., 'qh ferr'). 
    +  This allows the same code to use static memory or heap memory. 
    +  If qh_QHpointer is defined, qh_qh is a pointer to an allocated qhT; 
    +  otherwise qh_qh is a global static data structure of type qhT.
    +
    +------------------
    +Compiling Qhull with Microsoft Visual C++
    +
    +  To compile 32-bit Qhull with Microsoft Visual C++ 2010 and later
    +  - Download and extract Qhull (either GitHub, .tgz file, or .zip file)
    +  - Load solution build/qhull-32.sln 
    +  - Build target 'Win32'
    +  - Project qhulltest requires Qt for DevStudio (http://www.qt.io)
    +    Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/5.2.0/5.2.0/msvc2012)
    +    If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified'
    +
    +  To compile 64-bit Qhull with Microsoft Visual C++ 2010 and later
    +  - 64-bit Qhull has larger data structures due to 64-bit pointers
    +  - Download and extract Qhull (either GitHub, .tgz file, or .zip file)
    +  - Load solution build/qhull-64.sln 
    +  - Build target 'Win32'
    +  - Project qhulltest requires Qt for DevStudio (http://www.qt.io)
    +    Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/5.2.0/5.2.0/msvc2012_64)
    +    If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified'
    +  
    +  To compile Qhull with Microsoft Visual C++ 2005 (vcproj files)
    +  - Download and extract Qhull (either GitHub, .tgz file, or .zip file)
    +  - Load solution build/qhull.sln 
    +  - Build target 'win32' (not 'x64')
    +  - Project qhulltest requires Qt for DevStudio (http://www.qt.io)
    +    Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/4.7.4)
    +    If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified'
    +  
    +-----------------
    +Compiling Qhull with Qt Creator
    +
    +  Qt (http://www.qt.io) is a C++ framework for Windows, Linux, and Macintosh
    +
    +  Qhull uses QTestLib to test qhull's C++ interface (see src/qhulltest/)
    +  
    +  To compile Qhull with Qt Creator
    +  - Download and extract Qhull (either GitHub, .tgz file, or .zip file)
    +  - Download the Qt SDK
    +  - Start Qt Creator
    +  - Load src/qhull-all.pro
    +  - Build
    +
    +-----------------
    +Compiling Qhull with mingw on Windows
    +
    +  To compile Qhull with MINGW
    +  - Download and extract Qhull (either GitHub, .tgz file, or .zip file)
    +  - Install Road Bash (http://www.qhull.org/bash)
    +    or install MSYS (http://www.mingw.org/wiki/msys)
    +  - Install MINGW-w64 (http://sourceforge.net/projects/mingw-w64).  
    +    Mingw is included with Qt SDK.  
    +  - make
    +  
    +-----------------
    +Compiling Qhull with cygwin on Windows
    +
    +  To compile Qhull with cygwin
    +  - Download and extract Qhull (either GitHub, .tgz file, or .zip file)
    +  - Install cygwin (http://www.cygwin.com)
    +  - Include packages for gcc, make, ar, and ln
    +  - make
    +
    +-----------------
    +Compiling from Makfile without gcc
    +
    +  The file, qhull-src.tgz, contains documentation and source files for
    +  qhull and rbox.  
    +  
    +  To unpack the tgz file
    +  - tar zxf qhull-src.tgz
    +  - cd qhull
    +  - Use qhull/Makefile
    +   Simpler Makefiles are qhull/src/libqhull/Makefile and qhull/src/libqhull_r/Makefile
    +  
    +  Compiling qhull and rbox with Makefile
    +  - in Makefile, check the CC, CCOPTS1, PRINTMAN, and PRINTC defines
    +      - the defaults are gcc and enscript
    +      - CCOPTS1 should include the ANSI flag.  It defines __STDC__
    +  - in user.h, check the definitions of qh_SECticks and qh_CPUclock.
    +      - use '#define qh_CLOCKtype 2' for timing runs longer than 1 hour
    +  - type: make 
    +      - this builds: qhull qconvex qdelaunay qhalf qvoronoi rbox libqhull.a libqhull_r.a
    +  - type: make doc
    +      - this prints the man page
    +      - See also qhull/html/index.htm
    +  - if your compiler reports many errors, it is probably not a ANSI C compiler
    +      - you will need to set the -ansi switch or find another compiler
    +  - if your compiler warns about missing prototypes for fprintf() etc.
    +      - this is ok, your compiler should have these in stdio.h
    +  - if your compiler warns about missing prototypes for memset() etc.
    +      - include memory.h in qhull_a.h
    +  - if your compiler reports "global.c: storage size of 'qh_qh' isn't known"
    +      - delete the initializer "={0}" in global.c, stat.c and mem.c
    +  - if your compiler warns about "stat.c: improper initializer"
    +      - this is ok, the initializer is not used
    +  - if you have trouble building libqhull.a with 'ar'
    +      - try 'make -f Makefile.txt qhullx' 
    +  - if the code compiles, the qhull test case will automatically execute
    +  - if an error occurs, there's an incompatibility between machines
    +      - If you can, try a different compiler 
    +      - You can turn off the Qhull memory manager with qh_NOmem in mem.h
    +      - You can turn off compiler optimization (-O2 in Makefile)
    +      - If you find the source of the problem, please let us know
    +  - to install the programs and their man pages:
    +      - define MANDIR and BINDIR
    +      - type 'make install'
    +
    +  - if you have Geomview (www.geomview.org)
    +       - try  'rbox 100 | qconvex G >a' and load 'a' into Geomview
    +       - run 'q_eg' for Geomview examples of Qhull output (see qh-eg.htm)
    +
    +------------------
    +Compiling on other machines and compilers
    +
    +  Qhull may compile with Borland C++ 5.0 bcc32.  A Makefile is included.
    +  Execute 'cd src/libqhull; make -f Mborland'.  If you use the Borland IDE, set
    +  the ANSI option in Options:Project:Compiler:Source:Language-compliance.
    +  
    +  Qhull may compile with Borland C++ 4.02 for Win32 and DOS Power Pack.  
    +  Use 'cd src/libqhull; make -f Mborland -D_DPMI'.  Qhull 1.0 compiles with 
    +  Borland C++ 4.02.  For rbox 1.0, use "bcc32 -WX -w- -O2-e -erbox -lc rbox.c".  
    +  Use the same options for Qhull 1.0. [D. Zwick]
    +  
    +  If you have troubles with the memory manager, you can turn it off by
    +  defining qh_NOmem in mem.h.
    +
    +-----------------
    +Distributed files
    +
    +  README.txt           // Instructions for installing Qhull 
    +  REGISTER.txt         // Qhull registration 
    +  COPYING.txt          // Copyright notice 
    +  QHULL-GO.lnk         // Windows icon for eg/qhull-go.bat
    +  Announce.txt         // Announcement 
    +  CMakeLists.txt       // CMake build file (2.6 or later)
    +  CMakeModules/CheckLFS.cmake // enables Large File Support in cmake
    +  File_id.diz          // Package descriptor
    +  index.htm            // Home page 
    +  Makefile             // Makefile for gcc and other compilers
    +  qhull*.md5sum        // md5sum for all files
    +
    +  bin/*                // Qhull executables and dll (.zip only)
    +  build/qhull*.pc.in   // pkg-config templates for qhull_r, qhull, and qhull_p
    +  build/qhull-32.sln   // 32-bit DevStudio solution and project files (2010 and later)
    +  build/*-32.vcxproj
    +  build/qhull-64.sln   // 64-bit DevStudio solution and project files (2010 and later)
    +  build/*-64.vcxproj
    +  build/qhull.sln      // DevStudio solution and project files (2005 and 2009)
    +  build/*.vcproj
    +  eg/*                 // Test scripts and geomview files from q_eg
    +  html/index.htm       // Manual
    +  html/qh-faq.htm      // Frequently asked questions
    +  html/qh-get.htm      // Download page
    +  html/qhull-cpp.xml   // C++ style notes as a Road FAQ (www.qhull.org/road)
    +  src/Changes.txt      // Change history for Qhull and rbox 
    +  src/qhull-all.pro    // Qt project
    +
    +eg/ 
    +  q_eg                 // shell script for Geomview examples (eg.01.cube)
    +  q_egtest             // shell script for Geomview test examples
    +  q_test               // shell script to test qhull
    +  q_test-ok.txt        // output from q_test
    +  qhulltest-ok.txt     // output from qhulltest (Qt only)
    +  make-vcproj.sh       // bash shell script to create vcproj and vcxprog files
    +  qhull-zip.sh	       // bash shell script for distribution files
    +
    +rbox consists of (bin, html):
    +  rbox.exe             // Win32 executable (.zip only) 
    +  rbox.htm             // html manual 
    +  rbox.man             // Unix man page 
    +  rbox.txt
    +
    +qhull consists of (bin, html):
    +  qconvex.exe          // Win32 executables and dlls (.zip download only) 
    +  qhull.exe            // Built with the reentrant library (about 2% slower)
    +  qdelaunay.exe
    +  qhalf.exe
    +  qvoronoi.exe
    +  qhull_r.dll
    +  qhull-go.bat         // command window
    +  qconvex.htm          // html manual
    +  qdelaun.htm
    +  qdelau_f.htm        
    +  qhalf.htm
    +  qvoronoi.htm
    +  qvoron_f.htm
    +  qh-eg.htm
    +  qh-code.htm
    +  qh-impre.htm
    +  index.htm
    +  qh-opt*.htm
    +  qh-quick.htm
    +  qh--*.gif            // images for manual
    +  normal_voronoi_knauss_oesterle.jpg
    +  qhull.man            // Unix man page 
    +  qhull.txt
    +
    +bin/
    +  msvcr80.dll          // Visual C++ redistributable file (.zip download only)
    +
    +src/
    +  qhull/unix.c         // Qhull and rbox applications using non-reentrant libqhullstatic.a
    +  rbox/rbox.c
    +  qconvex/qconvex.c    
    +  qhalf/qhalf.c
    +  qdelaunay/qdelaunay.c
    +  qvoronoi/qvoronoi.c
    +
    +  qhull/unix_r.c        // Qhull and rbox applications using reentrant libqhullstatic_r.a
    +  rbox/rbox_r.c
    +  qconvex/qconvex_r.c   // Qhull applications built with reentrant libqhull_r/Makefile  
    +  qhalf/qhalf_r.c
    +  qdelaunay/qdelaun_r.c
    +  qvoronoi/qvoronoi_r.c
    +
    +  user_eg/user_eg_r.c     // example of using qhull_r.dll from a user program
    +  user_eg2/user_eg2_r.c   // example of using libqhullstatic_r.a from a user program
    +  user_eg3/user_eg3_r.cpp // example of Qhull's C++ interface libqhullcpp with libqhullstatic_r.a
    +  qhulltest/qhulltest.cpp // Test of Qhull's C++ interface using Qt's QTestLib
    +  qhull-*.pri             // Include files for Qt projects
    +  testqset_r/testqset_r.c  // Test of reentrant qset_r.c and mem_r.c
    +  testqset/testqset.c     // Test of non-rentrant qset.c and mem.c
    +
    +
    +src/libqhull
    +  libqhull.pro         // Qt project for non-rentrant, shared library (qhull.dll)
    +  index.htm            // design documentation for libqhull
    +  qh-*.htm
    +  qhull-exports.def    // Export Definition file for Visual C++
    +  Makefile             // Simple gcc Makefile for qhull and libqhullstatic.a
    +  Mborland             // Makefile for Borland C++ 5.0
    +
    +  libqhull.h           // header file for qhull
    +  user.h               // header file of user definable constants 
    +  libqhull.c           // Quickhull algorithm with partitioning
    +  user.c               // user re-definable functions 
    +  usermem.c
    +  userprintf.c
    +  userprintf_rbox.c
    +
    +  qhull_a.h            // include files for libqhull/*.c 
    +  geom.c               // geometric routines 
    +  geom2.c
    +  geom.h     
    +  global.c             // global variables 
    +  io.c                 // input-output routines 
    +  io.h   
    +  mem.c                // memory routines, this is stand-alone code 
    +  mem.h
    +  merge.c              // merging of non-convex facets 
    +  merge.h
    +  poly.c               // polyhedron routines 
    +  poly2.c
    +  poly.h 
    +  qset.c               // set routines, this only depends on mem.c 
    +  qset.h
    +  random.c             // utilities w/ Park & Miller's random number generator
    +  random.h
    +  rboxlib.c            // point set generator for rbox
    +  stat.c               // statistics 
    +  stat.h
    +
    +src/libqhull_r
    +  libqhull_r.pro       // Qt project for rentrant, shared library (qhull_r.dll)
    +  index.htm            // design documentation for libqhull_r
    +  qh-*_r.htm
    +  qhull-exports_r.def  // Export Definition file for Visual C++
    +  Makefile             // Simple gcc Makefile for qhull and libqhullstatic.a
    +
    +  libqhull_r.h          // header file for qhull
    +  user_r.h              // header file of user definable constants 
    +  libqhull_r.c          // Quickhull algorithm wi_r.hpartitioning
    +  user_r.c              // user re-definable functions 
    +  usermem.c
    +  userprintf.c
    +  userprintf_rbox.c
    +  qhull_ra.h            // include files for libqhull/*_r.c
    +  geom_r.c              // geometric routines 
    +  geom2.c
    +  geom_r.h    
    +  global_r.c            // global variables 
    +  io_r.c                // input-output routines 
    +  io_r.h  
    +  mem_r.c               // memory routines, this is stand-alone code 
    +  mem.h
    +  merge_r.c             // merging of non-convex facets 
    +  merge.h
    +  poly_r.c              // polyhedron routines 
    +  poly2.c
    +  poly_r.h
    +  qset_r.c              // set routines, this only depends on mem_r.c
    +  qset.h
    +  random_r.c            // utilities w/ Park & Miller's random number generator
    +  random.h
    +  rboxlib_r.c           // point set generator for rbox
    +  stat_r.c              // statistics 
    +  stat.h
    +
    +src/libqhullcpp/
    +  libqhullcpp.pro      // Qt project for renentrant, static C++ library     
    +  Qhull.cpp            // Calls libqhull_r.c from C++
    +  Qhull.h
    +  qt-qhull.cpp         // Supporting methods for Qt
    +    
    +  Coordinates.cpp      // input classes
    +  Coordinates.h
    +
    +  PointCoordinates.cpp
    +  PointCoordinates.h
    +  RboxPoints.cpp       // call rboxlib.c from C++
    +  RboxPoints.h
    +
    +  QhullFacet.cpp       // data structure classes
    +  QhullFacet.h
    +  QhullHyperplane.cpp
    +  QhullHyperplane.h
    +  QhullPoint.cpp
    +  QhullPoint.h
    +  QhullQh.cpp
    +  QhullRidge.cpp
    +  QhullRidge.h
    +  QhullVertex.cpp
    +  QhullVertex.h
    +  
    +  QhullFacetList.cpp   // collection classes
    +  QhullFacetList.h
    +  QhullFacetSet.cpp
    +  QhullFacetSet.h
    +  QhullIterator.h
    +  QhullLinkedList.h
    +  QhullPoints.cpp
    +  QhullPoints.h
    +  QhullPointSet.cpp
    +  QhullPointSet.h
    +  QhullSet.cpp
    +  QhullSet.h
    +  QhullSets.h
    +  QhullVertexSet.cpp
    +  QhullVertexSet.h
    +
    +  functionObjects.h    // supporting classes
    +  QhullError.cpp
    +  QhullError.h
    +  QhullQh.cpp
    +  QhullQh.h
    +  QhullStat.cpp
    +  QhullStat.h
    +  RoadError.cpp        // Supporting base classes
    +  RoadError.h
    +  RoadLogEvent.cpp
    +  RoadLogEvent.h
    +  usermem_r-cpp.cpp    // Optional override for qh_exit() to throw an error
    +
    +src/libqhullstatic/
    +  libqhullstatic.pro   // Qt project for non-reentrant, static library     
    +     
    +src/libqhullstatic_r/
    +  libqhullstatic_r.pro // Qt project for reentrant, static library     
    +     
    +src/qhulltest/
    +  qhulltest.pro        // Qt project for test of C++ interface     
    +  Coordinates_test.cpp // Test of each class
    +  PointCoordinates_test.cpp
    +  Qhull_test.cpp
    +  QhullFacet_test.cpp
    +  QhullFacetList_test.cpp
    +  QhullFacetSet_test.cpp
    +  QhullHyperplane_test.cpp
    +  QhullLinkedList_test.cpp
    +  QhullPoint_test.cpp
    +  QhullPoints_test.cpp
    +  QhullPointSet_test.cpp
    +  QhullRidge_test.cpp
    +  QhullSet_test.cpp
    +  QhullVertex_test.cpp
    +  QhullVertexSet_test.cpp
    +  RboxPoints_test.cpp
    +  RoadTest.cpp         // Run multiple test files with QTestLib
    +  RoadTest.h
    +
    +-----------------
    +Authors:
    +
    +  C. Bradford Barber                  Hannu Huhdanpaa (Version 1.0)
    +  bradb@shore.net                     hannu@qhull.org
    +  
    +  Qhull 1.0 and 2.0 were developed under NSF grants NSF/DMS-8920161 
    +  and NSF-CCR-91-15793 750-7504 at the Geometry Center and Harvard 
    +  University.  If you find Qhull useful, please let us know.
    diff --git a/xs/src/qhull/REGISTER.txt b/xs/src/qhull/REGISTER.txt
    new file mode 100644
    index 0000000000..16ccb1a58d
    --- /dev/null
    +++ b/xs/src/qhull/REGISTER.txt
    @@ -0,0 +1,32 @@
    +Dear Qhull User
    +
    +We would like to find out how you are using our software.  Think of
    +Qhull as a new kind of shareware: you share your science and successes 
    +with us, and we share our software and support with you. 
    +
    +If you use Qhull, please send us a note telling
    +us what you are doing with it.
    +
    +We need to know:
    +
    +  (1) What you are working on - an abstract of your work would be
    +      fine.
    +
    +  (2) How Qhull has helped you, for example, by increasing your 
    +      productivity or allowing you to do things you could not do
    +      before.  If Qhull had a direct bearing on your work, please 
    +      tell us about this.
    +
    +We encourage you to cite Qhull in your publications.  
    +
    +To cite Qhull, please use
    +
    +        Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., "The Quickhull 
    +        algorithm for convex hulls," ACM Trans. on Mathematical Software,
    +        22(4):469-483, Dec 1996, http://www.qhull.org.
    +
    +Please send e-mail to
    +
    +    bradb@shore.net
    +
    +Thank you!
    diff --git a/xs/src/qhull/html/index.htm b/xs/src/qhull/html/index.htm
    new file mode 100644
    index 0000000000..ca4789b47f
    --- /dev/null
    +++ b/xs/src/qhull/html/index.htm
    @@ -0,0 +1,935 @@
    +
    +
    +
    +
    +
    +
    +Qhull manual
    +
    +
    +
    +
    +
    +

    Up: Home page for Qhull
    +Up:News about Qhull
    +Up: FAQ about Qhull
    +To: Qhull manual: Table of Contents +(please wait while loading)
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    + +


    + +

    [random-fixed] Qhull manual

    + +

    Qhull is a general dimension code for computing convex hulls, +Delaunay triangulations, halfspace intersections about a point, Voronoi +diagrams, furthest-site Delaunay triangulations, and +furthest-site Voronoi diagrams. These structures have +applications in science, engineering, statistics, and +mathematics. See Fukuda's +introduction to convex hulls, Delaunay triangulations, +Voronoi diagrams, and linear programming. For a detailed +introduction, see O'Rourke ['94], Computational +Geometry in C. +

    + +

    There are six programs. Except for rbox, they use +the same code. Each program includes instructions and examples. +

    +
      +
    • qconvex -- convex hulls +
    • qdelaunay -- Delaunay triangulations and + furthest-site Delaunay triangulations +
    • qhalf -- halfspace intersections about a point +
    • qhull -- all structures with additional options +
    • qvoronoi -- Voronoi diagrams and + furthest-site Voronoi diagrams +
    • rbox -- generate point distributions for qhull +
    +
    + +

    Qhull implements the Quickhull algorithm for computing the +convex hull. Qhull includes options +for hull volume, facet area, multiple output formats, and +graphical output. It can approximate a convex hull.

    + +

    Qhull handles roundoff errors from floating point +arithmetic. It generates a convex hull with "thick" facets. +A facet's outer plane is clearly above all of the points; +its inner plane is clearly below the facet's vertices. Any +exact convex hull must lie between the inner and outer plane. + +

    Qhull uses merged facets, triangulated output, or joggled +input. Triangulated output triangulates non-simplicial, merged +facets. Joggled input also +guarantees simplicial output, but it +is less accurate than merged facets. For merged facets, Qhull +reports the maximum outer and inner plane. + +

    Brad Barber, Arlington, MA

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »Qhull manual: Table of +Contents

    + + +

    »When to use Qhull

    +
    + +

    Qhull constructs convex hulls, Delaunay triangulations, +halfspace intersections about a point, Voronoi diagrams, furthest-site Delaunay +triangulations, and furthest-site Voronoi diagrams.

    + +

    For convex hulls and halfspace intersections, Qhull may be used +for 2-d upto 8-d. For Voronoi diagrams and Delaunay triangulations, Qhull may be +used for 2-d upto 7-d. In higher dimensions, the size of the output +grows rapidly and Qhull does not work well with virtual memory. +If n is the size of +the input and d is the dimension (d>=3), the size of the output +and execution time +grows by n^(floor(d/2) +[see Performance]. For example, do +not try to build a 16-d convex hull of 1000 points. It will +have on the order of 1,000,000,000,000,000,000,000,000 facets. + +

    On a 600 MHz Pentium 3, Qhull computes the 2-d convex hull of +300,000 cocircular points in 11 seconds. It computes the +2-d Delaunay triangulation and 3-d convex hull of 120,000 points +in 12 seconds. It computes the +3-d Delaunay triangulation and 4-d convex hull of 40,000 points +in 18 seconds. It computes the +4-d Delaunay triangulation and 5-d convex hull of 6,000 points +in 12 seconds. It computes the +5-d Delaunay triangulation and 6-d convex hull of 1,000 points +in 12 seconds. It computes the +6-d Delaunay triangulation and 7-d convex hull of 300 points +in 15 seconds. It computes the +7-d Delaunay triangulation and 8-d convex hull of 120 points +in 15 seconds. It computes the +8-d Delaunay triangulation and 9-d convex hull of 70 points +in 15 seconds. It computes the +9-d Delaunay triangulation and 10-d convex hull of 50 points +in 17 seconds. The 10-d convex hull of 50 points has about 90,000 facets. + + +

    Qhull does not support constrained Delaunay +triangulations, triangulation of non-convex surfaces, mesh +generation of non-convex objects, or medium-sized inputs in 9-D +and higher.

    + +

    This is a big package with many options. It is one of the +fastest available. It is the only 3-d code that handles precision +problems due to floating point arithmetic. For example, it +implements the identity function for extreme points (see Imprecision in Qhull).

    + +

    [2016] A newly discovered, bad case for Qhull is multiple, nearly incident points within a 10^-13 ball of 3-d and higher +Delaunay triangulations (input sites in the unit cube). Nearly incident points within substantially +smaller or larger balls are OK. Error QH6271 is reported if a problem occurs. A future release of Qhull +will handle this case. For more information, see "Nearly coincident points on an edge" in Limitations of merged facets + +

    If you need a short code for convex hull, Delaunay +triangulation, or Voronoi volumes consider Clarkson's hull +program. If you need 2-d Delaunay triangulations consider +Shewchuk's triangle +program. It is much faster than Qhull and it allows +constraints. Both programs use exact arithmetic. They are in http://www.netlib.org/voronoi/. + +

    If your input is in general position (i.e., no coplanar or colinear points), +

  • Tomilov's quickhull.hpp () +or Qhull version +1.0 may meet your needs. Both programs detect precision problems, +but do not handle them.

    + +

    CGAL is a library of efficient and reliable +geometric algorithms. It uses C++ templates and the Boost library to produce dimension-specific +code. This allows more efficient use of memory than Qhull's general-dimension +code. CGAL simulates arbitrary precision while Qhull handles round-off error +with thick facets. Compare the two approaches with Robustness Issues in CGAL, +and Imprecision in Qhull. + + +

    Leda is a +library for writing computational +geometry programs and other combinatorial algorithms. It +includes routines for computing 3-d convex +hulls, 2-d Delaunay triangulations, and 3-d Delaunay triangulations. +It provides rational arithmetic and graphical output. It runs on most +platforms. + +

    If your problem is in high dimensions with a few, +non-simplicial facets, try Fukuda's cdd. +It is much faster than Qhull for these distributions.

    + +

    Custom software for 2-d and 3-d convex hulls may be faster +than Qhull. Custom software should use less memory. Qhull uses +general-dimension data structures and code. The data structures +support non-simplicial facets.

    + +

    Qhull is not suitable for mesh generation or triangulation of +arbitrary surfaces. You may use Qhull if the surface is convex or +completely visible from an interior point (e.g., a star-shaped +polyhedron). First, project each site to a sphere that is +centered at the interior point. Then, compute the convex hull of +the projected sites. The facets of the convex hull correspond to +a triangulation of the surface. For mesh generation of arbitrary +surfaces, see Schneiders' +Finite Element Mesh Generation.

    + +

    Qhull is not suitable for constrained Delaunay triangulations. +With a lot of work, you can write a program that uses Qhull to +add constraints by adding additional points to the triangulation.

    + +

    Qhull is not suitable for the subdivision of arbitrary +objects. Use qdelaunay to subdivide a convex object.

    + +
  • +

    »Description of +Qhull

    +
    + +

    »definition

    +
    + +

    The convex hull of a point set P is the smallest +convex set that contains P. If P is finite, the +convex hull defines a matrix A and a vector b such +that for all x in P, Ax+b <= [0,...].

    + +

    Qhull computes the convex hull in 2-d, 3-d, 4-d, and higher +dimensions. Qhull represents a convex hull as a list of facets. +Each facet has a set of vertices, a set of neighboring facets, +and a halfspace. A halfspace is defined by a unit normal and an +offset (i.e., a row of A and an element of b).

    + +

    Qhull accounts for round-off error. It returns +"thick" facets defined by two parallel hyperplanes. The +outer planes contain all input points. The inner planes exclude +all output vertices. See Imprecise +convex hulls.

    + +

    Qhull may be used for the Delaunay triangulation or the +Voronoi diagram of a set of points. It may be used for the +intersection of halfspaces.

    + +
    +

    »input format

    +
    + +

    The input data on stdin consists of:

    + +
      +
    • first line contains the dimension
    • +
    • second line contains the number of input points
    • +
    • remaining lines contain point coordinates
    • +
    + +

    For example:

    + +
    +    3  #sample 3-d input
    +    5
    +    0.4 -0.5 1.0
    +    1000 -1e-5 -100
    +    0.3 0.2 0.1
    +    1.0 1.0 1.0
    +    0 0 0
    +
    + +

    Input may be entered by hand. End the input with a control-D +(^D) character.

    + +

    To input data from a file, use I/O redirection or 'TI file'. The filename may not +include spaces or quotes.

    + +

    A comment starts with a non-numeric character and continues to +the end of line. The first comment is reported in summaries and +statistics. With multiple qhull commands, use option 'FQ' to place a comment in the output.

    + +

    The dimension and number of points can be reversed. Comments +and line breaks are ignored. Error reporting is better if there +is one point per line.

    + +
    +

    »option format

    +
    + +

    Use options to specify the output formats and control +Qhull. The qhull program takes all options. The +other programs use a subset of the options. They disallow +experimental and inappropriate options. + +

    +
      +
    • +qconvex == qhull +
    • +qdelaunay == qhull d Qbb +
    • +qhalf == qhull H +
    • +qvoronoi == qhull v Qbb +
    +
    + +

    Single letters are used for output formats and precision +constants. The other options are grouped into menus for formats +('F'), Geomview ('G '), printing ('P'), Qhull control ('Q '), and tracing ('T'). The menu options may be listed +together (e.g., 'GrD3' for 'Gr' and 'GD3'). Options may be in any +order. Capitalized options take a numeric argument (except for 'PG' and 'F' +options). Use option 'FO' to print +the selected options.

    + +

    Qhull uses zero-relative indexing. If there are n +points, the index of the first point is 0 and the index of +the last point is n-1.

    + +

    The default options are:

    + +
      +
    • summary output ('s')
    • +
    • merged facets ('C-0' in 2-d, + 3-d, 4-d; 'Qx' in 5-d and + up)
    • +
    + +

    Except for bounding box +('Qbk:n', etc.), drop facets +('Pdk:n', etc.), and +Qhull command ('FQ'), only the last +occurence of an option counts. +Bounding box and drop facets may be repeated for each dimension. +Option 'FQ' may be repeated any number of times. + +

    The Unix tcsh and ksh shells make it easy to +try out different options. In Windows 95, use a command window with doskey +and a window scroller (e.g., peruse).

    + +
    +

    »output format

    +
    + +

    To write the results to a file, use I/O redirection or 'TO file'. Windows 95 users should use +'TO file' or the console. If a filename is surrounded by single quotes, +it may include spaces. +

    + +

    The default output option is a short summary ('s') to stdout. There are many +others (see output and formats). You can list vertex incidences, +vertices and facets, vertex coordinates, or facet normals. You +can view Qhull objects with Geomview, Mathematica, or Maple. You can +print the internal data structures. You can call Qhull from your +application (see Qhull library).

    + +

    For example, 'qhull o' lists the +vertices and facets of the convex hull.

    + +

    Error messages and additional summaries ('s') go to stderr. Unless +redirected, stderr is the console.

    + +
    +

    »algorithm

    +
    + +

    Qhull implements the Quickhull algorithm for convex hull +[Barber et al. '96]. This algorithm +combines the 2-d Quickhull algorithm with the n-d +beneath-beyond algorithm [c.f., Preparata & Shamos '85]. It is similar to the randomized +algorithms of Clarkson and others [Clarkson & Shor '89; Clarkson et al. '93; +Mulmuley '94]. For a demonstration, see How Qhull adds a point. The main +advantages of Quickhull are output sensitive performance (in +terms of the number of extreme points), reduced space +requirements, and floating-point error handling.

    + +
    +

    »data structures

    +
    + +

    Qhull produces the following data structures for dimension d: +

    + +
      +
    • A coordinate is a real number in floating point + format.
    • +
    • A point is an array of d coordinates. + With option 'QJ', the + coordinates are joggled by a small amount.
    • +
    • A vertex is an input point.
    • +
    • A hyperplane is d normal coefficients and + an offset. The length of the normal is one. The + hyperplane defines a halfspace. If V is a normal, b + is an offset, and x is a point inside the convex + hull, then Vx+b <0.
    • +
    • An outer plane is a positive + offset from a hyperplane. When Qhull is done, all points + will be below all outer planes.
    • +
    • An inner plane is a negative + offset from a hyperplane. When Qhull is done, all + vertices will be above the corresponding inner planes.
    • +
    • An orientation is either 'top' or 'bottom'. It is the + topological equivalent of a hyperplane's geometric + orientation.
    • +
    • A simplicial facet is a set of + d neighboring facets, a set of d vertices, a + hyperplane equation, an inner plane, an outer plane, and + an orientation. For example in 3-d, a simplicial facet is + a triangle.
    • +
    • A centrum is a point on a facet's hyperplane. A + centrum is the average of a facet's vertices. Neighboring + facets are convex if each centrum is below the + neighbor facet's hyperplane.
    • +
    • A ridge is a set of d-1 vertices, two + neighboring facets, and an orientation. For example in + 3-d, a ridge is a line segment.
    • +
    • A non-simplicial facet is a set of ridges, a + hyperplane equation, a centrum, an outer plane, and an + inner plane. The ridges determine a set of neighboring + facets, a set of vertices, and an orientation. Qhull + produces a non-simplicial facet when it merges two facets + together. For example, a cube has six non-simplicial + facets.
    • +
    + +

    For examples, use option 'f'. See polyhedron operations for further +design documentation.

    + +
    +

    »Imprecision in Qhull

    +
    + +

    See Imprecision in Qhull and Merged facets or joggled input

    + +
    +

    »Examples of Qhull

    +
    + +

    See Examples of Qhull. Most of these examples require Geomview. +Some of the examples have pictures +.

    + +
    +
    +

    »Options for using Qhull

    +
    + +

    See Options.

    + +
    +

    »Qhull internals

    +
    + +

    See Internals.

    + +
    +

    »Geomview, Qhull's +graphical viewer

    +
    + +

    Geomview +is an interactive geometry viewing program. +Geomview provides a good visualization of Qhull's 2-d and 3-d results. + +

    Qhull includes Examples of Qhull that may be viewed with Geomview. + +

    Geomview can help visulalize a 3-d Delaunay triangulation or the surface of a 4-d convex hull, +Use option 'QVn' to select the 3-D facets adjacent to a vertex. + +

    You may use Geomview to create movies that animate your objects (c.f., How can I create a video animation?). +Geomview helped create the mathematical videos "Not Knot", "Outside In", and "The Shape of Space" from the Geometry Center. + + +

    »Installing Geomview

    +
    + +

    Geomview is an open source project +under SourceForge. + +

    +For build instructions see +Downloading Geomview. +Geomview builds under Linux, Unix, Macintosh OS X, and Windows. + +

    Geomview has installable packages for Debian and Ubuntu. +The OS X build needs Xcode, an X11 SDK, and Lesstif or Motif. +The Windows build uses Cygwin (see Building Geomview below for instructions). + +

    If using Xforms (e.g., for Geomview's External Modules), install the 'libXpm-devel' package from cygwin and move the xforms directory into your geomview directory, e.g.,
    mv xforms-1.2.4 geomview-1.9.5/xforms + +

    Geomview's ndview provides multiple views into 4-d and higher objects. +This module is out-of-date (geomview-users: 4dview). +Download NDview-sgi.tar.Z at newpieces and 4dview at Geomview/modules. + +

    +

    »Using Geomview

    +
    + +

    Use Geomview to view Examples of Qhull. You can spin the convex hull, fly a camera through its facets, +and see how Qhull produces thick facets in response to round-off error. + +

    Follow these instructions to view 'eg,01.cube' from Examples of Qhull +

      +
    1. Launch an XTerm command shell +
        +
      • If needed, start the X terminal server, Use 'xinit' or 'startx' in /usr/X11R6/bin
        xinit -- -multiwindow -clipboard
        startx +
      • Start an XTerm command shell. In Windows, click the Cygwin/bash icon on your desktop. +
      • Set the DISPLAY variable, e.g.,
        export DISPLAY=:0
        export DISPLAY=:0 >>~/.bashenv +
      +
    2. Use Qhull's Geomview options to create a geomview object +
        +
      • rbox c D3 | qconvex G >eg.01.cube +
      • On windows, convert the output to Unix text format with 'd2u'
        rbox c D3 | qconvex G | d2u >eg.01.cube
        d2u eg.* +
      +
    3. Run Geomview +
        +
      • Start Geomview with your example
        ./geomview eg.01.cube +
      • Follow the instructions in Gemoview Tutorial +
      • Geomview creates the Geomview control panel with Targets and External Module, the Geomview toolbar with buttons for controlling Geomview, and the Geomview camera window showing a cube. +
      • Clear the camera window by selecting your object in the Targets list and 'Edit > Delete' or 'dd' +
      • Load the Geomview files panel. Select 'Open' in the 'File' menu. +
      • Set 'Filter' in the files panel to your example directory followed by '/*' (e.g., '/usr/local/qhull-2015.2/eg/*') +
      • Click 'Filter' in the files panel to view your examples in the 'Files' list. +
      • Load another example into the camera window by selecting it and clicking 'OK'. +
      • Review the instructions for Interacting with Geomview +
      • When viewing multiple objects at once, you may want to turn off normalization. In the 'Inspect > Apperance' control panel, set 'Normalize' to 'None'. +
      +
    + +

    Geomview defines GCL (a textual API for controlling Geomview) and OOGL (a textual file format for defining objects). +

      +
    • To control Geomview, you may use any program that reads and writes from stdin and stdout. For example, it could report Qhull's information about a vertex identified by a double-click 'pick' event. +
    • GCL command language for controlling Geomview +
    • OOGL file format for defining objects (tutorial). +
    • External Modules for interacting with Geomview via GCL +
    • Interact with your objects via pick commands in response to right-mouse double clicks. Enable pick events with the interest command. +
    + +
    +

    »Building Geomview for Windows

    +
    + +

    Compile Geomview under Cygwin. For detailed instructions, see +Building Savi and Geomview under Windows. These instructions are somewhat out-of-date. Updated +instructions follow. + +

    How to compile Geomview under 32-bit Cygwin (October 2015)

    +
      +
    1. Note: L. Wood has run into multiple issues with Geomview on Cygwin. He recommends Virtualbox/Ubuntu +and a one-click install of geomview via the Ubuntu package. See his Savi/Geomview link above. +
    2. Install 32-bit Cygwin as follows. +For additional guidance, see Cygwin's Installing and Updating Cygwin Packages +and Setup cygwin. +
        +
      • Launch the cygwin installer. +
      • Select a mirror from Cygwin mirrors (e.g., http://mirrors.kernel.org/sourceware/cygwin/ in California). +
      • Select the packages to install. Besides the cygwin packages listed in the Savi/Windows instructions consider adding +
          +
        • Default -- libXm-devel (required for /usr/include/Xm/Xm.h) +
        • Devel -- bashdb, gcc-core (in place of gcc), gdb +
        • Lib -- libGL-devel, libGLU1 (required, obsolete), libGLU-devel (required, obsolete), libjpeg-devel(XForms), libXext-devel (required), libXpm-devel (Xforms) +libGL and lib +
        • Math -- bc +
        • Net -- autossh, inetutils, openssh +
        • System -- chere +
        • Utils -- dos2unix (required for qhull), keychain +
        • If installing perl, ActiveState Perl may be a better choice than cygwin's perl. Perl is not used by Geomview or Qhull. +
        • Cygwin Package Search -- Search for cygwin programs and packages +
        +
      • Click 'Next' to download and install the packages. +
      • If the download is incomplete, try again. +
      • If you try again after a successful install, cygwin will uninstall and reinstall all modules.. +
      • Click on the 'Cywin Terminal' icon on the Desktop. It sets up a user directory in /home from /etc/skel/... +
      • Mount your disk drives
        mount c: /c # Ignore the warning /c does not exist +
      +
    3. Consider installing the Road Bash scripts (/etc/road-*) from Road. +They define aliases and functions for Unix command shells (Unix, Linux, Mac OS X, Windows), +
        +
      • Download Road Bash and unzip the downloaded file +
      • Copy .../bash/etc/road-* to the Cywin /etc directory (by default, C:\cygwin\etc). +
      • Using the cygwin terminal, convert the road scripts to Unix format
        d2u /etc/road-* +
      • Try it
        source /etc/road-home.bashrc +
      • Install it
        cp /etc/road-home.bashrc ~/.bashrc +
      +
    4. Launch the X terminal server from 'Start > All programs > Cygwin-X > Xwin Server'. Alternatively, run 'startx' +
    5. Launch an XTerm shell +
        +
      • Right click the Cywin icon on the system tray in the Windows taskbar. +
      • Select 'System Tools > XTerm' +
      +
    6. Download and extract Geomview -- Downloading Geomview +
    7. Compile Geomview +
        +
      • ./configure +
      • make +
      +
    8. If './configure' fails, check 'config.log' at the failing step. Look carefully for missing libraries, etc. The Geomview FAQ contains suggestions (e.g., "configure claims it can't find OpenGl"). +
    9. If 'make' fails, read the output carefully for error messages. Usually it is a missing include file or package. Locate and install the missing cygwin packages +(Cygwin Package Search). +
    + +
    +
    +

    »What to do if something +goes wrong

    +
    + +

    Please report bugs to qhull_bug@qhull.org +. Please report if Qhull crashes. Please report if Qhull +generates an "internal error". Please report if Qhull +produces a poor approximate hull in 2-d, 3-d or 4-d. Please +report documentation errors. Please report missing or incorrect +links.

    + +

    If you do not understand something, try a small example. The rbox program is an easy way to generate +test cases. The Geomview program helps to +visualize the output from Qhull.

    + +

    If Qhull does not compile, it is due to an incompatibility +between your system and ours. The first thing to check is that +your compiler is ANSI standard. Qhull produces a compiler error +if __STDC__ is not defined. You may need to set a flag (e.g., +'-A' or '-ansi').

    + +

    If Qhull compiles but crashes on the test case (rbox D4), +there's still incompatibility between your system and ours. +Sometimes it is due to memory management. This can be turned off +with qh_NOmem in mem.h. Please let us know if you figure out how +to fix these problems.

    + +

    If you doubt the output from Qhull, add option 'Tv'. It checks that every point is +inside the outer planes of the convex hull. It checks that every +facet is convex with its neighbors. It checks the topology of the +convex hull.

    + +

    Qhull should work on all inputs. It may report precision +errors if you turn off merged facets with option 'Q0'. This can get as bad as facets with +flipped orientation or two facets with the same vertices. You'll +get a long help message if you run into such a case. They are +easy to generate with rbox.

    + +

    If you do find a problem, try to simplify it before reporting +the error. Try different size inputs to locate the smallest one +that causes an error. You're welcome to hunt through the code +using the execution trace ('T4') as +a guide. This is especially true if you're incorporating Qhull +into your own program.

    + +

    When you report an error, please attach a data set to the end +of your message. Include the options that you used with Qhull, +the results of option 'FO', and any +messages generated by Qhull. This allows me to see the error for +myself. Qhull is maintained part-time.

    + +
    +

    »Email

    +
    + +

    Please send correspondence to Brad Barber at qhull@qhull.org +and report bugs to qhull_bug@qhull.org +. Let me know how you use Qhull. If you mention it in a +paper, please send a reference and abstract.

    + +

    If you would like to get Qhull announcements (e.g., a new +version) and news (any bugs that get fixed, etc.), let us know +and we will add you to our mailing list. For Internet news about geometric algorithms +and convex hulls, look at comp.graphics.algorithms and +sci.math.num-analysis. For Qhull news look at qhull-news.html.

    + +
    +

    »Authors

    +
    + +
    +   C. Bradford Barber                    Hannu Huhdanpaa
    +   bradb@shore.net                       hannu@qhull.org
    +
    + +
    +

    »Acknowledgments

    +
    + +

    A special thanks to David Dobkin for his guidance. A special +thanks to Albert Marden, Victor Milenkovic, the Geometry Center, +and Harvard University for supporting this work.

    + +

    A special thanks to Mark Phillips, Robert Miner, and Stuart Levy for running the Geometry + Center web site long after the Geometry Center closed. + Stuart moved the web site to the University of Illinois at Champaign-Urbana. +Mark and Robert are founders of Geometry Technologies. +Mark, Stuart, and Tamara Munzner are the original authors of Geomview. + +

    A special thanks to Endocardial +Solutions, Inc. of St. Paul, Minnesota for their support of the +internal documentation (src/libqhull/index.htm). They use Qhull to build 3-d models of +heart chambers.

    + +

    Qhull 1.0 and 2.0 were developed under National Science Foundation +grants NSF/DMS-8920161 and NSF-CCR-91-15793 750-7504. If you find +it useful, please let us know.

    + +

    The Geometry Center was supported by grant DMS-8920161 from the +National Science Foundation, by grant DOE/DE-FG02-92ER25137 from +the Department of Energy, by the University of Minnesota, and by +Minnesota Technology, Inc.

    + +
    +

    »References

    +
    + +

    Aurenhammer, F., "Voronoi diagrams +-- A survey of a fundamental geometric data structure," ACM +Computing Surveys, 1991, 23:345-405.

    + +

    Barber, C. B., D.P. Dobkin, and H.T. +Huhdanpaa, "The Quickhull Algorithm for Convex Hulls," ACM +Transactions on Mathematical Software, 22(4):469-483, Dec 1996, www.qhull.org +[http://portal.acm.org; +http://citeseerx.ist.psu.edu]. +

    + +

    Clarkson, K.L. and P.W. Shor, +"Applications of random sampling in computational geometry, +II", Discrete Computational Geometry, 4:387-421, 1989

    + +

    Clarkson, K.L., K. Mehlhorn, and R. +Seidel, "Four results on randomized incremental +construction," Computational Geometry: Theory and +Applications, vol. 3, p. 185-211, 1993.

    + +

    Devillers, et. al., +"Walking in a triangulation," ACM Symposium on +Computational Geometry, June 3-5,2001, Medford MA. + +

    Dobkin, D.P. and D.G. Kirkpatrick, +"Determining the separation of preprocessed polyhedra--a +unified approach," in Proc. 17th Inter. Colloq. Automata +Lang. Program., in Lecture Notes in Computer Science, +Springer-Verlag, 443:400-413, 1990.

    + +

    Edelsbrunner, H, Geometry and Topology for Mesh Generation, +Cambridge University Press, 2001. + +

    Gartner, B., "Fast and robust smallest enclosing balls", Algorithms - ESA '99, LNCS 1643. + +

    Golub, G.H. and van Loan, C.F., Matric Computations, Baltimore, Maryland, USA: John Hopkins Press, 1983 + +

    Fortune, S., "Computational +geometry," in R. Martin, editor, Directions in Geometric +Computation, Information Geometers, 47 Stockers Avenue, +Winchester, SO22 5LB, UK, ISBN 1-874728-02-X, 1993.

    + +

    Milenkovic, V., "Robust polygon +modeling," Computer-Aided Design, vol. 25, p. 546-566, +September 1993.

    + +

    Mucke, E.P., I. Saias, B. Zhu, Fast +randomized point location without preprocessing in Two- and +Three-dimensional Delaunay Triangulations, ACM Symposium on +Computational Geometry, p. 274-283, 1996 [GeomDir]. +

    + +

    Mulmuley, K., Computational Geometry, +An Introduction Through Randomized Algorithms, Prentice-Hall, +NJ, 1994.

    + +

    O'Rourke, J., Computational Geometry +in C, Cambridge University Press, 1994.

    + +

    Preparata, F. and M. Shamos, Computational +Geometry, Springer-Verlag, New York, 1985.

    + +
    + +
    + +

    Up: Home page for Qhull
    +Up:News about Qhull
    +Up: FAQ about Qhull
    +To: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Dn: Imprecision in Qhull
    +Dn: Description of Qhull examples
    +Dn: Qhull internals
    +Dn: Qhull functions, macros, and data +structures + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/normal_voronoi_knauss_oesterle.jpg b/xs/src/qhull/html/normal_voronoi_knauss_oesterle.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f46d421274edd97de13d25e306985c43e872165b GIT binary patch literal 23924 zcmbTe1ymf((=WU@K|*ksu($EtF09Z^I zI82z=UH~~%NCcRFHo$*27+5$czevb$P*9;2>fZriVc_6k;o%Sv;NhXQeW2w4cuWMW z_iUnw*ouZolnyxT0kJvAz>n2kxJpxJR2)W*-`=3$;S&%NQPa@U(KB#zar5x<@r!*D zmync_{;aH`s-~`?sby?pYG!U>Y31bX;_Bw^;TiZnC^#haM_62ZLSj<#&y>{My!?W~ zqT-U$n%cVhhQ_AmmhPV3zW#y1q2cKn@a)|D!Xjj2b8CBNckj>s!TH7I)%DHo-TlK~ zxnKZr|B40u{#UU7LoQ6HT(I!)aPUZf<${5AgYIyc@CfhO5V1rRkqjNMDcJ*%aX!Z8 zRCm1rawwhQ8aYm(;8AgIP@n%5?H|eh?*#ky|4Xv}670X_f&kFqV4x2V4if+ZjPVU^ zPn$fXdXS{bs%@hWU2{TzxYG3jcxb^>6D37Pk#&IOEM*CWe@|C^%Lb%Owja+yCJ;;tQPP|Hdoy#yW~&0C^ud8mKI`dA0yr;lg-m1AS0dw*SKw zl)>hIdBrjECm!cd)$@^K`P&&;%96r`8Dam5oySL#s#}-sNC(sFXiZ%+EZWC+N*hp_L-O;1i02pv@DCO9x2*OG}3J zZziMblMdLIK!9<0SdPXBAU_I(wVoo~x`p67%Un_M2_Gj(11N1(Oe(N~n-(s?-nz^QGi9m$-nh23f-6uSYQs$LN zN{ee`@^+~Qgm*Fgpfp}lsA}3K|7yjg8pj?M4;znz(66Y_$ASQm3}aLH+cAn^zVu&( z=wSKj?+QAvvXRgKWj%~R7UMH39jxGgm9r@sL*j7&!}ND~&_;~HeChgN91{K!J|WwY zexU#^Rs;)od#a&;aVXCgmU2k~l59%@0YuvpTj|!NS-=X_*_LomA7>*&A9MbaOoRX5 zD)CU2`KQEn+BO^c=5Gm9o&H`USNH!wKwD9*eDA}qO|g3*5Ny$a)^PqS0z+rkk3?G% zuY9O6u$z4LLjO03>Z!g+ z(;MR0?T8xJOLvmW0o4CrdZ5(;mice5K?fV~f35o81QrM&{J%PD5{WW@=`%j`IV3_= z_Mhh<07FL4rnDJZr-5886BdtS6#g$ELeT)&N5i072PNRqJ17}J36y;VB@WUJ&>D1o zG7bN!0Uc6I@EQewm0sLG9H*&-O@i(h)*lUhoIj(UR5cwtgrMoRj_zb7jRR(IEyraT>%lu6j2;+QRg9AzTdx57+bx%;sWN)Js3NtxZ&E<;)lOf+ z9y-C^V0?=Y|JFHaq|AobPD6{ecJPw5OOTlm(BW49y6c9frj4(Z`THsQ^nDUOoXJ!? z*z*yyYr+Plxop3`d!5dndsXx|p+isA6)=aa?&-UWlEl>D)L?7$IC zx>H)20Wf(sIk)2qBzmaErdE$hMu#HZNl07I?-mAm&kPR7G0zc9x={qrgNm)=hKU5; z&7G5>?%hV-y#kPL$TMh;@VM+xZEe_V$(=_xW6$a&AL`~)A ztudc8+WQ2n;s-o7UO&VrR;K=^VjX%!_(tJ!jpP^ zw!eqzO=G|DB>HQVuBX>x-v&A13j=2>yMhI>YCkR$R3v=N_d4E=F5v`M;_(O1uwXk5 zQ%0#Ot;!7MegsfGrA&w@0PB_eOx@-8ZMnXFmHqf8RiYEriY!Cs6$ikrQZfJH09wy^-JPE>IBuEFzzX+IK0ZNdP2B#(t+5_40;~Jg+LtJ zG9~RAiJEUUBCh~Pjqz>!9Mb0&u(o9Y9^I1CG1OQA>xf-wou2S`B3}V<)AR8b$Bk1%1Tz zh;u)y=>Ftfd*9^Ima#=Urkwbj!f~X85ngs2NmO-OcS34tZ-u-mUF{(Aug_U*BdpyV#{xAwORV*{`Ih>kXgmje9}5SZSBB_3R(ZBQJzDo-nkxJdfb(JcgexblE$&c%xsn?22c1!xLL znuRQ697ku1lb?%H$b?-trkmKSmF(l_5A(gU8*u{e*i;;I-F|x z;UyaZA*!*R_K$m~rXS|+g2C0h2O><<0Niu~Eo$`m@dA#pWsa1v4m_8L9-~+(? zu4xX%vP0Gc0uuXF#`RzPJbs=bFa|{h+iJ^)2c>EUAH5IVy0r222T!;sq0oTWG4Tka zCVb8OJ`VT?9=y3JuWHb;*s@CuPg~n;ce(%a3Yg&nJz(xlqU04VtQ_*^yPlmSZJLb; zq;EBw3Hv+B(JYJ)yUK*ipbq9rCGDc`U<;@Qvd3_|ocIw{HB>H3BD8VeaGSK(mnNv& zRrcF|D=s^0O7XDuumJ+kipB(qRFA;6>Lq@3DaLqh`|L=g7^4E1<*M}uLdM`* zz@^b+&!1s;uBnB?B>X1r0zB-jO+pLp`^0C->9%4SinMWcLvApsjai21t8}HLxQS;pe0qVgxOvaj%&IG%`es>^= z!=cW6NWDxX_Y3YNffi@k-pA!Kw9BqP4r|4^8+&KBKkR(RyE0eJBSHwVSfm(wnt&8D zcI)D9o5v>cgiVcX!x51LwLz)@$`|PrgTjd;Vt%=6tUT|e3zlC26H**ZNC)3f^owa1 z)I~Q(=xkR9lxKQK**6vIu;Wpq+ljrRR`as7E+0eN(o*t3oqc{lrT(Z1rD;1j1vlH8 zlgVl)uI5!7^3%6vuK-xsv<+K67X?hqIY$Sy+7GEdK$7Ms9HRc<120RX%xO!*3`|Wu zZib#6!fkcs4PC!u&-kU)9!t{WWh_XKFZPIu$Gc)Cd=#g2?1;nD z60Y7;I{h2zbzhCbgX5cJJ^JO3<&Gb2zp`SGw)lb+RqPDnARY!8M0|NhQIWG!>wyZ5 z85Ji~20ZW0)WoKO6>w|>691I(aBmQ5VQIZVRE)k5-YR+pRMrCHc2718YQLutHbh)F z&72i|DkT#=; zCUL^3NMMZa0i22bAh=~@WknMO1QoVHnHdPVIuaMA+k`3cXcC^==`i5TcI%OP)TIiE z{S*7WxJ|c%3zDp&UpJKlipr)Na>5)vYgG2H0GlaC5O-|HX{r%*nqGBuXp&d(E5P(4 z{gVRl<-QdAE^PhW-X0!UZ8O+&+5HOGs*et1Q=#k|FYs|JFmFwXxfZt#^;>5Qcu?i~ zl7|8y>&}OU4#sQ}46}*px3!_RMwV29>#k(^H)Pj^R5eo_U=KG)=qn&8-;5yo5qLMz zymcsrY+INA4Zd}b%G%47Fp8SIJ?(jF!LQB_4a^@^LJg*bAkWS9$*Zk$^!sRBo85v6 zRGU+^OIEFakZJ1dWQvLsc+sbz!OVCkxlGe_zozr0i_4merRhaJ1>Mmv|k;#B4q@^sBmmaXV^eJSwrJ=ZwjTwoZ zN(><+-(>tiIaq&)IDOsvh!4+IcZx4ODz2nQ{7n=z(RhDbIkQPNQia7C$NmZ^6h#NK z-;DI5!X3A|36({~aYv?*;JWZqTqFLBBqo)LtX?)v&K4TDzp8BPJ)s59tvj+*yb&8o zLQb$}lbK;jq}5brRtaL;LxRCU%gXk0=nyUOFnJA^r2N=K;BJTfnp#$tuQpbU`@-gCy(ZR=1MDa6*Kwgp@d zqQm(`C6eu3BfGdTAS6Q0BUBa#ndU~+T-YL&=32gcFqz_f#*hw*A9c^%{32pdsZ*@y zzGV$FK(JYG!jT*`%4V65?gnI|3SJ`fI*GX!?zt>;)ss6O8)aWc4R@#TP@7)HQ9~Vq z2|er*!XVtOKCz>|(9+CzOHx=VF^)s>JA0A?b=TDY?yzJpHkG*V`t#-a=+Sme0C``K zx^%+F!v$3gN6Qf+i#5&GAk^jNv=TisM3q{`QP)VEz7wU9+L)u!UKQCPYD}anyMUWhYyBW847!B|9yd&HUG7rpM!_*y^ZSWRrLa>Pa^W(xpTv@7*_#~SI z#Fs(F%fpUFBReubF$%VLh)YB?Y`SLpR)pnr=aD(~&$j8#jri&HA0ey3s0ry^L(=0j zsG%{~rHg))yo>&|b*MR?%(1#mSeXyCNhUXB{;p(T|5~q-lSb}TZ++7mkca5V$jR~A z{-mdx83YPquo5;jdJ7cx{&`5s4JtPo{l5HU+lhWotD93-rLto&Vpz(S>F#f-RBs01`5Ka>mj*(!HRf0lE zP*Vhjn4qycG*pM8Fr!LFe@!r=QOUt^tz5JHgIUidyr0&LMO&*{bCdh=v3mIo?g?X7z(tlxk5P`C0zxuSE!y1PeOH7_gD+>)B>|eX}hfXb&}XgDvlQ zEXM$e%`|wb++^)%ps&h}!H?atJS=|>fXH+K!T+kJDMa#R>cD4~g0v9Q(=;4C% zmf$aRf~ddSAwk}4W%A3+ka(}o=B$s{UL4$DVXWxw&-DK^aBIO}6CrW>`CuFd==^4V zxKL5h;Gri@rre+Rb?Z03LG+IL%K-JLC5asJ?}+4geZ0&|+1FdyeC5YvehvGn?d?r) zHqs0X*BhOD*Bc$5M>1guBMV{^_9!IhFOQ|na}Uh+`pC%Qm4n$+kNua5KLzeJM;N>b z@s5JS$hod0%+`3ZUWCP*roK-*C*RVo>N$M2yH%=-SE$asuvql-{ka_G-W##TNKf)^ zMe-w~qduBDeVe8OX=M)SPH%Z3I+sjEi&U=}cLr_^M>I7qcy2aK@y6zk| zalCfpXA&b=_cn=v6^Bf=`t5;N^u}JJSTRX|VeQg^(&DNZ6?@kO!yxPDsF3r+%n+{W zJA1LrouD>4@B3gAsx70auhM^zloaFz&Q;n#o`>*k?wPZN)d3-kx?R}4PE(+^z!K%I z<8My7$zgGOGAnuelcRGNl+w(QO1X8zW+@)F|?MGWg^qJG;~BH6L}gu|dN|6hUdQg@Oc7B~d+j5A~3sNP8iiE4aZm zUQbut-j*=VQUf|9sAkx7KjGC{++>*ta8&4hl8F;}(GWil~i@-lU@88{LPb)7KmGUWcSF@pk| zMiHvWP*bVZSev;;LpP*28;v5If5wn8SCXrNMcc+xi*avi!h_K-cJZQ_za2Ae15JF0 z7FjD=gDf#MB`ug!V1pcwhC45MulbZG-_~2>(0;Y*ff@oRx(%0VM71=OhjYQl`LoD? z5bEds1^Dgg`UL)F4#L>}Nh=s7;~YV$W)L0aYA6VM690HZCOvwfGdF8`Em$v>Q&xdxA1gmfFAVDx5Lz_5CTYjhP- zUl*7$1!DUwd=v;K-v<-@sZ)tMu4)r@4fjzM?+dVtU9NPkYB){hxK`FVi7%$IrpfZ0 zz+vh&43=eg9wZ$gU_3rRkQxu~?AYgi0nR9ZtY)ZO$f_{0qNmUue`}{p6W9z~+Ud>C zdJD;}rPm3C}j!@QGnj48L7RH{{{N^^F zBWd!AxZ_T?-uCSdiXC7ZQMF3_~%9694!)u0Wxfug}dBR<6>WT>0!ggy&4c?g&PQWSbBO_gtXk&mThv z+t}N|f`NrDKhA~M(o0|bNm8*5w&&$>O(WHYt*HrY7w^*EaxAiTF|9o5ORDe+&}$?b zKh(c7FMtnvS<2TFO0$E#x6Ytc?-5Z{CEee)~c()iDkI* zUt6Uv)P5mV@@c~4ZSj42Bt!!DN(V^}DObBO=Hn4g%)cf1NLm68#2sK?EyB_72s4(l^-CSlok|aqnLN0erH>u?9_jCqqXC6$-hBL`F zADlO+co{@QS8)pHDin+F##Rq1h2N?RZj-Ht9eBIsEUtY&m&o!kOLghFR}V)vqG;*Y zp^SMu_t?XGBA^b!-6?28CCztiDH%%o!Y<)~IuCfJ5AfXx4|06cjByRzqwJ&u znmSqcW|>Wdg05V8CmWRbnA%ciNgIv2D~)SSHhTv+WP#@o8uMJybt6=27Vsv>BXkU{ z_$U3%+Z4|VG&R6)mg$ryW|?vr?-j@2vQI&q1rmmM1`^fjk`ngdc z{(>+U9BNzt#WSy?Sv$&O=4MdXua}6~x#LP;&xME9bD*oaMf4N;=V)-)Nwq2_TT>oM zm@B==Zh7p&_EZ>#vN(Vs4@Xf1k**Kt3k)$4 zr*kDaCbdbyTP7u(14%WZ1h3Q!X1_lEsDfh7;cDuZr)sh2YwP{YJurA!ZPJo-n~XN) zWJ8McYJ|U-`Kvs;!;Gn;-#g02unUEE-64wAzcZ$ENwPyDyrT!u_yEonCaND$t>!uI zg1h;Wt?mORMq-2Rqk;RHG44r+J?BfJ)NMQv-M%KChBz{+)xMS+ULg9yoZNCM%R z7axRUfRxdGQ!klk!JB<42M;J4g}k$hlJYKND{4J9R%blLlKB;oXh?NN9OK%%@#EgB zWUEMjM+u>cbFZb*VXuF#xOCUqCiFZY{i{cjzRv!|ikxGd7GCTKo&)1{mI2_!g*G&Y zU#OrV#psYzf}o1V{enn=4X&iC3j$|Mt~1ZaX(Sg1cM;o33R8%p<@1w zw?KpRe1)z%bu#n$QMlcjdUmWYp{%afhC#z;_b|rOrBV&cSQ8@~>a&vJR_7^@@Yo{^ z!FKP!W34}ruA3heS!QD>O)po4RvpzP%J?w$5_x3FKk|QHV!iLi68Q11Qi#BFK1~`; z>&Z!Xe~=Qa&g^8-_J(bC>!oa|k(6KIjm`>n(0H6E|L+jcO@=?IwjrX$KrP*}kQ8pv zRyfzRx0@GpIH#H;`vKkr;lL2Z+iY^%B|j7?n{LLP?qa{tH&nOe-5t^T!)bdQKw%C_ zv^}ti`X@Y$cRkEq?&a46$zv_c$Wf~6_ypqw=CNyns-Jv>N76KWXWx?_R*Gu80@m5C z?FXW*a?s|O3!3Ix$dEHd9!-dGvf8q59_HqQgH}_lov5&%ldcrdLdE7GTTFDXI>z$2Ek)dFe{%58NWBkIbjmT70bl zZBj<{52h{#6x1e(Ql_a)K67ZD>k*C;U)^`N!JOn69%uW|`5XdCz`}{!y=KFaeJc!Q zFdY7?c8687sey~MAi0SauNtquQj=~z1HIAFE8uq2INCwy(!FIuzNtCBhv-YNCJi(7 z&i-Ygue5;rNs+LBmZykChJf~*iLG+4o1aO&0jz@e5)7#Wkqa<0j7|MLxQ*_tr(mtJ z#LJ$TO=fTwGp2zZ7lYbs{nu`@SJg3UYoJf*nv`)edU%WpG{ai^&0Y9OIj2P=4i)(m z8paxPbLhj(6Mg9Q2aG>5mWCx_8*)}lUz%Dle`dFZTW2FtYq7r>g1T_%v|MsKt z@#A8hK-|)&aw2O82NV(SZZeN~&e0$9*+SZ;BDp@E@I0>5C$ny_GH6K>xOx+4 z@0R4Mxb*ejf8cDd@v|8Ir?aFy;T)(1{ooA{Sp} zdOyAg@^m{nz8+8zL9(hMYc;mt@R3xSxtg6xHIJG*p%#+))fcj>{7#!vfCn)|C3K zf;>7%PzF7SWbJLXvX6wnrKkXN&Rk<@e+Y`%nlj$IhNsw(e69Y}R_pl|l;NEDeok2z ztaSo^Q4aRtp1P4r4kC}4*iubbLh3l*O5} zt`Np16UN{V&FpK@!Bo&U(tm9?bV=~*l(OH~OQQ^m{dNVvKA+MO{IMv9ph{%A@KHYA zm`$(^^W+uaPKR`jnC^zXP#?xNm>U3AjUyeyapU{~BMzOv{^u?Y96b!BvMg>zH^e7Uy#k~mvg zR&?mf`1TiCgC{Mms_xSXGwI&V$5e5Q7qCJsAW6uUCR zGKuhvj!S<>@tW1gmzxt!yRJO|+Z-(xc`_V=7%O^}<Xe4WoP4NIUF)+$hM#*xIU26(oBmV^tnb0QAvJ1sy-cL_=ut%5Cr+5V-=h#2{cn4Hloa9_77;}!6xC^%ve{x@)%Vu{^_ zry;QU+WG1H1OcFYms6CckJa5U>OE*`?)s~nY9;T)LE&G0~3vO@PUcE11<;()2-w`UP$7Vr<~E%B?BUB5JL=! zKT9~C=b0CFPge>BdEAtE+CtV8YMo)RQ>a1#WMcCKFPM|QK8N5Zh6~A!+q3@i8+@&u zb?ZlT;fDn|BADSkSid>eR`Sp7d+#KVrDlFzBdZ$gfkLt|+KR6ITItJuDVO((K=L`cIkLY^) zTP@mUU3l?UVISGMP;wGyD9*-yng5piItD+j6~4kUO}FdTTh^{+uy#~61Ak0<*nFP) zyNW0Dm`7j;am;KmPTHL&(tDO{xDR6r-9ys=BHII#N(i?#$=dY_m{jv8Pb-#6)jzD-K&gQnJuOT zQu6S13BFwgch<5nQ^)akVFfdsct#uU%ZuYt3N#}g7HzI_K$kmY8IB8C_4T#4cz9-v z#G!m=dy7^>H?ZT+amAHFEOfls)a*UzYrqx|we0ETLx;I0hZ(X_7>{yd=|U>6nek)D zF{~tOY>cCqhc5Fy*!D!)`n`YzfukYgMKIZdVyu=jWu*GXZjz>mjA~1w^fA@wK=iGu zbxIq%3}-SHxXHj}wY31w_nR1)qPA>{rtdgb zq!%gmpss>K|F5IHkKcy(+ge>gO^G3_)yFUU&x|wW<$fD)&m5ORl*=&3T5TOYR4x2wm97FymJr_gI2ec4Ady==nSD6E^&ocUr=K>_D_AH^;d zqI`$4=kfv5O8R~9+{`1-bCJ$S)SlB@5P)%VcPbaL3c-TL8J*D*){U*NWF9z1$)v0L1h)BI1QqPV^Jr&18u{3YCk}fD?iUU_v?K zB`ea@Ng-8MP8eqA;Q=@bvOjTRQM=Qu2&%LD&W#($$FX!cghG zvk)v{8RI+KGCnmTtw?Yet%HbS-s@xNbLFmB&$C*j8asAa5Ni%RVq&C3H$Uy)@V6%h zR-XlD?8@P<&R#S5o~|gLR5dtSqg-R!rS9pqBrAip( z82UbI6ulk80~q(Wop`jqrfeec{z;=iM@Tb>GIn9G7h%jizpv|>Z0xf6#y+P-fl=E3 znCIf@8{C@lD*$ui%#T4ECad($X5VvIQz3VG%t-(SRj_Bc3F&V0;yW zzJGGS-VpSI!0E=lD{5fLW5^Ojf>Lj~=`KiXXq>0nev^}@Q)>ra)nQLkBIxA17@VNE z@EH55_455eXfumb7ovl~y?X4S8FPChoL8M!Or+-746dLGQB2%3l8qXNuMr;-=rp{n zD$SHsTq7h(jI$i=u&QfxryRQG} zegI2H`fhb^jJ{~x&`2;I36+ic4mgpI zxS2DzzQs);@b#$DICJ{39WUP2nEEj7Bdlrq5oLfX6fMl{t>`i<@&Ilud6)|^8Pk;? zh9v_c2iqky>prm9kqB@gl56ihTN{99SD;W42`fkw==#I=LUd>&2;3y7>vDRV%&)sl zFYZx|)tT{zdJraB!SK2abe438C0pjSsVU;rCTWk6NG9}l5<|GD(oHk$xt|-yibU)i% z^9mViq;{{m9_4Ihk&L#c?~IdU(OZ2GlZ9WKROc0(Q0aHP>5i_gJV{uw`D{K7_P=@EDHw;ve2kRk=1f7la=t&XMFjsiSa7ap?RYHc7=6PS;b1 z&$D&RR4_*Nr^sM0i9e|OCvn`}=B|VW^`y7ZFF%YM&&c0xW{HB@v_H#R$O4yr~%-Dn9n=ZhEX>+=3!$$17JzRa+$`?Jf7c9Jrj6cu=bCa=9BStRGVtjjzi zsFns}dyn4oZ*d9%+H>50>FT#1U#FwHnPiy;^Mdj?C#&H-D`a8nv%Ve4EE3LO)H@vbP22Fh-qvczzI-Hb7wzY+{Fa?-rU0h;wUAR! z(nJ1G$)9LzTgtME@eGP3%1p7J8aA{=D~l5q_^2ekuYhybaF;1o?YLIpo2G2SKkU4> z6I|&1$@GmL6;dss4!>a?+oN_KhmlGMZY$H>%?=I~ye7~NxF7OuR!u4uVwo(b+<;qa z0@AoNu?4G=e()ncLPrfpm9|%ZB05<4-fwSwaXoM^1S;#Pgm2nz`Ydqz0Ef^cj_J*# zpV7<0X13BPWMuH{P$hbQ8&60QOWpr&bFSRnMGYTQ*8pFKSAd}5^I)O@$SNlIt81cu z0@_=S2{^7mmvW1DXAKX*l|3iVmHG^rSCOFpa2<4VWJ_1A-yJro3rUK0-(-ILUL^?y z?=;dY0gi7&v}Whp&q|~tgP4(ux)7M-j_P#@mmxw;x)f(Y8t01 zbKPlzA-EAiHfSdYQWM^@W7e`sJ7&K!zPD6n5LgtDUp!hylwsT?_@t>K1;Aux+0hyQYPVf9sUSJE{ zED7o&G^3T|<+$+r9P0l10IG2U&G`jFa?}7E#V@M^Ouq=$lZVaLhZ-Femjhzs@4|@T z($9q}byK^?&LNigjgP?9<~t}R4ij;-G#MD-%hk4u-SVSRYgKDGN`8m~o4mvb1|+5` z|D!bg`;WRJcF@Wa@%l3{mGM=1rS7-2UjB-fAeqA-@BGk;pvdLc;xmG@wVSQi`)@w5 z!)^(eH&PHn;+MNlP@-nC>`Yzph;{9+CQ@dmZ7k5#&&aa-Iqtm8TfAfY0#1JM=8dEU zk9gi!fR%hl5`!~gW0?gR!LKkL@CTNAZMJxh|Ag8~Wy!{2av*^UEcf!*OR}`SjxG363CER*C|21`0hr z1*;HM#3{hE{~9TcUZp*!y8=z#W}CSdc{o_Vm&(!5zWZ)$`Sg%AUbeWja-goLt1^H?y!0 z^xkJ^?TX^OOYgFVJqmmOG8_dWtGAX3AFc&g>ZYvZz1zNJ@py)LI^^vkWBa9O9Z->N zRc5EfIQTuOW^3X{2-!5<^@RSYmz`D@LK~K4?a)mH;(7?2JNshuV@*+MmR(+8pbi(L z?%h*_O7+PCbd6bA=^1+6{6kbMVli$7Ws=Nd&xbegGVLJ^uFt}TQium+c(qd}g@HQf z=#gdp3v8mq6Fdzn0GzFAu8+j0=nHZrsb(2vR>>$!Nir$k2}tuM9zw14#)m{Jt9hlm ztAhtM%fr(d#T-d|hY>TjyFW#ySBEbsc*Uu{4b1F|kV-yL<4<*m7xftD5fa>2Yj;-1 zPr$R?cW_+gX{MfgQ`-vO)^d$Le9xDi_TIq6F?J%^Vt#<4fi`vpaW^NjS~>SwIc7)U znb5Z#d~qAku{8SQH+~wsvbC2dYn(m~A-_n`!PVZ0z6V#X;$>{7u9#BQrHuf)51g(9 ztjC!t8ZF6(Runc_JkWNhxCeVC;+C7pNvHjT=OBI==+&=?cetuMW1J#$6mYMkK> z`7y+Mo{glWD;DXK!#vz%MC=u?-hAH;Tg&Q=gUu2dJsjc_5)RDN9m<$}o3bHEEw)9- zF}5wQG7;?!j{)wk638+Dk+IXM4ZKb&GFB2LmgP-uDM=AN~R zY4KUO&|e6tTi^Ul-~Yt>%g)B^b&!`36MI>m;wylp2Vmlg^knT{9}4NCKFz|JX6=>5 zwnXAJ;{%*OTPtsphvN79eo&d-4r8Vn6R$|XAjj-Zw?kSZ+#@YbsMYEcs&f+AWDK}5 zF%ZS}9kGVj@`+pG_w?c;WN=>}gEL z7E6;fUM`jRBEKXG>r-~rs`$hlo=0bhjbPpx$C&$X8 zXpv4oRt7(GIJaVm28{`O7IR7yjKJPL*i+dcQoxm4i}blyLzBh+{e9@TYyYU9x>oQJ zgkO@cfszkNLmxrAiQ*fGNo_oXJH)N1B4{Y)*l|!#fnpEFu_{>aW2I{6eh*rzcJ$j& zB`Ky;BPZTb*ZHP~nDpmuBU$JwY#2v{0)V5NqS$Wl%|$Cfit-aglB_pscEv)S<1iHk zRT||kM2yU*4DM16c_)53D%(f$T%D99O{m%a_zh*fsu8RFE)`j)>M>Bomh6Ifg@s?1_Q&jYFV{3K1)KdKEf`^tcaXc4C%NI9;db%SBSYWK(wXo zWUXHTpqolBs#?jL^&8v812SPQ& zs!;t#Bplw6e|eO8D)xgT79=&+pR#cupAZ~|rcH*U59M0gn_6t=IYu}<2X~-9wfE!L z@KajezXF)HVP;zgOV5a@|+3( zSfU+icO@U^A=iyma45Sa42stup!u^3-(cv>gZwu1u{FQhIeQHPUoJlC_^pWnuSL)k5QQ&d!$f$U_8n5l^{*C3(f1aoM+RyErUq~xF_^)!(QVM_8-uUD4idEe zu^J}HfTPJ1Lub0n8k%*ukn&C=KpOA#E7KbmlD>M#(mM23*g#hr)Ew4Y*hg!fSFE$0 zEAUK0s)nwrxtvr7mK}tD#e&Q`E{fg4rPpSjtnOXy%OYCgk9;@*d$?9V{pt-7@EJN$ zaMH!^l>$FUjY&|?a6W$Vajmn`zAz(CsbuLVuRp*$qg~51jWBMeC7)}Vz+Xvup+Bh3 z<&0?`(M%AH8x)oYF}>gbvJg+;^|9Lo-%t=oWnT@)KJ1pc@5#uJV_ALq6r@G?^XJmH zcN+m@dGLOjmSR2YV`G-Q$=c9K_&ewT=@IF1wXg;HmUI|V)0DHo*ykmgIfG0dNnQC_ zQ%V&Z65gJOZ|(a!OkauMBhhD?;Eu+KnbtE=jI(-OZ~KIK>O?-FV`Gm~o00zZ_pOJ# z0&Lw;RqB+>-Tf3N&02|NTT~|#0Z4*W=?GM=LZxlOh-Y&-&_5~wMI7z;^IF>4uX|Z` zo5!@_8b|#UoJ5$wevU(j`*gM(R{cK$@)-^0Pjzqh+>^r1C)@<*rqu)SuRqh`o;dGg zmM4h$dsGlcPT}A4uAAabcfWcf6dFp>k^jFcZJQ488wA)pLc#p(5d%|W! zXe|fbKuPC!Q}y;07lAZ5?ry9l)@LcBXcnVT)V(B;{$-Co_+C8$uQ~D8igj&!#Fv`A z{F24?rzRv;$|?>ORE`b?I#31Zbk@+c$*ylA%#wmIq%bN!gb|U+>yE;(W4xB;-tmNo z&)w$&rx_!t{J)i4@b;nop%dvEmBEc?ZT6G7<_YnEll|7{e+tjoY;Ve3MdVFyAxYxL z2eV_-uTGwn0e@JTCAwK;g&sN4W>UW^432pndCyR4W!2=F7CW~~*jJA)P>jj6dMvAl zU%-J=CXuM!X;4LLZ6xn*?;aIHJ0^0hqp2Nv9@Uv6G;*{THli614Uov+k>m8FGU00r zl`E%1aRlH3V3CA~-s;>Q{RljFt?wO)Y_-cxGI5Wy*+Qy4$Si+Wtlt3JL36Jq(iwGb9WML{n{Q!-Xqqw zJSh@Fx~8W8056klVozx$AqGDJO>$mznIcDZanxrO*+B@j@I|PSR*mnjT5<<0=!0sH zrb~WRLNUopLHpnyL-%7nz1#dh`t>cIw|#0PMU+c#A?q9f_7=OT9kD(Q_ zr|9rop!2_fCvyCcz*Kq)t%jc)IV>#X-ry2(44*83dgF%r*1YOrnd5A3Mj(;U(@0I4 zdKJu3JeD^^Esfq0h#%_T_*{KImle?XW@%!3jSBaRi*3(u&T_q30q#GDdlQP{3uIvO z4RjFCHTIho^|O(yX+>DKcZhw~_s2u*eFav`Tb*`jrblxX^`sZ}qii<=B|qnzkG!9O z{RpFVYjOwCOa_!10Cg=lG-iMuKzrnB(-Uo2?l}OfouqINU!m z{uQ>*?@>IS^&G39?s9Y4T|^6cWzcnK&f)r12A2mF*-FYzNco3;(XDHH|jix=#Y~1OV&z~)x6aN5xuk=68vTn4?l_bJaasL2J&p(momBPhY zWDWwevJi5TFW!v$k8e>`Z%(Oqr`&&OUCFi?)C-9Y;*kU;h-1mg+ku|GoYSMWo?DR# zMpo+EhBkrJns%$CYI>9r!KUf=v)Y$-+^Rqfe~F2}?s)C_)U>xL8CQNUhH&vurbBZN zMH0El1Au)2`eL$x((fey0FO(HWo~mE!Igh8TY7(hE@qiwp7&2%q6-^;JI#;**W`~S zkF`ZUo8jnK3#~^`ykz6cwtzx_k9RpAl`Gr}Q&^ok8;wB7V}{x_wYGAM!Xicswg*$r zJ;|;_+t?P#)Y-BQc0kmosU$aqIRc?YbSzOKo*&DrCan^Ug)+RkG5e7871@D|`O_XGe9h`e2U;ai(~EjeMAOt^wJmJ5~h zBD)P5^6Gy9$4nk+t)OT=H9l{H=l;po&o820OZ&M`er%$Vo!m*94lSr~g3c2KimBAfPx2*tfct1(Hu=tIo*=pAFTD`U6$vkr* zQi_Lh$UU$r`qrawq~Bl5X2NY&?pJwk2O>ShlB@ahYoU|I8r%2@!PMU3drO43GrFme z#L7rQ#BM8*(3-;5Y%T7r&c79=+l_)tiy@3F9+KmzL)RYulmUl%YbL5R`G|9OBzqb) zT(jWeSbaws_N(_Ag{6wy=rGw@-^CM`m8UE~kmGj3q!0idd*Y#%Su~4gxSfgCZxf95 zw}}4$CL{6{ogKq1n@w=5ER1}#EHFs#k~yFY_ZqH;bV|>sUR$E!6x-Ye7#%RKync0h z(%VdU4c(>P%+cCLAMDe_6L~F#%LY7T{*}x$@C8x!DE)r>e~oUdMHhxI-|XrHhTc2| zJpJa!Uep29>DrZ-h`c%WUl7FjfwoC3f9q|D;5z!Rs-8ccVb9>}`)?HKcIme3OuTi9 z-s&HkIg`K9v64aNs(5QqSiaLcM6Kka`G`q7;w`m-^~Oixn)ELM>PNylwaxvE(o3R5 z5^-rFnFLJCHn>0C198t$wCB{9?ggKXx`JrBGWa`8mlr|S{{U8G7%b=RN1sGczmPTJ z@LtN90m>HYa2Z?uZH1ZUcS7{bx15`Y4DzoqZ(EP-WuOViKw^u#g-Naf+YC zwy{ZSn!c8it%c_*%Ahn&{Bbro;Nu6W2fbm(eSZ3UHX`O(Vln{~EO#oO_Ce{KicJk3 zlc8K%OW_$LI(&A`tK1t%MX6;5H2K}wF+3L9qzrzQh4BW_H5Tym#V}imG{p+LPSyfR z2T{}U827CY7~LHj^GDMnd<`}@<8T1MZ0?o67C8R^fKvQGh>asmw}|X?wUDOY;zgPA zzmo&_PzM~!`e?OFh??qIHkiwa+j-2VXh^BT_h zezMErD@YzfER~G+3Drs@zd-qGqH5yoRB7G&58#)D$DOYP^P=%}~f`Fd>zy3EC(i^Jd>O9+1O~pQ$_# z)YOhMP)MiJq-IR|?ynrI_E9~z8$eN=quV(-_pHrw>iYIA5Na0}60l;~er%WmR=@+qVOP4s)8@y43FPZ9miFf_wBM zW#g~!NbVfx4tij)C*Ff^FL>YbrWj=Y<9<|FE*=`x?(|(kS!D@%XLP13%UMY&aT)3l zzi@hkgIBKf*}N&K#i?j4xBeN_!H;C)D#_L0m=27+bJu{w9kX6HAHd(0bRHGGFn`3R z=GIH=9aX%!?NzWiy4fO-|ms6iu%vT< zcqhG9v+&KKk)&;NN|rd+dnjoD_S|-GGv0tD(WSPJ!#b>kPDl>e?gT9%lWbcwZD#jdLGNheXC!?x?||tcDLd=u41~98$Iwz z79QNcnu?3hp*h9}0=(Mx?(0yymgeqxC%Bc1JGsdwkR65H=Z5vGo%-*Gue95#o148V z#?Ea20Koo2N%U&x^z(6TaU83(DzIXN z3^4RQ)RWtT`KC@GtyR!nA_Yq;Qj`maE&L0;f)lR zmgoQg$vYzh`qqz!^`8-GlcX9Q_3UjU1rS2ytcTF2M*Kx}8s4XGG%?$1w+jxifVA>y zcG0RXe}sj=FX~tI#V|On4@r{F?O#xuD|DF}6BD1EgN?mAb?R%X(1+UXXV7%(o0~mi z(oZ(X$m0__ZVeIRsmZ|TdVP8I()5d&1HN3xxNua z+AU6VzB9>Bs+{B6m93y0#PcG`?Q95<&RSE+{4>ygwPwp$o*Q+vzKt~t5z-0rc6#h@ zyocy<>J3&)V0yRdQmv|YB++2G)_7cLcTwEw&|J6nb?^dPOOgc8IQz0je(U4DFvNci zGs4;nY5q0~sVFx3LN)B9a;~U1DF?VGZY!ID$jvfE1WhEa!BrGwvFre^SxqWW_(o^& z?6+{q;&!zdg$7&7Rt_!8_kN@gU_I#Q2bSxXlIpsx&FqcLu2=)q4A)QN8?rR7311K# z1eslbygzr5`0h2zv;<@fkF9NZhkN)JKxP>++}x+>86V1kCDE*om8JNO>^|(owopI! zgr_n;0E8n$fV_L=XSt9&1tEUqdCy)I zu~=fo{{V`6b^ic?=lKd?R8}LXBDb`ue%EI9+02XcC)9TQM;}nNpY~Xn@7>4$00Bln z(x8(&)5tRx?XC|-L&JWA(-FCRVri1%-9p>kpq@1y6gO4>0D!jT z{{Vp0O4Q|npo0GZO=irRWX0nrt3e?g{R0(0_R!P{)X`^NYiv~1*We8_XmL|@;UVbxvx4_t-lNE%cftEbiQSU%9w6rJsA&wsOy>l?r$MB z*9{nO=s;v~(bV!7eMLs5i#T2=rqQK;Hftm^*-QTbJ?&389_#7bkx;8urb%QqJzA@) zRDjbFU>Z<)6#!~*JcMoBdWxpfwoZ!Vjsvc24cZQa5jx;w_F)71C+S3wT=BAh^WOCszN{{RvL z*KekMYn-^yopNJs!}X-lBb7G5TZoj9H*P1_(x%d+lT5x#m`HqOUN;$86pS*k?g+@O zIqYPJoT1{hd@W}Pn&VZ!Ky0+|BxOJH(nI$X@jRbkYHT}~;l8r)b-Y?!wjXG<)CrqX zwUPHSLf>*Q*x}S+w`1>Ou{=qkE{}B+TFNeT%cS$H?o?n)AHSmxj5{B~s@_RHrF%8h z`=pv@F3pal*G1u$4PicutfYfVoS7n&47H@#;zb?7=*!%DfklRL1XYbYPq@^s(%Q}= zB(2+^1Gy)l1Ju>{^hw%fMv^o_*fJd84_=x5D)zZA*sPmO(G}vfaf$9&VoQ_n{nhke z&nB3PciM%vg#?ybEP`v-1uiegVcjD19<9{-3grV8aOy~4bDFwsFn(f9D78`iIixbH z#}urygwG>-j>p!l$>hs4l9c7xw@*_~@`>^8G-f!ntO{QMs_0B(<7V+HYH2W38<*^1M9|6?usk;GduJS+^Imcz^vznZxt_-0O9SAJqdi3cYu@-C zTlnR^@U83`<*>`TTdvE9hkfv$n0-b^y>hntb>+qCS=-ymCDXRWiAT&&rYq9CK)xUF zzPi8hE4#SVLZ!!_9os;GokL|d@;ds}n7$<2-s&v+rkE|nu$yyxYQe6f2l#`q`H!x7 zKB9q`FD0*mU~79vj_btEyJomwnJmHTsp|fxwQ_d%v%z|okxb&{AIuVG=G~mX&V&a`;;13j>=VS=bZUZU5$X`A z{{T~Pe~1t6zh7gK+*K=x9`nF6c05UBlVq zKAwbi2eB30X&O1wA!G;bc2Qjm>uHp6JYqIvAbRdlq;??sPzNL8sfYH|_OUi??E+e$ z9Y1!vN&HUZ_|{sH=qdvtk=fkk*pg4FG~o2aSOG};qw=Q&U+#*8Jvd1~69iB0ih3YI zc4c$a1I0kHQ-qWO)lG839XD1*5;H{h7fO0J%0iRxf(Z9PRMF|)9e=jk-)X0ol|1Nw z>!Ndk?nXsbj!cSTE~`|^)k$8ZN2Uj$cwYW}M^A>{-M`{^Uc4B54b<3E6lX(*=%nixwdkXM8TB%*QZ#|Z*~?)il&{cHs#L( z*7OcR^jGKg^b{=w$Q7zU)mg(uBS#{YbtPF(P)BNdwra#!4NnJ=Py@6(R*KU~Qn1RUO*F@vx3Zd2iv`MtI%$rs6{hT>z*3UkBp7uBdx}Y|;(b0VFDX_8 zkjg;*H$Bg;2Li8br1I#nT+T~K;}}F$9>WwEN2O>O#UYNBfX*;+(xH2NZ9j2>wW={_XJWxvtx2mH6v;BtNbVf`zQHu*f_ zyPp~lH$?EP{EeBr$sfJNs}$3{L2~t0AaI|r_k~}O;2m5EC3QWH?3L``%%l(E@8)`!Vk_1 z{cBn&W{So;IOJrvjO7)0+IsMMikPN5Qf;STF=WzVKX3*70jknzY`=CiVeBiSC_O0( zQD8Y{vs6Dk0e-7Mw~HLczm;^&9(sy}?4o|C2h)ly2S4^$PyDkK@3BC7#w%I$@9sGs z#-V#T)A&OVY5=cc8rfaNb8YfV6K+%JX_DLTk=+PV#GSa{eJWfs95Rr7#XB?sOsuKZ zay>^h zHI}6#lSoA|709Nr;-_V%6`%+Rrj@G%Q%ceTxmsyjvR0T@fFLPND^?2AO3(#zwHqy3 zDNJPcpbDjFrE1YiLX_+ULbRJI*2+z^fUIMr^6JT2LbMF8vlMNq_ELnP3fpMeR)SLb zbi(CLw1zy@zb=n1kX)tZpGrKUdsW{qjg$q-MjAs1tG-<($^zzmma6N9P)EIFU+60; zlMl+>y(_2LNwSrK=5C{=HP?jvISa`jood$!Du&4bvX$bQZ)T826$UF%r?oJrwH6DM zjC9hNtA#0yl)~jkOw@i~HF1)oD@BGuvYWE3ZKT^pfP-Zl4Nta>w5$bvv=q%)+e@~c zfT+-h7$Y@aT|OrM9jDY*p4wfstQRn((^!A2f6P^BEoYCa5$F$3C*1 z95BK6rQp{~C7MVu$`904NpzUo?qyH4D+S709rU_Js}1$9-;Dgr+1I!==^B*qNv3K4 z0A@ngq!zWCKXx<8g#Q4zPaec)n)8WtyNLOW?)ukB;EVQVmqXOiqJrS=f>s|aQjUi` zf$#M_@k?RuY9XnkJc@|NBr?2HAz340tf#RZYDDTP$Tdpxc&6?8(M2#Dk7`0ajTBG= zN}ozXr#&d5fDozlG~I{NiYNgJA4+LM*il6QG^e#Rr?nJN14C8YrM+T0S~dO&QNhD4+!L{i!w|Lq!w}LWk0m z%RZD*KnBC;X?CAVD4+l*wL53E6i@=}K8BPb^rDJF6em3?;XSCLm=Y|0)hfg@_kT(# RpbH2+s%0FGD58Kr|Jfm)sf+*s literal 0 HcmV?d00001 diff --git a/xs/src/qhull/html/qconvex.htm b/xs/src/qhull/html/qconvex.htm new file mode 100644 index 0000000000..38a363b082 --- /dev/null +++ b/xs/src/qhull/html/qconvex.htm @@ -0,0 +1,630 @@ + + + + +qconvex -- convex hull + + + + +Up: +Home page for Qhull
    +Up: Qhull manual -- Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options +
    + +

    [cone]qconvex -- convex hull

    + +

    The convex hull of a set of points is the smallest convex set +containing the points. See the detailed introduction by O'Rourke +['94]. See Description of Qhull and How Qhull adds a point.

    + +
    +
    +
    Example: rbox 10 D3 | qconvex s o TO result
    +
    Compute the 3-d convex hull of 10 random points. Write a + summary to the console and the points and facets to + 'result'.
    + +
     
    +
    Example: rbox c | qconvex n
    +
    Print the normals for each facet of a cube.
    +
     
    +
    Example: rbox c | qconvex i Qt
    +
    Print the triangulated facets of a cube.
    +
     
    +
    Example: rbox y 500 W0 | qconvex
    +
    Compute the convex hull of a simplex with 500 + points on its surface.
    +
     
    +
    Example: rbox x W1e-12 1000 | qconvex + QR0
    +
    Compute the convex hull of 1000 points near the + surface of a randomly rotated simplex. Report + the maximum thickness of a facet.
    +
     
    +
    Example: rbox 1000 s | qconvex s FA
    +
    Compute the convex hull of 1000 cospherical + points. Verify the results and print a summary + with the total area and volume.
    +
     
    +
    Example: rbox d D12 | qconvex QR0 FA
    +
    Compute the convex hull of a 12-d diamond. + Randomly rotate the input. Note the large number + of facets and the small volume.
    +
     
    +
    Example: rbox c D7 | qconvex FA TF1000
    +
    Compute the convex hull of the 7-d hypercube. + Report on progress every 1000 facets. Computing + the convex hull of the 9-d hypercube takes too + much time and space.
    +
     
    +
    Example: rbox c d D2 | qconvex Qc s f Fx | more
    +
    Dump all fields of all facets for a square and a + diamond. Also print a summary and a list of + vertices. Note the coplanar points.
    +
     
    +
    +
    + +

    Except for rbox, all of the qhull programs compute a convex hull. + +

    By default, Qhull merges coplanar facets. For example, the convex +hull of a cube's vertices has six facets. + +

    If you use 'Qt' (triangulated output), +all facets will be simplicial (e.g., triangles in 2-d). For the cube +example, it will have 12 facets. Some facets may be +degenerate and have zero area. + +

    If you use 'QJ' (joggled input), +all facets will be simplicial. The corresponding vertices will be +slightly perturbed and identical points will be joggled apart. +Joggled input is less accurate that triangulated +output.See Merged facets or joggled input.

    + +

    The output for 4-d convex hulls may be confusing if the convex +hull contains non-simplicial facets (e.g., a hypercube). See +Why +are there extra points in a 4-d or higher convex hull?
    +

    +

    + +

    The 'qconvex' program is equivalent to +'qhull' in 2-d to 4-d, and +'qhull Qx' +in 5-d and higher. It disables the following Qhull +options: d v H Qbb Qf Qg Qm +Qr Qu Qv Qx Qz TR E V Fp Gt Q0,etc. + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »qconvex synopsis

    +
    +qconvex- compute the convex hull.
    +    input (stdin): dimension, number of points, point coordinates
    +    comments start with a non-numeric character
    +
    +options (qconvex.htm):
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Tv   - verify result: structure, convexity, and point inclusion
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +output options (subset):
    +    s    - summary of results (default)
    +    i    - vertices incident to each facet
    +    n    - normals with offsets
    +    p    - vertex coordinates (includes coplanar points if 'Qc')
    +    Fx   - extreme points (convex hull vertices)
    +    FA   - compute total area and volume
    +    o    - OFF format (dim, n, points, facets)
    +    G    - Geomview output (2-d, 3-d, and 4-d)
    +    m    - Mathematica output (2-d and 3-d)
    +    QVn  - print facets that include point n, -n if not
    +    TO file- output results to file, may be enclosed in single quotes
    +
    +examples:
    +    rbox c D2 | qconvex s n                    rbox c D2 | qconvex i
    +    rbox c D2 | qconvex o                      rbox 1000 s | qconvex s Tv FA
    +    rbox c d D2 | qconvex s Qc Fx              rbox y 1000 W0 | qconvex s n
    +    rbox y 1000 W0 | qconvex s QJ              rbox d G1 D12 | qconvex QR0 FA Pp
    +    rbox c D7 | qconvex FA TF1000
    +
    + +

    »qconvex +input

    +
    + +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qconvex < data.txt), a pipe (e.g., rbox 10 | qconvex), +or the 'TI' option (e.g., qconvex TI data.txt). + +

    Comments start with a non-numeric character. Error reporting is +simpler if there is one point per line. Dimension +and number of points may be reversed. + +

    Here is the input for computing the convex +hull of the unit cube. The output is the normals, one +per facet.

    + +
    +

    rbox c > data

    +
    +3 RBOX c
    +8
    +  -0.5   -0.5   -0.5
    +  -0.5   -0.5    0.5
    +  -0.5    0.5   -0.5
    +  -0.5    0.5    0.5
    +   0.5   -0.5   -0.5
    +   0.5   -0.5    0.5
    +   0.5    0.5   -0.5
    +   0.5    0.5    0.5
    +
    +

    qconvex s n < data

    +
    +
    +Convex hull of 8 points in 3-d:
    +
    +  Number of vertices: 8
    +  Number of facets: 6
    +  Number of non-simplicial facets: 6
    +
    +Statistics for: RBOX c | QCONVEX s n
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 11
    +  Number of distance tests for qhull: 35
    +  Number of merged facets: 6
    +  Number of distance tests for merging: 84
    +  CPU seconds to compute hull (after input): 0.081
    +
    +4
    +6
    +     0      0     -1   -0.5
    +     0     -1      0   -0.5
    +     1      0      0   -0.5
    +    -1      0      0   -0.5
    +     0      1      0   -0.5
    +     0      0      1   -0.5
    +
    +
    + +
    +

    »qconvex outputs

    +
    + +

    These options control the output of qconvex. They may be used +individually or together.

    +
    +
    +
     
    +
    Vertices
    +
    Fx
    +
    list extreme points (i.e., vertices). The first line is the number of + extreme points. Each point is listed, one per line. The cube example + has eight vertices.
    +
    Fv
    +
    list vertices for each facet. The first line is the number of facets. + Each remaining line starts with the number of vertices. For the cube example, + each facet has four vertices.
    +
    i
    +
    list vertices for each facet. The first line is the number of facets. The + remaining lines list the vertices for each facet. In 4-d and + higher, triangulate non-simplicial facets by adding an extra point.
    +
     
    +
     
    +
    Coordinates
    +
    o
    +
    print vertices and facets of the convex hull in OFF format. The + first line is the dimension. The second line is the number of + vertices, facets, and ridges. The vertex + coordinates are next, followed by the facets. Each facet starts with + the number of vertices. The cube example has four vertices per facet.
    +
    Ft
    +
    print a triangulation of the convex hull in OFF format. The first line + is the dimension. The second line is the number of vertices and added points, + followed by the number of facets and the number of ridges. + The vertex coordinates are next, followed by the centrum coordinates. There is + one centrum for each non-simplicial facet. + The cube example has six centrums, one per square. + Each facet starts with the number of vertices or centrums. + In the cube example, each facet uses two vertices and one centrum.
    +
    p
    +
    print vertex coordinates. The first line is the dimension and the second + line is the number of vertices. The following lines are the coordinates of each + vertex. The cube example has eight vertices.
    +
    Qc p
    +
    print coordinates of vertices and coplanar points. The first line is the dimension. + The second line is the number of vertices and coplanar points. The coordinates + are next, one line per point. Use 'Qc Qi p' + to print the coordinates of all points.
    +
     
    +
     
    +
    Facets
    +
    Fn
    +
    list neighboring facets for each facet. The first line is the + number of facets. Each remaining line starts with the number of + neighboring facets. The cube example has four neighbors per facet.
    +
    FN
    +
    list neighboring facets for each point. The first line is the + total number of points. Each remaining line starts with the number of + neighboring facets. Each vertex of the cube example has three neighboring + facets. Use 'Qc Qi FN' + to include coplanar and interior points.
    +
    Fa
    +
    print area for each facet. The first line is the number of facets. + Facet area follows, one line per facet. For the cube example, each facet has area one.
    +
    FI
    +
    list facet IDs. The first line is the number of + facets. The IDs follow, one per line.
    + +
     
    +
     
    +
    Coplanar and interior points
    +
    Fc
    +
    list coplanar points for each facet. The first line is the number + of facets. The remaining lines start with the number of coplanar points. + A coplanar point is assigned to one facet.
    +
    Qi Fc
    +
    list interior points for each facet. The first line is the number + of facets. The remaining lines start with the number of interior points. + A coplanar point is assigned to one facet.
    +
    FP
    +
    print distance to nearest vertex for coplanar points. The first line is the + number of coplanar points. Each remaining line starts with the point ID of + a vertex, followed by the point ID of a coplanar point, its facet, and distance. + Use 'Qc Qi + FP' for coplanar and interior points.
    + +
     
    +
     
    +
    Hyperplanes
    +
    n
    +
    print hyperplane for each facet. The first line is the dimension. The + second line is the number of facets. Each remaining line is the hyperplane's + coefficients followed by its offset.
    +
    Fo
    +
    print outer plane for each facet. The output plane is above all points. + The first line is the dimension. The + second line is the number of facets. Each remaining line is the outer plane's + coefficients followed by its offset.
    +
    Fi
    +
    print inner plane for each facet. The inner plane of a facet is + below its vertices. + The first line is the dimension. The + second line is the number of facets. Each remaining line is the inner plane's + coefficients followed by its offset.
    + +
     
    +
     
    +
    General
    +
    s
    +
    print summary for the convex hull. Use 'Fs' and 'FS' if you need numeric data.
    +
    FA
    +
    compute total area and volume for 's' and 'FS'
    +
    m
    +
    Mathematica output for the convex hull in 2-d or 3-d.
    +
    FM
    +
    Maple output for the convex hull in 2-d or 3-d.
    +
    G
    +
    Geomview output for the convex hull in 2-d, 3-d, or 4-d.
    + +
     
    +
     
    +
    Scaling and rotation
    +
    Qbk:n
    +
    scale k'th coordinate to lower bound.
    +
    QBk:n
    +
    scale k'th coordinate to upper bound.
    +
    QbB
    +
    scale input to unit cube centered at the origin.
    +
    QRn
    +
    randomly rotate the input with a random seed of n. If n=0, the + seed is the time. If n=-1, use time for the random seed, but do + not rotate the input.
    +
    Qbk:0Bk:0
    +
    remove k'th coordinate from input. This computes the + convex hull in one lower dimension.
    +
    +
    + +
    +

    »qconvex controls

    +
    + +

    These options provide additional control:

    + +
    +
    +
    Qt
    +
    triangulated output. Qhull triangulates non-simplicial facets. It may produce + degenerate facets of zero area.
    +
    QJ
    +
    joggle the input instead of merging facets. This guarantees simplicial facets + (e.g., triangles in 3-d). It is less accurate than triangulated output ('Qt').
    +
    Qc
    +
    keep coplanar points
    +
    Qi
    +
    keep interior points
    +
    f
    +
    facet dump. Print the data structure for each facet.
    +
    QVn
    +
    select facets containing point n as a vertex,
    +
    QGn
    +
    select facets that are visible from point n + (marked 'good'). Use -n for the remainder.
    +
    PDk:0
    +
    select facets with a negative coordinate for dimension k
    +
    TFn
    +
    report progress after constructing n facets
    +
    Tv
    +
    verify result
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    Qs
    +
    search all points for the initial simplex. If Qhull can + not construct an initial simplex, it reports a +descriptive message. Usually, the point set is degenerate and one +or more dimensions should be removed ('Qbk:0Bk:0'). +If not, use option 'Qs'. It performs an exhaustive search for the +best initial simplex. This is expensive is high dimensions.
    +
    +
    + +
    +

    »qconvex graphics

    +
    + +

    Display 2-d, 3-d, and 4-d convex hulls with Geomview ('G').

    + +

    Display 2-d and 3-d convex hulls with Mathematica ('m').

    + +

    To view 4-d convex hulls in 3-d, use 'Pd0d1d2d3' to select the positive +octant and 'GrD2' to drop dimension +2.

    + +
    +

    »qconvex notes

    +
    + +

    Qhull always computes a convex hull. The +convex hull may be used for other geometric structures. The +general technique is to transform the structure into an +equivalent convex hull problem. For example, the Delaunay +triangulation is equivalent to the convex hull of the input sites +after lifting the points to a paraboloid.

    + +
    +

    »qconvex +conventions

    +
    + +

    The following terminology is used for convex hulls in Qhull. +See Qhull's data structures.

    + +
      +
    • point - d coordinates
    • +
    • vertex - extreme point of the input set
    • +
    • ridge - d-1 vertices between two + neighboring facets
    • +
    • hyperplane - halfspace defined by a unit normal + and offset
    • +
    • coplanar point - a nearly incident point to a + hyperplane
    • +
    • centrum - a point on the hyperplane for testing + convexity
    • +
    • facet - a facet with vertices, ridges, coplanar + points, neighboring facets, and hyperplane
    • +
    • simplicial facet - a facet with d + vertices, d ridges, and d neighbors
    • +
    • non-simplicial facet - a facet with more than d + vertices
    • +
    • good facet - a facet selected by 'QVn', etc.
    • +
    +
    +

    »qconvex options

    + +
    +qconvex- compute the convex hull
    +    http://www.qhull.org
    +
    +input (stdin):
    +    first lines: dimension and number of points (or vice-versa).
    +    other lines: point coordinates, best if one point per line
    +    comments:    start with a non-numeric character
    +
    +options:
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Qc   - keep coplanar points with nearest facet
    +    Qi   - keep interior points with nearest facet
    +
    +Qhull control options:
    +    Qbk:n   - scale coord k so that low bound is n
    +      QBk:n - scale coord k so that upper bound is n (QBk is 0.5)
    +    QbB  - scale input to unit cube centered at the origin
    +    Qbk:0Bk:0 - remove k-th coordinate from input
    +    QJn  - randomly joggle input in range [-n,n]
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)
    +    Qs   - search all points for the initial simplex
    +    QGn  - good facet if visible from point n, -n for not visible
    +    QVn  - good facet if it includes point n, -n if not
    +
    +Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - print statistics
    +    Tv   - verify result: structure, convexity, and point inclusion
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when point n added to hull
    +     TMn - turn on tracing at merge n
    +     TWn - trace merge facets when width > n
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)
    +     TCn - stop qhull after building cone for point n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Un   - max distance below plane for a new, coplanar point
    +    Wn   - min facet width for outside point (before roundoff)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    f    - facet dump
    +    G    - Geomview output (see below)
    +    i    - vertices incident to each facet
    +    m    - Mathematica output (2-d and 3-d)
    +    n    - normals with offsets
    +    o    - OFF file format (dim, points and facets; Voronoi regions)
    +    p    - point coordinates
    +    s    - summary (stderr)
    +
    +More formats:
    +    Fa   - area for each facet
    +    FA   - compute total area and volume for option 's'
    +    Fc   - count plus coplanar points for each facet
    +           use 'Qc' (default) for coplanar and 'Qi' for interior
    +    FC   - centrum for each facet
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FD   - use cdd format for numeric output (offset first)
    +    FF   - facet dump without ridges
    +    Fi   - inner plane for each facet
    +    FI   - ID for each facet
    +    Fm   - merge count for each facet (511 max)
    +    FM   - Maple output (2-d and 3-d)
    +    Fn   - count plus neighboring facets for each facet
    +    FN   - count plus neighboring facets for each point
    +    Fo   - outer plane (or max_outside) for each facet
    +    FO   - options and precision constants
    +    FP   - nearest vertex for each coplanar point
    +    FQ   - command used for qconvex
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,
    +                      for output: #vertices, #facets,
    +                                  #coplanar points, #non-simplicial facets
    +                    #real (2), max outer plane, min vertex
    +    FS   - sizes:   #int (0)
    +                    #real(2) tot area, tot volume
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)
    +    Fv   - count plus vertices for each facet
    +    FV   - average of vertices (a feasible point for 'H')
    +    Fx   - extreme points (in order for 2-d)
    +
    +Geomview output (2-d, 3-d, and 4-d)
    +    Ga   - all points as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices as spheres
    +    Gi   - inner planes only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc   - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +
    +Print options:
    +    PAn  - keep n largest facets by area
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)
    +    PDk:n - drop facet if normal[k] >= n
    +    Pg   - print good facets (needs 'QGn' or 'QVn')
    +    PFn  - keep facets whose area is at least n
    +    PG   - print neighbors of good facets
    +    PMn  - keep n facets with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +•Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qdelau_f.htm b/xs/src/qhull/html/qdelau_f.htm new file mode 100644 index 0000000000..d8981e16bc --- /dev/null +++ b/xs/src/qhull/html/qdelau_f.htm @@ -0,0 +1,416 @@ + + + + +qdelaunay Qu -- furthest-site Delaunay triangulation + + + + +Up: +Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +
    + +

    [delaunay]qdelaunay Qu -- furthest-site Delaunay triangulation

    + +

    The furthest-site Delaunay triangulation corresponds to the upper facets of the Delaunay construction. +Its vertices are the +extreme points of the input sites. +It is the dual of the furthest-site Voronoi diagram. + +

    +
    +
    Example: rbox 10 D2 | qdelaunay Qu Qt s + i TO + result
    +
    Compute the 2-d, furthest-site Delaunay triangulation of 10 random + points. Triangulate the output. + Write a summary to the console and the regions to + 'result'.
    +
     
    +
    Example: rbox 10 D2 | qdelaunay Qu QJ s + i TO + result
    +
    Compute the 2-d, furthest-site Delaunay triangulation of 10 random + points. Joggle the input to guarantee triangular output. + Write a summary to the console and the regions to + 'result'.
    +
     
    +
    Example: rbox r y c G1 D2 | qdelaunay Qu s + Fv TO + result
    +
    Compute the 2-d, furthest-site Delaunay triangulation of a triangle inside + a square. + Write a summary to the console and unoriented regions to 'result'. + Merge regions for cocircular input sites (e.g., the square). + The square is the only furthest-site + Delaunay region.
    +
    +
    + +

    As with the Delaunay triangulation, Qhull computes the +furthest-site Delaunay triangulation by lifting the input sites to a +paraboloid. The lower facets correspond to the Delaunay +triangulation while the upper facets correspond to the +furthest-site triangulation. Neither triangulation includes +"vertical" facets (i.e., facets whose last hyperplane +coefficient is nearly zero). Vertical facets correspond to input +sites that are coplanar to the convex hull of the input. An +example is points on the boundary of a lattice.

    + +

    By default, qdelaunay merges cocircular and cospherical regions. +For example, the furthest-site Delaunay triangulation of a square inside a diamond +('rbox D2 c d G4 | qdelaunay Qu') consists of one region (the diamond). + +

    If you use 'Qt' (triangulated output), +all furthest-site Delaunay regions will be simplicial (e.g., triangles in 2-d). +Some regions may be +degenerate and have zero area. + +

    If you use 'QJ' (joggled input), all furthest-site +Delaunay regions +will be simplicial (e.g., triangles in 2-d). Joggled input +is less accurate than triangulated output ('Qt'). See Merged facets or joggled input.

    + +

    The output for 3-d, furthest-site Delaunay triangulations may be confusing if the +input contains cospherical data. See the FAQ item +Why +are there extra points in a 4-d or higher convex hull? +Avoid these problems with triangulated output ('Qt') or +joggled input ('QJ'). +

    + +

    The 'qdelaunay' program is equivalent to +'qhull d Qbb' in 2-d to 3-d, and +'qhull d Qbb Qx' +in 4-d and higher. It disables the following Qhull +options: d n v H U Qb QB Qc Qf Qg Qi +Qm Qr QR Qv Qx TR E V FC Fi Fo Fp FV Q0,etc. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »furthest-site qdelaunay synopsis

    +
    + +See qdelaunay synopsis. The same +program is used for both constructions. Use option 'Qu' +for furthest-site Delaunay triangulations. + +
    +

    »furthest-site qdelaunay +input

    + +
    +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qdelaunay Qu < data.txt), a pipe (e.g., rbox 10 | qdelaunay Qu), +or the 'TI' option (e.g., qdelaunay Qu TI data.txt). + +

    For example, this is a square containing four random points. +Its furthest-site Delaunay +triangulation contains one square. +

    +

    +rbox c 4 D2 > data +
    +2 RBOX c 4 D2
    +8
    +-0.4999921736307369 -0.3684622117955817
    +0.2556053225468894 -0.0413498678629751
    +0.0327672376602583 -0.2810408135699488
    +-0.452955383763607 0.17886471718444
    +  -0.5   -0.5
    +  -0.5    0.5
    +   0.5   -0.5
    +   0.5    0.5
    +
    + +

    qdelaunay Qu i < data +

    +
    +Furthest-site Delaunay triangulation by the convex hull of 8 points in 3-d:
    +
    +  Number of input sites: 8
    +  Number of Delaunay regions: 1
    +  Number of non-simplicial Delaunay regions: 1
    +
    +Statistics for: RBOX c 4 D2 | QDELAUNAY s Qu i
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 20
    +  Number of facets in hull: 11
    +  Number of distance tests for qhull: 34
    +  Number of merged facets: 1
    +  Number of distance tests for merging: 107
    +  CPU seconds to compute hull (after input): 0.02
    +
    +1
    +7 6 4 5
    +
    +
    + +
    +

    »furthest-site qdelaunay +outputs

    +
    + +

    These options control the output of furthest-site Delaunay triangulations:

    +
    + +
    +
    furthest-site Delaunay regions
    +
    i
    +
    list input sites for each furthest-site Delaunay region. The first line is the number of regions. The + remaining lines list the input sites for each region. The regions are + oriented. In 3-d and + higher, report cospherical sites by adding extra points. For the points-in-square example, + the square is the only furthest-site Delaunay region.
    +
    Fv
    +
    list input sites for each furthest-site Delaunay region. The first line is the number of regions. + Each remaining line starts with the number of input sites. The regions + are unoriented. For the points-in-square example, + the square is the only furthest-site Delaunay region.
    +
    Ft
    +
    print a triangulation of the furthest-site Delaunay regions in OFF format. The first line + is the dimension. The second line is the number of input sites and added points, + followed by the number of simplices and the number of ridges. + The input coordinates are next, followed by the centrum coordinates. There is + one centrum for each non-simplicial furthest-site Delaunay region. Each remaining line starts + with dimension+1. The + simplices are oriented. + For the points-in-square example, the square has a centrum at the + origin. It splits the square into four triangular regions.
    +
    Fn
    +
    list neighboring regions for each furthest-site Delaunay region. The first line is the + number of regions. Each remaining line starts with the number of + neighboring regions. Negative indices (e.g., -1) indicate regions + outside of the furthest-site Delaunay triangulation. + For the points-in-square example, the four neighboring regions + are outside of the triangulation. They belong to the regular + Delaunay triangulation.
    +
    FN
    +
    list the furthest-site Delaunay regions for each input site. The first line is the + total number of input sites. Each remaining line starts with the number of + furthest-site Delaunay regions. Negative indices (e.g., -1) indicate regions + outside of the furthest-site Delaunay triangulation. + For the points-in-square example, the four random points belong to no region + while the square's vertices belong to region 0 and three + regions outside of the furthest-site Delaunay triangulation.
    +
    Fa
    +
    print area for each furthest-site Delaunay region. The first line is the number of regions. + The areas follow, one line per region. For the points-in-square example, the + square has unit area.
    + +
     
    +
     
    +
    Input sites
    +
    Fx
    +
    list extreme points of the input sites. These points are vertices of the furthest-point + Delaunay triangulation. They are on the + boundary of the convex hull. The first line is the number of + extreme points. Each point is listed, one per line. The points-in-square example + has four extreme points.
    +
     
    +
     
    +
    General
    +
    FA
    +
    compute total area for 's' + and 'FS'. This is the + same as the area of the convex hull.
    +
    o
    +
    print upper facets of the corresponding convex hull (a + paraboloid)
    +
    m
    +
    Mathematica output for the upper facets of the paraboloid (2-d triangulations).
    +
    FM
    +
    Maple output for the upper facets of the paraboloid (2-d triangulations).
    +
    G
    +
    Geomview output for the paraboloid (2-d or 3-d triangulations).
    +
    s
    +
    print summary for the furthest-site Delaunay triangulation. Use 'Fs' and 'FS' for numeric data.
    +
    +
    + +
    +

    »furthest-site qdelaunay +controls

    +
    + +

    These options provide additional control:

    +
    + +
    +
    Qu
    +
    must be used for furthest-site Delaunay triangulation.
    +
    Qt
    +
    triangulated output. Qhull triangulates non-simplicial facets. It may produce + degenerate facets of zero area.
    +
    QJ
    +
    joggle the input to avoid cospherical and coincident + sites. It is less accurate than triangulated output ('Qt').
    +
    QVn
    +
    select facets adjacent to input site n (marked + 'good').
    +
    Tv
    +
    verify result.
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    TFn
    +
    report progress after constructing n facets
    +
    PDk:1
    +
    include upper and lower facets in the output. Set k + to the last dimension (e.g., 'PD2:1' for 2-d inputs).
    +
    f
    +
    facet dump. Print the data structure for each facet (i.e., furthest-site Delaunay region).
    +
    +
    + +
    +

    »furthest-site qdelaunay +graphics

    +
    + +See Delaunay graphics. +They are the same except for Mathematica and Maple output. + +
    +

    »furthest-site +qdelaunay notes

    +
    + +

    The furthest-site Delaunay triangulation does not +record coincident input sites. Use qdelaunay instead. + +

    qdelaunay Qu does not work for purely cocircular +or cospherical points (e.g., rbox c | qdelaunay Qu). Instead, +use qdelaunay Qz -- when all points are vertices of the convex +hull of the input sites, the Delaunay triangulation is the same +as the furthest-site Delaunay triangulation. + +

    A non-simplicial, furthest-site Delaunay region indicates nearly cocircular or +cospherical input sites. To avoid non-simplicial regions triangulate +the output ('Qt') or joggle +the input ('QJ'). Joggled input +is less accurate than triangulated output. +You may also triangulate +non-simplicial regions with option 'Ft'. It adds +the centrum to non-simplicial regions. Alternatively, use an exact arithmetic code.

    + +

    Furthest-site Delaunay triangulations do not include facets that are +coplanar with the convex hull of the input sites. A facet is +coplanar if the last coefficient of its normal is +nearly zero (see qh_ZEROdelaunay). + +

    +

    »furthest-site qdelaunay conventions

    +
    + +

    The following terminology is used for furthest-site Delaunay +triangulations in Qhull. The underlying structure is the upper +facets of a convex hull in one higher dimension. See convex hull conventions, Delaunay conventions, +and Qhull's data structures

    +
    +
      +
    • input site - a point in the input (one dimension + lower than a point on the convex hull)
    • +
    • point - d+1 coordinates. The last + coordinate is the sum of the squares of the input site's + coordinates
    • +
    • vertex - a point on the paraboloid. It + corresponds to a unique input site.
    • +
    • furthest-site Delaunay facet - an upper facet of the + paraboloid. The last coefficient of its normal is + clearly positive.
    • +
    • furthest-site Delaunay region - a furthest-site Delaunay + facet projected to the input sites
    • +
    • non-simplicial facet - more than d + points are cocircular or cospherical
    • +
    • good facet - a furthest-site Delaunay facet with optional + restrictions by 'QVn', etc.
    • +
    +
    +
    +

    »furthest-site qdelaunay options

    +
    + +See qdelaunay options. The same +program is used for both constructions. Use option 'Qu' +for furthest-site Delaunay triangulations. + +
    + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qdelaun.htm b/xs/src/qhull/html/qdelaun.htm new file mode 100644 index 0000000000..a42223c663 --- /dev/null +++ b/xs/src/qhull/html/qdelaun.htm @@ -0,0 +1,628 @@ + + + + +qdelaunay -- Delaunay triangulation + + + + +Up: +Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +
    + +

    [delaunay]qdelaunay -- Delaunay triangulation

    + +

    The Delaunay triangulation is the triangulation with empty +circumspheres. It has many useful properties and applications. +See the survey article by Aurenhammer ['91] and the detailed introduction +by O'Rourke ['94].

    + +
    +
    +
    Example: rbox r y c G0.1 D2 | qdelaunay s + Fv TO + result
    +
    Compute the 2-d Delaunay triangulation of a triangle and + a small square. + Write a summary to the console and unoriented regions to 'result'. + Merge regions for cocircular input sites (i.e., the + square).
    +
     
    +
    Example: rbox r y c G0.1 D2 | qdelaunay s + Fv Qt
    +
    Compute the 2-d Delaunay triangulation of a triangle and + a small square. Write a summary and unoriented + regions to the console. Produce triangulated output.
    +
     
    +
    Example: rbox 10 D2 | qdelaunay QJ s + i TO + result
    +
    Compute the 2-d Delaunay triangulation of 10 random + points. Joggle the input to guarantee triangular output. + Write a summary to the console and the regions to + 'result'.
    +
    +
    + +

    Qhull computes the Delaunay triangulation by computing a +convex hull. It lifts the input sites to a paraboloid by adding +the sum of the squares of the coordinates. It scales the height +of the paraboloid to improve numeric precision ('Qbb'). +It computes the convex +hull of the lifted sites, and projects the lower convex hull to +the input. + +

    Each region of the Delaunay triangulation +corresponds to a facet of the lower half of the convex hull. +Facets of the upper half of the convex hull correspond to the furthest-site Delaunay triangulation. +See the examples, Delaunay and +Voronoi diagrams.

    + +

    See Qhull FAQ - Delaunay and +Voronoi diagram questions.

    + +

    By default, qdelaunay merges cocircular and cospherical regions. +For example, the Delaunay triangulation of a square inside a diamond +('rbox D2 c d G4 | qdelaunay') contains one region for the square. + +

    Use option 'Qz' if the input is circular, cospherical, or +nearly so. It improves precision by adding a point "at infinity," above the corresponding paraboloid. + +

    If you use 'Qt' (triangulated output), +all Delaunay regions will be simplicial (e.g., triangles in 2-d). +Some regions may be +degenerate and have zero area. Triangulated output identifies coincident +points. + +

    If you use 'QJ' (joggled input), all Delaunay regions +will be simplicial (e.g., triangles in 2-d). Coincident points will +create small regions since the points are joggled apart. Joggled input +is less accurate than triangulated output ('Qt'). See Merged facets or joggled input.

    + +

    The output for 3-d Delaunay triangulations may be confusing if the +input contains cospherical data. See the FAQ item +Why +are there extra points in a 4-d or higher convex hull? +Avoid these problems with triangulated output ('Qt') or +joggled input ('QJ'). +

    + +

    The 'qdelaunay' program is equivalent to +'qhull d Qbb' in 2-d to 3-d, and +'qhull d Qbb Qx' +in 4-d and higher. It disables the following Qhull +options: d n v H U Qb QB Qc Qf Qg Qi +Qm Qr QR Qv Qx TR E V FC Fi Fo Fp Ft FV Q0,etc. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »qdelaunay synopsis

    + +
    +qdelaunay- compute the Delaunay triangulation.
    +    input (stdin): dimension, number of points, point coordinates
    +    comments start with a non-numeric character
    +
    +options (qdelaun.htm):
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Qu   - furthest-site Delaunay triangulation
    +    Tv   - verify result: structure, convexity, and in-circle test
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +output options (subset):
    +    s    - summary of results (default)
    +    i    - vertices incident to each Delaunay region
    +    Fx   - extreme points (vertices of the convex hull)
    +    o    - OFF format (shows the points lifted to a paraboloid)
    +    G    - Geomview output (2-d and 3-d points lifted to a paraboloid)
    +    m    - Mathematica output (2-d inputs lifted to a paraboloid)
    +    QVn  - print Delaunay regions that include point n, -n if not
    +    TO file- output results to file, may be enclosed in single quotes
    +
    +examples:
    +    rbox c P0 D2 | qdelaunay s o          rbox c P0 D2 | qdelaunay i
    +    rbox c P0 D3 | qdelaunay Fv Qt        rbox c P0 D2 | qdelaunay s Qu Fv
    +    rbox c G1 d D2 | qdelaunay s i        rbox c G1 d D2 | qdelaunay s i Qt
    +    rbox M3,4 z 100 D2 | qdelaunay s      rbox M3,4 z 100 D2 | qdelaunay s Qt
    +
    + + +

    »qdelaunay +input

    + +
    +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qdelaunay < data.txt), a pipe (e.g., rbox 10 | qdelaunay), +or the 'TI' option (e.g., qdelaunay TI data.txt). + +

    For example, this is four cocircular points inside a square. Its Delaunay +triangulation contains 8 triangles and one four-sided +figure. +

    +

    +rbox s 4 W0 c G1 D2 > data +
    +2 RBOX s 4 W0 c D2
    +8
    +-0.4941988586954018 -0.07594397977563715
    +-0.06448037284989526 0.4958248496365813
    +0.4911154367094632 0.09383830681375946
    +-0.348353580869097 -0.3586778257652367
    +    -1     -1
    +    -1      1
    +     1     -1
    +     1      1
    +
    + +

    qdelaunay s i < data +

    +
    +Delaunay triangulation by the convex hull of 8 points in 3-d
    +
    +  Number of input sites: 8
    +  Number of Delaunay regions: 9
    +  Number of non-simplicial Delaunay regions: 1
    +
    +Statistics for: RBOX s 4 W0 c D2 | QDELAUNAY s i
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 18
    +  Number of facets in hull: 10
    +  Number of distance tests for qhull: 33
    +  Number of merged facets: 2
    +  Number of distance tests for merging: 102
    +  CPU seconds to compute hull (after input): 0.028
    +
    +9
    +1 7 5
    +6 3 4
    +2 3 6
    +7 2 6
    +2 7 1
    +0 5 4
    +3 0 4
    +0 1 5
    +1 0 3 2
    +
    +
    + +
    +

    »qdelaunay +outputs

    +
    + +

    These options control the output of Delaunay triangulations:

    +
    + +
    +
    Delaunay regions
    +
    i
    +
    list input sites for each Delaunay region. The first line is the number of regions. The + remaining lines list the input sites for each region. The regions are + oriented. In 3-d and + higher, report cospherical sites by adding extra points. Use triangulated + output ('Qt') to avoid non-simpicial regions. For the circle-in-square example, + eight Delaunay regions are triangular and the ninth has four input sites.
    +
    Fv
    +
    list input sites for each Delaunay region. The first line is the number of regions. + Each remaining line starts with the number of input sites. The regions + are unoriented. For the circle-in-square example, + eight Delaunay regions are triangular and the ninth has four input sites.
    +
    Fn
    +
    list neighboring regions for each Delaunay region. The first line is the + number of regions. Each remaining line starts with the number of + neighboring regions. Negative indices (e.g., -1) indicate regions + outside of the Delaunay triangulation. + For the circle-in-square example, the four regions on the square are neighbors to + the region-at-infinity.
    +
    FN
    +
    list the Delaunay regions for each input site. The first line is the + total number of input sites. Each remaining line starts with the number of + Delaunay regions. Negative indices (e.g., -1) indicate regions + outside of the Delaunay triangulation. + For the circle-in-square example, each point on the circle belongs to four + Delaunay regions. Use 'Qc FN' + to include coincident input sites and deleted vertices.
    +
    Fa
    +
    print area for each Delaunay region. The first line is the number of regions. + The areas follow, one line per region. For the circle-in-square example, the + cocircular region has area 0.4.
    +
     
    +
     
    +
    Input sites
    +
    Fc
    +
    list coincident input sites for each Delaunay region. + The first line is the number of regions. The remaining lines start with + the number of coincident sites and deleted vertices. Deleted vertices + indicate highly degenerate input (see'Fs'). + A coincident site is assigned to one Delaunay + region. Do not use 'QJ' with 'Fc'; the joggle will separate + coincident sites.
    +
    FP
    +
    print coincident input sites with distance to + nearest site (i.e., vertex). The first line is the + number of coincident sites. Each remaining line starts with the point ID of + an input site, followed by the point ID of a coincident point, its region, and distance. + Includes deleted vertices which + indicate highly degenerate input (see'Fs'). + Do not use 'QJ' with 'FP'; the joggle will separate + coincident sites.
    +
    Fx
    +
    list extreme points of the input sites. These points are on the + boundary of the convex hull. The first line is the number of + extreme points. Each point is listed, one per line. The circle-in-square example + has four extreme points.
    +
     
    +
     
    +
    General
    +
    FA
    +
    compute total area for 's' + and 'FS'
    +
    o
    +
    print lower facets of the corresponding convex hull (a + paraboloid)
    +
    m
    +
    Mathematica output for the lower facets of the paraboloid (2-d triangulations).
    +
    FM
    +
    Maple output for the lower facets of the paraboloid (2-d triangulations).
    +
    G
    +
    Geomview output for the paraboloid (2-d or 3-d triangulations).
    +
    s
    +
    print summary for the Delaunay triangulation. Use 'Fs' and 'FS' for numeric data.
    +
    +
    + +
    +

    »qdelaunay +controls

    +
    + +

    These options provide additional control:

    +
    + +
    +
    Qt
    +
    triangulated output. Qhull triangulates non-simplicial facets. It may produce +degenerate facets of zero area.
    +
    QJ
    +
    joggle the input to avoid cospherical and coincident + sites. It is less accurate than triangulated output ('Qt').
    +
    Qu
    +
    compute the furthest-site Delaunay triangulation.
    +
    Qz
    +
    add a point above the paraboloid to reduce precision + errors. Use it for nearly cocircular/cospherical input + (e.g., 'rbox c | qdelaunay Qz'). The point is printed for + options 'Ft' and 'o'.
    +
    QVn
    +
    select facets adjacent to input site n (marked + 'good').
    +
    Tv
    +
    verify result.
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    TFn
    +
    report progress after constructing n facets
    +
    PDk:1
    +
    include upper and lower facets in the output. Set k + to the last dimension (e.g., 'PD2:1' for 2-d inputs).
    +
    f
    +
    facet dump. Print the data structure for each facet (i.e., Delaunay region).
    +
    +
    + +
    +

    »qdelaunay +graphics

    +
    + +

    For 2-d and 3-d Delaunay triangulations, Geomview ('qdelaunay G') displays the corresponding convex +hull (a paraboloid).

    + +

    To view a 2-d Delaunay triangulation, use 'qdelaunay GrD2' to drop the last dimension. This +is the same as viewing the hull without perspective (see +Geomview's 'cameras' menu).

    + +

    To view a 3-d Delaunay triangulation, use 'qdelaunay GrD3' to drop the last dimension. You +may see extra edges. These are interior edges that Geomview moves +towards the viewer (see 'lines closer' in Geomview's camera +options). Use option 'Gt' to make +the outer ridges transparent in 3-d. See Delaunay and Voronoi examples.

    + +

    For 2-d Delaunay triangulations, Mathematica ('m') and Maple ('FM') output displays the lower facets of the corresponding convex +hull (a paraboloid).

    + +

    For 2-d, furthest-site Delaunay triangulations, Maple and Mathematica output ('Qu m') displays the upper facets of the corresponding convex +hull (a paraboloid).

    + +
    +

    »qdelaunay +notes

    +
    + +

    You can simplify the Delaunay triangulation by enclosing the input +sites in a large square or cube. This is particularly recommended +for cocircular or cospherical input data. + +

    A non-simplicial Delaunay region indicates nearly cocircular or +cospherical input sites. To avoid non-simplicial regions either triangulate +the output ('Qt') or joggle +the input ('QJ'). Triangulated output +is more accurate than joggled input. Alternatively, use an exact arithmetic code.

    + +

    Delaunay triangulations do not include facets that are +coplanar with the convex hull of the input sites. A facet is +coplanar if the last coefficient of its normal is +nearly zero (see qh_ZEROdelaunay). + +

    See Imprecision issues :: Delaunay triangulations +for a discussion of precision issues. Deleted vertices indicate +highly degenerate input. They are listed in the summary output and +option 'Fs'.

    + +

    To compute the Delaunay triangulation of points on a sphere, +compute their convex hull. If the sphere is the unit sphere at +the origin, the facet normals are the Voronoi vertices of the +input. The points may be restricted to a hemisphere. [S. Fortune] +

    + +

    The 3-d Delaunay triangulation of regular points on a half +spiral (e.g., 'rbox 100 l | qdelaunay') has quadratic size, while the Delaunay triangulation +of random 3-d points is +approximately linear for reasonably sized point sets. + +

    With the Qhull library, you +can use qh_findbestfacet in poly2.c to locate the facet +that contains a point. You should first lift the point to the +paraboloid (i.e., the last coordinate is the sum of the squares +of the point's coordinates -- qh_setdelaunay). Do not use options +'Qbb', 'QbB', +'Qbk:n', or 'QBk:n' since these scale the last +coordinate.

    + +

    If a point is interior to the convex hull of the input set, it +is interior to the adjacent vertices of the Delaunay +triangulation. This is demonstrated by the following pipe for +point 0: + +

    +    qdelaunay <data s FQ QV0 p | qconvex s Qb3:0B3:0 p
    +
    + +

    The first call to qdelaunay returns the neighboring points of +point 0 in the Delaunay triangulation. The second call to qconvex +returns the vertices of the convex hull of these points (after +dropping the lifted coordinate). If point 0 is interior to the +original point set, it is interior to the reduced point set.

    + +
    +

    »qdelaunay conventions

    +
    + +

    The following terminology is used for Delaunay triangulations +in Qhull for dimension d. The underlying structure is the +lower facets of a convex hull in dimension d+1. For +further information, see data +structures and convex hull +conventions.

    +
    +
      +
    • input site - a point in the input (one dimension + lower than a point on the convex hull)
    • +
    • point - a point has d+1 coordinates. The + last coordinate is the sum of the squares of the input + site's coordinates
    • +
    • coplanar point - a coincident + input site or a deleted vertex. Deleted vertices + indicate highly degenerate input.
    • +
    • vertex - a point on the paraboloid. It + corresponds to a unique input site.
    • +
    • point-at-infinity - a point added above the + paraboloid by option 'Qz'
    • +
    • lower facet - a facet corresponding to a + Delaunay region. The last coefficient of its normal is + clearly negative.
    • +
    • upper facet - a facet corresponding to a + furthest-site Delaunay region. The last coefficient of + its normal is clearly positive.
    • +
    • Delaunay region - a + lower facet projected to the input sites
    • +
    • upper Delaunay region - an upper facet projected + to the input sites
    • +
    • non-simplicial facet - more than d + input sites are cocircular or cospherical
    • +
    • good facet - a Delaunay region with optional + restrictions by 'QVn', etc.
    • +
    +
    +
    +

    »qdelaunay options

    + +
    +qdelaunay- compute the Delaunay triangulation
    +    http://www.qhull.org
    +
    +input (stdin):
    +    first lines: dimension and number of points (or vice-versa).
    +    other lines: point coordinates, best if one point per line
    +    comments:    start with a non-numeric character
    +
    +options:
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Qu   - compute furthest-site Delaunay triangulation
    +
    +Qhull control options:
    +    QJn  - randomly joggle input in range [-n,n]
    +    Qs   - search all points for the initial simplex
    +    Qz   - add point-at-infinity to Delaunay triangulation
    +    QGn  - print Delaunay region if visible from point n, -n if not
    +    QVn  - print Delaunay regions that include point n, -n if not
    +
    +Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - print statistics
    +    Tv   - verify result: structure, convexity, and in-circle test
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when point n added to hull
    +     TMn - turn on tracing at merge n
    +     TWn - trace merge facets when width > n
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)
    +     TCn - stop qhull after building cone for point n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Wn   - min facet width for outside point (before roundoff)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    f    - facet dump
    +    G    - Geomview output (see below)
    +    i    - vertices incident to each Delaunay region
    +    m    - Mathematica output (2-d only, lifted to a paraboloid)
    +    o    - OFF format (dim, points, and facets as a paraboloid)
    +    p    - point coordinates (lifted to a paraboloid)
    +    s    - summary (stderr)
    +
    +More formats:
    +    Fa   - area for each Delaunay region
    +    FA   - compute total area for option 's'
    +    Fc   - count plus coincident points for each Delaunay region
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FD   - use cdd format for numeric output (offset first)
    +    FF   - facet dump without ridges
    +    FI   - ID of each Delaunay region
    +    Fm   - merge count for each Delaunay region (511 max)
    +    FM   - Maple output (2-d only, lifted to a paraboloid)
    +    Fn   - count plus neighboring region for each Delaunay region
    +    FN   - count plus neighboring region for each point
    +    FO   - options and precision constants
    +    FP   - nearest point and distance for each coincident point
    +    FQ   - command used for qdelaunay
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,
    +                    for output: #vertices, #Delaunay regions,
    +                                #coincident points, #non-simplicial regions
    +                    #real (2), max outer plane, min vertex
    +    FS   - sizes:   #int (0)
    +                    #real (2), tot area, 0
    +    Fv   - count plus vertices for each Delaunay region
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)
    +
    +Geomview options (2-d and 3-d)
    +    Ga   - all points as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices as spheres
    +    Gi   - inner planes only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc     - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +    Gt   - transparent outer ridges to view 3-d Delaunay
    +
    +Print options:
    +    PAn  - keep n largest Delaunay regions by area
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)
    +    PDk:n - drop facet if normal[k] >= n
    +    Pg   - print good Delaunay regions (needs 'QGn' or 'QVn')
    +    PFn  - keep Delaunay regions whose area is at least n
    +    PG   - print neighbors of good regions (needs 'QGn' or 'QVn')
    +    PMn  - keep n Delaunay regions with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh--4d.gif b/xs/src/qhull/html/qh--4d.gif new file mode 100644 index 0000000000000000000000000000000000000000..08be18c8a5a1c89f07da6abb451283cbf176d5b4 GIT binary patch literal 4372 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 aB{fPSgP!@PkGDE|e!fd8+eHNc9R366^3h-b literal 0 HcmV?d00001 diff --git a/xs/src/qhull/html/qh--dt.gif b/xs/src/qhull/html/qh--dt.gif new file mode 100644 index 0000000000000000000000000000000000000000..b6d4e26727ce648be6ed6d4c8e5a49a367064f20 GIT binary patch literal 3772 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 b)E_=_r0*X`jy*hb;`bvK_r5-N2#Eb(@t%X< literal 0 HcmV?d00001 diff --git a/xs/src/qhull/html/qh--geom.gif b/xs/src/qhull/html/qh--geom.gif new file mode 100644 index 0000000000000000000000000000000000000000..9c70b54991a4574015671ec0e0b29102c5346737 GIT binary patch literal 318 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 tio%^!)G! zn&62?Ji-!#4mCI6p^dgm_q}kY@|4fOha03I1zQ+GNh(q4UGDkT*d%~OJss#Xiy9tq zU~79Jtm+!4`o^2s#6H%f#BW#{(;&vjrd#ojNqG9aO>S~Hw6Inb*g_F;FgBxCoen-^ z!5U*Y2`YG?U(o&*5Cka3JMLi$ULP32%v}YyV+`)#GkT=93PKZYY8sF$W zf+b)}-H+Q`;OX!|x>HgK9zgWuHFZT39=`H>yra{qRqg{GU=MrDVGeFEhkN0BbB_z0 z571Hx5vUM~P=2?jD`>*Ji+&GZJi;7=@Pyhs?dcsH!VrdtaKNojcL}4R11z~hD3}3s zp$mZQD~G}(2w(;?zysKDpoA}S5Q9XRp$&8Ixa5Hmj1lZy>s)8XezSVdu>=0-!Wf0< zM=yG9@WTqLu!RMxK6yR>p#&|VbttHiXKV%+1ox;w?jf#w-48+uqv(U^h2IBZXrmDP zCc+if9O`@QANS z34>q@W)KDBfCG!*4SRqF(x?V7fEgQr45HYJ=s*B7aEdDz4%k2mUl0RuKnTpJ0@2_N z-jI%qKo0CEjUF(4ZGj4QfCbYa2?ubE^>6@o5C+0nbnn0b3@`wLpbg}(2nI=z=|~RI zD2+!zJcJ<)m4F21dS%DU_So37znc@G%hA00S?H4#)tLl5h?iumzJq z03j)oZuyo6Sp-`d3zQ&oUnvmVAeLAVlgNM!=ztDlAPJ5T4R8sU(SQbi;0T=%3vNIM zcZm>r`I3E!40>P+{m>7R5CnUm288LBhpCuB&m + + + +Qhull code + + + + + +

    Up: Home page for Qhull +
    +Up: Qhull manual: Table of +Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull code: Table of Contents +(please wait while loading)
    +Dn: Qhull functions, macros, and data +structures +

    + +
    + +

    [4-d cube] Qhull code

    + +

    This section discusses the code for Qhull.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »Qhull code: Table of +Contents

    + + + +
    + +

    »Reentrant Qhull

    + +

    Qhull-2015 introduces reentrant Qhull (libqhull_r). Reentrant Qhull uses a qhT* argument instead of global data structures. +The qhT* pointer is the first argument to most Qhull routines. It allows multiple instances of Qhull to run at the same time. +It simplifies the C++ interface to Qhull. + +

    New code should be written with libqhull_r. Existing users of libqhull should consider converting to libqhull_r. +Although libqhull will be supported indefinitely, improvements may not be implemented. +Reentrant qhull is 1-2% slower than non-reentrant qhull. + +

    Note: Reentrant Qhull is not thread safe. Do not invoke Qhull routines with the same qhT* pointer from multiple threads. + +

    »How to convert code to reentrant Qhull

    + +

    C++ users need to convert to libqhull_r. +The new C++ interface does a better, but not perfect, job of hiding Qhull's C data structures. +The previous C++ interface was unusual due to Qhull's global data structures. + +

    All other users should consider converting to libqhull_r. The conversion is straight forward. +The original conversion of libqhull to libqhull_r required thousands of changes, mostly global +search and replace. The first run of Qhull (unix_r.c) produced the same +output, and nearly the same log files, as the original (unix.c). + +

    Suggestions to help with conversion. +

      +
    • Compare qconvex_r.c with qconvex.c. Define a qhT object and a pointer it. The qhT* pointer is the first argument to most Qhull functions. +Clear qh_qh-<NOerrext before calling qh_initflags(). Invoke QHULL_LIB_CHECK to check for a compatible Qhull library. +
    • Compare user_eg2_r.c with user_eg2.c +
    • Compare user_eg_r.c with user_eg.c. If you use qhT before invoking qh_init_A, call qh_zero() to clear the qhT object. +user_eg_r.c includes multiple Qhull runs. +
    • Review user_eg3_r.cpp. As with the other programs, invoke QHULL_LIB_CHECK. +Simple C++ programs should compile as is. +
    • Compare QhullFacet.cpp with the same file in Qhull-2012.1. UsingLibQhull was replaced with the macro QH_TRY_() and 'qh_qh-<NOerrext= true'. +
    • For detailed notes on libqhull_r, see "libqhull_r (reentrant Qhull)" and "Source code changes for libqhull_r" in Changes.txt. +
    • For detailed notes on libqhullcpp, see "C++ interface" and following sections in Changes.txt. +
    • For regexps and conversion notes, see README_r.txt (unedited). +
    + +

    »Qhull on 64-bit computers

    + +

    Qhull compiles for 64-bit hosts. Since the size of a pointer on a 64-bit host is double the size on a 32-bit host, +memory consumption increases about 50% for simplicial facets and up-to 100% for non-simplicial facets. + +

    You can check memory consumption with option Ts. It includes the size of +each data structure: +

      +
    • 32-bit -- merge 24 ridge 20 vertex 28 facet 88 normal 24 ridge vertices 16 facet vertices or neighbors 20 +
    • 64-bit -- merge 32 ridge 32 vertex 48 facet 120 normal 32 ridge vertices 40 facet vertices or neighbors 48 +
    + +

    For Qhull 2015, the maximum identifier for ridges, vertices, and facets was increased +from 24-bits to 32-bits. This allows for larger convex hulls, but may increase the size of +the corresponding data structures. The sizes for Qhull 2012.1 were +

      +
    • 32-bit -- merge 24 ridge 16 vertex 24 facet 88 +
    • 64-bit -- merge 32 ridge 32 vertex 40 facet 120 +
    + +

    »Calling Qhull from +C++ programs

    + +

    Qhull 2015 uses reentrant Qhull for its C++ interface. If you used +the C++ interface from qhull 2012.1, you may need to adjust how you initialize and use +the Qhull classes. See How to convert code to reentrant Qhull. + +

    +Qhull's C++ interface allows you to explore the results of running Qhull. +It provides access to Qhull's data structures. +Most of the classes derive from the corresponding qhull data structure. +For example, QhullFacet is an instance of Qhull's facetT. +

    + +

    You can retain most of the data in Qhull and use the C++ interface to explore its results. +Each object contains a reference the Qhull's data structure (via QhullQh), making the C++ representation less memory efficient. +

    + +

    Besides using the C++ interface, you can also use libqhull_r directly. For example, +the FOREACHfacet_(...) macro will visit each facet in turn. +

    + +

    The C++ interface to Qhull is incomplete. You may need to extend the interface. +If so, you will need to understand Qhull's data structures and read the code. + +Example (c.f., user_eg3 eg-100). It prints the facets generated by Qhull. + +

    +    RboxPoints rbox;
    +    rbox.appendRandomPoints("100");
    +    Qhull qhull;
    +    qhull.runQhull("", rbox);
    +    QhullFacetList facets(qhull);
    +    cout<< facets;
    +
    + +

    +The C++ iterface for RboxPoints redefines the fprintf() calls +in rboxlib.c. Instead of writing its output to stdout, RboxPoints appends +the output to a std::vector. +The same technique may be used for calling Qhull from C++. +

    +
    • +Run Qhull with option 'Ta' to annotate the +output with qh_fprintf() identifiers. +
    • +Redefine qh_fprintf() for these identifiers. +
    • +See RboxPoints.cpp for an example. +
    +

    +Since the C++ interface uses reentrant Qhull, multiple threads may run Qhull at the same time. Each thread is +one run of Qhull. +

    + +

    +Do not have two threads accessing the same Qhull instance. Qhull is not thread-safe. +

    + +

    »CoordinateIterator

    +

    +A CoordinateIterator or ConstCoordinateIterator [RboxPoints.cpp] is a std::vector<realT>::iterator for Rbox and Qhull coordinates. +It is the result type of RboxPoints.coordinates(). +

    + +

    Qhull does not use CoordinateIterator for its data structures. A point in Qhull is an array of reals instead of a std::vector. +See QhullPoint. +

    + +

    »Qhull

    +

    +Qhull is the top-level class for running Qhull. +It initializes Qhull, runs the computation, and records errors. +It provides access to the global data structure QhullQh, +Qhull's facets, and vertices. +

    + +

    »QhullError

    +

    +QhullError is derived from std::exception. It reports errors from Qhull and captures the output to stderr. +

    + +

    +If error handling is not set up, Qhull exits with a code from 1 to 5. The codes are defined by +qh_ERR* in libqhull_r.h. The exit is via qh_exit() in usermem_r.c. +The C++ interface does not report the +captured output in QhullError. Call Qhull::setErrorStream to send output to cerr instead. +

    + +

    »QhullFacet

    +

    +A QhullFacet is a facet of the convex hull, a region of the Delaunay triangulation, a vertex of a Voronoi diagram, +or an intersection of the halfspace intersection about a point. +A QhullFacet has a set of QhullVertex, a set of QhullRidge, and +a set of neighboring QhullFacets. +

    + +

    »QhullFacetList

    +

    +A QhullFacetList is a linked list of QhullFacet. The result of Qhull.runQhull is a QhullFacetList stored +in QhullQh. +

    + +

    »QhullFacetSet

    +

    +A QhullFacetSet is a QhullSet of QhullFacet. QhullFacetSet may be ordered or unordered. The neighboring facets of a QhullFacet is a QhullFacetSet. +The neighbors of a QhullFacet is a QhullFacetSet. +The neighbors are ordered for simplicial facets, matching the opposite vertex of the facet. +

    + +

    »QhullIterator

    +

    +QhullIterator contains macros for defining Java-style iterator templates from a STL-style iterator template. +

    + +

    »QhullLinkedList

    +

    +A QhullLinkedLIst is a template for linked lists with next and previous pointers. +QhullFacetList and QhullVertexList are QhullLinkedLists. +

    + +

    »QhullPoint

    +

    +A QhullPoint is an array of point coordinates, typically doubles. The length of the array is QhullQh.hull_dim. +The identifier of a QhullPoint is its 0-based index from QhullQh.first_point followed by QhullQh.other_points. +

    + +

    »QhullPointSet

    +

    +A QhullPointSet is a QhullSet of QhullPoint. The QhullPointSet of a QhullFacet is its coplanar points. +

    + +

    »QhullQh

    +

    +QhullQh is the root of Qhull's data structure. +It contains initialized constants, sets, buffers, and variables. +It contains an array and a set of QhullPoint, +a list of QhullFacet, and a list of QhullVertex. +The points are the input to Qhull. The facets and vertices are the result of running Qhull. +

    + +

    +Qhull's functions access QhullQh through the global variable, qh_qh. +The global data structures, qh_stat and qh_mem, record statistics and manage memory respectively. +

    + +

    »QhullRidge

    + +

    +A QhullRidge represents the edge between two QhullFacet's. +It is always simplicial with qh.hull_dim-1 QhullVertex)'s. +

    + +

    »QhullRidgeSet

    + +

    +A QhullRidgeSet is a QhullSet of QhullRidge. Each QhullFacet contains a QhullRidgeSet. +

    + +

    »QhullSet

    + +

    +A QhullSet is a set of pointers to objects. QhullSets may be ordered or unordered. They are the core data structure for Qhull. +

    + +

    »QhullVertex

    + +

    +A QhullVertex is a vertex of the convex hull. A simplicial QhullFacet has qh.hull_dim-1 vertices. A QhullVertex contains a QhullPoint. +It may list its neighboring QhullFacet's. +

    + +

    »QhullVertexList

    + +

    +A QhullVertexList is a QhullLinkedList of QhullVertex. +The global data structure, QhullQh contains a QhullVertexList of all +the vertices. +

    + +

    »QhullVertexSet

    + +

    +A QhullVertexSet is a QhullSet of QhullVertex. +The QhullVertexSet of a QhullFacet is the vertices of the facet. It is +ordered for simplicial facets and unordered for non-simplicial facets. +

    + +

    »RboxPoints

    + +

    +RboxPoints is a std::vector of point coordinates (QhullPoint). +It's iterator is CoordinateIterator. +

    +

    +RboxPoints.appendRandomPoints() appends points from a variety of distributions such as uniformly distributed within a cube and random points on a sphere. +It can also append a cube's vertices or specific points. +

    + +

    »Cpp questions for Qhull

    + +Developing C++ code requires many conventions, idioms, and technical details. +The following questions have either +mystified the author or do not have a clear answer. See also +C++ and Perl Guidelines. +and FIXUP notes in the code. +Please add notes to Qhull Wiki. + +
      +
    • FIXUP QH11028 Should return reference, but get reference to temporary +
      iterator Coordinates::operator++() { return iterator(++i); }
      +
    • size() as size_t, size_type, or int +
    • Should all containers have a reserve()? +
    • Qhull.feasiblePoint interface +
    • How to avoid copy constructor while logging, maybeThrowQhullMessage() +
    • How to configure Qhull output. Trace and results should go to stdout/stderr +
    • Qhull and RboxPoints messaging. e.g., ~Qhull, hasQhullMessage(). Rename them as QhullErrorMessage? +
    • How to add additional output to an error message, e.g., qh_setprint +
    • Is idx the best name for an index? It's rather cryptic, but BSD strings.h defines index(). +
    • Qhull::feasiblePoint Qhull::useOutputStream as field or getter? +
    • Define virtual functions for user customization of Qhull (e.g., qh_fprintf, qh_memfree,etc.) +
    • Figure out RoadError::global_log. clearQhullMessage currently clearGlobalLog +
    • Should the false QhullFacet be NULL or empty? e.g., QhullFacet::tricoplanarOwner() and QhullFacetSet::end() +
    • Should output format for floats be predefined (qh_REAL_1, 2.2g, 10.7g) or as currently set for stream +
    • Should cout << !point.defined() be blank or 'undefined' +
    • Infinite point as !defined() +
    • qlist and qlinkedlist define pointer, reference, size_type, difference_type, const_pointer, const_reference for the class but not for iterator and const_iterator + vector.h --
      reference operator[](difference_type _Off) const
      +
    • When forwarding an implementation is base() an approriate name (e.g., Coordinates::iterator::base() as std::vector::iterator). +
    • When forwarding an implementation, does not work "returning address of temporary" +
    • Also --, +=, and -= +
      iterator       &operator++() { return iterator(i++); }
      +
    • if vector inheritance is bad, is QhullVertexSet OK? +
    • Should QhullPointSet define pointer and reference data types? +
    + +

    »Calling Qhull from +C programs

    + +

    Warning: Qhull was not designed for calling from C +programs. You may find the C++ interface easier to use. +You will need to understand the data structures and read the code. +Most users will find it easier to call Qhull as an external +command. + +

    For examples of calling Qhull, see GNU Octave's +computational geometry code, +and Qhull's +user_eg_r.c, +user_eg2_r.c, and +user_r.c. To see how Qhull calls its library, read +unix_r.c, +qconvex.c, +qdelaun.c, +qhalf.c, and +qvoronoi.c. The '*_r.c' files are reentrant, otherwise they are non-reentrant. +Either version may be used. New code should use reentrant Qhull. + +

    See Reentrant Qhull functions, macros, and data +structures for internal documentation of Qhull. The +documentation provides an overview and index. To use the library +you will need to read and understand the code. For most users, it +is better to write data to a file, call the qhull program, and +read the results from the output file.

    + +

    If you use non-reentrant Qhull, be aware of the macros "qh" +and "qhstat", e.g., "qh hull_dim". They are +defined in libqhull.h. They allow the global data +structures to be pre-allocated (faster access) or dynamically +allocated (allows multiple copies).

    + +

    Qhull's Makefile produces a library, libqhull_r.a, +for inclusion in your programs. First review libqhull_r.h. +This defines the data structures used by Qhull and provides +prototypes for the top-level functions. +Most users will only need libqhull_r.h in their programs. For +example, the Qhull program is defined with libqhull_r.h and unix_r.c. +To access all functions, use qhull_ra.h. Include the file +with "#include <libqhull_r/qhull_ra.h>". This +avoids potential name conflicts.

    + +

    If you use the Qhull library, you are on your own as far as +bugs go. Start with small examples for which you know the output. +If you get a bug, try to duplicate it with the Qhull program. The +'Tc' option will catch many problems +as they occur. When an error occurs, use 'T4 TPn' +to trace from the last point added to the hull. Compare your +trace with the trace output from the Qhull program.

    + +

    Errors in the Qhull library are more likely than errors in the +Qhull program. These are usually due to feature interactions that +do not occur in the Qhull program. Please report all errors that +you find in the Qhull library. Please include suggestions for +improvement.

    + +

    »How to avoid exit(), fprintf(), stderr, and stdout

    + +

    Qhull sends output to qh.fout and errors, log messages, and summaries to qh.ferr. qh.fout is normally +stdout and qh.ferr is stderr. qh.fout may be redefined by option 'TO' or the caller. qh.ferr may be redirected to qh.fout by option 'Tz'.

    + +

    Qhull does not use stderr, stdout, fprintf(), or exit() directly.

    + +

    Qhull reports errors via qh_errexit() by writting a message to qh.ferr and invoking longjmp(). +This returns the caller to the corresponding setjmp() (c.f., QH_TRY_ in QhullQh.h). If +qh_errexit() is not available, Qhull functions call qh_exit(). qh_exit() normally calls exit(), +but may be redefined by the user. An example is +libqhullcpp/usermem_r-cpp.cpp. It redefines qh_exit() as a 'throw'.

    + +

    If qh_meminit() or qh_new_qhull() is called with ferr==NULL, then they set ferr to stderr. +Otherwise the Qhull libraries use qh->ferr and qh->qhmem.ferr for error output.

    + +

    If an error occurs before qh->ferr is initialized, Qhull invokes qh_fprintf_stderr(). The user +may redefine this function along with qh_exit(), qh_malloc(), and qh_free(). + +

    The Qhull libraries write output via qh_fprintf() [userprintf_r.c]. Otherwise, the Qhull +libraries do not use stdout, fprintf(), or printf(). Like qh_exit(), the user may redefine +qh_fprintf().

    + +

    »sets and quick memory +allocation

    + +

    You can use mem_r.c and qset_r.c individually. Mem_r.c +implements quick-fit memory allocation. It is faster than +malloc/free in applications that allocate and deallocate lots of +memory.

    + +

    Qset_r.c implements sets and related collections. It's +the inner loop of Qhull, so speed is more important than +abstraction. Set iteration is particularly fast. qset_r.c +just includes the functions needed for Qhull.

    + +

    »Delaunay triangulations +and point indices

    + +

    Here some unchecked code to print the point indices of each +Delaunay triangle. Use option 'QJ' if you want to avoid +non-simplicial facets. Note that upper Delaunay regions are +skipped. These facets correspond to the furthest-site Delaunay +triangulation.

    + +
    +
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  FORALLfacets {
    +    if (!facet->upperdelaunay) {
    +      printf ("%d", qh_setsize (facet->vertices);
    +      FOREACHvertex_(facet->vertices)
    +        printf (" %d", qh_pointid (vertex->point));
    +      printf ("\n");
    +    }
    +  }
    +
    +
    +
    + +

    »locate a facet with +qh_findbestfacet()

    + +

    The routine qh_findbestfacet in poly2_r.c is +particularly useful. It uses a directed search to locate the +facet that is furthest below a point. For Delaunay +triangulations, this facet is the Delaunay triangle that contains +the lifted point. For convex hulls, the distance of a point to +the convex hull is either the distance to this facet or the +distance to a subface of the facet.

    + +
    +

    Warning: If triangulated output ('Qt') and +the best facet is triangulated, qh_findbestfacet() returns one of +the corresponding 'tricoplanar' facets. The actual best facet may be a different +tricoplanar facet. +

    +See qh_nearvertex() in poly2.c for sample code to visit each +tricoplanar facet. To identify the correct tricoplanar facet, +see Devillers, et. al., ['01] +and Mucke, et al ['96]. If you +implement this test in general dimension, please notify +qhull@qhull.org. +

    + +

    qh_findbestfacet performs an exhaustive search if its directed +search returns a facet that is above the point. This occurs when +the point is inside the hull or if the curvature of the convex +hull is less than the curvature of a sphere centered at the point +(e.g., a point near a lens-shaped convex hull). When the later +occurs, the distance function is bimodal and a directed search +may return a facet on the far side of the convex hull.

    + +

    Algorithms that retain the previously constructed hulls +usually avoid an exhaustive search for the best facet. You may +use a hierarchical decomposition of the convex hull [Dobkin and +Kirkpatrick '90].

    + +

    To use qh_findbestfacet with Delaunay triangulations, lift the +point to a paraboloid by summing the squares of its coordinates +(see qh_setdelaunay in geom2_r.c). Do not scale the input with +options 'Qbk', 'QBk', 'QbB' or 'Qbb'. See Mucke, et al ['96] for a good point location +algorithm.

    + +

    The intersection of a ray with the convex hull may be found by +locating the facet closest to a distant point on the ray. +Intersecting the ray with the facet's hyperplane gives a new +point to test.

    + +

    »on-line construction with +qh_addpoint()

    + +

    The Qhull library may be used for the on-line construction of +convex hulls, Delaunay triangulations, and halfspace +intersections about a point. It may be slower than implementations that retain +intermediate convex hulls (e.g., Clarkson's hull +program). These implementations always use a directed search. +For the on-line construction of convex hulls and halfspace +intersections, Qhull may use an exhaustive search +(qh_findbestfacet).

    + +

    You may use qh_findbestfacet and qh_addpoint (libqhull.c) to add a point to +a convex hull. Do not modify the point's coordinates since +qh_addpoint does not make a copy of the coordinates. For Delaunay +triangulations, you need to lift the point to a paraboloid by +summing the squares of the coordinates (see qh_setdelaunay in +geom2.c). Do not scale the input with options 'Qbk', 'QBk', 'QbB' +or 'Qbb'. Do not deallocate the point's coordinates. You need to +provide a facet that is below the point (qh_findbestfacet). +

    + +

    You can not delete points. Another limitation is that Qhull +uses the initial set of points to determine the maximum roundoff +error (via the upper and lower bounds for each coordinate).

    + +

    For many applications, it is better to rebuild the hull from +scratch for each new point. This is especially true if the point +set is small or if many points are added at a time.

    + +

    Calling qh_addpoint from your program may be slower than +recomputing the convex hull with qh_qhull. This is especially +true if the added points are not appended to the qh first_point +array. In this case, Qhull must search a set to determine a +point's ID. [R. Weber]

    + +

    See user_eg.c for examples of the on-line construction of +convex hulls, Delaunay triangulations, and halfspace +intersections. The outline is:

    + +
    +
    +initialize qhull with an initial set of points
    +qh_qhull();
    +
    +for each additional point p
    +   append p to the end of the point array or allocate p separately
    +   lift p to the paraboloid by calling qh_setdelaunay
    +   facet= qh_findbestfacet (p, !qh_ALL, &bestdist, &isoutside);
    +   if (isoutside)
    +      if (!qh_addpoint (point, facet, False))
    +         break;  /* user requested an early exit with 'TVn' or 'TCn' */
    +
    +call qh_check_maxout() to compute outer planes
    +terminate qhull
    +
    + +

    »Constrained +Delaunay triangulation

    + +

    With a fair amount of work, Qhull is suitable for constrained +Delaunay triangulation. See Shewchuk, ACM Symposium on +Computational Geometry, Minneapolis 1998.

    + +

    Here's a quick way to add a constraint to a Delaunay +triangulation: subdivide the constraint into pieces shorter than +the minimum feature separation. You will need an independent +check of the constraint in the output since the minimum feature +separation may be incorrect. [H. Geron]

    + +

    »Tricoplanar facets and option 'Qt'

    + +

    Option 'Qt' triangulates non-simplicial +facets (e.g., a square facet in 3-d or a cubical facet in 4-d). +All facets share the same apex (i.e., the first vertex in facet->vertices). +For each triangulated facet, Qhull +sets facet->tricoplanar true and copies facet->center, facet->normal, facet->offset, and facet->maxoutside. One of +the facets owns facet->normal; its facet->keepcentrum is true. +If facet->isarea is false, facet->triowner points to the owning +facet. + +

    Qhull sets facet->degenerate if the facet's vertices belong +to the same ridge of the non-simplicial facet. + +

    To visit each tricoplanar facet of a non-simplicial facet, +either visit all neighbors of the apex or recursively visit +all neighbors of a tricoplanar facet. The tricoplanar facets +will have the same facet->center.

    + +

    See qh_detvridge for an example of ignoring tricoplanar facets.

    + +

    »Voronoi vertices of a +region

    + +

    The following code iterates over all Voronoi vertices for each +Voronoi region. Qhull computes Voronoi vertices from the convex +hull that corresponds to a Delaunay triangulation. An input site +corresponds to a vertex of the convex hull and a Voronoi vertex +corresponds to an adjacent facet. A facet is +"upperdelaunay" if it corresponds to a Voronoi vertex +"at-infinity". Qhull uses qh_printvoronoi in io.c +for 'qvoronoi o'

    + +
    +
    +/* please review this code for correctness */
    +qh_setvoronoi_all();
    +FORALLvertices {
    +   site_id = qh_pointid (vertex->point);
    +   if (qh hull_dim == 3)
    +      qh_order_vertexneighbors(vertex);
    +   infinity_seen = 0;
    +   FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay) {
    +        if (!infinity_seen) {
    +          infinity_seen = 1;
    +          ... process a Voronoi vertex "at infinity" ...
    +        }
    +      }else {
    +        voronoi_vertex = neighbor->center;
    +        ... your code goes here ...
    +      }
    +   }
    +}
    +
    +
    + +

    »Voronoi vertices of a +ridge

    + +

    Qhull uses qh_printvdiagram() in io.c to print the ridges of a +Voronoi diagram for option 'Fv'. +The helper function qh_eachvoronoi() does the real work. It calls +the callback 'printvridge' for each ridge of the Voronoi diagram. +

    + +

    You may call qh_printvdiagram2(), qh_eachvoronoi(), or +qh_eachvoronoi_all() with your own function. If you do not need +the total number of ridges, you can skip the first call to +qh_printvdiagram2(). See qh_printvridge() and qh_printvnorm() in +io.c for examples.

    + +

    »vertex neighbors of +a vertex

    + +

    To visit all of the vertices that share an edge with a vertex: +

    + +
      +
    • Generate neighbors for each vertex with + qh_vertexneighbors in poly2.c.
    • +
    • For simplicial facets, visit the vertices of each + neighbor
    • +
    • For non-simplicial facets,
        +
      • Generate ridges for neighbors with qh_makeridges + in merge.c.
      • +
      • Generate ridges for a vertex with qh_vertexridges + in merge.c.
      • +
      • Visit the vertices of these ridges.
      • +
      +
    • +
    + +

    For non-simplicial facets, the ridges form a simplicial +decomposition of the (d-2)-faces between each pair of facets -- +if you need 1-faces, you probably need to generate the full face +graph of the convex hull.

    + +

    »Performance of +Qhull

    + +

    Empirically, Qhull's performance is balanced in the sense that +the average case happens on average. This may always be true if +the precision of the input is limited to at most O(log n) +bits. Empirically, the maximum number of vertices occurs at the +end of constructing the hull.

    + +

    Let n be the number of input points, v be the +number of output vertices, and f_v be the maximum number +of facets for a convex hull of v vertices. If both +conditions hold, Qhull runs in O(n log v) in 2-d and 3-d +and O(n f_v/v) otherwise. The function f_v +increases rapidly with dimension. It is O(v^floor(d/2) / +floor(d/2)!).

    + +

    The time complexity for merging is unknown. Options 'C-0' and 'Qx' +(defaults) handle precision problems due to floating-point +arithmetic. They are optimized for simplicial outputs.

    + +

    When running large data sets, you should monitor Qhull's +performance with the 'TFn' option. +The time per facet is approximately constant. In high-d with many +merged facets, the size of the ridge sets grows rapidly. For +example the product of 8-d simplices contains 18 facets and +500,000 ridges. This will increase the time needed per facet.

    + +

    As dimension increases, the number of facets and ridges in a +convex hull grows rapidly for the same number of vertices. For +example, the convex hull of 300 cospherical points in 6-d has +30,000 facets.

    + +

    If Qhull appears to stop processing facets, check the memory +usage of Qhull. If more than 5-10% of Qhull is in virtual memory, +its performance will degrade rapidly.

    + +

    When building hulls in 20-d and higher, you can follow the +progress of Qhull with option 'T1'. +It reports each major event in processing a point.

    + +

    To reduce memory requirements, recompile Qhull for +single-precision reals (REALfloat in user.h). +Single-precision does not work with joggle ('QJ'). Check qh_MEMalign in user.h +and the match between free list sizes and data structure sizes +(see the end of the statistics report from 'Ts'). If free list sizes do not match, +you may be able to use a smaller qh_MEMalign. Setting +qh_COMPUTEfurthest saves a small amount of memory, as does +clearing qh_MAXoutside (both in user.h).

    + +

    Shewchuk is working on a 3-d version of his triangle +program. It is optimized for 3-d simplicial Delaunay triangulation +and uses less memory than Qhull.

    + +

    To reduce the size of the Qhull executable, consider +qh_NOtrace and qh_KEEPstatistics 0 in user.h. By +changing user.c you can also remove the input/output +code in io.c. If you don't need facet merging, then +version 1.01 of Qhull is much smaller. It contains some bugs that +prevent Qhull from initializing in simple test cases. It is +slower in high dimensions.

    + +

    The precision options, 'Vn', 'Wn', 'Un'. +'A-n', 'C-n', +'An', 'Cn', +and 'Qx', may have large effects on +Qhull performance. You will need to experiment to find the best +combination for your application.

    + +

    The verify option ('Tv') checks +every point after the hull is complete. If facet merging is used, +it checks that every point is inside every facet. This can take a +very long time if there are many points and many facets. You can +interrupt the verify without losing your output. If facet merging +is not used and there are many points and facets, Qhull uses a +directed search instead of an exhaustive search. This should be +fast enough for most point sets. Directed search is not used for +facet merging because directed search was already used for +updating the facets' outer planes.

    + +

    The check-frequently option ('Tc') +becomes expensive as the dimension increases. The verify option +('Tv') performs many of the same +checks before outputting the results.

    + +

    Options 'Q0' (no pre-merging), 'Q3' (no checks for redundant vertices), +'Q5' (no updates for outer planes), +and 'Q8' (no near-interior points) +increase Qhull's speed. The corresponding operations may not be +needed in your application.

    + +

    In 2-d and 3-d, a partial hull may be faster to produce. +Option 'QgGn' only builds facets +visible to point n. Option 'QgVn' +only builds facets that contain point n. In higher-dimensions, +this does not reduce the number of facets.

    + +

    User.h includes a number of performance-related +constants. Changes may improve Qhull performance on your data +sets. To understand their effect on performance, you will need to +read the corresponding code.

    + +

    GNU gprof reports that the dominate cost for 3-d +convex hull of cosperical points is qh_distplane(), mainly called +from qh_findbestnew(). The dominate cost for 3-d Delaunay triangulation +is creating new facets in qh_addpoint(), while qh_distplane() remains +the most expensive function. + +

    +

    »Enhancements to Qhull

    + +

    There are many ways in which Qhull can be improved.

    + +
    +[Jan 2016] Suggestions
    +------------
    +To do for a future verson of Qhull
    + - Add a post-merge pass for Delaunay slivers.  Merge into a neighbor with a circumsphere that includes the opposite point. [M. Treacy]
    + - Add a merge pass before cone creation to remove duplicate subridges between horizon facets
    + - Option to add a bounding box for Delaunay triangulations, e,g., nearly coincident points
    + - Report error when rbox Cn,r,m does not produce points (e.g., 'r' distributions)
    + - Rescale output to match 'QbB' on input [J. Metz, 1/30/2014 12:21p]
    + - Run through valgrind
    + - Notes to compgeom on conformant triangulation and Voronoi volume
    + - Git: Create signed tags for Qhull versions
    + - Implement weighted Delaunay triangulation and weighted Voronoi diagram [A. Liebscher]
    +   e.g., Sugihara, "Three-dimensional convex hull as a fruitful source of diagrams," Theoretical Computer Science, 2000, 235:325-337
    + - testqset: test qh_setdelnth and move-to-front
    + - Makefile: Re-review gcc/g++ warnings.  OK in 2011.
    + - Break up -Wextra into its components or figure out how to override -Wunused-but-set-variable
    +   unused-but-set-variable is reporting incorrectly.  All instances are annotated.
    + - CMakelists.txt:  Why are files duplicated for cmake build
    + - CMakeLists.txt: configure the 'ctest' target
    + - The size of maxpoints in qh_maxsimplex should be d+3 unique points to help avoid QH6154
    +
    + - Can countT be defined as 'int', 'unsigned int', or 64-bit int?
    +   countT is currently defined as 'int' in qset_r.h
    +   Vertex ID and ridge ID perhaps should be countT, They are currently 'unsigned'
    +   Check use of 'int' vs. countT in all cpp code
    +   Check use of 'int' vs. countT in all c code
    +   qset_r.h defines countT -- duplicates code in user_r.h -- need to add to qset.h/user.h
    +   countT -1 used as a flag in Coordinates.mid(), QhullFacet->id()
    +   Also QhullPoints indexOf and lastIndexOf
    +   Also QhullPointSet indexOf and lastIndexOf
    +   Coordinates.indexOf assumes countT is signed (from end)
    +   Coordinates.lastIndexOf assumes countT is signed (from end)
    +   All error messages with countT are wrong, convert to int?
    +   RboxPoints.qh_fprintf_rbox, etc. message 9393 assumes countT but may be int, va_arg(args, countT);  Need to split
    +
    +------------
    +To do for a furture version of the C++ interface
    + - Fix C++ memory leak in user_eg3 [M. Sandim]
    + - Document C++ using Doxygen conventions (//! and //!<)
    + - Should Qhull manage the output formats for doubles?  QH11010 user_r.h defines qh_REAL_1 as %6.8g
    + - Allocate memory for QhullSet using Qhull.qhmem.  Create default constructors for QhullVertexSet etc.  Also mid() etc.
    + - Add interior point for automatic translation?
    + - Add hasNext() to all next() iterators (e.g., QhullVertex)
    + - Add defineAs() to each object
    + - Write a program with concurrent Qhull
    + - Write QhullStat and QhullStat_test
    + - Add QList and vector instance of facetT*, etc.
    + - Generalize QhullPointSetIterator
    + - qh-code.htm: Document changes to C++ interface.
    +      Organize C++ documentation into collection classes, etc.
    + - Review all C++ classes and C++ tests
    + - QhullVertexSet uses QhullSetBase::referenceSetT() to free it's memory.   Probably needed elsewhere
    + - The Boost Graph Library provides C++ classes for graph data structures. It may help
    +   enhance Qhull's C++ interface [Dr. Dobb's 9/00 p. 29-38; OOPSLA '99 p. 399-414].
    +
    +[Jan 2010] Suggestions
    + - Generate vcproj from qtpro files
    +   cd qtpro && qmake -spec win32-msvc2005 -tp vc -recursive
    +   sed -i 's/C\:\/bash\/local\/qhull\/qtpro\///' qhull-all.sln
    +   Change qhullcpp to libqhull.dll
    +   Allow both builds on same host (keep /tmp separate)
    + - Make distribution -- remove tmp, news, .git, leftovers from project, change CRLF
    +     search for 2010.1, Dates
    +     qhulltest --all added to output
    +     Add md5sum
    +     Add test of user_eg3, etc.
    + - C++ class for access to statistics, accumulate vs. add
    + - Add dialog box to RoadError-- a virtual function?
    + - Option 'Gt' does not make visible all facets of the mesh example, rbox 32 M1,0,1 | qhull d Gt
    + - Option to select bounded Voronoi regions [A. Uzunovic]
    + - Merge small volume boundary cells into unbounded regions [Dominik Szczerba]
    + - Postmerge with merge options
    + - Add const to C code
    + - Add modify operators and MutablePointCoordinateIterator to PointCoordinates
    + - Add Qtest::toString() functions for QhullPoint and others.  QByteArray and qstrdup()
    + - Fix option Qt for conformant triangulations of merged facets
    + - Investigate flipped facet -- rbox 100 s D3 t1263080158 | qhull R1e-3 Tcv Qc
    + - Add doc comments to c++ code
    + - Measure performance of Qhull, seconds per point by dimension
    + - Report potential wraparound of 64-bit ints -- e.g., a large set or points
    +
    +Documentation
    +- Qhull::addPoint().  Problems with qh_findbestfacet and otherpoints see
    +   qh-code.htm#inc on-line construction with qh_addpoint()
    +- How to handle 64-bit possible loss of data.  WARN64, ptr_intT, size_t/int
    +- Show custom of qh_fprintf
    +- grep 'qh_mem ' x | sort | awk '{ print $2; }' | uniq -c | grep -vE ' (2|4|6|8|10|12|14|16|20|64|162)[^0-9]'
    +- qtpro/qhulltest contains .pro and Makefile.  Remove Makefiles by setting shadow directory to ../../tmp/projectname
    +- Rules for use of qh_qh and multi processes
    +    UsingQhull
    +    errorIfAnotherUser
    +    ~QhullPoints() needs ownership of qh_qh
    +    Does !qh_pointer work?
    +    When is qh_qh required?  Minimize the time.
    +   qhmem, qhstat.ferr
    +   qhull_inuse==1 when qhull globals active [not useful?]
    +   rbox_inuse==1 when rbox globals active
    +   - Multithreaded -- call largest dimension for infinityPoint() and origin()
    + - Better documentation for qhmem totshort, freesize, etc.
    + - how to change .h, .c, and .cpp to text/html.  OK in Opera
    + - QhullVertex.dimension() is not quite correct, epensive
    + - Check globalAngleEpsilon
    + - Deprecate save_qhull()
    +
    +[Dec 2003] Here is a partial list:
    + - fix finddelaunay() in user_eg.c for tricoplanar facets
    + - write a BGL, C++ interface to Qhull
    +     http://www.boost.org/libs/graph/doc/table_of_contents.html
    + - change qh_save_qhull to swap the qhT structure instead of using pointers
    + - change error handling and tracing to be independent of 'qh ferr'
    + - determine the maximum width for a given set of parameters
    + - prove that directed search locates all coplanar facets
    + - in high-d merging, can a loop of facets become disconnected?
    + - find a way to improve inner hulls in 5-d and higher
    + - determine the best policy for facet visibility ('Vn')
    + - determine the limitations of 'Qg'
    +
    +Precision improvements:
    + - For 'Qt', resolve cross-linked, butterfly ridges.
    +     May allow retriangulation in qh_addpoint().
    + - for Delaunay triangulations ('d' or 'v') under joggled input ('QJ'),
    +     remove vertical facets whose lowest vertex may be coplanar with convex hull
    + - review use of 'Qbb' with 'd QJ'.  Is MAXabs_coord better than MAXwidth?
    + - check Sugihara and Iri's better in-sphere test [Canadian
    +     Conf. on Comp. Geo., 1989; Univ. of Tokyo RMI 89-05]
    + - replace centrum with center of mass and facet area
    + - handle numeric overflow in qh_normalize and elsewhere
    + - merge flipped facets into non-flipped neighbors.
    +     currently they merge into best neighbor (appears ok)
    + - determine min norm for Cramer's rule (qh_sethyperplane_det).  It looks high.
    + - improve facet width for very narrow distributions
    +
    +New features:
    + - implement Matlab's tsearch() using Qhull
    + - compute volume of Voronoi regions.  You need to determine the dual face
    +   graph in all dimensions [see Clarkson's hull program]
    + - compute alpha shapes [see Clarkson's hull program]
    + - implement deletion of Delaunay vertices
    +      see Devillers, ACM Symposium on Computational Geometry, Minneapolis 1999.
    + - compute largest empty circle [see O'Rourke, chapter 5.5.3] [Hase]
    + - list redundant (i.e., coincident) vertices [Spitz]
    + - implement Mucke, et al, ['96] for point location in Delaunay triangulations
    + - implement convex hull of moving points
    + - implement constrained Delaunay diagrams
    +      see Shewchuk, ACM Symposium on Computational Geometry, Minneapolis 1998.
    + - estimate outer volume of hull
    + - automatically determine lower dimensional hulls
    + - allow "color" data for input points
    +      need to insert a coordinate for Delaunay triangulations
    +
    +Input/output improvements:
    + - Support the VTK Visualization Toolkit, http://www.kitware.com/vtk.html
    + - generate output data array for Qhull library [Gautier]
    + - need improved DOS window with screen fonts, scrollbar, cut/paste
    + - generate Geomview output for Voronoi ridges and unbounded rays
    + - generate Geomview output for halfspace intersection
    + - generate Geomview display of furthest-site Voronoi diagram
    + - use 'GDn' to view 5-d facets in 4-d
    + - convert Geomview output for other 3-d viewers
    + - add interactive output option to avoid recomputing a hull
    + - orient vertex neighbors for 'Fv' in 3-d and 2-d
    + - track total number of ridges for summary and logging
    +
    +Performance improvements:
    + - optimize Qhull for 2-d Delaunay triangulations
    + -   use O'Rourke's '94 vertex->duplicate_edge
    + -   add bucketing
    + -   better to specialize all of the code (ca. 2-3x faster w/o merging)
    + - use updated LU decomposition to speed up hyperplane construction
    + -        [Gill et al. 1974, Math. Comp. 28:505-35]
    + - construct hyperplanes from the corresponding horizon/visible facets
    + - for merging in high d, do not use vertex->neighbors
    +
    +
    + +

    Please let us know about your applications and improvements.

    + +
    + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of +Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull code: Table of Contents
    +Dn: Qhull functions, macros, and data +structures + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see changes.txt

    + + diff --git a/xs/src/qhull/html/qh-eg.htm b/xs/src/qhull/html/qh-eg.htm new file mode 100644 index 0000000000..a08f0d13f4 --- /dev/null +++ b/xs/src/qhull/html/qh-eg.htm @@ -0,0 +1,693 @@ + + + + +Examples of Qhull + + + + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull examples: Table of Contents (please wait +while loading)
    + +


    + +

    [halfspace] Examples of Qhull

    + +

    This section of the Qhull manual will introduce you to Qhull +and its options. Each example is a file for viewing with Geomview. You will need to +use a Unix computer with a copy of Geomview. +

    +If you are not running Unix, you can view pictures +for some of the examples. To understand Qhull without Geomview, try the +examples in Programs and +Programs/input. You can also try small +examples that you compute by hand. Use rbox +to generate examples. +

    +To generate the Geomview examples, execute the shell script eg/q_eg. +It uses rbox. The shell script eg/q_egtest generates +test examples, and eg/q_test exercises the code. If you +find yourself viewing the inside of a 3-d example, use Geomview's +normalization option on the 'obscure' menu.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »Qhull examples: Table of +Contents

    + + + +
    + + +
    + +

    »2-d and 3-d examples

    + +

    »rbox c D3 | qconvex G +>eg.01.cube

    + +

    The first example is a cube in 3-d. The color of each facet +indicates its normal. For example, normal [0,0,1] along the Z +axis is (r=0.5, g=0.5, b=1.0). With the 'Dn' option in rbox, +you can generate hypercubes in any dimension. Above 7-d the +number of intermediate facets grows rapidly. Use 'TFn' to track qconvex's progress. Note +that each facet is a square that qconvex merged from coplanar +triangles.

    + +

    »rbox c d G3.0 | qconvex G +>eg.02.diamond.cube

    + +

    The second example is a cube plus a diamond ('d') scaled by rbox's +'G' option. In higher dimensions, diamonds are much simpler than +hypercubes.

    + +

    »rbox s 100 D3 | qconvex G +>eg.03.sphere

    + +

    The rbox s option generates random points and +projects them to the d-sphere. All points should be on the convex +hull. Notice that random points look more clustered than you +might expect. You can get a smoother distribution by merging +facets and printing the vertices, e.g., rbox 1000 s | qconvex +A-0.95 p | qconvex G >eg.99.

    + +

    »rbox s 100 D2 | qconvex G +>eg.04.circle

    + +

    In 2-d, there are many ways to generate a convex hull. One of +the earliest algorithms, and one of the fastest, is the 2-d +Quickhull algorithm [c.f., Preparata & Shamos '85]. It was the model for +Qhull.

    + +

    »rbox 10 l | qconvex G +>eg.05.spiral

    + +

    One rotation of a spiral.

    + +

    »rbox 1000 D2 | qconvex C-0.03 +Qc Gapcv >eg.06.merge.square

    + +

    This demonstrates how Qhull handles precision errors. Option 'C-0.03' requires a clearly convex angle +between adjacent facets. Otherwise, Qhull merges the facets.

    + +

    This is the convex hull of random points in a square. The +facets have thickness because they must be outside all points and +must include their vertices. The colored lines represent the +original points and the spheres represent the vertices. Floating +in the middle of each facet is the centrum. Each centrum is at +least 0.03 below the planes of its neighbors. This guarantees +that the facets are convex.

    + +

    »rbox 1000 D3 | qconvex G +>eg.07.box

    + +

    Here's the same distribution but in 3-d with Qhull handling +machine roundoff errors. Note the large number of facets.

    + +

    »rbox c G0.4 s 500 | qconvex G +>eg.08a.cube.sphere

    + +

    The sphere is just barely poking out of the cube. Try the same +distribution with randomization turned on ('Qr'). This turns Qhull into a +randomized incremental algorithm. To compare Qhull and +randomization, look at the number of hyperplanes created and the +number of points partitioned. Don't compare CPU times since Qhull's +implementation of randomization is inefficient. The number of +hyperplanes and partitionings indicate the dominant costs for +Qhull. With randomization, you'll notice that the number of +facets created is larger than before. This is especially true as +you increase the number of points. It is because the randomized +algorithm builds most of the sphere before it adds the cube's +vertices.

    + +

    »rbox d G0.6 s 500 | qconvex G +>eg.08b.diamond.sphere

    + +

    This is a combination of the diamond distribution and the +sphere.

    + +

    »rbox 100 L3 G0.5 s | qconvex +G >eg.09.lens

    + +

    Each half of the lens distribution lies on a sphere of radius +three. A directed search for the furthest facet below a point +(e.g., qh_findbest in geom.c) may fail if started from +an arbitrary facet. For example, if the first facet is on the +opposite side of the lens, a directed search will report that the +point is inside the convex hull even though it is outside. This +problem occurs whenever the curvature of the convex hull is less +than a sphere centered at the test point.

    + +

    To prevent this problem, Qhull does not use directed search +all the time. When Qhull processes a point on the edge of the +lens, it partitions the remaining points with an exhaustive +search instead of a directed search (see qh_findbestnew in geom2.c). +

    + +

    »How Qhull adds a point

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qconvex Ga QG0 >eg.10a.sphere.visible

    + +

    The next 4 examples show how Qhull adds a point. The point +[0.5,0.5,0.5] is at one corner of the bounding box. Qhull adds a +point using the beneath-beyond algorithm. First Qhull finds all +of the facets that are visible from the point. Qhull will replace +these facets with new facets.

    + +

    »rbox 100 s +P0.5,0.5,0.5|qconvex Ga QG-0 >eg.10b.sphere.beyond

    + +

    These are the facets that are not visible from the point. +Qhull will keep these facets.

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qconvex PG Ga QG0 >eg.10c.sphere.horizon

    + +

    These facets are the horizon facets; they border the visible +facets. The inside edges are the horizon ridges. Each horizon +ridge will form the base for a new facet.

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qconvex Ga QV0 PgG >eg.10d.sphere.cone

    + +

    This is the cone of points from the new point to the horizon +facets. Try combining this image with eg.10c.sphere.horizon +and eg.10a.sphere.visible. +

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qconvex Ga >eg.10e.sphere.new

    + +

    This is the convex hull after [0.5,0.5,0.5] has been added. +Note that in actual practice, the above sequence would never +happen. Unlike the randomized algorithms, Qhull always processes +a point that is furthest in an outside set. A point like +[0.5,0.5,0.5] would be one of the first points processed.

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qhull Ga QV0g Q0 >eg.14.sphere.corner

    + +

    The 'QVn', 'QGn ' and 'Pdk' +options define good facets for Qhull. In this case 'QV0' defines the 0'th point +[0.5,0.5,0.5] as the good vertex, and 'Qg' +tells Qhull to only build facets that might be part of a good +facet. This technique reduces output size in low dimensions. It +does not work with facet merging.

    + +

    »Triangulated output or joggled input

    + +

    »rbox 500 W0 | qconvex QR0 Qc Gvp >eg.15a.surface

    + +

    This is the convex hull of 500 points on the surface of +a cube. Note the large, non-simplicial facet for each face. +Qhull merges non-convex facets. + +

    If the facets were not merged, Qhull +would report precision problems. For example, turn off facet merging +with option 'Q0'. Qhull may report concave +facets, flipped facets, or other precision errors: +

    +rbox 500 W0 | qhull QR0 Q0 +
    + +

    +

    »rbox 500 W0 | qconvex QR0 Qt Qc Gvp >eg.15b.triangle

    + +

    Like the previous examples, this is the convex hull of 500 points on the +surface of a cube. Option 'Qt' triangulates the +non-simplicial facets. Triangulated output is +particularly helpful for Delaunay triangulations. + +

    +

    »rbox 500 W0 | qconvex QR0 QJ5e-2 Qc Gvp >eg.15c.joggle

    + +

    This is the convex hull of 500 joggled points on the surface of +a cube. The option 'QJ5e-2' +sets a very large joggle to make the effect visible. Notice +that all of the facets are triangles. If you rotate the cube, +you'll see red-yellow lines for coplanar points. +

    +With option 'QJ', Qhull joggles the +input to avoid precision problems. It adds a small random number +to each input coordinate. If a precision +error occurs, it increases the joggle and tries again. It repeats +this process until no precision problems occur. +

    +Joggled input is a simple solution to precision problems in +computational geometry. Qhull can also merge facets to handle +precision problems. See Merged facets or joggled input. + +

    »Delaunay and Voronoi diagrams

    + +

    »qdelaunay Qt +<eg.data.17 GnraD2 >eg.17a.delaunay.2

    + +

    +The input file, eg.data.17, consists of a square, 15 random +points within the outside half of the square, and 6 co-circular +points centered on the square. + +

    The Delaunay triangulation is the triangulation with empty +circumcircles. The input for this example is unusual because it +includes six co-circular points. Every triangular subset of these +points has the same circumcircle. Option 'Qt' +triangulates the co-circular facet.

    + +

    »qdelaunay <eg.data.17 +GnraD2 >eg.17b.delaunay.2i

    + +

    This is the same example without triangulated output ('Qt'). qdelaunay +merges the non-unique Delaunay triangles into a hexagon.

    + +

    »qdelaunay <eg.data.17 +Ga >eg.17c.delaunay.2-3

    + +

    This is how Qhull generated both diagrams. Use Geomview's +'obscure' menu to turn off normalization, and Geomview's +'cameras' menu to turn off perspective. Then load this object +with one of the previous diagrams.

    + +

    The points are lifted to a paraboloid by summing the squares +of each coordinate. These are the light blue points. Then the +convex hull is taken. That's what you see here. If you look up +the Z-axis, you'll see that points and edges coincide.

    + +

    »qvoronoi QJ +<eg.data.17 Gna >eg.17d.voronoi.2

    + +

    The Voronoi diagram is the dual of the Delaunay triangulation. +Here you see the original sites and the Voronoi vertices. +Notice the each +vertex is equidistant from three sites. The edges indicate the +Voronoi region for a site. Qhull does not draw the unbounded +edges. Instead, it draws extra edges to close the unbounded +Voronoi regions. You may find it helpful to enclose the input +points in a square. You can compute the unbounded +rays from option 'Fo'. +

    + +

    Instead +of triangulated output ('Qt'), this +example uses joggled input ('QJ'). +Normally, you should use neither 'QJ' nor 'Qt' for Voronoi diagrams. + +

    »qvoronoi <eg.data.17 +Gna >eg.17e.voronoi.2i

    + +

    This looks the same as the previous diagrams, but take a look +at the data. Run 'qvoronoi p <eg/eg.data.17'. This prints +the Voronoi vertices. + +

    With 'QJ', there are four nearly identical Voronoi vertices +within 10^-11 of the origin. Option 'QJ' joggled the input. After the joggle, +the cocircular +input sites are no longer cocircular. The corresponding Voronoi vertices are +similar but not identical. + +

    This example does not use options 'Qt' or 'QJ'. The cocircular +input sites define one Voronoi vertex near the origin.

    + +

    Option 'Qt' would triangulate the corresponding Delaunay region into +four triangles. Each triangle is assigned the same Voronoi vertex.

    + +

    » rbox c G0.1 d | +qdelaunay Gt Qz <eg.17f.delaunay.3

    + +

    This is the 3-d Delaunay triangulation of a small cube inside +a prism. Since the outside ridges are transparent, it shows the +interior of the outermost facets. If you slice open the +triangulation with Geomview's ginsu, you will see that the innermost +facet is a cube. Note the use of 'Qz' +to add a point "at infinity". This avoids a degenerate +input due to cospherical points.

    + +

    »rbox 10 D2 d | qdelaunay +Qu G >eg.18a.furthest.2-3

    + +

    The furthest-site Voronoi diagram contains Voronoi regions for +points that are furthest from an input site. It is the +dual of the furthest-site Delaunay triangulation. You can +determine the furthest-site Delaunay triangulation from the +convex hull of the lifted points (eg.17c.delaunay.2-3). +The upper convex hull (blue) generates the furthest-site Delaunay +triangulation.

    + +

    »rbox 10 D2 d | qdelaunay +Qu Pd2 G >eg.18b.furthest-up.2-3

    + +

    This is the upper convex hull of the preceding example. The +furthest-site Delaunay triangulation is the projection of the +upper convex hull back to the input points. The furthest-site +Voronoi vertices are the circumcenters of the furthest-site +Delaunay triangles.

    + +

    »rbox 10 D2 d | qvoronoi +Qu Gv >eg.18c.furthest.2

    + +

    This shows an incomplete furthest-site Voronoi diagram. It +only shows regions with more than two vertices. The regions are +artificially truncated. The actual regions are unbounded. You can +print the regions' vertices with 'qvoronoi Qu o'.

    + +

    Use Geomview's 'obscure' menu to turn off normalization, and +Geomview's 'cameras' menu to turn off perspective. Then load this +with the upper convex hull.

    + +

    »rbox 10 D3 | qvoronoi QV5 +p | qconvex G >eg.19.voronoi.region.3

    + +

    This shows the Voronoi region for input site 5 of a 3-d +Voronoi diagram.

    + +

    »Facet merging for imprecision

    + +

    »rbox r s 20 Z1 G0.2 | +qconvex G >eg.20.cone

    + +

    There are two things unusual about this cone. +One is the large flat disk at one end and the other is the +rectangles about the middle. That's how the points were +generated, and if those points were exact, this is the correct +hull. But rbox used floating point arithmetic to +generate the data. So the precise convex hull should have been +triangles instead of rectangles. By requiring convexity, Qhull +has recovered the original design.

    + +

    »rbox 200 s | qhull Q0 +R0.01 Gav Po >eg.21a.roundoff.errors

    + +

    This is the convex hull of 200 cospherical points with +precision errors ignored ('Q0'). To +demonstrate the effect of roundoff error, we've added a random +perturbation ('R0.01') to every +distance and hyperplane calculation. Qhull, like all other convex +hull algorithms with floating point arithmetic, makes +inconsistent decisions and generates wildly wrong results. In +this case, one or more facets are flipped over. These facets have +the wrong color. You can also turn on 'normals' in Geomview's +appearances menu and turn off 'facing normals'. There should be +some white lines pointing in the wrong direction. These +correspond to flipped facets.

    + +

    Different machines may not produce this picture. If your +machine generated a long error message, decrease the number of +points or the random perturbation ('R0.01'). +If it did not report flipped facets, increase the number of +points or perturbation.

    + +

    »rbox 200 s | qconvex Qc +R0.01 Gpav >eg.21b.roundoff.fixed

    + +

    Qhull handles the random perturbations and returns an +imprecise sphere. +In this case, the output is a weak approximation to the points. +This is because a random perturbation of 'R0.01 ' is equivalent to losing all but +1.8 digits of precision. The outer planes float above the points +because Qhull needs to allow for the maximum roundoff error.

    +

    +If you start with a smaller random perturbation, you +can use joggle ('QJn') to avoid +precision problems. You need to set n significantly +larger than the random perturbation. For example, try +'rbox 200 s | qconvex Qc R1e-4 QJ1e-1'. + +

    »rbox 1000 s| qconvex C0.01 +Qc Gcrp >eg.22a.merge.sphere.01

    + +

    »rbox 1000 s| qconvex +C-0.01 Qc Gcrp >eg.22b.merge.sphere.-01

    + +

    »rbox 1000 s| qconvex C0.05 +Qc Gcrpv >eg.22c.merge.sphere.05

    + +

    »rbox 1000 s| qconvex +C-0.05 Qc Gcrpv >eg.22d.merge.sphere.-05

    + +

    The next four examples compare post-merging and pre-merging ('Cn' vs. 'C-n'). +Qhull uses '-' as a flag to indicate pre-merging.

    + +

    Post-merging happens after the convex hull is built. During +post-merging, Qhull repeatedly merges an independent set of +non-convex facets. For a given set of parameters, the result is +about as good as one can hope for.

    + +

    Pre-merging does the same thing as post-merging, except that +it happens after adding each point to the convex hull. With +pre-merging, Qhull guarantees a convex hull, but the facets are +wider than those from post-merging. If a pre-merge option is not +specified, Qhull handles machine round-off errors.

    + +

    You may see coplanar points appearing slightly outside +the facets of the last example. This is becomes Geomview moves +line segments forward toward the viewer. You can avoid the +effect by setting 'lines closer' to '0' in Geomview's camera menu. + +

    »rbox 1000 | qconvex s +Gcprvah C0.1 Qc >eg.23.merge.cube

    + +

    Here's the 3-d imprecise cube with all of the Geomview +options. There's spheres for the vertices, radii for the coplanar +points, dots for the interior points, hyperplane intersections, +centrums, and inner and outer planes. The radii are shorter than +the spheres because this uses post-merging ('C0.1') +instead of pre-merging. + +

    »4-d objects

    + +

    With Qhull and Geomview you can develop an intuitive sense of +4-d surfaces. When you get into trouble, think of viewing the +surface of a 3-d sphere in a 2-d plane.

    + +

    »rbox 5000 D4 | qconvex s GD0v +Pd0:0.5 C-0.02 C0.1 >eg.24.merge.cube.4d-in-3d

    + +

    Here's one facet of the imprecise cube in 4-d. It's projected +into 3-d (the 'GDn' option drops +dimension n). Each ridge consists of two triangles between this +facet and the neighboring facet. In this case, Geomview displays +the topological ridges, i.e., as triangles between three +vertices. That is why the cube looks lopsided.

    + +

    »rbox 5000 D4 | qconvex s +C-0.02 C0.1 Gh >eg.30.4d.merge.cube

    + +

    Here +is the equivalent in 4-d of the imprecise square +and imprecise cube. It's the imprecise convex +hull of 5000 random points in a hypercube. It's a full 4-d object +so Geomview's ginsu does not work. If you view it in +Geomview, you'll be inside the hypercube. To view 4-d objects +directly, either load the 4dview module or the ndview +module. For 4dview, you must have started Geomview +in the same directory as the object. For ndview, +initialize a set of windows with the prefab menu, and load the +object through Geomview. The 4dview module includes an +option for slicing along any hyperplane. If you do this in the x, +y, or z plane, you'll see the inside of a hypercube.

    + +

    The 'Gh' option prints the +geometric intersections between adjacent facets. Note the strong +convexity constraint for post-merging ('C0.1'). +It deletes the small facets.

    + +

    »rbox 20 D3 | qdelaunay G +>eg.31.4d.delaunay

    + +

    The Delaunay triangulation of 3-d sites corresponds to a 4-d +convex hull. You can't see 4-d directly but each facet is a 3-d +object that you can project to 3-d. This is exactly the same as +projecting a 2-d facet of a soccer ball onto a plane.

    + +

    Here we see all of the facets together. You can use Geomview's +ndview to look at the object from several directions. +Try turning on edges in the appearance menu. You'll notice that +some edges seem to disappear. That's because the object is +actually two sets of overlapping facets.

    + +

    You can slice the object apart using Geomview's 4dview. +With 4dview, try slicing along the w axis to get a +single set of facets and then slice along the x axis to look +inside. Another interesting picture is to slice away the equator +in the w dimension.

    + +

    »rbox 30 s D4 | qconvex s G +Pd0d1d2D3

    + +

    This is the positive octant of the convex hull of 30 4-d +points. When looking at 4-d, it's easier to look at just a few +facets at once. If you picked a facet that was directly above +you, then that facet looks exactly the same in 3-d as it looks in +4-d. If you pick several facets, then you need to imagine that +the space of a facet is rotated relative to its neighbors. Try +Geomview's ndview on this example.

    + +

    »Halfspace intersections

    + +

    »rbox 10 r s Z1 G0.3 | +qconvex G >eg.33a.cone

    + +

    »rbox 10 r s Z1 G0.3 | +qconvex FV n | qhalf G >eg.33b.cone.dual

    + +

    »rbox 10 r s Z1 G0.3 | +qconvex FV n | qhalf Fp | qconvex G >eg.33c.cone.halfspace

    + +

    These examples illustrate halfspace intersection. The first +picture is the convex hull of two 20-gons plus an apex. The +second picture is the dual of the first. Try loading both +at once. Each vertex of the second picture corresponds to a facet +or halfspace of the first. The vertices with four edges +correspond to a facet with four neighbors. Similarly the facets +correspond to vertices. A facet's normal coefficients divided by +its negative offset is the vertice's coordinates. The coordinates +are the intersection of the original halfspaces.

    + +

    The third picture shows how Qhull can go back and forth +between equivalent representations. It starts with a cone, +generates the halfspaces that define each facet of the cone, and +then intersects these halfspaces. Except for roundoff error, the +third picture is a duplicate of the first.

    + +
    + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull examples: Table of Contents (please wait +while loading)
    + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-faq.htm b/xs/src/qhull/html/qh-faq.htm new file mode 100644 index 0000000000..feda544a75 --- /dev/null +++ b/xs/src/qhull/html/qh-faq.htm @@ -0,0 +1,1547 @@ + + + + + + +Qhull FAQ + + + + + +

    Up: Home page for Qhull +(http://www.qhull.org)
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: FAQ: Table of Contents (please +wait while loading)
    + +


    + +

    [4-d cube] Frequently Asked Questions about Qhull

    +

    If your question does not appear here, see:

    + + + +

    Qhull is a general dimension code for computing convex hulls, +Delaunay triangulations, halfspace intersections about a point, +Voronoi diagrams, furthest-site Delaunay triangulations, and +furthest-site Voronoi diagrams. These structures have +applications in science, engineering, statistics, and +mathematics. For a detailed introduction, see O'Rourke ['94], Computational Geometry in C. +

    + +

    There are separate programs for each application of +Qhull. These programs disable experimental and inappropriate +options. If you prefer, you may use Qhull directly. All programs +run the same code. + +

    Version 3.1 added triangulated output ('Qt'). +It should be used for Delaunay triangulations instead of +using joggled input ('QJ'). + +

    Brad Barber, Arlington MA, +2010/01/09

    + +

    Copyright © 1998-2015 C.B. Barber

    + +
    + +

    »FAQ: Table of Contents

    + +

    Within each category, the most recently asked questions are +first. +

      +
    • Startup questions
        +
      • How do I run Qhull from Windows? +
      • How do I enter points for Qhull? +
      • How do I learn to use Qhull?
      • +
      +
    • Convex hull questions
        +
      • How do I report just the area and volume of a + convex hull? +
      • Why are there extra points in a 4-d or higher + convex hull? +
      • How do I report duplicate + vertices?
      • +
      +
    • Delaunay triangulation questions
        +
      • How do I get rid of nearly flat Delaunay + triangles? +
      • How do I find the Delaunay triangle or Voronoi + region that is closest to a point? + +
      • How do I compute the Delaunay triangulation of a + non-convex object? + +
      • How do I mesh a volume from a set of triangulated + surface points? + +
      • Can Qhull produce a triangular mesh for an + object? + +
      • For 3-d Delaunay triangulations, how do I + report the triangles of each tetrahedron? + +
      • How do I construct a 3-d Delaunay triangulation? +
      • How do I get the triangles for a 2-d Delaunay + triangulation and the vertices of its Voronoi diagram? +
      • Can Qhull triangulate a + hundred 16-d points?
      • +
      + +
    • Voronoi diagram questions
        +
      • See also "Delaunay diagram questions". Qhull computes the Voronoi diagram from the Delaunay triagulation. +
      • How do I compute the volume of a Voronoi region? +
      • How do I get the radii of the empty + spheres for each Voronoi vertex? + +
      • What is the Voronoi diagram of a square? + +
      • How do I construct the Voronoi diagram of + cospherical points? +
      • Can Qhull compute the unbounded rays of the + Voronoi diagram? +
      +
    • Approximation questions
        +
      • How do I approximate data + with a simplex?
      • +
      +
    • Halfspace questions
        +
      • How do I compute the + intersection of halfspaces with Qhull?
      • +
      +
    • Qhull library questions
        +
      • Is Qhull available for Mathematica, Matlab, or + Maple? + +
      • Why are there too few ridges? +
      • Can Qhull use coordinates without placing them in + a data file? +
      • How large are Qhull's data structures? +
      • Can Qhull construct convex hulls and Delaunay + triangulations one point at a time? +
      • How do I visit the ridges of a Delaunay + triangulation? +
      • How do I visit the Delaunay facets? +
      • When is a point outside or inside a facet? +
      • How do I find the facet that is closest to a + point? +
      • How do I find the Delaunay triangle or Voronoi + region that is closest to a point? +
      • How do I list the vertices? +
      • How do I test code that uses the Qhull library? +
      • When I compute a plane + equation from a facet, I sometimes get an + outward-pointing normal and sometimes an + inward-pointing normal
      • +
      +
    • +
    + +
    + +

    »Startup questions

    + +

    »How do I run Qhull +from Windows?

    + +

    Qhull is a console program. You will first need a command window +(i.e., a "command prompt"). You can double click on +'eg\Qhull-go.bat'.

    + +
      +
    • Type 'qconvex', 'qdelaunay', 'qhalf', 'qvoronoi, + 'qhull', and 'rbox' for a synopsis of each program. + +
    • Type 'rbox c D2 | qconvex s i' to compute the + convex hull of a square. + +
    • Type 'rbox c D2 | qconvex s i TO results.txt' to + write the results to the file 'results.txt'. A summary is still printed on + the the console. + +
    • Type 'rbox c D2' to see the input format for + qconvex. + +
    • Type 'qconvex < data.txt s i TO results.txt' to + read input data from 'data.txt'. + +
    • If you want to enter data by hand, type 'qconvex s i TO + results.txt' to read input data from the console. Type in + the numbers and end with a ctrl-D.
    • +
    + +

    »How do I enter +points for Qhull?

    + +

    Qhull takes its data from standard input. For example, create +a file named 'data.txt' with the following contents:

    + +
    +
    +2  #sample 2-d input
    +5  #number of points
    +1 2  #coordinates of points
    +-1.1 3
    +3 2.2
    +4 5
    +-10 -10
    +
    +
    + +

    Then call qconvex with 'qconvex < data.txt'. It will print a +summary of the convex hull. Use 'qconvex < data.txt o' to print +the vertices and edges. See also input +format.

    + +

    You can generate sample data with rbox, e.g., 'rbox 10' +generates 10 random points in 3-d. Use a pipe ('|') to run rbox +and qhull together, e.g.,

    + +
    +

    rbox c | qconvex o

    +
    + +

    computes the convex hull of a cube.

    + +

    »How do I learn to +use Qhull?

    + +

    First read:

    + + + +

    Look at Qhull's on-line documentation:

    + +
      +
    • 'qconvex' gives a synopsis of qconvex and its options + +
    • 'rbox' lists all of the options for generating point + sets +
    • 'qconvex - | more' lists the options for qconvex +
    • 'qconvex .' gives a concise list of options +
    • 'qdelaunay', 'qhalf', 'qvoronoi', and 'qhull' also have a synopsis and option list
    • +
    + +

    Then try out the Qhull programs on small examples.

    + +
      +
    • 'rbox c' lists the vertices of a cube +
    • 'rbox c | qconvex' is the convex hull of a cube +
    • 'rbox c | qconvex o' lists the vertices and facets of + a cube +
    • 'rbox c | qconvex Qt o' triangulates the cube +
    • 'rbox c | qconvex QJ o' joggles the input and + triangulates the cube +
    • 'rbox c D2 | qconvex' generates the convex hull of a + square +
    • 'rbox c D4 | qconvex' generates the convex hull of a + hypercube +
    • 'rbox 6 s D2 | qconvex p Fx' lists 6 random points in + a circle and lists the vertices of their convex hull in order +
    • 'rbox c D2 c G2 | qdelaunay' computes the Delaunay + triangulation of two embedded squares. It merges the cospherical facets. +
    • 'rbox c D2 c G2 | qdelaunay Qt' computes the Delaunay + triangulation of two embedded squares. It triangulates the cospherical facets. +
    • 'rbox c D2 c G2 | qvoronoi o' computes the + corresponding Voronoi vertices and regions. +
    • 'rbox c D2 c G2 | qvoronio Fv' shows the Voronoi diagram + for the previous example. Each line is one edge of the + diagram. The first number is 4, the next two numbers list + a pair of input sites, and the last two numbers list the + corresponding pair of Voronoi vertices.
    • +
    + +

    Install Geomview +if you are running SGI Irix, Solaris, SunOS, Linux, HP, IBM +RS/6000, DEC Alpha, or Next. You can then visualize the output of +Qhull. Qhull comes with Geomview examples. +

    + +

    Then try Qhull with a small example of your application. Work +out the results by hand. Then experiment with Qhull's options to +find the ones that you need.

    + +

    You will need to decide how Qhull should handle precision +problems. It can triangulate the output ('Qt'), joggle the input ('QJ'), or merge facets (the default).

    + +
      +
    • With joggle, Qhull produces simplicial (i.e., + triangular) output by joggling the input. After joggle, + no points are cocircular or cospherical. +
    • With facet merging, Qhull produces a better + approximation and does not modify the input. +
    • With triangulated output, Qhull merges facets and triangulates + the result.
    • +
    • See Merged facets or joggled input.
    • +
    + +
    +

    »Convex hull questions

    + +

    »How do I report just the area + and volume of a convex hull?

    + +Use option 'FS'. For example, + +
    +C:\qhull>rbox 10 | qconvex FS
    +0
    +2 2.192915621644613 0.2027867899638665
    +
    +C:\qhull>rbox 10 | qconvex FA
    +
    +Convex hull of 10 points in 3-d:
    +
    +  Number of vertices: 10
    +  Number of facets: 16
    +
    +Statistics for: RBOX 10 | QCONVEX FA
    +
    +  Number of points processed: 10
    +  Number of hyperplanes created: 28
    +  Number of distance tests for qhull: 44
    +  CPU seconds to compute hull (after input):  0
    +  Total facet area:   2.1929156
    +  Total volume:       0.20278679
    +
    + +

    »Why are there extra +points in a 4-d or higher convex hull?

    + +

    You may see extra points if you use options 'i' or 'Ft' + without using triangulated output ('Qt'). +The extra points occur when a facet is non-simplicial (i.e., a +facet with more than d vertices). For example, Qhull +reports the following for one facet of the convex hull of a hypercube. +Option 'Pd0:0.5' returns the facet along the positive-x axis:

    + +
    +
    +rbox c D4 | qconvex i Pd0:0.5
    +12
    +17 13 14 15
    +17 13 12 14
    +17 11 13 15
    +17 14 11 15
    +17 10 11 14
    +17 14 12 8
    +17 12 13 8
    +17 10 14 8
    +17 11 10 8
    +17 13 9 8
    +17 9 11 8
    +17 11 9 13
    +
    +
    + +

    The 4-d hypercube has 16 vertices; so point "17" was +added by qconvex. Qhull adds the point in order to report a +simplicial decomposition of the facet. The point corresponds to +the "centrum" which Qhull computes to test for +convexity.

    + +

    Triangulate the output ('Qt') to avoid the extra points. +Since the hypercube is 4-d, each simplicial facet is a tetrahedron. +

    +
    +C:\qhull3.1>rbox c D4 | qconvex i Pd0:0.5 Qt
    +9
    +9 13 14 15
    +12 9 13 14
    +9 11 13 15
    +11 9 14 15
    +9 10 11 14
    +12 9 14 8
    +9 12 13 8
    +9 10 14 8
    +10 9 11 8
    +
    +
    + +

    Use the 'Fv' option to print the +vertices of simplicial and non-simplicial facets. For example, +here is the same hypercube facet with option 'Fv' instead of 'i': +

    + +
    +
    +C:\qhull>rbox c D4 | qconvex Pd0:0.5 Fv
    +1
    +8 9 10 12 11 13 14 15 8
    +
    +
    + +

    The coordinates of the extra point are printed with the 'Ft' option.

    + +
    +
    +rbox c D4 | qconvex Pd0:0.5 Ft
    +4
    +17 12 3
    +  -0.5   -0.5   -0.5   -0.5
    +  -0.5   -0.5   -0.5    0.5
    +  -0.5   -0.5    0.5   -0.5
    +  -0.5   -0.5    0.5    0.5
    +  -0.5    0.5   -0.5   -0.5
    +  -0.5    0.5   -0.5    0.5
    +  -0.5    0.5    0.5   -0.5
    +  -0.5    0.5    0.5    0.5
    +   0.5   -0.5   -0.5   -0.5
    +   0.5   -0.5   -0.5    0.5
    +   0.5   -0.5    0.5   -0.5
    +   0.5   -0.5    0.5    0.5
    +   0.5    0.5   -0.5   -0.5
    +   0.5    0.5   -0.5    0.5
    +   0.5    0.5    0.5   -0.5
    +   0.5    0.5    0.5    0.5
    +   0.5      0      0      0
    +4 16 13 14 15
    +4 16 13 12 14
    +4 16 11 13 15
    +4 16 14 11 15
    +4 16 10 11 14
    +4 16 14 12 8
    +4 16 12 13 8
    +4 16 10 14 8
    +4 16 11 10 8
    +4 16 13 9 8
    +4 16 9 11 8
    +4 16 11 9 13
    +
    +
    + +

    »How do I report +duplicate vertices?

    + +

    There's no direct way. You can use option +'FP' to +report the distance to the nearest vertex for coplanar input +points. Select the minimum distance for a duplicated vertex, and +locate all input sites less than this distance.

    + +

    For Delaunay triangulations, all coplanar points are nearly +incident to a vertex. If you want a report of coincident input +sites, do not use option 'QJ'. By +adding a small random quantity to each input coordinate, it +prevents coincident input sites.

    + +
    +

    »Delaunay triangulation questions

    + +

    »How do I get rid of +nearly flat Delaunay triangles?

    + +

    Nearly flat triangles occur when boundary points are nearly +collinear or coplanar. They also occur for nearly coincident +points. Both events can easily occur when using joggle. For example +(rbox 10 W0 D2 | qdelaunay QJ Fa) lists the areas of the Delaunay +triangles of 10 points on the boundary of a square. Some of +these triangles are nearly flat. This occurs when one point +is joggled inside of two other points. In this case, nearly flat +triangles do not occur with triangulated output (rbox 10 W0 D2 | qdelaunay Qt Fa). + + +

    Another example, (rbox c P0 P0 D2 | qdelaunay QJ Fa), computes the +areas of the Delaunay triangles for the unit square and two +instances of the origin. Four of the triangles have an area +of 0.25 while two have an area of 2.0e-11. The later are due to +the duplicated origin. With triangulated output (rbox c P0 P0 D2 | qdelaunay Qt Fa) +there are four triangles of equal area. + +

    Nearly flat triangles also occur without using joggle. For +example, (rbox c P0 P0,0.4999999999 | qdelaunay Fa), computes +the areas of the Delaunay triangles for the unit square, +a nearly collinear point, and the origin. One triangle has an +area of 3.3e-11. + +

    Unfortunately, none of Qhull's merging options remove nearly +flat Delaunay triangles due to nearly collinear or coplanar boundary +points. +The merging options concern the empty circumsphere +property of Delaunay triangles. This is independent of the area of +the Delaunay triangles. Qhull does handle nearly coincident points. + +

    If you are calling Qhull from a program, you can merge slivers into an adjacent facet. +In d dimensions with simplicial facets (e.g., from 'Qt'), each facet has +d+1 neighbors. Each neighbor shares d vertices of the facet's d+1 vertices. Let the +other vertex be the opposite vertex. For each neighboring facet, if its circumsphere +includes the opposite.vertex, the two facets can be merged. [M. Treacy] + +

    You can handle collinear or coplanar boundary points by +enclosing the points in a box. For example, +(rbox c P0 P0,0.4999999999 c G1 | qdelaunay Fa), surrounds the +previous points with [(1,1), (1,-1), (-1,-1), (-1, 1)]. +Its Delaunay triangulation does not include a +nearly flat triangle. The box also simplifies the graphical +output from Qhull. + +

    Without joggle, Qhull lists coincident points as "coplanar" +points. For example, (rbox c P0 P0 D2 | qdelaunay Fa), ignores +the duplicated origin and lists four triangles of size 0.25. +Use 'Fc' to list the coincident points (e.g., +rbox c P0 P0 D2 | qdelaunay Fc). + +

    There is no easy way to determine coincident points with joggle. +Joggle removes all coincident, cocircular, and cospherical points +before running Qhull. Instead use facet merging (the default) +or triangulated output ('Qt'). + +

    »How do I compute +the Delaunay triangulation of a non-convex object?

    + +

    A similar question is +"How do I mesh a volume from a set of triangulated surface points?" + +

    This is an instance of the constrained Delaunay Triangulation +problem. Qhull does not handle constraints. The boundary of the +Delaunay triangulation is always convex. But if the input set +contains enough points, the triangulation will include the +boundary. The number of points needed depends on the input. + +

    Shewchuk has developed a theory of constrained Delaunay triangulations. +See his +paper at the +1998 Computational Geometry Conference. Using these ideas, constraints +could be added to Qhull. They would have many applications. + +

    There is a large literature on mesh generation and many commercial +offerings. For pointers see +Owen's International Meshing Roundtable +and Schneiders' +Finite Element Mesh Generation page.

    + +

    »Can Qhull +produce a triangular mesh for an object?

    + +

    Yes for convex objects, no for non-convex objects. For +non-convex objects, it triangulates the concavities. Unless the +object has many points on its surface, triangles may cross the +surface.

    + +

    »For 3-d Delaunay +triangulations, how do I report the triangles of each +tetrahedron?

    + +

    For points in general position, a 3-d Delaunay triangulation +generates tetrahedron. Each face of a tetrahedron is a triangle. +For example, the 3-d Delaunay triangulation of random points on +the surface of a cube, is a cellular structure of tetrahedron.

    + +

    Use triangulated output ('qdelaunay Qt i') or joggled input ('qdelaunay QJ i') +to generate the Delaunay triangulation. +Option 'i' reports each tetrahedron. The triangles are +every combination of 3 vertices. Each triangle is a +"ridge" of the Delaunay triangulation.

    + +

    For example,

    + +
    +        rbox 10 | qdelaunay Qt i
    +        14
    +        9 5 8 7
    +        0 9 8 7
    +        5 3 8 7
    +        3 0 8 7
    +        5 4 8 1
    +        4 6 8 1
    +        2 9 5 8
    +        4 2 5 8
    +        4 2 9 5
    +        6 2 4 8
    +        9 2 0 8
    +        2 6 0 8
    +        2 4 9 1
    +        2 6 4 1
    +
    + +

    is the Delaunay triangulation of 10 random points. Ridge 9-5-8 +occurs twice. Once for tetrahedron 9 5 8 7 and the other for +tetrahedron 2 9 5 8.

    + +

    You can also use the Qhull library to generate the triangles. +See "How do I visit the ridges of a +Delaunay triangulation?"

    + +

    »How do I construct a +3-d Delaunay triangulation?

    + +

    For 3-d Delaunay triangulations with cospherical input sites, +use triangulated output ('Qt') or +joggled input ('QJ'). Otherwise +option 'i' will +triangulate non-simplicial facets by adding a point to the facet. + +

    If you want non-simplicial output for cospherical sites, use +option +'Fv' or 'o'. +For option 'o', ignore the last coordinate. It is the lifted +coordinate for the corresponding convex hull in 4-d. + +

    The following example is a cube +inside a tetrahedron. The 8-vertex facet is the cube. Ignore the +last coordinates.

    + +
    +
    +C:\qhull>rbox r y c G0.1 | qdelaunay Fv
    +4
    +12 20 44
    +   0.5      0      0 0.3055555555555555
    +   0    0.5      0 0.3055555555555555
    +   0      0    0.5 0.3055555555555555
    +  -0.5   -0.5   -0.5 0.9999999999999999
    +  -0.1   -0.1   -0.1 -6.938893903907228e-018
    +  -0.1   -0.1    0.1 -6.938893903907228e-018
    +  -0.1    0.1   -0.1 -6.938893903907228e-018
    +  -0.1    0.1    0.1 -6.938893903907228e-018
    +   0.1   -0.1   -0.1 -6.938893903907228e-018
    +   0.1   -0.1    0.1 -6.938893903907228e-018
    +   0.1    0.1   -0.1 -6.938893903907228e-018
    +   0.1    0.1    0.1 -6.938893903907228e-018
    +4 2 11 1 0
    +4 10 1 0 3
    +4 11 10 1 0
    +4 2 9 0 3
    +4 9 11 2 0
    +4 7 2 1 3
    +4 11 7 2 1
    +4 8 10 0 3
    +4 9 8 0 3
    +5 8 9 10 11 0
    +4 10 6 1 3
    +4 6 7 1 3
    +5 6 8 10 4 3
    +5 6 7 10 11 1
    +4 5 9 2 3
    +4 7 5 2 3
    +5 5 8 9 4 3
    +5 5 6 7 4 3
    +8 5 6 8 7 9 10 11 4
    +5 5 7 9 11 2
    +
    +
    + +

    If you want simplicial output use options +'Qt i' or +'QJ i', e.g., +

    + +
    +
    +rbox r y c G0.1 | qdelaunay Qt i
    +31
    +2 11 1 0
    +11 10 1 0
    +9 11 2 0
    +11 7 2 1
    +8 10 0 3
    +9 8 0 3
    +10 6 1 3
    +6 7 1 3
    +5 9 2 3
    +7 5 2 3
    +9 8 10 11
    +8 10 11 0
    +9 8 11 0
    +6 8 10 4
    +8 6 10 3
    +6 8 4 3
    +6 7 10 11
    +10 6 11 1
    +6 7 11 1
    +8 5 4 3
    +5 8 9 3
    +5 6 4 3
    +6 5 7 3
    +5 9 10 11
    +8 5 9 10
    +7 5 10 11
    +5 6 7 10
    +8 5 10 4
    +5 6 10 4
    +5 9 11 2
    +7 5 11 2
    +
    +
    + +

    »How do I get the +triangles for a 2-d Delaunay triangulation and the vertices of +its Voronoi diagram?

    + +

    To compute the Delaunay triangles indexed by the indices of +the input sites, use

    + +
    +

    rbox 10 D2 | qdelaunay Qt i

    +
    + +

    To compute the Voronoi vertices and the Voronoi region for +each input site, use

    + +
    +

    rbox 10 D2 | qvoronoi o

    +
    + +

    To compute each edge ("ridge") of the Voronoi +diagram for each pair of adjacent input sites, use

    + +
    +

    rbox 10 D2 | qvoronoi Fv

    +
    + +

    To compute the area and volume of the Voronoi region for input site 5 (site 0 is the first one), +use

    + +
    +

    rbox 10 D2 | qvoronoi QV5 p | qconvex s FS

    +
    + +

    To compute the lines ("hyperplanes") that define the +Voronoi region for input site 5, use

    + +
    +

    rbox 10 D2 | qvoronoi QV5 p | qconvex n

    +
    +or +
    +

    rbox 10 D2 | qvoronoi QV5 Fi Fo

    +
    + +

    To list the extreme points of the input sites use

    + +
    +

    rbox 10 D2 | qdelaunay Fx

    +
    + +

    You will get the same point ids with

    + +
    +

    rbox 10 D2 | qconvex Fx

    +
    + +

    »Can Qhull triangulate +a hundred 16-d points?

    + +

    No. This is an immense structure. A triangulation of 19, 16-d +points has 43 simplices. If you add one point at a time, the +triangulation increased as follows: 43, 189, 523, 1289, 2830, +6071, 11410, 20487. The last triangulation for 26 points used 13 +megabytes of memory. When Qhull uses virtual memory, it becomes +too slow to use.

    + +
    +

    »Voronoi +diagram questions

    + +

    »How do I compute the volume of a Voronoi region?

    + +

    For each Voronoi region, compute the convex hull of the region's Voronoi vertices. The volume of each convex hull is the volume +of the corresponding Vornoi region.

    + +

    For example, to compute the volume of the bounded Voronoi region about [0,0,0]: output the origin's Voronoi vertices and +compute the volume of their convex hull. The last number from option 'FS' is the volume.

    +
    +rbox P0 10 | qvoronoi QV0 p | qhull FS
    +0
    +2 1.448134756744281 0.1067973560800857
    +
    + +

    For another example, see How do I get the triangles for a 2-d Delaunay + triangulation and the vertices of its Voronoi diagram?

    + +

    This approach is slow if you are using the command line. A faster approcach is to call Qhull from +a program. The fastest method is Clarkson's hull program. +It computes the volume for all Voronoi regions.

    + +

    An unbounded Voronoi region does not have a volume.

    + +

    »How do I get the radii of the empty + spheres for each Voronoi vertex?

    + +Use option 'Fi' to list each bisector (i.e. Delaunay ridge). Then compute the +minimum distance for each Voronoi vertex. + +

    There's other ways to get the same information. Let me know if you +find a better method. + +

    »What is the Voronoi diagram + of a square?

    + +

    +Consider a square, +

    +C:\qhull>rbox c D2
    +2 RBOX c D2
    +4
    +  -0.5   -0.5
    +  -0.5    0.5
    +   0.5   -0.5
    +   0.5    0.5
    +
    + +

    There's two ways to compute the Voronoi diagram: with facet merging +or with joggle. With facet merging, the +result is: + +

    +C:\qhull>rbox c D2 | qvoronoi Qz
    +
    +Voronoi diagram by the convex hull of 5 points in 3-d:
    +
    +  Number of Voronoi regions and at-infinity: 5
    +  Number of Voronoi vertices: 1
    +  Number of facets in hull: 5
    +
    +Statistics for: RBOX c D2 | QVORONOI Qz
    +
    +  Number of points processed: 5
    +  Number of hyperplanes created: 7
    +  Number of distance tests for qhull: 8
    +  Number of merged facets: 1
    +  Number of distance tests for merging: 29
    +  CPU seconds to compute hull (after input):  0
    +
    +C:\qhull>rbox c D2 | qvoronoi Qz o
    +2
    +2 5 1
    +-10.101 -10.101
    +     0      0
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +0
    +
    +C:\qhull>rbox c D2 | qvoronoi Qz Fv
    +4
    +4 0 1 0 1
    +4 0 2 0 1
    +4 1 3 0 1
    +4 2 3 0 1
    +
    + +

    There is one Voronoi vertex at the origin and rays from the origin +along each of the coordinate axes. +The last line '4 2 3 0 1' means that there is +a ray that bisects input points #2 and #3 from infinity (vertex 0) to +the origin (vertex 1). +Option 'Qz' adds an artificial point since the input is cocircular. +Coordinates -10.101 indicate the +vertex at infinity. + +

    With triangulated output, the Voronoi vertex is +duplicated: + +

    +C:\qhull3.1>rbox c D2 | qvoronoi Qt Qz
    +
    +Voronoi diagram by the convex hull of 5 points in 3-d:
    +
    +  Number of Voronoi regions and at-infinity: 5
    +  Number of Voronoi vertices: 2
    +  Number of triangulated facets: 1
    +
    +Statistics for: RBOX c D2 | QVORONOI Qt Qz
    +
    +  Number of points processed: 5
    +  Number of hyperplanes created: 7
    +  Number of facets in hull: 6
    +  Number of distance tests for qhull: 8
    +  Number of distance tests for merging: 33
    +  Number of distance tests for checking: 30
    +  Number of merged facets: 1
    +  CPU seconds to compute hull (after input): 0.05
    +
    +C:\qhull3.1>rbox c D2 | qvoronoi Qt Qz o
    +2
    +3 5 1
    +-10.101 -10.101
    +     0      0
    +     0      0
    +3 2 0 1
    +2 1 0
    +2 2 0
    +3 2 0 1
    +0
    +
    +C:\qhull3.1>rbox c D2 | qvoronoi Qt Qz Fv
    +4
    +4 0 2 0 2
    +4 0 1 0 1
    +4 1 3 0 1
    +4 2 3 0 2
    +
    + + +

    With joggle, the input is no longer cocircular and the Voronoi vertex is +split into two: + +

    +C:\qhull>rbox c D2 | qvoronoi Qt Qz
    +
    +C:\qhull>rbox c D2 | qvoronoi QJ o
    +2
    +3 4 1
    +-10.101 -10.101
    +-4.71511718558304e-012 -1.775812830118184e-011
    +9.020340030474472e-012 -4.02267108512433e-012
    +2 0 1
    +3 2 1 0
    +3 2 0 1
    +2 2 0
    +
    +C:\qhull>rbox c D2 | qvoronoi QJ Fv
    +5
    +4 0 2 0 1
    +4 0 1 0 1
    +4 1 2 1 2
    +4 1 3 0 2
    +4 2 3 0 2
    +
    + +

    Note that the Voronoi diagram includes the same rays as + before plus a short edge between the two vertices.

    + + +

    »How do I construct +the Voronoi diagram of cospherical points?

    + +

    Three-d terrain data can be approximated with cospherical +points. The Delaunay triangulation of cospherical points is the +same as their convex hull. If the points lie on the unit sphere, +the facet normals are the Voronoi vertices [via S. Fortune].

    + +

    For example, consider the points {[1,0,0], [-1,0,0], [0,1,0], +...}. Their convex hull is:

    + +
    +rbox d G1 | qconvex o
    +3
    +6 8 12
    +     0      0     -1
    +     0      0      1
    +     0     -1      0
    +     0      1      0
    +    -1      0      0
    +     1      0      0
    +3 3 1 4
    +3 1 3 5
    +3 0 3 4
    +3 3 0 5
    +3 2 1 5
    +3 1 2 4
    +3 2 0 4
    +3 0 2 5
    +
    + +

    The facet normals are:

    + +
    +rbox d G1 | qconvex n
    +4
    +8
    +-0.5773502691896258  0.5773502691896258  0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258  0.5773502691896258  0.5773502691896258 -0.5773502691896258
    +-0.5773502691896258  0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258  0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258 -0.5773502691896258  0.5773502691896258 -0.5773502691896258
    +-0.5773502691896258 -0.5773502691896258  0.5773502691896258 -0.5773502691896258
    +-0.5773502691896258 -0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258 -0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    +
    + +

    If you drop the offset from each line (the last number), each +line is the Voronoi vertex for the corresponding facet. The +neighboring facets for each point define the Voronoi region for +each point. For example:

    + +
    +rbox d G1 | qconvex FN
    +6
    +4 7 3 2 6
    +4 5 0 1 4
    +4 7 4 5 6
    +4 3 1 0 2
    +4 6 2 0 5
    +4 7 3 1 4
    +
    + +

    The Voronoi vertices {7, 3, 2, 6} define the Voronoi region +for point 0. Point 0 is [0,0,-1]. Its Voronoi vertices are

    + +
    +-0.5773502691896258  0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258  0.5773502691896258 -0.5773502691896258
    +-0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    +
    + +

    In this case, the Voronoi vertices are oriented, but in +general they are unordered.

    + +

    By taking the dual of the Delaunay triangulation, you can +construct the Voronoi diagram. For cospherical points, the convex +hull vertices for each facet, define the input sites for each +Voronoi vertex. In 3-d, the input sites are oriented. For +example:

    + +
    +rbox d G1 | qconvex i
    +8
    +3 1 4
    +1 3 5
    +0 3 4
    +3 0 5
    +2 1 5
    +1 2 4
    +2 0 4
    +0 2 5
    +
    + +

    The convex hull vertices for facet 0 are {3, 1, 4}. So Voronoi +vertex 0 (i.e., [-0.577, 0.577, 0.577]) is the Voronoi vertex for +input sites {3, 1, 4} (i.e., {[0,1,0], [0,0,1], [-1,0,0]}).

    + +

    »Can Qhull compute the +unbounded rays of the Voronoi diagram?

    + +

    Use 'Fo' to compute the separating +hyperplanes for unbounded Voronoi regions. The corresponding ray +goes to infinity from the Voronoi vertices. If you enclose the +input sites in a large enough box, the outermost bounded regions +will represent the unbounded regions of the original points.

    + +

    If you do not box the input sites, you can identify the +unbounded regions. They list '0' as a vertex. Vertex 0 represents +"infinity". Each unbounded ray includes vertex 0 in +option 'Fv. See Voronoi graphics and Voronoi notes.

    + +
    +

    »Approximation questions

    + +

    »How do I +approximate data with a simplex

    + +

    Qhull may be used to help select a simplex that approximates a +data set. It will take experimentation. Geomview will help to +visualize the results. This task may be difficult to do in 5-d +and higher. Use rbox options 'x' and 'y' to produce random +distributions within a simplex. Your methods work if you can +recover the simplex.

    + +

    Use Qhull's precision options to get a first approximation to +the hull, say with 10 to 50 facets. For example, try 'C0.05' to +remove small facets after constructing the hull. Use 'W0.05' to +ignore points within 0.05 of a facet. Use 'PA5' to print the five +largest facets by area.

    + +

    Then use other methods to fit a simplex to this data. Remove +outlying vertices with few nearby points. Look for large facets +in different quadrants. You can use option 'Pd0d1d2' to print all +the facets in a quadrant.

    + +

    In 4-d and higher, use the outer planes (option 'Fo' or +'facet->maxoutside') since the hyperplane of an approximate +facet may be below many of the input points.

    + +

    For example, consider fitting a cube to 1000 uniformly random +points in the unit cube. In this case, the first try was good:

    + +
    +
    +rbox 1000 | qconvex W0.05 C0.05 PA6 Fo
    +4
    +6
    +0.35715408374381 0.08706467018177928 -0.9299788727015564 -0.5985514741284483
    +0.995841591359023 -0.02512604712761577 0.08756829720435189 -0.5258834069202866
    +0.02448099521570909 -0.02685210459017302 0.9993396046151313 -0.5158104982631999
    +-0.9990223929415094 -0.01261133513150079 0.04236994958247349 -0.509218270408407
    +-0.0128069014364698 -0.9998380680115362 0.01264203427283151 -0.5002512653670584
    +0.01120895057872914 0.01803671994177704 -0.9997744926535512 -0.5056824072956361
    +
    +
    + +
    +

    »Halfspace questions

    + +

    »How do I compute the + intersection of halfspaces with Qhull?

    + +

    Qhull computes the halfspace intersection about a point. The +point must be inside all of the halfspaces. Given a point, a +duality turns a halfspace intersection problem into a convex +hull problem. + +

    Use linear programming if you +do not know a point in the interior of the halfspaces. +See the notes for qhalf. You will need + a linear programming code. This may require a fair amount of work to + implement.

    + + + +
    +

    »Qhull library +questions

    + +

    »Is Qhull available for Mathematica, Matlab, or Maple?

    + +

    MATLAB + +

    Z. You of MathWorks added qhull to MATLAB 6. +See functions convhulln, + delaunayn, + griddata3, + griddatan, + tsearch, + tsearchn, and + voronoin. V. Brumberg update MATLAB R14 for Qhull 2003.1 and triangulated output. + +

    Engwirda wrote mesh2d for unstructured mesh generation in MATLAB. +It is based on the iterative method of Persson and generally results in better quality meshes than delaunay refinement. + + +

    Mathematica and Maple + +

    See qh-math +for a Delaunay interface to Mathematica. It includes projects for CodeWarrior +on the Macintosh and Visual C++ on Win32 PCs. + +

    See Mathematica ('m') and Maple ('FM') output options. + +

    +

    »Why are there too few ridges?

    + +The following sample code may produce fewer ridges than expected: + +
    +  facetT *facetp;
    +  ridgeT *ridge, **ridgep;
    +
    +  FORALLfacets {
    +    printf("facet f%d\n", facet->id);
    +    FOREACHridge_(facet->ridges) {
    +      printf("   ridge r%d between f%d and f%d\n", ridge->id, ridge->top->id, ridge->bottom->id);
    +    }
    +  }
    +
    + +

    Qhull does not create ridges for simplicial facets. +Instead it computes ridges from facet->neighbors. To make ridges for a +simplicial facet, use qh_makeridges() in merge.c. Usefacet->visit_id to visit +each ridge once (instead of twice). For example, + +

    +  facetT *facet, *neighbor;
    +  ridgeT *ridge, **ridgep;
    +
    +  qh visit_id++;
    +  FORALLfacets {
    +    printf("facet f%d\n", facet->id);
    +    qh_makeridges(facet);
    +    facet->visitId= qh visit_id;
    +    FOREACHridge_(facet->ridges) {
    +        neighbor= otherfacet_(ridge, visible);
    +        if (neighbor->visitid != qh visit_id)
    +            printf("   ridge r%d between f%d and f%d\n", ridge->id, ridge->top->id, ridge->bottom->id);
    +    }
    +  }
    +
    + +

    »Can Qhull use coordinates without placing + them in a data file?

    + +

    You may call Qhull from a program. Please use the reentrant Qhull library (libqhullstatic_r.a, libqhull_r.so, or qhull_r.dll). + +See user_eg.c and "Qhull-template" in user_r.c for examples.. + +See Qhull internals for an introduction to Qhull's reentrant library and its C++ interface. + +

    Hint: Start with a small example for which you know the + answer.

    + +

    »How large are Qhull's data structures?

    + +

    Qhull uses a general-dimension data structure. +The size depends on the dimension. Use option 'Ts' to print +out the memory statistics [e.g., 'rbox D2 10 | qconvex Ts']. + + +

    »Can Qhull construct +convex hulls and Delaunay triangulations one point at a time?

    + +

    The Qhull library may be used to construct convex hulls and +Delaunay triangulations one point at a time. It may not be used +for deleting points or moving points.

    + +

    Qhull is designed for batch processing. Neither Clarkson's +randomized incremental algorithm nor Qhull are designed for +on-line operation. For many applications, it is better to +reconstruct the convex hull or Delaunay triangulation from +scratch for each new point.

    + +

    With random point sets and on-line processing, Clarkson's +algorithm should run faster than Qhull. Clarkson uses the +intermediate facets to reject new, interior points, while Qhull, +when used on-line, visits every facet to reject such points. If +used on-line for n points, Clarkson may take O(n) times as much +memory as the average off-line case, while Qhull's space +requirement does not change.

    + +

    If you triangulate the output before adding all the points +(option 'Qt' and procedure qh_triangulate), you must set +option 'Q11'. It duplicates the +normals of triangulated facets and recomputes the centrums. +This should be avoided for regular use since triangulated facets +are not clearly convex with their neighbors. It appears to +work most of the time, but fails for cases that Qhull normally +handles well [see the test call to qh_triangulate in qh_addpoint]. + +

    »How do I visit the +ridges of a Delaunay triangulation?

    + +

    To visit the ridges of a Delaunay triangulation, visit each +facet. Each ridge will appear twice since it belongs to two +facets. In pseudo-code:

    + +
    +    for each facet of the triangulation
    +        if the facet is Delaunay (i.e., part of the lower convex hull)
    +            for each ridge of the facet
    +                if the ridge's neighboring facet has not been visited
    +                    ... process a ridge of the Delaunay triangulation ...
    +
    + +

    In undebugged, C code:

    + +
    +    qh visit_id++;
    +    FORALLfacets_(facetlist)
    +        if (!facet->upperdelaunay) {
    +            facet->visitid= qh visit_id;
    +            qh_makeridges(facet);
    +            FOREACHridge_(facet->ridges) {
    +                neighbor= otherfacet_(ridge, facet);
    +                if (neighbor->visitid != qh visit_id) {
    +                    /* Print ridge here with facet-id and neighbor-id */
    +                    /*fprintf(fp, "f%d\tf%d\t",facet->id,neighbor->ID);*/
    +                    FOREACHvertex_(ridge->vertices)
    +                        fprintf(fp,"%d ",qh_pointid (vertex->point) );
    +                    qh_printfacetNvertex_simplicial (fp, facet, format);
    +                    fprintf(fp," ");
    +                    if(neighbor->upperdelaunay)
    +                        fprintf(fp," -1 -1 -1 -1 ");
    +                    else
    +                        qh_printfacetNvertex_simplicial (fp, neighbor, format);
    +                    fprintf(fp,"\n");
    +                }
    +            }
    +        }
    +    }
    +
    + +

    Qhull should be redesigned as a class library, or at least as +an API. It currently provides everything needed, but the +programmer has to do a lot of work. Hopefully someone will write +C++ wrapper classes or a Python module for Qhull.

    + +

    »How do I visit the +Delaunay regions?

    + +

    Qhull constructs a Delaunay triangulation by lifting the +input sites to a paraboloid. The Delaunay triangulation +corresponds to the lower convex hull of the lifted points. To +visit each facet of the lower convex hull, use:

    + +
    +    facetT *facet;
    +
    +    ...
    +    FORALLfacets {
    +        if (!facet->upperdelaunay) {
    +            ... only facets for Delaunay regions ...
    +        }
    +    }
    +
    + +

    »When is a point +outside or inside a facet?

    + +

    A point is outside of a facet if it is clearly outside the +facet's outer plane. The outer plane is defined by an offset +(facet->maxoutside) from the facet's hyperplane.

    + +
    +    facetT *facet;
    +    pointT *point;
    +    realT dist;
    +
    +    ...
    +    qh_distplane(point, facet, &dist);
    +    if (dist > facet->maxoutside + 2 * qh DISTround) {
    +        /* point is clearly outside of facet */
    +    }
    +
    + +

    A point is inside of a facet if it is clearly inside the +facet's inner plane. The inner plane is computed as the maximum +distance of a vertex to the facet. It may be computed for an +individual facet, or you may use the maximum over all facets. For +example:

    + +
    +    facetT *facet;
    +    pointT *point;
    +    realT dist;
    +
    +    ...
    +    qh_distplane(point, facet, &dist);
    +    if (dist < qh min_vertex - 2 * qh DISTround) {
    +        /* point is clearly inside of facet */
    +    }
    +
    + +

    Both tests include two qh.DISTrounds because the computation +of the furthest point from a facet may be off by qh.DISTround and +the computation of the current distance to the facet may be off +by qh.DISTround.

    + +

    »How do I find the +facet that is closest to a point?

    + +

    Use qh_findbestfacet(). For example,

    + +
    +    coordT point[ DIM ];
    +    boolT isoutside;
    +    realT bestdist;
    +    facetT *facet;
    +
    +    ... set coordinates for point ...
    +
    +    facet= qh_findbestfacet (point, qh_ALL, &bestdist, &isoutside);
    +
    +    /* 'facet' is the closest facet to 'point' */
    +
    + +

    qh_findbestfacet() performs a directed search for the facet +furthest below the point. If the point lies inside this facet, +qh_findbestfacet() performs an exhaustive search of all facets. +An exhaustive search may be needed because a facet on the far +side of a lens-shaped distribution may be closer to a point than +all of the facet's neighbors. The exhaustive search may be +skipped for spherical distributions.

    + +

    Also see, "How do I find the +Delaunay triangle that is closest to a +point?"

    + +

    »How do I find the +Delaunay triangle or Voronoi region that is closest to a point?

    + +

    A Delaunay triangulation subdivides the plane, or in general +dimension, subdivides space. Given a point, how do you determine +the subdivision containing the point? Or, given a set of points, +how do you determine the subdivision containing each point of the set? +Efficiency is important -- an exhaustive search of the subdivision +is too slow. + +

    First compute the Delaunay triangle with qh_new_qhull() in user_r.c or Qhull::runQhull(). +Lift the point to the paraboloid by summing the squares of the +coordinates. Use qh_findbestfacet() [poly2.c] to find the closest Delaunay +triangle. Determine the closest vertex to find the corresponding +Voronoi region. Do not use options +'Qbb', 'QbB', +'Qbk:n', or 'QBk:n' since these scale the last +coordinate. Optimizations of qh_findbestfacet() should +be possible for Delaunay triangulations.

    + +

    You first need to lift the point to the paraboloid (i.e., the +last coordinate is the sum of the squares of the point's coordinates). +The +routine, qh_setdelaunay() [geom2.c], lifts an array of points to the +paraboloid. The following excerpt is from findclosest() in +user_eg.c.

    + +
    +    coordT point[ DIM + 1];  /* one extra coordinate for lifting the point */
    +    boolT isoutside;
    +    realT bestdist;
    +    facetT *facet;
    +
    +    ... set coordinates for point[] ...
    +
    +    qh_setdelaunay (DIM+1, 1, point);
    +    facet= qh_findbestfacet (point, qh_ALL, &bestdist, &isoutside);
    +    /* 'facet' is the closest Delaunay triangle to 'point' */
    +
    + +

    The returned facet either contains the point or it is the +closest Delaunay triangle along the convex hull of the input set. + +

    Point location is an active research area in Computational +Geometry. For a practical approach, see Mucke, et al, "Fast randomized +point location without preprocessing in two- and +three-dimensional Delaunay triangulations," Computational +Geometry '96, p. 274-283, May 1996. +For an introduction to planar point location see [O'Rourke '93]. +Also see, "How do I find the facet that is closest to a +point?"

    + +

    To locate the closest Voronoi region, determine the closest +vertex of the closest Delaunay triangle.

    + +
    +    realT dist, bestdist= REALmax;
    +        vertexT *bestvertex= NULL, *vertex, **vertexp;
    +
    +    /* 'facet' is the closest Delaunay triangle to 'point' */
    +
    +    FOREACHvertex_( facet->vertices ) {
    +        dist= qh_pointdist( point, vertex->point, DIM );
    +        if (dist < bestdist) {
    +            bestdist= dist;
    +            bestvertex= vertex;
    +        }
    +    }
    +    /* 'bestvertex' represents the Voronoi region closest to 'point'.  The corresponding
    +       input site is 'bestvertex->point' */
    +
    + +

    »How do I list the +vertices?

    + +

    To list the vertices (i.e., extreme points) of the convex hull +use

    + +
    +
    +    vertexT *vertex;
    +
    +    FORALLvertices {
    +      ...
    +      // vertex->point is the coordinates of the vertex
    +      // qh_pointid(vertex->point) is the point ID of the vertex
    +      ...
    +    }
    +    
    +
    + +

    »How do I test code +that uses the Qhull library?

    + +

    Compare the output from your program with the output from the +Qhull program. Use option 'T1' or 'T4' to trace what Qhull is +doing. Prepare a small example for which you know the +output. Run the example through the Qhull program and your code. +Compare the trace outputs. If you do everything right, the two +trace outputs should be almost the same. The trace output will +also guide you to the functions that you need to review.

    + +

    »When I compute a +plane equation from a facet, I sometimes get an outward-pointing +normal and sometimes an inward-pointing normal

    + +

    Qhull orients simplicial facets, and prints oriented output +for 'i', 'Ft', and other options. The orientation depends on both +the vertex order and the flag facet->toporient.

    + +

    Qhull does not orient + non-simplicial facets. Instead it orients the facet's ridges. These are + printed with the 'Qt' and 'Ft' option. The facet's hyperplane is oriented.

    + +
    +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: FAQ: Table of Contents
    + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: +Sept. 25, 1995 --- Last modified: see top +

    + + diff --git a/xs/src/qhull/html/qh-get.htm b/xs/src/qhull/html/qh-get.htm new file mode 100644 index 0000000000..c39ed22564 --- /dev/null +++ b/xs/src/qhull/html/qh-get.htm @@ -0,0 +1,106 @@ + + + + +Qhull Downloads + + + + +

    Up: Qhull Home Page
    +

    + +
    + +

    [CONE] Qhull Downloads

    + + + +
    + +

    Up: Qhull Home Page
    +

    + +
    + +

    [HOME] The Geometry Center Home Page

    + +

    Comments to: qhull@qhull.org
    + + diff --git a/xs/src/qhull/html/qh-impre.htm b/xs/src/qhull/html/qh-impre.htm new file mode 100644 index 0000000000..cfbe0acb82 --- /dev/null +++ b/xs/src/qhull/html/qh-impre.htm @@ -0,0 +1,826 @@ + + + + +Imprecision in Qhull + + + + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of +Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull imprecision: Table of Contents +(please wait while loading) + +


    + +

    [4-d cube] Imprecision in Qhull

    + +

    This section of the Qhull manual discusses the problems caused +by coplanar points and why Qhull uses options 'C-0' or 'Qx' +by default. If you ignore precision issues with option 'Q0', the output from Qhull can be +arbitrarily bad. Qhull avoids precision problems if you merge facets (the default) or joggle +the input ('QJ').

    + +

    Use option 'Tv' to verify the +output from Qhull. It verifies that adjacent facets are clearly +convex. It verifies that all points are on or below all facets.

    + +

    Qhull automatically tests for convexity if it detects +precision errors while constructing the hull.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »Qhull +imprecision: Table of Contents

    + + + +
    + +

    »Precision problems

    + +

    Since Qhull uses floating point arithmetic, roundoff error +occurs with each calculation. This causes problems for +geometric algorithms. Other floating point codes for convex +hulls, Delaunay triangulations, and Voronoi diagrams also suffer +from these problems. Qhull handles most of them.

    + +

    There are several kinds of precision errors:

    + +
      +
    • Representation error occurs when there are not enough + digits to represent a number, e.g., 1/3.
    • +
    • Measurement error occurs when the input coordinates are + from measurements.
    • +
    • Roundoff error occurs when a calculation is rounded to a + fixed number of digits, e.g., a floating point + calculation.
    • +
    • Approximation error occurs when the user wants an + approximate result because the exact result contains too + much detail.
    • +
    + +

    Under imprecision, calculations may return erroneous results. +For example, roundoff error can turn a small, positive number +into a small, negative number. See Milenkovic ['93] for a discussion of strict +robust geometry. Qhull does not meet Milenkovic's criterion +for accuracy. Qhull's error bound is empirical instead of +theoretical.

    + +

    Qhull 1.0 checked for precision errors but did not handle +them. The output could contain concave facets, facets with +inverted orientation ("flipped" facets), more than two +facets adjacent to a ridge, and two facets with exactly the same +set of vertices.

    + +

    Qhull 2.4 and later automatically handles errors due to +machine round-off. Option 'C-0' or 'Qx' is set by default. In 5-d and +higher, the output is clearly convex but an input point could be +outside of the hull. This may be corrected by using option 'C-0', but then the output may contain +wide facets.

    + +

    Qhull 2.5 and later provides option 'QJ' +to joggled input. Each input coordinate is modified by a +small, random quantity. If a precision error occurs, a larger +modification is tried. When no precision errors occur, Qhull is +done.

    + +

    Qhull 3.1 and later provides option 'Qt' +for triangulated output. This removes the need for +joggled input ('QJ'). +Non-simplicial facets are triangulated. +The facets may have zero area. +Triangulated output is particularly useful for Delaunay triangulations.

    + +

    By handling round-off errors, Qhull can provide a variety of +output formats. For example, it can return the halfspace that +defines each facet ('n'). The +halfspaces include roundoff error. If the halfspaces were exact, +their intersection would return the original extreme points. With +imprecise halfspaces and exact arithmetic, nearly incident points +may be returned for an original extreme point. By handling +roundoff error, Qhull returns one intersection point for each of +the original extreme points. Qhull may split or merge an extreme +point, but this appears to be unlikely.

    + +

    The following pipe implements the identity function for +extreme points (with roundoff): +

    + qconvex FV n | qhalf Fp +
    + +

    Bernd Gartner published his +Miniball +algorithm ["Fast and robust smallest enclosing balls", Algorithms - ESA '99, LNCS 1643]. +It uses floating point arithmetic and a carefully designed primitive operation. +It is practical to 20-D or higher, and identifies at least two points on the +convex hull of the input set. Like Qhull, it is an incremental algorithm that +processes points furthest from the intermediate result and ignores +points that are close to the intermediate result. + +

    »Merged facets or joggled input

    + +

    This section discusses the choice between merged facets and joggled input. +By default, Qhull uses merged facets to handle +precision problems. With option 'QJ', +the input is joggled. See examples +of joggled input and triangulated output. +

      +
    • Use merged facets (the default) +when you want non-simplicial output (e.g., the faces of a cube). +
    • Use merged facets and triangulated output ('Qt') when +you want simplicial output and coplanar facets (e.g., triangles for a Delaunay triangulation). +
    • Use joggled input ('QJ') when you need clearly-convex, +simplicial output. +
    + +

    The choice between merged facets and joggled input depends on +the application. Both run about the same speed. Joggled input may +be faster if the initial joggle is sufficiently large to avoid +precision errors. + +

    Most applications should used merged facets +with triangulated output.

    + +

    Use merged facets (the +default, 'C-0') +or triangulated output ('Qt') if

    + +
      +
    • Your application supports non-simplicial facets, or + it allows degenerate, simplicial facets (option 'Qt').
    • +
    • You do not want the input modified.
    • +
    • You want to set additional options for approximating the + hull.
    • +
    • You use single precision arithmetic (realT). +
    • +
    + +

    Use joggled input ('QJ') if

    + +
      +
    • Your application needs clearly convex, simplicial output
    • +
    • Your application supports perturbed input points and narrow triangles.
    • +
    • Seven significant digits is sufficient accuracy.
    • +
    + +

    You may use both techniques or combine joggle with +post-merging ('Cn').

    + +

    Other researchers have used techniques similar to joggled +input. Sullivan and Beichel [ref?] randomly perturb the input +before computing the Delaunay triangulation. Corkum and Wyllie +[news://comp.graphics, 1990] randomly rotate a polytope before +testing point inclusion. Edelsbrunner and Mucke [Symp. Comp. +Geo., 1988] and Yap [J. Comp. Sys. Sci., 1990] symbolically +perturb the input to remove singularities.

    + +

    Merged facets ('C-0') handles +precision problems directly. If a precision problem occurs, Qhull +merges one of the offending facets into one of its neighbors. +Since all precision problems in Qhull are associated with one or +more facets, Qhull will either fix the problem or attempt to merge the +last remaining facets.

    + +

    »Delaunay +triangulations

    + +

    Programs that use Delaunay triangulations traditionally assume +a triangulated input. By default, qdelaunay +merges regions with cocircular or cospherical input sites. +If you want a simplicial triangulation +use triangulated output ('Qt') or joggled +input ('QJ'). + +

    For Delaunay triangulations, triangulated +output should produce good results. All points are within roundoff error of +a paraboloid. If two points are nearly incident, one will be a +coplanar point. So all points are clearly separated and convex. +If qhull reports deleted vertices, the triangulation +may contain serious precision faults. Deleted vertices are reported +in the summary ('s', 'Fs'

    + +

    You should use option 'Qbb' with Delaunay +triangulations. It scales the last coordinate and may reduce +roundoff error. It is automatically set for qdelaunay, +qvoronoi, and option 'QJ'.

    + +

    Edelsbrunner, H, Geometry and Topology for Mesh Generation, Cambridge University Press, 2001. +Good mathematical treatise on Delaunay triangulation and mesh generation for 2-d +and 3-d surfaces. The chapter on surface simplification is +particularly interesting. It is similar to facet merging in Qhull. + +

    Veron and Leon published an algorithm for shape preserving polyhedral +simplification with bounded error [Computers and Graphics, 22.5:565-585, 1998]. +It remove nodes using front propagation and multiple remeshing. + +

    »Halfspace intersection

    + +

    +The identity pipe for Qhull reveals some precision questions for +halfspace intersections. The identity pipe creates the convex hull of +a set of points and intersects the facets' hyperplanes. It should return the input +points, but narrow distributions may drop points while offset distributions may add +points. It may be better to normalize the input set about the origin. +For example, compare the first results with the later two results: [T. Abraham] +

    + rbox 100 s t | tee r | qconvex FV n | qhalf Fp | cat - r | /bin/sort -n | tail +
    + rbox 100 L1e5 t | tee r | qconvex FV n | qhalf Fp | cat - r | /bin/sort -n | tail +
    + rbox 100 s O10 t | tee r | qconvex FV n | qhalf Fp | cat - r | /bin/sort -n | tail +
    + + +

    »Merged facets

    + +

    Qhull detects precision +problems when computing distances. A precision problem occurs if +the distance computation is less than the maximum roundoff error. +Qhull treats the result of a hyperplane computation as if it +were exact.

    + +

    Qhull handles precision problems by merging non-convex facets. +The result of merging two facets is a thick facet defined by an inner +plane and an outer plane. The inner and outer planes +are offsets from the facet's hyperplane. The inner plane is +clearly below the facet's vertices. At the end of Qhull, the +outer planes are clearly above all input points. Any exact convex +hull must lie between the inner and outer planes.

    + +

    Qhull tests for convexity by computing a point for each facet. +This point is called the facet's centrum. It is the +arithmetic center of the facet's vertices projected to the +facet's hyperplane. For simplicial facets with d +vertices, the centrum is equivalent to the centroid or center of +gravity.

    + +

    Two neighboring facets are convex if each centrum is clearly +below the other hyperplane. The 'Cn' +or 'C-n' options sets the centrum's +radius to n . A centrum is clearly below a hyperplane if +the computed distance from the centrum to the hyperplane is +greater than the centrum's radius plus two maximum roundoff +errors. Two are required because the centrum can be the maximum +roundoff error above its hyperplane and the distance computation +can be high by the maximum roundoff error.

    + +

    With the 'C-n' or 'A-n ' options, Qhull merges non-convex +facets while constructing the hull. The remaining facets are +clearly convex. With the 'Qx ' +option, Qhull merges coplanar facets after constructing the hull. +While constructing the hull, it merges coplanar horizon facets, +flipped facets, concave facets and duplicated ridges. With 'Qx', coplanar points may be missed, but +it appears to be unlikely.

    + +

    If the user sets the 'An' or 'A-n' option, then all neighboring +facets are clearly convex and the maximum (exact) cosine of an +angle is n.

    + +

    If 'C-0' or 'Qx' is used without other precision +options (default), Qhull tests vertices instead of centrums for +adjacent simplices. In 3-d, if simplex abc is adjacent to +simplex bcd, Qhull tests that vertex a is clearly +below simplex bcd , and vertex d is clearly below +simplex abc. When building the hull, Qhull tests vertices +if the horizon is simplicial and no merges occur.

    + +

    »How Qhull merges facets

    + +

    If two facets are not clearly convex, then Qhull removes one +or the other facet by merging the facet into a neighbor. It +selects the merge which minimizes the distance from the +neighboring hyperplane to the facet's vertices. Qhull also +performs merges when a facet has fewer than d neighbors (called a +degenerate facet), when a facet's vertices are included in a +neighboring facet's vertices (called a redundant facet), when a +facet's orientation is flipped, or when a ridge occurs between +more than two facets.

    + +

    Qhull performs merges in a series of passes sorted by merge +angle. Each pass merges those facets which haven't already been +merged in that pass. After a pass, Qhull checks for redundant +vertices. For example, if a vertex has only two neighbors in 3-d, +the vertex is redundant and Qhull merges it into an adjacent +vertex.

    + +

    Merging two simplicial facets creates a non-simplicial facet +of d+1 vertices. Additional merges create larger facets. +When merging facet A into facet B, Qhull retains facet B's +hyperplane. It merges the vertices, neighbors, and ridges of both +facets. It recomputes the centrum if a wide merge has not +occurred (qh_WIDEcoplanar) and the number of extra vertices is +smaller than a constant (qh_MAXnewcentrum).

    + + +

    »Limitations of merged +facets

    + +
      +
    • Uneven dimensions -- +If one coordinate has a larger absolute value than other +coordinates, it may dominate the effect of roundoff errors on +distance computations. You may use option 'QbB' to scale points to the unit cube. +For Delaunay triangulations and Voronoi diagrams, qdelaunay +and qvoronoi always set +option 'Qbb'. It scales the last +coordinate to [0,m] where m is the maximum width of the +other coordinates. Option 'Qbb' is +needed for Delaunay triangulations of integer coordinates +and nearly cocircular points. + +

      For example, compare +

      +        rbox 1000 W0 t | qconvex Qb2:-1e-14B2:1e-14
      +
      +with +
      +        rbox 1000 W0 t | qconvex
      +
      +The distributions are the same but the first is compressed to a 2e-14 slab. +

      +

    • Post-merging of coplanar facets -- In 5-d and higher, option 'Qx' +(default) delays merging of coplanar facets until post-merging. +This may allow "dents" to occur in the intermediate +convex hulls. A point may be poorly partitioned and force a poor +approximation. See option 'Qx' for +further discussion.

      + +

      This is difficult to produce in 5-d and higher. Option 'Q6' turns off merging of concave +facets. This is similar to 'Qx'. It may lead to serious precision errors, +for example, +

      +        rbox 10000 W1e-13  | qhull Q6  Tv
      +
      + +

      +

    • Maximum facet width -- +Qhull reports the maximum outer plane and inner planes (if +more than roundoff error apart). There is no upper bound +for either figure. This is an area for further research. Qhull +does a good job of post-merging in all dimensions. Qhull does a +good job of pre-merging in 2-d, 3-d, and 4-d. With the 'Qx' option, it does a good job in +higher dimensions. In 5-d and higher, Qhull does poorly at +detecting redundant vertices.

      + +

      In the summary ('s'), look at the +ratio between the maximum facet width and the maximum width of a +single merge, e.g., "(3.4x)". Qhull usually reports a +ratio of four or lower in 3-d and six or lower in 4-d. If it +reports a ratio greater than 10, this may indicate an +implementation error. Narrow distributions (see following) may +produce wide facets. + +

      For example, if special processing for narrow distributions is +turned off ('Q10'), qhull may produce +a wide facet:

      +
      +         rbox 1000 L100000 s G1e-16 t1002074964 | qhull Tv Q10
      +
      + +

      +

    • Narrow distribution -- In 3-d, a narrow distribution may result in a poor +approximation. For example, if you do not use qdelaunay nor option +'Qbb', the furthest-site +Delaunay triangulation of nearly cocircular points may produce a poor +approximation: +
      +         rbox s 5000 W1e-13 D2 t1002151341 | qhull d Qt
      +         rbox 1000 s W1e-13 t1002231672 | qhull d Tv
      +
      + +

      During +construction of the hull, a point may be above two +facets with opposite orientations that span the input +set. Even though the point may be nearly coplanar with both +facets, and can be distant from the precise convex +hull of the input sites. Additional facets leave the point distant from +a facet. To fix this problem, add option 'Qbb' +(it scales the last coordinate). Option 'Qbb' +is automatically set for qdelaunay and qvoronoi. + +

      Qhull generates a warning if the initial simplex is narrow. +For narrow distributions, Qhull changes how it processes coplanar +points -- it does not make a point coplanar until the hull is +finished. +Use option 'Q10' to try Qhull without +special processing for narrow distributions. +For example, special processing is needed for: +

      +         rbox 1000 L100000 s G1e-16 t1002074964 | qhull Tv Q10
      +
      + +

      You may turn off the warning message by reducing +qh_WARNnarrow in user.h or by setting option +'Pp'.

      + +

      Similar problems occur for distributions with a large flat facet surrounded +with many small facet at a sharp angle to the large facet. +Qhull 3.1 fixes most of these problems, but a poor approximation can occur. +A point may be left outside of the convex hull ('Tv'). +Examples include +the furthest-site Delaunay triangulation of nearly cocircular points plus the origin, and the convex hull of a cone of nearly cocircular points. The width of the band is 10^-13. +

      +        rbox s 1000 W1e-13 P0 D2 t996799242 | qhull d Tv
      +        rbox 1000 s Z1 G1e-13 t1002152123 | qhull Tv
      +        rbox 1000 s Z1 G1e-13 t1002231668 | qhull Tv
      +
      + +

      +

    • Quadratic running time -- If the output contains large, non-simplicial +facets, the running time for Qhull may be quadratic in the size of the triangulated +output. For example, rbox 1000 s W1e-13 c G2 | qhull d is 4 times +faster for 500 points. The convex hull contains two large nearly spherical facets and +many nearly coplanar facets. Each new point retriangulates the spherical facet and repartitions the remaining points into all of the nearly coplanar facets. +In this case, quadratic running time is avoided if you use qdelaunay, +add option 'Qbb', +or add the origin ('P0') to the input. +

      +

    • Nearly coincident points within 1e-13 -- +Multiple, nearly coincident points within a 1e-13 ball of points in the unit cube +may lead to wide facets or quadratic running time. +For example, the convex hull a 1000 coincident, cospherical points in 4-D, +or the 3-D Delaunay triangulation of nearly coincident points, may lead to very +wide facets (e.g., 2267021951.3x). + +

      For Delaunay triangulations, the problem typically occurs for extreme points of the input +set (i.e., on the edge between the upper and lower convex hull). After multiple facet merges, four +facets may share the same, duplicate ridge and must be merged. +Some of these facets may be long and narrow, leading to a very wide merged facet. +If so, error QH6271 is reported. It may be overriden with option 'Q12'. + +

      Duplicate ridges occur when the horizon facets for a new point is "pinched". +In a duplicate ridge, a subridge (e.g., a line segment in 3-d) is shared by two horizon facets. +At least two of its vertices are nearly coincident. It is easy to generate coincident points with +option 'Cn,r,m' of rbox. It generates n points within an r ball for each of m input sites. For example, +every point of the following distributions has a nearly coincident point within a 1e-13 ball. +Substantially smaller or larger balls do not lead to pinched horizons. +

      +        rbox 1000 C1,1e-13 D4 s t | qhull
      +        rbox 75 C1,1e-13 t | qhull d
      +
      +For Delaunay triangulations, a bounding box may alleviate this error (e.g., rbox 500 C1,1E-13 t c G1 | qhull d). +A later release of qhull will avoid pinched horizons by merging duplicate subridges. A subridge is +merged by merging adjacent vertices. +

      +

    • Facet with zero-area -- +It is possible for a zero-area facet to be convex with its +neighbors. This can occur if the hyperplanes of neighboring +facets are above the facet's centrum, and the facet's hyperplane +is above the neighboring centrums. Qhull computes the facet's +hyperplane so that it passes through the facet's vertices. The +vertices can be collinear.

      + +

      +

    • No more facets -- Qhull reports an error if there are d+1 facets left +and two of the facets are not clearly convex. This typically +occurs when the convexity constraints are too strong or the input +points are degenerate. The former is more likely in 5-d and +higher -- especially with option 'C-n'.

      + +

      +

    • Deleted cone -- Lots of merging can end up deleting all +of the new facets for a point. This is a rare event that has +only been seen while debugging the code. + +

      +

    • Triangulated output leads to precision problems -- With sufficient +merging, the ridges of a non-simplicial facet may have serious topological +and geometric problems. A ridge may be between more than two +neighboring facets. If so, their triangulation ('Qt') +will fail since two facets have the same vertex set. Furthermore, +a triangulated facet may have flipped orientation compared to its +neighbors.
    • + +

      The triangulation process detects degenerate facets with +only two neighbors. These are marked degenerate. They have +zero area. + +

      +

    • Coplanar points -- +Option 'Qc' is determined by +qh_check_maxout() after constructing the hull. Qhull needs to +retain all possible coplanar points in the facets' coplanar sets. +This depends on qh_RATIOnearInside in user.h. +Furthermore, the cutoff for a coplanar point is arbitrarily set +at the minimum vertex. If coplanar points are important to your +application, remove the interior points by hand (set 'Qc Qi') or +make qh_RATIOnearInside sufficiently large.

      + +

      +

    • Maximum roundoff error -- Qhull computes the maximum roundoff error from the maximum +coordinates of the point set. Usually the maximum roundoff error +is a reasonable choice for all distance computations. The maximum +roundoff error could be computed separately for each point or for +each distance computation. This is expensive and it conflicts +with option 'C-n'. + +

      +

    • All flipped or upper Delaunay -- When a lot of merging occurs for +Delaunay triangulations, a new point may lead to no good facets. For example, +try a strong convexity constraint: +
      +        rbox 1000 s t993602376 | qdelaunay C-1e-3
      +
      + +
    + +

    »Joggled input

    + +

    Joggled input is a simple work-around for precision problems +in computational geometry ["joggle: to shake or jar +slightly," Amer. Heritage Dictionary]. Other names are +jostled input or random perturbation. +Qhull joggles the +input by modifying each coordinate by a small random quantity. If +a precision problem occurs, Qhull joggles the input with a larger +quantity and the algorithm is restarted. This process continues +until no precision problems occur. Unless all inputs incur +precision problems, Qhull will terminate. Qhull adjusts the inner +and outer planes to account for the joggled input.

    + +

    Neither joggle nor merged facets has an upper bound for the width of the output +facets, but both methods work well in practice. Joggled input is +easier to justify. Precision errors occur when the points are +nearly singular. For example, four points may be coplanar or +three points may be collinear. Consider a line and an incident +point. A precision error occurs if the point is within some +epsilon of the line. Now joggle the point away from the line by a +small, uniformly distributed, random quantity. If the point is +changed by more than epsilon, the precision error is avoided. The +probability of this event depends on the maximum joggle. Once the +maximum joggle is larger than epsilon, doubling the maximum +joggle will halve the probability of a precision error.

    + +

    With actual data, an analysis would need to account for each +point changing independently and other computations. It is easier +to determine the probabilities empirically ('TRn') . For example, consider +computing the convex hull of the unit cube centered on the +origin. The arithmetic has 16 significant decimal digits.

    + +
    +

    Convex hull of unit cube

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    joggleerror prob.
    1.0e-150.983
    2.0e-150.830
    4.0e-150.561
    8.0e-150.325
    1.6e-140.185
    3.2e-140.099
    6.4e-140.051
    1.3e-130.025
    2.6e-130.010
    5.1e-130.004
    1.0e-120.002
    2.0e-120.001
    +
    + +

    A larger joggle is needed for multiple points. Since the +number of potential singularities increases, the probability of +one or more precision errors increases. Here is an example.

    + +
    +

    Convex hull of 1000 points on unit cube

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    joggleerror prob.
    1.0e-120.870
    2.0e-120.700
    4.0e-120.450
    8.0e-120.250
    1.6e-110.110
    3.2e-110.065
    6.4e-110.030
    1.3e-100.010
    2.6e-100.008
    5.1e-090.003
    +
    + +

    Other distributions behave similarly. No distribution should +behave significantly worse. In Euclidean space, the probability +measure of all singularities is zero. With floating point +numbers, the probability of a singularity is non-zero. With +sufficient digits, the probability of a singularity is extremely +small for random data. For a sufficiently large joggle, all data +is nearly random data.

    + +

    Qhull uses an initial joggle of 30,000 times the maximum +roundoff error for a distance computation. This avoids most +potential singularities. If a failure occurs, Qhull retries at +the initial joggle (in case bad luck occurred). If it occurs +again, Qhull increases the joggle by ten-fold and tries again. +This process repeats until the joggle is a hundredth of the width +of the input points. Qhull reports an error after 100 attempts. +This should never happen with double-precision arithmetic. Once +the probability of success is non-zero, the probability of +success increases about ten-fold at each iteration. The +probability of repeated failures becomes extremely small.

    + +

    Merged facets produces a significantly better approximation. +Empirically, the maximum separation between inner and outer +facets is about 30 times the maximum roundoff error for a +distance computation. This is about 2,000 times better than +joggled input. Most applications though will not notice the +difference.

    + +

    »Exact arithmetic

    + +

    Exact arithmetic may be used instead of floating point. +Singularities such as coplanar points can either be handled +directly or the input can be symbolically perturbed. Using exact +arithmetic is slower than using floating point arithmetic and the +output may take more space. Chaining a sequence of operations +increases the time and space required. Some operations are +difficult to do.

    + +

    Clarkson's hull +program and Shewchuk's triangle +program are practical implementations of exact arithmetic.

    + +

    Clarkson limits the input precision to about fifteen digits. +This reduces the number of nearly singular computations. When a +determinant is nearly singular, he uses exact arithmetic to +compute a precise result.

    + +

    »Approximating a +convex hull

    + +

    Qhull may be used for approximating a convex hull. This is +particularly valuable in 5-d and higher where hulls can be +immense. You can use 'Qx C-n' to merge facets as the hull is +being constructed. Then use 'Cn' +and/or 'An' to merge small facets +during post-processing. You can print the n largest facets +with option 'PAn'. You can print +facets whose area is at least n with option 'PFn'. You can output the outer planes +and an interior point with 'FV Fo' and then compute their intersection +with 'qhalf'.

    + +

    To approximate a convex hull in 6-d and higher, use +post-merging with 'Wn' (e.g., qhull +W1e-1 C1e-2 TF2000). Pre-merging with a convexity constraint +(e.g., qhull Qx C-1e-2) often produces a poor approximation or +terminates with a simplex. Option 'QbB' +may help to spread out the data.

    + +

    You will need to experiment to determine a satisfactory set of +options. Use rbox to generate test sets +quickly and Geomview to view +the results. You will probably want to write your own driver for +Qhull using the Qhull library. For example, you could select the +largest facet in each quadrant.

    + + +
    + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of +Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull imprecision: Table of Contents + + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optc.htm b/xs/src/qhull/html/qh-optc.htm new file mode 100644 index 0000000000..87308180d7 --- /dev/null +++ b/xs/src/qhull/html/qh-optc.htm @@ -0,0 +1,292 @@ + + + + +Qhull precision options + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull precision options

    + +This section lists the precision options for Qhull. These options are +indicated by an upper-case letter followed by a number. + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Precision options

    + +

    Most users will not need to set these options. They are best +used for approximating a +convex hull. They may also be used for testing Qhull's handling +of precision errors.

    + +

    By default, Qhull uses options 'C-0' for +2-d, 3-d and 4-d, and 'Qx' for 5-d +and higher. These options use facet merging to handle precision +errors. You may also use joggled input 'QJ' +to avoid precision problems. +For more information see Imprecision in Qhull.

    + +
    +
     
    +
    General
    +
    Cn
    +
    centrum radius for post-merging
    +
    C-n
    +
    centrum radius for pre-merging
    +
    An
    +
    cosine of maximum angle for post-merging
    +
    A-n
    +
    cosine of maximum angle for pre-merging
    +
    Qx
    +
    exact pre-merges (allows coplanar facets)
    +
    C-0
    +
    handle all precision errors
    +
    Wn
    +
    min distance above plane for outside points
    +
    + +
    +
     
    +
    Experimental
    +
    Un
    +
    max distance below plane for a new, coplanar point
    +
    En
    +
    max roundoff error for distance computation
    +
    Vn
    +
    min distance above plane for a visible facet
    +
    Rn
    +
    randomly perturb computations by a factor of [1-n,1+n]
    +
    + +
    +
    + +
    + +

    »A-n - cosine of maximum +angle for pre-merging.

    + +

    Pre-merging occurs while Qhull constructs the hull. It is +indicated by 'C-n', 'A-n', or 'Qx'.

    + +

    If the angle between a pair of facet normals is greater than n, +Qhull merges one of the facets into a neighbor. It selects the +facet that is closest to a neighboring facet.

    + +

    For example, option 'A-0.99' merges facets during the +construction of the hull. If the cosine of the angle between +facets is greater than 0.99, one or the other facet is merged. +Qhull accounts for the maximum roundoff error.

    + +

    If 'A-n' is set without 'C-n', then 'C-0' is automatically set.

    + +

    In 5-d and higher, you should set 'Qx' +along with 'A-n'. It skips merges of coplanar facets until after +the hull is constructed and before 'An' and 'Cn' are checked.

    + +

    »An - cosine of maximum angle for +post-merging.

    + +

    Post merging occurs after the hull is constructed. For +example, option 'A0.99' merges a facet if the cosine of the angle +between facets is greater than 0.99. Qhull accounts for the +maximum roundoff error.

    + +

    If 'An' is set without 'Cn', then 'C0' is automatically set.

    + +

    »C-0 - handle all precision +errors

    + +

    Qhull handles precision errors by merging facets. The 'C-0' +option handles all precision errors in 2-d, 3-d, and 4-d. It is +set by default. It may be used in higher dimensions, but +sometimes the facet width grows rapidly. It is usually better to +use 'Qx' in 5-d and higher. +Use 'QJ' to joggle the input +instead of merging facets. +Use 'Q0' to turn both options off.

    + +

    Qhull optimizes 'C-0' ("_zero-centrum") by testing +vertices instead of centrums for adjacent simplices. This may be +slower in higher dimensions if merges decrease the number of +processed points. The optimization may be turned off by setting a +small value such as 'C-1e-30'. See How +Qhull handles imprecision.

    + +

    »C-n - centrum radius for +pre-merging

    + +

    Pre-merging occurs while Qhull constructs the hull. It is +indicated by 'C-n', 'A-n', or 'Qx'.

    + +

    The centrum of a facet is a point on the facet for +testing facet convexity. It is the average of the vertices +projected to the facet's hyperplane. Two adjacent facets are +convex if each centrum is clearly below the other facet.

    + +

    If adjacent facets are non-convex, one of the facets is merged +into a neighboring facet. Qhull merges the facet that is closest +to a neighboring facet.

    + +

    For option 'C-n', n is the centrum radius. For example, +'C-0.001' merges facets whenever the centrum is less than 0.001 +from a neighboring hyperplane. Qhull accounts for roundoff error +when testing the centrum.

    + +

    In 5-d and higher, you should set 'Qx' +along with 'C-n'. It skips merges of coplanar facets until after +the hull is constructed and before 'An' and 'Cn' are checked.

    + +

    »Cn - centrum radius for +post-merging

    + +

    Post-merging occurs after Qhull constructs the hull. It is +indicated by 'Cn' or 'An'.

    + +

    For option 'Cn', n is the centrum +radius. For example, 'C0.001' merges facets when the centrum is +less than 0.001 from a neighboring hyperplane. Qhull accounts for +roundoff error when testing the centrum.

    + +

    Both pre-merging and post-merging may be defined. If only +post-merging is used ('Q0' with +'Cn'), Qhull may fail to produce a hull due to precision errors +during the hull's construction.

    + +

    »En - max roundoff error +for distance computations

    + +

    This allows the user to change the maximum roundoff error +computed by Qhull. The value computed by Qhull may be overly +pessimistic. If 'En' is set too small, then the output may not be +convex. The statistic "max. distance of a new vertex to a +facet" (from option 'Ts') is a +reasonable upper bound for the actual roundoff error.

    + +

    »Rn - randomly perturb +computations

    + +

    This option perturbs every distance, hyperplane, and angle +computation by up to (+/- n * max_coord). It simulates the +effect of roundoff errors. Unless 'En' is +explicitly set, it is adjusted for 'Rn'. The command 'qhull Rn' +will generate a convex hull despite the perturbations. See the Examples section for an example.

    + +

    Options 'Rn C-n' have the effect of 'W2n' +and 'C-2n'. To use time as the random number +seed, use option 'QR-1'.

    + +

    »Un - max distance for a +new, coplanar point

    + +

    This allows the user to set coplanarity. When pre-merging ('C-n ', 'A-n' or 'Qx'), Qhull merges a new point into any +coplanar facets. The default value for 'Un' is 'Vn'.

    + +

    »Vn - min distance for a +visible facet

    + +

    This allows the user to set facet visibility. When adding a +point to the convex hull, Qhull determines all facets that are +visible from the point. A facet is visible if the distance from +the point to the facet is greater than 'Vn'.

    + +

    Without merging, the default value for 'Vn' is the roundoff +error ('En'). With merging, the default value +is the pre-merge centrum ('C-n') in 2-d or 3-d, +or three times that in other dimensions. If the outside width is +specified with option 'Wn ', the maximum, +default value for 'Vn' is 'Wn'.

    + +

    Qhull warns if 'Vn' is greater than 'Wn' and +furthest outside ('Qf') is not +selected; this combination usually results in flipped facets +(i.e., reversed normals).

    + +

    »Wn - min distance above +plane for outside points

    + +

    Points are added to the convex hull only if they are clearly +outside of a facet. A point is outside of a facet if its distance +to the facet is greater than 'Wn'. Without pre-merging, the +default value for 'Wn' is 'En '. If the user +specifies pre-merging and does not set 'Wn', than 'Wn' is set to +the maximum of 'C-n' and maxcoord*(1 - A-n).

    + +

    This option is good for approximating +a convex hull.

    + +

    Options 'Qc' and 'Qi' use the minimum vertex to +distinguish coplanar points from interior points.

    + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optf.htm b/xs/src/qhull/html/qh-optf.htm new file mode 100644 index 0000000000..3c7a2b1db2 --- /dev/null +++ b/xs/src/qhull/html/qh-optf.htm @@ -0,0 +1,736 @@ + + + + +Qhull format options (F) + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    +
    + +

    [delaunay] Qhull format options (F)

    + +

    This section lists the format options for Qhull. These options +are indicated by 'F' followed by a letter. See Output, Print, +and Geomview for other output +options.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Additional input & output formats

    + +

    These options allow for automatic processing of Qhull output. +Options 'i', 'o', +'n', and 'p' +may also be used.

    + +
    +
    +
    Summary and control +
    FA +
    compute total area and volume for option 's' + +
    FV +
    print average vertex (interior point for 'qhalf') +
    FQ +
    print command for qhull and input +
    FO +
    print options to stderr or stdout +
    FS +
    print sizes: total area and volume +
    Fs +
    print summary: dim, #points, total vertices and + facets, #vertices, #facets, max outer and inner plane +
    Fd +
    use format for input (offset first) +
    FD +
    use cdd format for normals (offset first) +
    FM +
    print Maple output (2-d and 3-d) +
    +
    +
    Facets, points, and vertices +
    Fa +
    print area for each facet +
    FC +
    print centrum for each facet +
    Fc +
    print coplanar points for each facet +
    Fx +
    print extreme points (i.e., vertices) of convex hull. + +
    FF +
    print facets w/o ridges +
    FI +
    print ID for each facet +
    Fi +
    print inner planes for each facet +
    Fm +
    print merge count for each facet (511 max) +
    FP +
    print nearest vertex for coplanar points +
    Fn +
    print neighboring facets for each facet +
    FN +
    print neighboring facets for each point +
    Fo +
    print outer planes for each facet +
    Ft +
    print triangulation with added points +
    Fv +
    print vertices for each facet +
    +
    +
    Delaunay, Voronoi, and halfspace +
    Fx +
    print extreme input sites of Delaunay triangulation + or Voronoi diagram. +
    Fp +
    print points at halfspace intersections +
    Fi +
    print separating hyperplanes for inner, bounded + Voronoi regions +
    Fo +
    print separating hyperplanes for outer, unbounded + Voronoi regions +
    Fv +
    print Voronoi diagram as ridges for each input pair +
    FC +
    print Voronoi vertex ("center") for each facet
    +
    + +
    + +

    »Fa - print area for each +facet

    + +

    The first line is the number of facets. The remaining lines +are the area for each facet, one facet per line. See 'FA' and 'FS' for computing the total area and volume.

    + +

    Use 'PAn' for printing the n +largest facets. Use option 'PFn' +for printing facets larger than n.

    + +

    For Delaunay triangulations, the area is the area of each +Delaunay triangle. For Voronoi vertices, the area is the area of +the dual facet to each vertex.

    + +

    Qhull uses the centrum and ridges to triangulate +non-simplicial facets. The area for non-simplicial facets is the +sum of the areas for each triangle. It is an approximation of the +actual area. The ridge's vertices are projected to the facet's +hyperplane. If a vertex is far below a facet (qh_WIDEcoplanar in user.h), +the corresponding triangles are ignored.

    + +

    For non-simplicial facets, vertices are often below the +facet's hyperplane. If so, the approximation is less than the +actual value and it may be significantly less.

    + +

    »FA - compute total area +and volume for option 's'

    + +

    With option 'FA', Qhull includes the total area and volume in +the summary ('s'). Option 'FS' also includes the total area and volume. +If facets are +merged, the area and volume are approximations. Option 'FA' is +automatically set for options 'Fa', 'PAn', and 'PFn'. +

    + +

    With 'qdelaunay s FA', Qhull computes the total area of +the Delaunay triangulation. This equals the volume of the convex +hull of the data points. With options 'qdelaunay Qu +s FA', Qhull computes the +total area of the furthest-site Delaunay triangulation. This +equals of the total area of the Delaunay triangulation.

    + +

    See 'Fa' for further details. Option 'FS' also computes the total area and volume.

    + +

    »Fc - print coplanar +points for each facet

    + +

    The output starts with the number of facets. Then each facet +is printed one per line. Each line is the number of coplanar +points followed by the point ids.

    + +

    By default, option 'Fc' reports coplanar points +('Qc'). You may also use +option 'Qi'. Options 'Qi Fc' prints +interior points while 'Qci Fc' prints both coplanar and interior +points. + +

    Each coplanar point or interior point is assigned to the +facet it is furthest above (resp., least below).

    + +

    Use 'Qc p' to print vertex and +coplanar point coordinates. Use 'Fv' +to print vertices.

    + +

    »FC - print centrum or +Voronoi vertex for each facet

    + +

    The output starts with the dimension followed by the number of +facets. Then each facet centrum is printed, one per line. For +qvoronoi, Voronoi vertices are +printed instead.

    + +

    »Fd - use cdd format for +input

    + +

    The input starts with comments. The first comment is reported +in the summary. Data starts after a "begin" line. The +next line is the number of points followed by the dimension plus +one and "real" or "integer". Then the points +are listed with a leading "1" or "1.0". The +data ends with an "end" line.

    + +

    For halfspaces ('qhalf Fd'), +the input format is the same. Each halfspace starts with its +offset. The signs of the offset and coefficients are the +opposite of Qhull's +convention. The first two lines of the input may be an interior +point in 'FV' format.

    + +

    »FD - use cdd format for +normals

    + +

    Option 'FD' prints normals ('n', 'Fo', 'Fi') or points ('p') in cdd format. The first line is the +command line that invoked Qhull. Data starts with a +"begin" line. The next line is the number of normals or +points followed by the dimension plus one and "real". +Then the normals or points are listed with the offset before the +coefficients. The offset for points is 1.0. For normals, +the offset and coefficients use the opposite sign from Qhull. +The data ends with an "end" line.

    + +

    »FF - print facets w/o +ridges

    + +

    Option 'FF' prints all fields of all facets (as in 'f') without printing the ridges. This is +useful in higher dimensions where a facet may have many ridges. +For simplicial facets, options 'FF' and 'f +' are equivalent.

    + +

    »Fi - print inner planes +for each facet

    + +

    The first line is the dimension plus one. The second line is +the number of facets. The remainder is one inner plane per line. +The format is the same as option 'n'.

    + +

    The inner plane is a plane that is below the facet's vertices. +It is an offset from the facet's hyperplane. It includes a +roundoff error for computing the vertex distance.

    + +

    Note that the inner planes for Geomview output ('Gi') include an additional offset for +vertex visualization and roundoff error.

    + +

    »Fi - print separating +hyperplanes for inner, bounded Voronoi regions

    + +

    With qvoronoi, 'Fi' prints the +separating hyperplanes for inner, bounded regions of the Voronoi +diagram. The first line is the number of ridges. Then each +hyperplane is printed, one per line. A line starts with the +number of indices and floats. The first pair of indices indicates +an adjacent pair of input sites. The next d floats are the +normalized coefficients for the hyperplane, and the last float is +the offset. The hyperplane is oriented toward 'QVn' (if defined), or the first input +site of the pair.

    + +

    Use 'Fo' for unbounded regions, +and 'Fv' for the corresponding +Voronoi vertices.

    + +

    Use 'Tv' to verify that the +hyperplanes are perpendicular bisectors. It will list relevant +statistics to stderr. The hyperplane is a perpendicular bisector +if the midpoint of the input sites lies on the plane, all Voronoi +vertices in the ridge lie on the plane, and the angle between the +input sites and the plane is ninety degrees. This is true if all +statistics are zero. Roundoff and computation errors make these +non-zero. The deviations appear to be largest when the +corresponding Delaunay triangles are large and thin; for example, +the Voronoi diagram of nearly cospherical points.

    + +

    »FI - print ID for each +facet

    + +

    Print facet identifiers. These are used internally and listed +with options 'f' and 'FF'. +Options 'Fn ' and 'FN' use +facet identifiers for negative indices.

    + +

    »Fm - print merge count +for each facet

    + +

    The first line is the number of facets. The remainder is the +number of merges for each facet, one per line. At most 511 merges +are reported for a facet. See 'PMn' +for printing the facets with the most merges.

    + +

    »FM - print Maple +output

    + +

    Qhull writes a Maple file for 2-d and 3-d convex hulls, +2-d and 3-d halfspace intersections, +and 2-d Delaunay triangulations. Qhull produces a 2-d +or 3-d plot. + +

    Warning: This option has not been tested in Maple. + +

    [From T. K. Abraham with help from M. R. Feinberg and N. Platinova.] +The following steps apply while working within the +Maple worksheet environment : +

      +
    1. Generate the data and store it as an array . For example, in 3-d, data generated +in Maple is of the form : x[i],y[i],z[i] +

      +

    2. Create a single variable and assign the entire array of data points to this variable. +Use the "seq" command within square brackets as shown in the following example. +(The square brackets are essential for the rest of the steps to work.) +

      +>data:=[seq([x[i],y[i],z[i]],i=1..n)]:# here n is the number of data points + +

    3. Next we need to write the data to a file to be read by qhull. Before +writing the data to a file, make sure that the qhull executable files and +the data file lie in the same subdirectory. If the executable files are +stored in the "C:\qhull3.1\" subdirectory, then save the file in the same +subdirectory, say "C:\qhull3.1\datafile.txt". For the sake of integrity of +the data file , it is best to first ensure that the data file does not +exist before writing into the data file. This can be done by running a +delete command first . To write the data to the file, use the "writedata" +and the "writedata[APPEND]" commands as illustrated in the following example : +

      +>system("del c:\\qhull3.1\\datafile.txt");#To erase any previous versions of the file +
      >writedata("c:\\qhull3.1\\datafile.txt ",[3, nops(data)]);#writing in qhull format +
      >writedata[APPEND]("c:\\ qhull3.1\\datafile.txt ", data);#writing the data points +

    4. +Use the 'FM' option to produce Maple output. Store the output as a ".mpl" file. +For example, using the file we created above, we type the following (in DOS environment) +

      +qconvex s FM <datafile.txt >dataplot.mpl + +

    5. +To read 3-d output in Maple, we use the 'read' command followed by +a 'display3d' command. For example (in Maple environment): +

      +>with (plots): +
      >read `c:\\qhull3.1\\dataplot.mpl`:#IMPORTANT - Note that the punctuation mark used is ' and NOT '. The correct punctuation mark is the one next to the key for "1" (not the punctuation mark near the enter key) +
      > qhullplot:=%: +
      > display3d(qhullplot); +

    + +

    For Delaunay triangulation orthogonal projection is better. + +

    For halfspace intersections, Qhull produces the dual +convex hull. + +

    See Is Qhull available for Maple? +for other URLs. + +

    »Fn - print neighboring +facets for each facet

    + +

    The output starts with the number of facets. Then each facet +is printed one per line. Each line is the number of neighbors +followed by an index for each neighbor. The indices match the +other facet output formats.

    + +

    For simplicial facets, each neighbor is opposite +the corresponding vertex (option 'Fv'). +Do not compare to option 'i'. Option 'i' +orients facets by reversing the order of two vertices. For non-simplicial facets, +the neighbors are unordered. + +

    A negative index indicates an unprinted facet due to printing +only good facets ('Pg', qdelaunay, +qvoronoi). It +is the negation of the facet's ID (option 'FI'). +For example, negative indices are used for facets "at +infinity" in the Delaunay triangulation.

    + +

    »FN - print neighboring +facets for each point

    + +

    The first line is the number of points. Then each point is +printed, one per line. For unassigned points (either interior or +coplanar), the line is "0". For assigned coplanar +points ('Qc'), the line is +"1" followed by the index of the facet that is furthest +below the point. For assigned interior points ('Qi'), the line is "1" +followed by the index of the facet that is least above the point. +For vertices that do not belong to good facet, the line is +"0"

    + +

    For vertices of good facets, the line is the number of +neighboring facets followed by the facet indices. The indices +correspond to the other 'F' formats. In 4-d +and higher, the facets are sorted by index. In 3-d, the facets +are in adjacency order (not oriented).

    + +

    A negative index indicates an unprinted facet due to printing +only good facets (qdelaunay, +qvoronoi, 'Pdk', +'Pg'). It is the negation of the +facet's ID (' FI'). For example, negative +indices are used for facets "at infinity" in the +Delaunay triangulation.

    + +

    For Voronoi vertices, option 'FN' lists the vertices of the +Voronoi region for each input site. Option 'FN' lists the regions +in site ID order. Option 'FN' corresponds to the second half of +option 'o'. To convert from 'FN' to 'o', replace negative indices with zero +and increment non-negative indices by one.

    + +

    If you are using the Qhull +library or C++ interface, option 'FN' has the side effect of reordering the +neighbors for a vertex

    + +

    »Fo - print outer planes +for each facet

    + +

    The first line is the dimension plus one. The second line is +the number of facets. The remainder is one outer plane per line. +The format is the same as option 'n'.

    + +

    The outer plane is a plane that is above all points. It is an +offset from the facet's hyperplane. It includes a roundoff error +for computing the point distance. When testing the outer plane +(e.g., 'Tv'), another roundoff error +should be added for the tested point.

    + +

    If outer planes are not checked ('Q5') +or not computed (!qh_MAXoutside), the maximum, computed outside +distance is used instead. This can be much larger than the actual +outer planes.

    + +

    Note that the outer planes for Geomview output ('G') include an additional offset for +vertex/point visualization, 'lines closer,' and roundoff error.

    + +

    »Fo - print separating +hyperplanes for outer, unbounded Voronoi regions

    + +

    With qvoronoi, 'Fo' prints the +separating hyperplanes for outer, unbounded regions of the +Voronoi diagram. The first line is the number of ridges. Then +each hyperplane is printed, one per line. A line starts with the +number of indices and floats. The first pair of indices indicates +an adjacent pair of input sites. The next d floats are the +normalized coefficients for the hyperplane, and the last float is +the offset. The hyperplane is oriented toward 'QVn' (if defined), or the first input +site of the pair.

    + +

    Option 'Fo' gives the hyperplanes for the unbounded rays of +the unbounded regions of the Voronoi diagram. Each hyperplane +goes through the midpoint of the corresponding input sites. The +rays are directed away from the input sites.

    + +

    Use 'Fi' for bounded regions, +and 'Fv' for the corresponding +Voronoi vertices. Use 'Tv' to verify +that the corresponding Voronoi vertices lie on the hyperplane.

    + +

    »FO - print list of +selected options

    + +

    Lists selected options and default values to stderr. +Additional 'FO's are printed to stdout.

    + +

    »Fp - print points at +halfspace intersections

    + +

    The first line is the number of intersection points. The +remainder is one intersection point per line. A intersection +point is the intersection of d or more halfspaces from +'qhalf'. It corresponds to a +facet of the dual polytope. The "infinity" point +[-10.101,-10.101,...] indicates an unbounded intersection.

    + +

    If [x,y,z] are the dual facet's normal coefficients and b<0 +is its offset, the halfspace intersection occurs at +[x/-b,y/-b,z/-b] plus the interior point. If b>=0, the +halfspace intersection is unbounded.

    + +

    »FP - print nearest +vertex for coplanar points

    + +

    The output starts with the number of coplanar points. Then +each coplanar point is printed one per line. Each line is the +point ID of the closest vertex, the point ID of the coplanar +point, the corresponding facet ID, and the distance. Sort the +lines to list the coplanar points nearest to each vertex.

    + +

    Use options 'Qc' and/or 'Qi' with 'FP'. Options 'Qc FP' prints +coplanar points while 'Qci FP' prints coplanar and interior +points. Option 'Qc' is automatically selected if 'Qi' is not +selected. + +

    For Delaunay triangulations (qdelaunay +or qvoronoi), a coplanar point is nearly +incident to a vertex. The distance is the distance in the +original point set.

    + +

    If imprecision problems are severe, Qhull will delete input +sites when constructing the Delaunay triangulation. Option 'FP' will +list these points along with coincident points.

    + +

    If there are many coplanar or coincident points and non-simplicial +facets are triangulated ('Qt'), option +'FP' may be inefficient. It redetermines the original vertex set +for each coplanar point.

    + +

    »FQ - print command for +qhull and input

    + +

    Prints qhull and input command, e.g., "rbox 10 s | qhull +FQ". Option 'FQ' may be repeated multiple times.

    + +

    »Fs - print summary

    + +

    The first line consists of number of integers ("10") +followed by the: +

      +
    • dimension +
    • number of points +
    • number of vertices +
    • number of facets +
    • number of vertices selected for output +
    • number of facets selected for output +
    • number of coplanar points for selected facets +
    • number of nonsimplicial or merged facets selected for + output +
    • number of deleted vertices
    • +
    • number of triangulated facets ('Qt')
    • +
    + +

    The second line consists of the number of reals +("2") followed by the: +

      +
    • maximum offset to an outer plane +
    • minimum offset to an inner plane.
    • +
    +Roundoff and joggle are included. +

    + +

    For Delaunay triangulations and Voronoi diagrams, the +number of deleted vertices should be zero. If greater than zero, then the +input is highly degenerate and coplanar points are not necessarily coincident +points. For example, 'RBOX 1000 s W1e-13 t995138628 | QHULL d Qbb' reports +deleted vertices; the input is nearly cospherical.

    + +

    Later versions of Qhull may produce additional integers or reals.

    + +

    »FS - print sizes

    + +

    The first line consists of the number of integers +("0"). The second line consists of the number of reals +("2"), followed by the total facet area, and the total +volume. Later versions of Qhull may produce additional integers +or reals.

    + +

    The total volume measures the volume of the intersection of +the halfspaces defined by each facet. It is computed from the +facet area. Both area and volume are approximations for +non-simplicial facets. See option 'Fa ' for +further notes. Option 'FA ' also computes the total area and volume.

    + +

    »Ft - print triangulation

    + +

    Prints a triangulation with added points for non-simplicial +facets. The output is

    + +
      +
    • The first line is the dimension +
    • The second line is the number of points, the number + of facets, and the number of ridges. +
    • All of the input points follow, one per line. +
    • The centrums follow, one per non-simplicial facet +
    • Then the facets follow as a list of point indices + preceded by the number of points. The simplices are + oriented.
    • +
    + +

    For convex hulls with simplicial facets, the output is the +same as option 'o'.

    + +

    The added points are the centrums of the non-simplicial +facets. Except for large facets, the centrum is the average +vertex coordinate projected to the facet's hyperplane. Large +facets may use an old centrum to avoid recomputing the centrum +after each merge. In either case, the centrum is clearly below +neighboring facets. See Precision issues. +

    + +

    The new simplices will not be clearly convex with their +neighbors and they will not satisfy the Delaunay property. They +may even have a flipped orientation. Use triangulated input ('Qt') for Delaunay triangulations. + +

    For Delaunay triangulations with simplicial facets, the output is the +same as option 'o' without the lifted +coordinate. Since 'Ft' is invalid for merged Delaunay facets, option +'Ft' is not available for qdelaunay or qvoronoi. It may be used with +joggled input ('QJ') or triangulated output ('Qt'), for example, rbox 10 c G 0.01 | qhull d QJ Ft

    + +

    If you add a point-at-infinity with 'Qz', +it is printed after the input sites and before any centrums. It +will not be used in a Delaunay facet.

    + +

    »Fv - print vertices for +each facet

    + +

    The first line is the number of facets. Then each facet is +printed, one per line. Each line is the number of vertices +followed by the corresponding point ids. Vertices are listed in +the order they were added to the hull (the last one added is the +first listed). +

    +

    Option 'i' also lists the vertices, +but it orients facets by reversing the order of two +vertices. Option 'i' triangulates non-simplicial, 4-d and higher facets by +adding vertices for the centrums. +

    + +

    »Fv - print Voronoi +diagram

    + +

    With qvoronoi, 'Fv' prints the +Voronoi diagram or furthest-site Voronoi diagram. The first line +is the number of ridges. Then each ridge is printed, one per +line. The first number is the count of indices. The second pair +of indices indicates a pair of input sites. The remaining indices +list the corresponding ridge of Voronoi vertices. Vertex 0 is the +vertex-at-infinity. It indicates an unbounded ray.

    + +

    All vertices of a ridge are coplanar. If the ridge is +unbounded, add the midpoint of the pair of input sites. The +unbounded ray is directed from the Voronoi vertices to infinity.

    + +

    Use 'Fo' for separating +hyperplanes of outer, unbounded regions. Use 'Fi' for separating hyperplanes of +inner, bounded regions.

    + +

    Option 'Fv' does not list ridges that require more than one +midpoint. For example, the Voronoi diagram of cospherical points +lists zero ridges (e.g., 'rbox 10 s | qvoronoi Fv Qz'). +Other examples are the Voronoi diagrams of a rectangular mesh +(e.g., 'rbox 27 M1,0 | qvoronoi Fv') or a point set with +a rectangular corner (e.g., +'rbox P4,4,4 P4,2,4 P2,4,4 P4,4,2 10 | qvoronoi Fv'). +Both cases miss unbounded rays at the corners. +To determine these ridges, surround the points with a +large cube (e.g., 'rbox 10 s c G2.0 | qvoronoi Fv Qz'). +The cube needs to be large enough to bound all Voronoi regions of the original point set. +Please report any other cases that are missed. If you +can formally describe these cases or +write code to handle them, please send email to qhull@qhull.org.

    + +

    »FV - print average +vertex

    + +

    The average vertex is the average of all vertex coordinates. +It is an interior point for halfspace intersection. The first +line is the dimension and "1"; the second line is the +coordinates. For example,

    + +
    +

    qconvex FV n | qhalf Fp

    +
    + +

    prints the extreme points of the original point set (roundoff +included).

    + +

    »Fx - print extreme +points (vertices) of convex hulls and Delaunay triangulations

    + +

    The first line is the number of points. The following lines +give the index of the corresponding points. The first point is +'0'.

    + +

    In 2-d, the extreme points (vertices) are listed in +counterclockwise order (by qh_ORIENTclock in user.h).

    + +

    In 3-d and higher convex hulls, the extreme points (vertices) +are sorted by index. This is the same order as option 'p' when it doesn't include coplanar or +interior points.

    + +

    For Delaunay triangulations, 'Fx' lists the extreme +points of the input sites (i.e., the vertices of their convex hull). The points +are unordered.

    + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: +Sept. 25, 1995 --- Last modified: see top +

    + + diff --git a/xs/src/qhull/html/qh-optg.htm b/xs/src/qhull/html/qh-optg.htm new file mode 100644 index 0000000000..a56e29df10 --- /dev/null +++ b/xs/src/qhull/html/qh-optg.htm @@ -0,0 +1,274 @@ + + + +Qhull Geomview options (G) + + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + + +

    [delaunay] Qhull Geomview options (G)

    + +This section lists the Geomview options for Qhull. These options are +indicated by 'G' followed by a letter. See +Output, Print, +and Format for other output options. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Geomview output options

    + +

    Geomview is the graphical +viewer for visualizing Qhull output in 2-d, 3-d and 4-d.

    + +

    Geomview displays each facet of the convex hull. The color of +a facet is determined by the coefficients of the facet's normal +equation. For imprecise hulls, Geomview displays the inner and +outer hull. Geomview can also display points, ridges, vertices, +coplanar points, and facet intersections.

    + +

    For 2-d Delaunay triangulations, Geomview displays the +corresponding paraboloid. Geomview displays the 2-d Voronoi +diagram. For halfspace intersections, it displays the +dual convex hull.

    + +
    +
     
    +
    General
    +
    G
    +
    display Geomview output
    +
    Gt
    +
    display transparent 3-d Delaunay triangulation
    +
    GDn
    +
    drop dimension n in 3-d and 4-d output
    + +
     
    +
     
    +
    Specific
    +
    Ga
    +
    display all points as dots
    +
    Gc
    +
    display centrums (2-d, 3-d)
    +
    Gp
    +
    display coplanar points and vertices as radii
    +
    Gh
    +
    display hyperplane intersections
    +
    Gi
    +
    display inner planes only (2-d, 3-d)
    +
    Go
    +
    display outer planes only (2-d, 3-d)
    +
    Gr
    +
    display ridges (3-d)
    +
    Gv
    +
    display vertices as spheres
    +
    Gn
    +
    do not display planes
    + +
    + +
    + +

    »G - produce output for +viewing with Geomview

    + +

    By default, option 'G' displays edges in 2-d, outer planes in +3-d, and ridges in 4-d.

    + +

    A ridge can be explicit or implicit. An explicit ridge is a (d-1)-dimensional +simplex between two facets. In 4-d, the explicit ridges are +triangles. An implicit ridge is the topological intersection of +two neighboring facets. It is the union of explicit ridges.

    + +

    For non-simplicial 4-d facets, the explicit ridges can be +quite complex. When displaying a ridge in 4-d, Qhull projects the +ridge's vertices to one of its facets' hyperplanes. Use 'Gh' to project ridges to the intersection of both +hyperplanes. This usually results in a cleaner display.

    + +

    For 2-d Delaunay triangulations, Geomview displays the +corresponding paraboloid. Geomview displays the 2-d Voronoi +diagram. For halfspace intersections, it displays the +dual convex hull. + +

    »Ga - display all +points as dots

    + +

    Each input point is displayed as a green dot.

    + +

    »Gc - display centrums +(3-d)

    + +

    The centrum is defined by a green radius sitting on a blue +plane. The plane corresponds to the facet's hyperplane. If you +sight along a facet's hyperplane, you will see that all +neighboring centrums are below the facet. The radius is defined +by 'C-n' or 'Cn'.

    + +

    »GDn - drop dimension +n in 3-d and 4-d output

    + +

    The result is a 2-d or 3-d object. In 4-d, this corresponds to +viewing the 4-d object from the nth axis without perspective. +It's best to view 4-d objects in pieces. Use the 'Pdk' 'Pg' +'PG' 'QGn' +and 'QVn' options to select a few +facets. If one of the facets is perpendicular to an axis, then +projecting along that axis will show the facet exactly as it is +in 4-d. If you generate many facets, use Geomview's ginsu +module to view the interior

    + +

    To view multiple 4-d dimensions at once, output the object +without 'GDn' and read it with Geomview's ndview. As you +rotate the object in one set of dimensions, you can see how it +changes in other sets of dimensions.

    + +

    For additional control over 4-d objects, output the object +without 'GDn' and read it with Geomview's 4dview. You +can slice the object along any 4-d plane. You can also flip the +halfspace that's deleted when slicing. By combining these +features, you can get some interesting cross sections.

    + +

    »Gh - display +hyperplane intersections (3-d, 4-d)

    + +

    In 3-d, the intersection is a black line. It lies on two +neighboring hyperplanes, c.f., the blue squares associated with +centrums ('Gc '). In 4-d, the ridges are +projected to the intersection of both hyperplanes. If you turn on +edges (Geomview's 'appearances' menu), each triangle corresponds +to one ridge. The ridges may overlap each other.

    + +

    »Gi - display inner +planes only (2-d, 3-d)

    + +

    The inner plane of a facet is below all of its vertices. It is +parallel to the facet's hyperplane. The inner plane's color is +the opposite of the outer plane's color, i.e., [1-r,1-g,1-b] . +Its edges are determined by the vertices.

    + +

    »Gn - do not display +planes

    + +

    By default, Geomview displays the precise plane (no merging) +or both inner and output planes (if merging). If merging, +Geomview does not display the inner plane if the the difference +between inner and outer is too small.

    + +

    »Go - display outer +planes only (2-d, 3-d)

    + +

    The outer plane of a facet is above all input points. It is +parallel to the facet's hyperplane. Its color is determined by +the facet's normal, and its edges are determined by the vertices.

    + +

    »Gp - display coplanar +points and vertices as radii

    + +

    Coplanar points ('Qc'), interior +points ('Qi'), outside points ('TCn' or 'TVn'), +and vertices are displayed as red and yellow radii. The radii are +perpendicular to the corresponding facet. Vertices are aligned +with an interior point. The radii define a ball which corresponds +to the imprecision of the point. The imprecision is the maximum +of the roundoff error, the centrum radius, and maxcoord * (1 - +A-n). It is at +least 1/20'th of the maximum coordinate, and ignores post merging +if pre-merging is done.

    + +

    If 'Gv' (print vertices as +spheres) is also selected, option 'Gp' displays coplanar +points as radii. Select options Qc' +and/or 'Qi'. Options 'Qc Gpv' displays +coplanar points while 'Qci Gpv' displays coplanar and interior +points. Option 'Qc' is automatically selected if 'Qi' is not +selected with options 'Gpv'. + +

    »Gr - display ridges +(3-d)

    + +

    A ridge connects the two vertices that are shared by +neighboring facets. It is displayed in green. A ridge is the +topological edge between two facets while the hyperplane +intersection is the geometric edge between two facets. Ridges are +always displayed in 4-d.

    + +

    »Gt - transparent 3-d +Delaunay

    + +

    A 3-d Delaunay triangulation looks like a convex hull with +interior facets. Option 'Gt' removes the outside ridges to reveal +the outermost facets. It automatically sets options 'Gr' and 'GDn'. See example eg.17f.delaunay.3.

    + +

    »Gv - display vertices +as spheres (2-d, 3-d)

    + +

    The radius of the sphere corresponds to the imprecision of the +data. See 'Gp' for determining the radius.

    + + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-opto.htm b/xs/src/qhull/html/qh-opto.htm new file mode 100644 index 0000000000..e7b21745c1 --- /dev/null +++ b/xs/src/qhull/html/qh-opto.htm @@ -0,0 +1,353 @@ + + + + +Qhull output options + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull output options

    + +

    This section lists the output options for Qhull. These options +are indicated by lower case characters. See Formats, Print, and Geomview for other output +options.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Output options

    + +

    Qhull prints its output to standard out. All output is printed +text. The default output is a summary (option 's'). +Other outputs may be specified as follows.

    + +
    +
    f
    +
    print all fields of all facets
    +
    n
    +
    print hyperplane normals with offsets
    +
    m
    +
    print Mathematica output (2-d and 3-d)
    +
    o
    +
    print OFF file format (dim, points and facets)
    +
    s
    +
    print summary to stderr
    +
    p
    +
    print vertex and point coordinates
    +
    i
    +
    print vertices incident to each facet
    +
     
    +
     
    +
    Related options
    +
    F
    +
    additional input/output formats
    +
    G
    +
    Geomview output
    +
    P
    +
    Print options
    +
    Ft
    +
    print triangulation with added points
    +
     
    +
    + +
    + +

    »f - print all fields of +all facets

    + +

    Print all fields of all facets. +The facet is the primary data structure for +Qhull. + +

    Option 'f' is for +debugging. Most of the fields are available via the 'F' options. If you need specialized +information from Qhull, you can use the Qhull library or C++ interface.

    + +

    Use the 'FF' option to print the +facets but not the ridges.

    + +

    »i - print vertices +incident to each facet

    + +

    The first line is the number of facets. The remaining lines +list the vertices for each facet, one facet per line. The indices +are 0-relative indices of the corresponding input points. The +facets are oriented. Option 'Fv' +displays an unoriented list of vertices with a vertex count per +line. Options 'o' and 'Ft' displays coordinates for each +vertex prior to the vertices for each facet.

    + +

    Simplicial facets (e.g., triangles in 3-d) consist of d +vertices. Non-simplicial facets in 3-d consist of 4 or more +vertices. For example, a facet of a cube consists of 4 vertices. +Use option 'Qt' to triangulate non-simplicial facets.

    + +

    For 4-d and higher convex hulls and 3-d and higher Delaunay +triangulations, d vertices are listed for all facets. A +non-simplicial facet is triangulated with its centrum and each +ridge. The index of the centrum is higher than any input point. +Use option 'Fv' to list the vertices +of non-simplicial facets as is. Use option 'Ft' to print the coordinates of the +centrums as well as those of the input points.

    + +

    »m - print Mathematica +output

    + +

    Qhull writes a Mathematica file for 2-d and 3-d convex hulls, +2-d and 3-d halfspace intersections, +and 2-d Delaunay triangulations. Qhull produces a list of +objects that you can assign to a variable in Mathematica, for +example: "list= << <outputfilename> ". +If the object is 2-d, it can be visualized by "Show[Graphics[list]] +". For 3-d objects the command is "Show[Graphics3D[list]] +". Now the object can be manipulated by commands of the +form "Show[%, <parametername> -> +<newvalue>]".

    + +

    For Delaunay triangulation orthogonal projection is better. +This can be specified, for example, by "BoxRatios: +Show[%, BoxRatios -> {1, 1, 1e-8}]". To see the +meaningful side of the 3-d object used to visualize 2-d Delaunay, +you need to change the viewpoint: "Show[%, ViewPoint +-> {0, 0, -1}]". By specifying different viewpoints +you can slowly rotate objects.

    + +

    For halfspace intersections, Qhull produces the dual +convex hull. + +

    See Is Qhull available for Mathematica? +for URLs. + +

    »n - print hyperplane +normals with offsets

    + +

    The first line is the dimension plus one. The second line is +the number of facets. The remaining lines are the normals for +each facet, one normal per line. The facet's offset follows its +normal coefficients.

    + +

    The normals point outward, i.e., the convex hull satisfies Ax +<= -b where A is the matrix of coefficients and b +is the vector of offsets.

    + +

    A point is inside or below a hyperplane if its distance +to the hyperplane is negative. A point is outside or above a hyperplane +if its distance to the hyperplane is positive. Otherwise a point is on or +coplanar to the hyperplane. + +

    If cdd output is specified ('FD'), +Qhull prints the command line, the keyword "begin", the +number of facets, the dimension (plus one), the keyword +"real", and the normals for each facet. The facet's +negative offset precedes its normal coefficients (i.e., if the +origin is an interior point, the offset is positive). Qhull ends +the output with the keyword "end".

    + +

    »o - print OFF file format +

    + +

    The output is:

    + +
      +
    • The first line is the dimension
    • +
    • The second line is the number of points, the number of + facets, and the number of ridges.
    • +
    • All of the input points follow, one per line.
    • +
    • Then Qhull prints the vertices for each facet. Each facet + is on a separate line. The first number is the number of + vertices. The remainder is the indices of the + corresponding points. The vertices are oriented in 2-d, + 3-d, and in simplicial facets.
    • +
    + +

    Option 'Ft' prints the same +information with added points for non-simplicial facets.

    + +

    Option 'i' displays vertices +without the point coordinates. Option 'p' +displays the point coordinates without vertex and facet information.

    + +

    In 3-d, Geomview can load the file directly if you delete the +first line (e.g., by piping through 'tail +2').

    + +

    For Voronoi diagrams (qvoronoi), option +'o' prints Voronoi vertices and Voronoi regions instead of input +points and facets. The first vertex is the infinity vertex +[-10.101, -10.101, ...]. Then, option 'o' lists the vertices in +the Voronoi region for each input site. The regions appear in +site ID order. In 2-d, the vertices of a Voronoi region are +sorted by adjacency (non-oriented). In 3-d and higher, the +Voronoi vertices are sorted by index. See the 'FN' option for listing Voronoi regions +without listing Voronoi vertices.

    + +

    If you are using the Qhull library, options 'v o' have the +side effect of reordering the neighbors for a vertex.

    + +

    »p - print vertex and +point coordinates

    + +

    The first line is the dimension. The second line is the number +of vertices. The remaining lines are the vertices, one vertex per +line. A vertex consists of its point coordinates

    + +

    With the 'Gc' and 'Gi' options, option 'p' also prints +coplanar and interior points respectively.

    + +

    For qvoronoi, it prints the +coordinates of each Voronoi vertex.

    + +

    For qdelaunay, it prints the +input sites as lifted to a paraboloid. For qhalf +it prints the dual points. For both, option 'p' is the same as the first +section of option 'o'.

    + +

    Use 'Fx' to list the point ids of +the extreme points (i.e., vertices).

    + +

    If a subset of the facets is selected ('Pdk', 'PDk', +'Pg' options), option 'p' only +prints vertices and points associated with those facets.

    + +

    If cdd-output format is selected ('FD'), +the first line is "begin". The second line is the +number of vertices, the dimension plus one, and "real". +The vertices follow with a leading "1". Output ends +with "end".

    + +

    »s - print summary to +stderr

    + +

    The default output of Qhull is a summary to stderr. Options 'FS' and 'Fs' +produce the same information for programs. Note: Windows 95 and 98 +treats stderr the same as stdout. Use option 'TO file' to separate +stderr and stdout.

    + +

    The summary lists the number of input points, the dimension, +the number of vertices in the convex hull, and the number of +facets in the convex hull. It lists the number of selected +("good") facets for options 'Pg', +'Pdk', qdelaunay, +or qvoronoi (Delaunay triangulations only +use the lower half of a convex hull). It lists the number of +coplanar points. For Delaunay triangulations without 'Qc', it lists the total number of +coplanar points. It lists the number of simplicial facets in +the output.

    + +

    The terminology depends on the output structure.

    + +

    The summary lists these statistics:

    + +
      +
    • number of points processed by Qhull
    • +
    • number of hyperplanes created
    • +
    • number of distance tests (not counting statistics, + summary, and checking)
    • +
    • number of merged facets (if any)
    • +
    • number of distance tests for merging (if any)
    • +
    • CPU seconds to compute the hull
    • +
    • the maximum joggle for 'QJ'
      + or, the probability of precision errors for 'QJ TRn' +
    • +
    • total area and volume (if computed, see 'FS' 'FA' + 'Fa' 'PAn')
    • +
    • max. distance of a point above a facet (if non-zero)
    • +
    • max. distance of a vertex below a facet (if non-zero)
    • +
    + +

    The statistics include intermediate hulls. For example 'rbox d +D4 | qhull' reports merged facets even though the final hull is +simplicial.

    + +

    Qhull starts counting CPU seconds after it has read and +projected the input points. It stops counting before producing +output. In the code, CPU seconds measures the execution time of +function qhull() in libqhull.c. If the number of CPU +seconds is clearly wrong, check qh_SECticks in user.h.

    + +

    The last two figures measure the maximum distance from a point +or vertex to a facet. They are not printed if less than roundoff +or if not merging. They account for roundoff error in computing +the distance (c.f., option 'Rn'). +Use 'Fs' to report the maximum outer +and inner plane.

    + +

    A number may appear in parentheses after the maximum distance +(e.g., 2.1x). It is the ratio between the maximum distance and +the worst-case distance due to merging two simplicial facets. It +should be small for 2-d, 3-d, and 4-d, and for higher dimensions +with 'Qx'. It is not printed if less +than 0.05.

    + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optp.htm b/xs/src/qhull/html/qh-optp.htm new file mode 100644 index 0000000000..9c6df90f5a --- /dev/null +++ b/xs/src/qhull/html/qh-optp.htm @@ -0,0 +1,253 @@ + + + + +Qhull print options (P) + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull print options (P)

    + +This section lists the print options for Qhull. These options are +indicated by 'P' followed by a letter. See +Output, Geomview, +and Format for other output options. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Print options

    +
    +
    +
     
    +
    General
    +
    Pp
    +
    do not report precision problems
    +
    Po
    +
    force output despite precision problems
    +
    Po
    +
    if error, output neighborhood of facet
    +
     
    +
     
    +
    Select
    +
    Pdk:n
    +
    print facets with normal[k] >= n (default 0.0)
    +
    PDk:n
    +
    print facets with normal[k] <= n
    +
    PFn
    +
    print facets whose area is at least n
    +
    Pg
    +
    print good facets only (needs 'QGn' + or 'QVn ')
    +
    PMn
    +
    print n facets with most merges
    +
    PAn
    +
    print n largest facets by area
    +
    PG
    +
    print neighbors of good facets
    +
    +
    +
    + +

    »PAn - keep n largest +facets by area

    + +

    The n largest facets are marked good for printing. This +may be useful for approximating +a hull. Unless 'PG' is set, 'Pg' +is automatically set.

    + +

    »Pdk:n - print facet if +normal[k] >= n

    + +

    For a given output, print only those facets with normal[k] >= n +and drop the others. For example, 'Pd0:0.5' prints facets with normal[0] +>= 0.5 . The default value of n is zero. For +example in 3-d, 'Pd0d1d2' prints facets in the positive octant. +

    +If no facets match, the closest facet is returned.

    +

    +On Windows 95, do not combine multiple options. A 'd' is considered +part of a number. For example, use 'Pd0:0.5 Pd1:0.5' instead of +'Pd0:0.5d1:0.5'. + +

    »PDk:n - print facet if +normal[k] <= n

    + +

    For a given output, print only those facets with normal[k] <= n +and drop the others. +For example, 'PD0:0.5' prints facets with normal[0] +<= 0.5 . The default value of n is zero. For +example in 3-d, 'PD0D1D2' displays facets in the negative octant. +

    +If no facets match, the closest facet is returned.

    + +

    In 2-d, 'd G PD2' displays the Delaunay triangulation instead +of the corresponding paraboloid.

    + +

    Be careful of placing 'Dk' or 'dk' immediately after a real +number. Some compilers treat the 'D' as a double precision +exponent.

    + +

    »PFn - keep facets whose +area is at least n

    + +

    The facets with area at least n are marked good for +printing. This may be useful for approximating a hull. Unless +'PG' is set, 'Pg' is +automatically set.

    + +

    »Pg - print good facets

    + +

    Qhull can mark facets as "good". This is used to

    + +
      +
    • mark the lower convex hull for Delaunay triangulations + and Voronoi diagrams
    • +
    • mark the facets that are visible from a point (the 'QGn ' option)
    • +
    • mark the facets that contain a point (the 'QVn' option).
    • +
    • indicate facets with a large enough area (options 'PAn' and 'PFn')
    • +
    + +

    Option 'Pg' only prints good facets that +also meet 'Pdk' and 'PDk' +options. It is automatically set for options 'PAn', +'PFn ', 'QGn', +and 'QVn'.

    + +

    »PG - print neighbors of +good facets

    + +

    Option 'PG' can be used with or without option 'Pg' +to print the neighbors of good facets. For example, options 'QGn' and 'QVn' +print the horizon facets for point n.

    + +

    »PMn - keep n facets with +most merges

    + +

    The n facets with the most merges are marked good for +printing. This may be useful for approximating a hull. Unless +'PG' is set, 'Pg' is +automatically set.

    + +

    Use option 'Fm' to print merges +per facet. + +

    »Po - force output despite +precision problems

    + +

    Use options 'Po' and 'Q0' if you +can not merge facets, triangulate the output ('Qt'), +or joggle the input (QJ). + +

    Option 'Po' can not force output when +duplicate ridges or duplicate facets occur. It may produce +erroneous results. For these reasons, merged facets, joggled input, or exact arithmetic are better.

    + +

    If you need a simplicial Delaunay triangulation, use +joggled input 'QJ' or triangulated +output 'Ft'. + +

    Option 'Po' may be used without 'Q0' +to remove some steps from Qhull or to output the neighborhood of +an error.

    + +

    Option 'Po' may be used with option 'Q5') +to skip qh_check_maxout (i.e., do not determine the maximum outside distance). +This can save a significant amount of time. + +

    If option 'Po' is used,

    + +
      +
    • most precision errors allow Qhull to continue.
    • +
    • verify ('Tv') does not check + coplanar points.
    • +
    • points are not partitioned into flipped facets and a + flipped facet is always visible to a point. This may + delete flipped facets from the output.
    • +
    + +

    »Po - if error, output +neighborhood of facet

    + +

    If an error occurs before the completion of Qhull and tracing +is not active, 'Po' outputs a neighborhood of the erroneous +facets (if any). It uses the current output options.

    + +

    See 'Po' - force output despite +precision problems. + +

    »Pp - do not report +precision problems

    + +

    With option 'Pp', Qhull does not print statistics about +precision problems, and it removes some of the warnings. It +removes the narrow hull warning.

    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optq.htm b/xs/src/qhull/html/qh-optq.htm new file mode 100644 index 0000000000..2edbb1fd43 --- /dev/null +++ b/xs/src/qhull/html/qh-optq.htm @@ -0,0 +1,731 @@ + + + + +Qhull control options (Q) + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull control options (Q)

    + +

    This section lists the control options for Qhull. These +options are indicated by 'Q' followed by a letter.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Qhull control options

    + +
    +
     
    +
    General
    +
    Qu
    +
    compute upper hull for furthest-site Delaunay + triangulation
    +
    Qc
    +
    keep coplanar points with nearest facet
    +
    Qi
    +
    keep interior points with nearest facet
    +
    QJ
    +
    joggled input to avoid precision problems
    +
    Qt
    +
    triangulated output
    +
     
    +
     
    +
    Precision handling
    +
    Qz
    +
    add a point-at-infinity for Delaunay triangulations
    +
    Qx
    +
    exact pre-merges (allows coplanar facets)
    +
    Qs
    +
    search all points for the initial simplex
    +
    Qbb
    +
    scale last coordinate to [0,m] for Delaunay
    +
    Qv
    +
    test vertex neighbors for convexity
    +
     
    +
     
    +
    Transform input
    +
    Qbk:0Bk:0
    +
    drop dimension k from input
    +
    QRn
    +
    random rotation (n=seed, n=0 time, n=-1 time/no rotate)
    +
    Qbk:n
    +
    scale coord[k] to low bound of n (default -0.5)
    +
    QBk:n
    +
    scale coord[k] to upper bound of n (default 0.5)
    +
    QbB
    +
    scale input to fit the unit cube
    +
     
    +
     
    +
    Select facets
    +
    QVn
    +
    good facet if it includes point n, -n if not
    +
    QGn
    +
    good facet if visible from point n, -n for not visible
    +
    Qg
    +
    only build good facets (needs 'QGn', 'QVn ', or 'Pdk')
    +
     
    +
     
    +
    Experimental
    +
    Q4
    +
    avoid merging old facets into new facets
    +
    Q5
    +
    do not correct outer planes at end of qhull
    +
    Q3
    +
    do not merge redundant vertices
    +
    Q6
    +
    do not pre-merge concave or coplanar facets
    +
    Q0
    +
    do not pre-merge facets with 'C-0' or 'Qx'
    +
    Q8
    +
    ignore near-interior points
    +
    Q2
    +
    merge all non-convex at once instead of independent sets
    +
    Qf
    +
    partition point to furthest outside facet
    +
    Q7
    +
    process facets depth-first instead of breadth-first
    +
    Q9
    +
    process furthest of furthest points
    +
    Q10
    +
    no special processing for narrow distributions
    +
    Q11
    +
    copy normals and recompute centrums for tricoplanar facets
    +
    Q12
    +
    do not error on wide merge due to duplicate ridge and nearly coincident points
    +
    Qm
    +
    process points only if they would increase the max. outer + plane
    +
    Qr
    +
    process random outside points instead of furthest one
    +
    Q1
    +
    sort merges by type instead of angle
    +
    + +
    + +

    »Qbb - scale the last +coordinate to [0,m] for Delaunay

    + +

    After scaling with option 'Qbb', the lower bound of the last +coordinate will be 0 and the upper bound will be the maximum +width of the other coordinates. Scaling happens after projecting +the points to a paraboloid and scaling other coordinates.

    + +

    Option 'Qbb' is automatically set for qdelaunay +and qvoronoi. Option 'Qbb' is automatically set for joggled input 'QJ'.

    + +

    Option 'Qbb' should be used for Delaunay triangulations with +integer coordinates. Since the last coordinate is the sum of +squares, it may be much larger than the other coordinates. For +example, rbox 10000 D2 B1e8 | qhull d has precision +problems while rbox 10000 D2 B1e8 | qhull d Qbb is OK.

    + +

    »QbB - scale the input to +fit the unit cube

    + +

    After scaling with option 'QbB', the lower bound will be -0.5 +and the upper bound +0.5 in all dimensions. For different bounds +change qh_DEFAULTbox in user.h (0.5 is best for Geomview).

    + +

    For Delaunay and Voronoi diagrams, scaling happens after +projection to the paraboloid. Under precise arithmetic, scaling +does not change the topology of the convex hull. Scaling may +reduce precision errors if coordinate values vary widely.

    + +

    »Qbk:n - scale coord[k] +to low bound

    + +

    After scaling, the lower bound for dimension k of the input +points will be n. 'Qbk' scales coord[k] to -0.5.

    + +

    »QBk:n - scale coord[k] +to upper bound

    + +

    After scaling, the upper bound for dimension k of the input +points will be n. 'QBk' scales coord[k] to 0.5.

    + +

    »Qbk:0Bk:0 - drop +dimension k from the input points

    + +

    Drop dimension k from the input points. For example, +'Qb1:0B1:0' deletes the y-coordinate from all input points. This +allows the user to take convex hulls of sub-dimensional objects. +It happens before the Delaunay and Voronoi transformation. +It happens after the halfspace transformation for both the data +and the feasible point.

    + +

    »Qc - keep coplanar points +with nearest facet

    + +

    During construction of the hull, a point is coplanar if it is +between 'Wn' above and 'Un' below a facet's hyperplane. A +different definition is used for output from Qhull.

    + +

    For output, a coplanar point is above the minimum vertex +(i.e., above the inner plane). With joggle ('QJ'), a coplanar point includes points +within one joggle of the inner plane.

    + +

    With option 'Qc', output formats 'p ', +'f', 'Gp', +'Fc', 'FN', +and 'FP' will print the coplanar +points. With options 'Qc Qi' these outputs +include the interior points.

    + +

    For Delaunay triangulations (qdelaunay +or qvoronoi), a coplanar point is a point +that is nearly incident to a vertex. All input points are either +vertices of the triangulation or coplanar.

    + +

    Qhull stores coplanar points with a facet. While constructing +the hull, it retains all points within qh_RATIOnearInside +(user.h) of a facet. In qh_check_maxout(), it uses these points +to determine the outer plane for each facet. With option 'Qc', +qh_check_maxout() retains points above the minimum vertex for the +hull. Other points are removed. If qh_RATIOnearInside is wrong or +if options 'Q5 Q8' are set, a +coplanar point may be missed in the output (see Qhull limitations).

    + +

    »Qf - partition point to +furthest outside facet

    + +

    After adding a new point to the convex hull, Qhull partitions +the outside points and coplanar points of the old, visible +facets. Without the 'f ' option and +merging, it assigns a point to the first facet that it is outside +('Wn'). When merging, it assigns a +point to the first facet that is more than several times outside +(see qh_DISToutside in user.h).

    + +

    If option 'Qf' is selected, Qhull performs a directed search +(no merging) or an exhaustive search (merging) of new facets. +Option 'Qf' may reduce precision errors if pre-merging does not +occur.

    + +

    Option 'Q9' processes the furthest of all +furthest points.

    + +

    »Qg - only build good +facets (needs 'QGn' 'QVn' or 'Pdk')

    + +

    Qhull has several options for defining and printing good +facets. With the 'Qg' option, Qhull will only +build those facets that it needs to determine the good facets in +the output. This may speed up Qhull in 2-d and 3-d. It is +useful for furthest-site Delaunay +triangulations (qdelaunay Qu, +invoke with 'qhull d Qbb Qu Qg'). +It is not effective in higher +dimensions because many facets see a given point and contain a +given vertex. It is not guaranteed to work for all combinations.

    + +

    See 'QGn', 'QVn', and 'Pdk' for defining good facets, and 'Pg' and 'PG' +for printing good facets and their neighbors. If pre-merging ('C-n') is not used and there are +coplanar facets, then 'Qg Pg' may produce a different result than +'Pg'.

    + +

    »QGn - good facet if +visible from point n, -n for not visible

    + +

    With option 'QGn', a facet is good (see 'Qg' +and 'Pg') if it is visible from +point n. If n < 0, a facet is good if it is not visible +from point n. Point n is not added to the hull (unless 'TCn' or 'TPn').

    + +

    With rbox, use the 'Pn,m,r' option +to define your point; it will be point 0 ('QG0').

    + +

    »Qi - keep interior points +with nearest facet

    + +

    Normally Qhull ignores points that are clearly interior to the +convex hull. With option 'Qi', Qhull treats interior points the +same as coplanar points. Option 'Qi' does not retain coplanar +points. You will probably want 'Qc ' as well.

    + +

    Option 'Qi' is automatically set for 'qdelaunay +Qc' and 'qvoronoi +Qc'. If you use +'qdelaunay Qi' or 'qvoronoi +Qi', option 's' reports all nearly +incident points while option 'Fs' +reports the number of interior points (should always be zero).

    + +

    With option 'Qi', output formats 'p', +'f','Gp', +'Fc', 'FN', +and 'FP' include interior points.

    + +

    »QJ or QJn - joggled +input to avoid precision errors

    + +

    Option 'QJ' or 'QJn' joggles each input coordinate by adding a +random number in the range [-n,n]. If a precision error occurs, +It tries again. If precision errors still occur, Qhull increases n +ten-fold and tries again. The maximum value for increasing n +is 0.01 times the maximum width of the input. Option 'QJ' selects +a default value for n. User.h +defines these parameters and a maximum number of retries. See Merged facets or joggled input.

    + +

    Users of joggled input should consider converting to +triangulated output ('Qt'). Triangulated output is +approximately 1000 times more accurate than joggled input. + +

    Option 'QJ' also sets 'Qbb' for +Delaunay triangulations and Voronoi diagrams. It does not set +'Qbb' if 'Qbk:n' or 'QBk:n' are set.

    + +

    If 'QJn' is set, Qhull does not merge facets unless requested +to. All facets are simplicial (triangular in 2-d). This may be +important for your application. You may also use triangulated output +('Qt') or Option 'Ft'. + +

    Qhull adjusts the outer and inner planes for 'QJn' ('Fs'). They are increased by sqrt(d)*n +to account for the maximum distance between a joggled point and +the corresponding input point. Coplanar points ('Qc') require an additional sqrt(d)*n +since vertices and coplanar points may be joggled in opposite +directions.

    + +

    For Delaunay triangulations (qdelaunay), joggle +happens before lifting the input sites to a paraboloid. Instead of +'QJ', you may use triangulated output ('Qt')

    + +

    This option is deprecated for Voronoi diagrams (qvoronoi). +It triangulates cospherical points, leading to duplicated Voronoi vertices.

    + +

    By default, 'QJn' uses a fixed random number seed. To use time +as the random number seed, select 'QR-1'. +The summary ('s') will show the +selected seed as 'QR-n'. + +

    With 'QJn', Qhull does not error on degenerate hyperplane +computations. Except for Delaunay and Voronoi computations, Qhull +does not error on coplanar points.

    + +

    Use option 'FO' to display the +selected options. Option 'FO' displays the joggle and the joggle +seed. If Qhull restarts, it will redisplay the options.

    + +

    Use option 'TRn' to estimate the +probability that Qhull will fail for a given 'QJn'. + +

    »Qm - only process points +that increase the maximum outer plane

    + +

    Qhull reports the maximum outer plane in its summary ('s'). With option 'Qm', Qhull does not +process points that are below the current, maximum outer plane. +This is equivalent to always adjusting 'Wn +' to the maximum distance of a coplanar point to a facet. It +is ignored for points above the upper convex hull of a Delaunay +triangulation. Option 'Qm' is no longer important for merging.

    + +

    »Qr - process random +outside points instead of furthest ones

    + +

    Normally, Qhull processes the furthest point of a facet's +outside points. Option 'Qr' instead selects a random outside +point for processing. This makes Qhull equivalent to the +randomized incremental algorithms.

    + +

    The original randomization algorithm by Clarkson and Shor ['89] used a conflict list which +is equivalent to Qhull's outside set. Later randomized algorithms +retained the previously constructed facets.

    + +

    To compare Qhull to the randomized algorithms with option +'Qr', compare "hyperplanes constructed" and +"distance tests". Qhull does not report CPU time +because the randomization is inefficient.

    + +

    »QRn - random rotation

    + +

    Option 'QRn' randomly rotates the input. For Delaunay +triangulations (qdelaunay or qvoronoi), +it rotates the lifted points about +the last axis.

    + +

    If n=0, use time as the random number seed. If n>0, +use n as the random number seed. If n=-1, don't rotate +but use time as the random number seed. If n<-1, +don't rotate but use n as the random number seed.

    + +

    »Qs - search all points +for the initial simplex

    + +

    Qhull constructs an initial simplex from d+1 points. It +selects points with the maximum and minimum coordinates and +non-zero determinants. If this fails, it searches all other +points. In 8-d and higher, Qhull selects points with the minimum +x or maximum coordinate (see qh_initialvertices in poly2.c ). +It rejects points with nearly zero determinants. This should work +for almost all input sets.

    + +

    If Qhull can not construct an initial simplex, it reports a +descriptive message. Usually, the point set is degenerate and one +or more dimensions should be removed ('Qbk:0Bk:0'). +If not, use option 'Qs'. It performs an exhaustive search for the +best initial simplex. This is expensive is high dimensions.

    + +

    »Qt - triangulated output

    + +

    By default, qhull merges facets to handle precision errors. This +produces non-simplicial facets (e.g., the convex hull of a cube has 6 square +facets). Each facet is non-simplicial because it has four vertices. + +

    Use option 'Qt' to triangulate all non-simplicial facets before generating +results. Alternatively, use joggled input ('QJ') to +prevent non-simplical facets. Unless 'Pp' is set, +qhull produces a warning if 'QJ' and 'Qt' are used together. + +

    For Delaunay triangulations (qdelaunay), triangulation +occurs after lifting the input sites to a paraboloid and computing the convex hull. +

    + +

    Option 'Qt' is deprecated for Voronoi diagrams (qvoronoi). +It triangulates cospherical points, leading to duplicated Voronoi vertices.

    + +

    Option 'Qt' may produce degenerate facets with zero area.

    + +

    Facet area and hull volumes may differ with and without +'Qt'. The triangulations are different and different triangles +may be ignored due to precision errors. + +

    With sufficient merging, the ridges of a non-simplicial facet may share more than two neighboring facets. If so, their triangulation ('Qt') will fail since two facets have the same vertex set.

    + +

    »Qu - compute upper hull +for furthest-site Delaunay triangulation

    + +

    When computing a Delaunay triangulation (qdelaunay +or qvoronoi), +Qhull computes both the the convex hull of points on a +paraboloid. It normally prints facets of the lower hull. These +correspond to the Delaunay triangulation. With option 'Qu', Qhull +prints facets of the upper hull. These correspond to the furthest-site Delaunay triangulation +and the furthest-site Voronoi diagram.

    + +

    Option 'qhull d Qbb Qu Qg' may improve the speed of option +'Qu'. If you use the Qhull library, a faster method is 1) use +Qhull to compute the convex hull of the input sites; 2) take the +extreme points (vertices) of the convex hull; 3) add one interior +point (e.g., +'FV', the average of d extreme points); 4) run +'qhull d Qbb Qu' or 'qhull v Qbb Qu' on these points.

    + +

    »Qv - test vertex +neighbors for convexity

    + +

    Normally, Qhull tests all facet neighbors for convexity. +Non-neighboring facets which share a vertex may not satisfy the +convexity constraint. This occurs when a facet undercuts the +centrum of another facet. They should still be convex. Option +'Qv' extends Qhull's convexity testing to all neighboring facets +of each vertex. The extra testing occurs after the hull is +constructed..

    + +

    »QVn - good facet if it +includes point n, -n if not

    + +

    With option 'QVn', a facet is good ('Qg', +'Pg') if one of its vertices is +point n. If n<0, a good facet does not include point n. + +

    If options 'PG' +and 'Qg' are not set, option 'Pg' +(print only good) +is automatically set. +

    + +

    Option 'QVn' behaves oddly with options 'Fx' +and 'qvoronoi Fv'. + +

    If used with option 'Qg' (only process good facets), point n is +either in the initial simplex or it is the first +point added to the hull. Options 'QVn Qg' require either 'QJ' or +'Q0' (no merging).

    + +

    »Qx - exact pre-merges +(allows coplanar facets)

    + +

    Option 'Qx' performs exact merges while building the hull. +Option 'Qx' is set by default in 5-d and higher. Use option 'Q0' to not use 'Qx' by default. Unless otherwise +specified, option 'Qx' sets option 'C-0'. +

    + +

    The "exact" merges are merging a point into a +coplanar facet (defined by 'Vn ', 'Un', and 'C-n'), +merging concave facets, merging duplicate ridges, and merging +flipped facets. Coplanar merges and angle coplanar merges ('A-n') are not performed. Concavity +testing is delayed until a merge occurs.

    + +

    After the hull is built, all coplanar merges are performed +(defined by 'C-n' and 'A-n'), then post-merges are performed +(defined by 'Cn' and 'An'). If facet progress is logged ('TFn'), Qhull reports each phase and +prints intermediate summaries and statistics ('Ts').

    + +

    Without 'Qx' in 5-d and higher, options 'C-n' and 'A-n' +may merge too many facets. Since redundant vertices are not +removed effectively, facets become increasingly wide.

    + +

    Option 'Qx' may report a wide facet. With 'Qx', coplanar +facets are not merged. This can produce a "dent" in an +intermediate hull. If a point is partitioned into a dent and it +is below the surrounding facets but above other facets, one or +more wide facets will occur. In practice, this is unlikely. To +observe this effect, run Qhull with option 'Q6' +which doesn't pre-merge concave facets. A concave facet makes a +large dent in the intermediate hull.

    + +

    Option 'Qx' may set an outer plane below one of the input +points. A coplanar point may be assigned to the wrong facet +because of a "dent" in an intermediate hull. After +constructing the hull, Qhull double checks all outer planes with +qh_check_maxout in poly2.c . If a coplanar point is +assigned to the wrong facet, qh_check_maxout may reach a local +maximum instead of locating all coplanar facets. This appears to +be unlikely.

    + +

    »Qz - add a +point-at-infinity for Delaunay triangulations

    + +

    Option 'Qz' adds a point above the paraboloid of lifted sites +for a Delaunay triangulation. It allows the Delaunay +triangulation of cospherical sites. It reduces precision errors +for nearly cospherical sites.

    + +

    »Q0 - no merging with C-0 +and Qx

    + +

    Turn off default merge options 'C-0' +and 'Qx'.

    + +

    With 'Q0' and without other pre-merge options, Qhull ignores +precision issues while constructing the convex hull. This may +lead to precision errors. If so, a descriptive warning is +generated. See Precision issues.

    + +

    »Q1 - sort merges by type +instead of angle

    + +

    Qhull sorts the coplanar facets before picking a subset of the +facets to merge. It merges concave and flipped facets first. Then +it merges facets that meet at a steep angle. With 'Q1', Qhull +sorts merges by type (coplanar, angle coplanar, concave) instead +of by angle. This may make the facets wider.

    + +

    »Q2 - merge all non-convex +at once instead of independent sets

    + +

    With 'Q2', Qhull merges all facets at once instead of +performing merges in independent sets. This may make the facets +wider.

    + +

    »Q3 - do not merge +redundant vertices

    + +

    With 'Q3', Qhull does not remove redundant vertices. In 6-d +and higher, Qhull never removes redundant vertices (since +vertices are highly interconnected). Option 'Q3' may be faster, +but it may result in wider facets. Its effect is easiest to see +in 3-d and 4-d.

    + +

    »Q4 - avoid merging old +facets into new facets

    + +

    With 'Q4', Qhull avoids merges of an old facet into a new +facet. This sometimes improves facet width and sometimes makes it +worse.

    + +

    »Q5 - do not correct outer +planes at end of qhull

    + +

    When merging facets or approximating a hull, Qhull tests +coplanar points and outer planes after constructing the hull. It +does this by performing a directed search (qh_findbest in geom.c). +It includes points that are just inside the hull.

    + +

    With options 'Q5' or 'Po', Qhull +does not test outer planes. The maximum outer plane is used +instead. Coplanar points ('Qc') are defined by +'Un'. An input point may be outside +of the maximum outer plane (this appears to be unlikely). An +interior point may be above 'Un' +from a hyperplane.

    + +

    Option 'Q5' may be used if outer planes are not needed. Outer +planes are needed for options 's', 'G', 'Go ', +'Fs', 'Fo', +'FF', and 'f'.

    + +

    »Q6 - do not pre-merge +concave or coplanar facets

    + +

    With 'Q6', Qhull does not pre-merge concave or coplanar +facets. This demonstrates the effect of "dents" when +using 'Qx'.

    + +

    »Q7 - depth-first +processing instead of breadth-first

    + +

    With 'Q7', Qhull processes facets in depth-first order instead +of breadth-first order. This may increase the locality of +reference in low dimensions. If so, Qhull may be able to use +virtual memory effectively.

    + +

    In 5-d and higher, many facets are visible from each +unprocessed point. So each iteration may access a large +proportion of allocated memory. This makes virtual memory +ineffectual. Once real memory is used up, Qhull will spend most +of its time waiting for I/O.

    + +

    Under 'Q7', Qhull runs slower and the facets may be wider.

    + +

    »Q8 - ignore near-interior +points

    + +

    With 'Q8' and merging, Qhull does not process interior points +that are near to a facet (as defined by qh_RATIOnearInside in +user.h). This avoids partitioning steps. It may miss a coplanar +point when adjusting outer hulls in qh_check_maxout(). The best +value for qh_RATIOnearInside is not known. Options 'Q8 Qc' may be sufficient.

    + +

    »Q9 - process furthest of +furthest points

    + +

    With 'Q9', Qhull processes the furthest point of all outside +sets. This may reduce precision problems. The furthest point of +all outside sets is not necessarily the furthest point from the +convex hull.

    + +

    »Q10 - no special processing +for narrow distributions

    + +

    With 'Q10', Qhull does not special-case narrow distributions. +See Limitations of merged facets for +more information. + +

    »Q11 - copy normals and recompute +centrums for +tricoplanar facets

    + +Option 'Qt' triangulates non-simplicial facets +into "tricoplanar" facets. +Normally tricoplanar facets share the same normal, centrum, and +Voronoi vertex. They can not be merged or replaced. With +option 'Q11', Qhull duplicates the normal and Voronoi vertex. +It recomputes the centrum. + +

    Use 'Q11' if you use the Qhull library to add points +incrementally and call qh_triangulate() after each point. +Otherwise, Qhull will report an error when it tries to +merge and replace a tricoplanar facet. + +

    With sufficient merging and new points, option 'Q11' may +lead to precision problems such +as duplicate ridges and concave facets. For example, if qh_triangulate() +is added to qh_addpoint(), RBOX 1000 s W1e-12 t1001813667 P0 | QHULL d Q11 Tv, +reports an error due to a duplicate ridge. + +

    »Q12 - do not error +on wide merge due to duplicate ridge and nearly coincident points

    + +

    In 3-d and higher Delaunay Triangulations or 4-d and higher convex hulls, multiple, +nearly coincident points may lead to very wide facets. An error is reported if a +merge across a duplicate ridge would increase the facet width by 100x or more. + +

    Use option 'Q12' to log a warning instead of throwing an error. + +

    For Delaunay triangulations, a bounding box may alleviate this error (e.g., rbox 500 C1,1E-13 t c G1 | qhull d). +This avoids the ill-defined edge between upper and lower convex hulls. +The problem will be fixed in a future release of Qhull. + +

    To demonstrate the problem, use rbox option 'Cn,r,m' to generate nearly coincident points. +For more information, see "Nearly coincident points on an edge" +in Nearly coincident points on an edge. + + +


    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optt.htm b/xs/src/qhull/html/qh-optt.htm new file mode 100644 index 0000000000..0709f58c66 --- /dev/null +++ b/xs/src/qhull/html/qh-optt.htm @@ -0,0 +1,278 @@ + + + + +Qhull trace options (T) + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull trace options (T)

    + +This section lists the trace options for Qhull. These options are +indicated by 'T' followed by a letter. + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Trace options

    + +
    +
     
    +
    General
    +
    Tz
    +
    output error information to stdout instead of stderr
    +
    TI file
    +
    input data from a file
    +
    TO file
    +
    output results to a file
    +
    Ts
    +
    print statistics
    +
    TFn
    +
    report progress whenever n or more facets created
    +
    TRn
    +
    rerun qhull n times
    +
    Tv
    +
    verify result: structure, convexity, and point inclusion
    + +
     
    +
     
    +
    Debugging
    +
    Tc
    +
    check frequently during execution
    +
    TVn
    +
    stop qhull after adding point n
    +
    TCn
    +
    stop qhull after building cone for point n
    +
    TV-n
    +
    stop qhull before adding point n
    +
    T4
    +
    trace at level n, 4=all, 5=mem/gauss, -1= events
    +
    TWn
    +
    trace merge facets when width > n
    +
    TMn
    +
    turn on tracing at merge n
    +
    TPn
    +
    turn on tracing when point n added to hull
    +
    + +
    + +

    »Tc - check frequently +during execution

    + +

    Qhull includes frequent checks of its data structures. Option +'Tc' will catch most inconsistency errors. It is slow and should +not be used for production runs. Option 'Tv' +performs the same checks after the hull is constructed.

    + +

    »TCn - stop qhull after +building cone for point n

    + +

    Qhull builds a cone from the point to its horizon facets. +Option 'TCn' stops Qhull just after building the cone. The output +for 'f' includes the cone and the old +hull.'.

    + +

    »TFn - report summary +whenever n or more facets created

    + +

    Option 'TFn' reports progress whenever more than n facets are +created. The test occurs just before adding a new point to the +hull. During post-merging, 'TFn' reports progress after more than +n/2 merges.

    + +

    »TI file - input data from file

    + +

    Input data from 'file' instead of stdin. The filename may not +contain spaces or use single quotes. +You may use I/O redirection +instead (e.g., 'rbox 10 | qdelaunay >results').

    + +

    »TMn - turn on tracing at +merge n

    + +

    Turn on tracing at n'th merge.

    + +

    »Tn - trace at level n

    + +

    Qhull includes full execution tracing. 'T-1' traces events. +'T1' traces the overall execution of the program. 'T2' and 'T3' +trace overall execution and geometric and topological events. +'T4' traces the algorithm. 'T5' includes information about memory +allocation and Gaussian elimination. 'T1' is useful for logging +progress of Qhull in high dimensions.

    + +

    Option 'Tn' can produce large amounts of output. Use options 'TPn', 'TWn', and 'TMn' to selectively +turn on tracing. Since all errors report the last processed +point, option 'TPn' is particularly useful.

    + +

    Different executions of the same program may produce different +traces and different results. The reason is that Qhull uses hashing +to match ridges of non-simplicial facets. For performance reasons, +the hash computation uses +memory addresses which may change across executions. + +

    »TO file - output results to file

    + +

    Redirect stdout to 'file'. The filename may be enclosed in +single quotes. Unix and Windows NT users may use I/O redirection +instead (e.g., 'rbox 10 | qdelaunay >results').

    +

    +Windows95 users should always use 'TO file'. If they use I/O redirection, +error output is not sent to the console. Qhull uses single quotes instead +of double quotes because a missing double quote can +freeze Windows95 (e.g., do not run, rbox 10 | qhull TO "x)

    +

    + +

    »TPn - turn on tracing +when point n added to hull

    + +

    Option 'TPn' turns on tracing when point n is added to +the hull. It also traces partitions of point n. This option +reduces the output size when tracing. It is the normal +method to determine the cause of a Qhull error. All Qhull errors +report the last point added. + +

    Use options 'TPn TVn' to +trace the addition of point n to the convex hull and stop when done.

    + +

    If used with option 'TWn', +'TPn' turns off tracing after adding point n to the hull. +Use options 'TPn TWn' to +trace the addition of point n to the convex hull, partitions +of point n, and wide merges.

    + +

    »TRn - rerun qhull n times

    + +

    Option 'TRn' reruns Qhull n times. It is usually used +with 'QJn' to determine the probability +that a given joggle will fail. The summary +('s') lists the failure +rate and the precision errors that occurred. +Option 'Ts' will report statistics for +all of the runs. Trace and output options only apply to the last +run. An event trace, 'T-1' reports events for all runs. + +

    Tracing applies to the last run of Qhull. If an error +is reported, the options list the run number as "_run". +To trace this run, set 'TRn' to the same value.

    + +

    »Ts - print statistics

    + +

    Option 'Ts' collects statistics and prints them to stderr. For +Delaunay triangulations, the angle statistics are restricted to +the lower or upper envelope.

    + +

    »Tv - verify result: +structure, convexity, and point inclusion

    + +

    Option 'Tv' checks the topological structure, convexity, and +point inclusion. If precision problems occurred, facet convexity +is tested whether or not 'Tv' is selected. Option 'Tv' does not +check point inclusion if forcing output with 'Po', or if 'Q5' +is set.

    + +

    The convex hull of a set of points is the smallest polytope +that includes the points. Option 'Tv' tests point inclusion. +Qhull verifies that all points are below all outer planes +(facet->maxoutside). Point inclusion is exhaustive if merging +or if the facet-point product is small enough; otherwise Qhull +verifies each point with a directed search (qh_findbest). To +force an exhaustive test when using option 'C-0' (default), use 'C-1e-30' instead.

    + +

    Point inclusion testing occurs after producing output. It +prints a message to stderr unless option 'Pp' is used. This allows the user to +interrupt Qhull without changing the output.

    + +

    With 'qvoronoi Fi' +and 'qvoronoi Fo', +option 'Tv' collects statistics that verify all Voronoi vertices lie +on the separating hyperplane, and for bounded regions, all +separating hyperplanes are perpendicular bisectors. + +

    »TV-n - stop qhull before +adding point n

    + +

    Qhull adds one point at a time to the convex hull. See how Qhull adds a point. Option 'TV-n' +stops Qhull just before adding a new point. Output shows the hull +at this time.

    + +

    »TVn - stop qhull after +adding point n

    + +

    Option 'TVn' stops Qhull after it has added point n. Output +shows the hull at this time.

    + +

    »TWn - trace merge facets +when width > n

    + +

    Along with TMn, this option allows the user to determine the +cause of a wide merge.

    +

    »Tz - send all output to +stdout

    + +

    Redirect stderr to stdout.

    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-quick.htm b/xs/src/qhull/html/qh-quick.htm new file mode 100644 index 0000000000..9d52e7d750 --- /dev/null +++ b/xs/src/qhull/html/qh-quick.htm @@ -0,0 +1,495 @@ + + + + +Qhull quick reference + + + + +

    Up: Home +page for Qhull
    +Up: Qhull manual
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull internals
    +To: Qhull functions, macros, and data structures
    +To: Qhull files
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + +

    [cone] Qhull quick reference

    + +This section lists all programs and options in Qhull. + +

    Copyright © 1995-2015 C.B. Barber

    + +

    +  +


    +Qhull programs +

    » Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    +
    qconvex -- convex hull
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    qdelaunay -- Delaunay triangulation
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    qdelaunay Qu -- furthest-site Delaunay triangulation
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    qhalf -- halfspace intersection about a point
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    qvoronoi -- Voronoi diagram
    +
    synopsis • input • outputs • + controls • graphics • notes • conventions • options
    +
     
    +
    qvoronoi Qu -- furthest-site Voronoi diagram
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    rbox -- generate point distributions for qhull
    +
    synopsis • outputs • examples • notes • options
    +
     
    +
    qhull -- convex hull and related structures
    +
    synopsis • input • outputs • controls • options
    +
    +  +
    +Qhull options + +

    » Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    'Fa' +Farea +'FA' +FArea-total +'Fc' +Fcoplanars +'FC' +FCentrums + +
    'Fd' +Fd-cdd-in +'FD' +FD-cdd-out +'FF' +FF-dump-xridge +'Fi' +Finner + +
    'Fi' +Finner_bounded +'FI' +FIDs +'Fm' +Fmerges +'FM' +FMaple + +
    'Fn' +Fneighbors +'FN' +FNeigh-vertex +'Fo' +Fouter +'Fo' +Fouter_unbounded + +
    'FO' +FOptions +'Fp' +Fpoint-intersect +'FP' +FPoint_near +'FQ' +FQhull + +
    'Fs' +Fsummary +'FS' +FSize +'Ft' +Ftriangles +'Fv' +Fvertices + +
    'Fv' +Fvoronoi +'FV' +FVertex-ave +'Fx' +Fxtremes + +Merged facets or joggled input + +
     
    'PAn' +PArea-keep +'Pdk:n' +Pdrop_low +'PDk:n' +Pdrop_high +'Pg' +Pgood + +
    'PFn' +PFacet_area_keep +'PG' +PGood_neighbors +'PMn' +PMerge-keep +'Po' +Poutput_forced + +
    'Po' +Poutput_error +'Pp' +Pprecision_not + +
     
    'd' +delaunay +'v' +voronoi +'G' +Geomview +'H' +Halfspace + +
    'f' +facet_dump +'i' +incidences +'m' +mathematica +'n' +normals + +
    'o' +OFF_format +'p' +points +'s' +summary + +
     
    'Gv' +Gvertices +'Gp' +Gpoints +'Ga' +Gall_points +'Gn' +Gno_planes + +
    'Gi' +Ginner +'Gc' +Gcentrums +'Gh' +Ghyperplanes +'Gr' +Gridges + +
    'Go' +Gouter +'GDn' +GDrop_dim +'Gt' +Gtransparent + +
     
    'T4' +T4_trace +'Tc' +Tcheck_often +'Ts' +Tstatistics +'Tv' +Tverify + +
    'Tz' +Tz_stdout +'TFn' +TFacet_log +'TI file' +TInput_file +'TPn' +TPoint_trace + +
    'TMn' +TMerge_trace +'TO file' +TOutput_file +'TRn' +TRerun +'TWn' +TWide_trace + +
    'TV-n' +TVertex_stop_before +
    'TVn' +TVertex_stop_after +'TCn' +TCone_stop_after + +
     
    'A-n' +Angle_max_pre +'An' +Angle_max_post +'C-0' +Centrum_roundoff +'C-n' +Centrum_size_pre + +
    'Cn' +Centrum_size_post +'En' +Error_round +'Rn' +Random_dist +'Vn' +Visible_min + +
    'Un' +Ucoplanar_max +'Wn' +Wide_outside + +
     
    'Qbk:n' +Qbound_low +'QBk:n' +QBound_high +'Qbk:0Bk:0' +Qbound_drop +'QbB' +QbB-scale-box + +
    'Qbb' +Qbb-scale-last +'Qc' +Qcoplanar +'Qf' +Qfurthest +'Qg' +Qgood_only + +
    'QGn' +QGood_point +'Qi' +Qinterior +'Qm' +Qmax_out +'QJn' +QJoggle + +
    'Qr' +Qrandom +'QRn' +QRotate +'Qs' +Qsearch_1st +'Qt' +Qtriangulate + +
    'Qu' +QupperDelaunay +'QVn' +QVertex_good +'Qv' +Qvneighbors +'Qx' +Qxact_merge + +
    'Qz' +Qzinfinite + +
     
    'Q0' +Q0_no_premerge +'Q1' +Q1_no_angle +'Q2' +Q2_no_independ +'Q3' +Q3_no_redundant + +
    'Q4' +Q4_no_old +'Q5' +Q5_no_check_out +'Q6' +Q6_no_concave +'Q7' +Q7_depth_first + +
    'Q8' +Q8_no_near_in +'Q9' +Q9_pick_furthest +'Q10' +Q10_no_narrow +'Q11' +Q11_trinormals +
    + + +


    + +

    Up: Home +page for Qhull
    +Up: Qhull manual
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull internals
    +To: Qhull functions, macros, and data structures
    +To: Qhull files
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser
    + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qhalf.htm b/xs/src/qhull/html/qhalf.htm new file mode 100644 index 0000000000..c87fe719ed --- /dev/null +++ b/xs/src/qhull/html/qhalf.htm @@ -0,0 +1,626 @@ + + + + +qhalf -- halfspace intersection about a point + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    [halfspace]qhalf -- halfspace intersection about a point

    + +

    The intersection of a set of halfspaces is a polytope. The +polytope may be unbounded. See Preparata & Shamos ['85] for a discussion. In low +dimensions, halfspace intersection may be used for linear +programming. + +

    +
    +

    Example: rbox c | qconvex FQ FV + n | qhalf Fp

    +
    Print the intersection of the facets of a cube. rbox c + generates the vertices of a cube. qconvex FV n returns of average + of the cube's vertices (in this case, the origin) and the halfspaces + that define the cube. qhalf Fp computes the intersection of + the halfspaces about the origin. The intersection is the vertices + of the original cube.
    + +

    Example: rbox c d G0.55 | qconvex FQ FV + n | qhalf Fp

    +
    Print the intersection of the facets of a cube and a diamond. There + are 24 facets and 14 intersection points. Four facets define each diamond + vertex. Six facets define each cube vertex. +
    + +

    Example: rbox c d G0.55 | qconvex FQ FV + n | qhalf Fp + Qt

    +
    Same as above except triangulate before computing + the intersection points. Three facets define each intersection + point. There are two duplicates of the diamond and four duplicates of the cube. +
    + +

    Example: rbox 10 s t10 | qconvex FQ FV + n | qhalf Fp Fn

    +
    Print the intersection of the facets of the convex hull of 10 cospherical points. + Include the intersection points and the neighboring intersections. + As in the previous examples, the intersection points are nearly the same as the + original input points. +
    +
    +
    + +

    In Qhull, a halfspace is defined by the points on or below a hyperplane. +The distance of each point to the hyperplane is less than or equal to zero. + +

    Qhull computes a halfspace intersection by the geometric +duality between points and halfspaces. +See halfspace examples, +qhalf notes, and +option 'p' of qhalf outputs.

    + +

    Qhalf's outputs are the intersection +points (Fp) and +the neighboring intersection points (Fn). +For random inputs, halfspace +intersections are usually defined by more than d halfspaces. See the sphere example. + +

    You can try triangulated output ('Qt') and joggled input ('QJ'). +It demonstrates that triangulated output is more accurate than joggled input. + +

    If you use 'Qt' (triangulated output), all +halfspace intersections are simplicial (e.g., three halfspaces per +intersection in 3-d). In 3-d, if more than three halfspaces intersect +at the same point, triangulated output will produce +duplicate intersections, one for each additional halfspace. See the third example, or +add 'Qt' to the sphere example.

    + +

    If you use 'QJ' (joggled input), all halfspace +intersections are simplicial. This may lead to nearly identical +intersections. For example, either replace 'Qt' with 'QJ' above, or add +'QJ' to the sphere example. +See Merged facets or joggled input.

    + +

    The 'qhalf' program is equivalent to +'qhull H' in 2-d to 4-d, and +'qhull H Qx' +in 5-d and higher. It disables the following Qhull +options: d n v Qbb QbB Qf Qg Qm +Qr QR Qv Qx Qz TR E V Fa FA FC FD FS Ft FV Gt Q0,etc. + + +

    Copyright © 1995-2015 C.B. Barber

    +
    + +

    »qhalf synopsis

    +
    +qhalf- halfspace intersection about a point.
    +    input (stdin): [dim, 1, interior point]
    +                   dim+1, n
    +                   halfspace coefficients + offset
    +    comments start with a non-numeric character
    +
    +options (qhalf.htm):
    +    Hn,n - specify coordinates of interior point
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Tv   - verify result: structure, convexity, and redundancy
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +output options (subset):
    +    s    - summary of results (default)
    +    Fp   - intersection coordinates
    +    Fv   - non-redundant halfspaces incident to each intersection
    +    Fx   - non-redundant halfspaces
    +    o    - OFF file format (dual convex hull)
    +    G    - Geomview output (dual convex hull)
    +    m    - Mathematica output (dual convex hull)
    +    QVn  - print intersections for halfspace n, -n if not
    +    TO file - output results to file, may be enclosed in single quotes
    +
    +examples:
    +    rbox d | qconvex n | qhalf s H0,0,0 Fp
    +    rbox c | qconvex FV n | qhalf s i
    +    rbox c | qconvex FV n | qhalf s o
    +
    + +

    »qhalf input

    + +
    +

    The input data on stdin consists of:

    +
      +
    • [optional] interior point +
        +
      • dimension +
      • 1 +
      • coordinates of interior point +
      +
    • dimension + 1 +
    • number of halfspaces
    • +
    • halfspace coefficients followed by offset
    • +
    + +

    Use I/O redirection (e.g., qhalf < data.txt), a pipe (e.g., rbox c | qconvex FV n | qhalf), +or the 'TI' option (e.g., qhalf TI data.txt). + +

    Qhull needs an interior point to compute the halfspace +intersection. An interior point is clearly inside all of the halfspaces. +A point is inside a halfspace if its distance to the corresponding hyperplane is negative. + +

    The interior point may be listed at the beginning of the input (as shown above). +If not, option +'Hn,n' defines the interior point as +[n,n,0,...] where 0 is the default coordinate (e.g., +'H0' is the origin). Use linear programming if you do not know +the interior point (see halfspace notes),

    + +

    The input to qhalf is a set of halfspaces that are defined by their hyperplanes. +Each halfspace is defined by +d coefficients followed by a signed offset. This defines +a linear inequality. The coefficients define a vector that is +normal to the halfspace. +The vector may have any length. If it +has length one, the offset is the distance from the origin to the +halfspace's boundary. Points in the halfspace have a negative distance to the hyperplane. +The distance from the interior point to each +halfspace is likewise negative.

    + +

    The halfspace format is the same as Qhull's output options 'n', 'Fo', +and 'Fi'. Use option 'Fd' to use cdd format for the +halfspaces.

    + +

    For example, here is the input for computing the intersection +of halfplanes that form a cube.

    + +
    +

    rbox c | qconvex FQ FV n TO data

    +
    +RBOX c | QCONVEX FQ FV n
    +3 1
    +     0      0      0
    +4
    +6
    +     0      0     -1   -0.5
    +     0     -1      0   -0.5
    +     1      0      0   -0.5
    +    -1      0      0   -0.5
    +     0      1      0   -0.5
    +     0      0      1   -0.5
    +
    +

    qhalf s Fp < data

    +
    +
    +Halfspace intersection by the convex hull of 6 points in 3-d:
    +
    +  Number of halfspaces: 6
    +  Number of non-redundant halfspaces: 6
    +  Number of intersection points: 8
    +
    +Statistics for: RBOX c | QCONVEX FQ FV n | QHALF s Fp
    +
    +  Number of points processed: 6
    +  Number of hyperplanes created: 11
    +  Number of distance tests for qhull: 11
    +  Number of merged facets: 1
    +  Number of distance tests for merging: 45
    +  CPU seconds to compute hull (after input):  0
    +
    +3
    +3
    +8
    +  -0.5    0.5    0.5
    +   0.5    0.5    0.5
    +  -0.5    0.5   -0.5
    +   0.5    0.5   -0.5
    +   0.5   -0.5    0.5
    +  -0.5   -0.5    0.5
    +  -0.5   -0.5   -0.5
    +   0.5   -0.5   -0.5
    +
    +
    + +
    +

    »qhalf outputs

    +
    + +

    The following options control the output for halfspace +intersection.

    +
    +
    +
     
    +
    Intersections
    +
    FN
    +
    list intersection points for each non-redundant + halfspace. The first line + is the number of non-redundant halfspaces. Each remaining + lines starts with the number of intersection points. For the cube + example, each halfspace has four intersection points.
    +
    Fn
    +
    list neighboring intersections for each intersection point. The first line + is the number of intersection points. Each remaining line + starts with the number of neighboring intersections. For the cube + example, each intersection point has three neighboring intersections. +

    + In 3-d, a non-simplicial intersection has more than three neighboring + intersections. For random data (e.g., the sphere example), non-simplicial intersections are the norm. + Option 'Qt' produces three + neighboring intersections per intersection by duplicating the intersection + points. Option QJ' produces three + neighboring intersections per intersection by joggling the hyperplanes and + hence their intersections. +

    +
    Fp
    +
    print intersection coordinates. The first line is the dimension and the + second line is the number of intersection points. The following lines are the + coordinates of each intersection.
    +
    FI
    +
    list intersection IDs. The first line is the number of + intersections. The IDs follow, one per line.
    +
     
    +
     
    +
    Halfspaces
    +
    Fx
    +
    list non-redundant halfspaces. The first line is the number of + non-redundant halfspaces. The other lines list one halfspace per line. + A halfspace is non-redundant if it + defines a facet of the intersection. Redundant halfspaces are ignored. For + the cube example, all of the halfspaces are non-redundant. +
    +
    Fv
    +
    list non-redundant halfspaces incident to each intersection point. + The first line is the number of + non-redundant halfspaces. Each remaining line starts with the number + of non-redundant halfspaces. For the + cube example, each intersection is incident to three halfspaces.
    +
    i
    +
    list non-redundant halfspaces incident to each intersection point. The first + line is the number of intersection points. Each remaining line + lists the incident, non-redundant halfspaces. For the + cube example, each intersection is incident to three halfspaces. +
    +
    Fc
    +
    list coplanar halfspaces for each intersection point. The first line is + the number of intersection points. Each remaining line starts with + the number of coplanar halfspaces. A coplanar halfspace is listed for + one intersection point even though it is coplanar to multiple intersection + points.
    +
    Qi Fc
    +
    list redundant halfspaces for each intersection point. The first line is + the number of intersection points. Each remaining line starts with + the number of redundant halfspaces. Use options 'Qc Qi Fc' to list + coplanar and redundant halfspaces.
    + +
     
    +
     
    +
    General
    +
    s
    +
    print summary for the halfspace intersection. Use 'Fs' if you need numeric data.
    +
    o
    +
    print vertices and facets of the dual convex hull. The + first line is the dimension. The second line is the number of + vertices, facets, and ridges. The vertex + coordinates are next, followed by the facets, one per line.
    +
    p
    +
    print vertex coordinates of the dual convex hull. Each vertex corresponds + to a non-redundant halfspace. Its coordinates are the negative of the hyperplane's coefficients + divided by the offset plus the inner product of the coefficients and + the interior point (-c/(b+a.p). + Options 'p Qc' includes coplanar halfspaces. + Options 'p Qi' includes redundant halfspaces.
    +
    m
    +
    Mathematica output for the dual convex hull in 2-d or 3-d.
    +
    FM
    +
    Maple output for the dual convex hull in 2-d or 3-d.
    +
    G
    +
    Geomview output for the dual convex hull in 2-d, 3-d, or 4-d.
    +
    +
    + +
    +

    »qhalf controls

    +
    + +

    These options provide additional control:

    + +
    +
    +
    Qt
    +
    triangulated output. If a 3-d intersection is defined by more than + three hyperplanes, Qhull produces duplicate intersections -- one for + each extra hyperplane.
    +
    QJ
    +
    joggle the input instead of merging facets. In 3-d, this guarantees that + each intersection is defined by three hyperplanes.
    +
    f
    +
    facet dump. Print the data structure for each intersection (i.e., + facet)
    +
    TFn
    +
    report summary after constructing n + intersections
    +
    QVn
    +
    select intersection points for halfspace n + (marked 'good')
    +
    QGn
    +
    select intersection points that are visible to halfspace n + (marked 'good'). Use -n for the remainder.
    +
    Qbk:0Bk:0
    +
    remove the k-th coordinate from the input. This computes the + halfspace intersection in one lower dimension.
    +
    Tv
    +
    verify result
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    Qs
    +
    search all points for the initial simplex. If Qhull can + not construct an initial simplex, it reports a +descriptive message. Usually, the point set is degenerate and one +or more dimensions should be removed ('Qbk:0Bk:0'). +If not, use option 'Qs'. It performs an exhaustive search for the +best initial simplex. This is expensive is high dimensions.
    +
    +
    + + +
    +

    »qhalf graphics

    +
    + +

    To view the results with Geomview, compute the convex hull of +the intersection points ('qhull FQ H0 Fp | qhull G'). See Halfspace examples.

    + +
    +

    »qhalf notes

    +
    + +

    See halfspace intersection for precision issues related to qhalf.

    + +

    If you do not know an interior point for the halfspaces, use +linear programming to find one. Assume, n halfspaces +defined by: aj*x1+bj*x2+cj*x3+dj<=0, j=1..n. Perform +the following linear program:

    + +
    +

    max(x5) aj*x1+bj*x2+cj*x3+dj*x4+x5<=0, j=1..n

    +
    + +

    Then, if [x1,x2,x3,x4,x5] is an optimal solution with +x4>0 and x5>0 we get:

    + +
    +

    aj*(x1/x4)+bj*(x2/x4)+cj*(x3/x4)+dj<=(-x5/x4) j=1..n and (-x5/x4)<0, +

    +
    + +

    and conclude that the point [x1/x4,x2/x4,x3/x4] is in +the interior of all the halfspaces. Since x5 is +optimal, this point is "way in" the interior (good +for precision errors).

    + +

    After finding an interior point, the rest of the intersection +algorithm is from Preparata & Shamos ['85, p. 316, "A simple case +..."]. Translate the halfspaces so that the interior point +is the origin. Calculate the dual polytope. The dual polytope is +the convex hull of the vertices dual to the original faces in +regard to the unit sphere (i.e., halfspaces at distance d +from the origin are dual to vertices at distance 1/d). +Then calculate the resulting polytope, which is the dual of the +dual polytope, and translate the origin back to the interior +point [S. Spitz, S. Teller, D. Strawn].

    + + +
    +

    »qhalf +conventions

    +
    + +

    The following terminology is used for halfspace intersection +in Qhull. This is the hardest structure to understand. The +underlying structure is a convex hull with one vertex per +non-redundant halfspace. See convex hull +conventions and Qhull's data structures.

    + +
      +
    • interior point - a point in the intersection of + the halfspaces. Qhull needs an interior point to compute + the intersection. See halfspace input.
    • +
    • halfspace - d coordinates for the + normal and a signed offset. The distance to an interior + point is negative.
    • +
    • non-redundant halfspace - a halfspace that + defines an output facet
    • +
    • vertex - a dual vertex in the convex hull + corresponding to a non-redundant halfspace
    • +
    • coplanar point - the dual point corresponding to + a similar halfspace
    • +
    • interior point - the dual point corresponding to + a redundant halfspace
    • +
    • intersection point- the intersection of d + or more non-redundant halfspaces
    • +
    • facet - a dual facet in the convex hull + corresponding to an intersection point
    • +
    • non-simplicial facet - more than d + halfspaces intersect at a point
    • +
    • good facet - an intersection point that + satisfies restriction 'QVn', + etc.
    • +
    + +
    +

    »qhalf options

    + +
    +qhalf- compute the intersection of halfspaces about a point
    +    http://www.qhull.org
    +
    +input (stdin):
    +    optional interior point: dimension, 1, coordinates
    +    first lines: dimension+1 and number of halfspaces
    +    other lines: halfspace coefficients followed by offset
    +    comments:    start with a non-numeric character
    +
    +options:
    +    Hn,n - specify coordinates of interior point
    +    Qt   - triangulated ouput
    +    QJ   - joggle input instead of merging facets
    +    Qc   - keep coplanar halfspaces
    +    Qi   - keep other redundant halfspaces
    +
    +Qhull control options:
    +    QJn  - randomly joggle input in range [-n,n]
    +    Qbk:0Bk:0 - remove k-th coordinate from input
    +    Qs   - search all halfspaces for the initial simplex
    +    QGn  - print intersection if redundant to halfspace n, -n for not
    +    QVn  - print intersections for halfspace n, -n if not
    +
    +Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - print statistics
    +    Tv   - verify result: structure, convexity, and redundancy
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when halfspace n added to intersection
    +    TMn  - turn on tracing at merge n
    +    TWn  - trace merge facets when width > n
    +    TVn  - stop qhull after adding halfspace n, -n for before (see TCn)
    +    TCn  - stop qhull after building cone for halfspace n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Un   - max distance below plane for a new, coplanar halfspace
    +    Wn   - min facet width for outside halfspace (before roundoff)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    f    - facet dump
    +    G    - Geomview output (dual convex hull)
    +    i    - non-redundant halfspaces incident to each intersection
    +    m    - Mathematica output (dual convex hull)
    +    o    - OFF format (dual convex hull: dimension, points, and facets)
    +    p    - vertex coordinates of dual convex hull (coplanars if 'Qc' or 'Qi')
    +    s    - summary (stderr)
    +
    +More formats:
    +    Fc   - count plus redundant halfspaces for each intersection
    +         -   Qc (default) for coplanar and Qi for other redundant
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FF   - facet dump without ridges
    +    FI   - ID of each intersection
    +    Fm   - merge count for each intersection (511 max)
    +    FM   - Maple output (dual convex hull)
    +    Fn   - count plus neighboring intersections for each intersection
    +    FN   - count plus intersections for each non-redundant halfspace
    +    FO   - options and precision constants
    +    Fp   - dim, count, and intersection coordinates
    +    FP   - nearest halfspace and distance for each redundant halfspace
    +    FQ   - command used for qhalf
    +    Fs   - summary: #int (8), dim, #halfspaces, #non-redundant, #intersections
    +                      for output: #non-redundant, #intersections, #coplanar
    +                                  halfspaces, #non-simplicial intersections
    +                    #real (2), max outer plane, min vertex
    +    Fv   - count plus non-redundant halfspaces for each intersection
    +    Fx   - non-redundant halfspaces
    +
    +Geomview output (2-d, 3-d and 4-d; dual convex hull)
    +    Ga   - all points (i.e., transformed halfspaces) as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices (i.e., non-redundant halfspaces) as spheres
    +    Gi   - inner planes (i.e., halfspace intersections) only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc   - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +
    +Print options:
    +    PAn  - keep n largest facets (i.e., intersections) by area
    +    Pdk:n- drop facet if normal[k] <= n (default 0.0)
    +    PDk:n- drop facet if normal[k] >= n
    +    Pg   - print good facets (needs 'QGn' or 'QVn')
    +    PFn  - keep facets whose area is at least n
    +    PG   - print neighbors of good facets
    +    PMn  - keep n facets with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + + diff --git a/xs/src/qhull/html/qhull-cpp.xml b/xs/src/qhull/html/qhull-cpp.xml new file mode 100644 index 0000000000..ae755e8266 --- /dev/null +++ b/xs/src/qhull/html/qhull-cpp.xml @@ -0,0 +1,214 @@ + + + + +

    Qhull C++ -- C++ interface to Qhull

    + + Copyright (c) 2009-2015, C.B. Barber + + +
    +

    This draft + document records some of the design decisions for Qhull C++. Convert it to HTML by road-faq.xsl from road-faq. + + Please send comments and suggestions to bradb@shore.net +

    +
    +
    +
    + Help +
    • +
    • +
    • +
    +
    +
    +
    • +
    • +
    +
    +
    +
    + . +
    + +
    + + + + Qhull's collection APIs are modeled on Qt's collection API (QList, QVector, QHash) w/o QT_STRICT_ITERATORS. They support STL and Qt programming. + +

    Some of Qhull's collection classes derive from STL classes. If so, + please avoid additional STL functions and operators added by inheritance. + These collection classes may be rewritten to derive from Qt classes instead. + See Road's . +

    + + + Qhull's collection API (where applicable). For documentation, see Qt's QList, QMap, QListIterator, QMapIterator, QMutableListIterator, and QMutableMapIterator +
    • + STL types [list, qlinkedlist, qlist, qvector, vector] -- const_iterator, iterator +
    • + STL types describing iterators [list, qlinkedlist, qlist, qvector, vector] -- const_pointer, const_reference, difference_type, + pointer, reference, size_type, value_type. + Pointer and reference types not defined if unavailable (not needed for <algorithm>) +
    • + const_iterator, iterator types -- difference_type, iterator_category, pointer, reference, value_type +
    • + Qt types [qlinkedlist, qlist, qvector] -- ConstIterator, Iterator, QhullclassIterator, MutableQhullclassIterator. + Qt's foreach requires const_iterator. +
    • + Types for sets/maps [hash_map, QHash] -- key_compare, key_type, mapped_type +
    • + Constructor -- default constructor, copy constructor, assignment operator, destructor +
    • + Conversion -- to/from/as corresponding C, STL, and Qt constructs. Include toQList and toStdVector (may be filtered, e.g., QhullFacetSet). + Do not define fromStdList and fromQList if container is not reference counted (i.e., acts like a value) +
    • + Get/set -- configuration options for class +
    • + STL-style iterator - begin, constBegin, constEnd, end, key, value, =, *, [], ->, ++, --, +, -, ==, !=, <, + <=, >, >=, const_iterator(iterator), iterator COMPARE const_iterator. + An iterator is an abstraction of a pointer. It is not aware of its container. +
    • + Java-style iterator [qiterator.h] - countRemaining, findNext, findPrevious, hasNext, hasPrevious, next, peekNext, peekPrevious, previous, toBack, toFront, = Coordinates +
    • + Mutable Java-style iterator adds - insert, remove, setValue, value +
    • + Element access -- back, first, front, last +
    • + Element access w/ index -- [], at (const& only), constData, data, mid, value +
    • + Read-only - (int)count, empty, isEmpty, (size_t)size. Count() and size() may be filtered. If so, they may be zero when !empty(). +
    • + Read-only for sets/maps - capacity, key, keys, reserve, resize, values +
    • + Operator - ==, !=, +, +=, << +
    • + Read-write -- append, clear, erase, insert, move, prepend, pop_back, pop_front, push_back, push_front, removeAll, removeAt, removeFirst, removeLast, replace, + swap, takeAt, takeFirst, takeLast +
    • + Read-write for sets/maps -- insertMulti, squeeze, take, unite +
    • + Search -- contains(const T &), count(const T &), indexOf, lastIndexOf +
    • + Search for sets/maps -- constFind, lowerBound, upperBound +
    • + Stream I/O -- stream << +
    + + STL list and vector -- For unfiltered access to each element. +
    • + Apache: Creating your own containers -- requirements for STL containers. Iterators should define the types from 'iterator_traits'. +
    • + STL types -- allocator_type, const_iterator, const_pointer, const_reference, const_reverse_iterator, difference_type, iterator, iterator_category, pointer, reference, reverse_iterator, size_type, value_type +
    • + STL constructors -- MyType(), MyType(count), MyType(count, value), MyType(first, last), + MyType(MyType&), +
    • + STL getter/setters -- at (random_access only), back, begin, capacity, end, front, rbegin, rend, size, max_size +
    • + STL predicates -- empty +
    • + STL iterator types -- const_pointer, const_reference, difference_type, iterator_category, pointer, reference, value_type +
    • + STL iterator operators -- *, -<, ++, --, +=, -=, +, -, [], ==, !=, <, >, >=, <= +
    • + STL operators -- =, [] (random_access only), ==, !=, <, >, <=, >= +
    • + STL modifiers -- assign, clear, erase, insert, pop_back, push_back, reserve, resize, swap +
    • +
    + + Qt Qlist -- For unfiltered access to each element +
    • +
    • + Additional Qt types -- ConstIterator, Iterator, QListIterator, QMutableListIterator +
    • + Additional Qt get/set -- constBegin, constEnd, count, first, last, value (random_access only) +
    • + Additional Qt predicates -- isEmpty +
    • + Additional Qt -- mid (random_access only) +
    • + Additional Qt search -- contains, count(T&), indexOf (random_access only), lastIndeOf (random_access only) +
    • + Additional Qt modifiers -- append, insert(index,value) (random_access only), move (random_access only), pop_front, prepend, push_front, removeAll, removeAt (random_access only), removeFirst, removeLast, replace, swap by index, takeAt, takeFirst, takeLast +
    • + Additional Qt operators -- +, <<, +=, + stream << and >> +
    • + Unsupported types by Qt -- allocator_type, const_reverse_iterator, reverse_iterator +
    • + Unsupported accessors by Qt -- max_size, rbegin, rend +
    • + Unsupported constructors by Qt -- multi-value constructors +
    • + unsupported modifiers by Qt -- assign, muli-value inserts, STL's swaps +
    • +
    + + STL map and Qt QMap. These use nearly the same API as list and vector classes. They add the following. +
    • + STL types -- key_compare, key_type, mapped_type +
    • + STL search -- equal_range, find, lower_bound, upper_bound +
    • + Qt removes -- equal_range, key_compare +
    • + Qt renames -- lowerBound, upperBound +
    • + Qt adds -- constFind, insertMulti, key, keys, take, uniqueKeys, unite, values +
    • + Not applicable to map and QMap -- at, back, pop_back, pop_front, push_back, push_front, swap +
    • + Not applicable to QMap -- append, first, last, lastIndexOf, mid, move, prepend, removeAll, removeAt, removeFirst, removeLast, replace, squeeze, takeAt, takeFirst, takeLast +
    • + Not applicable to map -- assign +
    + + Qt QHash. STL extensions provide similar classes, e.g., Microsoft's stdext::hash_set. THey are nearly the same as QMap +
    • +
    • +
    • + Not applicable to Qhash -- lowerBound, unite, upperBound, +
    • + Qt adds -- squeeze +
    +
    + +
    • + check... -- Throw error on failure +
    • + try... -- Return false on failure. Do not throw errors. +
    • + ...Temporarily -- lifetime depends on source. e.g., toByteArrayTemporarily +
    • + ...p -- indicates pointer-to. +
    • + end... -- points to one beyond the last available +
    • + private functions -- No syntactic indication. They may become public later on. +
    • + Error messages -- Preceed error messages with the name of the class throwing the error (e.g. "ClassName: ..."). If this is an internal error, use "ClassName inconsistent: ..." +
    • + parameter order -- qhRunId, dimension, coordinates, count. +
    • + toClass -- Convert into a Class object (makes a deep copy) +
    • + qRunId -- Requires Qh installed. Some routines allow 0 for limited info (e.g., operator<<) +
    • + Disable methods in derived classes -- If the default constructor, copy constructor, or copy assignment is disabled, it should be also disabled in derived classes (better error messages). +
    • + Constructor order -- default constructor, other constructors, copy constructor, copy assignment, destructor +
    +
    +
    +
    diff --git a/xs/src/qhull/html/qhull.htm b/xs/src/qhull/html/qhull.htm new file mode 100644 index 0000000000..0a2aa75e06 --- /dev/null +++ b/xs/src/qhull/html/qhull.htm @@ -0,0 +1,473 @@ + + + + +qhull -- convex hull and related structures + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis • input +• outputs • controls +• options +


    + +

    [cone]qhull -- convex hull and related structures

    + +

    The convex hull of a set of points is the smallest convex set +containing the points. The Delaunay triangulation and furthest-site +Delaunay triangulation are equivalent to a convex hull in one +higher dimension. Halfspace intersection about a point is +equivalent to a convex hull by polar duality. + +

    The qhull program provides options to build these +structures and to experiment with the process. Use the +qconvex, +qdelaunay, qhalf, +and qvoronoi programs +to build specific structures. You may use qhull instead. +It takes the same options and uses the same code. +

    +
    +
    Example: rbox 1000 D3 | qhull + C-1e-4 + FO + Ts +
    +
    Compute the 3-d convex hull of 1000 random + points. + Centrums must be 10^-4 below neighboring + hyperplanes. Print the options and precision constants. + When done, print statistics. These options may be + used with any of the Qhull programs.
    +
     
    +
    Example: rbox 1000 D3 | qhull d + Qbb + R1e-4 + Q0
    +
    Compute the 3-d Delaunay triangulation of 1000 random + points. Randomly perturb all calculations by + [0.9999,1.0001]. Do not correct precision problems. + This leads to serious precision errors.
    +
    +
    +

    Use the following equivalences when calling qhull in 2-d to 4-d (a 3-d +Delaunay triangulation is a 4-d convex hull): +

    + +
    + +

    Use the following equivalences when calling qhull in 5-d and higher (a 4-d +Delaunay triangulation is a 5-d convex hull): +

    + +
    + + +

    By default, Qhull merges coplanar facets. For example, the convex +hull of a cube's vertices has six facets. + +

    If you use 'Qt' (triangulated output), +all facets will be simplicial (e.g., triangles in 2-d). For the cube +example, it will have 12 facets. Some facets may be +degenerate and have zero area. + +

    If you use 'QJ' (joggled input), +all facets will be simplicial. The corresponding vertices will be +slightly perturbed. Joggled input is less accurate that triangulated +output.See Merged facets or joggled input.

    + +

    The output for 4-d convex hulls may be confusing if the convex +hull contains non-simplicial facets (e.g., a hypercube). See +Why +are there extra points in a 4-d or higher convex hull?
    +

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »qhull synopsis

    +
    +qhull- compute convex hulls and related structures.
    +    input (stdin): dimension, n, point coordinates
    +    comments start with a non-numeric character
    +    halfspace: use dim+1 and put offsets after coefficients
    +
    +options (qh-quick.htm):
    +    d    - Delaunay triangulation by lifting points to a paraboloid
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)
    +    v    - Voronoi diagram as the dual of the Delaunay triangulation
    +    v Qu - furthest-site Voronoi diagram
    +    H1,1 - Halfspace intersection about [1,1,0,...] via polar duality
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Tv   - verify result: structure, convexity, and point inclusion
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +Output options (subset):
    +    s    - summary of results (default)
    +    i    - vertices incident to each facet
    +    n    - normals with offsets
    +    p    - vertex coordinates (if 'Qc', includes coplanar points)
    +           if 'v', Voronoi vertices
    +    Fp   - halfspace intersections
    +    Fx   - extreme points (convex hull vertices)
    +    FA   - compute total area and volume
    +    o    - OFF format (if 'v', outputs Voronoi regions)
    +    G    - Geomview output (2-d, 3-d and 4-d)
    +    m    - Mathematica output (2-d and 3-d)
    +    QVn  - print facets that include point n, -n if not
    +    TO file- output results to file, may be enclosed in single quotes
    +
    +examples:
    +    rbox c d D2 | qhull Qc s f Fx | more      rbox 1000 s | qhull Tv s FA
    +    rbox 10 D2 | qhull d QJ s i TO result     rbox 10 D2 | qhull v Qbb Qt p
    +    rbox 10 D2 | qhull d Qu QJ m              rbox 10 D2 | qhull v Qu QJ o
    +    rbox c | qhull n                          rbox c | qhull FV n | qhull H Fp
    +    rbox d D12 | qhull QR0 FA                 rbox c D7 | qhull FA TF1000
    +    rbox y 1000 W0 | qhull                    rbox 10 | qhull v QJ o Fv
    +
    + +

    »qhull input

    +
    + +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qhull < data.txt), a pipe (e.g., rbox 10 | qhull), +or the 'TI' option (e.g., qhull TI data.txt). + +

    Comments start with a non-numeric character. Error reporting is +simpler if there is one point per line. Dimension +and number of points may be reversed. For halfspace intersection, +an interior point may be prepended (see qhalf input). + +

    Here is the input for computing the convex +hull of the unit cube. The output is the normals, one +per facet.

    + +
    +

    rbox c > data

    +
    +3 RBOX c
    +8
    +  -0.5   -0.5   -0.5
    +  -0.5   -0.5    0.5
    +  -0.5    0.5   -0.5
    +  -0.5    0.5    0.5
    +   0.5   -0.5   -0.5
    +   0.5   -0.5    0.5
    +   0.5    0.5   -0.5
    +   0.5    0.5    0.5
    +
    +

    qhull s n < data

    +
    +
    +Convex hull of 8 points in 3-d:
    +
    +  Number of vertices: 8
    +  Number of facets: 6
    +  Number of non-simplicial facets: 6
    +
    +Statistics for: RBOX c | QHULL s n
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 11
    +  Number of distance tests for qhull: 35
    +  Number of merged facets: 6
    +  Number of distance tests for merging: 84
    +  CPU seconds to compute hull (after input): 0.081
    +
    +4
    +6
    +     0      0     -1   -0.5
    +     0     -1      0   -0.5
    +     1      0      0   -0.5
    +    -1      0      0   -0.5
    +     0      1      0   -0.5
    +     0      0      1   -0.5
    +
    +
    + +
    +

    »qhull outputs

    +
    + +

    These options control the output of qhull. They may be used +individually or together.

    +
    +
    +
     
    +
    General
    +
    qhull
    +
    compute the convex hull of the input points. + See qconvex.
    +
    qhull d Qbb
    +
    compute the Delaunay triangulation by lifting the points + to a paraboloid. Use option 'Qbb' + to scale the paraboloid and improve numeric precision. + See qdelaunay.
    +
    qhull v Qbb
    +
    compute the Voronoi diagram by computing the Delaunay + triangulation. Use option 'Qbb' + to scale the paraboloid and improve numeric precision. + See qvoronoi.
    +
    qhull H
    +
    compute the halfspace intersection about a point via polar + duality. The point is below the hyperplane that defines the halfspace. + See qhalf.
    +
    +
    + +

    For a full list of output options see +

    + +
    + +
    +

    »qhull controls

    +
    + +

    For a full list of control options see +

    + +
    + +
    +

    »qhull options

    + +
    +qhull- compute convex hulls and related structures.
    +    http://www.qhull.org
    +
    +input (stdin):
    +    first lines: dimension and number of points (or vice-versa).
    +    other lines: point coordinates, best if one point per line
    +    comments:    start with a non-numeric character
    +    halfspaces:  use dim plus one and put offset after coefficients.
    +                 May be preceded by a single interior point ('H').
    +
    +options:
    +    d    - Delaunay triangulation by lifting points to a paraboloid
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)
    +    v    - Voronoi diagram (dual of the Delaunay triangulation)
    +    v Qu - furthest-site Voronoi diagram
    +    Hn,n,... - halfspace intersection about point [n,n,0,...]
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Qc   - keep coplanar points with nearest facet
    +    Qi   - keep interior points with nearest facet
    +
    +Qhull control options:
    +    Qbk:n   - scale coord k so that low bound is n
    +      QBk:n - scale coord k so that upper bound is n (QBk is 0.5)
    +    QbB  - scale input to unit cube centered at the origin
    +    Qbb  - scale last coordinate to [0,m] for Delaunay triangulations
    +    Qbk:0Bk:0 - remove k-th coordinate from input
    +    QJn  - randomly joggle input in range [-n,n]
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)
    +    Qf   - partition point to furthest outside facet
    +    Qg   - only build good facets (needs 'QGn', 'QVn', or 'PdD')
    +    Qm   - only process points that would increase max_outside
    +    Qr   - process random outside points instead of furthest ones
    +    Qs   - search all points for the initial simplex
    +    Qu   - for 'd' or 'v', compute upper hull without point at-infinity
    +              returns furthest-site Delaunay triangulation
    +    Qv   - test vertex neighbors for convexity
    +    Qx   - exact pre-merges (skips coplanar and anglomaniacs facets)
    +    Qz   - add point-at-infinity to Delaunay triangulation
    +    QGn  - good facet if visible from point n, -n for not visible
    +    QVn  - good facet if it includes point n, -n if not
    +    Q0   - turn off default p remerge with 'C-0'/'Qx'
    +    Q1     - sort merges by type instead of angle
    +    Q2   - merge all non-convex at once instead of independent sets
    +    Q3   - do not merge redundant vertices
    +    Q4   - avoid old>new merges
    +    Q5   - do not correct outer planes at end of qhull
    +    Q6   - do not pre-merge concave or coplanar facets
    +    Q7   - depth-first processing instead of breadth-first
    +    Q8   - do not process near-inside points
    +    Q9   - process furthest of furthest points
    +    Q10  - no special processing for narrow distributions
    +    Q11  - copy normals and recompute centrums for tricoplanar facets
    +    Q12  - do not error on wide merge due to duplicate ridge and nearly coincident points
    +
    +Towpaths Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - print statistics
    +    Tv   - verify result: structure, convexity, and point inclusion
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when point n added to hull
    +     TMn - turn on tracing at merge n
    +     TWn - trace merge facets when width > n
    +    TRn  - rerun qhull n times.  Use with 'QJn'
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)
    +     TCn - stop qhull after building cone for point n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    En   - max roundoff error for distance computation
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Vn   - min distance above plane for a visible facet (default 3C-n or En)
    +    Un   - max distance below plane for a new, coplanar point (default Vn)
    +    Wn   - min facet width for outside point (before roundoff, default 2Vn)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    f    - facet dump
    +    G    - Geomview output (see below)
    +    i    - vertices incident to each facet
    +    m    - Mathematica output (2-d and 3-d)
    +    o    - OFF format (dim, points and facets; Voronoi regions)
    +    n    - normals with offsets
    +    p    - vertex coordinates or Voronoi vertices (coplanar points if 'Qc')
    +    s    - summary (stderr)
    +
    +More formats:
    +    Fa   - area for each facet
    +    FA   - compute total area and volume for option 's'
    +    Fc   - count plus coplanar points for each facet
    +           use 'Qc' (default) for coplanar and 'Qi' for interior
    +    FC   - centrum or Voronoi center for each facet
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FD   - use cdd format for numeric output (offset first)
    +    FF   - facet dump without ridges
    +    Fi   - inner plane for each facet
    +           for 'v', separating hyperplanes for bounded Voronoi regions
    +    FI   - ID of each facet
    +    Fm   - merge count for each facet (511 max)
    +    FM   - Maple output (2-d and 3-d)
    +    Fn   - count plus neighboring facets for each facet
    +    FN   - count plus neighboring facets for each point
    +    Fo   - outer plane (or max_outside) for each facet
    +           for 'v', separating hyperplanes for unbounded Voronoi regions
    +    FO   - options and precision constants
    +    Fp   - dim, count, and intersection coordinates (halfspace only)
    +    FP   - nearest vertex and distance for each coplanar point
    +    FQ   - command used for qhull
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,
    +                      output: #vertices, #facets, #coplanars, #nonsimplicial
    +                    #real (2), max outer plane, min vertex
    +    FS   - sizes:   #int (0)
    +                    #real(2) tot area, tot volume
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)
    +    Fv   - count plus vertices for each facet
    +           for 'v', Voronoi diagram as Voronoi vertices for pairs of sites
    +    FV   - average of vertices (a feasible point for 'H')
    +    Fx   - extreme points (in order for 2-d)
    +
    +Geomview options (2-d, 3-d, and 4-d; 2-d Voronoi)
    +    Ga   - all points as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices as spheres
    +    Gi   - inner planes only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc   - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +    Gt   - for 3-d 'd', transparent outer ridges
    +
    +Print options:
    +    PAn  - keep n largest facets by area
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)
    +    PDk:n - drop facet if normal[k] >= n
    +    Pg   - print good facets (needs 'QGn' or 'QVn')
    +    PFn  - keep facets whose area is at least n
    +    PG   - print neighbors of good facets
    +    PMn  - keep n facets with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis • input +• outputs • controls +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qhull.man b/xs/src/qhull/html/qhull.man new file mode 100644 index 0000000000..8d1dc08ace --- /dev/null +++ b/xs/src/qhull/html/qhull.man @@ -0,0 +1,1008 @@ +.\" This is the Unix manual page for qhull, written in nroff, the standard +.\" manual formatter for Unix systems. To format it, type +.\" +.\" nroff -man qhull.man +.\" +.\" This will print a formatted copy to standard output. If you want +.\" to ensure that the output is plain ASCII, free of any control +.\" characters that nroff uses for underlining etc, pipe the output +.\" through "col -b": +.\" +.\" nroff -man qhull.man | col -b +.\" +.\" Warning: a leading quote "'" or dot "." will not format correctly +.\" +.TH qhull 1 "2003/12/30" "Geometry Center" +.SH NAME +qhull \- convex hull, Delaunay triangulation, Voronoi diagram, +halfspace intersection about a point, hull volume, facet area +.SH SYNOPSIS +.nf +qhull- compute convex hulls and related structures + input (stdin): dimension, #points, point coordinates + first comment (non-numeric) is listed in the summary + halfspace: use dim plus one with offsets after coefficients + +options (qh-quick.htm): + d - Delaunay triangulation by lifting points to a paraboloid + v - Voronoi diagram via the Delaunay triangulation + H1,1 - Halfspace intersection about [1,1,0,...] + d Qu - Furthest-site Delaunay triangulation (upper convex hull) + v Qu - Furthest-site Voronoi diagram + Qt - triangulated output + QJ - Joggle the input to avoid precision problems + . - concise list of all options + - - one-line description of all options + +Output options (subset): + FA - compute total area and volume + Fx - extreme points (convex hull vertices) + G - Geomview output (2-d, 3-d and 4-d) + Fp - halfspace intersection coordinates + m - Mathematica output (2-d and 3-d) + n - normals with offsets + o - OFF file format (if Voronoi, outputs regions) + TO file- output results to file, may be enclosed in single quotes + f - print all fields of all facets + s - summary of results (default) + Tv - verify result: structure, convexity, and point inclusion + p - vertex coordinates (centers for Voronoi) + i - vertices incident to each facet + +example: + rbox 1000 s | qhull Tv s FA +.fi + + - html manual: index.htm + - installation: README.txt + - see also: COPYING.txt, REGISTER.txt, Changes.txt + - WWW: + - GIT: + - mirror: + - news: + - Geomview: + - news group: + - FAQ: + - email: qhull@qhull.org + - bug reports: qhull_bug@qhull.org + +The sections are: + - INTRODUCTION + - DESCRIPTION, a description of Qhull + - IMPRECISION, how Qhull handles imprecision + - OPTIONS + - Input and output options + - Additional input/output formats + - Precision options + - Geomview options + - Print options + - Qhull options + - Trace options + - BUGS + - E-MAIL + - SEE ALSO + - AUTHORS + - ACKNOWLEGEMENTS + +This man page briefly describes all Qhull options. Please report +any mismatches with Qhull's html manual (index.htm). + +.PP +.SH INTRODUCTION +Qhull is a general dimension code for computing convex hulls, Delaunay +triangulations, Voronoi diagram, furthest\[hy]site Voronoi diagram, +furthest\[hy]site Delaunay triangulations, and +halfspace intersections about a point. It implements the Quickhull algorithm for +computing the convex hull. Qhull handles round\[hy]off errors from floating +point arithmetic. It can approximate a convex hull. + +The program includes options for hull volume, facet area, partial hulls, +input transformations, randomization, tracing, multiple output formats, and +execution statistics. The program can be called from within your application. +You can view the results in 2\[hy]d, 3\[hy]d and 4\[hy]d with Geomview. +.PP +.SH DESCRIPTION +.PP +The format of input is the following: first line contains the dimension, +second line contains the number of input points, and point coordinates follow. +The dimension and number of points can be reversed. +Comments and line breaks are ignored. A comment starts with a +non\[hy]numeric character and continues to the end of line. The first comment +is reported in summaries and statistics. +Error reporting is +better if there is one point per line. +.PP +The default printout option is a short summary. There are many +other output formats. +.PP +Qhull implements the Quickhull algorithm for convex hull. This algorithm combines +the 2\[hy]d Quickhull algorithm with the n\[hy]d beneath\[hy]beyond algorithm +[c.f., Preparata & Shamos '85]. +It is similar to the randomized algorithms of Clarkson and +others [Clarkson et al. '93]. The main +advantages of Quickhull are output sensitive performance, reduced +space requirements, and automatic handling of precision problems. +.PP +The data structure produced by Qhull consists of vertices, ridges, and facets. +A vertex is a point of the input set. A ridge is a set of d vertices +and two neighboring facets. For example in 3\[hy]d, a ridge is an edge of the +polyhedron. A facet is a set of ridges, a set of neighboring facets, a set +of incident vertices, and a hyperplane equation. For simplicial facets, the +ridges are defined by the vertices and neighboring facets. When Qhull +merges two facets, it produces a non\[hy]simplicial +facet. A non\[hy]simplicial facet has more than d neighbors and may share more than +one ridge with a neighbor. +.PP +.SH IMPRECISION +.PP +Since Qhull uses floating point arithmetic, roundoff error may occur for each +calculation. This causes problems +for most geometric algorithms. +.PP +Qhull automatically sets option 'C\-0' in 2\[hy]d, 3\[hy]d, and 4\[hy]d, or +option 'Qx' in 5\[hy]d and higher. These options handle precision problems +by merging facets. Alternatively, use option 'QJ' to joggle the +input. +.PP +With 'C\-0', Qhull merges non\[hy]convex +facets while constructing the hull. The remaining facets are +clearly convex. With 'Qx', Qhull merges +coplanar horizon facets, flipped facets, concave facets and +duplicated ridges. It merges coplanar facets after constructing +the hull. +With 'Qx', coplanar points may be missed, but it +appears to be unlikely. +.PP +To guarantee triangular output, joggle the input with option 'QJ'. Facet +merging will not occur. +.SH OPTIONS +.PP +To get a list of the most important options, execute 'qhull' by itself. +To get a complete list of options, +execute 'qhull \-'. +To get a complete, concise list of options, execute 'qhull .'. + +Options can be in any order. +Capitalized options take an argument (except 'PG' and 'F' options). +Single letters are used for output formats and precision constants. The +other options are grouped into menus for other output formats ('F'), +Geomview output ('G'), +printing ('P'), Qhull control ('Q'), and tracing ('T'). +.TP +Main options: +.TP +default +Compute the convex hull of the input points. Report a summary of +the result. +.TP +d +Compute the Delaunay triangulation by lifting the input points to a +paraboloid. The 'o' option prints the input points and facets. +The 'QJ' option guarantees triangular output. The 'Ft' +option prints a triangulation. It adds points (the centrums) to non\[hy]simplicial +facets. +.TP +v +Compute the Voronoi diagram from the Delaunay triangulation. +The 'p' option prints the Voronoi vertices. +The 'o' option prints the Voronoi vertices and the +vertices in each Voronoi region. It lists regions in +site ID order. +The 'Fv' option prints each ridge of the Voronoi diagram. +The first or zero'th vertex +indicates the infinity vertex. Its coordinates are +qh_INFINITE (\-10.101). It indicates unbounded Voronoi +regions or degenerate Delaunay triangles. +.TP +Hn,n,... +Compute halfspace intersection about [n,n,0,...]. +The input is a set of halfspaces +defined in the same format as 'n', 'Fo', and 'Fi'. +Use 'Fp' to print the intersection points. Use 'Fv' +to list the intersection points for each halfspace. The +other output formats display the dual convex hull. + +The point [n,n,n,...] is a feasible point for the halfspaces, i.e., +a point that is inside all +of the halfspaces (Hx+b <= 0). The default coordinate value is 0. + +The input may start with a feasible point. If so, use 'H' by itself. +The input starts with a feasible point when the first number is the dimension, +the second number is "1", and the coordinates complete a line. The 'FV' +option produces a feasible point for a convex hull. +.TP +d Qu +Compute the furthest\[hy]site Delaunay triangulation from the upper +convex hull. The 'o' option prints the input points and facets. +The 'QJ' option guarantees triangular otuput. You can also use 'Ft' +to triangulate via the centrums of non\[hy]simplicial +facets. +.TP +v Qu +Compute the furthest\[hy]site Voronoi diagram. +The 'p' option prints the Voronoi vertices. +The 'o' option prints the Voronoi vertices and the +vertices in each Voronoi region. +The 'Fv' option prints each ridge of the Voronoi diagram. +The first or zero'th vertex +indicates the infinity vertex at infinity. Its coordinates are +qh_INFINITE (\-10.101). It indicates unbounded Voronoi regions +and degenerate Delaunay triangles. +.PP +.TP +Input/Output options: +.TP +f +Print out all facets and all fields of each facet. +.TP +G +Output the hull in Geomview format. For imprecise hulls, +Geomview displays the inner and outer hull. Geomview can also +display points, ridges, vertices, coplanar points, and +facet intersections. See below for a list of options. + +For Delaunay triangulations, 'G' displays the +corresponding paraboloid. For halfspace intersection, 'G' displays the +dual polytope. +.TP +i +Output the incident vertices for each facet. +Qhull prints the number of facets followed by the +vertices of each facet. One facet is printed per line. The numbers +are the 0\[hy]relative indices of the corresponding input points. +The facets +are oriented. + +In 4d and higher, +Qhull triangulates non\[hy]simplicial facets. Each apex (the first vertex) is +a created point that corresponds to the facet's centrum. Its index is greater +than the indices of the input points. Each base +corresponds to a simplicial ridge between two facets. +To print the vertices without triangulation, use option 'Fv'. +.TP +m +Output the hull in Mathematica format. Qhull writes a Mathematica file for 2\[hy]d and 3\[hy]d +convex hulls and for 2\[hy]d Delaunay triangulations. Qhull produces a list of objects +that you can assign to a variable in Mathematica, for example: +"list= << ". If the object is 2\[hy]d, it can be +visualized by "Show[Graphics[list]] ". For 3\[hy]d objects the command is +"Show[Graphics3D[list]]". +.TP +n +Output the normal equation for each facet. +Qhull prints the dimension (plus one), the number of facets, +and the normals for each facet. The facet's offset follows its +normal coefficients. +.TP +o +Output the facets in OFF file format. +Qhull prints the dimension, number of points, number +of facets, and number of ridges. Then it prints the coordinates of +the input points and the vertices for each facet. Each facet is on +a separate line. The first number is the number of vertices. The +remainder are the indices of the corresponding points. The vertices are +oriented in 2\[hy]d, 3\[hy]d, and in simplicial facets. + +For 2\[hy]d Voronoi diagrams, +the vertices are sorted by adjacency, but not oriented. In 3\[hy]d and higher, +the Voronoi vertices are sorted by index. +See the 'v' option for more information. +.TP +p +Output the coordinates of each vertex point. +Qhull prints the dimension, the number of points, +and the coordinates for each vertex. +With the 'Gc' and 'Gi' options, it also prints coplanar +and interior points. For Voronoi diagrams, it prints the coordinates +of each Voronoi vertex. +.TP +s +Print a summary to stderr. If no output options +are specified at all, a summary goes to stdout. The summary lists +the number of input points, the dimension, the number of vertices +in the convex hull, the number of facets in the convex hull, the +number of good facets (if 'Pg'), and statistics. + +The last two statistics (if needed) measure the maximum distance +from a point or vertex to a +facet. The number in parenthesis (e.g., 2.1x) is the ratio between the +maximum distance and the worst\[hy]case distance due to merging +two simplicial facets. +.PP +.TP +Precision options +.TP +An +Maximum angle given as a cosine. If the angle between a pair of facet +normals +is greater than n, Qhull merges one of the facets into a neighbor. +If 'n' is negative, Qhull tests angles after adding +each point to the hull (pre\[hy]merging). +If 'n' is positive, Qhull tests angles after +constructing the hull (post\[hy]merging). +Both pre\[hy] and post\[hy]merging can be defined. + +Option 'C0' or 'C\-0' is set if the corresponding 'Cn' or 'C\-n' +is not set. If 'Qx' +is set, then 'A\-n' and 'C\-n' are checked after the hull is constructed +and before 'An' and 'Cn' are checked. +.TP +Cn +Centrum radius. +If a centrum is less than n below a neighboring facet, Qhull merges one +of the facets. +If 'n' is negative or '\-0', Qhull tests and merges facets after adding +each point to the hull. This is called "pre\[hy]merging". If 'n' is positive, +Qhull tests for convexity after constructing the hull ("post\[hy]merging"). +Both pre\[hy] and post\[hy]merging can be defined. + +For 5\[hy]d and higher, 'Qx' should be used +instead of 'C\-n'. Otherwise, most or all facets may be merged +together. +.TP +En +Maximum roundoff error for distance computations. +.TP +Rn +Randomly perturb distance computations up to +/\- n * max_coord. +This option perturbs every distance, hyperplane, and angle computation. +To use time as the random number seed, use option 'QR\-1'. +.TP +Vn +Minimum distance for a facet to be visible. +A facet is visible if the distance from the point to the +facet is greater than 'Vn'. + +Without merging, the default value for 'Vn' is the round\[hy]off error ('En'). +With merging, the default value is the pre\[hy]merge centrum ('C\-n') in 2\[hy]d or +3\[hy]d, or three times that in other dimensions. If the outside width +is specified ('Wn'), the maximum, default value for 'Vn' is 'Wn'. +.TP +Un +Maximum distance below a facet for a point to be coplanar to the facet. The +default value is 'Vn'. +.TP +Wn +Minimum outside width of the hull. Points are added to the convex hull +only if they are clearly outside of a facet. A point is outside of a +facet if its distance to the facet is greater than 'Wn'. The normal +value for 'Wn' is 'En'. If the user specifies pre\[hy]merging and +does not set 'Wn', than 'Wn' is set +to the premerge 'Cn' and maxcoord*(1\-An). +.PP +.TP +Additional input/output formats +.TP +Fa +Print area for each facet. +For Delaunay triangulations, the area is the area of the triangle. +For Voronoi diagrams, the area is the area of the dual facet. +Use 'PAn' for printing the n largest facets, and option 'PFn' for +printing facets larger than 'n'. + +The area for non\[hy]simplicial facets is the sum of the +areas for each ridge to the centrum. Vertices far below +the facet's hyperplane are ignored. +The reported area may be significantly less than the actual area. +.TP +FA +Compute the total area and volume for option 's'. It is an approximation +for non\[hy]simplicial facets (see 'Fa'). +.TP +Fc +Print coplanar points for each facet. The output starts with the +number of facets. Then each facet is printed one per line. Each line +is the number of coplanar points followed by the point ids. +Option 'Qi' includes the interior points. Each coplanar point (interior point) is +assigned to the facet it is furthest above (resp., least below). +.TP +FC +Print centrums for each facet. The output starts with the +dimension followed by the number of facets. +Then each facet centrum is printed, one per line. +.TP +Fd +Read input in cdd format with homogeneous points. +The input starts with comments. The first comment is reported in +the summary. +Data starts after a "begin" line. The next line is the number of points +followed by the dimension+1 and "real" or "integer". Then the points +are listed with a leading "1" or "1.0". The data ends with an "end" line. + +For halfspaces ('Fd Hn,n,...'), the input format is the same. Each halfspace +starts with its offset. The sign of the offset is the opposite of Qhull's +convention. +.TP +FD +Print normals ('n', 'Fo', 'Fi') or points ('p') in cdd format. +The first line is the command line that invoked Qhull. +Data starts with a "begin" line. The next line is the number of normals or points +followed by the dimension+1 and "real". Then the normals or points +are listed with the offset before the coefficients. The offset for points is +1.0. The offset for normals has the opposite sign. +The data ends with an "end" line. +.TP +FF +Print facets (as in 'f') without printing the ridges. +.TP +Fi +Print inner planes for each facet. The inner plane is below all vertices. +.TP +Fi +Print separating hyperplanes for bounded, inner regions of the Voronoi +diagram. The first line is the number +of ridges. Then each hyperplane is printed, one per line. A line starts +with the number of indices and floats. The first pair lists +adjacent input +sites, the next d floats are the normalized coefficients for the hyperplane, +and the last float is the offset. The hyperplane is oriented toward 'QVn' +(if defined), or the first input site of the pair. Use 'Tv' to +verify that the hyperplanes are perpendicular bisectors. Use 'Fo' for +unbounded regions, and 'Fv' for the corresponding Voronoi vertices. +.TP +FI +Print facet identifiers. +.TP +Fm +Print number of merges for each facet. At most 511 merges are reported for +a facet. See 'PMn' for printing the facets with the most merges. +.TP +FM +Output the hull in Maple format. Qhull writes a Maple +file for 2\[hy]d and 3\[hy]d +convex hulls and for 2\[hy]d Delaunay triangulations. Qhull produces a '.mpl' +file for displaying with display3d(). +.TP +Fn +Print neighbors for each facet. The output starts with the number of facets. +Then each facet is printed one per line. Each line +is the number of neighbors followed by an index for each neighbor. The indices +match the other facet output formats. + +A negative index indicates an unprinted +facet due to printing only good facets ('Pg'). It is the negation of the facet's +ID (option 'FI'). +For example, negative indices are used for facets +"at infinity" in the Delaunay triangulation. +.TP +FN +Print vertex neighbors or coplanar facet for each point. +The first line is the number +of points. Then each point is printed, one per line. If the +point is coplanar, the line is "1" followed by the facet's ID. +If the point is +not a selected vertex, the line is "0". +Otherwise, each line is the number of +neighbors followed by the corresponding facet indices (see 'Fn'). +.TP +Fo +Print outer planes for each facet in the same format as 'n'. +The outer plane is above all points. +.TP +Fo +Print separating hyperplanes for unbounded, outer regions of the Voronoi +diagram. The first line is the number +of ridges. Then each hyperplane is printed, one per line. A line starts +with the number of indices and floats. The first pair lists +adjacent input +sites, the next d floats are the normalized coefficients for the hyperplane, +and the last float is the offset. The hyperplane is oriented toward 'QVn' +(if defined), or the first input site of the pair. Use 'Tv' to +verify that the hyperplanes are perpendicular bisectors. Use 'Fi' for +bounded regions, and 'Fv' for the corresponding Voronoi vertices. +.TP +FO +List all options to stderr, including the default values. Additional 'FO's +are printed to stdout. +.TP +Fp +Print points for halfspace intersections (option 'Hn,n,...'). Each +intersection corresponds to a facet of the dual polytope. +The "infinity" point [\-10.101,\-10.101,...] +indicates an unbounded intersection. +.TP +FP +For each coplanar point ('Qc') print the point ID of the nearest vertex, +the point ID, the facet ID, and the distance. +.TP +FQ +Print command used for qhull and input. +.TP +Fs +Print a summary. The first line consists of the number of integers ("8"), +followed by the dimension, the number of points, the number of vertices, +the number of facets, the number of vertices selected for output, the +number of facets selected for output, the number of coplanar points selected +for output, number of simplicial, unmerged facets in output + +The second line consists of the number of reals ("2"), +followed by the maxmimum offset to an outer plane and and minimum offset to +an inner plane. Roundoff is included. Later +versions of Qhull may produce additional integers or reals. +.TP +FS +Print the size of the hull. The first line consists of the number of integers ("0"). +The second line consists of the number of reals ("2"), +followed by the total facet area, and the total volume. +Later +versions of Qhull may produce additional integers or reals. + +The total volume measures the volume +of the intersection of the halfspaces defined by each facet. +Both area and volume are +approximations for non\[hy]simplicial facets. See option 'Fa'. +.TP +Ft +Print a triangulation with added points for non\[hy]simplicial +facets. The first line is the dimension and the second line is the +number of points and the number of facets. The points follow, one +per line, then the facets follow as a list of point indices. With option 'Qz', the +points include the point\[hy]at\[hy]infinity. +.TP +Fv +Print vertices for each facet. The first line is the number +of facets. Then each facet is printed, one per line. Each line is +the number of vertices followed by the corresponding point ids. Vertices +are listed in the order they were added to the hull (the last one is first). +.TP +Fv +Print all ridges of a Voronoi diagram. The first line is the number +of ridges. Then each ridge is printed, one per line. A line starts +with the number of indices. The first pair lists adjacent input +sites, the remaining indices list Voronoi vertices. Vertex '0' indicates +the vertex\[hy]at\[hy]infinity (i.e., an unbounded ray). In 3\[hy]d, the vertices +are listed in order. See 'Fi' and 'Fo' for separating hyperplanes. +.TP +FV +Print average vertex. The average vertex is a feasible point +for halfspace intersection. +.TP +Fx +List extreme points (vertices) of the convex hull. The first line +is the number of points. The other lines give the indices of the +corresponding points. The first point is '0'. In 2\[hy]d, the points +occur in counter\[hy]clockwise order; otherwise they occur in input order. +For Delaunay triangulations, 'Fx' lists the extreme points of the +input sites. The points are unordered. +.PP +.TP +Geomview options +.TP +G +Produce a file for viewing with Geomview. Without other options, +Qhull displays edges in 2\[hy]d, outer planes in 3\[hy]d, and ridges in 4\[hy]d. +A ridge can be +explicit or implicit. An explicit ridge is a dim\-1 dimensional simplex +between two facets. +In 4\[hy]d, the explicit ridges are triangles. +When displaying a ridge in 4\[hy]d, Qhull projects the ridge's vertices to +one of its facets' hyperplanes. +Use 'Gh' to +project ridges to the intersection of both hyperplanes. +.TP +Ga +Display all input points as dots. +.TP +Gc +Display the centrum for each facet in 3\[hy]d. The centrum is defined by a +green radius sitting on a blue plane. The plane corresponds to the +facet's hyperplane. +The radius is defined by 'C\-n' or 'Cn'. +.TP +GDn +Drop dimension n in 3\[hy]d or 4\[hy]d. The result is a 2\[hy]d or 3\[hy]d object. +.TP +Gh +Display hyperplane intersections in 3\[hy]d and 4\[hy]d. In 3\[hy]d, the +intersection is a black line. It lies on two neighboring hyperplanes +(c.f., the blue squares associated with centrums ('Gc')). In 4\[hy]d, +the ridges are projected to the intersection of both hyperplanes. +.TP +Gi +Display inner planes in 2\[hy]d and 3\[hy]d. The inner plane of a facet +is below all of its vertices. It is parallel to the facet's hyperplane. +The inner plane's color is the opposite (1\-r,1\-g,1\-b) of the outer +plane. Its edges are determined by the vertices. +.TP +Gn +Do not display inner or outer planes. By default, +Geomview displays the precise plane (no merging) or both +inner and output planes (merging). Under merging, Geomview does +not display the inner plane if the +the difference between inner and outer is too small. +.TP +Go +Display outer planes in 2\[hy]d and 3\[hy]d. The outer plane of a facet +is above all input points. It is parallel to the facet's hyperplane. +Its color is determined by the facet's normal, and its +edges are determined by the vertices. +.TP +Gp +Display coplanar points and vertices as radii. A radius defines a ball +which corresponds to the imprecision of the point. The imprecision is +the maximum of the roundoff error, the centrum radius, and maxcoord * +(1\-An). It is at least 1/20'th of the maximum coordinate, +and ignores post\[hy]merging if pre\[hy]merging is done. +.TP +Gr +Display ridges in 3\[hy]d. A ridge connects the two vertices that are shared +by neighboring facets. Ridges are always displayed in 4\[hy]d. +.TP +Gt +A 3\[hy]d Delaunay triangulation looks like a convex hull with interior +facets. Option 'Gt' removes the outside ridges to reveal the outermost +facets. It automatically sets options 'Gr' and 'GDn'. +.TP +Gv +Display vertices as spheres. The radius of the sphere corresponds to +the imprecision of the data. See 'Gp' for determining the radius. +.PP +.TP +Print options +.TP +PAn +Only the n largest facets are marked good for printing. +Unless 'PG' is set, 'Pg' is automatically set. +.TP +Pdk:n +Drop facet from output if normal[k] <= n. The option 'Pdk' uses the +default value of 0 for n. +.TP +PDk:n +Drop facet from output if normal[k] >= n. The option 'PDk' uses the +default value of 0 for n. +.TP +PFn +Only facets with area at least 'n' are marked good for printing. +Unless 'PG' is set, 'Pg' is automatically set. +.TP +Pg +Print only good facets. A good facet is either visible from a point +(the 'QGn' option) or includes a point (the 'QVn' option). It also meets the +requirements of 'Pdk' and 'PDk' options. Option 'Pg' is automatically +set for options 'PAn' and 'PFn'. +.TP +PG +Print neighbors of good facets. +.TP +PMn +Only the n facets with the most merges are marked good for printing. +Unless 'PG' is set, 'Pg' is automatically set. +.TP +Po +Force output despite precision problems. Verify ('Tv') does not check +coplanar points. +Flipped facets are reported and concave facets are counted. +If 'Po' is used, points are not +partitioned into flipped facets and a flipped facet is always visible +to a point. +Also, if an error occurs before the completion of Qhull and tracing is +not active, 'Po' outputs a neighborhood of the erroneous facets +(if any). +.TP +Pp +Do not report precision problems. +.PP +.TP +Qhull control options +.TP +Qbk:0Bk:0 +Drop dimension k from the input points. This allows the user to +take convex hulls of sub\[hy]dimensional objects. It happens before +the Delaunay and Voronoi transformation. +.TP +QbB +Scale the input points to fit the unit cube. After scaling, the lower +bound will be \-0.5 and the upper bound +0.5 in all dimensions. +For Delaunay and +Voronoi diagrams, scaling happens after projection to the paraboloid. +Under precise +arithmetic, scaling does not change the topology of the convex hull. +.TP +Qbb +Scale the last coordinate to [0, m] where m is the maximum absolute +value of the other coordinates. For Delaunay and +Voronoi diagrams, scaling happens after projection to the paraboloid. +It reduces roundoff error for inputs with integer coordinates. +Under precise +arithmetic, scaling does not change the topology of the convex hull. +.TP +Qbk:n +Scale the k'th coordinate of the input points. After scaling, the lower +bound of the input points will be n. 'Qbk' scales to \-0.5. +.TP +QBk:n +Scale the k'th coordinate of the input points. After scaling, the upper +bound will be n. 'QBk' scales to +0.5. +.TP +Qc +Keep coplanar points with the nearest facet. Output +formats 'p', 'f', 'Gp', 'Fc', 'FN', and 'FP' will print the points. +.TP +Qf +Partition points to the furthest outside facet. +.TP +Qg +Only build good facets. With the 'Qg' option, Qhull will only build +those facets that it needs to determine the good facets in the output. +See 'QGn', 'QVn', and 'PdD' for defining good facets, and 'Pg' and 'PG' +for printing good facets and their neighbors. +.TP +QGn +A facet is good (see 'Qg' and 'Pg') if it is visible from point n. If n < 0, a facet is +good if it is not visible from point n. Point n is not added to the +hull (unless 'TCn' or 'TPn'). +With rbox, use the 'Pn,m,r' option to define your point; it +will be point 0 (QG0). +.TP +Qi +Keep interior points with the nearest facet. +Output formats 'p', 'f', 'Gp', 'FN', 'FP', and 'Fc' will print the points. +.TP +QJn +Joggle each input coordinate by adding a random number in [\-n,n]. If a +precision error occurs, then qhull increases n and tries again. It does +not increase n beyond a certain value, and it stops after a certain number +of attempts [see user.h]. Option 'QJ' +selects a default value for n. The output will be simplicial. For +Delaunay triangulations, 'QJn' sets 'Qbb' to scale the last coordinate +(not if 'Qbk:n' or 'QBk:n' is set). +\'QJn' is deprecated for Voronoi diagrams. See also 'Qt'. +.TP +Qm +Only process points that would otherwise increase max_outside. Other +points are treated as coplanar or interior points. +.TP +Qr +Process random outside points instead of furthest ones. This makes +Qhull equivalent to the randomized incremental algorithms. CPU time +is not reported since the randomization is inefficient. +.TP +QRn +Randomly rotate the input points. If n=0, use time as the random number seed. +If n>0, use n as the random number seed. If n=\-1, don't rotate but use +time as the random number seed. For Delaunay triangulations ('d' and 'v'), +rotate about the last axis. +.TP +Qs +Search all points for the initial simplex. +.TP +Qt +Triangulated output. Triangulate all non\[hy]simplicial facets. +\'Qt' is deprecated for Voronoi diagrams. See also 'Qt'. +.TP +Qv +Test vertex neighbors for convexity after post\[hy]merging. +To use the 'Qv' option, you also need to set a merge option +(e.g., 'Qx' or 'C\-0'). +.TP +QVn +A good facet (see 'Qg' and 'Pg') includes point n. If n<0, then a good facet does not +include point n. The point is either in the initial simplex or it +is the first point added to the hull. Option 'QVn' may not be used with merging. +.TP +Qx +Perform exact merges while building the hull. The "exact" merges +are merging a point into a coplanar facet (defined by 'Vn', 'Un', +and 'C\-n'), merging concave facets, merging duplicate ridges, and +merging flipped facets. Coplanar merges and angle coplanar merges ('A\-n') +are not performed. Concavity testing is delayed until a merge occurs. + +After +the hull is built, all coplanar merges are performed (defined by 'C\-n' +and 'A\-n'), then post\[hy]merges are performed +(defined by 'Cn' and 'An'). +.TP +Qz +Add a point "at infinity" that is above the paraboloid for Delaunay triangulations +and Voronoi diagrams. This reduces precision problems and allows the triangulation +of cospherical points. +.PP +.TP +Qhull experiments and speedups +.TP +Q0 +Turn off pre\[hy]merging as a default option. +With 'Q0'/'Qx' and without explicit pre\[hy]merge options, Qhull +ignores precision issues while constructing the convex hull. This +may lead to precision errors. If so, a descriptive warning is +generated. +.TP +Q1 +With 'Q1', Qhull sorts merges by type (coplanar, angle coplanar, concave) +instead of by angle. +.TP +Q2 +With 'Q2', Qhull merges all facets at once instead of using +independent sets of merges and then retesting. +.TP +Q3 +With 'Q3', Qhull does not remove redundant vertices. +.TP +Q4 +With 'Q4', Qhull avoids merges of an old facet into a new facet. +.TP +Q5 +With 'Q5', Qhull does not correct outer planes at the end. The +maximum outer plane is used instead. +.TP +Q6 +With 'Q6', Qhull does not pre\[hy]merge concave or coplanar facets. +.TP +Q7 +With 'Q7', Qhull processes facets in depth\[hy]first order instead of +breadth\[hy]first order. +.TP +Q8 +With 'Q8' and merging, Qhull does not retain near\[hy]interior points for adjusting +outer planes. 'Qc' will probably retain +all points that adjust outer planes. +.TP +Q9 +With 'Q9', Qhull processes the furthest of all outside sets at each iteration. +.TP +Q10 +With 'Q10', Qhull does not use special processing for narrow distributions. +.TP +Q11 +With 'Q11', Qhull copies normals and recompute centrums for tricoplanar facets. +.TP +Q12 +With 'Q12', Qhull does not report a very wide merge due to a duplicated ridge with nearly coincident vertices +.PP +.TP +Trace options +.TP +Tn +Trace at level n. Qhull includes full execution tracing. 'T\-1' +traces events. 'T1' traces +the overall execution of the program. 'T2' and 'T3' trace overall +execution and geometric and topological events. 'T4' traces the +algorithm. 'T5' includes information about memory allocation and +Gaussian elimination. +.TP +Ta +Annotate output with codes that identify the +corresponding qh_fprintf() statement. +.TP +Tc +Check frequently during execution. This will catch most inconsistency +errors. +.TP +TCn +Stop Qhull after building the cone of new facets for point n. The +output for 'f' includes the cone and the old hull. +See also 'TVn'. +.TP +TFn +Report progress whenever more than n facets are created +During post\[hy]merging, 'TFn' +reports progress after more than n/2 merges. +.TP +TI file +Input data from 'file'. The filename may not include spaces or +quotes. +.TP +TO file +Output results to 'file'. The name may be enclosed in single +quotes. +.TP +TPn +Turn on tracing when point n is added to the hull. Trace +partitions of point n. If used with TWn, turn off +tracing after adding point n to the hull. +.TP +TRn +Rerun qhull n times. Usually used with 'QJn' to determine the +probability that a given joggle will fail. +.TP +Ts +Collect statistics and print to stderr at the end of execution. +.TP +Tv +Verify the convex hull. This checks the topological structure, facet +convexity, and point inclusion. +If precision problems occurred, facet convexity is tested whether or +not 'Tv' is selected. +Option 'Tv' does not check point inclusion if forcing output with 'Po', +or if 'Q5' is set. + +For point inclusion testing, Qhull verifies that all points are below +all outer planes (facet\->maxoutside). Point inclusion is exhaustive +if merging or if the facet\[hy]point product is small enough; +otherwise Qhull verifies each point with a directed +search (qh_findbest). + +Point inclusion testing occurs after producing output. It prints +a message to stderr unless option 'Pp' is used. This +allows the user to interrupt Qhull without changing the output. +.TP +TVn +Stop Qhull after adding point n. If n < 0, stop Qhull before adding +point n. Output shows the hull at this time. See also 'TCn' +.TP +TMn +Turn on tracing at n'th merge. +.TP +TWn +Trace merge facets when the width is greater than n. +.TP +Tz +Redirect stderr to stdout. +.PP +.SH BUGS +Please report bugs to Brad Barber at qhull_bug@qhull.org. + +If Qhull does not compile, it is due to an incompatibility between your +system and ours. The first thing to check is that your compiler is +ANSI standard. If it is, check the man page for the best options, or +find someone to help you. If you locate the cause of your problem, +please send email since it might help others. + +If Qhull compiles but crashes on the test case (rbox D4), there's +still incompatibility between your system and ours. Typically it's +been due to mem.c and memory alignment. You can use qh_NOmem in mem.h +to turn off memory management. Please let us know if you figure out +how to fix these problems. + +If you do find a problem, try to simplify it before reporting the +error. Try different size inputs to locate the smallest one that +causes an error. You're welcome to hunt through the code using the +execution trace as a guide. This is especially true if you're +incorporating Qhull into your own program. + +When you do report an error, please attach a data set to the +end of your message. This allows us to see the error for ourselves. +Qhull is maintained part\[hy]time. +.PP +.SH E\[hy]MAIL +Please send correspondence to qhull@qhull.org and report bugs to +qhull_bug@qhull.org. Let us know how you use Qhull. If you +mention it in a paper, please send the reference and an abstract. + +If you would like to get Qhull announcements (e.g., a new version) +and news (any bugs that get fixed, etc.), let us know and we will add you to +our mailing list. If you would like to communicate with other +Qhull users, we will add you to the qhull_users alias. +For Internet news about geometric algorithms and convex hulls, look at +comp.graphics.algorithms and sci.math.num\-analysis + +.SH SEE ALSO +rbox(1) + +Barber, C. B., D.P. Dobkin, and H.T. Huhdanpaa, +"The Quickhull Algorithm for Convex Hulls," ACM +Trans. on Mathematical Software, 22(4):469\[en]483, Dec. 1996. +http://portal.acm.org/citation.cfm?doid=235815.235821 +http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.117.405 + +Clarkson, K.L., K. Mehlhorn, and R. Seidel, "Four results on randomized +incremental construction," Computational Geometry: Theory and Applications, +vol. 3, p. 185\[en]211, 1993. + +Preparata, F. and M. Shamos, Computational +Geometry, Springer\[hy]Verlag, New York, 1985. + +.PP +.SH AUTHORS +.nf + C. Bradford Barber Hannu Huhdanpaa + bradb@shore.net hannu@qhull.org + + .fi + +.SH ACKNOWLEDGEMENTS + +A special thanks to Albert Marden, Victor Milenkovic, the Geometry Center, +Harvard University, and Endocardial Solutions, Inc. for supporting this work. + +Qhull 1.0 and 2.0 were developed under National Science Foundation +grants NSF/DMS\[hy]8920161 and NSF\[hy]CCR\[hy]91\[hy]15793 750\[hy]7504. David Dobkin +guided the original work at Princeton University. +If you find it useful, please let us know. + +The Geometry Center is supported by grant DMS\[hy]8920161 from the National +Science Foundation, by grant DOE/DE\[hy]FG02\[hy]92ER25137 from the Department +of Energy, by the University of Minnesota, and by Minnesota Technology, Inc. + +Qhull is available from http://www.qhull.org diff --git a/xs/src/qhull/html/qhull.txt b/xs/src/qhull/html/qhull.txt new file mode 100644 index 0000000000..03753547e9 --- /dev/null +++ b/xs/src/qhull/html/qhull.txt @@ -0,0 +1,1263 @@ + + + +qhull(1) qhull(1) + + +NAME + qhull - convex hull, Delaunay triangulation, Voronoi dia- + gram, halfspace intersection about a point, hull volume, facet area + +SYNOPSIS + qhull- compute convex hulls and related structures + input (stdin): dimension, #points, point coordinates + first comment (non-numeric) is listed in the summary + halfspace: use dim plus one with offsets after coefficients + + options (qh-quick.htm): + d - Delaunay triangulation by lifting points to a paraboloid + v - Voronoi diagram via the Delaunay triangulation + H1,1 - Halfspace intersection about [1,1,0,...] + d Qu - Furthest-site Delaunay triangulation (upper convex hull) + v Qu - Furthest-site Voronoi diagram + QJ - Joggle the input to avoid precision problems + . - concise list of all options + - - one-line description of all options + + Output options (subset): + FA - compute total area and volume + Fx - extreme points (convex hull vertices) + G - Geomview output (2-d, 3-d and 4-d) + Fp - halfspace intersection coordinates + m - Mathematica output (2-d and 3-d) + n - normals with offsets + o - OFF file format (if Voronoi, outputs regions) + TO file- output results to file, may be enclosed in single quotes + f - print all fields of all facets + s - summary of results (default) + Tv - verify result: structure, convexity, and point inclusion + p - vertex coordinates + i - vertices incident to each facet + + example: + rbox 1000 s | qhull Tv s FA + + - html manual: index.htm + - installation: README.txt + - see also: COPYING.txt, REGISTER.txt, Changes.txt + - WWW: + - GIT: + - mirror: + - news: + - Geomview: + - news group: + - FAQ: + - email: qhull@qhull.org + - bug reports: qhull_bug@qhull.org + + + + +Geometry Center 2003/12/30 1 + + + + + +qhull(1) qhull(1) + + + The sections are: + - INTRODUCTION + - DESCRIPTION, a description of Qhull + - IMPRECISION, how Qhull handles imprecision + - OPTIONS + - Input and output options + - Additional input/output formats + - Precision options + - Geomview options + - Print options + - Qhull options + - Trace options + - BUGS + - E-MAIL + - SEE ALSO + - AUTHORS + - ACKNOWLEGEMENTS + + This man page briefly describes all Qhull options. Please + report any mismatches with Qhull's html manual (qh- + man.htm). + + + +INTRODUCTION + Qhull is a general dimension code for computing convex + hulls, Delaunay triangulations, Voronoi diagram, furthest- + site Voronoi diagram, furthest-site Delaunay triangula- + tions, and halfspace intersections about a point. It + implements the Quickhull algorithm for computing the con- + vex hull. Qhull handles round-off errors from floating + point arithmetic. It can approximate a convex hull. + + The program includes options for hull volume, facet area, + partial hulls, input transformations, randomization, trac- + ing, multiple output formats, and execution statistics. + The program can be called from within your application. + You can view the results in 2-d, 3-d and 4-d with + Geomview. + + +DESCRIPTION + The format of input is the following: first line contains + the dimension, second line contains the number of input + points, and point coordinates follow. The dimension and + number of points can be reversed. Comments and line + breaks are ignored. A comment starts with a non-numeric + character and continues to the end of line. The first + comment is reported in summaries and statistics. Error + reporting is better if there is one point per line. + + The default printout option is a short summary. There are + many other output formats. + + + + +Geometry Center 2003/12/30 2 + + + + + +qhull(1) qhull(1) + + + Qhull implements the Quickhull algorithm for convex hull. + This algorithm combines the 2-d Quickhull algorithm with + the n-d beneath-beyond algorithm [c.f., Preparata & Shamos + '85]. It is similar to the randomized algorithms of + Clarkson and others [Clarkson et al. '93]. The main + advantages of Quickhull are output sensitive performance, + reduced space requirements, and automatic handling of pre- + cision problems. + + The data structure produced by Qhull consists of vertices, + ridges, and facets. A vertex is a point of the input set. + A ridge is a set of d vertices and two neighboring facets. + For example in 3-d, a ridge is an edge of the polyhedron. + A facet is a set of ridges, a set of neighboring facets, a + set of incident vertices, and a hyperplane equation. For + simplicial facets, the ridges are defined by the vertices + and neighboring facets. When Qhull merges two facets, it + produces a non-simplicial facet. A non-simplicial facet + has more than d neighbors and may share more than one + ridge with a neighbor. + + +IMPRECISION + Since Qhull uses floating point arithmetic, roundoff error + may occur for each calculation. This causes problems for + most geometric algorithms. + + Qhull automatically sets option 'C-0' in 2-d, 3-d, and + 4-d, or option 'Qx' in 5-d and higher. These options han- + dle precision problems by merging facets. Alternatively, + use option 'QJ' to joggle the input. + + With 'C-0', Qhull merges non-convex facets while con- + structing the hull. The remaining facets are clearly con- + vex. With 'Qx', Qhull merges coplanar horizon facets, + flipped facets, concave facets and duplicated ridges. It + merges coplanar facets after constructing the hull. With + 'Qx', coplanar points may be missed, but it appears to be + unlikely. + + To guarantee triangular output, joggle the input with + option 'QJ'. Facet merging will not occur. + +OPTIONS + To get a list of the most important options, execute + 'qhull' by itself. To get a complete list of options, + execute 'qhull -'. To get a complete, concise list of + options, execute 'qhull .'. + + Options can be in any order. Capitalized options take an + argument (except 'PG' and 'F' options). Single letters + are used for output formats and precision constants. The + other options are grouped into menus for other output for- + mats ('F'), Geomview output ('G'), printing ('P'), Qhull + + + +Geometry Center 2003/12/30 3 + + + + + +qhull(1) qhull(1) + + + control ('Q'), and tracing ('T'). + + Main options: + + default + Compute the convex hull of the input points. + Report a summary of the result. + + d Compute the Delaunay triangulation by lifting the + input points to a paraboloid. The 'o' option + prints the input points and facets. The 'QJ' + option guarantees triangular output. The 'Ft' + option prints a triangulation. It adds points (the + centrums) to non-simplicial facets. + + v Compute the Voronoi diagram from the Delaunay tri- + angulation. The 'p' option prints the Voronoi ver- + tices. The 'o' option prints the Voronoi vertices + and the vertices in each Voronoi region. It lists + regions in site id order. The 'Fv' option prints + each ridge of the Voronoi diagram. The first or + zero'th vertex indicates the infinity vertex. Its + coordinates are qh_INFINITE (-10.101). It indi- + cates unbounded Voronoi regions or degenerate + Delaunay triangles. + + Hn,n,... + Compute halfspace intersection about [n,n,0,...]. + The input is a set of halfspaces defined in the + same format as 'n', 'Fo', and 'Fi'. Use 'Fp' to + print the intersection points. Use 'Fv' to list + the intersection points for each halfspace. The + other output formats display the dual convex hull. + + The point [n,n,n,...] is a feasible point for the + halfspaces, i.e., a point that is inside all of the + halfspaces (Hx+b <= 0). The default coordinate + value is 0. + + The input may start with a feasible point. If so, + use 'H' by itself. The input starts with a feasi- + ble point when the first number is the dimension, + the second number is "1", and the coordinates com- + plete a line. The 'FV' option produces a feasible + point for a convex hull. + + d Qu Compute the furthest-site Delaunay triangulation + from the upper convex hull. The 'o' option prints + the input points and facets. The 'QJ' option guar- + antees triangular otuput. You can also use facets. + + v Qu Compute the furthest-site Voronoi diagram. The 'p' + option prints the Voronoi vertices. The 'o' option + prints the Voronoi vertices and the vertices in + + + +Geometry Center 2003/12/30 4 + + + + + +qhull(1) qhull(1) + + + each Voronoi region. The 'Fv' option prints each + ridge of the Voronoi diagram. The first or zero'th + vertex indicates the infinity vertex at infinity. + Its coordinates are qh_INFINITE (-10.101). It + indicates unbounded Voronoi regions and degenerate + Delaunay triangles. + + Qt Triangulated output. + + + Input/Output options: + + f Print out all facets and all fields of each facet. + + G Output the hull in Geomview format. For imprecise + hulls, Geomview displays the inner and outer hull. + Geomview can also display points, ridges, vertices, + coplanar points, and facet intersections. See + below for a list of options. + + For Delaunay triangulations, 'G' displays the cor- + responding paraboloid. For halfspace intersection, + 'G' displays the dual polytope. + + i Output the incident vertices for each facet. Qhull + prints the number of facets followed by the ver- + tices of each facet. One facet is printed per + line. The numbers are the 0-relative indices of + the corresponding input points. The facets are + oriented. + + In 4-d and higher, Qhull triangulates non-simpli- + cial facets. Each apex (the first vertex) is a + created point that corresponds to the facet's cen- + trum. Its index is greater than the indices of the + input points. Each base corresponds to a simpli- + cial ridge between two facets. To print the ver- + tices without triangulation, use option 'Fv'. + + m Output the hull in Mathematica format. Qhull + writes a Mathematica file for 2-d and 3-d convex + hulls and for 2-d Delaunay triangulations. Qhull + produces a list of objects that you can assign to a + variable in Mathematica, for example: "list= << + ". If the object is 2-d, it can be + visualized by "Show[Graphics[list]] ". For 3-d + objects the command is "Show[Graphics3D[list]]". + + n Output the normal equation for each facet. Qhull + prints the dimension (plus one), the number of + facets, and the normals for each facet. The + facet's offset follows its normal coefficients. + + o Output the facets in OFF file format. Qhull prints + the dimension, number of points, number of facets, + and number of ridges. Then it prints the + + + +Geometry Center 2003/12/30 5 + + + + + +qhull(1) qhull(1) + + + coordinates of the input points and the vertices + for each facet. Each facet is on a separate line. + The first number is the number of vertices. The + remainder are the indices of the corresponding + points. The vertices are oriented in 2-d, 3-d, and + in simplicial facets. + + For 2-d Voronoi diagrams, the vertices are sorted + by adjacency, but not oriented. In 3-d and higher, + the Voronoi vertices are sorted by index. See the + 'v' option for more information. + + p Output the coordinates of each vertex point. Qhull + prints the dimension, the number of points, and the + coordinates for each vertex. With the 'Gc' and + 'Gi' options, it also prints coplanar and interior + points. For Voronoi diagrams, it prints the coor- + dinates of each Voronoi vertex. + + s Print a summary to stderr. If no output options + are specified at all, a summary goes to stdout. + The summary lists the number of input points, the + dimension, the number of vertices in the convex + hull, the number of facets in the convex hull, the + number of good facets (if 'Pg'), and statistics. + + The last two statistics (if needed) measure the + maximum distance from a point or vertex to a facet. + The number in parenthesis (e.g., 2.1x) is the ratio + between the maximum distance and the worst-case + distance due to merging two simplicial facets. + + + Precision options + + An Maximum angle given as a cosine. If the angle + between a pair of facet normals is greater than n, Qhull + merges one of the facets into a neighbor. If 'n' + is negative, Qhull tests angles after adding each + point to the hull (pre-merging). If 'n' is posi- + tive, Qhull tests angles after constructing the + hull (post-merging). Both pre- and post-merging + can be defined. + + Option 'C0' or 'C-0' is set if the corresponding + 'Cn' or 'C-n' is not set. If 'Qx' is set, then 'A- + n' and 'C-n' are checked after the hull is con- + structed and before 'An' and 'Cn' are checked. + + Cn Centrum radius. If a centrum is less than n below + a neighboring facet, Qhull merges one of the + facets. If 'n' is negative or '-0', Qhull tests + and merges facets after adding each point to the + hull. This is called "pre-merging". If 'n' is + + + +Geometry Center 2003/12/30 6 + + + + + +qhull(1) qhull(1) + + + positive, Qhull tests for convexity after con- + structing the hull ("post-merging"). Both pre- and + post-merging can be defined. + + For 5-d and higher, 'Qx' should be used instead of + 'C-n'. Otherwise, most or all facets may be merged + together. + + En Maximum roundoff error for distance computations. + + Rn Randomly perturb distance computations up to +/- n + * max_coord. This option perturbs every distance, + hyperplane, and angle computation. To use time as + the random number seed, use option 'QR-1'. + + Vn Minimum distance for a facet to be visible. A + facet is visible if the distance from the point to + the facet is greater than 'Vn'. + + Without merging, the default value for 'Vn' is the + round-off error ('En'). With merging, the default + value is the pre-merge centrum ('C-n') in 2-d or + 3--d, or three times that in other dimensions. If + the outside width is specified ('Wn'), the maximum, + default value for 'Vn' is 'Wn'. + + Un Maximum distance below a facet for a point to be + coplanar to the facet. The default value is 'Vn'. + + Wn Minimum outside width of the hull. Points are + added to the convex hull only if they are clearly + outside of a facet. A point is outside of a facet + if its distance to the facet is greater than 'Wn'. + The normal value for 'Wn' is 'En'. If the user + specifies pre-merging and does not set 'Wn', than + 'Wn' is set to the premerge 'Cn' and maxco- + ord*(1-An). + + + Additional input/output formats + + Fa Print area for each facet. For Delaunay triangula- + tions, the area is the area of the triangle. For + Voronoi diagrams, the area is the area of the dual + facet. Use 'PAn' for printing the n largest + facets, and option 'PFn' for printing facets larger + than 'n'. + + The area for non-simplicial facets is the sum of + the areas for each ridge to the centrum. Vertices + far below the facet's hyperplane are ignored. The + reported area may be significantly less than the + actual area. + + + + +Geometry Center 2003/12/30 7 + + + + + +qhull(1) qhull(1) + + + FA Compute the total area and volume for option 's'. + It is an approximation for non-simplicial facets + (see 'Fa'). + + Fc Print coplanar points for each facet. The output + starts with the number of facets. Then each facet + is printed one per line. Each line is the number + of coplanar points followed by the point ids. + Option 'Qi' includes the interior points. Each + coplanar point (interior point) is assigned to the + facet it is furthest above (resp., least below). + + FC Print centrums for each facet. The output starts + with the dimension followed by the number of + facets. Then each facet centrum is printed, one + per line. + + Fd Read input in cdd format with homogeneous points. + The input starts with comments. The first comment + is reported in the summary. Data starts after a + "begin" line. The next line is the number of + points followed by the dimension+1 and "real" or + "integer". Then the points are listed with a + leading "1" or "1.0". The data ends with an "end" + line. + + For halfspaces ('Fd Hn,n,...'), the input format is + the same. Each halfspace starts with its offset. + The sign of the offset is the opposite of Qhull's + convention. + + FD Print normals ('n', 'Fo', 'Fi') or points ('p') in + cdd format. The first line is the command line + that invoked Qhull. Data starts with a "begin" + line. The next line is the number of normals or + points followed by the dimension+1 and "real". + Then the normals or points are listed with the + offset before the coefficients. The offset for + points is 1.0. The offset for normals has the + opposite sign. The data ends with an "end" line. + + FF Print facets (as in 'f') without printing the + ridges. + + Fi Print inner planes for each facet. The inner plane + is below all vertices. + + Fi Print separating hyperplanes for bounded, inner + regions of the Voronoi diagram. The first line is + the number of ridges. Then each hyperplane is + printed, one per line. A line starts with the num- + ber of indices and floats. The first pair lists + adjacent input sites, the next d floats are the + normalized coefficients for the hyperplane, and the + + + +Geometry Center 2003/12/30 8 + + + + + +qhull(1) qhull(1) + + + last float is the offset. The hyperplane is ori- + ented toward verify that the hyperplanes are per- + pendicular bisectors. Use 'Fo' for unbounded + regions, and 'Fv' for the corresponding Voronoi + vertices. + + FI Print facet identifiers. + + Fm Print number of merges for each facet. At most 511 + merges are reported for a facet. See 'PMn' for + printing the facets with the most merges. + + FM Output the hull in Maple format. See 'm' + + Fn Print neighbors for each facet. The output starts + with the number of facets. Then each facet is + printed one per line. Each line is the number of + neighbors followed by an index for each neighbor. + The indices match the other facet output formats. + + A negative index indicates an unprinted facet due + to printing only good facets ('Pg'). It is the + negation of the facet's id (option 'FI'). For + example, negative indices are used for facets "at + infinity" in the Delaunay triangulation. + + FN Print vertex neighbors or coplanar facet for each + point. The first line is the number of points. + Then each point is printed, one per line. If the + point is coplanar, the line is "1" followed by the + facet's id. If the point is not a selected vertex, + the line is "0". Otherwise, each line is the num- + ber of neighbors followed by the corresponding + facet indices (see 'Fn'). + + Fo Print outer planes for each facet in the same for- + mat as 'n'. The outer plane is above all points. + + Fo Print separating hyperplanes for unbounded, outer + regions of the Voronoi diagram. The first line is + the number of ridges. Then each hyperplane is + printed, one per line. A line starts with the num- + ber of indices and floats. The first pair lists + adjacent input sites, the next d floats are the + normalized coefficients for the hyperplane, and the + last float is the offset. The hyperplane is ori- + ented toward verify that the hyperplanes are per- + pendicular bisectors. Use 'Fi' for bounded + regions, and 'Fv' for the corresponding Voronoi + vertices. + + FO List all options to stderr, including the default + values. Additional 'FO's are printed to stdout. + + Fp Print points for halfspace intersections (option + 'Hn,n,...'). Each intersection corresponds to a + + + +Geometry Center 2003/12/30 9 + + + +qhull(1) qhull(1) + + + facet of the dual polytope. The "infinity" point + [-10.101,-10.101,...] indicates an unbounded + intersection. + + FP For each coplanar point ('Qc') print the point id + of the nearest vertex, the point id, the facet id, + and the distance. + + FQ Print command used for qhull and input. + + Fs Print a summary. The first line consists of the + number of integers ("7"), followed by the dimen- + sion, the number of points, the number of vertices, + the number of facets, the number of vertices + selected for output, the number of facets selected + for output, the number of coplanar points selected + for output. + + The second line consists of the number of reals + ("2"), followed by the maxmimum offset to an outer + plane and and minimum offset to an inner plane. + Roundoff is included. Later versions of Qhull may + produce additional integers or reals. + + FS Print the size of the hull. The first line con- + sists of the number of integers ("0"). The second + line consists of the number of reals ("2"), fol- + lowed by the total facet area, and the total vol- + ume. Later versions of Qhull may produce addi- + tional integers or reals. + + The total volume measures the volume of the inter- + section of the halfspaces defined by each facet. + Both area and volume are approximations for non- + simplicial facets. See option 'Fa'. + + Ft Print a triangulation with added points for non- + simplicial facets. The first line is the dimension + and the second line is the number of points and the + number of facets. The points follow, one per line, + then the facets follow as a list of point indices. + With option points include the point-at-infinity. + + Fv Print vertices for each facet. The first line is + the number of facets. Then each facet is printed, + one per line. Each line is the number of vertices + followed by the corresponding point ids. Vertices + are listed in the order they were added to the hull + (the last one is first). + + Fv Print all ridges of a Voronoi diagram. The first + line is the number of ridges. Then each ridge is + printed, one per line. A line starts with the num- + ber of indices. The first pair lists adjacent + + + +Geometry Center 2003/12/30 10 + + + + + +qhull(1) qhull(1) + + + input sites, the remaining indices list Voronoi + vertices. Vertex '0' indicates the vertex-at- + infinity (i.e., an unbounded ray). In 3-d, the + vertices are listed in order. See 'Fi' and 'Fo' + for separating hyperplanes. + + FV Print average vertex. The average vertex is a fea- + sible point for halfspace intersection. + + Fx List extreme points (vertices) of the convex hull. + The first line is the number of points. The other + lines give the indices of the corresponding points. + The first point is '0'. In 2-d, the points occur + in counter-clockwise order; otherwise they occur in + input order. For Delaunay triangulations, 'Fx' + lists the extreme points of the input sites. The + points are unordered. + + + Geomview options + + G Produce a file for viewing with Geomview. Without + other options, Qhull displays edges in 2-d, outer + planes in 3-d, and ridges in 4-d. A ridge can be + explicit or implicit. An explicit ridge is a dim-1 + dimensional simplex between two facets. In 4-d, + the explicit ridges are triangles. When displaying + a ridge in 4-d, Qhull projects the ridge's vertices + to one of its facets' hyperplanes. Use 'Gh' to + project ridges to the intersection of both hyper- + planes. + + Ga Display all input points as dots. + + Gc Display the centrum for each facet in 3-d. The + centrum is defined by a green radius sitting on a + blue plane. The plane corresponds to the facet's + hyperplane. The radius is defined by 'C-n' or + 'Cn'. + + GDn Drop dimension n in 3-d or 4-d. The result is a + 2-d or 3-d object. + + Gh Display hyperplane intersections in 3-d and 4-d. + In 3-d, the intersection is a black line. It lies + on two neighboring hyperplanes (c.f., the blue + squares associated with centrums ('Gc')). In 4-d, + the ridges are projected to the intersection of + both hyperplanes. + + Gi Display inner planes in 2-d and 3-d. The inner + plane of a facet is below all of its vertices. It + is parallel to the facet's hyperplane. The inner + plane's color is the opposite (1-r,1-g,1-b) of the + + + +Geometry Center 2003/12/30 11 + + + + + +qhull(1) qhull(1) + + + outer plane. Its edges are determined by the ver- + tices. + + Gn Do not display inner or outer planes. By default, + Geomview displays the precise plane (no merging) or + both inner and output planes (merging). Under + merging, Geomview does not display the inner plane + if the the difference between inner and outer is + too small. + + Go Display outer planes in 2-d and 3-d. The outer + plane of a facet is above all input points. It is + parallel to the facet's hyperplane. Its color is + determined by the facet's normal, and its edges are + determined by the vertices. + + Gp Display coplanar points and vertices as radii. A + radius defines a ball which corresponds to the + imprecision of the point. The imprecision is the + maximum of the roundoff error, the centrum radius, + and maxcoord * (1-An). It is at least 1/20'th of + the maximum coordinate, and ignores post-merging if + pre-merging is done. + + Gr Display ridges in 3-d. A ridge connects the two + vertices that are shared by neighboring facets. + Ridges are always displayed in 4-d. + + Gt A 3-d Delaunay triangulation looks like a convex + hull with interior facets. Option 'Gt' removes the + outside ridges to reveal the outermost facets. It + automatically sets options 'Gr' and 'GDn'. + + Gv Display vertices as spheres. The radius of the + sphere corresponds to the imprecision of the data. + See 'Gp' for determining the radius. + + + Print options + + PAn Only the n largest facets are marked good for + printing. Unless 'PG' is set, 'Pg' is automati- + cally set. + + Pdk:n Drop facet from output if normal[k] <= n. The + option 'Pdk' uses the default value of 0 for n. + + PDk:n Drop facet from output if normal[k] >= n. The + option 'PDk' uses the default value of 0 for n. + + PFn Only facets with area at least 'n' are marked good + for printing. Unless 'PG' is set, 'Pg' is automat- + ically set. + + + + +Geometry Center 2003/12/30 12 + + + + + +qhull(1) qhull(1) + + + Pg Print only good facets. A good facet is either + visible from a point (the 'QGn' option) or includes + a point (the 'QVn' option). It also meets the + requirements of 'Pdk' and 'PDk' options. Option + 'Pg' is automatically set for options 'PAn' and + 'PFn'. + + PG Print neighbors of good facets. + + PMn Only the n facets with the most merges are marked + good for printing. Unless 'PG' is set, 'Pg' is + automatically set. + + Po Force output despite precision problems. Verify ('Tv') does not check + coplanar points. Flipped facets are reported and + concave facets are counted. If 'Po' is used, + points are not partitioned into flipped facets and + a flipped facet is always visible to a point. + Also, if an error occurs before the completion of + Qhull and tracing is not active, 'Po' outputs a + neighborhood of the erroneous facets (if any). + + Pp Do not report precision problems. + + + Qhull control options + + Qbk:0Bk:0 + Drop dimension k from the input points. This + allows the user to take convex hulls of sub-dimen- + sional objects. It happens before the Delaunay and + Voronoi transformation. + + QbB Scale the input points to fit the unit cube. After + scaling, the lower bound will be -0.5 and the upper + bound +0.5 in all dimensions. For Delaunay and + Voronoi diagrams, scaling happens after projection + to the paraboloid. Under precise arithmetic, scal- + ing does not change the topology of the convex + hull. + + Qbb Scale the last coordinate to [0, m] where m is the + maximum absolute value of the other coordinates. + For Delaunay and Voronoi diagrams, scaling happens + after projection to the paraboloid. It reduces + roundoff error for inputs with integer coordinates. + Under precise arithmetic, scaling does not change + the topology of the convex hull. + + Qbk:n Scale the k'th coordinate of the input points. + After scaling, the lower bound of the input points + will be n. 'Qbk' scales to -0.5. + + + +Geometry Center 2003/12/30 13 + + + + + +qhull(1) qhull(1) + + + QBk:n Scale the k'th coordinate of the input points. + After scaling, the upper bound will be n. 'QBk' + scales to +0.5. + + Qc Keep coplanar points with the nearest facet. Out- + put formats 'p', 'f', 'Gp', 'Fc', 'FN', and 'FP' + will print the points. + + Qf Partition points to the furthest outside facet. + + Qg Only build good facets. With the 'Qg' option, + Qhull will only build those facets that it needs to + determine the good facets in the output. See + 'QGn', 'QVn', and 'PdD' for defining good facets, + and 'Pg' and 'PG' for printing good facets and + their neighbors. + + QGn A facet is good (see 'Qg' and 'Pg') if it is visi- + ble from point n. If n < 0, a facet is good if it + is not visible from point n. Point n is not added + to the hull (unless 'TCn' or 'TPn'). With rbox, + use the 'Pn,m,r' option to define your point; it + will be point 0 (QG0). + + Qi Keep interior points with the nearest facet. Out- + put formats 'p', 'f', 'Gp', 'FN', 'FP', and 'Fc' + will print the points. + + QJn Joggle each input coordinate by adding a random + number in [-n,n]. If a precision error occurs, + then qhull increases n and tries again. It does + not increase n beyond a certain value, and it stops + after a certain number of attempts [see user.h]. + Option 'QJ' selects a default value for n. The + output will be simplicial. For Delaunay triangula- + tions, 'QJn' sets 'Qbb' to scale the last coordi- + nate (not if 'Qbk:n' or 'QBk:n' is set). 'QJn' is + deprecated for Voronoi diagrams. See also 'Qt'. + + Qm Only process points that would otherwise increase + max_outside. Other points are treated as coplanar + or interior points. + + Qr Process random outside points instead of furthest + ones. This makes Qhull equivalent to the random- + ized incremental algorithms. CPU time is not + reported since the randomization is inefficient. + + QRn Randomly rotate the input points. If n=0, use time + as the random number seed. If n>0, use n as the + random number seed. If n=-1, don't rotate but use + time as the random number seed. For Delaunay tri- + angulations ('d' and 'v'), rotate about the last + axis. + + + + +Geometry Center 2003/12/30 14 + + + + + +qhull(1) qhull(1) + + + Qs Search all points for the initial simplex. + + Qt Triangulated output. Triangulate non-simplicial + facets. 'Qt' is deprecated for Voronoi diagrams. + See also 'QJn' + + Qv Test vertex neighbors for convexity after post- + merging. To use the 'Qv' option, you also need to + set a merge option (e.g., 'Qx' or 'C-0'). + + QVn A good facet (see 'Qg' and 'Pg') includes point n. + If n<0, then a good facet does not include point n. + The point is either in the initial simplex or it is + the first point added to the hull. Option 'QVn' + may not be used with merging. + + Qx Perform exact merges while building the hull. The + "exact" merges are merging a point into a coplanar + facet (defined by 'Vn', 'Un', and 'C-n'), merging + concave facets, merging duplicate ridges, and merg- + ing flipped facets. Coplanar merges and angle + coplanar merges ('A-n') are not performed. Concav- + ity testing is delayed until a merge occurs. + + After the hull is built, all coplanar merges are + performed (defined by 'C-n' and 'A-n'), then post- + merges are performed (defined by 'Cn' and 'An'). + + Qz Add a point "at infinity" that is above the + paraboloid for Delaunay triangulations and Voronoi + diagrams. This reduces precision problems and + allows the triangulation of cospherical points. + + + Qhull experiments and speedups + + Q0 Turn off pre-merging as a default option. With + 'Q0'/'Qx' and without explicit pre-merge options, + Qhull ignores precision issues while constructing + the convex hull. This may lead to precision + errors. If so, a descriptive warning is generated. + + Q1 With 'Q1', Qhull sorts merges by type (coplanar, + angle coplanar, concave) instead of by angle. + + Q2 With 'Q2', Qhull merges all facets at once instead + of using independent sets of merges and then + retesting. + + Q3 With 'Q3', Qhull does not remove redundant ver- + tices. + + Q4 With 'Q4', Qhull avoids merges of an old facet into + a new facet. + + Q5 With 'Q5', Qhull does not correct outer planes at + the end. The maximum outer plane is used instead. + + + + +Geometry Center 2003/12/30 15 + + + + + +qhull(1) qhull(1) + + + Q6 With 'Q6', Qhull does not pre-merge concave or + coplanar facets. + + Q7 With 'Q7', Qhull processes facets in depth-first + order instead of breadth-first order. + + Q8 With 'Q8' and merging, Qhull does not retain near- + interior points for adjusting outer planes. 'Qc' + will probably retain all points that adjust outer + planes. + + Q9 With 'Q9', Qhull processes the furthest of all out- + side sets at each iteration. + + Q10 With 'Q10', Qhull does not use special processing + for narrow distributions. + + Q11 With 'Q11', Qhull copies normals and recomputes + centrums for tricoplanar facets. + + Q12 With 'Q12', Qhull does not report a very wide merge due + to a duplicated ridge with nearly coincident vertices + + Trace options + + Tn Trace at level n. Qhull includes full execution + tracing. 'T-1' traces events. 'T1' traces the + overall execution of the program. 'T2' and 'T3' + trace overall execution and geometric and topologi- + cal events. 'T4' traces the algorithm. 'T5' + includes information about memory allocation and + Gaussian elimination. + + Ta Annotate output with codes that identify the + corresponding qh_fprintf() statement. + + Tc Check frequently during execution. This will catch + most inconsistency errors. + + TCn Stop Qhull after building the cone of new facets + for point n. The output for 'f' includes the cone + and the old hull. See also 'TVn'. + + TFn Report progress whenever more than n facets are + created During post-merging, 'TFn' reports progress + after more than n/2 merges. + + TI file + Input data from 'file'. The filename may not include + spaces or quotes. + + TO file + Output results to 'file'. The name may be enclosed + in single quotes. + + TPn Turn on tracing when point n is added to the hull. + Trace partitions of point n. If used with TWn, turn off + tracing after adding point n to the hull. + + TRn Rerun qhull n times. Usually used with 'QJn' to + determine the probability that a given joggle will + fail. + + Ts Collect statistics and print to stderr at the end + of execution. + + Tv Verify the convex hull. This checks the topologi- + cal structure, facet convexity, and point inclu- + sion. If precision problems occurred, facet con- + vexity is tested whether or not 'Tv' is selected. + Option 'Tv' does not check point inclusion if + + + +Geometry Center 2003/12/30 16 + + + + + +qhull(1) qhull(1) + + + forcing output with 'Po', or if 'Q5' is set. + + For point inclusion testing, Qhull verifies that + all points are below all outer planes (facet->max- + outside). Point inclusion is exhaustive if merging + or if the facet-point product is small enough; oth- + erwise Qhull verifies each point with a directed + search (qh_findbest). + + Point inclusion testing occurs after producing out- + put. It prints a message to stderr unless option + 'Pp' is used. This allows the user to interrupt + Qhull without changing the output. + + TVn Stop Qhull after adding point n. If n < 0, stop + Qhull before adding point n. Output shows the hull + at this time. See also 'TCn' + + TMn Turn on tracing at n'th merge. + + TWn Trace merge facets when the width is greater than + n. + + Tz Redirect stderr to stdout. + + +BUGS + Please report bugs to Brad Barber at + qhull_bug@qhull.org. + + If Qhull does not compile, it is due to an incompatibility + between your system and ours. The first thing to check is + that your compiler is ANSI standard. If it is, check the + man page for the best options, or find someone to help + you. If you locate the cause of your problem, please send + email since it might help others. + + If Qhull compiles but crashes on the test case (rbox D4), + there's still incompatibility between your system and + ours. Typically it's been due to mem.c and memory align- + ment. You can use qh_NOmem in mem.h to turn off memory + management. Please let us know if you figure out how to + fix these problems. + + If you do find a problem, try to simplify it before + reporting the error. Try different size inputs to locate + the smallest one that causes an error. You're welcome to + hunt through the code using the execution trace as a + guide. This is especially true if you're incorporating + Qhull into your own program. + + When you do report an error, please attach a data set to + the end of your message. This allows us to see the error + for ourselves. Qhull is maintained part-time. + + + +Geometry Center 2003/12/30 17 + + + + + +qhull(1) qhull(1) + + +E-MAIL + Please send correspondence to qhull@qhull.org and + report bugs to qhull_bug@qhull.org. Let us know how + you use Qhull. If you mention it in a paper, please send + the reference and an abstract. + + If you would like to get Qhull announcements (e.g., a new + version) and news (any bugs that get fixed, etc.), let us + know and we will add you to our mailing list. If you + would like to communicate with other Qhull users, we will + add you to the qhull_users alias. For Internet news about + geometric algorithms and convex hulls, look at comp.graph- + ics.algorithms and sci.math.num-analysis + + +SEE ALSO + rbox(1) + + Barber, C. B., D.P. Dobkin, and H.T. Huhdanpaa, "The + Quickhull Algorithm for Convex Hulls," ACM Trans. on Math- + ematical Software, 22(4):469-483, Dec. 1996. + http://portal.acm.org/citation.cfm?doid=235815.235821 + http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.117.405 + + + Clarkson, K.L., K. Mehlhorn, and R. Seidel, "Four results + on randomized incremental construction," Computational + Geometry: Theory and Applications, vol. 3, p. 185-211, + 1993. + + Preparata, F. and M. Shamos, Computational Geometry, + Springer-Verlag, New York, 1985. + + + +AUTHORS + C. Bradford Barber Hannu Huhdanpaa + bradb@shore.net hannu@qhull.org + + + +ACKNOWLEDGEMENTS + A special thanks to Albert Marden, Victor Milenkovic, the + Geometry Center, Harvard University, and Endocardial Solu- + tions, Inc. for supporting this work. + + Qhull 1.0 and 2.0 were developed under National Science Foundation + grants NSF/DMS-8920161 and NSF-CCR-91-15793 750-7504. David Dobkin + + + +Geometry Center 2003/12/30 18 + + + + + +qhull(1) qhull(1) + + + guided the original work at Princeton University. If you find it + useful, please let us know. + + The Geometry Center was supported by grant DMS-8920161 from the National + Science Foundation, by grant DOE/DE-FG02-92ER25137 from the Department + of Energy, by the University of Minnesota, and by Minnesota Technology, Inc. + + Qhull is available from http://www.qhull.org + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Geometry Center 2003/12/30 19 + + diff --git a/xs/src/qhull/html/qvoron_f.htm b/xs/src/qhull/html/qvoron_f.htm new file mode 100644 index 0000000000..db538b5ab5 --- /dev/null +++ b/xs/src/qhull/html/qvoron_f.htm @@ -0,0 +1,396 @@ + + + + +qvoronoi Qu -- furthest-site Voronoi diagram + + + + +Up: +Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +
    + +

    [delaunay]qvoronoi Qu -- furthest-site Voronoi diagram

    + +

    The furthest-site Voronoi diagram is the furthest-neighbor map for a set of +points. Each region contains those points that are further +from one input site than any other input site. See the +survey article by Aurenhammer ['91] +and the brief introduction by O'Rourke ['94]. The furthest-site Voronoi diagram is the dual of the furthest-site Delaunay triangulation. +

    + +
    +
    +
    Example: rbox 10 D2 | qvoronoi Qu s + o TO + result
    +
    Compute the 2-d, furthest-site Voronoi diagram of 10 + random points. Write a summary to the console and the Voronoi + regions and vertices to 'result'. The first vertex of the + result indicates unbounded regions. Almost all regions + are unbounded.
    +
    + +
    +
    Example: rbox r y c G1 D2 | qvoronoi Qu + s + Fn TO + result
    +
    Compute the 2-d furthest-site Voronoi diagram of a square + and a small triangle. Write a summary to the console and the Voronoi + vertices for each input site to 'result'. + The origin is the only furthest-site Voronoi vertex. The + negative indices indicate vertices-at-infinity.
    +
    +
    + +

    +Qhull computes the furthest-site Voronoi diagram via the +furthest-site Delaunay triangulation. +Each furthest-site Voronoi vertex is the circumcenter of an upper +facet of the Delaunay triangulation. Each furthest-site Voronoi +region corresponds to a vertex of the Delaunay triangulation +(i.e., an input site).

    + +

    See Qhull FAQ - Delaunay and +Voronoi diagram questions.

    + +

    The 'qvonoroi' program is equivalent to +'qhull v Qbb' in 2-d to 3-d, and +'qhull v Qbb Qx' +in 4-d and higher. It disables the following Qhull +options: d n m v H U Qb +QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V Fa FA FC Fp FS Ft FV Gt Q0,etc. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    +

    »furthest-site qvoronoi synopsis

    +
    + +See qvoronoi synopsis. The same +program is used for both constructions. Use option 'Qu' +for furthest-site Voronoi diagrams. + + +
    +

    »furthest-site qvoronoi +input

    +
    +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qvoronoi Qu < data.txt), a pipe (e.g., rbox 10 | qvoronoi Qu), +or the 'TI' option (e.g., qvoronoi TI data.txt Qu). + +

    For example, this is a square containing four random points. +Its furthest-site Voronoi diagram has on vertex and four unbounded, +separating hyperplanes (i.e., the coordinate axes) +

    +

    +rbox c 4 D2 > data +
    +2 RBOX c 4 D2
    +8
    +-0.4999921736307369 -0.3684622117955817
    +0.2556053225468894 -0.0413498678629751
    +0.0327672376602583 -0.2810408135699488
    +-0.452955383763607 0.17886471718444
    +  -0.5   -0.5
    +  -0.5    0.5
    +   0.5   -0.5
    +   0.5    0.5
    +
    + +

    qvoronoi Qu s Fo < data +

    +
    +Furthest-site Voronoi vertices by the convex hull of 8 points in 3-d:
    +
    +  Number of Voronoi regions: 8
    +  Number of Voronoi vertices: 1
    +  Number of non-simplicial Voronoi vertices: 1
    +
    +Statistics for: RBOX c 4 D2 | QVORONOI Qu s Fo
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 20
    +  Number of facets in hull: 11
    +  Number of distance tests for qhull: 34
    +  Number of merged facets: 1
    +  Number of distance tests for merging: 107
    +  CPU seconds to compute hull (after input):  0
    +
    +4
    +5 4 5      0      1      0
    +5 4 6      1      0      0
    +5 5 7      1      0      0
    +5 6 7      0      1      0
    +
    +
    + +
    +

    » furthest-site qvoronoi +outputs

    +
    + +

    These options control the output of furthest-site Voronoi diagrams.

    +
    + +
    +
     
    +
    furthest-site Voronoi vertices
    +
    p
    +
    print the coordinates of the furthest-site Voronoi vertices. The first line + is the dimension. The second line is the number of vertices. Each + remaining line is a furthest-site Voronoi vertex. The points-in-square example + has one furthest-site Voronoi vertex at the origin.
    +
    Fn
    +
    list the neighboring furthest-site Voronoi vertices for each furthest-site Voronoi + vertex. The first line is the number of Voronoi vertices. Each + remaining line starts with the number of neighboring vertices. Negative indices (e.g., -1) indicate vertices + outside of the Voronoi diagram. In the points-in-square example, the + Voronoi vertex at the origin has four neighbors-at-infinity.
    +
    FN
    +
    list the furthest-site Voronoi vertices for each furthest-site Voronoi region. The first line is + the number of Voronoi regions. Each remaining line starts with the + number of Voronoi vertices. Negative indices (e.g., -1) indicate vertices + outside of the Voronoi diagram. + In the points-in-square example, all regions share the Voronoi vertex + at the origin.
    + +
     
    +
     
    +
    furthest-site Voronoi regions
    +
    o
    +
    print the furthest-site Voronoi regions in OFF format. The first line is the + dimension. The second line is the number of vertices, the number + of input sites, and "1". The third line represents the vertex-at-infinity. + Its coordinates are "-10.101". The next lines are the coordinates + of the furthest-site Voronoi vertices. Each remaining line starts with the number + of Voronoi vertices in a Voronoi region. In 2-d, the vertices are +listed in adjacency order (unoriented). In 3-d and higher, the +vertices are listed in numeric order. In the points-in-square + example, each unbounded region includes the Voronoi vertex at + the origin. Lines consisting of 0 indicate + interior input sites.
    +
    Fi
    +
    print separating hyperplanes for inner, bounded furthest-site Voronoi + regions. The first number is the number of separating + hyperplanes. Each remaining line starts with 3+dim. The + next two numbers are adjacent input sites. The next dim + numbers are the coefficients of the separating hyperplane. The + last number is its offset. The are no bounded, separating hyperplanes + for the points-in-square example.
    +
    Fo
    +
    print separating hyperplanes for outer, unbounded furthest-site Voronoi + regions. The first number is the number of separating + hyperplanes. Each remaining line starts with 3+dim. The + next two numbers are adjacent input sites on the convex hull. The + next dim + numbers are the coefficients of the separating hyperplane. The + last number is its offset. The points-in-square example has four + unbounded, separating hyperplanes.
    +
     
    +
     
    +
    Input sites
    +
    Fv
    +
    list ridges of furthest-site Voronoi vertices for pairs of input sites. The + first line is the number of ridges. Each remaining line starts with + two plus the number of Voronoi vertices in the ridge. The next + two numbers are two adjacent input sites. The remaining numbers list + the Voronoi vertices. As with option 'o', a 0 indicates + the vertex-at-infinity + and an unbounded, separating hyperplane. + The perpendicular bisector (separating hyperplane) + of the input sites is a flat through these vertices. + In the points-in-square example, the ridge for each edge of the square + is unbounded.
    +
     
    +
     
    +
    General
    +
    s
    +
    print summary of the furthest-site Voronoi diagram. Use 'Fs' for numeric data.
    +
    i
    +
    list input sites for each furthest-site Delaunay region. Use option 'Pp' + to avoid the warning. The first line is the number of regions. The + remaining lines list the input sites for each region. The regions are + oriented. In the points-in-square example, the square region has four + input sites. In 3-d and higher, report cospherical sites by adding extra points. +
    +
    G
    +
    Geomview output for 2-d furthest-site Voronoi diagrams.
    +
    +
    + +
    +

    » furthest-site qvoronoi +controls

    +
    + +

    These options provide additional control:

    +
    + +
    +
    Qu
    +
    must be used.
    +
    QVn
    +
    select furthest-site Voronoi vertices for input site n
    +
    Tv
    +
    verify result
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    TFn
    +
    report progress after constructing n facets
    +
    PDk:1
    +
    include upper and lower facets in the output. Set k + to the last dimension (e.g., 'PD2:1' for 2-d inputs).
    +
    f
    +
    facet dump. Print the data structure for each facet (i.e., + furthest-site Voronoi vertex).
    +
    + +
    +
    +

    » furthest-site qvoronoi +graphics

    +
    +

    In 2-d, Geomview output ('G') +displays a furthest-site Voronoi diagram with extra edges to +close the unbounded furthest-site Voronoi regions. All regions +will be unbounded. Since the points-in-box example has only +one furthest-site Voronoi vertex, the Geomview output is one +point.

    + +

    See the Delaunay and Voronoi +examples for a 2-d example. Turn off normalization (on +Geomview's 'obscure' menu) when comparing the furthest-site +Voronoi diagram with the corresponding Voronoi diagram.

    + +
    +

    »furthest-site qvoronoi +notes

    +
    + +

    See Voronoi notes.

    + +
    +

    »furthest-site qvoronoi conventions

    +
    + +

    The following terminology is used for furthest-site Voronoi +diagrams in Qhull. The underlying structure is a furthest-site +Delaunay triangulation from a convex hull in one higher +dimension. Upper facets of the Delaunay triangulation correspond +to vertices of the furthest-site Voronoi diagram. Vertices of the +furthest-site Delaunay triangulation correspond to input sites. +They also define regions of the furthest-site Voronoi diagram. +All vertices are extreme points of the input sites. See qconvex conventions, furthest-site delaunay +conventions, and Qhull's data structures.

    + +
      +
    • input site - a point in the input (one dimension + lower than a point on the convex hull)
    • +
    • point - a point has d+1 coordinates. The + last coordinate is the sum of the squares of the input + site's coordinates
    • +
    • vertex - a point on the upper facets of the + paraboloid. It corresponds to a unique input site.
    • +
    • furthest-site Delaunay facet - an upper facet of the + paraboloid. The last coefficient of its normal is + clearly positive.
    • +
    • furthest-site Voronoi vertex - the circumcenter + of a furthest-site Delaunay facet
    • +
    • furthest-site Voronoi region - the region of + Euclidean space further from an input site than any other + input site. Qhull lists the furthest-site Voronoi + vertices that define each furthest-site Voronoi region.
    • +
    • furthest-site Voronoi diagram - the graph of the + furthest-site Voronoi regions with the ridges (edges) + between the regions.
    • +
    • infinity vertex - the Voronoi vertex for + unbounded furthest-site Voronoi regions in 'o' output format. Its + coordinates are -10.101.
    • +
    • good facet - an furthest-site Voronoi vertex with + optional restrictions by 'QVn', + etc.
    • +
    + +
    +

    »furthest-site qvoronoi options

    +
    + +See qvoronoi options. The same +program is used for both constructions. Use option 'Qu' +for furthest-site Voronoi diagrams. +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qvoronoi.htm b/xs/src/qhull/html/qvoronoi.htm new file mode 100644 index 0000000000..6d81d48c15 --- /dev/null +++ b/xs/src/qhull/html/qvoronoi.htm @@ -0,0 +1,667 @@ + + + + +qvoronoi -- Voronoi diagram + + + + +Up: +Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +
    + +

    [voronoi]qvoronoi -- Voronoi diagram

    + +

    The Voronoi diagram is the nearest-neighbor map for a set of +points. Each region contains those points that are nearer +one input site than any other input site. It has many useful properties and applications. See the +survey article by Aurenhammer ['91] +and the detailed introduction by O'Rourke ['94]. The Voronoi diagram is the +dual of the Delaunay triangulation.

    + +
    +
    +
    Example: rbox 10 D3 | qvoronoi s + o TO + result
    +
    Compute the 3-d Voronoi diagram of 10 random points. Write a + summary to the console and the Voronoi vertices and + regions to 'result'. The first vertex of the result + indicates unbounded regions.
    + +
     
    +
    Example: rbox r y c G0.1 D2 | qvoronoi + s + o TO + result
    +
    Compute the 2-d Voronoi diagram of a triangle and a small + square. Write a + summary to the console and Voronoi vertices and regions + to 'result'. Report a single Voronoi vertex for + cocircular input sites. The first vertex of the result + indicates unbounded regions. The origin is the Voronoi + vertex for the square.
    + +
     
    +
    Example: rbox r y c G0.1 D2 | qvoronoi Fv + TO result
    +
    Compute the 2-d Voronoi diagram of a triangle and a small + square. Write a + summary to the console and the Voronoi ridges to + 'result'. Each ridge is the perpendicular bisector of a + pair of input sites. Vertex "0" indicates + unbounded ridges. Vertex "8" is the Voronoi + vertex for the square.
    + +
     
    +
    Example: rbox r y c G0.1 D2 | qvoronoi Fi
    +
    Print the bounded, separating hyperplanes for the 2-d Voronoi diagram of a + triangle and a small + square. Note the four hyperplanes (i.e., lines) for Voronoi vertex + "8". It is at the origin. +
    +
    +
    + +

    Qhull computes the Voronoi diagram via the Delaunay +triangulation. Each Voronoi +vertex is the circumcenter of a facet of the Delaunay +triangulation. Each Voronoi region corresponds to a vertex (i.e., input site) of the +Delaunay triangulation.

    + +

    Qhull outputs the Voronoi vertices for each Voronoi region. With +option 'Fv', +it lists all ridges of the Voronoi diagram with the corresponding +pairs of input sites. With +options 'Fi' and 'Fo', +it lists the bounded and unbounded separating hyperplanes. +You can also output a single Voronoi region +for further processing [see graphics].

    + +

    Use option 'Qz' if the input is circular, cospherical, or +nearly so. It improves precision by adding a point "at infinity," above the corresponding paraboloid. + +

    See Qhull FAQ - Delaunay and +Voronoi diagram questions.

    + +

    The 'qvonoroi' program is equivalent to +'qhull v Qbb' in 2-d to 3-d, and +'qhull v Qbb Qx' +in 4-d and higher. It disables the following Qhull +options: d n v Qbb QbB Qf Qg Qm +Qr QR Qv Qx Qz TR E V Fa FA FC FD FS Ft FV Gt Q0,etc. + +

    Copyright © 1995-2015 C.B. Barber

    + +

    Voronoi image by KOOK Architecture, Silvan Oesterle and Michael Knauss. + +


    +

    »qvoronoi synopsis

    + +
    +qvoronoi- compute the Voronoi diagram.
    +    input (stdin): dimension, number of points, point coordinates
    +    comments start with a non-numeric character
    +
    +options (qh-voron.htm):
    +    Qu   - compute furthest-site Voronoi diagram
    +    Tv   - verify result: structure, convexity, and in-circle test
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +output options (subset):
    +    s    - summary of results (default)
    +    p    - Voronoi vertices
    +    o    - OFF file format (dim, Voronoi vertices, and Voronoi regions)
    +    FN   - count and Voronoi vertices for each Voronoi region
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites
    +    Fi   - separating hyperplanes for bounded regions, 'Fo' for unbounded
    +    G    - Geomview output (2-d only)
    +    QVn  - Voronoi vertices for input point n, -n if not
    +    TO file- output results to file, may be enclosed in single quotes
    +
    +examples:
    +rbox c P0 D2 | qvoronoi s o         rbox c P0 D2 | qvoronoi Fi
    +rbox c P0 D2 | qvoronoi Fo          rbox c P0 D2 | qvoronoi Fv
    +rbox c P0 D2 | qvoronoi s Qu Fv     rbox c P0 D2 | qvoronoi Qu Fo
    +rbox c G1 d D2 | qvoronoi s p       rbox c P0 D2 | qvoronoi s Fv QV0
    +
    + +

    »qvoronoi input

    +
    +The input data on stdin consists of: +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qvoronoi < data.txt), a pipe (e.g., rbox 10 | qvoronoi), +or the 'TI' option (e.g., qvoronoi TI data.txt). + +

    For example, this is four cocircular points inside a square. Their Voronoi +diagram has nine vertices and eight regions. Notice the Voronoi vertex +at the origin, and the Voronoi vertices (on each axis) for the four +sides of the square. +

    +

    +rbox s 4 W0 c G1 D2 > data +
    +2 RBOX s 4 W0 c D2
    +8
    +-0.4941988586954018 -0.07594397977563715
    +-0.06448037284989526 0.4958248496365813
    +0.4911154367094632 0.09383830681375946
    +-0.348353580869097 -0.3586778257652367
    +    -1     -1
    +    -1      1
    +     1     -1
    +     1      1
    +
    + +

    qvoronoi s p < data +

    +
    +Voronoi diagram by the convex hull of 8 points in 3-d:
    +
    +  Number of Voronoi regions: 8
    +  Number of Voronoi vertices: 9
    +  Number of non-simplicial Voronoi vertices: 1
    +
    +Statistics for: RBOX s 4 W0 c D2 | QVORONOI s p
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 18
    +  Number of facets in hull: 10
    +  Number of distance tests for qhull: 33
    +  Number of merged facets: 2
    +  Number of distance tests for merging: 102
    +  CPU seconds to compute hull (after input): 0.094
    +
    +2
    +9
    +4.217546450968612e-17 1.735507986399734
    +-8.402566836762659e-17 -1.364368854147395
    +0.3447488772716865 -0.6395484723719818
    +1.719446929853986 2.136555906154247e-17
    +0.4967882915039657 0.68662371396699
    +-1.729928876283549 1.343733067524222e-17
    +-0.8906163241424728 -0.4594150543829102
    +-0.6656840313875723 0.5003013793414868
    +-7.318364664277155e-19 -1.188217818408333e-16
    +
    +
    + +
    +

    » qvoronoi +outputs

    +
    + +

    These options control the output of Voronoi diagrams.

    +
    + +
    +
     
    +
    Voronoi vertices
    +
    p
    +
    print the coordinates of the Voronoi vertices. The first line + is the dimension. The second line is the number of vertices. Each + remaining line is a Voronoi vertex.
    +
    Fn
    +
    list the neighboring Voronoi vertices for each Voronoi + vertex. The first line is the number of Voronoi vertices. Each + remaining line starts with the number of neighboring vertices. + Negative vertices (e.g., -1) indicate vertices + outside of the Voronoi diagram. + In the circle-in-box example, the + Voronoi vertex at the origin has four neighbors.
    +
    FN
    +
    list the Voronoi vertices for each Voronoi region. The first line is + the number of Voronoi regions. Each remaining line starts with the + number of Voronoi vertices. Negative indices (e.g., -1) indicate vertices + outside of the Voronoi diagram. + In the circle-in-box example, the four bounded regions are defined by four + Voronoi vertices.
    + +
     
    +
     
    +
    Voronoi regions
    +
    o
    +
    print the Voronoi regions in OFF format. The first line is the + dimension. The second line is the number of vertices, the number + of input sites, and "1". The third line represents the vertex-at-infinity. + Its coordinates are "-10.101". The next lines are the coordinates + of the Voronoi vertices. Each remaining line starts with the number + of Voronoi vertices in a Voronoi region. In 2-d, the vertices are +listed in adjacency order (unoriented). In 3-d and higher, the +vertices are listed in numeric order. In the circle-in-square + example, each bounded region includes the Voronoi vertex at + the origin. Lines consisting of 0 indicate + coplanar input sites or 'Qz'.
    +
    Fi
    +
    print separating hyperplanes for inner, bounded Voronoi + regions. The first number is the number of separating + hyperplanes. Each remaining line starts with 3+dim. The + next two numbers are adjacent input sites. The next dim + numbers are the coefficients of the separating hyperplane. The + last number is its offset. Use 'Tv' to verify that the +hyperplanes are perpendicular bisectors. It will list relevant +statistics to stderr.
    +
    Fo
    +
    print separating hyperplanes for outer, unbounded Voronoi + regions. The first number is the number of separating + hyperplanes. Each remaining line starts with 3+dim. The + next two numbers are adjacent input sites on the convex hull. The + next dim + numbers are the coefficients of the separating hyperplane. The + last number is its offset. Use 'Tv' to verify that the +hyperplanes are perpendicular bisectors. It will list relevant +statistics to stderr,
    +
     
    +
     
    +
    Input sites
    +
    Fv
    +
    list ridges of Voronoi vertices for pairs of input sites. The + first line is the number of ridges. Each remaining line starts with + two plus the number of Voronoi vertices in the ridge. The next + two numbers are two adjacent input sites. The remaining numbers list + the Voronoi vertices. As with option 'o', a 0 indicates + the vertex-at-infinity + and an unbounded, separating hyperplane. + The perpendicular bisector (separating hyperplane) + of the input sites is a flat through these vertices. + In the circle-in-square example, the ridge for each edge of the square + is unbounded.
    +
    Fc
    +
    list coincident input sites for each Voronoi vertex. + The first line is the number of vertices. The remaining lines start with + the number of coincident sites and deleted vertices. Deleted vertices + indicate highly degenerate input (see'Fs'). + A coincident site is assigned to one Voronoi + vertex. Do not use 'QJ' with 'Fc'; the joggle will separate + coincident sites.
    +
    FP
    +
    print coincident input sites with distance to + nearest site (i.e., vertex). The first line is the + number of coincident sites. Each remaining line starts with the point ID of + an input site, followed by the point ID of a coincident point, its vertex, and distance. + Includes deleted vertices which + indicate highly degenerate input (see'Fs'). + Do not use 'QJ' with 'FP'; the joggle will separate + coincident sites.
    +
     
    +
     
    +
    General
    +
    s
    +
    print summary of the Voronoi diagram. Use 'Fs' for numeric data.
    +
    i
    +
    list input sites for each Delaunay region. Use option 'Pp' + to avoid the warning. The first line is the number of regions. The + remaining lines list the input sites for each region. The regions are + oriented. In the circle-in-square example, the cocircular region has four + edges. In 3-d and higher, report cospherical sites by adding extra points. +
    +
    G
    +
    Geomview output for 2-d Voronoi diagrams.
    +
    +
    +
    +

    » qvoronoi +controls

    +
    + +

    These options provide additional control:

    +
    + +
    +
    Qu
    +
    compute the furthest-site Voronoi diagram.
    +
    QVn
    +
    select Voronoi vertices for input site n
    +
    Qz
    +
    add a point above the paraboloid to reduce precision + errors. Use it for nearly cocircular/cospherical input + (e.g., 'rbox c | qvoronoi Qz').
    +
    Tv
    +
    verify result
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    TFn
    +
    report progress after constructing n facets
    +
    PDk:1
    +
    include upper and lower facets in the output. Set k + to the last dimension (e.g., 'PD2:1' for 2-d inputs).
    +
    f
    +
    facet dump. Print the data structure for each facet (i.e., + Voronoi vertex).
    +
    + +
    +
    +

    » qvoronoi +graphics

    +
    + +

    In 2-d, Geomview output ('G') +displays a Voronoi diagram with extra edges to close the +unbounded Voronoi regions. To view the unbounded rays, enclose +the input points in a square.

    + +

    You can also view individual Voronoi regions in 3-d. To +view the Voronoi region for site 3 in Geomview, execute

    + +
    +

    qvoronoi <data QV3 p | qconvex s G >output

    +
    + +

    The qvoronoi command returns the Voronoi vertices +for input site 3. The qconvex command computes their convex hull. +This is the Voronoi region for input site 3. Its +hyperplane normals (qconvex 'n') are the same as the separating hyperplanes +from options 'Fi' +and 'Fo' (up to roundoff error). + +

    See the Delaunay and Voronoi +examples for 2-d and 3-d examples. Turn off normalization (on +Geomview's 'obscure' menu) when comparing the Voronoi diagram +with the corresponding Delaunay triangulation.

    + +
    +

    »qvoronoi +notes

    +
    + +

    You can simplify the Voronoi diagram by enclosing the input +sites in a large square or cube. This is particularly recommended +for cocircular or cospherical input data.

    + +

    See Voronoi graphics for computing +the convex hull of a Voronoi region.

    + +

    Voronoi diagrams do not include facets that are +coplanar with the convex hull of the input sites. A facet is +coplanar if the last coefficient of its normal is +nearly zero (see qh_ZEROdelaunay). + +

    Unbounded regions can be confusing. For example, 'rbox c | +qvoronoi Qz o' produces the Voronoi regions for the vertices +of a cube centered at the origin. All regions are unbounded. The +output is

    + +
    +
    3
    +2 9 1
    +-10.101 -10.101 -10.101
    +     0      0      0
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +0
    +
    +
    + +

    The first line is the dimension. The second line is the number +of vertices and the number of regions. There is one region per +input point plus a region for the point-at-infinity added by +option 'Qz'. The next two lines +lists the Voronoi vertices. The first vertex is the infinity +vertex. It is indicate by the coordinates -10.101. The +second vertex is the origin. The next nine lines list the +regions. Each region lists two vertices -- the infinity vertex +and the origin. The last line is "0" because no region +is associated with the point-at-infinity. A "0" would +also be listed for nearly incident input sites.

    + +

    To use option 'Fv', add an +interior point. For example,

    + +
    +
    +rbox c P0 | qvoronoi Fv
    +20
    +5 0 7 1 3 5
    +5 0 3 1 4 5
    +5 0 5 1 2 3
    +5 0 1 1 2 4
    +5 0 6 2 3 6
    +5 0 2 2 4 6
    +5 0 4 4 5 6
    +5 0 8 5 3 6
    +5 1 2 0 2 4
    +5 1 3 0 1 4
    +5 1 5 0 1 2
    +5 2 4 0 4 6
    +5 2 6 0 2 6
    +5 3 4 0 4 5
    +5 3 7 0 1 5
    +5 4 8 0 6 5
    +5 5 6 0 2 3
    +5 5 7 0 1 3
    +5 6 8 0 6 3
    +5 7 8 0 3 5
    +
    +
    + +

    The output consists of 20 ridges and each ridge lists a pair +of input sites and a triplet of Voronoi vertices. The first eight +ridges connect the origin ('P0'). The remainder list the edges of +the cube. Each edge generates an unbounded ray through the +midpoint. The corresponding separating planes ('Fo') follow each +pair of coordinate axes.

    + +

    Options 'Qt' (triangulated output) +and 'QJ' (joggled input) are deprecated. They may produce +unexpected results. If you use these options, cocircular and cospherical input sites will +produce duplicate or nearly duplicate Voronoi vertices. See also Merged facets or joggled input.

    + +
    +

    »qvoronoi conventions

    +
    + +

    The following terminology is used for Voronoi diagrams in +Qhull. The underlying structure is a Delaunay triangulation from +a convex hull in one higher dimension. Facets of the Delaunay +triangulation correspond to vertices of the Voronoi diagram. +Vertices of the Delaunay triangulation correspond to input sites. +They also correspond to regions of the Voronoi diagram. See convex hull conventions, Delaunay conventions, and +Qhull's data structures.

    +
    + +
      +
    • input site - a point in the input (one dimension + lower than a point on the convex hull)
    • +
    • point - a point has d+1 coordinates. The + last coordinate is the sum of the squares of the input + site's coordinates
    • +
    • coplanar point - a nearly incident + input site
    • +
    • vertex - a point on the paraboloid. It + corresponds to a unique input site.
    • +
    • point-at-infinity - a point added above the + paraboloid by option 'Qz'
    • +
    • Delaunay facet - a lower facet of the + paraboloid. The last coefficient of its normal is + clearly negative.
    • +
    • Voronoi vertex - the circumcenter of a Delaunay + facet
    • +
    • Voronoi region - the Voronoi vertices for an + input site. The region of Euclidean space nearest to an + input site.
    • +
    • Voronoi diagram - the graph of the Voronoi + regions. It includes the ridges (i.e., edges) between the + regions.
    • +
    • vertex-at-infinity - the Voronoi vertex that + indicates unbounded Voronoi regions in 'o' output format. Its + coordinates are -10.101.
    • +
    • good facet - a Voronoi vertex with optional + restrictions by 'QVn', etc.
    • +
    + +
    +
    +

    »qvoronoi options

    + +
    +qvoronoi- compute the Voronoi diagram
    +    http://www.qhull.org
    +
    +input (stdin):
    +    first lines: dimension and number of points (or vice-versa).
    +    other lines: point coordinates, best if one point per line
    +    comments:    start with a non-numeric character
    +
    +options:
    +    Qu   - compute furthest-site Voronoi diagram
    +
    +Qhull control options:
    +    QJn  - randomly joggle input in range [-n,n]
    +    Qs   - search all points for the initial simplex
    +    Qz   - add point-at-infinity to Voronoi diagram
    +    QGn  - Voronoi vertices if visible from point n, -n if not
    +    QVn  - Voronoi vertices for input point n, -n if not
    +
    +Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - statistics
    +    Tv   - verify result: structure, convexity, and in-circle test
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when point n added to hull
    +     TMn - turn on tracing at merge n
    +     TWn - trace merge facets when width > n
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)
    +     TCn - stop qhull after building cone for point n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Wn   - min facet width for non-coincident point (before roundoff)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    s    - summary to stderr
    +    p    - Voronoi vertices
    +    o    - OFF format (dim, Voronoi vertices, and Voronoi regions)
    +    i    - Delaunay regions (use 'Pp' to avoid warning)
    +    f    - facet dump
    +
    +More formats:
    +    Fc   - count plus coincident points (by Voronoi vertex)
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FD   - use cdd format for output (offset first)
    +    FF   - facet dump without ridges
    +    Fi   - separating hyperplanes for bounded Voronoi regions
    +    FI   - ID for each Voronoi vertex
    +    Fm   - merge count for each Voronoi vertex (511 max)
    +    Fn   - count plus neighboring Voronoi vertices for each Voronoi vertex
    +    FN   - count and Voronoi vertices for each Voronoi region
    +    Fo   - separating hyperplanes for unbounded Voronoi regions
    +    FO   - options and precision constants
    +    FP   - nearest point and distance for each coincident point
    +    FQ   - command used for qvoronoi
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,
    +                    for output: #Voronoi regions, #Voronoi vertices,
    +                                #coincident points, #non-simplicial regions
    +                    #real (2), max outer plane and min vertex
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)
    +
    +Geomview options (2-d only)
    +    Ga   - all points as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices as spheres
    +    Gi   - inner planes only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc   - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +
    +Print options:
    +    PAn  - keep n largest Voronoi vertices by 'area'
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)
    +    PDk:n - drop facet if normal[k] >= n
    +    Pg   - print good Voronoi vertices (needs 'QGn' or 'QVn')
    +    PFn  - keep Voronoi vertices whose 'area' is at least n
    +    PG   - print neighbors of good Voronoi vertices
    +    PMn  - keep n Voronoi vertices with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/rbox.htm b/xs/src/qhull/html/rbox.htm new file mode 100644 index 0000000000..9c28face56 --- /dev/null +++ b/xs/src/qhull/html/rbox.htm @@ -0,0 +1,277 @@ + + + + +rbox -- generate point distributions + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis • outputs +• examples • notes +• options +


    + +

    [CONE]rbox -- generate point distributions

    + +
    + rbox generates random or regular points according to the + options given, and outputs the points to stdout. The + points are generated in a cube, unless 's', 'x', or 'y' + are given. + +
    +

    »rbox synopsis

    +
    +rbox- generate various point distributions.  Default is random in cube.
    +
    +args (any order, space separated):
    +  3000    number of random points in cube, lens, spiral, sphere or grid
    +  D3      dimension 3-d
    +  c       add a unit cube to the output ('c G2.0' sets size)
    +  d       add a unit diamond to the output ('d G2.0' sets size)
    +  l       generate a regular 3-d spiral
    +  r       generate a regular polygon, ('r s Z1 G0.1' makes a cone)
    +  s       generate cospherical points
    +  x       generate random points in simplex, may use 'r' or 'Wn'
    +  y       same as 'x', plus simplex
    +  Cn,r,m  add n nearly coincident points within radius r of m points
    +  Pn,m,r  add point [n,m,r] first, pads with 0
    +
    +  Ln      lens distribution of radius n.  Also 's', 'r', 'G', 'W'.
    +  Mn,m,r  lattice (Mesh) rotated by [n,-m,0], [m,n,0], [0,0,r], ...
    +          '27 M1,0,1' is {0,1,2} x {0,1,2} x {0,1,2}.  Try 'M3,4 z'.
    +  W0.1    random distribution within 0.1 of the cube's or sphere's surface
    +  Z0.5 s  random points in a 0.5 disk projected to a sphere
    +  Z0.5 s G0.6 same as Z0.5 within a 0.6 gap
    +
    +  Bn      bounding box coordinates, default 0.5
    +  h       output as homogeneous coordinates for cdd
    +  n       remove command line from the first line of output
    +  On      offset coordinates by n
    +  t       use time as the random number seed (default is command line)
    +  tn      use n as the random number seed
    +  z       print integer coordinates, default 'Bn' is 1e+06
    +
    + +

    »rbox outputs

    +
    + +The format of the output is the following: first line contains + the dimension and a comment, second line contains the + number of points, and the following lines contain the points, + one point per line. Points are represented by their coordinate values. + +

    For example, rbox c 10 D2 generates +

    +
    +2 RBOX c 10 D2
    +14
    +-0.4999921736307369 -0.3684622117955817
    +0.2556053225468894 -0.0413498678629751
    +0.0327672376602583 -0.2810408135699488
    +-0.452955383763607 0.17886471718444
    +0.1792964061529342 0.4346928963760779
    +-0.1164979223315585 0.01941637230982666
    +0.3309653464993139 -0.4654278894564396
    +-0.4465383649305798 0.02970019358182344
    +0.1711493843897706 -0.4923018137852678
    +-0.1165843490665633 -0.433157762450313
    +  -0.5   -0.5
    +  -0.5    0.5
    +   0.5   -0.5
    +   0.5    0.5
    +
    + +
    + +
    +

    »rbox examples

    + +
    +       rbox 10
    +              10 random points in the unit cube centered  at  the
    +              origin.
    +
    +       rbox 10 s D2
    +              10 random points on a 2-d circle.
    +
    +       rbox 100 W0
    +              100 random points on the surface of a cube.
    +
    +       rbox 1000 s D4
    +              1000 random points on a 4-d sphere.
    +
    +       rbox c D5 O0.5
    +              a 5-d hypercube with one corner at the origin.
    +
    +       rbox d D10
    +              a 10-d diamond.
    +
    +       rbox x 1000 r W0
    +              100 random points on the surface of a fixed simplex
    +
    +       rbox y D12
    +              a 12-d simplex.
    +
    +       rbox l 10
    +              10 random points along a spiral
    +
    +       rbox l 10 r
    +              10 regular points  along  a  spiral  plus  two  end
    +              points
    +
    +       rbox 1000 L10000 D4 s
    +              1000 random points on the surface of a narrow lens.
    +
    +           rbox 1000 L100000 s G1e-6
    +                  1000 random points near the edge of a narrow lens
    +
    +       rbox c G2 d G3
    +              a cube with coordinates +2/-2 and  a  diamond  with
    +              coordinates +3/-3.
    +
    +       rbox 64 M3,4 z
    +              a  rotated,  {0,1,2,3} x {0,1,2,3} x {0,1,2,3} lat-
    +              tice (Mesh) of integer points.
    +
    +       rbox P0 P0 P0 P0 P0
    +              5 copies of the origin in 3-d.  Try 'rbox P0 P0  P0
    +              P0 P0 | qhull QJ'.
    +
    +       r 100 s Z1 G0.1
    +              two  cospherical  100-gons plus another cospherical
    +              point.
    +
    +       100 s Z1
    +              a cone of points.
    +
    +       100 s Z1e-7
    +              a narrow cone of points with many precision errors.
    +
    + +

    »rbox notes

    +
    +Some combinations of arguments generate odd results. + +
    +

    »rbox options

    + +
    +       n      number of points
    +
    +       Dn     dimension n-d (default 3-d)
    +
    +       Bn     bounding box coordinates (default 0.5)
    +
    +       l      spiral distribution, available only in 3-d
    +
    +       Ln     lens  distribution  of  radius n.  May be used with
    +              's', 'r', 'G', and 'W'.
    +
    +       Mn,m,r lattice  (Mesh)  rotated  by  {[n,-m,0],   [m,n,0],
    +              [0,0,r],  ...}.   Use  'Mm,n'  for a rigid rotation
    +              with r = sqrt(n^2+m^2).  'M1,0'  is  an  orthogonal
    +              lattice.   For  example,  '27  M1,0'  is  {0,1,2} x
    +              {0,1,2} x {0,1,2}.
    +
    +       s      cospherical points randomly generated in a cube and
    +              projected to the unit sphere
    +
    +       x      simplicial  distribution.   It  is fixed for option
    +              'r'.  May be used with 'W'.
    +
    +       y      simplicial distribution plus a simplex.   Both  'x'
    +              and 'y' generate the same points.
    +
    +       Wn     restrict  points  to distance n of the surface of a
    +              sphere or a cube
    +
    +       c      add a unit cube to the output
    +
    +       c Gm   add a cube with all combinations of +m  and  -m  to
    +              the output
    +
    +       d      add a unit diamond to the output.
    +
    +       d Gm   add a diamond made of 0, +m and -m to the output
    +
    +       Cn,r,m add n nearly coincident points within radius r of m points
    +
    +       Pn,m,r add point [n,m,r] to the output first.  Pad coordi-
    +              nates with 0.0.
    +
    +       n      Remove the command line from the first line of out-
    +              put.
    +
    +       On     offset the data by adding n to each coordinate.
    +
    +       t      use  time  in  seconds  as  the  random number seed
    +              (default is command line).
    +
    +       tn     set the random number seed to n.
    +
    +       z      generate integer coordinates.  Use 'Bn'  to  change
    +              the  range.   The  default  is 'B1e6' for six-digit
    +              coordinates.  In R^4, seven-digit coordinates  will
    +              overflow hyperplane normalization.
    +
    +       Zn s   restrict points to a disk about the z+ axis and the
    +              sphere (default Z1.0).  Includes the opposite pole.
    +              'Z1e-6'  generates  degenerate  points under single
    +              precision.
    +
    +       Zn Gm s
    +              same as Zn with an empty center (default G0.5).
    +
    +       r s D2 generate a regular polygon
    +
    +       r s Z1 G0.1
    +              generate a regular cone
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis • outputs +• examples • notes +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: August 12, 1998

    + + diff --git a/xs/src/qhull/html/rbox.man b/xs/src/qhull/html/rbox.man new file mode 100644 index 0000000000..3ea6395e69 --- /dev/null +++ b/xs/src/qhull/html/rbox.man @@ -0,0 +1,176 @@ +.\" This is the Unix manual page for rbox, written in nroff, the standard +.\" manual formatter for Unix systems. To format it, type +.\" +.\" nroff -man rbox.man +.\" +.\" This will print a formatted copy to standard output. If you want +.\" to ensure that the output is plain ascii, free of any control +.\" characters that nroff uses for underlining etc, pipe the output +.\" through "col -b": +.\" +.\" nroff -man rbox.man | col -b +.\" +.TH rbox 1 "August 10, 1998" "Geometry Center" +.SH NAME +rbox \- generate point distributions for qhull +.SH SYNOPSIS +Command "rbox" (w/o arguments) lists the options. +.SH DESCRIPTION +.PP +rbox generates random or regular points according to the options given, and +outputs +the points to stdout. The points are generated in a cube, unless 's' or 'k' +option is +given. The format of the output is the following: first line +contains the dimension and a comment, +second line contains the number of points, and the +following lines contain the points, one point per line. Points are represented +by their coordinate values. +.SH EXAMPLES +.TP +rbox 10 +10 random points in the unit cube centered at the origin. +.TP +rbox 10 s D2 +10 random points on a 2\[hy]d circle. +.TP +rbox 100 W0 +100 random points on the surface of a cube. +.TP +rbox 1000 s D4 +1000 random points on a 4\[hy]d sphere. +.TP +rbox c D5 O0.5 +a 5\[hy]d hypercube with one corner at the origin. +.TP +rbox d D10 +a 10\[hy]d diamond. +.TP +rbox x 1000 r W0 +100 random points on the surface of a fixed simplex +.TP +rbox y D12 +a 12\[hy]d simplex. +.TP +rbox l 10 +10 random points along a spiral +.TP +rbox l 10 r +10 regular points along a spiral plus two end points +.TP +rbox 1000 L10000 D4 s +1000 random points on the surface of a narrow lens. +.TP +rbox c G2 d G3 +a cube with coordinates +2/\-2 and a diamond with coordinates +3/\-3. +.TP +rbox 64 M3,4 z +a rotated, {0,1,2,3} x {0,1,2,3} x {0,1,2,3} lattice (Mesh) of integer +points. 'rbox 64 M1,0' is orthogonal. +.TP +rbox P0 P0 P0 P0 P0 +5 copies of the origin in 3\-d. Try 'rbox P0 P0 P0 P0 P0 | qhull QJ'. +.TP +r 100 s Z1 G0.1 +two cospherical 100\-gons plus another cospherical point. +.TP +100 s Z1 +a cone of points. +.TP +100 s Z1e\-7 +a narrow cone of points with many precision errors. +.SH OPTIONS +.TP +n +number of points +.TP +Dn +dimension n\[hy]d (default 3\[hy]d) +.TP +Bn +bounding box coordinates (default 0.5) +.TP +l +spiral distribution, available only in 3\[hy]d +.TP +Ln +lens distribution of radius n. May be used with 's', 'r', 'G', and 'W'. +.TP +Mn,m,r +lattice (Mesh) rotated by {[n,\-m,0], [m,n,0], [0,0,r], ...}. +Use 'Mm,n' for a rigid rotation with r = sqrt(n^2+m^2). 'M1,0' is an +orthogonal lattice. For example, '27 M1,0' is {0,1,2} x {0,1,2} x +{0,1,2}. '27 M3,4 z' is a rotated integer lattice. +.TP +s +cospherical points randomly generated in a cube and projected to the unit sphere +.TP +x +simplicial distribution. It is fixed for option 'r'. May be used with 'W'. +.TP +y +simplicial distribution plus a simplex. Both 'x' and 'y' generate the same points. +.TP +Wn +restrict points to distance n of the surface of a sphere or a cube +.TP +c +add a unit cube to the output +.TP +c Gm +add a cube with all combinations of +m and \-m to the output +.TP +d +add a unit diamond to the output. +.TP +d Gm +add a diamond made of 0, +m and \-m to the output +.TP +Cn,r,m +add n nearly coincident points within radius r of m points +.TP +Pn,m,r +add point [n,m,r] to the output first. Pad coordinates with 0.0. +.TP +n +Remove the command line from the first line of output. +.TP +On +offset the data by adding n to each coordinate. +.TP +t +use time in seconds as the random number seed (default is command line). +.TP +tn +set the random number seed to n. +.TP +z +generate integer coordinates. Use 'Bn' to change the range. +The default is 'B1e6' for six\[hy]digit coordinates. In R^4, seven\[hy]digit +coordinates will overflow hyperplane normalization. +.TP +Zn s +restrict points to a disk about the z+ axis and the sphere (default Z1.0). +Includes the opposite pole. 'Z1e\-6' generates degenerate points under +single precision. +.TP +Zn Gm s +same as Zn with an empty center (default G0.5). +.TP +r s D2 +generate a regular polygon +.TP +r s Z1 G0.1 +generate a regular cone +.SH BUGS +Some combinations of arguments generate odd results. + +Report bugs to qhull_bug@qhull.org, other correspondence to qhull@qhull.org +.SH SEE ALSO +qhull(1) +.SH AUTHOR +.nf +C. Bradford Barber +bradb@shore.net +.fi + diff --git a/xs/src/qhull/html/rbox.txt b/xs/src/qhull/html/rbox.txt new file mode 100644 index 0000000000..e3cf721892 --- /dev/null +++ b/xs/src/qhull/html/rbox.txt @@ -0,0 +1,195 @@ + + + +rbox(1) rbox(1) + + +NAME + rbox - generate point distributions for qhull + +SYNOPSIS + Command "rbox" (w/o arguments) lists the options. + +DESCRIPTION + rbox generates random or regular points according to the + options given, and outputs the points to stdout. The + points are generated in a cube, unless 's' or given. The + format of the output is the following: first line contains + the dimension and a comment, second line contains the num- + ber of points, and the following lines contain the points, + one point per line. Points are represented by their coor- + dinate values. + +EXAMPLES + rbox 10 + 10 random points in the unit cube centered at the + origin. + + rbox 10 s D2 + 10 random points on a 2-d circle. + + rbox 100 W0 + 100 random points on the surface of a cube. + + rbox 1000 s D4 + 1000 random points on a 4-d sphere. + + rbox c D5 O0.5 + a 5-d hypercube with one corner at the origin. + + rbox d D10 + a 10-d diamond. + + rbox x 1000 r W0 + 100 random points on the surface of a fixed simplex + + rbox y D12 + a 12-d simplex. + + rbox l 10 + 10 random points along a spiral + + rbox l 10 r + 10 regular points along a spiral plus two end + points + + rbox 1000 L10000 D4 s + 1000 random points on the surface of a narrow lens. + + rbox c G2 d G3 + a cube with coordinates +2/-2 and a diamond with + + + +Geometry Center August 10, 1998 1 + + + + + +rbox(1) rbox(1) + + + coordinates +3/-3. + + rbox 64 M3,4 z + a rotated, {0,1,2,3} x {0,1,2,3} x {0,1,2,3} lat- + tice (Mesh) of integer points. + + rbox P0 P0 P0 P0 P0 + 5 copies of the origin in 3-d. Try 'rbox P0 P0 P0 + P0 P0 | qhull QJ'. + + r 100 s Z1 G0.1 + two cospherical 100-gons plus another cospherical + point. + + 100 s Z1 + a cone of points. + + 100 s Z1e-7 + a narrow cone of points with many precision errors. + +OPTIONS + n number of points + + Dn dimension n-d (default 3-d) + + Bn bounding box coordinates (default 0.5) + + l spiral distribution, available only in 3-d + + Ln lens distribution of radius n. May be used with + 's', 'r', 'G', and 'W'. + + Mn,m,r lattice (Mesh) rotated by {[n,-m,0], [m,n,0], + [0,0,r], ...}. Use 'Mm,n' for a rigid rotation + with r = sqrt(n^2+m^2). 'M1,0' is an orthogonal + lattice. For example, '27 M1,0' is {0,1,2} x + {0,1,2} x {0,1,2}. + + s cospherical points randomly generated in a cube and + projected to the unit sphere + + x simplicial distribution. It is fixed for option + 'r'. May be used with 'W'. + + y simplicial distribution plus a simplex. Both 'x' + and 'y' generate the same points. + + Wn restrict points to distance n of the surface of a + sphere or a cube + + c add a unit cube to the output + + c Gm add a cube with all combinations of +m and -m to + the output + + + +Geometry Center August 10, 1998 2 + + + + + +rbox(1) rbox(1) + + + d add a unit diamond to the output. + + d Gm add a diamond made of 0, +m and -m to the output + + Cn,r,m add n nearly coincident points within radius r of m points + + Pn,m,r add point [n,m,r] to the output first. Pad coordi- + nates with 0.0. + + n Remove the command line from the first line of out- + put. + + On offset the data by adding n to each coordinate. + + t use time in seconds as the random number seed + (default is command line). + + tn set the random number seed to n. + + z generate integer coordinates. Use 'Bn' to change + the range. The default is 'B1e6' for six-digit + coordinates. In R^4, seven-digit coordinates will + overflow hyperplane normalization. + + Zn s restrict points to a disk about the z+ axis and the + sphere (default Z1.0). Includes the opposite pole. + 'Z1e-6' generates degenerate points under single + precision. + + Zn Gm s + same as Zn with an empty center (default G0.5). + + r s D2 generate a regular polygon + + r s Z1 G0.1 + generate a regular cone + +BUGS + Some combinations of arguments generate odd results. + + Report bugs to qhull_bug@qhull.org, other correspon- + dence to qhull@qhull.org + +SEE ALSO + qhull(1) + +AUTHOR + C. Bradford Barber + bradb@shore.net + + + + + +Geometry Center August 10, 1998 3 + + diff --git a/xs/src/qhull/index.htm b/xs/src/qhull/index.htm new file mode 100644 index 0000000000..4ea7806c93 --- /dev/null +++ b/xs/src/qhull/index.htm @@ -0,0 +1,284 @@ + + + + +Qhull code for Convex Hull, Delaunay Triangulation, Voronoi Diagram, and Halfspace Intersection about a Point + + + + +URL: http://www.qhull.org +
    To: +News +• Download +• CiteSeer +• Images +• Manual +• FAQ +• Programs +• Options +

    + +
    + + +
    +

    Qhull

    + [CONE] +
    +Qhull computes the convex hull, Delaunay triangulation, Voronoi diagram, +halfspace intersection about a point, furthest-site Delaunay +triangulation, and furthest-site Voronoi diagram. The source code runs in +2-d, 3-d, 4-d, and higher dimensions. Qhull implements the Quickhull +algorithm for computing the convex hull. It handles roundoff +errors from floating point arithmetic. It computes volumes, +surface areas, and approximations to the convex hull.

    + + +

    Qhull does not support triangulation of non-convex surfaces, mesh +generation of non-convex objects, medium-sized inputs in 9-D +and higher, alpha shapes, weighted Voronoi diagrams, Voronoi volumes, or +constrained Delaunay triangulations,

    + +

    Qhull 2015.2 introduces reentrant Qhull. It allows concurrent Qhull runs and simplifies the C++ interface to Qhull. +If you call Qhull from your program, you should use reentrant Qhull (libqhull_r) instead of qh_QHpointer (libqhull). +If you use Qhull 2003.1. please upgrade or apply poly.c-qh_gethash.patch. +

    +
    + +
    +
    + + + +
    + +

    Introduction +

      +
    • Fukuda's introduction to convex hulls, Delaunay + triangulations, Voronoi diagrams, and linear programming
    • +
    • Lambert's Java visualization of convex hull algorithms
    • +
    • LEDA Guide to geometry algorithms +
    • MathWorld's Computational Geometry from Wolfram Research +
    • Skiena's Computational Geometry from his Algorithm Design Manual. +
    • Stony Brook Algorithm Repository, computational geometry
    • +
    + +

    Qhull Documentation and Support +

    + +

    Related URLs +

    + +

    FAQs and Newsgroups +

    + +
    + +

    The program includes options for input transformations, +randomization, tracing, multiple output formats, and execution +statistics. The program can be called from within your +application.

    + +

    You can view the results in 2-d, 3-d and 4-d with Geomview. An alternative +is VTK.

    + +

    For an article about Qhull, download from + ACM or CiteSeer: +

    + +
    +

    Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., "The + Quickhull algorithm for convex hulls," ACM Trans. on + Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org

    +
    + +

    Abstract:

    + +
    +

    The convex hull of a set of points is the smallest convex + set that contains the points. This article presents a + practical convex hull algorithm that combines the + two-dimensional Quickhull Algorithm with the general + dimension Beneath-Beyond Algorithm. It is similar to the + randomized, incremental algorithms for convex hull and + Delaunay triangulation. We provide empirical evidence that + the algorithm runs faster when the input contains non-extreme + points, and that it uses less memory.

    +

    Computational geometry algorithms have traditionally + assumed that input sets are well behaved. When an algorithm + is implemented with floating point arithmetic, this + assumption can lead to serious errors. We briefly describe a + solution to this problem when computing the convex hull in + two, three, or four dimensions. The output is a set of + "thick" facets that contain all possible exact convex hulls + of the input. A variation is effective in five or more + dimensions.

    +
    + +
    + +

    Up: Past Software +Projects of the Geometry Center
    +URL: http://www.qhull.org +
    To: +News +• Download +• CiteSeer +• Images +• Manual +• FAQ +• Programs +• Options +

    + +
    + +

    [HOME] The Geometry Center Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: May 17 1995 --- + + diff --git a/xs/src/qhull/origCMakeLists.txt b/xs/src/qhull/origCMakeLists.txt new file mode 100644 index 0000000000..1034d1dea9 --- /dev/null +++ b/xs/src/qhull/origCMakeLists.txt @@ -0,0 +1,426 @@ +# CMakeLists.txt -- CMake configuration file for qhull, qhull6, and related programs +# +# To install CMake +# Download from http://www.cmake.org/download/ +# +# To find the available targets for CMake -G "..." +# cmake --help +# +# To build with MSYS/mingw +# cd build && cmake -G "MSYS Makefiles" .. && cmake .. +# make +# make install +# +# To uninstall on unix or MSYS/mingw +# xargs rm [B. Boeckel] + - Moved include file for each C++ source file to the top of the includes + - Prepend cpp includes with "libqhullcpp/" + - RoadLogEvent includes RoadLogEvent.h + - QhullIterator.h: Only QHULL_DECLARE_SEQUENTIAL_ITERATOR is used. + + - Compared src/libqhull/* to src/libqhull_r/* and resolved differences + - qh_printpoint in io.c skips qh_IDnone like io_r.c + - qhull_p-exports.def: Added three missing exports + - set_r.h: Removed countT. Too many issues + + - libqhull_r/Makefile: Add help prompts to 'make qtest' + - libqhull.pro: Add '../libqhull/' to sources and headers + - libqhull/Makefile: Fixed -I,./,,/src + + - qhull-zip.sh: Add CMakeModules to tarball [C. Rosenvik] + - CMakeLists.txt: Add targets qhullp and user_egp for qh_QHpointer and libqhull_p + - Reorganized 'make help' + - Makefile cleanall: Delete testqset and qhulltest from bin/ + - Fix filetype of Unix-only files + - Fix Unix line endings for Makefile and check in qhull-zip.sh + - Fix Windows line-endings and check in qhull-zip.sh + - qhull-zip.sh: Check for Unix text files + + ------------ +Qhull 2015.1 2016/01/03 (7.1.0) + - Add Rbox option 'Cn,r,m' to add nearly coincident points. Trigger for duplicate ridges + - Add Qhull option 'Q12' to ignore error on wide merge due to duplicate ridge + + - qh_findbestlower: Call qh_findfacet_all to fix rare "flipped or upper Delaunay" error QH6228. + QH6228 input provided by J. Metz. Reported (date order): L. Fiaschi, N. Bowler, A. Liebscher, V. Vieira, N. Rhinehart, N. Vance, P. Shafer + - qh_check_dupridge: Check if wide merge due to duplicate ridge from nearly coincident points + - qh_initialhull: Fix error messages for initial simplex is flat + - qh_determinant: increased 2-d and 3-d nearzero by 10x due to a counter-example + - rbox: Add qh_outcoord() to output coordinates w/ or w/o iscdd + - qh_meminit (mem.c): Add call to qh_memcheck + - Compare libqhull/... to libqhull_r/... and resolve differences + - Update builds for DevStudio (qhull.sln for msdev 2005..2009, qhull-32.sln and qhull-64.sln for recent releases) + + - qh-impre.htm: Add a section about precision errors for 'Nearly coincident points on an edge' + - html/index.htm#geomview: Document how to install, build, and use Geomview. + - html/index.htm: Emphasize program links and move related urls to end + - qhull/index.htm: Emphasize manual, geomview, and imprecision + - Fix documentation links in libqhull_r/index.htm + - Add 'Functions' link to documentation headers + - Change '...' to '...' + - libqhull_r/index.htm -- Add instructions for configuring web browsers for source links. + - libqhull_r/ -- Fix source links for ..._r.htm files + +------------ +Qhull 2015.0.7 2015/11/09 (7.0.7) + - Fix return type of operator-> in QhullLinkedList and other collection classes [F. Jares] + - Fix return types for QhullLinkedList + - Fix return types for QhullPoints + - Simplify return type for Coordinates::operator[] (same as QList) + - Add const to operators for QhullSet::iterator and add documentation + - Coordinates.h: Fix return types for operations of iterator and const_iterator + - Drop use of Perforce changelist number in qhull_VERSION of CMakeLists.txt + - Rename the md5sum files as *.tgz.md5sum instead of *-tgz.md5sum + - Fix build dependency for testqset_r [asekez] + - rbox.c depends on Qhull due to qh_lib_check which uses qh_version2 for error messages + - QhullFacet_test.cpp: Annotate Qhull invocations. Allows their repetition. + - QhullFacet_test.cpp: Adjust epsilon on distance tests + - Do not create libqhullcpp as a shared library. Qhull C++ classes may change layout and size. + - qhull-cpp.xml: Make a relative path to road-faq.xsl + +------------ +Qhull 2015.0.6 2015/10/20 (7.0.6.2013) + - In the libraries, exit() is only called from qh_exit(). qh_exit may be redefined. + - Add qh_fprintf_stderr to usermem.c. May be overridden to avoid use of stderr [D. Sterratt] + Add usermem to testqset builds + Used by qh_fprintf_rbox + - Remove most instances of stderr/stdout from libqhull, libqhull_r, and libqhullcpp [D. Sterratt] + qh_fprintf_stderr may be redefined. qh_meminit and qh_new_qhull use stderr as the default ferr + - qh_initflags: Use qh.fout instead of stdout for 'TO file'. A library caller may define a different qh.fout. + - qh_settemppush: Call qh_fprintf() instead of fprintf() on error. + - Rename qh_call_qhull as "Qhull-template" from user.c. Updated its references. + + - qh-code.htm: "How to avoid exit(), fprintf(), stderr, and stdout" + - html/index.htm: Fix table of contents for qh-code + - libqhull_r/index.htm: Rewrite introduction to Reentrant Qhull + - qh-faq.htm: Rewrite "Can Qhull use coordinates without placing them in a data file?" + - qh-get.html: Link to github + - Remove qhull_interface.cpp from the documentation + +------------ +Qhull 2015.0.5 2015/10/12 (7.0.5.1995) +- qh_new_qhull: default 'errfile' is 'stderr'. outfile and errfile are optional [B. Pearlmutter] +- qh_new_qhull: returns qh_ERRinput instead of exit() if qhull_cmd is not "qhull ..." [B. Pearlmutter] +- qhalf_r.c,etc: Add clear of qh.NOerrexit +- global.c: gcc 4.4.0 mingw32 segfault cleared by adding comment +- usermem_r-cpp.cpp: Optional file to redefine qh_exit() as throw "QH10003.." [B. Pearlmutter] + qh_exit() is called by qhull_r when qh_errexit() is not available. + +- html/index.htm: Add bibliographic reference to Golub & van Loan and other references [R. Gaul] +- qhalf.htm: A halfspace is the points on or below a hyperplane [D. Strawn] +- qh-opto.htm#n: Defined inside, outside, on, above, and below a hyperplane [D. Strawn] +- qhalf.htm#notes: Recast the linear program using negative halfspaces (as used by Qhull) [D. Strawn] +- qhull_a.h: Fix comment '#include "libqhull/qhull_a.h" [fe rew] + +- build/qhull*.pc.in: Templates for pkg-config (derived from Fedorra) [P. McMunn] + https://bitbucket.org/mgorny/pkg-config-spec/src/c1bf12afe0df6d95f2fe3f5e1ffb4c50f018825d/pkg-config-spec.txt?at=master&fileviewer=file-view-default +- Makefile: Remove user_eg3.o from LIBQHULLCPP_OBJS +- Makefile: Add .h dependencies for unix_r.o, etc. +- libqhull/Makefile: Fix build of rbox +- libqhull_r/Makefile: Fix build -I +- qhull.sln/user_eg3: Add dependency on libcpp +- Removed bin/libqhull_r.dll (should be qhull_r.dll) +- Removed build/qhulltest.vcproj (see build/qhulltest/qhulltest.vcproj) + +------------ +Qhull 2015.0.4 2015/9/30 (7.0.4.1984) + - qh-get.htm: Unix tarball includes version number (e.g., qhull-2015-src-7.1.0.1940.tgz) [Hauptman] + - qglobal.c: Add qh_version2 with Unix version for "-V" option [Hauptman] + - build/qhull-32.sln, *-32.vcxproj: Add Visual Studio 32-bit build for 2010+ + - build/qhull-64.sln, *-64.vcxproj: Add Visual Studio 64-bit build for 2010+ [G. Lodron] + - make-vcproj.sh: Restore to eg/... It is required for Visual Studio builds + - README.txt: updated builds and reentrant Qhull + - Add documentation for QHULL_LIB_CHECK + - qh_lib_check: Check for unknown QHULL_LIB_TYPE + - qh-code.htm: Add memory requirements for 32- and 64-bit + +------------ +Qhull 2015.0.3 2015/9/22 + - qh_mem, qh_merge: Log before 'delete' instead of afterwards [Coverity, K. Schwehr] + - qh_merge: Test for NULL horizon in qh_checkzero [Coverity, K. Schwehr] + - qh_matchneighbor: Check for matchfacet not a neighbor of facet [Coverity, K. Schwehr] + - qh_triangulate: Explicit check for visible==NULL [Coverity, K. Schwehr] + - qh_findbestfacet (unused by qhull): Fix test of isoutside [Coverity, K. Schwehr] + - qh_check_maxout: Check bestfacet!=0 for logging its id [Coverity, K. Schwehr] + - qh_nearvertex: Check for bestvertex not found [Coverity, K. Schwehr] + - qh_checkfacet: Check for missing neighbors of simplicial facets [Coverity, K. Schwehr] + - qh_setdelnth: Check 'nth' before using it [Coverity, K. Schwehr] + - Annotate code for Coverity warnings (most of these protected by qh_errexit) [K. Schwehr] + + - qh_printfacet3math: explicit format string (duplicates change to io.c) [B. Pearlmutter] + - libqhull_r.h: fix spelling error (duplicates change to libqhull.h) [B. Pearlmutter] + - unix_r.c: fix spelling error (duplicates change to unix.c) [B. Pearlmutter] + - qhull_a.h: define qhullUnused() only if defined(__cplusplus) [R. Stogner] + - qh_version: Use const char str[]= "string" instead of const char * str= "string" [U. Drepper, p. 27] + - qh_newvertex: Use UINT_MAX instead of 0xFFFFFFFF + - qh_newridge: Use UINT_MAX instead of 0xFFFFFFFF + - Reviewed FIXUP notes + + - QhullRidge_test: t_foreach use 'foreach(const QhullVertex &v, vertices) + - Made '#include "RoadTest.h" consistent across all C++ tests + + - qh-code.htm: May also use libqhull_r (e.g., FOREACHfacet_(...)) + - qh-get.htm: Add list of download build repositories + + - Add CMakeModules/CheckLFS.cmake: Enables Large File Support [B. Pearlmutter] + - Makefile: Use -fpic at all times instead of -fPIC, [U. Drepper p. 15] + +------------ +Qhull 2015.0.2 2015/9/1 + - global_r.c: Fixed spelling of /* duplicated in...qh_clear_outputflags */ [K. Schwehr] + - Replaced Gitorious with GitHub + - Moved 'to do' comments into Changes.txt + +------------ +Qhull 2015.0.1 2015/8/31 + + Source code changes + - Increased size of vertexT.id and ridgeT.id to 2^32 [H. Strandenes, C. Carson, K. Nguyen] + Reworded the warning message for ridgeT.id overflow. It does not affect Qhull output + - Add qh_lib_check to check for a compatible Qhull library. + Programs should call QHULL_LIB_CHECK before calling Qhull. + - Include headers prefixed with libqhull/, libqhull_r/, or libqhullcpp/ + - Renamed debugging routines dfacet/dvertex to qh_dfacet/qh_dvertex + - Rewrote user_eg, user_eg2, and user_eg3 as reentrant code + - Renamed 'qh_rand_seed' to 'qh_last_random'. Declare it as DATA + - qh_initqhull_start2 sets qh->NOerrexit on initialization + User must clear NOerrexit after setjmp() + + Other source code changes + - Define ptr_intT as 'long long' for __MINGW64__ [A. Voskov] + - poly_r.c: initialize horizon_skip [K. Schwehr] + - Removed vertexT.dim and MAX_vdim. It is not used by reentrant Qhull. + - Removed qhull_inuse. Not used by C++ + - Removed old __MWERKS__/__POWERPC__ code that speed up SIOUX I/O + - Moved #include libqhull/... before system includes (e.g., + - Comment-out _isatty declaration. Avoids "C4273 ... inconsistent dll linkage" + - Add random.h/random_r.h as an include file to random.c/random_r.c + - Rename rbox routines to qh_roundi/qh_out1/qh_out2n/qh_out3n + - Rename dfacet and dvertex to qh_dfacet and qh_dvertex + - Replace 'qhmem .zzz' with 'qhmem.zzz' + - Removed spaces between function name and parentheses + - Rename 'enum statistics' to 'enum qh_statistics' + - Declare rbox as DATA in qhull-exports.def and qhull_p-exports.def + - In comments, use 'qh.zzz' to reference qhT fields + - In qh_fprintf, use qhmem.ferr to report errors + - qh_fprintf may be called for errors in qh_initstatistics and qh_meminit + - qh_pointid returns qh_IDnone, qh_IDinterior, qh_IDunknown in place of -3, -2, -1 resp. + - getid_() returns qh_IDunknown in place of -1 + - After qh_meminit, qhmem.ferr is non-zero (stderr is the default) + - Update qh_MEMalign in testqset.c to user.h (with realT and void*) + - Split rboxT into a header file + - Add rboxlib.h to libqhull_a.h + - Rename PI to qh_PI and extend to 30 digits + - Rename MAXdim to qh_MAXdim + - Change spacing for type annotations '*' and '&' in C++ header files + - Test for !rbox_output/cpp_object in qh_fprintf_rbox + - Remove 'inline' annotation from explicit inline declarations + - Column 25 formatting for iterators, etc. + - Use '#//!\name' for section headers + - QhullFacet.cpp: zinc_(Zdistio); + - Clear qhT.ALLOWrestart in qh_errexit + - Replace longjmp with qh_errexit_rbox in qh_rboxpoints + - Add jmpExtra after rbox_errexit to protect against compiler errors + - Add qh.ISqhullQh to indicate initialization by QhullQh() + - Add library warnings to 'rbox D4', user_eg, user_eg2, user_eg3 + - Add headers to q_eg, q_egtest, and q_test + - Check that qh.NOerrexit is cleared before call to qh_initflags + +Qhull documentation + - README.txt: Added references to qh-code.htm + - README.txt: Added section 'Calling Qhull from C programs' + - qh-code.htm: Moved Performance after C++ and C interface + - qh-code.htm: Moved Cpp Questions to end of the C++ section + - qh-code.htm: Fixed documentation for 'include' path. It should be include/libqhull + - qconvex.htm: Fixed documentation for 'i'. It triangulates in 4-d and higher [ref] + - Clarified qhalf space documentation for the interior point [J. Santos] + - rbox.c: Version is same date as qh_version in global.c + - gobal_r.c: Version includes a '.r' suffix to indicate 'reentrant' + +Qhull builds + - Development moved to http://github.com/qhull/qhull + git clone git@github.com:qhull/qhull.git + - Exchanged make targets for testing. + 'make test' is a quick test of qhull programs. + 'make testall' is a thorough test + - Added 'make help' and 'make test' to libqhull and libqhull_r Makefiles + - CMakeLists.txt: Remove libqhull, libqhull_r, and libqhullcpp from include_directories + - CMakeLists.txt: Add qhull_SHAREDR for qhull_r + - CMakeLists.txt: Retain qhull_SHARED and qhull_SHAREDP (qh_QHpointer) + - CMakeLists.txt: Move qhull_SHARED and qhull_SHAREDP (qh_QHpointer) to qhull_TARGETS_OLD + Drop qhull_STATICP (use qhull_SHAREDP or qhull_STATIC) + Set SOVERSION and VERSION for shared libraries + - Move qhull_p-exports.def back to libqhull + - Switched to mingw-w64-install for gcc + - Improved prompts for 'make' + - qhull-all.pro: Remove user_eg3.cpp from OTHER_FILES + - libqhull.pro: Ordered object files by frequency of execution, as done before + - Add the folder name to C++ includes and remove libqhullcpp from INCLUDEPATH + - Changed CONFIG+=qtestlib to QT+=testlib + - Changed Makefile to gcc -O3 (was -O2) + - Changed libqhull/libqhull_r Makefiles to both produce rbox, qhull, ..., user_eg, and user_eg2 + - Removed Debian 'config/...'. It was needed for Qhull 2012. + +libqhull_r (reentrant Qhull) + - Replaced qh_qh with a parameter to each procedure [P. Klosterman] + No more globally defined data structures in Qhull + Simplified multithreading and C++ user interface + All functions are reentrant (Qt: "A reentrant function can ... be called simultaneously from multiple threads, but only if each invocation uses its own data.") + No more qh_QHpointer. + See user_eg3 and qhulltest + New libraries + libqhull_r -- Shared library with reentrant sources (e.g., poly_r.h and poly_r.c which replace libqhull's poly.h and poly.c) + libqhullstatic_r -- Static library with the same sources as libqhull_r + libqhullcpp -- The C++ interface using libqhullstatic_r (further notes below) + New executables + testqset_r -- Test qset_r.c (the reentrant version of qset.c + + Source code changes for libqhull_r + - Add qh_zero() to initialize and zero memory for qh_new_qhull + - Remove qh_save_qhull(), qh_restore_qhull(), and qh.old_qhstat from global_r.c + - Remove qh_freeqhull2() (global_r.c) + - Remove qh_freestatistics() (stat_r.c) + - Remove qh_compare_vertexpoint (qhT is not available, unused code) + - Remove conditional code for __POWERPC__ from unix_r.c and rbox_r.c + - Move qh_last_random into qh->last_random (random_r.c) + - Rename sources files with a '_r' suffix. qhull_a.h becomes qhull_ra.h + - Replace 'qh' macro with 'qh->' + - Replace global qhT with parameter-0 + - Add qhmemT to beginning of qhT. It may not be used standalone. + - Add qhstatT to end of qhT + - Remove qhull_inuse + - Change qhmem.zzz to qh->qhmem.zzz + - Replace qh_qhstat with qh->qhstat + - Remove qh_freestatistics + - Replace qh_last_random with qh->last_random + - Replace rboxT with qh->rbox_errexit, rbox_isinteger, rbox_out_offset + - Replace rbox.ferr/fout with qh->ferr/fout + - No qh for qh_exit, qh_free, qh_malloc, qh_strtod, qh_strtol, qh_stddev + - New qmake include files qhull-app-c_r.pri, qhull-app-shared_r.pri, qhull-libqhull-src_r.pri + - Replace 'int' with 'countT' and 'COUNTmax' for large counts and identifiers + - qhset converted to countT + - Removed vertexT.dim -- No longer needed by cpp + Removed MAX_vdim + - Guarantee that qh->run_id!=0. Old code assumed that qh_RANDOMint was 31 bits + +Changes to libqhullcpp + - Added QhullVertexSet.h to libqhullcpp.pro and libqhullpcpp.pro + - QhullVertexSet: error if qhsettemp_defined at copy constructor/assignment (otherwise double free) + - Enable QhullSet.operator=. Copy constructor and assignment only copies pointers + - Changed QhullPoint.operator==() to sqrt(distanceEpsilon) + - Added assignment of base class QhullPoints to PointCoordinates.operator= + - Enable QhullPoints.operator= + - Rename PointCoordinates.point_comment to describe_points + - Add 'typename T' to definition of QhullSet::value() + +C++ interface + - Reimplemented C++ interface on reentrant libqhull_r instead of libqhull + - Prepend include files with libqhullcpp/ + - Replaced UsingLibQhull with QhullQh and macro QH_TRY + Removed UsingLibQhull.currentAngleEpsilon and related routines + Removed UsingLibQhull_test.cpp + Replaced globalDistanceEpsilon with QhullQh.distanceEpsilon + Replaced globalAngleEpsilon with QhullQh.angleEpsilon + Moved UsingQhullLib.checkQhullMemoryEmpty to QhullQh.checkAndFreeQhullMemory + Replaced FACTORepsilon=10 with QhullQh.factor_epsilon=1.0 + - To avoid -Wshadow for QhullQh*, use 'qqh' for parameters and 'qh()' for methods + - Moved messaging from Qhull to QhullQh + - Add check of RboxPoints* in qh_fprintf_rbox + - Renamed Qhull.initializeQhull to Qhull.allocateQhullQh + Added qh_freeqhull(!qh_ALL) as done by unix.c and other programs + - Moved QhullPoints.extraCoordinatesCount into QhullPoints.cpp + - Replaced section tags with '#//!\name ...' + - Removed qhRunId from print() to ostream. + - Removed print() to ostream. Use '<< qhullPoint' or '<< qhullPoint.print("message")' + +C++ interface for most classes + - Remove qhRunId + - Add QhullQh *qh_qh to all types + Pointer comparisons of facetT,etc. do not test corresponding qh_qh + Added to end of type for debugging information, unless wasteful alignment + - Add QhullQh * to all constructors + - All constructors may use Qhull & instead of QhullQh * + - For inherited QhullQh types, change to 'protected' + - Renamed 'o' to 'other' except where used extensively in iterators + - Except for conditional code, merged the Conversion section into GetSet + - Removed empty(). Use isEmpty() instead + - Add operator= instead of keeping it private + - print_message=0 not allowed. Use "" instead. + - Rename isDefined() to isValid() to match Qt conventions + +C++ interface by class + - Coordinates + Removed empty(). Use isEmpty() instead + Added append(dim, coordT*) + Reformated the iterators + Convert to countT + - PointCoordinates + Added constructors for Qhull or QhullQh* (provides access to QhullPoint.operator==) + Removed PointCoordinates(int pointDimension) since PointCoordinates should have a comment. Also, it is ambiguous with PointCoordinates(QhullQh*) + Renamed point_comment to describe_points + Convert to countT + - Qhull + Remove qhull_run_i + Remove qh_active + Replace property feasiblePoint with field feasible_point and methods setFeasiblePoint/feasiblePoint + Returns qh.feasible_point if defined + Moved useOutputStream to QhullQh use_output_stream + Renamed useOutputStream() to hasOutputStream() + Replaced qhull_dimension with qh->input_dim //! Dimension of result (qh.hull_dim or one less for Delaunay/Voronoi) + Removed global s_qhull_output= 0; + Move qhull_status, qhull_message, error_stream, output_stream to QhullQh + Renamed qhullQh() to qh() + Added check of base address to allocateQhullQh(), Was not needed for qhullpcpp + - QhullFacet + Constructor requires Qhull or QhullQh* pointer + Convert to countT + Dropped implicit conversion from facetT + Dropped runId + Add print("message") to replace print() + - QhullFacetList + Constructor requires Qhull or QhullQh* pointer + Convert to countT + Dropped runId + - QhullFacetSet + Removed empty(). Use isEmpty() instead + Constructor requires Qhull or QhullQh* pointer + Convert to countT + Dropped runId + Add operator= + Implement print("message") + - QhullHyperplane + Add hyperplaneAngle() method + Rewrite operator== to use hyperplaneAngle() + Reorganize fields to keep pointers aligned + Except for default constructor requires Qhull or QhullQh* pointer + Enable copy assignment + Reorganized header + - QhullLinkedList + Add operator= + Removed empty(). Use isEmpty() instead + Convert to countT + iterator(T) made iterator(const T &) + const_iterator(T) made const_iterator(const T &) + const_iterator(iterator) made const_iterator(const iterator &) + - QhullPoint + Add constructors for Qhull or QhullQh* pointer (for id() and operator==) + Add defineAs(coordT*) + Add getBaseT() and base_type for QhullSet + Added checks for point_coordinates==0 + Removed static QhullPoint::id(), use QhullPoint.id() instead + distance() throws an error if dimension doesn't agree or if a point is undefined + Convert to countT + If !qh_qh, operator==() requires equal coordinates + Use cout<

    [R. Richter, S. Pasko] + - Remove deprecated libqhull/qhull.h + Use libqhull/libqhull.h instead. Avoids confusion with libqhullcpp/Qhull.h + - Makefile: Add LIBDIR, INCDIR, and DESTDIR to install [L.H. de Mello] + Separate MAN install from DOC install + Create install directories + Installs headers to include/libqhull, include/libqhullcpp, include/road + - CMakeLists.txt: Add MAN_INSTALL_DIR for qhull.1 and rbox.1 man pages + Add RoadTest.h to include/road for Qt users (road_HEADERS) + - Renamed md5sum files to avoid two extensions + - qh-get.htm: Add Readme links and 2009.1 note. + - qh-optf.htm: Fix link + - index.htm: Updated Google Scholar link + - qhull-zip.sh: Improved error message. + +------------ +Qhull 2011.1 2011/04/17 6.2.0.1373 + +Changes to deliverables + - qvoronoi: Deprecated 'Qt' and 'QJn'. Removed from documentation and prompts. + These options produced duplicate Voronoi vertices for cospherical data. + - Removed doskey from Qhull-go.bat. It is incompatible with Windows 7 + - Added 'facets' argument to user_eg3.cpp + - user_eg links with shared library + - qhulltest.cpp: Add closing prompt. + +Changes to build system + - Reorganized source directories + - Moved executables to bin directory + - Add CMake build for all targets (CMakeFiles.txt) [M. Moll assisted] + - Add gcc build for all targets (Makefile) + - Fixed location of qhull.man and rbox.man [M. Moll] + - Add DevStudio builds for all targets (build/*.vcproj) + - Added shared library (lib/qhull6.dll) + Added qh_QHpointer_dllimport to work around problems with MSVC + - Added static libraries with and without qh_QHpointer (lib/qhullstatic.lib) + - Added eg/make-vcproj.sh to create vcproj/sln files from cmake and qmake + - Document location of qh_QHpointer + - Use shadow build directory + - Made -fno-strict-aliasing conditional on gcc version + - Added src/qhull-app-cpp.pri, src/qhull-app-c.pri, etc. for common settings + - Add .gitignore with ignored files and directories. + - Use .git/info/exclude for locally excluded files. + - Fixed MBorland for new directory structure + - cleanall (Makefile): Delete 'linked' programs due to libqhull_r and libqhull/Makefile + +Changes to documentation + - qvoronoi.htm: Remove quotes from qvoronoi example + - qhull-cpp.xml: Add naming conventions + - index.htm: Add Google Scholar references + - qh-optf.htm: Add note about order of 'Fn' matching 'Fv' order [Q. Pan] + - Add patch for old builds in qh-get.htm + - Added C++ compiling instructions to README.txt + - Add instructions for fixing the DOS window + - Changed DOS window to command window + - Fixed html links + - qh-get.htm: Dropped the Spanish mirror site. It was disabled. + +Changes to C code + - mem.h: Define ptr_intT as 'long long' for Microsoft Windows _win64 builds. + On Linux and Mac, 'long' is 64-bits on a 64-bit host + - Added qh_QHpointer_dllimport to work around MSVC problem + - qconvex.c,etc.: Define prototype for _isatty + - Define MSG_QHULL_ERROR in user.h + - Move MSG_FIXUP to 11000 and updated FIXUP QH11... + +Changes to test code + - Add note to q_test than R1e-3 may error (qh-code.htm, Enhancements) + - Add test for executables to q_eg, etc. + - Fixed Qhull-go.bat. QHULL-GO invokes it with command.com, + +Changes to C++ interface + - QhullFacet: Added isSimplicial, isTopOrient, isTriCoplanar, isUpperDelaunay + - Added Qhull::defineVertexFacetNeighbors() for facetNeighbors of vertices. + Automatically called for facet merging and Voronoi diagrams + Do not print QhullVertex::facetNeighbors is !facetNeighborsDefined() + - Assigned FIXUP identifiers + - QhullError: Add copy constructor, assignment operator, and destructor + - Add throw() specifiers to RoadError and QhullError + - Renamed RoadError::defined() to RoadError::isDefined() + - Add #error to Qhull.h if qh_QHpointer is not defined + +Changes to C++ code + - Fixed bug reported by renangms. Vertex output throws error QH10034 + and defineVertexNeighbors() does not exist. + - Define QHULL_USES_QT for qt-qhull.cpp [renangms] + - Reviewed all copy constructors and copy assignments. Updated comments. + Defined Qhull copy constructor and copy assignment [G. Rivet-Sabourin] + Disabled UsingQhullLib default constructor, copy construct, and copy assign + - Merged changes from J. Obermayr in gitorious/jobermayrs-qhull:next + - Fix strncat limit in rboxlib.c and global.c + - Changes to CMakeLists.txt for openSUSE + - Fixed additional uses of strncat + - Fixed QhullFacet::PrintRidges to check hasNextRidge3d() + - Removed gcc warnings for shadowing from code (src/qhull-warn.pri) + - Removed semicolon after extern "C" {...} + - Removed experimental QhullEvent/QhullLog + - Use fabs() instead of abs() to avoid accidental conversions to int + - Fixed type of vertex->neighbors in qh_printvoronoi [no effect on results] + - Removed unnecessary if statement in qh_printvoronoi + +------------ +qhull 2010.1 2010/01/14 +- Fixed quote for #include in qhull.h [U.Hergenhahn, K.Roland] +- Add qt-qhull.cpp with Qt conditional code +- Add libqhullp.proj +- Add libqhull5 to Readme, Announce, download +- Reviewed #pragma +- Reviewed FIXUP and assigned QH tags +- All projects compile with warnings enabled +- Replaced 'up' glyphs with » +- Moved cpp questions to qh-code.htm#questions-cpp +- Moved suggestions to qh-code.htm#enhance +- Moved documentation requests to qh-code.htm#enhance +- Add md5sum file to distributions +- Switched to DevStudio builds to avoid dependent libraries, 10% slower + Removed user_eg3.exe and qhullcpp.dll from Windows build + Fix qhull.sln and project files for qh_QHpointer +- Add eg/qhull-zip.sh to build qhull distribution files + +------------ +qhull 2010.1 2010/01/10 +- Test for NULL fp in qh_eachvoronoi [D. Szczerba] + +qhull 2010.1 2010/01/09 + +Changes to build and distribution +- Use qh_QHpointer=0 for libqhull.a, qhull, rbox, etc. + Use -Dqh_QHpointer for libqhullp.a, qhullcpp.dll, etc. + qh_QHpointer [2010, gcc] 4% time 4% space, [2003, msvc] 8% time 2% space +- Add config/ and project/debian/ for Autoconf build [R. Laboissiere] + from debian branch in git and http://savannah.nongnu.org/cvs/?group=qhull +- Add CMakeLists.txt [kwilliams] +- Fix tabs in Makefile.txt [mschamschula] +- Add -fno-strict-aliasing to Makefile for gcc 4.1, 4.2, and 4.3 qset segfault +- Remove user_eg.exe and user_eg2.exe from Windows distribution +- Order object files by frequency of execution for better locality. + +Changes to source +- Remove ptr_intT from qh_matchvertices. It was int since the beginning. +- user.h requires for CLOCKS_PER_SEC +- Move ostream<

      ---------------------------------
    +
    +   geom.c
    +   geometric routines of qhull
    +
    +   see qh-geom.htm and geom.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/geom.c#2 $$Change: 1995 $
    +   $DateTime: 2015/10/13 21:59:42 $$Author: bbarber $
    +
    +   infrequent code goes into geom2.c
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*---------------------------------
    +
    +  qh_distplane( point, facet, dist )
    +    return distance from point to facet
    +
    +  returns:
    +    dist
    +    if qh.RANDOMdist, joggles result
    +
    +  notes:
    +    dist > 0 if point is above facet (i.e., outside)
    +    does not error (for qh_sortfacets, qh_outerinner)
    +
    +  see:
    +    qh_distnorm in geom2.c
    +    qh_distplane [geom.c], QhullFacet::distance, and QhullHyperplane::distance are copies
    +*/
    +void qh_distplane(pointT *point, facetT *facet, realT *dist) {
    +  coordT *normal= facet->normal, *coordp, randr;
    +  int k;
    +
    +  switch (qh hull_dim){
    +  case 2:
    +    *dist= facet->offset + point[0] * normal[0] + point[1] * normal[1];
    +    break;
    +  case 3:
    +    *dist= facet->offset + point[0] * normal[0] + point[1] * normal[1] + point[2] * normal[2];
    +    break;
    +  case 4:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3];
    +    break;
    +  case 5:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4];
    +    break;
    +  case 6:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5];
    +    break;
    +  case 7:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6];
    +    break;
    +  case 8:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6]+point[7]*normal[7];
    +    break;
    +  default:
    +    *dist= facet->offset;
    +    coordp= point;
    +    for (k=qh hull_dim; k--; )
    +      *dist += *coordp++ * *normal++;
    +    break;
    +  }
    +  zinc_(Zdistplane);
    +  if (!qh RANDOMdist && qh IStracing < 4)
    +    return;
    +  if (qh RANDOMdist) {
    +    randr= qh_RANDOMint;
    +    *dist += (2.0 * randr / qh_RANDOMmax - 1.0) *
    +      qh RANDOMfactor * qh MAXabs_coord;
    +  }
    +  if (qh IStracing >= 4) {
    +    qh_fprintf(qh ferr, 8001, "qh_distplane: ");
    +    qh_fprintf(qh ferr, 8002, qh_REAL_1, *dist);
    +    qh_fprintf(qh ferr, 8003, "from p%d to f%d\n", qh_pointid(point), facet->id);
    +  }
    +  return;
    +} /* distplane */
    +
    +
    +/*---------------------------------
    +
    +  qh_findbest( point, startfacet, bestoutside, qh_ISnewfacets, qh_NOupper, dist, isoutside, numpart )
    +    find facet that is furthest below a point
    +    for upperDelaunay facets
    +      returns facet only if !qh_NOupper and clearly above
    +
    +  input:
    +    starts search at 'startfacet' (can not be flipped)
    +    if !bestoutside(qh_ALL), stops at qh.MINoutside
    +
    +  returns:
    +    best facet (reports error if NULL)
    +    early out if isoutside defined and bestdist > qh.MINoutside
    +    dist is distance to facet
    +    isoutside is true if point is outside of facet
    +    numpart counts the number of distance tests
    +
    +  see also:
    +    qh_findbestnew()
    +
    +  notes:
    +    If merging (testhorizon), searches horizon facets of coplanar best facets because
    +    after qh_distplane, this and qh_partitionpoint are the most expensive in 3-d
    +      avoid calls to distplane, function calls, and real number operations.
    +    caller traces result
    +    Optimized for outside points.   Tried recording a search set for qh_findhorizon.
    +    Made code more complicated.
    +
    +  when called by qh_partitionvisible():
    +    indicated by qh_ISnewfacets
    +    qh.newfacet_list is list of simplicial, new facets
    +    qh_findbestnew set if qh_sharpnewfacets returns True (to use qh_findbestnew)
    +    qh.bestfacet_notsharp set if qh_sharpnewfacets returns False
    +
    +  when called by qh_findfacet(), qh_partitionpoint(), qh_partitioncoplanar(),
    +                 qh_check_bestdist(), qh_addpoint()
    +    indicated by !qh_ISnewfacets
    +    returns best facet in neighborhood of given facet
    +      this is best facet overall if dist > -   qh.MAXcoplanar
    +        or hull has at least a "spherical" curvature
    +
    +  design:
    +    initialize and test for early exit
    +    repeat while there are better facets
    +      for each neighbor of facet
    +        exit if outside facet found
    +        test for better facet
    +    if point is inside and partitioning
    +      test for new facets with a "sharp" intersection
    +      if so, future calls go to qh_findbestnew()
    +    test horizon facets
    +*/
    +facetT *qh_findbest(pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT isnewfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart) {
    +  realT bestdist= -REALmax/2 /* avoid underflow */;
    +  facetT *facet, *neighbor, **neighborp;
    +  facetT *bestfacet= NULL, *lastfacet= NULL;
    +  int oldtrace= qh IStracing;
    +  unsigned int visitid= ++qh visit_id;
    +  int numpartnew=0;
    +  boolT testhorizon = True; /* needed if precise, e.g., rbox c D6 | qhull Q0 Tv */
    +
    +  zinc_(Zfindbest);
    +  if (qh IStracing >= 3 || (qh TRACElevel && qh TRACEpoint >= 0 && qh TRACEpoint == qh_pointid(point))) {
    +    if (qh TRACElevel > qh IStracing)
    +      qh IStracing= qh TRACElevel;
    +    qh_fprintf(qh ferr, 8004, "qh_findbest: point p%d starting at f%d isnewfacets? %d, unless %d exit if > %2.2g\n",
    +             qh_pointid(point), startfacet->id, isnewfacets, bestoutside, qh MINoutside);
    +    qh_fprintf(qh ferr, 8005, "  testhorizon? %d noupper? %d", testhorizon, noupper);
    +    qh_fprintf(qh ferr, 8006, "  Last point added was p%d.", qh furthest_id);
    +    qh_fprintf(qh ferr, 8007, "  Last merge was #%d.  max_outside %2.2g\n", zzval_(Ztotmerge), qh max_outside);
    +  }
    +  if (isoutside)
    +    *isoutside= True;
    +  if (!startfacet->flipped) {  /* test startfacet */
    +    *numpart= 1;
    +    qh_distplane(point, startfacet, dist);  /* this code is duplicated below */
    +    if (!bestoutside && *dist >= qh MINoutside
    +    && (!startfacet->upperdelaunay || !noupper)) {
    +      bestfacet= startfacet;
    +      goto LABELreturn_best;
    +    }
    +    bestdist= *dist;
    +    if (!startfacet->upperdelaunay) {
    +      bestfacet= startfacet;
    +    }
    +  }else
    +    *numpart= 0;
    +  startfacet->visitid= visitid;
    +  facet= startfacet;
    +  while (facet) {
    +    trace4((qh ferr, 4001, "qh_findbest: neighbors of f%d, bestdist %2.2g f%d\n",
    +                facet->id, bestdist, getid_(bestfacet)));
    +    lastfacet= facet;
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->newfacet && isnewfacets)
    +        continue;
    +      if (neighbor->visitid == visitid)
    +        continue;
    +      neighbor->visitid= visitid;
    +      if (!neighbor->flipped) {  /* code duplicated above */
    +        (*numpart)++;
    +        qh_distplane(point, neighbor, dist);
    +        if (*dist > bestdist) {
    +          if (!bestoutside && *dist >= qh MINoutside
    +          && (!neighbor->upperdelaunay || !noupper)) {
    +            bestfacet= neighbor;
    +            goto LABELreturn_best;
    +          }
    +          if (!neighbor->upperdelaunay) {
    +            bestfacet= neighbor;
    +            bestdist= *dist;
    +            break; /* switch to neighbor */
    +          }else if (!bestfacet) {
    +            bestdist= *dist;
    +            break; /* switch to neighbor */
    +          }
    +        } /* end of *dist>bestdist */
    +      } /* end of !flipped */
    +    } /* end of FOREACHneighbor */
    +    facet= neighbor;  /* non-NULL only if *dist>bestdist */
    +  } /* end of while facet (directed search) */
    +  if (isnewfacets) {
    +    if (!bestfacet) {
    +      bestdist= -REALmax/2;
    +      bestfacet= qh_findbestnew(point, startfacet->next, &bestdist, bestoutside, isoutside, &numpartnew);
    +      testhorizon= False; /* qh_findbestnew calls qh_findbesthorizon */
    +    }else if (!qh findbest_notsharp && bestdist < - qh DISTround) {
    +      if (qh_sharpnewfacets()) {
    +        /* seldom used, qh_findbestnew will retest all facets */
    +        zinc_(Zfindnewsharp);
    +        bestfacet= qh_findbestnew(point, bestfacet, &bestdist, bestoutside, isoutside, &numpartnew);
    +        testhorizon= False; /* qh_findbestnew calls qh_findbesthorizon */
    +        qh findbestnew= True;
    +      }else
    +        qh findbest_notsharp= True;
    +    }
    +  }
    +  if (!bestfacet)
    +    bestfacet= qh_findbestlower(lastfacet, point, &bestdist, numpart);
    +  if (testhorizon)
    +    bestfacet= qh_findbesthorizon(!qh_IScheckmax, point, bestfacet, noupper, &bestdist, &numpartnew);
    +  *dist= bestdist;
    +  if (isoutside && bestdist < qh MINoutside)
    +    *isoutside= False;
    +LABELreturn_best:
    +  zadd_(Zfindbesttot, *numpart);
    +  zmax_(Zfindbestmax, *numpart);
    +  (*numpart) += numpartnew;
    +  qh IStracing= oldtrace;
    +  return bestfacet;
    +}  /* findbest */
    +
    +
    +/*---------------------------------
    +
    +  qh_findbesthorizon( qh_IScheckmax, point, startfacet, qh_NOupper, &bestdist, &numpart )
    +    search coplanar and better horizon facets from startfacet/bestdist
    +    ischeckmax turns off statistics and minsearch update
    +    all arguments must be initialized
    +  returns(ischeckmax):
    +    best facet
    +  returns(!ischeckmax):
    +    best facet that is not upperdelaunay
    +    allows upperdelaunay that is clearly outside
    +  returns:
    +    bestdist is distance to bestfacet
    +    numpart -- updates number of distance tests
    +
    +  notes:
    +    no early out -- use qh_findbest() or qh_findbestnew()
    +    Searches coplanar or better horizon facets
    +
    +  when called by qh_check_maxout() (qh_IScheckmax)
    +    startfacet must be closest to the point
    +      Otherwise, if point is beyond and below startfacet, startfacet may be a local minimum
    +      even though other facets are below the point.
    +    updates facet->maxoutside for good, visited facets
    +    may return NULL
    +
    +    searchdist is qh.max_outside + 2 * DISTround
    +      + max( MINvisible('Vn'), MAXcoplanar('Un'));
    +    This setting is a guess.  It must be at least max_outside + 2*DISTround
    +    because a facet may have a geometric neighbor across a vertex
    +
    +  design:
    +    for each horizon facet of coplanar best facets
    +      continue if clearly inside
    +      unless upperdelaunay or clearly outside
    +         update best facet
    +*/
    +facetT *qh_findbesthorizon(boolT ischeckmax, pointT* point, facetT *startfacet, boolT noupper, realT *bestdist, int *numpart) {
    +  facetT *bestfacet= startfacet;
    +  realT dist;
    +  facetT *neighbor, **neighborp, *facet;
    +  facetT *nextfacet= NULL; /* optimize last facet of coplanarfacetset */
    +  int numpartinit= *numpart, coplanarfacetset_size;
    +  unsigned int visitid= ++qh visit_id;
    +  boolT newbest= False; /* for tracing */
    +  realT minsearch, searchdist;  /* skip facets that are too far from point */
    +
    +  if (!ischeckmax) {
    +    zinc_(Zfindhorizon);
    +  }else {
    +#if qh_MAXoutside
    +    if ((!qh ONLYgood || startfacet->good) && *bestdist > startfacet->maxoutside)
    +      startfacet->maxoutside= *bestdist;
    +#endif
    +  }
    +  searchdist= qh_SEARCHdist; /* multiple of qh.max_outside and precision constants */
    +  minsearch= *bestdist - searchdist;
    +  if (ischeckmax) {
    +    /* Always check coplanar facets.  Needed for RBOX 1000 s Z1 G1e-13 t996564279 | QHULL Tv */
    +    minimize_(minsearch, -searchdist);
    +  }
    +  coplanarfacetset_size= 0;
    +  facet= startfacet;
    +  while (True) {
    +    trace4((qh ferr, 4002, "qh_findbesthorizon: neighbors of f%d bestdist %2.2g f%d ischeckmax? %d noupper? %d minsearch %2.2g searchdist %2.2g\n",
    +                facet->id, *bestdist, getid_(bestfacet), ischeckmax, noupper,
    +                minsearch, searchdist));
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == visitid)
    +        continue;
    +      neighbor->visitid= visitid;
    +      if (!neighbor->flipped) {
    +        qh_distplane(point, neighbor, &dist);
    +        (*numpart)++;
    +        if (dist > *bestdist) {
    +          if (!neighbor->upperdelaunay || ischeckmax || (!noupper && dist >= qh MINoutside)) {
    +            bestfacet= neighbor;
    +            *bestdist= dist;
    +            newbest= True;
    +            if (!ischeckmax) {
    +              minsearch= dist - searchdist;
    +              if (dist > *bestdist + searchdist) {
    +                zinc_(Zfindjump);  /* everything in qh.coplanarfacetset at least searchdist below */
    +                coplanarfacetset_size= 0;
    +              }
    +            }
    +          }
    +        }else if (dist < minsearch)
    +          continue;  /* if ischeckmax, dist can't be positive */
    +#if qh_MAXoutside
    +        if (ischeckmax && dist > neighbor->maxoutside)
    +          neighbor->maxoutside= dist;
    +#endif
    +      } /* end of !flipped */
    +      if (nextfacet) {
    +        if (!coplanarfacetset_size++) {
    +          SETfirst_(qh coplanarfacetset)= nextfacet;
    +          SETtruncate_(qh coplanarfacetset, 1);
    +        }else
    +          qh_setappend(&qh coplanarfacetset, nextfacet); /* Was needed for RBOX 1000 s W1e-13 P0 t996547055 | QHULL d Qbb Qc Tv
    +                                                 and RBOX 1000 s Z1 G1e-13 t996564279 | qhull Tv  */
    +      }
    +      nextfacet= neighbor;
    +    } /* end of EACHneighbor */
    +    facet= nextfacet;
    +    if (facet)
    +      nextfacet= NULL;
    +    else if (!coplanarfacetset_size)
    +      break;
    +    else if (!--coplanarfacetset_size) {
    +      facet= SETfirstt_(qh coplanarfacetset, facetT);
    +      SETtruncate_(qh coplanarfacetset, 0);
    +    }else
    +      facet= (facetT*)qh_setdellast(qh coplanarfacetset);
    +  } /* while True, for each facet in qh.coplanarfacetset */
    +  if (!ischeckmax) {
    +    zadd_(Zfindhorizontot, *numpart - numpartinit);
    +    zmax_(Zfindhorizonmax, *numpart - numpartinit);
    +    if (newbest)
    +      zinc_(Zparthorizon);
    +  }
    +  trace4((qh ferr, 4003, "qh_findbesthorizon: newbest? %d bestfacet f%d bestdist %2.2g\n", newbest, getid_(bestfacet), *bestdist));
    +  return bestfacet;
    +}  /* findbesthorizon */
    +
    +/*---------------------------------
    +
    +  qh_findbestnew( point, startfacet, dist, isoutside, numpart )
    +    find best newfacet for point
    +    searches all of qh.newfacet_list starting at startfacet
    +    searches horizon facets of coplanar best newfacets
    +    searches all facets if startfacet == qh.facet_list
    +  returns:
    +    best new or horizon facet that is not upperdelaunay
    +    early out if isoutside and not 'Qf'
    +    dist is distance to facet
    +    isoutside is true if point is outside of facet
    +    numpart is number of distance tests
    +
    +  notes:
    +    Always used for merged new facets (see qh_USEfindbestnew)
    +    Avoids upperdelaunay facet unless (isoutside and outside)
    +
    +    Uses qh.visit_id, qh.coplanarfacetset.
    +    If share visit_id with qh_findbest, coplanarfacetset is incorrect.
    +
    +    If merging (testhorizon), searches horizon facets of coplanar best facets because
    +    a point maybe coplanar to the bestfacet, below its horizon facet,
    +    and above a horizon facet of a coplanar newfacet.  For example,
    +      rbox 1000 s Z1 G1e-13 | qhull
    +      rbox 1000 s W1e-13 P0 t992110337 | QHULL d Qbb Qc
    +
    +    qh_findbestnew() used if
    +       qh_sharpnewfacets -- newfacets contains a sharp angle
    +       if many merges, qh_premerge found a merge, or 'Qf' (qh.findbestnew)
    +
    +  see also:
    +    qh_partitionall() and qh_findbest()
    +
    +  design:
    +    for each new facet starting from startfacet
    +      test distance from point to facet
    +      return facet if clearly outside
    +      unless upperdelaunay and a lowerdelaunay exists
    +         update best facet
    +    test horizon facets
    +*/
    +facetT *qh_findbestnew(pointT *point, facetT *startfacet,
    +           realT *dist, boolT bestoutside, boolT *isoutside, int *numpart) {
    +  realT bestdist= -REALmax/2;
    +  facetT *bestfacet= NULL, *facet;
    +  int oldtrace= qh IStracing, i;
    +  unsigned int visitid= ++qh visit_id;
    +  realT distoutside= 0.0;
    +  boolT isdistoutside; /* True if distoutside is defined */
    +  boolT testhorizon = True; /* needed if precise, e.g., rbox c D6 | qhull Q0 Tv */
    +
    +  if (!startfacet) {
    +    if (qh MERGING)
    +      qh_fprintf(qh ferr, 6001, "qhull precision error (qh_findbestnew): merging has formed and deleted a cone of new facets.  Can not continue.\n");
    +    else
    +      qh_fprintf(qh ferr, 6002, "qhull internal error (qh_findbestnew): no new facets for point p%d\n",
    +              qh furthest_id);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  zinc_(Zfindnew);
    +  if (qh BESToutside || bestoutside)
    +    isdistoutside= False;
    +  else {
    +    isdistoutside= True;
    +    distoutside= qh_DISToutside; /* multiple of qh.MINoutside & qh.max_outside, see user.h */
    +  }
    +  if (isoutside)
    +    *isoutside= True;
    +  *numpart= 0;
    +  if (qh IStracing >= 3 || (qh TRACElevel && qh TRACEpoint >= 0 && qh TRACEpoint == qh_pointid(point))) {
    +    if (qh TRACElevel > qh IStracing)
    +      qh IStracing= qh TRACElevel;
    +    qh_fprintf(qh ferr, 8008, "qh_findbestnew: point p%d facet f%d. Stop? %d if dist > %2.2g\n",
    +             qh_pointid(point), startfacet->id, isdistoutside, distoutside);
    +    qh_fprintf(qh ferr, 8009, "  Last point added p%d visitid %d.",  qh furthest_id, visitid);
    +    qh_fprintf(qh ferr, 8010, "  Last merge was #%d.\n", zzval_(Ztotmerge));
    +  }
    +  /* visit all new facets starting with startfacet, maybe qh facet_list */
    +  for (i=0, facet=startfacet; i < 2; i++, facet= qh newfacet_list) {
    +    FORALLfacet_(facet) {
    +      if (facet == startfacet && i)
    +        break;
    +      facet->visitid= visitid;
    +      if (!facet->flipped) {
    +        qh_distplane(point, facet, dist);
    +        (*numpart)++;
    +        if (*dist > bestdist) {
    +          if (!facet->upperdelaunay || *dist >= qh MINoutside) {
    +            bestfacet= facet;
    +            if (isdistoutside && *dist >= distoutside)
    +              goto LABELreturn_bestnew;
    +            bestdist= *dist;
    +          }
    +        }
    +      } /* end of !flipped */
    +    } /* FORALLfacet from startfacet or qh newfacet_list */
    +  }
    +  if (testhorizon || !bestfacet) /* testhorizon is always True.  Keep the same code as qh_findbest */
    +    bestfacet= qh_findbesthorizon(!qh_IScheckmax, point, bestfacet ? bestfacet : startfacet,
    +                                        !qh_NOupper, &bestdist, numpart);
    +  *dist= bestdist;
    +  if (isoutside && *dist < qh MINoutside)
    +    *isoutside= False;
    +LABELreturn_bestnew:
    +  zadd_(Zfindnewtot, *numpart);
    +  zmax_(Zfindnewmax, *numpart);
    +  trace4((qh ferr, 4004, "qh_findbestnew: bestfacet f%d bestdist %2.2g\n", getid_(bestfacet), *dist));
    +  qh IStracing= oldtrace;
    +  return bestfacet;
    +}  /* findbestnew */
    +
    +/* ============ hyperplane functions -- keep code together [?] ============ */
    +
    +/*---------------------------------
    +
    +  qh_backnormal( rows, numrow, numcol, sign, normal, nearzero )
    +    given an upper-triangular rows array and a sign,
    +    solve for normal equation x using back substitution over rows U
    +
    +  returns:
    +     normal= x
    +
    +     if will not be able to divzero() when normalized(qh.MINdenom_2 and qh.MINdenom_1_2),
    +       if fails on last row
    +         this means that the hyperplane intersects [0,..,1]
    +         sets last coordinate of normal to sign
    +       otherwise
    +         sets tail of normal to [...,sign,0,...], i.e., solves for b= [0...0]
    +         sets nearzero
    +
    +  notes:
    +     assumes numrow == numcol-1
    +
    +     see Golub & van Loan, 1983, Eq. 4.4-9 for "Gaussian elimination with complete pivoting"
    +
    +     solves Ux=b where Ax=b and PA=LU
    +     b= [0,...,0,sign or 0]  (sign is either -1 or +1)
    +     last row of A= [0,...,0,1]
    +
    +     1) Ly=Pb == y=b since P only permutes the 0's of   b
    +
    +  design:
    +    for each row from end
    +      perform back substitution
    +      if near zero
    +        use qh_divzero for division
    +        if zero divide and not last row
    +          set tail of normal to 0
    +*/
    +void qh_backnormal(realT **rows, int numrow, int numcol, boolT sign,
    +        coordT *normal, boolT *nearzero) {
    +  int i, j;
    +  coordT *normalp, *normal_tail, *ai, *ak;
    +  realT diagonal;
    +  boolT waszero;
    +  int zerocol= -1;
    +
    +  normalp= normal + numcol - 1;
    +  *normalp--= (sign ? -1.0 : 1.0);
    +  for (i=numrow; i--; ) {
    +    *normalp= 0.0;
    +    ai= rows[i] + i + 1;
    +    ak= normalp+1;
    +    for (j=i+1; j < numcol; j++)
    +      *normalp -= *ai++ * *ak++;
    +    diagonal= (rows[i])[i];
    +    if (fabs_(diagonal) > qh MINdenom_2)
    +      *(normalp--) /= diagonal;
    +    else {
    +      waszero= False;
    +      *normalp= qh_divzero(*normalp, diagonal, qh MINdenom_1_2, &waszero);
    +      if (waszero) {
    +        zerocol= i;
    +        *(normalp--)= (sign ? -1.0 : 1.0);
    +        for (normal_tail= normalp+2; normal_tail < normal + numcol; normal_tail++)
    +          *normal_tail= 0.0;
    +      }else
    +        normalp--;
    +    }
    +  }
    +  if (zerocol != -1) {
    +    zzinc_(Zback0);
    +    *nearzero= True;
    +    trace4((qh ferr, 4005, "qh_backnormal: zero diagonal at column %d.\n", i));
    +    qh_precision("zero diagonal on back substitution");
    +  }
    +} /* backnormal */
    +
    +/*---------------------------------
    +
    +  qh_gausselim( rows, numrow, numcol, sign )
    +    Gaussian elimination with partial pivoting
    +
    +  returns:
    +    rows is upper triangular (includes row exchanges)
    +    flips sign for each row exchange
    +    sets nearzero if pivot[k] < qh.NEARzero[k], else clears it
    +
    +  notes:
    +    if nearzero, the determinant's sign may be incorrect.
    +    assumes numrow <= numcol
    +
    +  design:
    +    for each row
    +      determine pivot and exchange rows if necessary
    +      test for near zero
    +      perform gaussian elimination step
    +*/
    +void qh_gausselim(realT **rows, int numrow, int numcol, boolT *sign, boolT *nearzero) {
    +  realT *ai, *ak, *rowp, *pivotrow;
    +  realT n, pivot, pivot_abs= 0.0, temp;
    +  int i, j, k, pivoti, flip=0;
    +
    +  *nearzero= False;
    +  for (k=0; k < numrow; k++) {
    +    pivot_abs= fabs_((rows[k])[k]);
    +    pivoti= k;
    +    for (i=k+1; i < numrow; i++) {
    +      if ((temp= fabs_((rows[i])[k])) > pivot_abs) {
    +        pivot_abs= temp;
    +        pivoti= i;
    +      }
    +    }
    +    if (pivoti != k) {
    +      rowp= rows[pivoti];
    +      rows[pivoti]= rows[k];
    +      rows[k]= rowp;
    +      *sign ^= 1;
    +      flip ^= 1;
    +    }
    +    if (pivot_abs <= qh NEARzero[k]) {
    +      *nearzero= True;
    +      if (pivot_abs == 0.0) {   /* remainder of column == 0 */
    +        if (qh IStracing >= 4) {
    +          qh_fprintf(qh ferr, 8011, "qh_gausselim: 0 pivot at column %d. (%2.2g < %2.2g)\n", k, pivot_abs, qh DISTround);
    +          qh_printmatrix(qh ferr, "Matrix:", rows, numrow, numcol);
    +        }
    +        zzinc_(Zgauss0);
    +        qh_precision("zero pivot for Gaussian elimination");
    +        goto LABELnextcol;
    +      }
    +    }
    +    pivotrow= rows[k] + k;
    +    pivot= *pivotrow++;  /* signed value of pivot, and remainder of row */
    +    for (i=k+1; i < numrow; i++) {
    +      ai= rows[i] + k;
    +      ak= pivotrow;
    +      n= (*ai++)/pivot;   /* divzero() not needed since |pivot| >= |*ai| */
    +      for (j= numcol - (k+1); j--; )
    +        *ai++ -= n * *ak++;
    +    }
    +  LABELnextcol:
    +    ;
    +  }
    +  wmin_(Wmindenom, pivot_abs);  /* last pivot element */
    +  if (qh IStracing >= 5)
    +    qh_printmatrix(qh ferr, "qh_gausselem: result", rows, numrow, numcol);
    +} /* gausselim */
    +
    +
    +/*---------------------------------
    +
    +  qh_getangle( vect1, vect2 )
    +    returns the dot product of two vectors
    +    if qh.RANDOMdist, joggles result
    +
    +  notes:
    +    the angle may be > 1.0 or < -1.0 because of roundoff errors
    +
    +*/
    +realT qh_getangle(pointT *vect1, pointT *vect2) {
    +  realT angle= 0, randr;
    +  int k;
    +
    +  for (k=qh hull_dim; k--; )
    +    angle += *vect1++ * *vect2++;
    +  if (qh RANDOMdist) {
    +    randr= qh_RANDOMint;
    +    angle += (2.0 * randr / qh_RANDOMmax - 1.0) *
    +      qh RANDOMfactor;
    +  }
    +  trace4((qh ferr, 4006, "qh_getangle: %2.2g\n", angle));
    +  return(angle);
    +} /* getangle */
    +
    +
    +/*---------------------------------
    +
    +  qh_getcenter( vertices )
    +    returns arithmetic center of a set of vertices as a new point
    +
    +  notes:
    +    allocates point array for center
    +*/
    +pointT *qh_getcenter(setT *vertices) {
    +  int k;
    +  pointT *center, *coord;
    +  vertexT *vertex, **vertexp;
    +  int count= qh_setsize(vertices);
    +
    +  if (count < 2) {
    +    qh_fprintf(qh ferr, 6003, "qhull internal error (qh_getcenter): not defined for %d points\n", count);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  center= (pointT *)qh_memalloc(qh normal_size);
    +  for (k=0; k < qh hull_dim; k++) {
    +    coord= center+k;
    +    *coord= 0.0;
    +    FOREACHvertex_(vertices)
    +      *coord += vertex->point[k];
    +    *coord /= count;  /* count>=2 by QH6003 */
    +  }
    +  return(center);
    +} /* getcenter */
    +
    +
    +/*---------------------------------
    +
    +  qh_getcentrum( facet )
    +    returns the centrum for a facet as a new point
    +
    +  notes:
    +    allocates the centrum
    +*/
    +pointT *qh_getcentrum(facetT *facet) {
    +  realT dist;
    +  pointT *centrum, *point;
    +
    +  point= qh_getcenter(facet->vertices);
    +  zzinc_(Zcentrumtests);
    +  qh_distplane(point, facet, &dist);
    +  centrum= qh_projectpoint(point, facet, dist);
    +  qh_memfree(point, qh normal_size);
    +  trace4((qh ferr, 4007, "qh_getcentrum: for f%d, %d vertices dist= %2.2g\n",
    +          facet->id, qh_setsize(facet->vertices), dist));
    +  return centrum;
    +} /* getcentrum */
    +
    +
    +/*---------------------------------
    +
    +  qh_getdistance( facet, neighbor, mindist, maxdist )
    +    returns the maxdist and mindist distance of any vertex from neighbor
    +
    +  returns:
    +    the max absolute value
    +
    +  design:
    +    for each vertex of facet that is not in neighbor
    +      test the distance from vertex to neighbor
    +*/
    +realT qh_getdistance(facetT *facet, facetT *neighbor, realT *mindist, realT *maxdist) {
    +  vertexT *vertex, **vertexp;
    +  realT dist, maxd, mind;
    +
    +  FOREACHvertex_(facet->vertices)
    +    vertex->seen= False;
    +  FOREACHvertex_(neighbor->vertices)
    +    vertex->seen= True;
    +  mind= 0.0;
    +  maxd= 0.0;
    +  FOREACHvertex_(facet->vertices) {
    +    if (!vertex->seen) {
    +      zzinc_(Zbestdist);
    +      qh_distplane(vertex->point, neighbor, &dist);
    +      if (dist < mind)
    +        mind= dist;
    +      else if (dist > maxd)
    +        maxd= dist;
    +    }
    +  }
    +  *mindist= mind;
    +  *maxdist= maxd;
    +  mind= -mind;
    +  if (maxd > mind)
    +    return maxd;
    +  else
    +    return mind;
    +} /* getdistance */
    +
    +
    +/*---------------------------------
    +
    +  qh_normalize( normal, dim, toporient )
    +    normalize a vector and report if too small
    +    does not use min norm
    +
    +  see:
    +    qh_normalize2
    +*/
    +void qh_normalize(coordT *normal, int dim, boolT toporient) {
    +  qh_normalize2( normal, dim, toporient, NULL, NULL);
    +} /* normalize */
    +
    +/*---------------------------------
    +
    +  qh_normalize2( normal, dim, toporient, minnorm, ismin )
    +    normalize a vector and report if too small
    +    qh.MINdenom/MINdenom1 are the upper limits for divide overflow
    +
    +  returns:
    +    normalized vector
    +    flips sign if !toporient
    +    if minnorm non-NULL,
    +      sets ismin if normal < minnorm
    +
    +  notes:
    +    if zero norm
    +       sets all elements to sqrt(1.0/dim)
    +    if divide by zero (divzero())
    +       sets largest element to   +/-1
    +       bumps Znearlysingular
    +
    +  design:
    +    computes norm
    +    test for minnorm
    +    if not near zero
    +      normalizes normal
    +    else if zero norm
    +      sets normal to standard value
    +    else
    +      uses qh_divzero to normalize
    +      if nearzero
    +        sets norm to direction of maximum value
    +*/
    +void qh_normalize2(coordT *normal, int dim, boolT toporient,
    +            realT *minnorm, boolT *ismin) {
    +  int k;
    +  realT *colp, *maxp, norm= 0, temp, *norm1, *norm2, *norm3;
    +  boolT zerodiv;
    +
    +  norm1= normal+1;
    +  norm2= normal+2;
    +  norm3= normal+3;
    +  if (dim == 2)
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1));
    +  else if (dim == 3)
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2));
    +  else if (dim == 4) {
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2)
    +               + (*norm3)*(*norm3));
    +  }else if (dim > 4) {
    +    norm= (*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2)
    +               + (*norm3)*(*norm3);
    +    for (k=dim-4, colp=normal+4; k--; colp++)
    +      norm += (*colp) * (*colp);
    +    norm= sqrt(norm);
    +  }
    +  if (minnorm) {
    +    if (norm < *minnorm)
    +      *ismin= True;
    +    else
    +      *ismin= False;
    +  }
    +  wmin_(Wmindenom, norm);
    +  if (norm > qh MINdenom) {
    +    if (!toporient)
    +      norm= -norm;
    +    *normal /= norm;
    +    *norm1 /= norm;
    +    if (dim == 2)
    +      ; /* all done */
    +    else if (dim == 3)
    +      *norm2 /= norm;
    +    else if (dim == 4) {
    +      *norm2 /= norm;
    +      *norm3 /= norm;
    +    }else if (dim >4) {
    +      *norm2 /= norm;
    +      *norm3 /= norm;
    +      for (k=dim-4, colp=normal+4; k--; )
    +        *colp++ /= norm;
    +    }
    +  }else if (norm == 0.0) {
    +    temp= sqrt(1.0/dim);
    +    for (k=dim, colp=normal; k--; )
    +      *colp++ = temp;
    +  }else {
    +    if (!toporient)
    +      norm= -norm;
    +    for (k=dim, colp=normal; k--; colp++) { /* k used below */
    +      temp= qh_divzero(*colp, norm, qh MINdenom_1, &zerodiv);
    +      if (!zerodiv)
    +        *colp= temp;
    +      else {
    +        maxp= qh_maxabsval(normal, dim);
    +        temp= ((*maxp * norm >= 0.0) ? 1.0 : -1.0);
    +        for (k=dim, colp=normal; k--; colp++)
    +          *colp= 0.0;
    +        *maxp= temp;
    +        zzinc_(Znearlysingular);
    +        trace0((qh ferr, 1, "qh_normalize: norm=%2.2g too small during p%d\n",
    +               norm, qh furthest_id));
    +        return;
    +      }
    +    }
    +  }
    +} /* normalize */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectpoint( point, facet, dist )
    +    project point onto a facet by dist
    +
    +  returns:
    +    returns a new point
    +
    +  notes:
    +    if dist= distplane(point,facet)
    +      this projects point to hyperplane
    +    assumes qh_memfree_() is valid for normal_size
    +*/
    +pointT *qh_projectpoint(pointT *point, facetT *facet, realT dist) {
    +  pointT *newpoint, *np, *normal;
    +  int normsize= qh normal_size;
    +  int k;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_(normsize, freelistp, newpoint, pointT);
    +  np= newpoint;
    +  normal= facet->normal;
    +  for (k=qh hull_dim; k--; )
    +    *(np++)= *point++ - dist * *normal++;
    +  return(newpoint);
    +} /* projectpoint */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfacetplane( facet )
    +    sets the hyperplane for a facet
    +    if qh.RANDOMdist, joggles hyperplane
    +
    +  notes:
    +    uses global buffers qh.gm_matrix and qh.gm_row
    +    overwrites facet->normal if already defined
    +    updates Wnewvertex if PRINTstatistics
    +    sets facet->upperdelaunay if upper envelope of Delaunay triangulation
    +
    +  design:
    +    copy vertex coordinates to qh.gm_matrix/gm_row
    +    compute determinate
    +    if nearzero
    +      recompute determinate with gaussian elimination
    +      if nearzero
    +        force outside orientation by testing interior point
    +*/
    +void qh_setfacetplane(facetT *facet) {
    +  pointT *point;
    +  vertexT *vertex, **vertexp;
    +  int normsize= qh normal_size;
    +  int k,i, oldtrace= 0;
    +  realT dist;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +  coordT *coord, *gmcoord;
    +  pointT *point0= SETfirstt_(facet->vertices, vertexT)->point;
    +  boolT nearzero= False;
    +
    +  zzinc_(Zsetplane);
    +  if (!facet->normal)
    +    qh_memalloc_(normsize, freelistp, facet->normal, coordT);
    +  if (facet == qh tracefacet) {
    +    oldtrace= qh IStracing;
    +    qh IStracing= 5;
    +    qh_fprintf(qh ferr, 8012, "qh_setfacetplane: facet f%d created.\n", facet->id);
    +    qh_fprintf(qh ferr, 8013, "  Last point added to hull was p%d.", qh furthest_id);
    +    if (zzval_(Ztotmerge))
    +      qh_fprintf(qh ferr, 8014, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    qh_fprintf(qh ferr, 8015, "\n\nCurrent summary is:\n");
    +      qh_printsummary(qh ferr);
    +  }
    +  if (qh hull_dim <= 4) {
    +    i= 0;
    +    if (qh RANDOMdist) {
    +      gmcoord= qh gm_matrix;
    +      FOREACHvertex_(facet->vertices) {
    +        qh gm_row[i++]= gmcoord;
    +        coord= vertex->point;
    +        for (k=qh hull_dim; k--; )
    +          *(gmcoord++)= *coord++ * qh_randomfactor(qh RANDOMa, qh RANDOMb);
    +      }
    +    }else {
    +      FOREACHvertex_(facet->vertices)
    +       qh gm_row[i++]= vertex->point;
    +    }
    +    qh_sethyperplane_det(qh hull_dim, qh gm_row, point0, facet->toporient,
    +                facet->normal, &facet->offset, &nearzero);
    +  }
    +  if (qh hull_dim > 4 || nearzero) {
    +    i= 0;
    +    gmcoord= qh gm_matrix;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->point != point0) {
    +        qh gm_row[i++]= gmcoord;
    +        coord= vertex->point;
    +        point= point0;
    +        for (k=qh hull_dim; k--; )
    +          *(gmcoord++)= *coord++ - *point++;
    +      }
    +    }
    +    qh gm_row[i]= gmcoord;  /* for areasimplex */
    +    if (qh RANDOMdist) {
    +      gmcoord= qh gm_matrix;
    +      for (i=qh hull_dim-1; i--; ) {
    +        for (k=qh hull_dim; k--; )
    +          *(gmcoord++) *= qh_randomfactor(qh RANDOMa, qh RANDOMb);
    +      }
    +    }
    +    qh_sethyperplane_gauss(qh hull_dim, qh gm_row, point0, facet->toporient,
    +                facet->normal, &facet->offset, &nearzero);
    +    if (nearzero) {
    +      if (qh_orientoutside(facet)) {
    +        trace0((qh ferr, 2, "qh_setfacetplane: flipped orientation after testing interior_point during p%d\n", qh furthest_id));
    +      /* this is part of using Gaussian Elimination.  For example in 5-d
    +           1 1 1 1 0
    +           1 1 1 1 1
    +           0 0 0 1 0
    +           0 1 0 0 0
    +           1 0 0 0 0
    +           norm= 0.38 0.38 -0.76 0.38 0
    +         has a determinate of 1, but g.e. after subtracting pt. 0 has
    +         0's in the diagonal, even with full pivoting.  It does work
    +         if you subtract pt. 4 instead. */
    +      }
    +    }
    +  }
    +  facet->upperdelaunay= False;
    +  if (qh DELAUNAY) {
    +    if (qh UPPERdelaunay) {     /* matches qh_triangulate_facet and qh.lower_threshold in qh_initbuild */
    +      if (facet->normal[qh hull_dim -1] >= qh ANGLEround * qh_ZEROdelaunay)
    +        facet->upperdelaunay= True;
    +    }else {
    +      if (facet->normal[qh hull_dim -1] > -qh ANGLEround * qh_ZEROdelaunay)
    +        facet->upperdelaunay= True;
    +    }
    +  }
    +  if (qh PRINTstatistics || qh IStracing || qh TRACElevel || qh JOGGLEmax < REALmax) {
    +    qh old_randomdist= qh RANDOMdist;
    +    qh RANDOMdist= False;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->point != point0) {
    +        boolT istrace= False;
    +        zinc_(Zdiststat);
    +        qh_distplane(vertex->point, facet, &dist);
    +        dist= fabs_(dist);
    +        zinc_(Znewvertex);
    +        wadd_(Wnewvertex, dist);
    +        if (dist > wwval_(Wnewvertexmax)) {
    +          wwval_(Wnewvertexmax)= dist;
    +          if (dist > qh max_outside) {
    +            qh max_outside= dist;  /* used by qh_maxouter() */
    +            if (dist > qh TRACEdist)
    +              istrace= True;
    +          }
    +        }else if (-dist > qh TRACEdist)
    +          istrace= True;
    +        if (istrace) {
    +          qh_fprintf(qh ferr, 8016, "qh_setfacetplane: ====== vertex p%d(v%d) increases max_outside to %2.2g for new facet f%d last p%d\n",
    +                qh_pointid(vertex->point), vertex->id, dist, facet->id, qh furthest_id);
    +          qh_errprint("DISTANT", facet, NULL, NULL, NULL);
    +        }
    +      }
    +    }
    +    qh RANDOMdist= qh old_randomdist;
    +  }
    +  if (qh IStracing >= 3) {
    +    qh_fprintf(qh ferr, 8017, "qh_setfacetplane: f%d offset %2.2g normal: ",
    +             facet->id, facet->offset);
    +    for (k=0; k < qh hull_dim; k++)
    +      qh_fprintf(qh ferr, 8018, "%2.2g ", facet->normal[k]);
    +    qh_fprintf(qh ferr, 8019, "\n");
    +  }
    +  if (facet == qh tracefacet)
    +    qh IStracing= oldtrace;
    +} /* setfacetplane */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethyperplane_det( dim, rows, point0, toporient, normal, offset, nearzero )
    +    given dim X dim array indexed by rows[], one row per point,
    +        toporient(flips all signs),
    +        and point0 (any row)
    +    set normalized hyperplane equation from oriented simplex
    +
    +  returns:
    +    normal (normalized)
    +    offset (places point0 on the hyperplane)
    +    sets nearzero if hyperplane not through points
    +
    +  notes:
    +    only defined for dim == 2..4
    +    rows[] is not modified
    +    solves det(P-V_0, V_n-V_0, ..., V_1-V_0)=0, i.e. every point is on hyperplane
    +    see Bower & Woodworth, A programmer's geometry, Butterworths 1983.
    +
    +  derivation of 3-d minnorm
    +    Goal: all vertices V_i within qh.one_merge of hyperplane
    +    Plan: exactly translate the facet so that V_0 is the origin
    +          exactly rotate the facet so that V_1 is on the x-axis and y_2=0.
    +          exactly rotate the effective perturbation to only effect n_0
    +             this introduces a factor of sqrt(3)
    +    n_0 = ((y_2-y_0)*(z_1-z_0) - (z_2-z_0)*(y_1-y_0)) / norm
    +    Let M_d be the max coordinate difference
    +    Let M_a be the greater of M_d and the max abs. coordinate
    +    Let u be machine roundoff and distround be max error for distance computation
    +    The max error for n_0 is sqrt(3) u M_a M_d / norm.  n_1 is approx. 1 and n_2 is approx. 0
    +    The max error for distance of V_1 is sqrt(3) u M_a M_d M_d / norm.  Offset=0 at origin
    +    Then minnorm = 1.8 u M_a M_d M_d / qh.ONEmerge
    +    Note that qh.one_merge is approx. 45.5 u M_a and norm is usually about M_d M_d
    +
    +  derivation of 4-d minnorm
    +    same as above except rotate the facet so that V_1 on x-axis and w_2, y_3, w_3=0
    +     [if two vertices fixed on x-axis, can rotate the other two in yzw.]
    +    n_0 = det3_(...) = y_2 det2_(z_1, w_1, z_3, w_3) = - y_2 w_1 z_3
    +     [all other terms contain at least two factors nearly zero.]
    +    The max error for n_0 is sqrt(4) u M_a M_d M_d / norm
    +    Then minnorm = 2 u M_a M_d M_d M_d / qh.ONEmerge
    +    Note that qh.one_merge is approx. 82 u M_a and norm is usually about M_d M_d M_d
    +*/
    +void qh_sethyperplane_det(int dim, coordT **rows, coordT *point0,
    +          boolT toporient, coordT *normal, realT *offset, boolT *nearzero) {
    +  realT maxround, dist;
    +  int i;
    +  pointT *point;
    +
    +
    +  if (dim == 2) {
    +    normal[0]= dY(1,0);
    +    normal[1]= dX(0,1);
    +    qh_normalize2(normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0]+point0[1]*normal[1]);
    +    *nearzero= False;  /* since nearzero norm => incident points */
    +  }else if (dim == 3) {
    +    normal[0]= det2_(dY(2,0), dZ(2,0),
    +                     dY(1,0), dZ(1,0));
    +    normal[1]= det2_(dX(1,0), dZ(1,0),
    +                     dX(2,0), dZ(2,0));
    +    normal[2]= det2_(dX(2,0), dY(2,0),
    +                     dX(1,0), dY(1,0));
    +    qh_normalize2(normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0] + point0[1]*normal[1]
    +               + point0[2]*normal[2]);
    +    maxround= qh DISTround;
    +    for (i=dim; i--; ) {
    +      point= rows[i];
    +      if (point != point0) {
    +        dist= *offset + (point[0]*normal[0] + point[1]*normal[1]
    +               + point[2]*normal[2]);
    +        if (dist > maxround || dist < -maxround) {
    +          *nearzero= True;
    +          break;
    +        }
    +      }
    +    }
    +  }else if (dim == 4) {
    +    normal[0]= - det3_(dY(2,0), dZ(2,0), dW(2,0),
    +                        dY(1,0), dZ(1,0), dW(1,0),
    +                        dY(3,0), dZ(3,0), dW(3,0));
    +    normal[1]=   det3_(dX(2,0), dZ(2,0), dW(2,0),
    +                        dX(1,0), dZ(1,0), dW(1,0),
    +                        dX(3,0), dZ(3,0), dW(3,0));
    +    normal[2]= - det3_(dX(2,0), dY(2,0), dW(2,0),
    +                        dX(1,0), dY(1,0), dW(1,0),
    +                        dX(3,0), dY(3,0), dW(3,0));
    +    normal[3]=   det3_(dX(2,0), dY(2,0), dZ(2,0),
    +                        dX(1,0), dY(1,0), dZ(1,0),
    +                        dX(3,0), dY(3,0), dZ(3,0));
    +    qh_normalize2(normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0] + point0[1]*normal[1]
    +               + point0[2]*normal[2] + point0[3]*normal[3]);
    +    maxround= qh DISTround;
    +    for (i=dim; i--; ) {
    +      point= rows[i];
    +      if (point != point0) {
    +        dist= *offset + (point[0]*normal[0] + point[1]*normal[1]
    +               + point[2]*normal[2] + point[3]*normal[3]);
    +        if (dist > maxround || dist < -maxround) {
    +          *nearzero= True;
    +          break;
    +        }
    +      }
    +    }
    +  }
    +  if (*nearzero) {
    +    zzinc_(Zminnorm);
    +    trace0((qh ferr, 3, "qh_sethyperplane_det: degenerate norm during p%d.\n", qh furthest_id));
    +    zzinc_(Znearlysingular);
    +  }
    +} /* sethyperplane_det */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethyperplane_gauss( dim, rows, point0, toporient, normal, offset, nearzero )
    +    given(dim-1) X dim array of rows[i]= V_{i+1} - V_0 (point0)
    +    set normalized hyperplane equation from oriented simplex
    +
    +  returns:
    +    normal (normalized)
    +    offset (places point0 on the hyperplane)
    +
    +  notes:
    +    if nearzero
    +      orientation may be incorrect because of incorrect sign flips in gausselim
    +    solves [V_n-V_0,...,V_1-V_0, 0 .. 0 1] * N == [0 .. 0 1]
    +        or [V_n-V_0,...,V_1-V_0, 0 .. 0 1] * N == [0]
    +    i.e., N is normal to the hyperplane, and the unnormalized
    +        distance to [0 .. 1] is either 1 or   0
    +
    +  design:
    +    perform gaussian elimination
    +    flip sign for negative values
    +    perform back substitution
    +    normalize result
    +    compute offset
    +*/
    +void qh_sethyperplane_gauss(int dim, coordT **rows, pointT *point0,
    +                boolT toporient, coordT *normal, coordT *offset, boolT *nearzero) {
    +  coordT *pointcoord, *normalcoef;
    +  int k;
    +  boolT sign= toporient, nearzero2= False;
    +
    +  qh_gausselim(rows, dim-1, dim, &sign, nearzero);
    +  for (k=dim-1; k--; ) {
    +    if ((rows[k])[k] < 0)
    +      sign ^= 1;
    +  }
    +  if (*nearzero) {
    +    zzinc_(Znearlysingular);
    +    trace0((qh ferr, 4, "qh_sethyperplane_gauss: nearly singular or axis parallel hyperplane during p%d.\n", qh furthest_id));
    +    qh_backnormal(rows, dim-1, dim, sign, normal, &nearzero2);
    +  }else {
    +    qh_backnormal(rows, dim-1, dim, sign, normal, &nearzero2);
    +    if (nearzero2) {
    +      zzinc_(Znearlysingular);
    +      trace0((qh ferr, 5, "qh_sethyperplane_gauss: singular or axis parallel hyperplane at normalization during p%d.\n", qh furthest_id));
    +    }
    +  }
    +  if (nearzero2)
    +    *nearzero= True;
    +  qh_normalize2(normal, dim, True, NULL, NULL);
    +  pointcoord= point0;
    +  normalcoef= normal;
    +  *offset= -(*pointcoord++ * *normalcoef++);
    +  for (k=dim-1; k--; )
    +    *offset -= *pointcoord++ * *normalcoef++;
    +} /* sethyperplane_gauss */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/geom.h b/xs/src/qhull/src/libqhull/geom.h
    new file mode 100644
    index 0000000000..16ef48d2d7
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/geom.h
    @@ -0,0 +1,176 @@
    +/*
      ---------------------------------
    +
    +  geom.h
    +    header file for geometric routines
    +
    +   see qh-geom.htm and geom.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/geom.h#1 $$Change: 1981 $
    +   $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFgeom
    +#define qhDEFgeom 1
    +
    +#include "libqhull.h"
    +
    +/* ============ -macros- ======================== */
    +
    +/*----------------------------------
    +
    +  fabs_(a)
    +    returns the absolute value of a
    +*/
    +#define fabs_( a ) ((( a ) < 0 ) ? -( a ):( a ))
    +
    +/*----------------------------------
    +
    +  fmax_(a,b)
    +    returns the maximum value of a and b
    +*/
    +#define fmax_( a,b )  ( ( a ) < ( b ) ? ( b ) : ( a ) )
    +
    +/*----------------------------------
    +
    +  fmin_(a,b)
    +    returns the minimum value of a and b
    +*/
    +#define fmin_( a,b )  ( ( a ) > ( b ) ? ( b ) : ( a ) )
    +
    +/*----------------------------------
    +
    +  maximize_(maxval, val)
    +    set maxval to val if val is greater than maxval
    +*/
    +#define maximize_( maxval, val ) { if (( maxval ) < ( val )) ( maxval )= ( val ); }
    +
    +/*----------------------------------
    +
    +  minimize_(minval, val)
    +    set minval to val if val is less than minval
    +*/
    +#define minimize_( minval, val ) { if (( minval ) > ( val )) ( minval )= ( val ); }
    +
    +/*----------------------------------
    +
    +  det2_(a1, a2,
    +        b1, b2)
    +
    +    compute a 2-d determinate
    +*/
    +#define det2_( a1,a2,b1,b2 ) (( a1 )*( b2 ) - ( a2 )*( b1 ))
    +
    +/*----------------------------------
    +
    +  det3_(a1, a2, a3,
    +       b1, b2, b3,
    +       c1, c2, c3)
    +
    +    compute a 3-d determinate
    +*/
    +#define det3_( a1,a2,a3,b1,b2,b3,c1,c2,c3 ) ( ( a1 )*det2_( b2,b3,c2,c3 ) \
    +                - ( b1 )*det2_( a2,a3,c2,c3 ) + ( c1 )*det2_( a2,a3,b2,b3 ) )
    +
    +/*----------------------------------
    +
    +  dX( p1, p2 )
    +  dY( p1, p2 )
    +  dZ( p1, p2 )
    +
    +    given two indices into rows[],
    +
    +    compute the difference between X, Y, or Z coordinates
    +*/
    +#define dX( p1,p2 )  ( *( rows[p1] ) - *( rows[p2] ))
    +#define dY( p1,p2 )  ( *( rows[p1]+1 ) - *( rows[p2]+1 ))
    +#define dZ( p1,p2 )  ( *( rows[p1]+2 ) - *( rows[p2]+2 ))
    +#define dW( p1,p2 )  ( *( rows[p1]+3 ) - *( rows[p2]+3 ))
    +
    +/*============= prototypes in alphabetical order, infrequent at end ======= */
    +
    +void    qh_backnormal(realT **rows, int numrow, int numcol, boolT sign, coordT *normal, boolT *nearzero);
    +void    qh_distplane(pointT *point, facetT *facet, realT *dist);
    +facetT *qh_findbest(pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT isnewfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart);
    +facetT *qh_findbesthorizon(boolT ischeckmax, pointT *point,
    +                     facetT *startfacet, boolT noupper, realT *bestdist, int *numpart);
    +facetT *qh_findbestnew(pointT *point, facetT *startfacet, realT *dist,
    +                     boolT bestoutside, boolT *isoutside, int *numpart);
    +void    qh_gausselim(realT **rows, int numrow, int numcol, boolT *sign, boolT *nearzero);
    +realT   qh_getangle(pointT *vect1, pointT *vect2);
    +pointT *qh_getcenter(setT *vertices);
    +pointT *qh_getcentrum(facetT *facet);
    +realT   qh_getdistance(facetT *facet, facetT *neighbor, realT *mindist, realT *maxdist);
    +void    qh_normalize(coordT *normal, int dim, boolT toporient);
    +void    qh_normalize2(coordT *normal, int dim, boolT toporient,
    +            realT *minnorm, boolT *ismin);
    +pointT *qh_projectpoint(pointT *point, facetT *facet, realT dist);
    +
    +void    qh_setfacetplane(facetT *newfacets);
    +void    qh_sethyperplane_det(int dim, coordT **rows, coordT *point0,
    +              boolT toporient, coordT *normal, realT *offset, boolT *nearzero);
    +void    qh_sethyperplane_gauss(int dim, coordT **rows, pointT *point0,
    +             boolT toporient, coordT *normal, coordT *offset, boolT *nearzero);
    +boolT   qh_sharpnewfacets(void);
    +
    +/*========= infrequently used code in geom2.c =============*/
    +
    +coordT *qh_copypoints(coordT *points, int numpoints, int dimension);
    +void    qh_crossproduct(int dim, realT vecA[3], realT vecB[3], realT vecC[3]);
    +realT   qh_determinant(realT **rows, int dim, boolT *nearzero);
    +realT   qh_detjoggle(pointT *points, int numpoints, int dimension);
    +void    qh_detroundoff(void);
    +realT   qh_detsimplex(pointT *apex, setT *points, int dim, boolT *nearzero);
    +realT   qh_distnorm(int dim, pointT *point, pointT *normal, realT *offsetp);
    +realT   qh_distround(int dimension, realT maxabs, realT maxsumabs);
    +realT   qh_divzero(realT numer, realT denom, realT mindenom1, boolT *zerodiv);
    +realT   qh_facetarea(facetT *facet);
    +realT   qh_facetarea_simplex(int dim, coordT *apex, setT *vertices,
    +          vertexT *notvertex,  boolT toporient, coordT *normal, realT *offset);
    +pointT *qh_facetcenter(setT *vertices);
    +facetT *qh_findgooddist(pointT *point, facetT *facetA, realT *distp, facetT **facetlist);
    +void    qh_getarea(facetT *facetlist);
    +boolT   qh_gram_schmidt(int dim, realT **rows);
    +boolT   qh_inthresholds(coordT *normal, realT *angle);
    +void    qh_joggleinput(void);
    +realT  *qh_maxabsval(realT *normal, int dim);
    +setT   *qh_maxmin(pointT *points, int numpoints, int dimension);
    +realT   qh_maxouter(void);
    +void    qh_maxsimplex(int dim, setT *maxpoints, pointT *points, int numpoints, setT **simplex);
    +realT   qh_minabsval(realT *normal, int dim);
    +int     qh_mindiff(realT *vecA, realT *vecB, int dim);
    +boolT   qh_orientoutside(facetT *facet);
    +void    qh_outerinner(facetT *facet, realT *outerplane, realT *innerplane);
    +coordT  qh_pointdist(pointT *point1, pointT *point2, int dim);
    +void    qh_printmatrix(FILE *fp, const char *string, realT **rows, int numrow, int numcol);
    +void    qh_printpoints(FILE *fp, const char *string, setT *points);
    +void    qh_projectinput(void);
    +void    qh_projectpoints(signed char *project, int n, realT *points,
    +             int numpoints, int dim, realT *newpoints, int newdim);
    +void    qh_rotateinput(realT **rows);
    +void    qh_rotatepoints(realT *points, int numpoints, int dim, realT **rows);
    +void    qh_scaleinput(void);
    +void    qh_scalelast(coordT *points, int numpoints, int dim, coordT low,
    +                   coordT high, coordT newhigh);
    +void    qh_scalepoints(pointT *points, int numpoints, int dim,
    +                realT *newlows, realT *newhighs);
    +boolT   qh_sethalfspace(int dim, coordT *coords, coordT **nextp,
    +              coordT *normal, coordT *offset, coordT *feasible);
    +coordT *qh_sethalfspace_all(int dim, int count, coordT *halfspaces, pointT *feasible);
    +pointT *qh_voronoi_center(int dim, setT *points);
    +
    +#endif /* qhDEFgeom */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/geom2.c b/xs/src/qhull/src/libqhull/geom2.c
    new file mode 100644
    index 0000000000..82ec4936ef
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/geom2.c
    @@ -0,0 +1,2094 @@
    +/*
      ---------------------------------
    +
    +
    +   geom2.c
    +   infrequently used geometric routines of qhull
    +
    +   see qh-geom.htm and geom.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/geom2.c#6 $$Change: 2065 $
    +   $DateTime: 2016/01/18 13:51:04 $$Author: bbarber $
    +
    +   frequently used code goes into geom.c
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*================== functions in alphabetic order ============*/
    +
    +/*---------------------------------
    +
    +  qh_copypoints( points, numpoints, dimension)
    +    return qh_malloc'd copy of points
    +  notes:
    +    qh_free the returned points to avoid a memory leak
    +*/
    +coordT *qh_copypoints(coordT *points, int numpoints, int dimension) {
    +  int size;
    +  coordT *newpoints;
    +
    +  size= numpoints * dimension * (int)sizeof(coordT);
    +  if (!(newpoints=(coordT*)qh_malloc((size_t)size))) {
    +    qh_fprintf(qh ferr, 6004, "qhull error: insufficient memory to copy %d points\n",
    +        numpoints);
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  memcpy((char *)newpoints, (char *)points, (size_t)size); /* newpoints!=0 by QH6004 */
    +  return newpoints;
    +} /* copypoints */
    +
    +/*---------------------------------
    +
    +  qh_crossproduct( dim, vecA, vecB, vecC )
    +    crossproduct of 2 dim vectors
    +    C= A x B
    +
    +  notes:
    +    from Glasner, Graphics Gems I, p. 639
    +    only defined for dim==3
    +*/
    +void qh_crossproduct(int dim, realT vecA[3], realT vecB[3], realT vecC[3]){
    +
    +  if (dim == 3) {
    +    vecC[0]=   det2_(vecA[1], vecA[2],
    +                     vecB[1], vecB[2]);
    +    vecC[1]= - det2_(vecA[0], vecA[2],
    +                     vecB[0], vecB[2]);
    +    vecC[2]=   det2_(vecA[0], vecA[1],
    +                     vecB[0], vecB[1]);
    +  }
    +} /* vcross */
    +
    +/*---------------------------------
    +
    +  qh_determinant( rows, dim, nearzero )
    +    compute signed determinant of a square matrix
    +    uses qh.NEARzero to test for degenerate matrices
    +
    +  returns:
    +    determinant
    +    overwrites rows and the matrix
    +    if dim == 2 or 3
    +      nearzero iff determinant < qh NEARzero[dim-1]
    +      (!quite correct, not critical)
    +    if dim >= 4
    +      nearzero iff diagonal[k] < qh NEARzero[k]
    +*/
    +realT qh_determinant(realT **rows, int dim, boolT *nearzero) {
    +  realT det=0;
    +  int i;
    +  boolT sign= False;
    +
    +  *nearzero= False;
    +  if (dim < 2) {
    +    qh_fprintf(qh ferr, 6005, "qhull internal error (qh_determinate): only implemented for dimension >= 2\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }else if (dim == 2) {
    +    det= det2_(rows[0][0], rows[0][1],
    +                 rows[1][0], rows[1][1]);
    +    if (fabs_(det) < 10*qh NEARzero[1])  /* not really correct, what should this be? */
    +      *nearzero= True;
    +  }else if (dim == 3) {
    +    det= det3_(rows[0][0], rows[0][1], rows[0][2],
    +                 rows[1][0], rows[1][1], rows[1][2],
    +                 rows[2][0], rows[2][1], rows[2][2]);
    +    if (fabs_(det) < 10*qh NEARzero[2])  /* what should this be?  det 5.5e-12 was flat for qh_maxsimplex of qdelaunay 0,0 27,27 -36,36 -9,63  */
    +      *nearzero= True;
    +  }else {
    +    qh_gausselim(rows, dim, dim, &sign, nearzero);  /* if nearzero, diagonal still ok*/
    +    det= 1.0;
    +    for (i=dim; i--; )
    +      det *= (rows[i])[i];
    +    if (sign)
    +      det= -det;
    +  }
    +  return det;
    +} /* determinant */
    +
    +/*---------------------------------
    +
    +  qh_detjoggle( points, numpoints, dimension )
    +    determine default max joggle for point array
    +      as qh_distround * qh_JOGGLEdefault
    +
    +  returns:
    +    initial value for JOGGLEmax from points and REALepsilon
    +
    +  notes:
    +    computes DISTround since qh_maxmin not called yet
    +    if qh SCALElast, last dimension will be scaled later to MAXwidth
    +
    +    loop duplicated from qh_maxmin
    +*/
    +realT qh_detjoggle(pointT *points, int numpoints, int dimension) {
    +  realT abscoord, distround, joggle, maxcoord, mincoord;
    +  pointT *point, *pointtemp;
    +  realT maxabs= -REALmax;
    +  realT sumabs= 0;
    +  realT maxwidth= 0;
    +  int k;
    +
    +  for (k=0; k < dimension; k++) {
    +    if (qh SCALElast && k == dimension-1)
    +      abscoord= maxwidth;
    +    else if (qh DELAUNAY && k == dimension-1) /* will qh_setdelaunay() */
    +      abscoord= 2 * maxabs * maxabs;  /* may be low by qh hull_dim/2 */
    +    else {
    +      maxcoord= -REALmax;
    +      mincoord= REALmax;
    +      FORALLpoint_(points, numpoints) {
    +        maximize_(maxcoord, point[k]);
    +        minimize_(mincoord, point[k]);
    +      }
    +      maximize_(maxwidth, maxcoord-mincoord);
    +      abscoord= fmax_(maxcoord, -mincoord);
    +    }
    +    sumabs += abscoord;
    +    maximize_(maxabs, abscoord);
    +  } /* for k */
    +  distround= qh_distround(qh hull_dim, maxabs, sumabs);
    +  joggle= distround * qh_JOGGLEdefault;
    +  maximize_(joggle, REALepsilon * qh_JOGGLEdefault);
    +  trace2((qh ferr, 2001, "qh_detjoggle: joggle=%2.2g maxwidth=%2.2g\n", joggle, maxwidth));
    +  return joggle;
    +} /* detjoggle */
    +
    +/*---------------------------------
    +
    +  qh_detroundoff()
    +    determine maximum roundoff errors from
    +      REALepsilon, REALmax, REALmin, qh.hull_dim, qh.MAXabs_coord,
    +      qh.MAXsumcoord, qh.MAXwidth, qh.MINdenom_1
    +
    +    accounts for qh.SETroundoff, qh.RANDOMdist, qh MERGEexact
    +      qh.premerge_cos, qh.postmerge_cos, qh.premerge_centrum,
    +      qh.postmerge_centrum, qh.MINoutside,
    +      qh_RATIOnearinside, qh_COPLANARratio, qh_WIDEcoplanar
    +
    +  returns:
    +    sets qh.DISTround, etc. (see below)
    +    appends precision constants to qh.qhull_options
    +
    +  see:
    +    qh_maxmin() for qh.NEARzero
    +
    +  design:
    +    determine qh.DISTround for distance computations
    +    determine minimum denominators for qh_divzero
    +    determine qh.ANGLEround for angle computations
    +    adjust qh.premerge_cos,... for roundoff error
    +    determine qh.ONEmerge for maximum error due to a single merge
    +    determine qh.NEARinside, qh.MAXcoplanar, qh.MINvisible,
    +      qh.MINoutside, qh.WIDEfacet
    +    initialize qh.max_vertex and qh.minvertex
    +*/
    +void qh_detroundoff(void) {
    +
    +  qh_option("_max-width", NULL, &qh MAXwidth);
    +  if (!qh SETroundoff) {
    +    qh DISTround= qh_distround(qh hull_dim, qh MAXabs_coord, qh MAXsumcoord);
    +    if (qh RANDOMdist)
    +      qh DISTround += qh RANDOMfactor * qh MAXabs_coord;
    +    qh_option("Error-roundoff", NULL, &qh DISTround);
    +  }
    +  qh MINdenom= qh MINdenom_1 * qh MAXabs_coord;
    +  qh MINdenom_1_2= sqrt(qh MINdenom_1 * qh hull_dim) ;  /* if will be normalized */
    +  qh MINdenom_2= qh MINdenom_1_2 * qh MAXabs_coord;
    +                                              /* for inner product */
    +  qh ANGLEround= 1.01 * qh hull_dim * REALepsilon;
    +  if (qh RANDOMdist)
    +    qh ANGLEround += qh RANDOMfactor;
    +  if (qh premerge_cos < REALmax/2) {
    +    qh premerge_cos -= qh ANGLEround;
    +    if (qh RANDOMdist)
    +      qh_option("Angle-premerge-with-random", NULL, &qh premerge_cos);
    +  }
    +  if (qh postmerge_cos < REALmax/2) {
    +    qh postmerge_cos -= qh ANGLEround;
    +    if (qh RANDOMdist)
    +      qh_option("Angle-postmerge-with-random", NULL, &qh postmerge_cos);
    +  }
    +  qh premerge_centrum += 2 * qh DISTround;    /*2 for centrum and distplane()*/
    +  qh postmerge_centrum += 2 * qh DISTround;
    +  if (qh RANDOMdist && (qh MERGEexact || qh PREmerge))
    +    qh_option("Centrum-premerge-with-random", NULL, &qh premerge_centrum);
    +  if (qh RANDOMdist && qh POSTmerge)
    +    qh_option("Centrum-postmerge-with-random", NULL, &qh postmerge_centrum);
    +  { /* compute ONEmerge, max vertex offset for merging simplicial facets */
    +    realT maxangle= 1.0, maxrho;
    +
    +    minimize_(maxangle, qh premerge_cos);
    +    minimize_(maxangle, qh postmerge_cos);
    +    /* max diameter * sin theta + DISTround for vertex to its hyperplane */
    +    qh ONEmerge= sqrt((realT)qh hull_dim) * qh MAXwidth *
    +      sqrt(1.0 - maxangle * maxangle) + qh DISTround;
    +    maxrho= qh hull_dim * qh premerge_centrum + qh DISTround;
    +    maximize_(qh ONEmerge, maxrho);
    +    maxrho= qh hull_dim * qh postmerge_centrum + qh DISTround;
    +    maximize_(qh ONEmerge, maxrho);
    +    if (qh MERGING)
    +      qh_option("_one-merge", NULL, &qh ONEmerge);
    +  }
    +  qh NEARinside= qh ONEmerge * qh_RATIOnearinside; /* only used if qh KEEPnearinside */
    +  if (qh JOGGLEmax < REALmax/2 && (qh KEEPcoplanar || qh KEEPinside)) {
    +    realT maxdist;             /* adjust qh.NEARinside for joggle */
    +    qh KEEPnearinside= True;
    +    maxdist= sqrt((realT)qh hull_dim) * qh JOGGLEmax + qh DISTround;
    +    maxdist= 2*maxdist;        /* vertex and coplanar point can joggle in opposite directions */
    +    maximize_(qh NEARinside, maxdist);  /* must agree with qh_nearcoplanar() */
    +  }
    +  if (qh KEEPnearinside)
    +    qh_option("_near-inside", NULL, &qh NEARinside);
    +  if (qh JOGGLEmax < qh DISTround) {
    +    qh_fprintf(qh ferr, 6006, "qhull error: the joggle for 'QJn', %.2g, is below roundoff for distance computations, %.2g\n",
    +         qh JOGGLEmax, qh DISTround);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh MINvisible > REALmax/2) {
    +    if (!qh MERGING)
    +      qh MINvisible= qh DISTround;
    +    else if (qh hull_dim <= 3)
    +      qh MINvisible= qh premerge_centrum;
    +    else
    +      qh MINvisible= qh_COPLANARratio * qh premerge_centrum;
    +    if (qh APPROXhull && qh MINvisible > qh MINoutside)
    +      qh MINvisible= qh MINoutside;
    +    qh_option("Visible-distance", NULL, &qh MINvisible);
    +  }
    +  if (qh MAXcoplanar > REALmax/2) {
    +    qh MAXcoplanar= qh MINvisible;
    +    qh_option("U-coplanar-distance", NULL, &qh MAXcoplanar);
    +  }
    +  if (!qh APPROXhull) {             /* user may specify qh MINoutside */
    +    qh MINoutside= 2 * qh MINvisible;
    +    if (qh premerge_cos < REALmax/2)
    +      maximize_(qh MINoutside, (1- qh premerge_cos) * qh MAXabs_coord);
    +    qh_option("Width-outside", NULL, &qh MINoutside);
    +  }
    +  qh WIDEfacet= qh MINoutside;
    +  maximize_(qh WIDEfacet, qh_WIDEcoplanar * qh MAXcoplanar);
    +  maximize_(qh WIDEfacet, qh_WIDEcoplanar * qh MINvisible);
    +  qh_option("_wide-facet", NULL, &qh WIDEfacet);
    +  if (qh MINvisible > qh MINoutside + 3 * REALepsilon
    +  && !qh BESToutside && !qh FORCEoutput)
    +    qh_fprintf(qh ferr, 7001, "qhull input warning: minimum visibility V%.2g is greater than \nminimum outside W%.2g.  Flipped facets are likely.\n",
    +             qh MINvisible, qh MINoutside);
    +  qh max_vertex= qh DISTround;
    +  qh min_vertex= -qh DISTround;
    +  /* numeric constants reported in printsummary */
    +} /* detroundoff */
    +
    +/*---------------------------------
    +
    +  qh_detsimplex( apex, points, dim, nearzero )
    +    compute determinant of a simplex with point apex and base points
    +
    +  returns:
    +     signed determinant and nearzero from qh_determinant
    +
    +  notes:
    +     uses qh.gm_matrix/qh.gm_row (assumes they're big enough)
    +
    +  design:
    +    construct qm_matrix by subtracting apex from points
    +    compute determinate
    +*/
    +realT qh_detsimplex(pointT *apex, setT *points, int dim, boolT *nearzero) {
    +  pointT *coorda, *coordp, *gmcoord, *point, **pointp;
    +  coordT **rows;
    +  int k,  i=0;
    +  realT det;
    +
    +  zinc_(Zdetsimplex);
    +  gmcoord= qh gm_matrix;
    +  rows= qh gm_row;
    +  FOREACHpoint_(points) {
    +    if (i == dim)
    +      break;
    +    rows[i++]= gmcoord;
    +    coordp= point;
    +    coorda= apex;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= *coordp++ - *coorda++;
    +  }
    +  if (i < dim) {
    +    qh_fprintf(qh ferr, 6007, "qhull internal error (qh_detsimplex): #points %d < dimension %d\n",
    +               i, dim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  det= qh_determinant(rows, dim, nearzero);
    +  trace2((qh ferr, 2002, "qh_detsimplex: det=%2.2g for point p%d, dim %d, nearzero? %d\n",
    +          det, qh_pointid(apex), dim, *nearzero));
    +  return det;
    +} /* detsimplex */
    +
    +/*---------------------------------
    +
    +  qh_distnorm( dim, point, normal, offset )
    +    return distance from point to hyperplane at normal/offset
    +
    +  returns:
    +    dist
    +
    +  notes:
    +    dist > 0 if point is outside of hyperplane
    +
    +  see:
    +    qh_distplane in geom.c
    +*/
    +realT qh_distnorm(int dim, pointT *point, pointT *normal, realT *offsetp) {
    +  coordT *normalp= normal, *coordp= point;
    +  realT dist;
    +  int k;
    +
    +  dist= *offsetp;
    +  for (k=dim; k--; )
    +    dist += *(coordp++) * *(normalp++);
    +  return dist;
    +} /* distnorm */
    +
    +/*---------------------------------
    +
    +  qh_distround(dimension, maxabs, maxsumabs )
    +    compute maximum round-off error for a distance computation
    +      to a normalized hyperplane
    +    maxabs is the maximum absolute value of a coordinate
    +    maxsumabs is the maximum possible sum of absolute coordinate values
    +
    +  returns:
    +    max dist round for REALepsilon
    +
    +  notes:
    +    calculate roundoff error according to Golub & van Loan, 1983, Lemma 3.2-1, "Rounding Errors"
    +    use sqrt(dim) since one vector is normalized
    +      or use maxsumabs since one vector is < 1
    +*/
    +realT qh_distround(int dimension, realT maxabs, realT maxsumabs) {
    +  realT maxdistsum, maxround;
    +
    +  maxdistsum= sqrt((realT)dimension) * maxabs;
    +  minimize_( maxdistsum, maxsumabs);
    +  maxround= REALepsilon * (dimension * maxdistsum * 1.01 + maxabs);
    +              /* adds maxabs for offset */
    +  trace4((qh ferr, 4008, "qh_distround: %2.2g maxabs %2.2g maxsumabs %2.2g maxdistsum %2.2g\n",
    +                 maxround, maxabs, maxsumabs, maxdistsum));
    +  return maxround;
    +} /* distround */
    +
    +/*---------------------------------
    +
    +  qh_divzero( numer, denom, mindenom1, zerodiv )
    +    divide by a number that's nearly zero
    +    mindenom1= minimum denominator for dividing into 1.0
    +
    +  returns:
    +    quotient
    +    sets zerodiv and returns 0.0 if it would overflow
    +
    +  design:
    +    if numer is nearly zero and abs(numer) < abs(denom)
    +      return numer/denom
    +    else if numer is nearly zero
    +      return 0 and zerodiv
    +    else if denom/numer non-zero
    +      return numer/denom
    +    else
    +      return 0 and zerodiv
    +*/
    +realT qh_divzero(realT numer, realT denom, realT mindenom1, boolT *zerodiv) {
    +  realT temp, numerx, denomx;
    +
    +
    +  if (numer < mindenom1 && numer > -mindenom1) {
    +    numerx= fabs_(numer);
    +    denomx= fabs_(denom);
    +    if (numerx < denomx) {
    +      *zerodiv= False;
    +      return numer/denom;
    +    }else {
    +      *zerodiv= True;
    +      return 0.0;
    +    }
    +  }
    +  temp= denom/numer;
    +  if (temp > mindenom1 || temp < -mindenom1) {
    +    *zerodiv= False;
    +    return numer/denom;
    +  }else {
    +    *zerodiv= True;
    +    return 0.0;
    +  }
    +} /* divzero */
    +
    +
    +/*---------------------------------
    +
    +  qh_facetarea( facet )
    +    return area for a facet
    +
    +  notes:
    +    if non-simplicial,
    +      uses centrum to triangulate facet and sums the projected areas.
    +    if (qh DELAUNAY),
    +      computes projected area instead for last coordinate
    +    assumes facet->normal exists
    +    projecting tricoplanar facets to the hyperplane does not appear to make a difference
    +
    +  design:
    +    if simplicial
    +      compute area
    +    else
    +      for each ridge
    +        compute area from centrum to ridge
    +    negate area if upper Delaunay facet
    +*/
    +realT qh_facetarea(facetT *facet) {
    +  vertexT *apex;
    +  pointT *centrum;
    +  realT area= 0.0;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (facet->simplicial) {
    +    apex= SETfirstt_(facet->vertices, vertexT);
    +    area= qh_facetarea_simplex(qh hull_dim, apex->point, facet->vertices,
    +                    apex, facet->toporient, facet->normal, &facet->offset);
    +  }else {
    +    if (qh CENTERtype == qh_AScentrum)
    +      centrum= facet->center;
    +    else
    +      centrum= qh_getcentrum(facet);
    +    FOREACHridge_(facet->ridges)
    +      area += qh_facetarea_simplex(qh hull_dim, centrum, ridge->vertices,
    +                 NULL, (boolT)(ridge->top == facet),  facet->normal, &facet->offset);
    +    if (qh CENTERtype != qh_AScentrum)
    +      qh_memfree(centrum, qh normal_size);
    +  }
    +  if (facet->upperdelaunay && qh DELAUNAY)
    +    area= -area;  /* the normal should be [0,...,1] */
    +  trace4((qh ferr, 4009, "qh_facetarea: f%d area %2.2g\n", facet->id, area));
    +  return area;
    +} /* facetarea */
    +
    +/*---------------------------------
    +
    +  qh_facetarea_simplex( dim, apex, vertices, notvertex, toporient, normal, offset )
    +    return area for a simplex defined by
    +      an apex, a base of vertices, an orientation, and a unit normal
    +    if simplicial or tricoplanar facet,
    +      notvertex is defined and it is skipped in vertices
    +
    +  returns:
    +    computes area of simplex projected to plane [normal,offset]
    +    returns 0 if vertex too far below plane (qh WIDEfacet)
    +      vertex can't be apex of tricoplanar facet
    +
    +  notes:
    +    if (qh DELAUNAY),
    +      computes projected area instead for last coordinate
    +    uses qh gm_matrix/gm_row and qh hull_dim
    +    helper function for qh_facetarea
    +
    +  design:
    +    if Notvertex
    +      translate simplex to apex
    +    else
    +      project simplex to normal/offset
    +      translate simplex to apex
    +    if Delaunay
    +      set last row/column to 0 with -1 on diagonal
    +    else
    +      set last row to Normal
    +    compute determinate
    +    scale and flip sign for area
    +*/
    +realT qh_facetarea_simplex(int dim, coordT *apex, setT *vertices,
    +        vertexT *notvertex,  boolT toporient, coordT *normal, realT *offset) {
    +  pointT *coorda, *coordp, *gmcoord;
    +  coordT **rows, *normalp;
    +  int k,  i=0;
    +  realT area, dist;
    +  vertexT *vertex, **vertexp;
    +  boolT nearzero;
    +
    +  gmcoord= qh gm_matrix;
    +  rows= qh gm_row;
    +  FOREACHvertex_(vertices) {
    +    if (vertex == notvertex)
    +      continue;
    +    rows[i++]= gmcoord;
    +    coorda= apex;
    +    coordp= vertex->point;
    +    normalp= normal;
    +    if (notvertex) {
    +      for (k=dim; k--; )
    +        *(gmcoord++)= *coordp++ - *coorda++;
    +    }else {
    +      dist= *offset;
    +      for (k=dim; k--; )
    +        dist += *coordp++ * *normalp++;
    +      if (dist < -qh WIDEfacet) {
    +        zinc_(Znoarea);
    +        return 0.0;
    +      }
    +      coordp= vertex->point;
    +      normalp= normal;
    +      for (k=dim; k--; )
    +        *(gmcoord++)= (*coordp++ - dist * *normalp++) - *coorda++;
    +    }
    +  }
    +  if (i != dim-1) {
    +    qh_fprintf(qh ferr, 6008, "qhull internal error (qh_facetarea_simplex): #points %d != dim %d -1\n",
    +               i, dim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  rows[i]= gmcoord;
    +  if (qh DELAUNAY) {
    +    for (i=0; i < dim-1; i++)
    +      rows[i][dim-1]= 0.0;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= 0.0;
    +    rows[dim-1][dim-1]= -1.0;
    +  }else {
    +    normalp= normal;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= *normalp++;
    +  }
    +  zinc_(Zdetsimplex);
    +  area= qh_determinant(rows, dim, &nearzero);
    +  if (toporient)
    +    area= -area;
    +  area *= qh AREAfactor;
    +  trace4((qh ferr, 4010, "qh_facetarea_simplex: area=%2.2g for point p%d, toporient %d, nearzero? %d\n",
    +          area, qh_pointid(apex), toporient, nearzero));
    +  return area;
    +} /* facetarea_simplex */
    +
    +/*---------------------------------
    +
    +  qh_facetcenter( vertices )
    +    return Voronoi center (Voronoi vertex) for a facet's vertices
    +
    +  returns:
    +    return temporary point equal to the center
    +
    +  see:
    +    qh_voronoi_center()
    +*/
    +pointT *qh_facetcenter(setT *vertices) {
    +  setT *points= qh_settemp(qh_setsize(vertices));
    +  vertexT *vertex, **vertexp;
    +  pointT *center;
    +
    +  FOREACHvertex_(vertices)
    +    qh_setappend(&points, vertex->point);
    +  center= qh_voronoi_center(qh hull_dim-1, points);
    +  qh_settempfree(&points);
    +  return center;
    +} /* facetcenter */
    +
    +/*---------------------------------
    +
    +  qh_findgooddist( point, facetA, dist, facetlist )
    +    find best good facet visible for point from facetA
    +    assumes facetA is visible from point
    +
    +  returns:
    +    best facet, i.e., good facet that is furthest from point
    +      distance to best facet
    +      NULL if none
    +
    +    moves good, visible facets (and some other visible facets)
    +      to end of qh facet_list
    +
    +  notes:
    +    uses qh visit_id
    +
    +  design:
    +    initialize bestfacet if facetA is good
    +    move facetA to end of facetlist
    +    for each facet on facetlist
    +      for each unvisited neighbor of facet
    +        move visible neighbors to end of facetlist
    +        update best good neighbor
    +        if no good neighbors, update best facet
    +*/
    +facetT *qh_findgooddist(pointT *point, facetT *facetA, realT *distp,
    +               facetT **facetlist) {
    +  realT bestdist= -REALmax, dist;
    +  facetT *neighbor, **neighborp, *bestfacet=NULL, *facet;
    +  boolT goodseen= False;
    +
    +  if (facetA->good) {
    +    zzinc_(Zcheckpart);  /* calls from check_bestdist occur after print stats */
    +    qh_distplane(point, facetA, &bestdist);
    +    bestfacet= facetA;
    +    goodseen= True;
    +  }
    +  qh_removefacet(facetA);
    +  qh_appendfacet(facetA);
    +  *facetlist= facetA;
    +  facetA->visitid= ++qh visit_id;
    +  FORALLfacet_(*facetlist) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == qh visit_id)
    +        continue;
    +      neighbor->visitid= qh visit_id;
    +      if (goodseen && !neighbor->good)
    +        continue;
    +      zzinc_(Zcheckpart);
    +      qh_distplane(point, neighbor, &dist);
    +      if (dist > 0) {
    +        qh_removefacet(neighbor);
    +        qh_appendfacet(neighbor);
    +        if (neighbor->good) {
    +          goodseen= True;
    +          if (dist > bestdist) {
    +            bestdist= dist;
    +            bestfacet= neighbor;
    +          }
    +        }
    +      }
    +    }
    +  }
    +  if (bestfacet) {
    +    *distp= bestdist;
    +    trace2((qh ferr, 2003, "qh_findgooddist: p%d is %2.2g above good facet f%d\n",
    +      qh_pointid(point), bestdist, bestfacet->id));
    +    return bestfacet;
    +  }
    +  trace4((qh ferr, 4011, "qh_findgooddist: no good facet for p%d above f%d\n",
    +      qh_pointid(point), facetA->id));
    +  return NULL;
    +}  /* findgooddist */
    +
    +/*---------------------------------
    +
    +  qh_getarea( facetlist )
    +    set area of all facets in facetlist
    +    collect statistics
    +    nop if hasAreaVolume
    +
    +  returns:
    +    sets qh totarea/totvol to total area and volume of convex hull
    +    for Delaunay triangulation, computes projected area of the lower or upper hull
    +      ignores upper hull if qh ATinfinity
    +
    +  notes:
    +    could compute outer volume by expanding facet area by rays from interior
    +    the following attempt at perpendicular projection underestimated badly:
    +      qh.totoutvol += (-dist + facet->maxoutside + qh DISTround)
    +                            * area/ qh hull_dim;
    +  design:
    +    for each facet on facetlist
    +      compute facet->area
    +      update qh.totarea and qh.totvol
    +*/
    +void qh_getarea(facetT *facetlist) {
    +  realT area;
    +  realT dist;
    +  facetT *facet;
    +
    +  if (qh hasAreaVolume)
    +    return;
    +  if (qh REPORTfreq)
    +    qh_fprintf(qh ferr, 8020, "computing area of each facet and volume of the convex hull\n");
    +  else
    +    trace1((qh ferr, 1001, "qh_getarea: computing volume and area for each facet\n"));
    +  qh totarea= qh totvol= 0.0;
    +  FORALLfacet_(facetlist) {
    +    if (!facet->normal)
    +      continue;
    +    if (facet->upperdelaunay && qh ATinfinity)
    +      continue;
    +    if (!facet->isarea) {
    +      facet->f.area= qh_facetarea(facet);
    +      facet->isarea= True;
    +    }
    +    area= facet->f.area;
    +    if (qh DELAUNAY) {
    +      if (facet->upperdelaunay == qh UPPERdelaunay)
    +        qh totarea += area;
    +    }else {
    +      qh totarea += area;
    +      qh_distplane(qh interior_point, facet, &dist);
    +      qh totvol += -dist * area/ qh hull_dim;
    +    }
    +    if (qh PRINTstatistics) {
    +      wadd_(Wareatot, area);
    +      wmax_(Wareamax, area);
    +      wmin_(Wareamin, area);
    +    }
    +  }
    +  qh hasAreaVolume= True;
    +} /* getarea */
    +
    +/*---------------------------------
    +
    +  qh_gram_schmidt( dim, row )
    +    implements Gram-Schmidt orthogonalization by rows
    +
    +  returns:
    +    false if zero norm
    +    overwrites rows[dim][dim]
    +
    +  notes:
    +    see Golub & van Loan, 1983, Algorithm 6.2-2, "Modified Gram-Schmidt"
    +    overflow due to small divisors not handled
    +
    +  design:
    +    for each row
    +      compute norm for row
    +      if non-zero, normalize row
    +      for each remaining rowA
    +        compute inner product of row and rowA
    +        reduce rowA by row * inner product
    +*/
    +boolT qh_gram_schmidt(int dim, realT **row) {
    +  realT *rowi, *rowj, norm;
    +  int i, j, k;
    +
    +  for (i=0; i < dim; i++) {
    +    rowi= row[i];
    +    for (norm= 0.0, k= dim; k--; rowi++)
    +      norm += *rowi * *rowi;
    +    norm= sqrt(norm);
    +    wmin_(Wmindenom, norm);
    +    if (norm == 0.0)  /* either 0 or overflow due to sqrt */
    +      return False;
    +    for (k=dim; k--; )
    +      *(--rowi) /= norm;
    +    for (j=i+1; j < dim; j++) {
    +      rowj= row[j];
    +      for (norm= 0.0, k=dim; k--; )
    +        norm += *rowi++ * *rowj++;
    +      for (k=dim; k--; )
    +        *(--rowj) -= *(--rowi) * norm;
    +    }
    +  }
    +  return True;
    +} /* gram_schmidt */
    +
    +
    +/*---------------------------------
    +
    +  qh_inthresholds( normal, angle )
    +    return True if normal within qh.lower_/upper_threshold
    +
    +  returns:
    +    estimate of angle by summing of threshold diffs
    +      angle may be NULL
    +      smaller "angle" is better
    +
    +  notes:
    +    invalid if qh.SPLITthresholds
    +
    +  see:
    +    qh.lower_threshold in qh_initbuild()
    +    qh_initthresholds()
    +
    +  design:
    +    for each dimension
    +      test threshold
    +*/
    +boolT qh_inthresholds(coordT *normal, realT *angle) {
    +  boolT within= True;
    +  int k;
    +  realT threshold;
    +
    +  if (angle)
    +    *angle= 0.0;
    +  for (k=0; k < qh hull_dim; k++) {
    +    threshold= qh lower_threshold[k];
    +    if (threshold > -REALmax/2) {
    +      if (normal[k] < threshold)
    +        within= False;
    +      if (angle) {
    +        threshold -= normal[k];
    +        *angle += fabs_(threshold);
    +      }
    +    }
    +    if (qh upper_threshold[k] < REALmax/2) {
    +      threshold= qh upper_threshold[k];
    +      if (normal[k] > threshold)
    +        within= False;
    +      if (angle) {
    +        threshold -= normal[k];
    +        *angle += fabs_(threshold);
    +      }
    +    }
    +  }
    +  return within;
    +} /* inthresholds */
    +
    +
    +/*---------------------------------
    +
    +  qh_joggleinput()
    +    randomly joggle input to Qhull by qh.JOGGLEmax
    +    initial input is qh.first_point/qh.num_points of qh.hull_dim
    +      repeated calls use qh.input_points/qh.num_points
    +
    +  returns:
    +    joggles points at qh.first_point/qh.num_points
    +    copies data to qh.input_points/qh.input_malloc if first time
    +    determines qh.JOGGLEmax if it was zero
    +    if qh.DELAUNAY
    +      computes the Delaunay projection of the joggled points
    +
    +  notes:
    +    if qh.DELAUNAY, unnecessarily joggles the last coordinate
    +    the initial 'QJn' may be set larger than qh_JOGGLEmaxincrease
    +
    +  design:
    +    if qh.DELAUNAY
    +      set qh.SCALElast for reduced precision errors
    +    if first call
    +      initialize qh.input_points to the original input points
    +      if qh.JOGGLEmax == 0
    +        determine default qh.JOGGLEmax
    +    else
    +      increase qh.JOGGLEmax according to qh.build_cnt
    +    joggle the input by adding a random number in [-qh.JOGGLEmax,qh.JOGGLEmax]
    +    if qh.DELAUNAY
    +      sets the Delaunay projection
    +*/
    +void qh_joggleinput(void) {
    +  int i, seed, size;
    +  coordT *coordp, *inputp;
    +  realT randr, randa, randb;
    +
    +  if (!qh input_points) { /* first call */
    +    qh input_points= qh first_point;
    +    qh input_malloc= qh POINTSmalloc;
    +    size= qh num_points * qh hull_dim * sizeof(coordT);
    +    if (!(qh first_point=(coordT*)qh_malloc((size_t)size))) {
    +      qh_fprintf(qh ferr, 6009, "qhull error: insufficient memory to joggle %d points\n",
    +          qh num_points);
    +      qh_errexit(qh_ERRmem, NULL, NULL);
    +    }
    +    qh POINTSmalloc= True;
    +    if (qh JOGGLEmax == 0.0) {
    +      qh JOGGLEmax= qh_detjoggle(qh input_points, qh num_points, qh hull_dim);
    +      qh_option("QJoggle", NULL, &qh JOGGLEmax);
    +    }
    +  }else {                 /* repeated call */
    +    if (!qh RERUN && qh build_cnt > qh_JOGGLEretry) {
    +      if (((qh build_cnt-qh_JOGGLEretry-1) % qh_JOGGLEagain) == 0) {
    +        realT maxjoggle= qh MAXwidth * qh_JOGGLEmaxincrease;
    +        if (qh JOGGLEmax < maxjoggle) {
    +          qh JOGGLEmax *= qh_JOGGLEincrease;
    +          minimize_(qh JOGGLEmax, maxjoggle);
    +        }
    +      }
    +    }
    +    qh_option("QJoggle", NULL, &qh JOGGLEmax);
    +  }
    +  if (qh build_cnt > 1 && qh JOGGLEmax > fmax_(qh MAXwidth/4, 0.1)) {
    +      qh_fprintf(qh ferr, 6010, "qhull error: the current joggle for 'QJn', %.2g, is too large for the width\nof the input.  If possible, recompile Qhull with higher-precision reals.\n",
    +                qh JOGGLEmax);
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  /* for some reason, using qh ROTATErandom and qh_RANDOMseed does not repeat the run. Use 'TRn' instead */
    +  seed= qh_RANDOMint;
    +  qh_option("_joggle-seed", &seed, NULL);
    +  trace0((qh ferr, 6, "qh_joggleinput: joggle input by %2.2g with seed %d\n",
    +    qh JOGGLEmax, seed));
    +  inputp= qh input_points;
    +  coordp= qh first_point;
    +  randa= 2.0 * qh JOGGLEmax/qh_RANDOMmax;
    +  randb= -qh JOGGLEmax;
    +  size= qh num_points * qh hull_dim;
    +  for (i=size; i--; ) {
    +    randr= qh_RANDOMint;
    +    *(coordp++)= *(inputp++) + (randr * randa + randb);
    +  }
    +  if (qh DELAUNAY) {
    +    qh last_low= qh last_high= qh last_newhigh= REALmax;
    +    qh_setdelaunay(qh hull_dim, qh num_points, qh first_point);
    +  }
    +} /* joggleinput */
    +
    +/*---------------------------------
    +
    +  qh_maxabsval( normal, dim )
    +    return pointer to maximum absolute value of a dim vector
    +    returns NULL if dim=0
    +*/
    +realT *qh_maxabsval(realT *normal, int dim) {
    +  realT maxval= -REALmax;
    +  realT *maxp= NULL, *colp, absval;
    +  int k;
    +
    +  for (k=dim, colp= normal; k--; colp++) {
    +    absval= fabs_(*colp);
    +    if (absval > maxval) {
    +      maxval= absval;
    +      maxp= colp;
    +    }
    +  }
    +  return maxp;
    +} /* maxabsval */
    +
    +
    +/*---------------------------------
    +
    +  qh_maxmin( points, numpoints, dimension )
    +    return max/min points for each dimension
    +    determine max and min coordinates
    +
    +  returns:
    +    returns a temporary set of max and min points
    +      may include duplicate points. Does not include qh.GOODpoint
    +    sets qh.NEARzero, qh.MAXabs_coord, qh.MAXsumcoord, qh.MAXwidth
    +         qh.MAXlastcoord, qh.MINlastcoord
    +    initializes qh.max_outside, qh.min_vertex, qh.WAScoplanar, qh.ZEROall_ok
    +
    +  notes:
    +    loop duplicated in qh_detjoggle()
    +
    +  design:
    +    initialize global precision variables
    +    checks definition of REAL...
    +    for each dimension
    +      for each point
    +        collect maximum and minimum point
    +      collect maximum of maximums and minimum of minimums
    +      determine qh.NEARzero for Gaussian Elimination
    +*/
    +setT *qh_maxmin(pointT *points, int numpoints, int dimension) {
    +  int k;
    +  realT maxcoord, temp;
    +  pointT *minimum, *maximum, *point, *pointtemp;
    +  setT *set;
    +
    +  qh max_outside= 0.0;
    +  qh MAXabs_coord= 0.0;
    +  qh MAXwidth= -REALmax;
    +  qh MAXsumcoord= 0.0;
    +  qh min_vertex= 0.0;
    +  qh WAScoplanar= False;
    +  if (qh ZEROcentrum)
    +    qh ZEROall_ok= True;
    +  if (REALmin < REALepsilon && REALmin < REALmax && REALmin > -REALmax
    +  && REALmax > 0.0 && -REALmax < 0.0)
    +    ; /* all ok */
    +  else {
    +    qh_fprintf(qh ferr, 6011, "qhull error: floating point constants in user.h are wrong\n\
    +REALepsilon %g REALmin %g REALmax %g -REALmax %g\n",
    +             REALepsilon, REALmin, REALmax, -REALmax);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  set= qh_settemp(2*dimension);
    +  for (k=0; k < dimension; k++) {
    +    if (points == qh GOODpointp)
    +      minimum= maximum= points + dimension;
    +    else
    +      minimum= maximum= points;
    +    FORALLpoint_(points, numpoints) {
    +      if (point == qh GOODpointp)
    +        continue;
    +      if (maximum[k] < point[k])
    +        maximum= point;
    +      else if (minimum[k] > point[k])
    +        minimum= point;
    +    }
    +    if (k == dimension-1) {
    +      qh MINlastcoord= minimum[k];
    +      qh MAXlastcoord= maximum[k];
    +    }
    +    if (qh SCALElast && k == dimension-1)
    +      maxcoord= qh MAXwidth;
    +    else {
    +      maxcoord= fmax_(maximum[k], -minimum[k]);
    +      if (qh GOODpointp) {
    +        temp= fmax_(qh GOODpointp[k], -qh GOODpointp[k]);
    +        maximize_(maxcoord, temp);
    +      }
    +      temp= maximum[k] - minimum[k];
    +      maximize_(qh MAXwidth, temp);
    +    }
    +    maximize_(qh MAXabs_coord, maxcoord);
    +    qh MAXsumcoord += maxcoord;
    +    qh_setappend(&set, maximum);
    +    qh_setappend(&set, minimum);
    +    /* calculation of qh NEARzero is based on Golub & van Loan, 1983,
    +       Eq. 4.4-13 for "Gaussian elimination with complete pivoting".
    +       Golub & van Loan say that n^3 can be ignored and 10 be used in
    +       place of rho */
    +    qh NEARzero[k]= 80 * qh MAXsumcoord * REALepsilon;
    +  }
    +  if (qh IStracing >=1)
    +    qh_printpoints(qh ferr, "qh_maxmin: found the max and min points(by dim):", set);
    +  return(set);
    +} /* maxmin */
    +
    +/*---------------------------------
    +
    +  qh_maxouter()
    +    return maximum distance from facet to outer plane
    +    normally this is qh.max_outside+qh.DISTround
    +    does not include qh.JOGGLEmax
    +
    +  see:
    +    qh_outerinner()
    +
    +  notes:
    +    need to add another qh.DISTround if testing actual point with computation
    +
    +  for joggle:
    +    qh_setfacetplane() updated qh.max_outer for Wnewvertexmax (max distance to vertex)
    +    need to use Wnewvertexmax since could have a coplanar point for a high
    +      facet that is replaced by a low facet
    +    need to add qh.JOGGLEmax if testing input points
    +*/
    +realT qh_maxouter(void) {
    +  realT dist;
    +
    +  dist= fmax_(qh max_outside, qh DISTround);
    +  dist += qh DISTround;
    +  trace4((qh ferr, 4012, "qh_maxouter: max distance from facet to outer plane is %2.2g max_outside is %2.2g\n", dist, qh max_outside));
    +  return dist;
    +} /* maxouter */
    +
    +/*---------------------------------
    +
    +  qh_maxsimplex( dim, maxpoints, points, numpoints, simplex )
    +    determines maximum simplex for a set of points
    +    starts from points already in simplex
    +    skips qh.GOODpointp (assumes that it isn't in maxpoints)
    +
    +  returns:
    +    simplex with dim+1 points
    +
    +  notes:
    +    assumes at least pointsneeded points in points
    +    maximizes determinate for x,y,z,w, etc.
    +    uses maxpoints as long as determinate is clearly non-zero
    +
    +  design:
    +    initialize simplex with at least two points
    +      (find points with max or min x coordinate)
    +    for each remaining dimension
    +      add point that maximizes the determinate
    +        (use points from maxpoints first)
    +*/
    +void qh_maxsimplex(int dim, setT *maxpoints, pointT *points, int numpoints, setT **simplex) {
    +  pointT *point, **pointp, *pointtemp, *maxpoint, *minx=NULL, *maxx=NULL;
    +  boolT nearzero, maxnearzero= False;
    +  int k, sizinit;
    +  realT maxdet= -REALmax, det, mincoord= REALmax, maxcoord= -REALmax;
    +
    +  sizinit= qh_setsize(*simplex);
    +  if (sizinit < 2) {
    +    if (qh_setsize(maxpoints) >= 2) {
    +      FOREACHpoint_(maxpoints) {
    +        if (maxcoord < point[0]) {
    +          maxcoord= point[0];
    +          maxx= point;
    +        }
    +        if (mincoord > point[0]) {
    +          mincoord= point[0];
    +          minx= point;
    +        }
    +      }
    +    }else {
    +      FORALLpoint_(points, numpoints) {
    +        if (point == qh GOODpointp)
    +          continue;
    +        if (maxcoord < point[0]) {
    +          maxcoord= point[0];
    +          maxx= point;
    +        }
    +        if (mincoord > point[0]) {
    +          mincoord= point[0];
    +          minx= point;
    +        }
    +      }
    +    }
    +    qh_setunique(simplex, minx);
    +    if (qh_setsize(*simplex) < 2)
    +      qh_setunique(simplex, maxx);
    +    sizinit= qh_setsize(*simplex);
    +    if (sizinit < 2) {
    +      qh_precision("input has same x coordinate");
    +      if (zzval_(Zsetplane) > qh hull_dim+1) {
    +        qh_fprintf(qh ferr, 6012, "qhull precision error (qh_maxsimplex for voronoi_center):\n%d points with the same x coordinate.\n",
    +                 qh_setsize(maxpoints)+numpoints);
    +        qh_errexit(qh_ERRprec, NULL, NULL);
    +      }else {
    +        qh_fprintf(qh ferr, 6013, "qhull input error: input is less than %d-dimensional since it has the same x coordinate\n", qh hull_dim);
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +    }
    +  }
    +  for (k=sizinit; k < dim+1; k++) {
    +    maxpoint= NULL;
    +    maxdet= -REALmax;
    +    FOREACHpoint_(maxpoints) {
    +      if (!qh_setin(*simplex, point)) {
    +        det= qh_detsimplex(point, *simplex, k, &nearzero);
    +        if ((det= fabs_(det)) > maxdet) {
    +          maxdet= det;
    +          maxpoint= point;
    +          maxnearzero= nearzero;
    +        }
    +      }
    +    }
    +    if (!maxpoint || maxnearzero) {
    +      zinc_(Zsearchpoints);
    +      if (!maxpoint) {
    +        trace0((qh ferr, 7, "qh_maxsimplex: searching all points for %d-th initial vertex.\n", k+1));
    +      }else {
    +        trace0((qh ferr, 8, "qh_maxsimplex: searching all points for %d-th initial vertex, better than p%d det %2.2g\n",
    +                k+1, qh_pointid(maxpoint), maxdet));
    +      }
    +      FORALLpoint_(points, numpoints) {
    +        if (point == qh GOODpointp)
    +          continue;
    +        if (!qh_setin(*simplex, point)) {
    +          det= qh_detsimplex(point, *simplex, k, &nearzero);
    +          if ((det= fabs_(det)) > maxdet) {
    +            maxdet= det;
    +            maxpoint= point;
    +            maxnearzero= nearzero;
    +          }
    +        }
    +      }
    +    } /* !maxpoint */
    +    if (!maxpoint) {
    +      qh_fprintf(qh ferr, 6014, "qhull internal error (qh_maxsimplex): not enough points available\n");
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +    qh_setappend(simplex, maxpoint);
    +    trace1((qh ferr, 1002, "qh_maxsimplex: selected point p%d for %d`th initial vertex, det=%2.2g\n",
    +            qh_pointid(maxpoint), k+1, maxdet));
    +  } /* k */
    +} /* maxsimplex */
    +
    +/*---------------------------------
    +
    +  qh_minabsval( normal, dim )
    +    return minimum absolute value of a dim vector
    +*/
    +realT qh_minabsval(realT *normal, int dim) {
    +  realT minval= 0;
    +  realT maxval= 0;
    +  realT *colp;
    +  int k;
    +
    +  for (k=dim, colp=normal; k--; colp++) {
    +    maximize_(maxval, *colp);
    +    minimize_(minval, *colp);
    +  }
    +  return fmax_(maxval, -minval);
    +} /* minabsval */
    +
    +
    +/*---------------------------------
    +
    +  qh_mindif( vecA, vecB, dim )
    +    return index of min abs. difference of two vectors
    +*/
    +int qh_mindiff(realT *vecA, realT *vecB, int dim) {
    +  realT mindiff= REALmax, diff;
    +  realT *vecAp= vecA, *vecBp= vecB;
    +  int k, mink= 0;
    +
    +  for (k=0; k < dim; k++) {
    +    diff= *vecAp++ - *vecBp++;
    +    diff= fabs_(diff);
    +    if (diff < mindiff) {
    +      mindiff= diff;
    +      mink= k;
    +    }
    +  }
    +  return mink;
    +} /* mindiff */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_orientoutside( facet  )
    +    make facet outside oriented via qh.interior_point
    +
    +  returns:
    +    True if facet reversed orientation.
    +*/
    +boolT qh_orientoutside(facetT *facet) {
    +  int k;
    +  realT dist;
    +
    +  qh_distplane(qh interior_point, facet, &dist);
    +  if (dist > 0) {
    +    for (k=qh hull_dim; k--; )
    +      facet->normal[k]= -facet->normal[k];
    +    facet->offset= -facet->offset;
    +    return True;
    +  }
    +  return False;
    +} /* orientoutside */
    +
    +/*---------------------------------
    +
    +  qh_outerinner( facet, outerplane, innerplane  )
    +    if facet and qh.maxoutdone (i.e., qh_check_maxout)
    +      returns outer and inner plane for facet
    +    else
    +      returns maximum outer and inner plane
    +    accounts for qh.JOGGLEmax
    +
    +  see:
    +    qh_maxouter(), qh_check_bestdist(), qh_check_points()
    +
    +  notes:
    +    outerplaner or innerplane may be NULL
    +    facet is const
    +    Does not error (QhullFacet)
    +
    +    includes qh.DISTround for actual points
    +    adds another qh.DISTround if testing with floating point arithmetic
    +*/
    +void qh_outerinner(facetT *facet, realT *outerplane, realT *innerplane) {
    +  realT dist, mindist;
    +  vertexT *vertex, **vertexp;
    +
    +  if (outerplane) {
    +    if (!qh_MAXoutside || !facet || !qh maxoutdone) {
    +      *outerplane= qh_maxouter();       /* includes qh.DISTround */
    +    }else { /* qh_MAXoutside ... */
    +#if qh_MAXoutside
    +      *outerplane= facet->maxoutside + qh DISTround;
    +#endif
    +
    +    }
    +    if (qh JOGGLEmax < REALmax/2)
    +      *outerplane += qh JOGGLEmax * sqrt((realT)qh hull_dim);
    +  }
    +  if (innerplane) {
    +    if (facet) {
    +      mindist= REALmax;
    +      FOREACHvertex_(facet->vertices) {
    +        zinc_(Zdistio);
    +        qh_distplane(vertex->point, facet, &dist);
    +        minimize_(mindist, dist);
    +      }
    +      *innerplane= mindist - qh DISTround;
    +    }else
    +      *innerplane= qh min_vertex - qh DISTround;
    +    if (qh JOGGLEmax < REALmax/2)
    +      *innerplane -= qh JOGGLEmax * sqrt((realT)qh hull_dim);
    +  }
    +} /* outerinner */
    +
    +/*---------------------------------
    +
    +  qh_pointdist( point1, point2, dim )
    +    return distance between two points
    +
    +  notes:
    +    returns distance squared if 'dim' is negative
    +*/
    +coordT qh_pointdist(pointT *point1, pointT *point2, int dim) {
    +  coordT dist, diff;
    +  int k;
    +
    +  dist= 0.0;
    +  for (k= (dim > 0 ? dim : -dim); k--; ) {
    +    diff= *point1++ - *point2++;
    +    dist += diff * diff;
    +  }
    +  if (dim > 0)
    +    return(sqrt(dist));
    +  return dist;
    +} /* pointdist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printmatrix( fp, string, rows, numrow, numcol )
    +    print matrix to fp given by row vectors
    +    print string as header
    +
    +  notes:
    +    print a vector by qh_printmatrix(fp, "", &vect, 1, len)
    +*/
    +void qh_printmatrix(FILE *fp, const char *string, realT **rows, int numrow, int numcol) {
    +  realT *rowp;
    +  realT r; /*bug fix*/
    +  int i,k;
    +
    +  qh_fprintf(fp, 9001, "%s\n", string);
    +  for (i=0; i < numrow; i++) {
    +    rowp= rows[i];
    +    for (k=0; k < numcol; k++) {
    +      r= *rowp++;
    +      qh_fprintf(fp, 9002, "%6.3g ", r);
    +    }
    +    qh_fprintf(fp, 9003, "\n");
    +  }
    +} /* printmatrix */
    +
    +
    +/*---------------------------------
    +
    +  qh_printpoints( fp, string, points )
    +    print pointids to fp for a set of points
    +    if string, prints string and 'p' point ids
    +*/
    +void qh_printpoints(FILE *fp, const char *string, setT *points) {
    +  pointT *point, **pointp;
    +
    +  if (string) {
    +    qh_fprintf(fp, 9004, "%s", string);
    +    FOREACHpoint_(points)
    +      qh_fprintf(fp, 9005, " p%d", qh_pointid(point));
    +    qh_fprintf(fp, 9006, "\n");
    +  }else {
    +    FOREACHpoint_(points)
    +      qh_fprintf(fp, 9007, " %d", qh_pointid(point));
    +    qh_fprintf(fp, 9008, "\n");
    +  }
    +} /* printpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectinput()
    +    project input points using qh.lower_bound/upper_bound and qh DELAUNAY
    +    if qh.lower_bound[k]=qh.upper_bound[k]= 0,
    +      removes dimension k
    +    if halfspace intersection
    +      removes dimension k from qh.feasible_point
    +    input points in qh first_point, num_points, input_dim
    +
    +  returns:
    +    new point array in qh first_point of qh hull_dim coordinates
    +    sets qh POINTSmalloc
    +    if qh DELAUNAY
    +      projects points to paraboloid
    +      lowbound/highbound is also projected
    +    if qh ATinfinity
    +      adds point "at-infinity"
    +    if qh POINTSmalloc
    +      frees old point array
    +
    +  notes:
    +    checks that qh.hull_dim agrees with qh.input_dim, PROJECTinput, and DELAUNAY
    +
    +
    +  design:
    +    sets project[k] to -1 (delete), 0 (keep), 1 (add for Delaunay)
    +    determines newdim and newnum for qh hull_dim and qh num_points
    +    projects points to newpoints
    +    projects qh.lower_bound to itself
    +    projects qh.upper_bound to itself
    +    if qh DELAUNAY
    +      if qh ATINFINITY
    +        projects points to paraboloid
    +        computes "infinity" point as vertex average and 10% above all points
    +      else
    +        uses qh_setdelaunay to project points to paraboloid
    +*/
    +void qh_projectinput(void) {
    +  int k,i;
    +  int newdim= qh input_dim, newnum= qh num_points;
    +  signed char *project;
    +  int projectsize= (qh input_dim+1)*sizeof(*project);
    +  pointT *newpoints, *coord, *infinity;
    +  realT paraboloid, maxboloid= 0;
    +
    +  project= (signed char*)qh_memalloc(projectsize);
    +  memset((char*)project, 0, (size_t)projectsize);
    +  for (k=0; k < qh input_dim; k++) {   /* skip Delaunay bound */
    +    if (qh lower_bound[k] == 0 && qh upper_bound[k] == 0) {
    +      project[k]= -1;
    +      newdim--;
    +    }
    +  }
    +  if (qh DELAUNAY) {
    +    project[k]= 1;
    +    newdim++;
    +    if (qh ATinfinity)
    +      newnum++;
    +  }
    +  if (newdim != qh hull_dim) {
    +    qh_memfree(project, projectsize);
    +    qh_fprintf(qh ferr, 6015, "qhull internal error (qh_projectinput): dimension after projection %d != hull_dim %d\n", newdim, qh hull_dim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (!(newpoints= qh temp_malloc= (coordT*)qh_malloc(newnum*newdim*sizeof(coordT)))){
    +    qh_memfree(project, projectsize);
    +    qh_fprintf(qh ferr, 6016, "qhull error: insufficient memory to project %d points\n",
    +           qh num_points);
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  /* qh_projectpoints throws error if mismatched dimensions */
    +  qh_projectpoints(project, qh input_dim+1, qh first_point,
    +                    qh num_points, qh input_dim, newpoints, newdim);
    +  trace1((qh ferr, 1003, "qh_projectinput: updating lower and upper_bound\n"));
    +  qh_projectpoints(project, qh input_dim+1, qh lower_bound,
    +                    1, qh input_dim+1, qh lower_bound, newdim+1);
    +  qh_projectpoints(project, qh input_dim+1, qh upper_bound,
    +                    1, qh input_dim+1, qh upper_bound, newdim+1);
    +  if (qh HALFspace) {
    +    if (!qh feasible_point) {
    +      qh_memfree(project, projectsize);
    +      qh_fprintf(qh ferr, 6017, "qhull internal error (qh_projectinput): HALFspace defined without qh.feasible_point\n");
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +    qh_projectpoints(project, qh input_dim, qh feasible_point,
    +                      1, qh input_dim, qh feasible_point, newdim);
    +  }
    +  qh_memfree(project, projectsize);
    +  if (qh POINTSmalloc)
    +    qh_free(qh first_point);
    +  qh first_point= newpoints;
    +  qh POINTSmalloc= True;
    +  qh temp_malloc= NULL;
    +  if (qh DELAUNAY && qh ATinfinity) {
    +    coord= qh first_point;
    +    infinity= qh first_point + qh hull_dim * qh num_points;
    +    for (k=qh hull_dim-1; k--; )
    +      infinity[k]= 0.0;
    +    for (i=qh num_points; i--; ) {
    +      paraboloid= 0.0;
    +      for (k=0; k < qh hull_dim-1; k++) {
    +        paraboloid += *coord * *coord;
    +        infinity[k] += *coord;
    +        coord++;
    +      }
    +      *(coord++)= paraboloid;
    +      maximize_(maxboloid, paraboloid);
    +    }
    +    /* coord == infinity */
    +    for (k=qh hull_dim-1; k--; )
    +      *(coord++) /= qh num_points;
    +    *(coord++)= maxboloid * 1.1;
    +    qh num_points++;
    +    trace0((qh ferr, 9, "qh_projectinput: projected points to paraboloid for Delaunay\n"));
    +  }else if (qh DELAUNAY)  /* !qh ATinfinity */
    +    qh_setdelaunay( qh hull_dim, qh num_points, qh first_point);
    +} /* projectinput */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectpoints( project, n, points, numpoints, dim, newpoints, newdim )
    +    project points/numpoints/dim to newpoints/newdim
    +    if project[k] == -1
    +      delete dimension k
    +    if project[k] == 1
    +      add dimension k by duplicating previous column
    +    n is size of project
    +
    +  notes:
    +    newpoints may be points if only adding dimension at end
    +
    +  design:
    +    check that 'project' and 'newdim' agree
    +    for each dimension
    +      if project == -1
    +        skip dimension
    +      else
    +        determine start of column in newpoints
    +        determine start of column in points
    +          if project == +1, duplicate previous column
    +        copy dimension (column) from points to newpoints
    +*/
    +void qh_projectpoints(signed char *project, int n, realT *points,
    +        int numpoints, int dim, realT *newpoints, int newdim) {
    +  int testdim= dim, oldk=0, newk=0, i,j=0,k;
    +  realT *newp, *oldp;
    +
    +  for (k=0; k < n; k++)
    +    testdim += project[k];
    +  if (testdim != newdim) {
    +    qh_fprintf(qh ferr, 6018, "qhull internal error (qh_projectpoints): newdim %d should be %d after projection\n",
    +      newdim, testdim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  for (j=0; j= dim)
    +          continue;
    +        oldp= points+oldk;
    +      }else
    +        oldp= points+oldk++;
    +      for (i=numpoints; i--; ) {
    +        *newp= *oldp;
    +        newp += newdim;
    +        oldp += dim;
    +      }
    +    }
    +    if (oldk >= dim)
    +      break;
    +  }
    +  trace1((qh ferr, 1004, "qh_projectpoints: projected %d points from dim %d to dim %d\n",
    +    numpoints, dim, newdim));
    +} /* projectpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_rotateinput( rows )
    +    rotate input using row matrix
    +    input points given by qh first_point, num_points, hull_dim
    +    assumes rows[dim] is a scratch buffer
    +    if qh POINTSmalloc, overwrites input points, else mallocs a new array
    +
    +  returns:
    +    rotated input
    +    sets qh POINTSmalloc
    +
    +  design:
    +    see qh_rotatepoints
    +*/
    +void qh_rotateinput(realT **rows) {
    +
    +  if (!qh POINTSmalloc) {
    +    qh first_point= qh_copypoints(qh first_point, qh num_points, qh hull_dim);
    +    qh POINTSmalloc= True;
    +  }
    +  qh_rotatepoints(qh first_point, qh num_points, qh hull_dim, rows);
    +}  /* rotateinput */
    +
    +/*---------------------------------
    +
    +  qh_rotatepoints( points, numpoints, dim, row )
    +    rotate numpoints points by a d-dim row matrix
    +    assumes rows[dim] is a scratch buffer
    +
    +  returns:
    +    rotated points in place
    +
    +  design:
    +    for each point
    +      for each coordinate
    +        use row[dim] to compute partial inner product
    +      for each coordinate
    +        rotate by partial inner product
    +*/
    +void qh_rotatepoints(realT *points, int numpoints, int dim, realT **row) {
    +  realT *point, *rowi, *coord= NULL, sum, *newval;
    +  int i,j,k;
    +
    +  if (qh IStracing >= 1)
    +    qh_printmatrix(qh ferr, "qh_rotatepoints: rotate points by", row, dim, dim);
    +  for (point= points, j= numpoints; j--; point += dim) {
    +    newval= row[dim];
    +    for (i=0; i < dim; i++) {
    +      rowi= row[i];
    +      coord= point;
    +      for (sum= 0.0, k= dim; k--; )
    +        sum += *rowi++ * *coord++;
    +      *(newval++)= sum;
    +    }
    +    for (k=dim; k--; )
    +      *(--coord)= *(--newval);
    +  }
    +} /* rotatepoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_scaleinput()
    +    scale input points using qh low_bound/high_bound
    +    input points given by qh first_point, num_points, hull_dim
    +    if qh POINTSmalloc, overwrites input points, else mallocs a new array
    +
    +  returns:
    +    scales coordinates of points to low_bound[k], high_bound[k]
    +    sets qh POINTSmalloc
    +
    +  design:
    +    see qh_scalepoints
    +*/
    +void qh_scaleinput(void) {
    +
    +  if (!qh POINTSmalloc) {
    +    qh first_point= qh_copypoints(qh first_point, qh num_points, qh hull_dim);
    +    qh POINTSmalloc= True;
    +  }
    +  qh_scalepoints(qh first_point, qh num_points, qh hull_dim,
    +       qh lower_bound, qh upper_bound);
    +}  /* scaleinput */
    +
    +/*---------------------------------
    +
    +  qh_scalelast( points, numpoints, dim, low, high, newhigh )
    +    scale last coordinate to [0,m] for Delaunay triangulations
    +    input points given by points, numpoints, dim
    +
    +  returns:
    +    changes scale of last coordinate from [low, high] to [0, newhigh]
    +    overwrites last coordinate of each point
    +    saves low/high/newhigh in qh.last_low, etc. for qh_setdelaunay()
    +
    +  notes:
    +    when called by qh_setdelaunay, low/high may not match actual data
    +
    +  design:
    +    compute scale and shift factors
    +    apply to last coordinate of each point
    +*/
    +void qh_scalelast(coordT *points, int numpoints, int dim, coordT low,
    +                   coordT high, coordT newhigh) {
    +  realT scale, shift;
    +  coordT *coord;
    +  int i;
    +  boolT nearzero= False;
    +
    +  trace4((qh ferr, 4013, "qh_scalelast: scale last coordinate from [%2.2g, %2.2g] to [0,%2.2g]\n",
    +    low, high, newhigh));
    +  qh last_low= low;
    +  qh last_high= high;
    +  qh last_newhigh= newhigh;
    +  scale= qh_divzero(newhigh, high - low,
    +                  qh MINdenom_1, &nearzero);
    +  if (nearzero) {
    +    if (qh DELAUNAY)
    +      qh_fprintf(qh ferr, 6019, "qhull input error: can not scale last coordinate.  Input is cocircular\n   or cospherical.   Use option 'Qz' to add a point at infinity.\n");
    +    else
    +      qh_fprintf(qh ferr, 6020, "qhull input error: can not scale last coordinate.  New bounds [0, %2.2g] are too wide for\nexisting bounds [%2.2g, %2.2g] (width %2.2g)\n",
    +                newhigh, low, high, high-low);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  shift= - low * newhigh / (high-low);
    +  coord= points + dim - 1;
    +  for (i=numpoints; i--; coord += dim)
    +    *coord= *coord * scale + shift;
    +} /* scalelast */
    +
    +/*---------------------------------
    +
    +  qh_scalepoints( points, numpoints, dim, newlows, newhighs )
    +    scale points to new lowbound and highbound
    +    retains old bound when newlow= -REALmax or newhigh= +REALmax
    +
    +  returns:
    +    scaled points
    +    overwrites old points
    +
    +  design:
    +    for each coordinate
    +      compute current low and high bound
    +      compute scale and shift factors
    +      scale all points
    +      enforce new low and high bound for all points
    +*/
    +void qh_scalepoints(pointT *points, int numpoints, int dim,
    +        realT *newlows, realT *newhighs) {
    +  int i,k;
    +  realT shift, scale, *coord, low, high, newlow, newhigh, mincoord, maxcoord;
    +  boolT nearzero= False;
    +
    +  for (k=0; k < dim; k++) {
    +    newhigh= newhighs[k];
    +    newlow= newlows[k];
    +    if (newhigh > REALmax/2 && newlow < -REALmax/2)
    +      continue;
    +    low= REALmax;
    +    high= -REALmax;
    +    for (i=numpoints, coord=points+k; i--; coord += dim) {
    +      minimize_(low, *coord);
    +      maximize_(high, *coord);
    +    }
    +    if (newhigh > REALmax/2)
    +      newhigh= high;
    +    if (newlow < -REALmax/2)
    +      newlow= low;
    +    if (qh DELAUNAY && k == dim-1 && newhigh < newlow) {
    +      qh_fprintf(qh ferr, 6021, "qhull input error: 'Qb%d' or 'QB%d' inverts paraboloid since high bound %.2g < low bound %.2g\n",
    +               k, k, newhigh, newlow);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    scale= qh_divzero(newhigh - newlow, high - low,
    +                  qh MINdenom_1, &nearzero);
    +    if (nearzero) {
    +      qh_fprintf(qh ferr, 6022, "qhull input error: %d'th dimension's new bounds [%2.2g, %2.2g] too wide for\nexisting bounds [%2.2g, %2.2g]\n",
    +              k, newlow, newhigh, low, high);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    shift= (newlow * high - low * newhigh)/(high-low);
    +    coord= points+k;
    +    for (i=numpoints; i--; coord += dim)
    +      *coord= *coord * scale + shift;
    +    coord= points+k;
    +    if (newlow < newhigh) {
    +      mincoord= newlow;
    +      maxcoord= newhigh;
    +    }else {
    +      mincoord= newhigh;
    +      maxcoord= newlow;
    +    }
    +    for (i=numpoints; i--; coord += dim) {
    +      minimize_(*coord, maxcoord);  /* because of roundoff error */
    +      maximize_(*coord, mincoord);
    +    }
    +    trace0((qh ferr, 10, "qh_scalepoints: scaled %d'th coordinate [%2.2g, %2.2g] to [%.2g, %.2g] for %d points by %2.2g and shifted %2.2g\n",
    +      k, low, high, newlow, newhigh, numpoints, scale, shift));
    +  }
    +} /* scalepoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelaunay( dim, count, points )
    +    project count points to dim-d paraboloid for Delaunay triangulation
    +
    +    dim is one more than the dimension of the input set
    +    assumes dim is at least 3 (i.e., at least a 2-d Delaunay triangulation)
    +
    +    points is a dim*count realT array.  The first dim-1 coordinates
    +    are the coordinates of the first input point.  array[dim] is
    +    the first coordinate of the second input point.  array[2*dim] is
    +    the first coordinate of the third input point.
    +
    +    if qh.last_low defined (i.e., 'Qbb' called qh_scalelast)
    +      calls qh_scalelast to scale the last coordinate the same as the other points
    +
    +  returns:
    +    for each point
    +      sets point[dim-1] to sum of squares of coordinates
    +    scale points to 'Qbb' if needed
    +
    +  notes:
    +    to project one point, use
    +      qh_setdelaunay(qh hull_dim, 1, point)
    +
    +    Do not use options 'Qbk', 'QBk', or 'QbB' since they scale
    +    the coordinates after the original projection.
    +
    +*/
    +void qh_setdelaunay(int dim, int count, pointT *points) {
    +  int i, k;
    +  coordT *coordp, coord;
    +  realT paraboloid;
    +
    +  trace0((qh ferr, 11, "qh_setdelaunay: project %d points to paraboloid for Delaunay triangulation\n", count));
    +  coordp= points;
    +  for (i=0; i < count; i++) {
    +    coord= *coordp++;
    +    paraboloid= coord*coord;
    +    for (k=dim-2; k--; ) {
    +      coord= *coordp++;
    +      paraboloid += coord*coord;
    +    }
    +    *coordp++ = paraboloid;
    +  }
    +  if (qh last_low < REALmax/2)
    +    qh_scalelast(points, count, dim, qh last_low, qh last_high, qh last_newhigh);
    +} /* setdelaunay */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethalfspace( dim, coords, nextp, normal, offset, feasible )
    +    set point to dual of halfspace relative to feasible point
    +    halfspace is normal coefficients and offset.
    +
    +  returns:
    +    false and prints error if feasible point is outside of hull
    +    overwrites coordinates for point at dim coords
    +    nextp= next point (coords)
    +    does not call qh_errexit
    +
    +  design:
    +    compute distance from feasible point to halfspace
    +    divide each normal coefficient by -dist
    +*/
    +boolT qh_sethalfspace(int dim, coordT *coords, coordT **nextp,
    +         coordT *normal, coordT *offset, coordT *feasible) {
    +  coordT *normp= normal, *feasiblep= feasible, *coordp= coords;
    +  realT dist;
    +  realT r; /*bug fix*/
    +  int k;
    +  boolT zerodiv;
    +
    +  dist= *offset;
    +  for (k=dim; k--; )
    +    dist += *(normp++) * *(feasiblep++);
    +  if (dist > 0)
    +    goto LABELerroroutside;
    +  normp= normal;
    +  if (dist < -qh MINdenom) {
    +    for (k=dim; k--; )
    +      *(coordp++)= *(normp++) / -dist;
    +  }else {
    +    for (k=dim; k--; ) {
    +      *(coordp++)= qh_divzero(*(normp++), -dist, qh MINdenom_1, &zerodiv);
    +      if (zerodiv)
    +        goto LABELerroroutside;
    +    }
    +  }
    +  *nextp= coordp;
    +  if (qh IStracing >= 4) {
    +    qh_fprintf(qh ferr, 8021, "qh_sethalfspace: halfspace at offset %6.2g to point: ", *offset);
    +    for (k=dim, coordp=coords; k--; ) {
    +      r= *coordp++;
    +      qh_fprintf(qh ferr, 8022, " %6.2g", r);
    +    }
    +    qh_fprintf(qh ferr, 8023, "\n");
    +  }
    +  return True;
    +LABELerroroutside:
    +  feasiblep= feasible;
    +  normp= normal;
    +  qh_fprintf(qh ferr, 6023, "qhull input error: feasible point is not clearly inside halfspace\nfeasible point: ");
    +  for (k=dim; k--; )
    +    qh_fprintf(qh ferr, 8024, qh_REAL_1, r=*(feasiblep++));
    +  qh_fprintf(qh ferr, 8025, "\n     halfspace: ");
    +  for (k=dim; k--; )
    +    qh_fprintf(qh ferr, 8026, qh_REAL_1, r=*(normp++));
    +  qh_fprintf(qh ferr, 8027, "\n     at offset: ");
    +  qh_fprintf(qh ferr, 8028, qh_REAL_1, *offset);
    +  qh_fprintf(qh ferr, 8029, " and distance: ");
    +  qh_fprintf(qh ferr, 8030, qh_REAL_1, dist);
    +  qh_fprintf(qh ferr, 8031, "\n");
    +  return False;
    +} /* sethalfspace */
    +
    +/*---------------------------------
    +
    +  qh_sethalfspace_all( dim, count, halfspaces, feasible )
    +    generate dual for halfspace intersection with feasible point
    +    array of count halfspaces
    +      each halfspace is normal coefficients followed by offset
    +      the origin is inside the halfspace if the offset is negative
    +    feasible is a point inside all halfspaces (http://www.qhull.org/html/qhalf.htm#notes)
    +
    +  returns:
    +    malloc'd array of count X dim-1 points
    +
    +  notes:
    +    call before qh_init_B or qh_initqhull_globals
    +    free memory when done
    +    unused/untested code: please email bradb@shore.net if this works ok for you
    +    if using option 'Fp', qh->feasible_point must be set (e.g., to 'feasible')
    +    qh->feasible_point is a malloc'd array that is freed by qh_freebuffers.
    +
    +  design:
    +    see qh_sethalfspace
    +*/
    +coordT *qh_sethalfspace_all(int dim, int count, coordT *halfspaces, pointT *feasible) {
    +  int i, newdim;
    +  pointT *newpoints;
    +  coordT *coordp, *normalp, *offsetp;
    +
    +  trace0((qh ferr, 12, "qh_sethalfspace_all: compute dual for halfspace intersection\n"));
    +  newdim= dim - 1;
    +  if (!(newpoints=(coordT*)qh_malloc(count*newdim*sizeof(coordT)))){
    +    qh_fprintf(qh ferr, 6024, "qhull error: insufficient memory to compute dual of %d halfspaces\n",
    +          count);
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  coordp= newpoints;
    +  normalp= halfspaces;
    +  for (i=0; i < count; i++) {
    +    offsetp= normalp + newdim;
    +    if (!qh_sethalfspace(newdim, coordp, &coordp, normalp, offsetp, feasible)) {
    +      qh_free(newpoints);  /* feasible is not inside halfspace as reported by qh_sethalfspace */
    +      qh_fprintf(qh ferr, 8032, "The halfspace was at index %d\n", i);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    normalp= offsetp + 1;
    +  }
    +  return newpoints;
    +} /* sethalfspace_all */
    +
    +
    +/*---------------------------------
    +
    +  qh_sharpnewfacets()
    +
    +  returns:
    +    true if could be an acute angle (facets in different quadrants)
    +
    +  notes:
    +    for qh_findbest
    +
    +  design:
    +    for all facets on qh.newfacet_list
    +      if two facets are in different quadrants
    +        set issharp
    +*/
    +boolT qh_sharpnewfacets(void) {
    +  facetT *facet;
    +  boolT issharp = False;
    +  int *quadrant, k;
    +
    +  quadrant= (int*)qh_memalloc(qh hull_dim * sizeof(int));
    +  FORALLfacet_(qh newfacet_list) {
    +    if (facet == qh newfacet_list) {
    +      for (k=qh hull_dim; k--; )
    +        quadrant[ k]= (facet->normal[ k] > 0);
    +    }else {
    +      for (k=qh hull_dim; k--; ) {
    +        if (quadrant[ k] != (facet->normal[ k] > 0)) {
    +          issharp= True;
    +          break;
    +        }
    +      }
    +    }
    +    if (issharp)
    +      break;
    +  }
    +  qh_memfree( quadrant, qh hull_dim * sizeof(int));
    +  trace3((qh ferr, 3001, "qh_sharpnewfacets: %d\n", issharp));
    +  return issharp;
    +} /* sharpnewfacets */
    +
    +/*---------------------------------
    +
    +  qh_voronoi_center( dim, points )
    +    return Voronoi center for a set of points
    +    dim is the orginal dimension of the points
    +    gh.gm_matrix/qh.gm_row are scratch buffers
    +
    +  returns:
    +    center as a temporary point (qh_memalloc)
    +    if non-simplicial,
    +      returns center for max simplex of points
    +
    +  notes:
    +    only called by qh_facetcenter
    +    from Bowyer & Woodwark, A Programmer's Geometry, 1983, p. 65
    +
    +  design:
    +    if non-simplicial
    +      determine max simplex for points
    +    translate point0 of simplex to origin
    +    compute sum of squares of diagonal
    +    compute determinate
    +    compute Voronoi center (see Bowyer & Woodwark)
    +*/
    +pointT *qh_voronoi_center(int dim, setT *points) {
    +  pointT *point, **pointp, *point0;
    +  pointT *center= (pointT*)qh_memalloc(qh center_size);
    +  setT *simplex;
    +  int i, j, k, size= qh_setsize(points);
    +  coordT *gmcoord;
    +  realT *diffp, sum2, *sum2row, *sum2p, det, factor;
    +  boolT nearzero, infinite;
    +
    +  if (size == dim+1)
    +    simplex= points;
    +  else if (size < dim+1) {
    +    qh_memfree(center, qh center_size);
    +    qh_fprintf(qh ferr, 6025, "qhull internal error (qh_voronoi_center):\n  need at least %d points to construct a Voronoi center\n",
    +             dim+1);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +    simplex= points;  /* never executed -- avoids warning */
    +  }else {
    +    simplex= qh_settemp(dim+1);
    +    qh_maxsimplex(dim, points, NULL, 0, &simplex);
    +  }
    +  point0= SETfirstt_(simplex, pointT);
    +  gmcoord= qh gm_matrix;
    +  for (k=0; k < dim; k++) {
    +    qh gm_row[k]= gmcoord;
    +    FOREACHpoint_(simplex) {
    +      if (point != point0)
    +        *(gmcoord++)= point[k] - point0[k];
    +    }
    +  }
    +  sum2row= gmcoord;
    +  for (i=0; i < dim; i++) {
    +    sum2= 0.0;
    +    for (k=0; k < dim; k++) {
    +      diffp= qh gm_row[k] + i;
    +      sum2 += *diffp * *diffp;
    +    }
    +    *(gmcoord++)= sum2;
    +  }
    +  det= qh_determinant(qh gm_row, dim, &nearzero);
    +  factor= qh_divzero(0.5, det, qh MINdenom, &infinite);
    +  if (infinite) {
    +    for (k=dim; k--; )
    +      center[k]= qh_INFINITE;
    +    if (qh IStracing)
    +      qh_printpoints(qh ferr, "qh_voronoi_center: at infinity for ", simplex);
    +  }else {
    +    for (i=0; i < dim; i++) {
    +      gmcoord= qh gm_matrix;
    +      sum2p= sum2row;
    +      for (k=0; k < dim; k++) {
    +        qh gm_row[k]= gmcoord;
    +        if (k == i) {
    +          for (j=dim; j--; )
    +            *(gmcoord++)= *sum2p++;
    +        }else {
    +          FOREACHpoint_(simplex) {
    +            if (point != point0)
    +              *(gmcoord++)= point[k] - point0[k];
    +          }
    +        }
    +      }
    +      center[i]= qh_determinant(qh gm_row, dim, &nearzero)*factor + point0[i];
    +    }
    +#ifndef qh_NOtrace
    +    if (qh IStracing >= 3) {
    +      qh_fprintf(qh ferr, 8033, "qh_voronoi_center: det %2.2g factor %2.2g ", det, factor);
    +      qh_printmatrix(qh ferr, "center:", ¢er, 1, dim);
    +      if (qh IStracing >= 5) {
    +        qh_printpoints(qh ferr, "points", simplex);
    +        FOREACHpoint_(simplex)
    +          qh_fprintf(qh ferr, 8034, "p%d dist %.2g, ", qh_pointid(point),
    +                   qh_pointdist(point, center, dim));
    +        qh_fprintf(qh ferr, 8035, "\n");
    +      }
    +    }
    +#endif
    +  }
    +  if (simplex != points)
    +    qh_settempfree(&simplex);
    +  return center;
    +} /* voronoi_center */
    +
    diff --git a/xs/src/qhull/src/libqhull/global.c b/xs/src/qhull/src/libqhull/global.c
    new file mode 100644
    index 0000000000..0328fea7b9
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/global.c
    @@ -0,0 +1,2217 @@
    +
    +/*
      ---------------------------------
    +
    +   global.c
    +   initializes all the globals of the qhull application
    +
    +   see README
    +
    +   see libqhull.h for qh.globals and function prototypes
    +
    +   see qhull_a.h for internal functions
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/global.c#17 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    + */
    +
    +#include "qhull_a.h"
    +
    +/*========= qh definition -- globals defined in libqhull.h =======================*/
    +
    +#if qh_QHpointer
    +qhT *qh_qh= NULL;       /* pointer to all global variables */
    +#else
    +qhT qh_qh;              /* all global variables.
    +                           Add "= {0}" if this causes a compiler error.
    +                           Also qh_qhstat in stat.c and qhmem in mem.c.  */
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_version
    +    version string by year and date
    +    qh_version2 for Unix users and -V
    +
    +    the revision increases on code changes only
    +
    +  notes:
    +    change date:    Changes.txt, Announce.txt, index.htm, README.txt,
    +                    qhull-news.html, Eudora signatures, CMakeLists.txt
    +    change version: README.txt, qh-get.htm, File_id.diz, Makefile.txt, CMakeLists.txt
    +    check that CmakeLists @version is the same as qh_version2
    +    change year:    Copying.txt
    +    check download size
    +    recompile user_eg.c, rbox.c, libqhull.c, qconvex.c, qdelaun.c qvoronoi.c, qhalf.c, testqset.c
    +*/
    +
    +const char qh_version[]= "2015.2 2016/01/18";
    +const char qh_version2[]= "qhull 7.2.0 (2015.2 2016/01/18)";
    +
    +/*---------------------------------
    +
    +  qh_appendprint( printFormat )
    +    append printFormat to qh.PRINTout unless already defined
    +*/
    +void qh_appendprint(qh_PRINT format) {
    +  int i;
    +
    +  for (i=0; i < qh_PRINTEND; i++) {
    +    if (qh PRINTout[i] == format && format != qh_PRINTqhull)
    +      break;
    +    if (!qh PRINTout[i]) {
    +      qh PRINTout[i]= format;
    +      break;
    +    }
    +  }
    +} /* appendprint */
    +
    +/*---------------------------------
    +
    +  qh_checkflags( commandStr, hiddenFlags )
    +    errors if commandStr contains hiddenFlags
    +    hiddenFlags starts and ends with a space and is space delimited (checked)
    +
    +  notes:
    +    ignores first word (e.g., "qconvex i")
    +    use qh_strtol/strtod since strtol/strtod may or may not skip trailing spaces
    +
    +  see:
    +    qh_initflags() initializes Qhull according to commandStr
    +*/
    +void qh_checkflags(char *command, char *hiddenflags) {
    +  char *s= command, *t, *chkerr; /* qh_skipfilename is non-const */
    +  char key, opt, prevopt;
    +  char chkkey[]= "   ";
    +  char chkopt[]=  "    ";
    +  char chkopt2[]= "     ";
    +  boolT waserr= False;
    +
    +  if (*hiddenflags != ' ' || hiddenflags[strlen(hiddenflags)-1] != ' ') {
    +    qh_fprintf(qh ferr, 6026, "qhull error (qh_checkflags): hiddenflags must start and end with a space: \"%s\"", hiddenflags);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (strpbrk(hiddenflags, ",\n\r\t")) {
    +    qh_fprintf(qh ferr, 6027, "qhull error (qh_checkflags): hiddenflags contains commas, newlines, or tabs: \"%s\"", hiddenflags);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  while (*s && !isspace(*s))  /* skip program name */
    +    s++;
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    key = *s++;
    +    chkerr = NULL;
    +    if (key == 'T' && (*s == 'I' || *s == 'O')) {  /* TI or TO 'file name' */
    +      s= qh_skipfilename(++s);
    +      continue;
    +    }
    +    chkkey[1]= key;
    +    if (strstr(hiddenflags, chkkey)) {
    +      chkerr= chkkey;
    +    }else if (isupper(key)) {
    +      opt= ' ';
    +      prevopt= ' ';
    +      chkopt[1]= key;
    +      chkopt2[1]= key;
    +      while (!chkerr && *s && !isspace(*s)) {
    +        opt= *s++;
    +        if (isalpha(opt)) {
    +          chkopt[2]= opt;
    +          if (strstr(hiddenflags, chkopt))
    +            chkerr= chkopt;
    +          if (prevopt != ' ') {
    +            chkopt2[2]= prevopt;
    +            chkopt2[3]= opt;
    +            if (strstr(hiddenflags, chkopt2))
    +              chkerr= chkopt2;
    +          }
    +        }else if (key == 'Q' && isdigit(opt) && prevopt != 'b'
    +              && (prevopt == ' ' || islower(prevopt))) {
    +            chkopt[2]= opt;
    +            if (strstr(hiddenflags, chkopt))
    +              chkerr= chkopt;
    +        }else {
    +          qh_strtod(s-1, &t);
    +          if (s < t)
    +            s= t;
    +        }
    +        prevopt= opt;
    +      }
    +    }
    +    if (chkerr) {
    +      *chkerr= '\'';
    +      chkerr[strlen(chkerr)-1]=  '\'';
    +      qh_fprintf(qh ferr, 6029, "qhull error: option %s is not used with this program.\n             It may be used with qhull.\n", chkerr);
    +      waserr= True;
    +    }
    +  }
    +  if (waserr)
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +} /* checkflags */
    +
    +/*---------------------------------
    +
    +  qh_clear_outputflags()
    +    Clear output flags for QhullPoints
    +*/
    +void qh_clear_outputflags(void) {
    +  int i,k;
    +
    +  qh ANNOTATEoutput= False;
    +  qh DOintersections= False;
    +  qh DROPdim= -1;
    +  qh FORCEoutput= False;
    +  qh GETarea= False;
    +  qh GOODpoint= 0;
    +  qh GOODpointp= NULL;
    +  qh GOODthreshold= False;
    +  qh GOODvertex= 0;
    +  qh GOODvertexp= NULL;
    +  qh IStracing= 0;
    +  qh KEEParea= False;
    +  qh KEEPmerge= False;
    +  qh KEEPminArea= REALmax;
    +  qh PRINTcentrums= False;
    +  qh PRINTcoplanar= False;
    +  qh PRINTdots= False;
    +  qh PRINTgood= False;
    +  qh PRINTinner= False;
    +  qh PRINTneighbors= False;
    +  qh PRINTnoplanes= False;
    +  qh PRINToptions1st= False;
    +  qh PRINTouter= False;
    +  qh PRINTprecision= True;
    +  qh PRINTridges= False;
    +  qh PRINTspheres= False;
    +  qh PRINTstatistics= False;
    +  qh PRINTsummary= False;
    +  qh PRINTtransparent= False;
    +  qh SPLITthresholds= False;
    +  qh TRACElevel= 0;
    +  qh TRInormals= False;
    +  qh USEstdout= False;
    +  qh VERIFYoutput= False;
    +  for (k=qh input_dim+1; k--; ) {  /* duplicated in qh_initqhull_buffers and qh_clear_outputflags */
    +    qh lower_threshold[k]= -REALmax;
    +    qh upper_threshold[k]= REALmax;
    +    qh lower_bound[k]= -REALmax;
    +    qh upper_bound[k]= REALmax;
    +  }
    +
    +  for (i=0; i < qh_PRINTEND; i++) {
    +    qh PRINTout[i]= qh_PRINTnone;
    +  }
    +
    +  if (!qh qhull_commandsiz2)
    +      qh qhull_commandsiz2= (int)strlen(qh qhull_command); /* WARN64 */
    +  else {
    +      qh qhull_command[qh qhull_commandsiz2]= '\0';
    +  }
    +  if (!qh qhull_optionsiz2)
    +    qh qhull_optionsiz2= (int)strlen(qh qhull_options);  /* WARN64 */
    +  else {
    +    qh qhull_options[qh qhull_optionsiz2]= '\0';
    +    qh qhull_optionlen= qh_OPTIONline;  /* start a new line */
    +  }
    +} /* clear_outputflags */
    +
    +/*---------------------------------
    +
    +  qh_clock()
    +    return user CPU time in 100ths (qh_SECtick)
    +    only defined for qh_CLOCKtype == 2
    +
    +  notes:
    +    use first value to determine time 0
    +    from Stevens '92 8.15
    +*/
    +unsigned long qh_clock(void) {
    +
    +#if (qh_CLOCKtype == 2)
    +  struct tms time;
    +  static long clktck;  /* initialized first call and never updated */
    +  double ratio, cpu;
    +  unsigned long ticks;
    +
    +  if (!clktck) {
    +    if ((clktck= sysconf(_SC_CLK_TCK)) < 0) {
    +      qh_fprintf(qh ferr, 6030, "qhull internal error (qh_clock): sysconf() failed.  Use qh_CLOCKtype 1 in user.h\n");
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +  }
    +  if (times(&time) == -1) {
    +    qh_fprintf(qh ferr, 6031, "qhull internal error (qh_clock): times() failed.  Use qh_CLOCKtype 1 in user.h\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  ratio= qh_SECticks / (double)clktck;
    +  ticks= time.tms_utime * ratio;
    +  return ticks;
    +#else
    +  qh_fprintf(qh ferr, 6032, "qhull internal error (qh_clock): use qh_CLOCKtype 2 in user.h\n");
    +  qh_errexit(qh_ERRqhull, NULL, NULL); /* never returns */
    +  return 0;
    +#endif
    +} /* clock */
    +
    +/*---------------------------------
    +
    +  qh_freebuffers()
    +    free up global memory buffers
    +
    +  notes:
    +    must match qh_initbuffers()
    +*/
    +void qh_freebuffers(void) {
    +
    +  trace5((qh ferr, 5001, "qh_freebuffers: freeing up global memory buffers\n"));
    +  /* allocated by qh_initqhull_buffers */
    +  qh_memfree(qh NEARzero, qh hull_dim * sizeof(realT));
    +  qh_memfree(qh lower_threshold, (qh input_dim+1) * sizeof(realT));
    +  qh_memfree(qh upper_threshold, (qh input_dim+1) * sizeof(realT));
    +  qh_memfree(qh lower_bound, (qh input_dim+1) * sizeof(realT));
    +  qh_memfree(qh upper_bound, (qh input_dim+1) * sizeof(realT));
    +  qh_memfree(qh gm_matrix, (qh hull_dim+1) * qh hull_dim * sizeof(coordT));
    +  qh_memfree(qh gm_row, (qh hull_dim+1) * sizeof(coordT *));
    +  qh NEARzero= qh lower_threshold= qh upper_threshold= NULL;
    +  qh lower_bound= qh upper_bound= NULL;
    +  qh gm_matrix= NULL;
    +  qh gm_row= NULL;
    +  qh_setfree(&qh other_points);
    +  qh_setfree(&qh del_vertices);
    +  qh_setfree(&qh coplanarfacetset);
    +  if (qh line)                /* allocated by qh_readinput, freed if no error */
    +    qh_free(qh line);
    +  if (qh half_space)
    +    qh_free(qh half_space);
    +  if (qh temp_malloc)
    +    qh_free(qh temp_malloc);
    +  if (qh feasible_point)      /* allocated by qh_readfeasible */
    +    qh_free(qh feasible_point);
    +  if (qh feasible_string)     /* allocated by qh_initflags */
    +    qh_free(qh feasible_string);
    +  qh line= qh feasible_string= NULL;
    +  qh half_space= qh feasible_point= qh temp_malloc= NULL;
    +  /* usually allocated by qh_readinput */
    +  if (qh first_point && qh POINTSmalloc) {
    +    qh_free(qh first_point);
    +    qh first_point= NULL;
    +  }
    +  if (qh input_points && qh input_malloc) { /* set by qh_joggleinput */
    +    qh_free(qh input_points);
    +    qh input_points= NULL;
    +  }
    +  trace5((qh ferr, 5002, "qh_freebuffers: finished\n"));
    +} /* freebuffers */
    +
    +
    +/*---------------------------------
    +
    +  qh_freebuild( allmem )
    +    free global memory used by qh_initbuild and qh_buildhull
    +    if !allmem,
    +      does not free short memory (e.g., facetT, freed by qh_memfreeshort)
    +
    +  design:
    +    free centrums
    +    free each vertex
    +    mark unattached ridges
    +    for each facet
    +      free ridges
    +      free outside set, coplanar set, neighbor set, ridge set, vertex set
    +      free facet
    +    free hash table
    +    free interior point
    +    free merge set
    +    free temporary sets
    +*/
    +void qh_freebuild(boolT allmem) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  ridgeT *ridge, **ridgep;
    +  mergeT *merge, **mergep;
    +
    +  trace1((qh ferr, 1005, "qh_freebuild: free memory from qh_inithull and qh_buildhull\n"));
    +  if (qh del_vertices)
    +    qh_settruncate(qh del_vertices, 0);
    +  if (allmem) {
    +    while ((vertex= qh vertex_list)) {
    +      if (vertex->next)
    +        qh_delvertex(vertex);
    +      else {
    +        qh_memfree(vertex, (int)sizeof(vertexT));
    +        qh newvertex_list= qh vertex_list= NULL;
    +      }
    +    }
    +  }else if (qh VERTEXneighbors) {
    +    FORALLvertices
    +      qh_setfreelong(&(vertex->neighbors));
    +  }
    +  qh VERTEXneighbors= False;
    +  qh GOODclosest= NULL;
    +  if (allmem) {
    +    FORALLfacets {
    +      FOREACHridge_(facet->ridges)
    +        ridge->seen= False;
    +    }
    +    FORALLfacets {
    +      if (facet->visible) {
    +        FOREACHridge_(facet->ridges) {
    +          if (!otherfacet_(ridge, facet)->visible)
    +            ridge->seen= True;  /* an unattached ridge */
    +        }
    +      }
    +    }
    +    while ((facet= qh facet_list)) {
    +      FOREACHridge_(facet->ridges) {
    +        if (ridge->seen) {
    +          qh_setfree(&(ridge->vertices));
    +          qh_memfree(ridge, (int)sizeof(ridgeT));
    +        }else
    +          ridge->seen= True;
    +      }
    +      qh_setfree(&(facet->outsideset));
    +      qh_setfree(&(facet->coplanarset));
    +      qh_setfree(&(facet->neighbors));
    +      qh_setfree(&(facet->ridges));
    +      qh_setfree(&(facet->vertices));
    +      if (facet->next)
    +        qh_delfacet(facet);
    +      else {
    +        qh_memfree(facet, (int)sizeof(facetT));
    +        qh visible_list= qh newfacet_list= qh facet_list= NULL;
    +      }
    +    }
    +  }else {
    +    FORALLfacets {
    +      qh_setfreelong(&(facet->outsideset));
    +      qh_setfreelong(&(facet->coplanarset));
    +      if (!facet->simplicial) {
    +        qh_setfreelong(&(facet->neighbors));
    +        qh_setfreelong(&(facet->ridges));
    +        qh_setfreelong(&(facet->vertices));
    +      }
    +    }
    +  }
    +  qh_setfree(&(qh hash_table));
    +  qh_memfree(qh interior_point, qh normal_size);
    +  qh interior_point= NULL;
    +  FOREACHmerge_(qh facet_mergeset)  /* usually empty */
    +    qh_memfree(merge, (int)sizeof(mergeT));
    +  qh facet_mergeset= NULL;  /* temp set */
    +  qh degen_mergeset= NULL;  /* temp set */
    +  qh_settempfree_all();
    +} /* freebuild */
    +
    +/*---------------------------------
    +
    +  qh_freeqhull( allmem )
    +    see qh_freeqhull2
    +    if qh_QHpointer, frees qh_qh
    +*/
    +void qh_freeqhull(boolT allmem) {
    +    qh_freeqhull2(allmem);
    +#if qh_QHpointer
    +    qh_free(qh_qh);
    +    qh_qh= NULL;
    +#endif
    +}
    +
    +/*---------------------------------
    +
    +qh_freeqhull2( allmem )
    +  free global memory and set qhT to 0
    +  if !allmem,
    +    does not free short memory (freed by qh_memfreeshort unless qh_NOmem)
    +
    +notes:
    +  sets qh.NOerrexit in case caller forgets to
    +  Does not throw errors
    +
    +see:
    +  see qh_initqhull_start2()
    +
    +design:
    +  free global and temporary memory from qh_initbuild and qh_buildhull
    +  free buffers
    +  free statistics
    +*/
    +void qh_freeqhull2(boolT allmem) {
    +
    +  qh NOerrexit= True;  /* no more setjmp since called at exit and ~QhullQh */
    +  trace1((qh ferr, 1006, "qh_freeqhull: free global memory\n"));
    +  qh_freebuild(allmem);
    +  qh_freebuffers();
    +  qh_freestatistics();
    +#if qh_QHpointer
    +  memset((char *)qh_qh, 0, sizeof(qhT));
    +  /* qh_qh freed by caller, qh_freeqhull() */
    +#else
    +  memset((char *)&qh_qh, 0, sizeof(qhT));
    +#endif
    +  qh NOerrexit= True;
    +} /* freeqhull2 */
    +
    +/*---------------------------------
    +
    +  qh_init_A( infile, outfile, errfile, argc, argv )
    +    initialize memory and stdio files
    +    convert input options to option string (qh.qhull_command)
    +
    +  notes:
    +    infile may be NULL if qh_readpoints() is not called
    +
    +    errfile should always be defined.  It is used for reporting
    +    errors.  outfile is used for output and format options.
    +
    +    argc/argv may be 0/NULL
    +
    +    called before error handling initialized
    +    qh_errexit() may not be used
    +*/
    +void qh_init_A(FILE *infile, FILE *outfile, FILE *errfile, int argc, char *argv[]) {
    +  qh_meminit(errfile);
    +  qh_initqhull_start(infile, outfile, errfile);
    +  qh_init_qhull_command(argc, argv);
    +} /* init_A */
    +
    +/*---------------------------------
    +
    +  qh_init_B( points, numpoints, dim, ismalloc )
    +    initialize globals for points array
    +
    +    points has numpoints dim-dimensional points
    +      points[0] is the first coordinate of the first point
    +      points[1] is the second coordinate of the first point
    +      points[dim] is the first coordinate of the second point
    +
    +    ismalloc=True
    +      Qhull will call qh_free(points) on exit or input transformation
    +    ismalloc=False
    +      Qhull will allocate a new point array if needed for input transformation
    +
    +    qh.qhull_command
    +      is the option string.
    +      It is defined by qh_init_B(), qh_qhull_command(), or qh_initflags
    +
    +  returns:
    +    if qh.PROJECTinput or (qh.DELAUNAY and qh.PROJECTdelaunay)
    +      projects the input to a new point array
    +
    +        if qh.DELAUNAY,
    +          qh.hull_dim is increased by one
    +        if qh.ATinfinity,
    +          qh_projectinput adds point-at-infinity for Delaunay tri.
    +
    +    if qh.SCALEinput
    +      changes the upper and lower bounds of the input, see qh_scaleinput()
    +
    +    if qh.ROTATEinput
    +      rotates the input by a random rotation, see qh_rotateinput()
    +      if qh.DELAUNAY
    +        rotates about the last coordinate
    +
    +  notes:
    +    called after points are defined
    +    qh_errexit() may be used
    +*/
    +void qh_init_B(coordT *points, int numpoints, int dim, boolT ismalloc) {
    +  qh_initqhull_globals(points, numpoints, dim, ismalloc);
    +  if (qhmem.LASTsize == 0)
    +    qh_initqhull_mem();
    +  /* mem.c and qset.c are initialized */
    +  qh_initqhull_buffers();
    +  qh_initthresholds(qh qhull_command);
    +  if (qh PROJECTinput || (qh DELAUNAY && qh PROJECTdelaunay))
    +    qh_projectinput();
    +  if (qh SCALEinput)
    +    qh_scaleinput();
    +  if (qh ROTATErandom >= 0) {
    +    qh_randommatrix(qh gm_matrix, qh hull_dim, qh gm_row);
    +    if (qh DELAUNAY) {
    +      int k, lastk= qh hull_dim-1;
    +      for (k=0; k < lastk; k++) {
    +        qh gm_row[k][lastk]= 0.0;
    +        qh gm_row[lastk][k]= 0.0;
    +      }
    +      qh gm_row[lastk][lastk]= 1.0;
    +    }
    +    qh_gram_schmidt(qh hull_dim, qh gm_row);
    +    qh_rotateinput(qh gm_row);
    +  }
    +} /* init_B */
    +
    +/*---------------------------------
    +
    +  qh_init_qhull_command( argc, argv )
    +    build qh.qhull_command from argc/argv
    +    Calls qh_exit if qhull_command is too short
    +
    +  returns:
    +    a space-delimited string of options (just as typed)
    +
    +  notes:
    +    makes option string easy to input and output
    +
    +    argc/argv may be 0/NULL
    +*/
    +void qh_init_qhull_command(int argc, char *argv[]) {
    +
    +  if (!qh_argv_to_command(argc, argv, qh qhull_command, (int)sizeof(qh qhull_command))){
    +    /* Assumes qh.ferr is defined. */
    +    qh_fprintf(qh ferr, 6033, "qhull input error: more than %d characters in command line\n",
    +          (int)sizeof(qh qhull_command));
    +    qh_exit(qh_ERRinput);  /* error reported, can not use qh_errexit */
    +  }
    +} /* init_qhull_command */
    +
    +/*---------------------------------
    +
    +  qh_initflags( commandStr )
    +    set flags and initialized constants from commandStr
    +    calls qh_exit() if qh->NOerrexit
    +
    +  returns:
    +    sets qh.qhull_command to command if needed
    +
    +  notes:
    +    ignores first word (e.g., "qhull d")
    +    use qh_strtol/strtod since strtol/strtod may or may not skip trailing spaces
    +
    +  see:
    +    qh_initthresholds() continues processing of 'Pdn' and 'PDn'
    +    'prompt' in unix.c for documentation
    +
    +  design:
    +    for each space-delimited option group
    +      if top-level option
    +        check syntax
    +        append appropriate option to option string
    +        set appropriate global variable or append printFormat to print options
    +      else
    +        for each sub-option
    +          check syntax
    +          append appropriate option to option string
    +          set appropriate global variable or append printFormat to print options
    +*/
    +void qh_initflags(char *command) {
    +  int k, i, lastproject;
    +  char *s= command, *t, *prev_s, *start, key;
    +  boolT isgeom= False, wasproject;
    +  realT r;
    +
    +  if(qh NOerrexit){/* without this comment, segfault in gcc 4.4.0 mingw32 */
    +    qh_fprintf(qh ferr, 6245, "qhull initflags error: qh.NOerrexit was not cleared before calling qh_initflags().  It should be cleared after setjmp().  Exit qhull.");
    +    qh_exit(6245);
    +  }
    +  if (command <= &qh qhull_command[0] || command > &qh qhull_command[0] + sizeof(qh qhull_command)) {
    +    if (command != &qh qhull_command[0]) {
    +      *qh qhull_command= '\0';
    +      strncat(qh qhull_command, command, sizeof(qh qhull_command)-strlen(qh qhull_command)-1);
    +    }
    +    while (*s && !isspace(*s))  /* skip program name */
    +      s++;
    +  }
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    prev_s= s;
    +    switch (*s++) {
    +    case 'd':
    +      qh_option("delaunay", NULL, NULL);
    +      qh DELAUNAY= True;
    +      break;
    +    case 'f':
    +      qh_option("facets", NULL, NULL);
    +      qh_appendprint(qh_PRINTfacets);
    +      break;
    +    case 'i':
    +      qh_option("incidence", NULL, NULL);
    +      qh_appendprint(qh_PRINTincidences);
    +      break;
    +    case 'm':
    +      qh_option("mathematica", NULL, NULL);
    +      qh_appendprint(qh_PRINTmathematica);
    +      break;
    +    case 'n':
    +      qh_option("normals", NULL, NULL);
    +      qh_appendprint(qh_PRINTnormals);
    +      break;
    +    case 'o':
    +      qh_option("offFile", NULL, NULL);
    +      qh_appendprint(qh_PRINToff);
    +      break;
    +    case 'p':
    +      qh_option("points", NULL, NULL);
    +      qh_appendprint(qh_PRINTpoints);
    +      break;
    +    case 's':
    +      qh_option("summary", NULL, NULL);
    +      qh PRINTsummary= True;
    +      break;
    +    case 'v':
    +      qh_option("voronoi", NULL, NULL);
    +      qh VORONOI= True;
    +      qh DELAUNAY= True;
    +      break;
    +    case 'A':
    +      if (!isdigit(*s) && *s != '.' && *s != '-')
    +        qh_fprintf(qh ferr, 7002, "qhull warning: no maximum cosine angle given for option 'An'.  Ignored.\n");
    +      else {
    +        if (*s == '-') {
    +          qh premerge_cos= -qh_strtod(s, &s);
    +          qh_option("Angle-premerge-", NULL, &qh premerge_cos);
    +          qh PREmerge= True;
    +        }else {
    +          qh postmerge_cos= qh_strtod(s, &s);
    +          qh_option("Angle-postmerge", NULL, &qh postmerge_cos);
    +          qh POSTmerge= True;
    +        }
    +        qh MERGING= True;
    +      }
    +      break;
    +    case 'C':
    +      if (!isdigit(*s) && *s != '.' && *s != '-')
    +        qh_fprintf(qh ferr, 7003, "qhull warning: no centrum radius given for option 'Cn'.  Ignored.\n");
    +      else {
    +        if (*s == '-') {
    +          qh premerge_centrum= -qh_strtod(s, &s);
    +          qh_option("Centrum-premerge-", NULL, &qh premerge_centrum);
    +          qh PREmerge= True;
    +        }else {
    +          qh postmerge_centrum= qh_strtod(s, &s);
    +          qh_option("Centrum-postmerge", NULL, &qh postmerge_centrum);
    +          qh POSTmerge= True;
    +        }
    +        qh MERGING= True;
    +      }
    +      break;
    +    case 'E':
    +      if (*s == '-')
    +        qh_fprintf(qh ferr, 7004, "qhull warning: negative maximum roundoff given for option 'An'.  Ignored.\n");
    +      else if (!isdigit(*s))
    +        qh_fprintf(qh ferr, 7005, "qhull warning: no maximum roundoff given for option 'En'.  Ignored.\n");
    +      else {
    +        qh DISTround= qh_strtod(s, &s);
    +        qh_option("Distance-roundoff", NULL, &qh DISTround);
    +        qh SETroundoff= True;
    +      }
    +      break;
    +    case 'H':
    +      start= s;
    +      qh HALFspace= True;
    +      qh_strtod(s, &t);
    +      while (t > s)  {
    +        if (*t && !isspace(*t)) {
    +          if (*t == ',')
    +            t++;
    +          else
    +            qh_fprintf(qh ferr, 7006, "qhull warning: origin for Halfspace intersection should be 'Hn,n,n,...'\n");
    +        }
    +        s= t;
    +        qh_strtod(s, &t);
    +      }
    +      if (start < t) {
    +        if (!(qh feasible_string= (char*)calloc((size_t)(t-start+1), (size_t)1))) {
    +          qh_fprintf(qh ferr, 6034, "qhull error: insufficient memory for 'Hn,n,n'\n");
    +          qh_errexit(qh_ERRmem, NULL, NULL);
    +        }
    +        strncpy(qh feasible_string, start, (size_t)(t-start));
    +        qh_option("Halfspace-about", NULL, NULL);
    +        qh_option(qh feasible_string, NULL, NULL);
    +      }else
    +        qh_option("Halfspace", NULL, NULL);
    +      break;
    +    case 'R':
    +      if (!isdigit(*s))
    +        qh_fprintf(qh ferr, 7007, "qhull warning: missing random perturbation for option 'Rn'.  Ignored\n");
    +      else {
    +        qh RANDOMfactor= qh_strtod(s, &s);
    +        qh_option("Random_perturb", NULL, &qh RANDOMfactor);
    +        qh RANDOMdist= True;
    +      }
    +      break;
    +    case 'V':
    +      if (!isdigit(*s) && *s != '-')
    +        qh_fprintf(qh ferr, 7008, "qhull warning: missing visible distance for option 'Vn'.  Ignored\n");
    +      else {
    +        qh MINvisible= qh_strtod(s, &s);
    +        qh_option("Visible", NULL, &qh MINvisible);
    +      }
    +      break;
    +    case 'U':
    +      if (!isdigit(*s) && *s != '-')
    +        qh_fprintf(qh ferr, 7009, "qhull warning: missing coplanar distance for option 'Un'.  Ignored\n");
    +      else {
    +        qh MAXcoplanar= qh_strtod(s, &s);
    +        qh_option("U-coplanar", NULL, &qh MAXcoplanar);
    +      }
    +      break;
    +    case 'W':
    +      if (*s == '-')
    +        qh_fprintf(qh ferr, 7010, "qhull warning: negative outside width for option 'Wn'.  Ignored.\n");
    +      else if (!isdigit(*s))
    +        qh_fprintf(qh ferr, 7011, "qhull warning: missing outside width for option 'Wn'.  Ignored\n");
    +      else {
    +        qh MINoutside= qh_strtod(s, &s);
    +        qh_option("W-outside", NULL, &qh MINoutside);
    +        qh APPROXhull= True;
    +      }
    +      break;
    +    /************  sub menus ***************/
    +    case 'F':
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'a':
    +          qh_option("Farea", NULL, NULL);
    +          qh_appendprint(qh_PRINTarea);
    +          qh GETarea= True;
    +          break;
    +        case 'A':
    +          qh_option("FArea-total", NULL, NULL);
    +          qh GETarea= True;
    +          break;
    +        case 'c':
    +          qh_option("Fcoplanars", NULL, NULL);
    +          qh_appendprint(qh_PRINTcoplanars);
    +          break;
    +        case 'C':
    +          qh_option("FCentrums", NULL, NULL);
    +          qh_appendprint(qh_PRINTcentrums);
    +          break;
    +        case 'd':
    +          qh_option("Fd-cdd-in", NULL, NULL);
    +          qh CDDinput= True;
    +          break;
    +        case 'D':
    +          qh_option("FD-cdd-out", NULL, NULL);
    +          qh CDDoutput= True;
    +          break;
    +        case 'F':
    +          qh_option("FFacets-xridge", NULL, NULL);
    +          qh_appendprint(qh_PRINTfacets_xridge);
    +          break;
    +        case 'i':
    +          qh_option("Finner", NULL, NULL);
    +          qh_appendprint(qh_PRINTinner);
    +          break;
    +        case 'I':
    +          qh_option("FIDs", NULL, NULL);
    +          qh_appendprint(qh_PRINTids);
    +          break;
    +        case 'm':
    +          qh_option("Fmerges", NULL, NULL);
    +          qh_appendprint(qh_PRINTmerges);
    +          break;
    +        case 'M':
    +          qh_option("FMaple", NULL, NULL);
    +          qh_appendprint(qh_PRINTmaple);
    +          break;
    +        case 'n':
    +          qh_option("Fneighbors", NULL, NULL);
    +          qh_appendprint(qh_PRINTneighbors);
    +          break;
    +        case 'N':
    +          qh_option("FNeighbors-vertex", NULL, NULL);
    +          qh_appendprint(qh_PRINTvneighbors);
    +          break;
    +        case 'o':
    +          qh_option("Fouter", NULL, NULL);
    +          qh_appendprint(qh_PRINTouter);
    +          break;
    +        case 'O':
    +          if (qh PRINToptions1st) {
    +            qh_option("FOptions", NULL, NULL);
    +            qh_appendprint(qh_PRINToptions);
    +          }else
    +            qh PRINToptions1st= True;
    +          break;
    +        case 'p':
    +          qh_option("Fpoint-intersect", NULL, NULL);
    +          qh_appendprint(qh_PRINTpointintersect);
    +          break;
    +        case 'P':
    +          qh_option("FPoint-nearest", NULL, NULL);
    +          qh_appendprint(qh_PRINTpointnearest);
    +          break;
    +        case 'Q':
    +          qh_option("FQhull", NULL, NULL);
    +          qh_appendprint(qh_PRINTqhull);
    +          break;
    +        case 's':
    +          qh_option("Fsummary", NULL, NULL);
    +          qh_appendprint(qh_PRINTsummary);
    +          break;
    +        case 'S':
    +          qh_option("FSize", NULL, NULL);
    +          qh_appendprint(qh_PRINTsize);
    +          qh GETarea= True;
    +          break;
    +        case 't':
    +          qh_option("Ftriangles", NULL, NULL);
    +          qh_appendprint(qh_PRINTtriangles);
    +          break;
    +        case 'v':
    +          /* option set in qh_initqhull_globals */
    +          qh_appendprint(qh_PRINTvertices);
    +          break;
    +        case 'V':
    +          qh_option("FVertex-average", NULL, NULL);
    +          qh_appendprint(qh_PRINTaverage);
    +          break;
    +        case 'x':
    +          qh_option("Fxtremes", NULL, NULL);
    +          qh_appendprint(qh_PRINTextremes);
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7012, "qhull warning: unknown 'F' output option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'G':
    +      isgeom= True;
    +      qh_appendprint(qh_PRINTgeom);
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'a':
    +          qh_option("Gall-points", NULL, NULL);
    +          qh PRINTdots= True;
    +          break;
    +        case 'c':
    +          qh_option("Gcentrums", NULL, NULL);
    +          qh PRINTcentrums= True;
    +          break;
    +        case 'h':
    +          qh_option("Gintersections", NULL, NULL);
    +          qh DOintersections= True;
    +          break;
    +        case 'i':
    +          qh_option("Ginner", NULL, NULL);
    +          qh PRINTinner= True;
    +          break;
    +        case 'n':
    +          qh_option("Gno-planes", NULL, NULL);
    +          qh PRINTnoplanes= True;
    +          break;
    +        case 'o':
    +          qh_option("Gouter", NULL, NULL);
    +          qh PRINTouter= True;
    +          break;
    +        case 'p':
    +          qh_option("Gpoints", NULL, NULL);
    +          qh PRINTcoplanar= True;
    +          break;
    +        case 'r':
    +          qh_option("Gridges", NULL, NULL);
    +          qh PRINTridges= True;
    +          break;
    +        case 't':
    +          qh_option("Gtransparent", NULL, NULL);
    +          qh PRINTtransparent= True;
    +          break;
    +        case 'v':
    +          qh_option("Gvertices", NULL, NULL);
    +          qh PRINTspheres= True;
    +          break;
    +        case 'D':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6035, "qhull input error: missing dimension for option 'GDn'\n");
    +          else {
    +            if (qh DROPdim >= 0)
    +              qh_fprintf(qh ferr, 7013, "qhull warning: can only drop one dimension.  Previous 'GD%d' ignored\n",
    +                   qh DROPdim);
    +            qh DROPdim= qh_strtol(s, &s);
    +            qh_option("GDrop-dim", &qh DROPdim, NULL);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7014, "qhull warning: unknown 'G' print option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'P':
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'd': case 'D':  /* see qh_initthresholds() */
    +          key= s[-1];
    +          i= qh_strtol(s, &s);
    +          r= 0;
    +          if (*s == ':') {
    +            s++;
    +            r= qh_strtod(s, &s);
    +          }
    +          if (key == 'd')
    +            qh_option("Pdrop-facets-dim-less", &i, &r);
    +          else
    +            qh_option("PDrop-facets-dim-more", &i, &r);
    +          break;
    +        case 'g':
    +          qh_option("Pgood-facets", NULL, NULL);
    +          qh PRINTgood= True;
    +          break;
    +        case 'G':
    +          qh_option("PGood-facet-neighbors", NULL, NULL);
    +          qh PRINTneighbors= True;
    +          break;
    +        case 'o':
    +          qh_option("Poutput-forced", NULL, NULL);
    +          qh FORCEoutput= True;
    +          break;
    +        case 'p':
    +          qh_option("Pprecision-ignore", NULL, NULL);
    +          qh PRINTprecision= False;
    +          break;
    +        case 'A':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6036, "qhull input error: missing facet count for keep area option 'PAn'\n");
    +          else {
    +            qh KEEParea= qh_strtol(s, &s);
    +            qh_option("PArea-keep", &qh KEEParea, NULL);
    +            qh GETarea= True;
    +          }
    +          break;
    +        case 'F':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6037, "qhull input error: missing facet area for option 'PFn'\n");
    +          else {
    +            qh KEEPminArea= qh_strtod(s, &s);
    +            qh_option("PFacet-area-keep", NULL, &qh KEEPminArea);
    +            qh GETarea= True;
    +          }
    +          break;
    +        case 'M':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6038, "qhull input error: missing merge count for option 'PMn'\n");
    +          else {
    +            qh KEEPmerge= qh_strtol(s, &s);
    +            qh_option("PMerge-keep", &qh KEEPmerge, NULL);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7015, "qhull warning: unknown 'P' print option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'Q':
    +      lastproject= -1;
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'b': case 'B':  /* handled by qh_initthresholds */
    +          key= s[-1];
    +          if (key == 'b' && *s == 'B') {
    +            s++;
    +            r= qh_DEFAULTbox;
    +            qh SCALEinput= True;
    +            qh_option("QbBound-unit-box", NULL, &r);
    +            break;
    +          }
    +          if (key == 'b' && *s == 'b') {
    +            s++;
    +            qh SCALElast= True;
    +            qh_option("Qbbound-last", NULL, NULL);
    +            break;
    +          }
    +          k= qh_strtol(s, &s);
    +          r= 0.0;
    +          wasproject= False;
    +          if (*s == ':') {
    +            s++;
    +            if ((r= qh_strtod(s, &s)) == 0.0) {
    +              t= s;            /* need true dimension for memory allocation */
    +              while (*t && !isspace(*t)) {
    +                if (toupper(*t++) == 'B'
    +                 && k == qh_strtol(t, &t)
    +                 && *t++ == ':'
    +                 && qh_strtod(t, &t) == 0.0) {
    +                  qh PROJECTinput++;
    +                  trace2((qh ferr, 2004, "qh_initflags: project dimension %d\n", k));
    +                  qh_option("Qb-project-dim", &k, NULL);
    +                  wasproject= True;
    +                  lastproject= k;
    +                  break;
    +                }
    +              }
    +            }
    +          }
    +          if (!wasproject) {
    +            if (lastproject == k && r == 0.0)
    +              lastproject= -1;  /* doesn't catch all possible sequences */
    +            else if (key == 'b') {
    +              qh SCALEinput= True;
    +              if (r == 0.0)
    +                r= -qh_DEFAULTbox;
    +              qh_option("Qbound-dim-low", &k, &r);
    +            }else {
    +              qh SCALEinput= True;
    +              if (r == 0.0)
    +                r= qh_DEFAULTbox;
    +              qh_option("QBound-dim-high", &k, &r);
    +            }
    +          }
    +          break;
    +        case 'c':
    +          qh_option("Qcoplanar-keep", NULL, NULL);
    +          qh KEEPcoplanar= True;
    +          break;
    +        case 'f':
    +          qh_option("Qfurthest-outside", NULL, NULL);
    +          qh BESToutside= True;
    +          break;
    +        case 'g':
    +          qh_option("Qgood-facets-only", NULL, NULL);
    +          qh ONLYgood= True;
    +          break;
    +        case 'i':
    +          qh_option("Qinterior-keep", NULL, NULL);
    +          qh KEEPinside= True;
    +          break;
    +        case 'm':
    +          qh_option("Qmax-outside-only", NULL, NULL);
    +          qh ONLYmax= True;
    +          break;
    +        case 'r':
    +          qh_option("Qrandom-outside", NULL, NULL);
    +          qh RANDOMoutside= True;
    +          break;
    +        case 's':
    +          qh_option("Qsearch-initial-simplex", NULL, NULL);
    +          qh ALLpoints= True;
    +          break;
    +        case 't':
    +          qh_option("Qtriangulate", NULL, NULL);
    +          qh TRIangulate= True;
    +          break;
    +        case 'T':
    +          qh_option("QTestPoints", NULL, NULL);
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6039, "qhull input error: missing number of test points for option 'QTn'\n");
    +          else {
    +            qh TESTpoints= qh_strtol(s, &s);
    +            qh_option("QTestPoints", &qh TESTpoints, NULL);
    +          }
    +          break;
    +        case 'u':
    +          qh_option("QupperDelaunay", NULL, NULL);
    +          qh UPPERdelaunay= True;
    +          break;
    +        case 'v':
    +          qh_option("Qvertex-neighbors-convex", NULL, NULL);
    +          qh TESTvneighbors= True;
    +          break;
    +        case 'x':
    +          qh_option("Qxact-merge", NULL, NULL);
    +          qh MERGEexact= True;
    +          break;
    +        case 'z':
    +          qh_option("Qz-infinity-point", NULL, NULL);
    +          qh ATinfinity= True;
    +          break;
    +        case '0':
    +          qh_option("Q0-no-premerge", NULL, NULL);
    +          qh NOpremerge= True;
    +          break;
    +        case '1':
    +          if (!isdigit(*s)) {
    +            qh_option("Q1-no-angle-sort", NULL, NULL);
    +            qh ANGLEmerge= False;
    +            break;
    +          }
    +          switch (*s++) {
    +          case '0':
    +            qh_option("Q10-no-narrow", NULL, NULL);
    +            qh NOnarrow= True;
    +            break;
    +          case '1':
    +            qh_option("Q11-trinormals Qtriangulate", NULL, NULL);
    +            qh TRInormals= True;
    +            qh TRIangulate= True;
    +            break;
    +          case '2':
    +            qh_option("Q12-no-wide-dup", NULL, NULL);
    +            qh NOwide= True;
    +            break;
    +          default:
    +            s--;
    +            qh_fprintf(qh ferr, 7016, "qhull warning: unknown 'Q' qhull option 1%c, rest ignored\n", (int)s[0]);
    +            while (*++s && !isspace(*s));
    +            break;
    +          }
    +          break;
    +        case '2':
    +          qh_option("Q2-no-merge-independent", NULL, NULL);
    +          qh MERGEindependent= False;
    +          goto LABELcheckdigit;
    +          break; /* no warnings */
    +        case '3':
    +          qh_option("Q3-no-merge-vertices", NULL, NULL);
    +          qh MERGEvertices= False;
    +        LABELcheckdigit:
    +          if (isdigit(*s))
    +            qh_fprintf(qh ferr, 7017, "qhull warning: can not follow '1', '2', or '3' with a digit.  '%c' skipped.\n",
    +                     *s++);
    +          break;
    +        case '4':
    +          qh_option("Q4-avoid-old-into-new", NULL, NULL);
    +          qh AVOIDold= True;
    +          break;
    +        case '5':
    +          qh_option("Q5-no-check-outer", NULL, NULL);
    +          qh SKIPcheckmax= True;
    +          break;
    +        case '6':
    +          qh_option("Q6-no-concave-merge", NULL, NULL);
    +          qh SKIPconvex= True;
    +          break;
    +        case '7':
    +          qh_option("Q7-no-breadth-first", NULL, NULL);
    +          qh VIRTUALmemory= True;
    +          break;
    +        case '8':
    +          qh_option("Q8-no-near-inside", NULL, NULL);
    +          qh NOnearinside= True;
    +          break;
    +        case '9':
    +          qh_option("Q9-pick-furthest", NULL, NULL);
    +          qh PICKfurthest= True;
    +          break;
    +        case 'G':
    +          i= qh_strtol(s, &t);
    +          if (qh GOODpoint)
    +            qh_fprintf(qh ferr, 7018, "qhull warning: good point already defined for option 'QGn'.  Ignored\n");
    +          else if (s == t)
    +            qh_fprintf(qh ferr, 7019, "qhull warning: missing good point id for option 'QGn'.  Ignored\n");
    +          else if (i < 0 || *s == '-') {
    +            qh GOODpoint= i-1;
    +            qh_option("QGood-if-dont-see-point", &i, NULL);
    +          }else {
    +            qh GOODpoint= i+1;
    +            qh_option("QGood-if-see-point", &i, NULL);
    +          }
    +          s= t;
    +          break;
    +        case 'J':
    +          if (!isdigit(*s) && *s != '-')
    +            qh JOGGLEmax= 0.0;
    +          else {
    +            qh JOGGLEmax= (realT) qh_strtod(s, &s);
    +            qh_option("QJoggle", NULL, &qh JOGGLEmax);
    +          }
    +          break;
    +        case 'R':
    +          if (!isdigit(*s) && *s != '-')
    +            qh_fprintf(qh ferr, 7020, "qhull warning: missing random seed for option 'QRn'.  Ignored\n");
    +          else {
    +            qh ROTATErandom= i= qh_strtol(s, &s);
    +            if (i > 0)
    +              qh_option("QRotate-id", &i, NULL );
    +            else if (i < -1)
    +              qh_option("QRandom-seed", &i, NULL );
    +          }
    +          break;
    +        case 'V':
    +          i= qh_strtol(s, &t);
    +          if (qh GOODvertex)
    +            qh_fprintf(qh ferr, 7021, "qhull warning: good vertex already defined for option 'QVn'.  Ignored\n");
    +          else if (s == t)
    +            qh_fprintf(qh ferr, 7022, "qhull warning: no good point id given for option 'QVn'.  Ignored\n");
    +          else if (i < 0) {
    +            qh GOODvertex= i - 1;
    +            qh_option("QV-good-facets-not-point", &i, NULL);
    +          }else {
    +            qh_option("QV-good-facets-point", &i, NULL);
    +            qh GOODvertex= i + 1;
    +          }
    +          s= t;
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7023, "qhull warning: unknown 'Q' qhull option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'T':
    +      while (*s && !isspace(*s)) {
    +        if (isdigit(*s) || *s == '-')
    +          qh IStracing= qh_strtol(s, &s);
    +        else switch (*s++) {
    +        case 'a':
    +          qh_option("Tannotate-output", NULL, NULL);
    +          qh ANNOTATEoutput= True;
    +          break;
    +        case 'c':
    +          qh_option("Tcheck-frequently", NULL, NULL);
    +          qh CHECKfrequently= True;
    +          break;
    +        case 's':
    +          qh_option("Tstatistics", NULL, NULL);
    +          qh PRINTstatistics= True;
    +          break;
    +        case 'v':
    +          qh_option("Tverify", NULL, NULL);
    +          qh VERIFYoutput= True;
    +          break;
    +        case 'z':
    +          if (qh ferr == qh_FILEstderr) {
    +            /* The C++ interface captures the output in qh_fprint_qhull() */
    +            qh_option("Tz-stdout", NULL, NULL);
    +            qh USEstdout= True;
    +          }else if (!qh fout)
    +            qh_fprintf(qh ferr, 7024, "qhull warning: output file undefined(stdout).  Option 'Tz' ignored.\n");
    +          else {
    +            qh_option("Tz-stdout", NULL, NULL);
    +            qh USEstdout= True;
    +            qh ferr= qh fout;
    +            qhmem.ferr= qh fout;
    +          }
    +          break;
    +        case 'C':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7025, "qhull warning: missing point id for cone for trace option 'TCn'.  Ignored\n");
    +          else {
    +            i= qh_strtol(s, &s);
    +            qh_option("TCone-stop", &i, NULL);
    +            qh STOPcone= i + 1;
    +          }
    +          break;
    +        case 'F':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7026, "qhull warning: missing frequency count for trace option 'TFn'.  Ignored\n");
    +          else {
    +            qh REPORTfreq= qh_strtol(s, &s);
    +            qh_option("TFacet-log", &qh REPORTfreq, NULL);
    +            qh REPORTfreq2= qh REPORTfreq/2;  /* for tracemerging() */
    +          }
    +          break;
    +        case 'I':
    +          if (!isspace(*s))
    +            qh_fprintf(qh ferr, 7027, "qhull warning: missing space between 'TI' and filename, %s\n", s);
    +          while (isspace(*s))
    +            s++;
    +          t= qh_skipfilename(s);
    +          {
    +            char filename[qh_FILENAMElen];
    +
    +            qh_copyfilename(filename, (int)sizeof(filename), s, (int)(t-s));   /* WARN64 */
    +            s= t;
    +            if (!freopen(filename, "r", stdin)) {
    +              qh_fprintf(qh ferr, 6041, "qhull error: could not open file \"%s\".", filename);
    +              qh_errexit(qh_ERRinput, NULL, NULL);
    +            }else {
    +              qh_option("TInput-file", NULL, NULL);
    +              qh_option(filename, NULL, NULL);
    +            }
    +          }
    +          break;
    +        case 'O':
    +            if (!isspace(*s))
    +                qh_fprintf(qh ferr, 7028, "qhull warning: missing space between 'TO' and filename, %s\n", s);
    +            while (isspace(*s))
    +                s++;
    +            t= qh_skipfilename(s);
    +            {
    +              char filename[qh_FILENAMElen];
    +
    +              qh_copyfilename(filename, (int)sizeof(filename), s, (int)(t-s));  /* WARN64 */
    +              s= t;
    +              if (!qh fout) {
    +                qh_fprintf(qh ferr, 6266, "qhull input warning: qh.fout was not set by caller.  Cannot use option 'TO' to redirect output.  Ignoring option 'TO'\n");
    +              }else if (!freopen(filename, "w", qh fout)) {
    +                qh_fprintf(qh ferr, 6044, "qhull error: could not open file \"%s\".", filename);
    +                qh_errexit(qh_ERRinput, NULL, NULL);
    +              }else {
    +                qh_option("TOutput-file", NULL, NULL);
    +              qh_option(filename, NULL, NULL);
    +            }
    +          }
    +          break;
    +        case 'P':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7029, "qhull warning: missing point id for trace option 'TPn'.  Ignored\n");
    +          else {
    +            qh TRACEpoint= qh_strtol(s, &s);
    +            qh_option("Trace-point", &qh TRACEpoint, NULL);
    +          }
    +          break;
    +        case 'M':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7030, "qhull warning: missing merge id for trace option 'TMn'.  Ignored\n");
    +          else {
    +            qh TRACEmerge= qh_strtol(s, &s);
    +            qh_option("Trace-merge", &qh TRACEmerge, NULL);
    +          }
    +          break;
    +        case 'R':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7031, "qhull warning: missing rerun count for trace option 'TRn'.  Ignored\n");
    +          else {
    +            qh RERUN= qh_strtol(s, &s);
    +            qh_option("TRerun", &qh RERUN, NULL);
    +          }
    +          break;
    +        case 'V':
    +          i= qh_strtol(s, &t);
    +          if (s == t)
    +            qh_fprintf(qh ferr, 7032, "qhull warning: missing furthest point id for trace option 'TVn'.  Ignored\n");
    +          else if (i < 0) {
    +            qh STOPpoint= i - 1;
    +            qh_option("TV-stop-before-point", &i, NULL);
    +          }else {
    +            qh STOPpoint= i + 1;
    +            qh_option("TV-stop-after-point", &i, NULL);
    +          }
    +          s= t;
    +          break;
    +        case 'W':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7033, "qhull warning: missing max width for trace option 'TWn'.  Ignored\n");
    +          else {
    +            qh TRACEdist= (realT) qh_strtod(s, &s);
    +            qh_option("TWide-trace", NULL, &qh TRACEdist);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7034, "qhull warning: unknown 'T' trace option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    default:
    +      qh_fprintf(qh ferr, 7035, "qhull warning: unknown flag %c(%x)\n", (int)s[-1],
    +               (int)s[-1]);
    +      break;
    +    }
    +    if (s-1 == prev_s && *s && !isspace(*s)) {
    +      qh_fprintf(qh ferr, 7036, "qhull warning: missing space after flag %c(%x); reserved for menu. Skipped.\n",
    +               (int)*prev_s, (int)*prev_s);
    +      while (*s && !isspace(*s))
    +        s++;
    +    }
    +  }
    +  if (qh STOPcone && qh JOGGLEmax < REALmax/2)
    +    qh_fprintf(qh ferr, 7078, "qhull warning: 'TCn' (stopCone) ignored when used with 'QJn' (joggle)\n");
    +  if (isgeom && !qh FORCEoutput && qh PRINTout[1])
    +    qh_fprintf(qh ferr, 7037, "qhull warning: additional output formats are not compatible with Geomview\n");
    +  /* set derived values in qh_initqhull_globals */
    +} /* initflags */
    +
    +
    +/*---------------------------------
    +
    +  qh_initqhull_buffers()
    +    initialize global memory buffers
    +
    +  notes:
    +    must match qh_freebuffers()
    +*/
    +void qh_initqhull_buffers(void) {
    +  int k;
    +
    +  qh TEMPsize= (qhmem.LASTsize - sizeof(setT))/SETelemsize;
    +  if (qh TEMPsize <= 0 || qh TEMPsize > qhmem.LASTsize)
    +    qh TEMPsize= 8;  /* e.g., if qh_NOmem */
    +  qh other_points= qh_setnew(qh TEMPsize);
    +  qh del_vertices= qh_setnew(qh TEMPsize);
    +  qh coplanarfacetset= qh_setnew(qh TEMPsize);
    +  qh NEARzero= (realT *)qh_memalloc(qh hull_dim * sizeof(realT));
    +  qh lower_threshold= (realT *)qh_memalloc((qh input_dim+1) * sizeof(realT));
    +  qh upper_threshold= (realT *)qh_memalloc((qh input_dim+1) * sizeof(realT));
    +  qh lower_bound= (realT *)qh_memalloc((qh input_dim+1) * sizeof(realT));
    +  qh upper_bound= (realT *)qh_memalloc((qh input_dim+1) * sizeof(realT));
    +  for (k=qh input_dim+1; k--; ) {  /* duplicated in qh_initqhull_buffers and qh_clear_outputflags */
    +    qh lower_threshold[k]= -REALmax;
    +    qh upper_threshold[k]= REALmax;
    +    qh lower_bound[k]= -REALmax;
    +    qh upper_bound[k]= REALmax;
    +  }
    +  qh gm_matrix= (coordT *)qh_memalloc((qh hull_dim+1) * qh hull_dim * sizeof(coordT));
    +  qh gm_row= (coordT **)qh_memalloc((qh hull_dim+1) * sizeof(coordT *));
    +} /* initqhull_buffers */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_globals( points, numpoints, dim, ismalloc )
    +    initialize globals
    +    if ismalloc
    +      points were malloc'd and qhull should free at end
    +
    +  returns:
    +    sets qh.first_point, num_points, input_dim, hull_dim and others
    +    seeds random number generator (seed=1 if tracing)
    +    modifies qh.hull_dim if ((qh.DELAUNAY and qh.PROJECTdelaunay) or qh.PROJECTinput)
    +    adjust user flags as needed
    +    also checks DIM3 dependencies and constants
    +
    +  notes:
    +    do not use qh_point() since an input transformation may move them elsewhere
    +
    +  see:
    +    qh_initqhull_start() sets default values for non-zero globals
    +
    +  design:
    +    initialize points array from input arguments
    +    test for qh.ZEROcentrum
    +      (i.e., use opposite vertex instead of cetrum for convexity testing)
    +    initialize qh.CENTERtype, qh.normal_size,
    +      qh.center_size, qh.TRACEpoint/level,
    +    initialize and test random numbers
    +    qh_initqhull_outputflags() -- adjust and test output flags
    +*/
    +void qh_initqhull_globals(coordT *points, int numpoints, int dim, boolT ismalloc) {
    +  int seed, pointsneeded, extra= 0, i, randi, k;
    +  realT randr;
    +  realT factorial;
    +
    +  time_t timedata;
    +
    +  trace0((qh ferr, 13, "qh_initqhull_globals: for %s | %s\n", qh rbox_command,
    +      qh qhull_command));
    +  qh POINTSmalloc= ismalloc;
    +  qh first_point= points;
    +  qh num_points= numpoints;
    +  qh hull_dim= qh input_dim= dim;
    +  if (!qh NOpremerge && !qh MERGEexact && !qh PREmerge && qh JOGGLEmax > REALmax/2) {
    +    qh MERGING= True;
    +    if (qh hull_dim <= 4) {
    +      qh PREmerge= True;
    +      qh_option("_pre-merge", NULL, NULL);
    +    }else {
    +      qh MERGEexact= True;
    +      qh_option("Qxact_merge", NULL, NULL);
    +    }
    +  }else if (qh MERGEexact)
    +    qh MERGING= True;
    +  if (!qh NOpremerge && qh JOGGLEmax > REALmax/2) {
    +#ifdef qh_NOmerge
    +    qh JOGGLEmax= 0.0;
    +#endif
    +  }
    +  if (qh TRIangulate && qh JOGGLEmax < REALmax/2 && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 7038, "qhull warning: joggle('QJ') always produces simplicial output.  Triangulated output('Qt') does nothing.\n");
    +  if (qh JOGGLEmax < REALmax/2 && qh DELAUNAY && !qh SCALEinput && !qh SCALElast) {
    +    qh SCALElast= True;
    +    qh_option("Qbbound-last-qj", NULL, NULL);
    +  }
    +  if (qh MERGING && !qh POSTmerge && qh premerge_cos > REALmax/2
    +  && qh premerge_centrum == 0) {
    +    qh ZEROcentrum= True;
    +    qh ZEROall_ok= True;
    +    qh_option("_zero-centrum", NULL, NULL);
    +  }
    +  if (qh JOGGLEmax < REALmax/2 && REALepsilon > 2e-8 && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 7039, "qhull warning: real epsilon, %2.2g, is probably too large for joggle('QJn')\nRecompile with double precision reals(see user.h).\n",
    +          REALepsilon);
    +#ifdef qh_NOmerge
    +  if (qh MERGING) {
    +    qh_fprintf(qh ferr, 6045, "qhull input error: merging not installed(qh_NOmerge + 'Qx', 'Cn' or 'An')\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +#endif
    +  if (qh DELAUNAY && qh KEEPcoplanar && !qh KEEPinside) {
    +    qh KEEPinside= True;
    +    qh_option("Qinterior-keep", NULL, NULL);
    +  }
    +  if (qh DELAUNAY && qh HALFspace) {
    +    qh_fprintf(qh ferr, 6046, "qhull input error: can not use Delaunay('d') or Voronoi('v') with halfspace intersection('H')\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (!qh DELAUNAY && (qh UPPERdelaunay || qh ATinfinity)) {
    +    qh_fprintf(qh ferr, 6047, "qhull input error: use upper-Delaunay('Qu') or infinity-point('Qz') with Delaunay('d') or Voronoi('v')\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh UPPERdelaunay && qh ATinfinity) {
    +    qh_fprintf(qh ferr, 6048, "qhull input error: can not use infinity-point('Qz') with upper-Delaunay('Qu')\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh SCALElast && !qh DELAUNAY && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 7040, "qhull input warning: option 'Qbb' (scale-last-coordinate) is normally used with 'd' or 'v'\n");
    +  qh DOcheckmax= (!qh SKIPcheckmax && qh MERGING );
    +  qh KEEPnearinside= (qh DOcheckmax && !(qh KEEPinside && qh KEEPcoplanar)
    +                          && !qh NOnearinside);
    +  if (qh MERGING)
    +    qh CENTERtype= qh_AScentrum;
    +  else if (qh VORONOI)
    +    qh CENTERtype= qh_ASvoronoi;
    +  if (qh TESTvneighbors && !qh MERGING) {
    +    qh_fprintf(qh ferr, 6049, "qhull input error: test vertex neighbors('Qv') needs a merge option\n");
    +    qh_errexit(qh_ERRinput, NULL ,NULL);
    +  }
    +  if (qh PROJECTinput || (qh DELAUNAY && qh PROJECTdelaunay)) {
    +    qh hull_dim -= qh PROJECTinput;
    +    if (qh DELAUNAY) {
    +      qh hull_dim++;
    +      if (qh ATinfinity)
    +        extra= 1;
    +    }
    +  }
    +  if (qh hull_dim <= 1) {
    +    qh_fprintf(qh ferr, 6050, "qhull error: dimension %d must be > 1\n", qh hull_dim);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  for (k=2, factorial=1.0; k < qh hull_dim; k++)
    +    factorial *= k;
    +  qh AREAfactor= 1.0 / factorial;
    +  trace2((qh ferr, 2005, "qh_initqhull_globals: initialize globals.  dim %d numpoints %d malloc? %d projected %d to hull_dim %d\n",
    +        dim, numpoints, ismalloc, qh PROJECTinput, qh hull_dim));
    +  qh normal_size= qh hull_dim * sizeof(coordT);
    +  qh center_size= qh normal_size - sizeof(coordT);
    +  pointsneeded= qh hull_dim+1;
    +  if (qh hull_dim > qh_DIMmergeVertex) {
    +    qh MERGEvertices= False;
    +    qh_option("Q3-no-merge-vertices-dim-high", NULL, NULL);
    +  }
    +  if (qh GOODpoint)
    +    pointsneeded++;
    +#ifdef qh_NOtrace
    +  if (qh IStracing) {
    +    qh_fprintf(qh ferr, 6051, "qhull input error: tracing is not installed(qh_NOtrace in user.h)");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +#endif
    +  if (qh RERUN > 1) {
    +    qh TRACElastrun= qh IStracing; /* qh_build_withrestart duplicates next conditional */
    +    if (qh IStracing != -1)
    +      qh IStracing= 0;
    +  }else if (qh TRACEpoint != qh_IDunknown || qh TRACEdist < REALmax/2 || qh TRACEmerge) {
    +    qh TRACElevel= (qh IStracing? qh IStracing : 3);
    +    qh IStracing= 0;
    +  }
    +  if (qh ROTATErandom == 0 || qh ROTATErandom == -1) {
    +    seed= (int)time(&timedata);
    +    if (qh ROTATErandom  == -1) {
    +      seed= -seed;
    +      qh_option("QRandom-seed", &seed, NULL );
    +    }else
    +      qh_option("QRotate-random", &seed, NULL);
    +    qh ROTATErandom= seed;
    +  }
    +  seed= qh ROTATErandom;
    +  if (seed == INT_MIN)    /* default value */
    +    seed= 1;
    +  else if (seed < 0)
    +    seed= -seed;
    +  qh_RANDOMseed_(seed);
    +  randr= 0.0;
    +  for (i=1000; i--; ) {
    +    randi= qh_RANDOMint;
    +    randr += randi;
    +    if (randi > qh_RANDOMmax) {
    +      qh_fprintf(qh ferr, 8036, "\
    +qhull configuration error (qh_RANDOMmax in user.h):\n\
    +   random integer %d > qh_RANDOMmax(%.8g)\n",
    +               randi, qh_RANDOMmax);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +  }
    +  qh_RANDOMseed_(seed);
    +  randr = randr/1000;
    +  if (randr < qh_RANDOMmax * 0.1
    +  || randr > qh_RANDOMmax * 0.9)
    +    qh_fprintf(qh ferr, 8037, "\
    +qhull configuration warning (qh_RANDOMmax in user.h):\n\
    +   average of 1000 random integers (%.2g) is much different than expected (%.2g).\n\
    +   Is qh_RANDOMmax (%.2g) wrong?\n",
    +             randr, qh_RANDOMmax * 0.5, qh_RANDOMmax);
    +  qh RANDOMa= 2.0 * qh RANDOMfactor/qh_RANDOMmax;
    +  qh RANDOMb= 1.0 - qh RANDOMfactor;
    +  if (qh_HASHfactor < 1.1) {
    +    qh_fprintf(qh ferr, 6052, "qhull internal error (qh_initqhull_globals): qh_HASHfactor %d must be at least 1.1.  Qhull uses linear hash probing\n",
    +      qh_HASHfactor);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (numpoints+extra < pointsneeded) {
    +    qh_fprintf(qh ferr, 6214, "qhull input error: not enough points(%d) to construct initial simplex (need %d)\n",
    +            numpoints, pointsneeded);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  qh_initqhull_outputflags();
    +} /* initqhull_globals */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_mem(  )
    +    initialize mem.c for qhull
    +    qh.hull_dim and qh.normal_size determine some of the allocation sizes
    +    if qh.MERGING,
    +      includes ridgeT
    +    calls qh_user_memsizes() to add up to 10 additional sizes for quick allocation
    +      (see numsizes below)
    +
    +  returns:
    +    mem.c already for qh_memalloc/qh_memfree (errors if called beforehand)
    +
    +  notes:
    +    qh_produceoutput() prints memsizes
    +
    +*/
    +void qh_initqhull_mem(void) {
    +  int numsizes;
    +  int i;
    +
    +  numsizes= 8+10;
    +  qh_meminitbuffers(qh IStracing, qh_MEMalign, numsizes,
    +                     qh_MEMbufsize,qh_MEMinitbuf);
    +  qh_memsize((int)sizeof(vertexT));
    +  if (qh MERGING) {
    +    qh_memsize((int)sizeof(ridgeT));
    +    qh_memsize((int)sizeof(mergeT));
    +  }
    +  qh_memsize((int)sizeof(facetT));
    +  i= sizeof(setT) + (qh hull_dim - 1) * SETelemsize;  /* ridge.vertices */
    +  qh_memsize(i);
    +  qh_memsize(qh normal_size);        /* normal */
    +  i += SETelemsize;                 /* facet.vertices, .ridges, .neighbors */
    +  qh_memsize(i);
    +  qh_user_memsizes();
    +  qh_memsetup();
    +} /* initqhull_mem */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_outputflags
    +    initialize flags concerned with output
    +
    +  returns:
    +    adjust user flags as needed
    +
    +  see:
    +    qh_clear_outputflags() resets the flags
    +
    +  design:
    +    test for qh.PRINTgood (i.e., only print 'good' facets)
    +    check for conflicting print output options
    +*/
    +void qh_initqhull_outputflags(void) {
    +  boolT printgeom= False, printmath= False, printcoplanar= False;
    +  int i;
    +
    +  trace3((qh ferr, 3024, "qh_initqhull_outputflags: %s\n", qh qhull_command));
    +  if (!(qh PRINTgood || qh PRINTneighbors)) {
    +    if (qh KEEParea || qh KEEPminArea < REALmax/2 || qh KEEPmerge || qh DELAUNAY
    +        || (!qh ONLYgood && (qh GOODvertex || qh GOODpoint))) {
    +      qh PRINTgood= True;
    +      qh_option("Pgood", NULL, NULL);
    +    }
    +  }
    +  if (qh PRINTtransparent) {
    +    if (qh hull_dim != 4 || !qh DELAUNAY || qh VORONOI || qh DROPdim >= 0) {
    +      qh_fprintf(qh ferr, 6215, "qhull input error: transparent Delaunay('Gt') needs 3-d Delaunay('d') w/o 'GDn'\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    qh DROPdim = 3;
    +    qh PRINTridges = True;
    +  }
    +  for (i=qh_PRINTEND; i--; ) {
    +    if (qh PRINTout[i] == qh_PRINTgeom)
    +      printgeom= True;
    +    else if (qh PRINTout[i] == qh_PRINTmathematica || qh PRINTout[i] == qh_PRINTmaple)
    +      printmath= True;
    +    else if (qh PRINTout[i] == qh_PRINTcoplanars)
    +      printcoplanar= True;
    +    else if (qh PRINTout[i] == qh_PRINTpointnearest)
    +      printcoplanar= True;
    +    else if (qh PRINTout[i] == qh_PRINTpointintersect && !qh HALFspace) {
    +      qh_fprintf(qh ferr, 6053, "qhull input error: option 'Fp' is only used for \nhalfspace intersection('Hn,n,n').\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }else if (qh PRINTout[i] == qh_PRINTtriangles && (qh HALFspace || qh VORONOI)) {
    +      qh_fprintf(qh ferr, 6054, "qhull input error: option 'Ft' is not available for Voronoi vertices or halfspace intersection\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }else if (qh PRINTout[i] == qh_PRINTcentrums && qh VORONOI) {
    +      qh_fprintf(qh ferr, 6055, "qhull input error: option 'FC' is not available for Voronoi vertices('v')\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }else if (qh PRINTout[i] == qh_PRINTvertices) {
    +      if (qh VORONOI)
    +        qh_option("Fvoronoi", NULL, NULL);
    +      else
    +        qh_option("Fvertices", NULL, NULL);
    +    }
    +  }
    +  if (printcoplanar && qh DELAUNAY && qh JOGGLEmax < REALmax/2) {
    +    if (qh PRINTprecision)
    +      qh_fprintf(qh ferr, 7041, "qhull input warning: 'QJ' (joggle) will usually prevent coincident input sites for options 'Fc' and 'FP'\n");
    +  }
    +  if (printmath && (qh hull_dim > 3 || qh VORONOI)) {
    +    qh_fprintf(qh ferr, 6056, "qhull input error: Mathematica and Maple output is only available for 2-d and 3-d convex hulls and 2-d Delaunay triangulations\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (printgeom) {
    +    if (qh hull_dim > 4) {
    +      qh_fprintf(qh ferr, 6057, "qhull input error: Geomview output is only available for 2-d, 3-d and 4-d\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh PRINTnoplanes && !(qh PRINTcoplanar + qh PRINTcentrums
    +     + qh PRINTdots + qh PRINTspheres + qh DOintersections + qh PRINTridges)) {
    +      qh_fprintf(qh ferr, 6058, "qhull input error: no output specified for Geomview\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh VORONOI && (qh hull_dim > 3 || qh DROPdim >= 0)) {
    +      qh_fprintf(qh ferr, 6059, "qhull input error: Geomview output for Voronoi diagrams only for 2-d\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    /* can not warn about furthest-site Geomview output: no lower_threshold */
    +    if (qh hull_dim == 4 && qh DROPdim == -1 &&
    +        (qh PRINTcoplanar || qh PRINTspheres || qh PRINTcentrums)) {
    +      qh_fprintf(qh ferr, 7042, "qhull input warning: coplanars, vertices, and centrums output not\n\
    +available for 4-d output(ignored).  Could use 'GDn' instead.\n");
    +      qh PRINTcoplanar= qh PRINTspheres= qh PRINTcentrums= False;
    +    }
    +  }
    +  if (!qh KEEPcoplanar && !qh KEEPinside && !qh ONLYgood) {
    +    if ((qh PRINTcoplanar && qh PRINTspheres) || printcoplanar) {
    +      if (qh QHULLfinished) {
    +        qh_fprintf(qh ferr, 7072, "qhull output warning: ignoring coplanar points, option 'Qc' was not set for the first run of qhull.\n");
    +      }else {
    +        qh KEEPcoplanar = True;
    +        qh_option("Qcoplanar", NULL, NULL);
    +      }
    +    }
    +  }
    +  qh PRINTdim= qh hull_dim;
    +  if (qh DROPdim >=0) {    /* after Geomview checks */
    +    if (qh DROPdim < qh hull_dim) {
    +      qh PRINTdim--;
    +      if (!printgeom || qh hull_dim < 3)
    +        qh_fprintf(qh ferr, 7043, "qhull input warning: drop dimension 'GD%d' is only available for 3-d/4-d Geomview\n", qh DROPdim);
    +    }else
    +      qh DROPdim= -1;
    +  }else if (qh VORONOI) {
    +    qh DROPdim= qh hull_dim-1;
    +    qh PRINTdim= qh hull_dim-1;
    +  }
    +} /* qh_initqhull_outputflags */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_start( infile, outfile, errfile )
    +    allocate memory if needed and call qh_initqhull_start2()
    +*/
    +void qh_initqhull_start(FILE *infile, FILE *outfile, FILE *errfile) {
    +
    +#if qh_QHpointer
    +  if (qh_qh) {
    +    qh_fprintf(errfile, 6205, "qhull error (qh_initqhull_start): qh_qh already defined.  Call qh_save_qhull() first\n");
    +    qh_exit(qh_ERRqhull);  /* no error handler */
    +  }
    +  if (!(qh_qh= (qhT *)qh_malloc(sizeof(qhT)))) {
    +    qh_fprintf(errfile, 6060, "qhull error (qh_initqhull_start): insufficient memory\n");
    +    qh_exit(qh_ERRmem);  /* no error handler */
    +  }
    +#endif
    +  qh_initstatistics();
    +  qh_initqhull_start2(infile, outfile, errfile);
    +} /* initqhull_start */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_start2( infile, outfile, errfile )
    +    start initialization of qhull
    +    initialize statistics, stdio, default values for global variables
    +    assumes qh_qh is defined
    +  notes:
    +    report errors elsewhere, error handling and g_qhull_output [Qhull.cpp, QhullQh()] not in initialized
    +  see:
    +    qh_maxmin() determines the precision constants
    +    qh_freeqhull2()
    +*/
    +void qh_initqhull_start2(FILE *infile, FILE *outfile, FILE *errfile) {
    +  time_t timedata;
    +  int seed;
    +
    +  qh_CPUclock; /* start the clock(for qh_clock).  One-shot. */
    +#if qh_QHpointer
    +  memset((char *)qh_qh, 0, sizeof(qhT));   /* every field is 0, FALSE, NULL */
    +#else
    +  memset((char *)&qh_qh, 0, sizeof(qhT));
    +#endif
    +  qh ANGLEmerge= True;
    +  qh DROPdim= -1;
    +  qh ferr= errfile;
    +  qh fin= infile;
    +  qh fout= outfile;
    +  qh furthest_id= qh_IDunknown;
    +  qh JOGGLEmax= REALmax;
    +  qh KEEPminArea = REALmax;
    +  qh last_low= REALmax;
    +  qh last_high= REALmax;
    +  qh last_newhigh= REALmax;
    +  qh max_outside= 0.0;
    +  qh max_vertex= 0.0;
    +  qh MAXabs_coord= 0.0;
    +  qh MAXsumcoord= 0.0;
    +  qh MAXwidth= -REALmax;
    +  qh MERGEindependent= True;
    +  qh MINdenom_1= fmax_(1.0/REALmax, REALmin); /* used by qh_scalepoints */
    +  qh MINoutside= 0.0;
    +  qh MINvisible= REALmax;
    +  qh MAXcoplanar= REALmax;
    +  qh outside_err= REALmax;
    +  qh premerge_centrum= 0.0;
    +  qh premerge_cos= REALmax;
    +  qh PRINTprecision= True;
    +  qh PRINTradius= 0.0;
    +  qh postmerge_cos= REALmax;
    +  qh postmerge_centrum= 0.0;
    +  qh ROTATErandom= INT_MIN;
    +  qh MERGEvertices= True;
    +  qh totarea= 0.0;
    +  qh totvol= 0.0;
    +  qh TRACEdist= REALmax;
    +  qh TRACEpoint= qh_IDunknown; /* recompile or use 'TPn' */
    +  qh tracefacet_id= UINT_MAX;  /* recompile to trace a facet */
    +  qh tracevertex_id= UINT_MAX; /* recompile to trace a vertex */
    +  seed= (int)time(&timedata);
    +  qh_RANDOMseed_(seed);
    +  qh run_id= qh_RANDOMint;
    +  if(!qh run_id)
    +      qh run_id++;  /* guarantee non-zero */
    +  qh_option("run-id", &qh run_id, NULL);
    +  strcat(qh qhull, "qhull");
    +} /* initqhull_start2 */
    +
    +/*---------------------------------
    +
    +  qh_initthresholds( commandString )
    +    set thresholds for printing and scaling from commandString
    +
    +  returns:
    +    sets qh.GOODthreshold or qh.SPLITthreshold if 'Pd0D1' used
    +
    +  see:
    +    qh_initflags(), 'Qbk' 'QBk' 'Pdk' and 'PDk'
    +    qh_inthresholds()
    +
    +  design:
    +    for each 'Pdn' or 'PDn' option
    +      check syntax
    +      set qh.lower_threshold or qh.upper_threshold
    +    set qh.GOODthreshold if an unbounded threshold is used
    +    set qh.SPLITthreshold if a bounded threshold is used
    +*/
    +void qh_initthresholds(char *command) {
    +  realT value;
    +  int idx, maxdim, k;
    +  char *s= command; /* non-const due to strtol */
    +  char key;
    +
    +  maxdim= qh input_dim;
    +  if (qh DELAUNAY && (qh PROJECTdelaunay || qh PROJECTinput))
    +    maxdim++;
    +  while (*s) {
    +    if (*s == '-')
    +      s++;
    +    if (*s == 'P') {
    +      s++;
    +      while (*s && !isspace(key= *s++)) {
    +        if (key == 'd' || key == 'D') {
    +          if (!isdigit(*s)) {
    +            qh_fprintf(qh ferr, 7044, "qhull warning: no dimension given for Print option '%c' at: %s.  Ignored\n",
    +                    key, s-1);
    +            continue;
    +          }
    +          idx= qh_strtol(s, &s);
    +          if (idx >= qh hull_dim) {
    +            qh_fprintf(qh ferr, 7045, "qhull warning: dimension %d for Print option '%c' is >= %d.  Ignored\n",
    +                idx, key, qh hull_dim);
    +            continue;
    +          }
    +          if (*s == ':') {
    +            s++;
    +            value= qh_strtod(s, &s);
    +            if (fabs((double)value) > 1.0) {
    +              qh_fprintf(qh ferr, 7046, "qhull warning: value %2.4g for Print option %c is > +1 or < -1.  Ignored\n",
    +                      value, key);
    +              continue;
    +            }
    +          }else
    +            value= 0.0;
    +          if (key == 'd')
    +            qh lower_threshold[idx]= value;
    +          else
    +            qh upper_threshold[idx]= value;
    +        }
    +      }
    +    }else if (*s == 'Q') {
    +      s++;
    +      while (*s && !isspace(key= *s++)) {
    +        if (key == 'b' && *s == 'B') {
    +          s++;
    +          for (k=maxdim; k--; ) {
    +            qh lower_bound[k]= -qh_DEFAULTbox;
    +            qh upper_bound[k]= qh_DEFAULTbox;
    +          }
    +        }else if (key == 'b' && *s == 'b')
    +          s++;
    +        else if (key == 'b' || key == 'B') {
    +          if (!isdigit(*s)) {
    +            qh_fprintf(qh ferr, 7047, "qhull warning: no dimension given for Qhull option %c.  Ignored\n",
    +                    key);
    +            continue;
    +          }
    +          idx= qh_strtol(s, &s);
    +          if (idx >= maxdim) {
    +            qh_fprintf(qh ferr, 7048, "qhull warning: dimension %d for Qhull option %c is >= %d.  Ignored\n",
    +                idx, key, maxdim);
    +            continue;
    +          }
    +          if (*s == ':') {
    +            s++;
    +            value= qh_strtod(s, &s);
    +          }else if (key == 'b')
    +            value= -qh_DEFAULTbox;
    +          else
    +            value= qh_DEFAULTbox;
    +          if (key == 'b')
    +            qh lower_bound[idx]= value;
    +          else
    +            qh upper_bound[idx]= value;
    +        }
    +      }
    +    }else {
    +      while (*s && !isspace(*s))
    +        s++;
    +    }
    +    while (isspace(*s))
    +      s++;
    +  }
    +  for (k=qh hull_dim; k--; ) {
    +    if (qh lower_threshold[k] > -REALmax/2) {
    +      qh GOODthreshold= True;
    +      if (qh upper_threshold[k] < REALmax/2) {
    +        qh SPLITthresholds= True;
    +        qh GOODthreshold= False;
    +        break;
    +      }
    +    }else if (qh upper_threshold[k] < REALmax/2)
    +      qh GOODthreshold= True;
    +  }
    +} /* initthresholds */
    +
    +/*---------------------------------
    +
    +  qh_lib_check( qhullLibraryType, qhTsize, vertexTsize, ridgeTsize, facetTsize, setTsize, qhmemTsize )
    +    Report error if library does not agree with caller
    +
    +  notes:
    +    NOerrors -- qh_lib_check can not call qh_errexit()
    +*/
    +void qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeTsize, int facetTsize, int setTsize, int qhmemTsize) {
    +    boolT iserror= False;
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)  /* user_r.h */
    +    // _CrtSetBreakAlloc(744);  /* Break at memalloc {744}, or 'watch' _crtBreakAlloc */
    +    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) );
    +    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
    +    _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR );
    +    _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR );
    +#endif
    +
    +    if (qhullLibraryType==QHULL_NON_REENTRANT) { /* 0 */
    +        if (qh_QHpointer) {
    +            qh_fprintf_stderr(6246, "qh_lib_check: Incorrect qhull library called.  Caller uses a static qhT while library uses a dynamic qhT via qh_QHpointer.  Both caller and library are non-reentrant.\n");
    +            iserror= True;
    +        }
    +    }else if (qhullLibraryType==QHULL_QH_POINTER) { /* 1 */
    +        if (!qh_QHpointer) {
    +            qh_fprintf_stderr(6247, "qh_lib_check: Incorrect qhull library called.  Caller uses a dynamic qhT via qh_QHpointer while library uses a static qhT.  Both caller and library are non-reentrant.\n");
    +            iserror= True;
    +        }
    +    }else if (qhullLibraryType==QHULL_REENTRANT) { /* 2 */
    +        qh_fprintf_stderr(6248, "qh_lib_check: Incorrect qhull library called.  Caller uses reentrant Qhull while library is non-reentrant\n");
    +        iserror= True;
    +    }else{
    +        qh_fprintf_stderr(6262, "qh_lib_check: Expecting qhullLibraryType QHULL_NON_REENTRANT(0), QHULL_QH_POINTER(1), or QHULL_REENTRANT(2).  Got %d\n", qhullLibraryType);
    +        iserror= True;
    +    }
    +    if (qhTsize != sizeof(qhT)) {
    +        qh_fprintf_stderr(6249, "qh_lib_check: Incorrect qhull library called.  Size of qhT for caller is %d, but for library is %d.\n", qhTsize, sizeof(qhT));
    +        iserror= True;
    +    }
    +    if (vertexTsize != sizeof(vertexT)) {
    +        qh_fprintf_stderr(6250, "qh_lib_check: Incorrect qhull library called.  Size of vertexT for caller is %d, but for library is %d.\n", vertexTsize, sizeof(vertexT));
    +        iserror= True;
    +    }
    +    if (ridgeTsize != sizeof(ridgeT)) {
    +        qh_fprintf_stderr(6251, "qh_lib_check: Incorrect qhull library called.  Size of ridgeT for caller is %d, but for library is %d.\n", ridgeTsize, sizeof(ridgeT));
    +        iserror= True;
    +    }
    +    if (facetTsize != sizeof(facetT)) {
    +        qh_fprintf_stderr(6252, "qh_lib_check: Incorrect qhull library called.  Size of facetT for caller is %d, but for library is %d.\n", facetTsize, sizeof(facetT));
    +        iserror= True;
    +    }
    +    if (setTsize && setTsize != sizeof(setT)) {
    +        qh_fprintf_stderr(6253, "qh_lib_check: Incorrect qhull library called.  Size of setT for caller is %d, but for library is %d.\n", setTsize, sizeof(setT));
    +        iserror= True;
    +    }
    +    if (qhmemTsize && qhmemTsize != sizeof(qhmemT)) {
    +        qh_fprintf_stderr(6254, "qh_lib_check: Incorrect qhull library called.  Size of qhmemT for caller is %d, but for library is %d.\n", qhmemTsize, sizeof(qhmemT));
    +        iserror= True;
    +    }
    +    if (iserror) {
    +        if(qh_QHpointer){
    +            qh_fprintf_stderr(6255, "qh_lib_check: Cannot continue.  Library '%s' uses a dynamic qhT via qh_QHpointer (e.g., qhull_p.so)\n", qh_version2);
    +        }else{
    +            qh_fprintf_stderr(6256, "qh_lib_check: Cannot continue.  Library '%s' uses a static qhT (e.g., libqhull.so)\n", qh_version2);
    +        }
    +        qh_exit(qh_ERRqhull);  /* can not use qh_errexit() */
    +    }
    +} /* lib_check */
    +
    +/*---------------------------------
    +
    +  qh_option( option, intVal, realVal )
    +    add an option description to qh.qhull_options
    +
    +  notes:
    +    NOerrors -- qh_option can not call qh_errexit() [qh_initqhull_start2]
    +    will be printed with statistics ('Ts') and errors
    +    strlen(option) < 40
    +*/
    +void qh_option(const char *option, int *i, realT *r) {
    +  char buf[200];
    +  int len, maxlen;
    +
    +  sprintf(buf, "  %s", option);
    +  if (i)
    +    sprintf(buf+strlen(buf), " %d", *i);
    +  if (r)
    +    sprintf(buf+strlen(buf), " %2.2g", *r);
    +  len= (int)strlen(buf);  /* WARN64 */
    +  qh qhull_optionlen += len;
    +  maxlen= sizeof(qh qhull_options) - len -1;
    +  maximize_(maxlen, 0);
    +  if (qh qhull_optionlen >= qh_OPTIONline && maxlen > 0) {
    +    qh qhull_optionlen= len;
    +    strncat(qh qhull_options, "\n", (size_t)(maxlen--));
    +  }
    +  strncat(qh qhull_options, buf, (size_t)maxlen);
    +} /* option */
    +
    +#if qh_QHpointer
    +/*---------------------------------
    +
    +  qh_restore_qhull( oldqh )
    +    restores a previously saved qhull
    +    also restores qh_qhstat and qhmem.tempstack
    +    Sets *oldqh to NULL
    +  notes:
    +    errors if current qhull hasn't been saved or freed
    +    uses qhmem for error reporting
    +
    +  NOTE 1998/5/11:
    +    Freeing memory after qh_save_qhull and qh_restore_qhull
    +    is complicated.  The procedures will be redesigned.
    +
    +  see:
    +    qh_save_qhull(), UsingLibQhull
    +*/
    +void qh_restore_qhull(qhT **oldqh) {
    +
    +  if (*oldqh && strcmp((*oldqh)->qhull, "qhull")) {
    +    qh_fprintf(qhmem.ferr, 6061, "qhull internal error (qh_restore_qhull): %p is not a qhull data structure\n",
    +                  *oldqh);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (qh_qh) {
    +    qh_fprintf(qhmem.ferr, 6062, "qhull internal error (qh_restore_qhull): did not save or free existing qhull\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (!*oldqh || !(*oldqh)->old_qhstat) {
    +    qh_fprintf(qhmem.ferr, 6063, "qhull internal error (qh_restore_qhull): did not previously save qhull %p\n",
    +                  *oldqh);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh_qh= *oldqh;
    +  *oldqh= NULL;
    +  qh_qhstat= qh old_qhstat;
    +  qhmem.tempstack= qh old_tempstack;
    +  qh old_qhstat= 0;
    +  qh old_tempstack= 0;
    +  trace1((qh ferr, 1007, "qh_restore_qhull: restored qhull from %p\n", *oldqh));
    +} /* restore_qhull */
    +
    +/*---------------------------------
    +
    +  qh_save_qhull(  )
    +    saves qhull for a later qh_restore_qhull
    +    also saves qh_qhstat and qhmem.tempstack
    +
    +  returns:
    +    qh_qh=NULL
    +
    +  notes:
    +    need to initialize qhull or call qh_restore_qhull before continuing
    +
    +  NOTE 1998/5/11:
    +    Freeing memory after qh_save_qhull and qh_restore_qhull
    +    is complicated.  The procedures will be redesigned.
    +
    +  see:
    +    qh_restore_qhull()
    +*/
    +qhT *qh_save_qhull(void) {
    +  qhT *oldqh;
    +
    +  trace1((qhmem.ferr, 1045, "qh_save_qhull: save qhull %p\n", qh_qh));
    +  if (!qh_qh) {
    +    qh_fprintf(qhmem.ferr, 6064, "qhull internal error (qh_save_qhull): qhull not initialized\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh old_qhstat= qh_qhstat;
    +  qh_qhstat= NULL;
    +  qh old_tempstack= qhmem.tempstack;
    +  qhmem.tempstack= NULL;
    +  oldqh= qh_qh;
    +  qh_qh= NULL;
    +  return oldqh;
    +} /* save_qhull */
    +
    +#endif
    +
    diff --git a/xs/src/qhull/src/libqhull/index.htm b/xs/src/qhull/src/libqhull/index.htm
    new file mode 100644
    index 0000000000..62b9d99701
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/index.htm
    @@ -0,0 +1,264 @@
    +
    +
    +
    +
    +Qhull functions, macros, and data structures
    +
    +
    +
    +
    +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code
    +To: Qhull files
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser + +


    + + +

    Qhull functions, macros, and data structures

    +
    +

    The following sections provide an overview and index to +Qhull's functions, macros, and data structures. +Each section starts with an introduction. +See also Calling +Qhull from C programs and Calling Qhull from C++ programs.

    + +

    Qhull uses the following conventions:

    +
    + +
      +
    • in code, global variables start with "qh " +
    • in documentation, global variables start with 'qh.' +
    • constants start with an upper case word +
    • important globals include an '_' +
    • functions, macros, and constants start with "qh_"
    • +
    • data types end in "T"
    • +
    • macros with arguments end in "_"
    • +
    • iterators are macros that use local variables
    • +
    • iterators for sets start with "FOREACH"
    • +
    • iterators for lists start with "FORALL"
    • +
    • qhull options are in single quotes (e.g., 'Pdn')
    • +
    • lists are sorted alphabetically
    • +
    • preprocessor directives on left margin for older compilers
    • +
    +
    +

    +When reading the code, please note that the +global data structure, 'qh', is a macro. It +either expands to "qh_qh." or to +"qh_qh->". The later is used for +applications which run concurrent calls to qh_qhull(). +

    +When reading code with an editor, a search for +'"function' +will locate the header of qh_function. A search for '* function' +will locate the tail of qh_function. + +

    A useful starting point is libqhull.h. It defines most +of Qhull data structures and top-level functions. Search for 'PFn' to +determine the corresponding constant in Qhull. Search for 'Fp' to +determine the corresponding qh_PRINT... constant. +Search io.c to learn how the print function is implemented.

    + +

    If your web browser is configured for .c and .h files, the function, macro, and data type links +go to the corresponding source location. To configure your web browser for .c and .h files. +

      +
    • In the Download Preferences or Options panel, add file extensions 'c' and 'h' to mime type 'text/html'. +
    • Opera 12.10 +
        +
      1. In Tools > Preferences > Advanced > Downloads +
      2. Uncheck 'Hide file types opened with Opera' +
      3. Quick find 'html' +
      4. Select 'text/html' > Edit +
      5. Add File extensions 'c,h,' +
      6. Click 'OK' +
      +
    • Internet Explorer -- Mime types are not available from 'Internet Options'. Is there a registry key for these settings? +
    • Firefox -- Mime types are not available from 'Preferences'. Is there an add-on to change the file extensions for a mime type? +
    • Chrome -- Can Chrome be configured? +
    + +

    +Please report documentation and link errors +to qhull-bug@qhull.org. +

    + +

    Copyright © 1997-2015 C.B. Barber

    + +
    + +

    »Qhull files

    +
    + +

    This sections lists the .c and .h files for Qhull. Please +refer to these files for detailed information.

    +
    + +
    +
    Makefile, CMakeLists.txt
    +
    Makefile is preconfigured for gcc. CMakeLists.txt supports multiple +platforms with CMake. +Qhull includes project files for Visual Studio and Qt. +
    + +
     
    +
    libqhull.h
    +
    Include file for the Qhull library (libqhull.so, qhull.dll, libqhullstatic.a). +Data structures are documented under Poly. +Global variables are documented under Global. +Other data structures and variables are documented under +Qhull or Geom.
    + +
     
    +
    Geom, +geom.h, +geom.c, +geom2.c, +random.c, +random.h
    +
    Geometric routines. These routines implement mathematical +functions such as Gaussian elimination and geometric +routines needed for Qhull. Frequently used routines are +in geom.c while infrequent ones are in geom2.c. +
    + +
     
    +
    Global, +global.c, +libqhull.h
    +
    Global routines. Qhull uses a global data structure, qh, +to store globally defined constants, lists, sets, and +variables. +global.c initializes and frees these +structures.
    + +
     
    +
    Io, io.h, +io.c
    +
    Input and output routines. Qhull provides a wide range of +input and output options.
    + +
     
    +
    Mem, +mem.h, +mem.c
    +
    Memory routines. Qhull provides memory allocation and +deallocation. It uses quick-fit allocation.
    + +
     
    +
    Merge, +merge.h, +merge.c
    +
    Merge routines. Qhull handles precision problems by +merged facets or joggled input. These routines merge simplicial facets, +merge non-simplicial facets, merge cycles of facets, and +rename redundant vertices.
    + +
     
    +
    Poly, +poly.h, +poly.c, +poly2.c, +libqhull.h
    +
    Polyhedral routines. Qhull produces a polyhedron as a +list of facets with vertices, neighbors, ridges, and +geometric information. libqhull.h defines the main +data structures. Frequently used routines are in poly.c +while infrequent ones are in poly2.c.
    + +
     
    +
    Qhull, +libqhull.c, +libqhull.h, +qhull_a.h, +unix.c , +qconvex.c , +qdelaun.c , +qhalf.c , +qvoronoi.c
    +
    Top-level routines. The Quickhull algorithm is +implemented by libqhull.c. qhull_a.h +includes all header files.
    + +
     
    +
    Set, +qset.h, +qset.c
    +
    Set routines. Qhull implements its data structures as +sets. A set is an array of pointers that is expanded as +needed. This is a separate package that may be used in +other applications.
    + +
     
    +
    Stat, +stat.h, +stat.c
    +
    Statistical routines. Qhull maintains statistics about +its implementation.
    + +
     
    +
    User, +user.h, +user.c, +user_eg.c, +user_eg2.c, +
    +
    User-defined routines. Qhull allows the user to configure +the code with defined constants and specialized routines. +
    +
    +
    + +
    +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull files
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    + +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/io.c b/xs/src/qhull/src/libqhull/io.c new file mode 100644 index 0000000000..401987ec08 --- /dev/null +++ b/xs/src/qhull/src/libqhull/io.c @@ -0,0 +1,4062 @@ +/*
      ---------------------------------
    +
    +   io.c
    +   Input/Output routines of qhull application
    +
    +   see qh-io.htm and io.h
    +
    +   see user.c for qh_errprint and qh_printfacetlist
    +
    +   unix.c calls qh_readpoints and qh_produce_output
    +
    +   unix.c and user.c are the only callers of io.c functions
    +   This allows the user to avoid loading io.o from qhull.a
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/io.c#5 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*========= -functions in alphabetical order after qh_produce_output()  =====*/
    +
    +/*---------------------------------
    +
    +  qh_produce_output()
    +  qh_produce_output2()
    +    prints out the result of qhull in desired format
    +    qh_produce_output2() does not call qh_prepare_output()
    +    if qh.GETarea
    +      computes and prints area and volume
    +    qh.PRINTout[] is an array of output formats
    +
    +  notes:
    +    prints output in qh.PRINTout order
    +*/
    +void qh_produce_output(void) {
    +    int tempsize= qh_setsize(qhmem.tempstack);
    +
    +    qh_prepare_output();
    +    qh_produce_output2();
    +    if (qh_setsize(qhmem.tempstack) != tempsize) {
    +        qh_fprintf(qh ferr, 6206, "qhull internal error (qh_produce_output): temporary sets not empty(%d)\n",
    +            qh_setsize(qhmem.tempstack));
    +        qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +} /* produce_output */
    +
    +
    +void qh_produce_output2(void) {
    +  int i, tempsize= qh_setsize(qhmem.tempstack), d_1;
    +
    +  if (qh PRINTsummary)
    +    qh_printsummary(qh ferr);
    +  else if (qh PRINTout[0] == qh_PRINTnone)
    +    qh_printsummary(qh fout);
    +  for (i=0; i < qh_PRINTEND; i++)
    +    qh_printfacets(qh fout, qh PRINTout[i], qh facet_list, NULL, !qh_ALL);
    +  qh_allstatistics();
    +  if (qh PRINTprecision && !qh MERGING && (qh JOGGLEmax > REALmax/2 || qh RERUN))
    +    qh_printstats(qh ferr, qhstat precision, NULL);
    +  if (qh VERIFYoutput && (zzval_(Zridge) > 0 || zzval_(Zridgemid) > 0))
    +    qh_printstats(qh ferr, qhstat vridges, NULL);
    +  if (qh PRINTstatistics) {
    +    qh_printstatistics(qh ferr, "");
    +    qh_memstatistics(qh ferr);
    +    d_1= sizeof(setT) + (qh hull_dim - 1) * SETelemsize;
    +    qh_fprintf(qh ferr, 8040, "\
    +    size in bytes: merge %d ridge %d vertex %d facet %d\n\
    +         normal %d ridge vertices %d facet vertices or neighbors %d\n",
    +            (int)sizeof(mergeT), (int)sizeof(ridgeT),
    +            (int)sizeof(vertexT), (int)sizeof(facetT),
    +            qh normal_size, d_1, d_1 + SETelemsize);
    +  }
    +  if (qh_setsize(qhmem.tempstack) != tempsize) {
    +    qh_fprintf(qh ferr, 6065, "qhull internal error (qh_produce_output2): temporary sets not empty(%d)\n",
    +             qh_setsize(qhmem.tempstack));
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +} /* produce_output2 */
    +
    +/*---------------------------------
    +
    +  qh_dfacet( id )
    +    print facet by id, for debugging
    +
    +*/
    +void qh_dfacet(unsigned id) {
    +  facetT *facet;
    +
    +  FORALLfacets {
    +    if (facet->id == id) {
    +      qh_printfacet(qh fout, facet);
    +      break;
    +    }
    +  }
    +} /* dfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_dvertex( id )
    +    print vertex by id, for debugging
    +*/
    +void qh_dvertex(unsigned id) {
    +  vertexT *vertex;
    +
    +  FORALLvertices {
    +    if (vertex->id == id) {
    +      qh_printvertex(qh fout, vertex);
    +      break;
    +    }
    +  }
    +} /* dvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_compare_facetarea( p1, p2 )
    +    used by qsort() to order facets by area
    +*/
    +int qh_compare_facetarea(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +
    +  if (!a->isarea)
    +    return -1;
    +  if (!b->isarea)
    +    return 1;
    +  if (a->f.area > b->f.area)
    +    return 1;
    +  else if (a->f.area == b->f.area)
    +    return 0;
    +  return -1;
    +} /* compare_facetarea */
    +
    +/*---------------------------------
    +
    +  qh_compare_facetmerge( p1, p2 )
    +    used by qsort() to order facets by number of merges
    +*/
    +int qh_compare_facetmerge(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +
    +  return(a->nummerge - b->nummerge);
    +} /* compare_facetvisit */
    +
    +/*---------------------------------
    +
    +  qh_compare_facetvisit( p1, p2 )
    +    used by qsort() to order facets by visit id or id
    +*/
    +int qh_compare_facetvisit(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +  int i,j;
    +
    +  if (!(i= a->visitid))
    +    i= 0 - a->id; /* do not convert to int, sign distinguishes id from visitid */
    +  if (!(j= b->visitid))
    +    j= 0 - b->id;
    +  return(i - j);
    +} /* compare_facetvisit */
    +
    +/*---------------------------------
    +
    +  qh_compare_vertexpoint( p1, p2 )
    +    used by qsort() to order vertices by point id
    +
    +  Not used.  Not available in libqhull_r.h since qh_pointid depends on qh
    +*/
    +int qh_compare_vertexpoint(const void *p1, const void *p2) {
    +  const vertexT *a= *((vertexT *const*)p1), *b= *((vertexT *const*)p2);
    +
    +  return((qh_pointid(a->point) > qh_pointid(b->point)?1:-1));
    +} /* compare_vertexpoint */
    +
    +/*---------------------------------
    +
    +  qh_copyfilename( dest, size, source, length )
    +    copy filename identified by qh_skipfilename()
    +
    +  notes:
    +    see qh_skipfilename() for syntax
    +*/
    +void qh_copyfilename(char *filename, int size, const char* source, int length) {
    +  char c= *source;
    +
    +  if (length > size + 1) {
    +      qh_fprintf(qh ferr, 6040, "qhull error: filename is more than %d characters, %s\n",  size-1, source);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  strncpy(filename, source, length);
    +  filename[length]= '\0';
    +  if (c == '\'' || c == '"') {
    +    char *s= filename + 1;
    +    char *t= filename;
    +    while (*s) {
    +      if (*s == c) {
    +          if (s[-1] == '\\')
    +              t[-1]= c;
    +      }else
    +          *t++= *s;
    +      s++;
    +    }
    +    *t= '\0';
    +  }
    +} /* copyfilename */
    +
    +/*---------------------------------
    +
    +  qh_countfacets( facetlist, facets, printall,
    +          numfacets, numsimplicial, totneighbors, numridges, numcoplanar, numtricoplanars  )
    +    count good facets for printing and set visitid
    +    if allfacets, ignores qh_skipfacet()
    +
    +  notes:
    +    qh_printsummary and qh_countfacets must match counts
    +
    +  returns:
    +    numfacets, numsimplicial, total neighbors, numridges, coplanars
    +    each facet with ->visitid indicating 1-relative position
    +      ->visitid==0 indicates not good
    +
    +  notes
    +    numfacets >= numsimplicial
    +    if qh.NEWfacets,
    +      does not count visible facets (matches qh_printafacet)
    +
    +  design:
    +    for all facets on facetlist and in facets set
    +      unless facet is skipped or visible (i.e., will be deleted)
    +        mark facet->visitid
    +        update counts
    +*/
    +void qh_countfacets(facetT *facetlist, setT *facets, boolT printall,
    +    int *numfacetsp, int *numsimplicialp, int *totneighborsp, int *numridgesp, int *numcoplanarsp, int *numtricoplanarsp) {
    +  facetT *facet, **facetp;
    +  int numfacets= 0, numsimplicial= 0, numridges= 0, totneighbors= 0, numcoplanars= 0, numtricoplanars= 0;
    +
    +  FORALLfacet_(facetlist) {
    +    if ((facet->visible && qh NEWfacets)
    +    || (!printall && qh_skipfacet(facet)))
    +      facet->visitid= 0;
    +    else {
    +      facet->visitid= ++numfacets;
    +      totneighbors += qh_setsize(facet->neighbors);
    +      if (facet->simplicial) {
    +        numsimplicial++;
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else
    +        numridges += qh_setsize(facet->ridges);
    +      if (facet->coplanarset)
    +        numcoplanars += qh_setsize(facet->coplanarset);
    +    }
    +  }
    +
    +  FOREACHfacet_(facets) {
    +    if ((facet->visible && qh NEWfacets)
    +    || (!printall && qh_skipfacet(facet)))
    +      facet->visitid= 0;
    +    else {
    +      facet->visitid= ++numfacets;
    +      totneighbors += qh_setsize(facet->neighbors);
    +      if (facet->simplicial){
    +        numsimplicial++;
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else
    +        numridges += qh_setsize(facet->ridges);
    +      if (facet->coplanarset)
    +        numcoplanars += qh_setsize(facet->coplanarset);
    +    }
    +  }
    +  qh visit_id += numfacets+1;
    +  *numfacetsp= numfacets;
    +  *numsimplicialp= numsimplicial;
    +  *totneighborsp= totneighbors;
    +  *numridgesp= numridges;
    +  *numcoplanarsp= numcoplanars;
    +  *numtricoplanarsp= numtricoplanars;
    +} /* countfacets */
    +
    +/*---------------------------------
    +
    +  qh_detvnorm( vertex, vertexA, centers, offset )
    +    compute separating plane of the Voronoi diagram for a pair of input sites
    +    centers= set of facets (i.e., Voronoi vertices)
    +      facet->visitid= 0 iff vertex-at-infinity (i.e., unbounded)
    +
    +  assumes:
    +    qh_ASvoronoi and qh_vertexneighbors() already set
    +
    +  returns:
    +    norm
    +      a pointer into qh.gm_matrix to qh.hull_dim-1 reals
    +      copy the data before reusing qh.gm_matrix
    +    offset
    +      if 'QVn'
    +        sign adjusted so that qh.GOODvertexp is inside
    +      else
    +        sign adjusted so that vertex is inside
    +
    +    qh.gm_matrix= simplex of points from centers relative to first center
    +
    +  notes:
    +    in io.c so that code for 'v Tv' can be removed by removing io.c
    +    returns pointer into qh.gm_matrix to avoid tracking of temporary memory
    +
    +  design:
    +    determine midpoint of input sites
    +    build points as the set of Voronoi vertices
    +    select a simplex from points (if necessary)
    +      include midpoint if the Voronoi region is unbounded
    +    relocate the first vertex of the simplex to the origin
    +    compute the normalized hyperplane through the simplex
    +    orient the hyperplane toward 'QVn' or 'vertex'
    +    if 'Tv' or 'Ts'
    +      if bounded
    +        test that hyperplane is the perpendicular bisector of the input sites
    +      test that Voronoi vertices not in the simplex are still on the hyperplane
    +    free up temporary memory
    +*/
    +pointT *qh_detvnorm(vertexT *vertex, vertexT *vertexA, setT *centers, realT *offsetp) {
    +  facetT *facet, **facetp;
    +  int  i, k, pointid, pointidA, point_i, point_n;
    +  setT *simplex= NULL;
    +  pointT *point, **pointp, *point0, *midpoint, *normal, *inpoint;
    +  coordT *coord, *gmcoord, *normalp;
    +  setT *points= qh_settemp(qh TEMPsize);
    +  boolT nearzero= False;
    +  boolT unbounded= False;
    +  int numcenters= 0;
    +  int dim= qh hull_dim - 1;
    +  realT dist, offset, angle, zero= 0.0;
    +
    +  midpoint= qh gm_matrix + qh hull_dim * qh hull_dim;  /* last row */
    +  for (k=0; k < dim; k++)
    +    midpoint[k]= (vertex->point[k] + vertexA->point[k])/2;
    +  FOREACHfacet_(centers) {
    +    numcenters++;
    +    if (!facet->visitid)
    +      unbounded= True;
    +    else {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(facet->vertices);
    +      qh_setappend(&points, facet->center);
    +    }
    +  }
    +  if (numcenters > dim) {
    +    simplex= qh_settemp(qh TEMPsize);
    +    qh_setappend(&simplex, vertex->point);
    +    if (unbounded)
    +      qh_setappend(&simplex, midpoint);
    +    qh_maxsimplex(dim, points, NULL, 0, &simplex);
    +    qh_setdelnth(simplex, 0);
    +  }else if (numcenters == dim) {
    +    if (unbounded)
    +      qh_setappend(&points, midpoint);
    +    simplex= points;
    +  }else {
    +    qh_fprintf(qh ferr, 6216, "qhull internal error (qh_detvnorm): too few points(%d) to compute separating plane\n", numcenters);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  i= 0;
    +  gmcoord= qh gm_matrix;
    +  point0= SETfirstt_(simplex, pointT);
    +  FOREACHpoint_(simplex) {
    +    if (qh IStracing >= 4)
    +      qh_printmatrix(qh ferr, "qh_detvnorm: Voronoi vertex or midpoint",
    +                              &point, 1, dim);
    +    if (point != point0) {
    +      qh gm_row[i++]= gmcoord;
    +      coord= point0;
    +      for (k=dim; k--; )
    +        *(gmcoord++)= *point++ - *coord++;
    +    }
    +  }
    +  qh gm_row[i]= gmcoord;  /* does not overlap midpoint, may be used later for qh_areasimplex */
    +  normal= gmcoord;
    +  qh_sethyperplane_gauss(dim, qh gm_row, point0, True,
    +                normal, &offset, &nearzero);
    +  if (qh GOODvertexp == vertexA->point)
    +    inpoint= vertexA->point;
    +  else
    +    inpoint= vertex->point;
    +  zinc_(Zdistio);
    +  dist= qh_distnorm(dim, inpoint, normal, &offset);
    +  if (dist > 0) {
    +    offset= -offset;
    +    normalp= normal;
    +    for (k=dim; k--; ) {
    +      *normalp= -(*normalp);
    +      normalp++;
    +    }
    +  }
    +  if (qh VERIFYoutput || qh PRINTstatistics) {
    +    pointid= qh_pointid(vertex->point);
    +    pointidA= qh_pointid(vertexA->point);
    +    if (!unbounded) {
    +      zinc_(Zdiststat);
    +      dist= qh_distnorm(dim, midpoint, normal, &offset);
    +      if (dist < 0)
    +        dist= -dist;
    +      zzinc_(Zridgemid);
    +      wwmax_(Wridgemidmax, dist);
    +      wwadd_(Wridgemid, dist);
    +      trace4((qh ferr, 4014, "qh_detvnorm: points %d %d midpoint dist %2.2g\n",
    +                 pointid, pointidA, dist));
    +      for (k=0; k < dim; k++)
    +        midpoint[k]= vertexA->point[k] - vertex->point[k];  /* overwrites midpoint! */
    +      qh_normalize(midpoint, dim, False);
    +      angle= qh_distnorm(dim, midpoint, normal, &zero); /* qh_detangle uses dim+1 */
    +      if (angle < 0.0)
    +        angle= angle + 1.0;
    +      else
    +        angle= angle - 1.0;
    +      if (angle < 0.0)
    +        angle -= angle;
    +      trace4((qh ferr, 4015, "qh_detvnorm: points %d %d angle %2.2g nearzero %d\n",
    +                 pointid, pointidA, angle, nearzero));
    +      if (nearzero) {
    +        zzinc_(Zridge0);
    +        wwmax_(Wridge0max, angle);
    +        wwadd_(Wridge0, angle);
    +      }else {
    +        zzinc_(Zridgeok)
    +        wwmax_(Wridgeokmax, angle);
    +        wwadd_(Wridgeok, angle);
    +      }
    +    }
    +    if (simplex != points) {
    +      FOREACHpoint_i_(points) {
    +        if (!qh_setin(simplex, point)) {
    +          facet= SETelemt_(centers, point_i, facetT);
    +          zinc_(Zdiststat);
    +          dist= qh_distnorm(dim, point, normal, &offset);
    +          if (dist < 0)
    +            dist= -dist;
    +          zzinc_(Zridge);
    +          wwmax_(Wridgemax, dist);
    +          wwadd_(Wridge, dist);
    +          trace4((qh ferr, 4016, "qh_detvnorm: points %d %d Voronoi vertex %d dist %2.2g\n",
    +                             pointid, pointidA, facet->visitid, dist));
    +        }
    +      }
    +    }
    +  }
    +  *offsetp= offset;
    +  if (simplex != points)
    +    qh_settempfree(&simplex);
    +  qh_settempfree(&points);
    +  return normal;
    +} /* detvnorm */
    +
    +/*---------------------------------
    +
    +  qh_detvridge( vertexA )
    +    determine Voronoi ridge from 'seen' neighbors of vertexA
    +    include one vertex-at-infinite if an !neighbor->visitid
    +
    +  returns:
    +    temporary set of centers (facets, i.e., Voronoi vertices)
    +    sorted by center id
    +*/
    +setT *qh_detvridge(vertexT *vertex) {
    +  setT *centers= qh_settemp(qh TEMPsize);
    +  setT *tricenters= qh_settemp(qh TEMPsize);
    +  facetT *neighbor, **neighborp;
    +  boolT firstinf= True;
    +
    +  FOREACHneighbor_(vertex) {
    +    if (neighbor->seen) {
    +      if (neighbor->visitid) {
    +        if (!neighbor->tricoplanar || qh_setunique(&tricenters, neighbor->center))
    +          qh_setappend(¢ers, neighbor);
    +      }else if (firstinf) {
    +        firstinf= False;
    +        qh_setappend(¢ers, neighbor);
    +      }
    +    }
    +  }
    +  qsort(SETaddr_(centers, facetT), (size_t)qh_setsize(centers),
    +             sizeof(facetT *), qh_compare_facetvisit);
    +  qh_settempfree(&tricenters);
    +  return centers;
    +} /* detvridge */
    +
    +/*---------------------------------
    +
    +  qh_detvridge3( atvertex, vertex )
    +    determine 3-d Voronoi ridge from 'seen' neighbors of atvertex and vertex
    +    include one vertex-at-infinite for !neighbor->visitid
    +    assumes all facet->seen2= True
    +
    +  returns:
    +    temporary set of centers (facets, i.e., Voronoi vertices)
    +    listed in adjacency order (!oriented)
    +    all facet->seen2= True
    +
    +  design:
    +    mark all neighbors of atvertex
    +    for each adjacent neighbor of both atvertex and vertex
    +      if neighbor selected
    +        add neighbor to set of Voronoi vertices
    +*/
    +setT *qh_detvridge3(vertexT *atvertex, vertexT *vertex) {
    +  setT *centers= qh_settemp(qh TEMPsize);
    +  setT *tricenters= qh_settemp(qh TEMPsize);
    +  facetT *neighbor, **neighborp, *facet= NULL;
    +  boolT firstinf= True;
    +
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen2= False;
    +  FOREACHneighbor_(vertex) {
    +    if (!neighbor->seen2) {
    +      facet= neighbor;
    +      break;
    +    }
    +  }
    +  while (facet) {
    +    facet->seen2= True;
    +    if (neighbor->seen) {
    +      if (facet->visitid) {
    +        if (!facet->tricoplanar || qh_setunique(&tricenters, facet->center))
    +          qh_setappend(¢ers, facet);
    +      }else if (firstinf) {
    +        firstinf= False;
    +        qh_setappend(¢ers, facet);
    +      }
    +    }
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->seen2) {
    +        if (qh_setin(vertex->neighbors, neighbor))
    +          break;
    +        else
    +          neighbor->seen2= True;
    +      }
    +    }
    +    facet= neighbor;
    +  }
    +  if (qh CHECKfrequently) {
    +    FOREACHneighbor_(vertex) {
    +      if (!neighbor->seen2) {
    +          qh_fprintf(qh ferr, 6217, "qhull internal error (qh_detvridge3): neighbors of vertex p%d are not connected at facet %d\n",
    +                 qh_pointid(vertex->point), neighbor->id);
    +        qh_errexit(qh_ERRqhull, neighbor, NULL);
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen2= True;
    +  qh_settempfree(&tricenters);
    +  return centers;
    +} /* detvridge3 */
    +
    +/*---------------------------------
    +
    +  qh_eachvoronoi( fp, printvridge, vertex, visitall, innerouter, inorder )
    +    if visitall,
    +      visit all Voronoi ridges for vertex (i.e., an input site)
    +    else
    +      visit all unvisited Voronoi ridges for vertex
    +      all vertex->seen= False if unvisited
    +    assumes
    +      all facet->seen= False
    +      all facet->seen2= True (for qh_detvridge3)
    +      all facet->visitid == 0 if vertex_at_infinity
    +                         == index of Voronoi vertex
    +                         >= qh.num_facets if ignored
    +    innerouter:
    +      qh_RIDGEall--  both inner (bounded) and outer(unbounded) ridges
    +      qh_RIDGEinner- only inner
    +      qh_RIDGEouter- only outer
    +
    +    if inorder
    +      orders vertices for 3-d Voronoi diagrams
    +
    +  returns:
    +    number of visited ridges (does not include previously visited ridges)
    +
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers)
    +        fp== any pointer (assumes FILE*)
    +        vertex,vertexA= pair of input sites that define a Voronoi ridge
    +        centers= set of facets (i.e., Voronoi vertices)
    +                 ->visitid == index or 0 if vertex_at_infinity
    +                 ordered for 3-d Voronoi diagram
    +  notes:
    +    uses qh.vertex_visit
    +
    +  see:
    +    qh_eachvoronoi_all()
    +
    +  design:
    +    mark selected neighbors of atvertex
    +    for each selected neighbor (either Voronoi vertex or vertex-at-infinity)
    +      for each unvisited vertex
    +        if atvertex and vertex share more than d-1 neighbors
    +          bump totalcount
    +          if printvridge defined
    +            build the set of shared neighbors (i.e., Voronoi vertices)
    +            call printvridge
    +*/
    +int qh_eachvoronoi(FILE *fp, printvridgeT printvridge, vertexT *atvertex, boolT visitall, qh_RIDGE innerouter, boolT inorder) {
    +  boolT unbounded;
    +  int count;
    +  facetT *neighbor, **neighborp, *neighborA, **neighborAp;
    +  setT *centers;
    +  setT *tricenters= qh_settemp(qh TEMPsize);
    +
    +  vertexT *vertex, **vertexp;
    +  boolT firstinf;
    +  unsigned int numfacets= (unsigned int)qh num_facets;
    +  int totridges= 0;
    +
    +  qh vertex_visit++;
    +  atvertex->seen= True;
    +  if (visitall) {
    +    FORALLvertices
    +      vertex->seen= False;
    +  }
    +  FOREACHneighbor_(atvertex) {
    +    if (neighbor->visitid < numfacets)
    +      neighbor->seen= True;
    +  }
    +  FOREACHneighbor_(atvertex) {
    +    if (neighbor->seen) {
    +      FOREACHvertex_(neighbor->vertices) {
    +        if (vertex->visitid != qh vertex_visit && !vertex->seen) {
    +          vertex->visitid= qh vertex_visit;
    +          count= 0;
    +          firstinf= True;
    +          qh_settruncate(tricenters, 0);
    +          FOREACHneighborA_(vertex) {
    +            if (neighborA->seen) {
    +              if (neighborA->visitid) {
    +                if (!neighborA->tricoplanar || qh_setunique(&tricenters, neighborA->center))
    +                  count++;
    +              }else if (firstinf) {
    +                count++;
    +                firstinf= False;
    +              }
    +            }
    +          }
    +          if (count >= qh hull_dim - 1) {  /* e.g., 3 for 3-d Voronoi */
    +            if (firstinf) {
    +              if (innerouter == qh_RIDGEouter)
    +                continue;
    +              unbounded= False;
    +            }else {
    +              if (innerouter == qh_RIDGEinner)
    +                continue;
    +              unbounded= True;
    +            }
    +            totridges++;
    +            trace4((qh ferr, 4017, "qh_eachvoronoi: Voronoi ridge of %d vertices between sites %d and %d\n",
    +                  count, qh_pointid(atvertex->point), qh_pointid(vertex->point)));
    +            if (printvridge && fp) {
    +              if (inorder && qh hull_dim == 3+1) /* 3-d Voronoi diagram */
    +                centers= qh_detvridge3(atvertex, vertex);
    +              else
    +                centers= qh_detvridge(vertex);
    +              (*printvridge)(fp, atvertex, vertex, centers, unbounded);
    +              qh_settempfree(¢ers);
    +            }
    +          }
    +        }
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen= False;
    +  qh_settempfree(&tricenters);
    +  return totridges;
    +} /* eachvoronoi */
    +
    +
    +/*---------------------------------
    +
    +  qh_eachvoronoi_all( fp, printvridge, isUpper, innerouter, inorder )
    +    visit all Voronoi ridges
    +
    +    innerouter:
    +      see qh_eachvoronoi()
    +
    +    if inorder
    +      orders vertices for 3-d Voronoi diagrams
    +
    +  returns
    +    total number of ridges
    +
    +    if isUpper == facet->upperdelaunay  (i.e., a Vornoi vertex)
    +      facet->visitid= Voronoi vertex index(same as 'o' format)
    +    else
    +      facet->visitid= 0
    +
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers)
    +      [see qh_eachvoronoi]
    +
    +  notes:
    +    Not used for qhull.exe
    +    same effect as qh_printvdiagram but ridges not sorted by point id
    +*/
    +int qh_eachvoronoi_all(FILE *fp, printvridgeT printvridge, boolT isUpper, qh_RIDGE innerouter, boolT inorder) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  int numcenters= 1;  /* vertex 0 is vertex-at-infinity */
    +  int totridges= 0;
    +
    +  qh_clearcenters(qh_ASvoronoi);
    +  qh_vertexneighbors();
    +  maximize_(qh visit_id, (unsigned) qh num_facets);
    +  FORALLfacets {
    +    facet->visitid= 0;
    +    facet->seen= False;
    +    facet->seen2= True;
    +  }
    +  FORALLfacets {
    +    if (facet->upperdelaunay == isUpper)
    +      facet->visitid= numcenters++;
    +  }
    +  FORALLvertices
    +    vertex->seen= False;
    +  FORALLvertices {
    +    if (qh GOODvertex > 0 && qh_pointid(vertex->point)+1 != qh GOODvertex)
    +      continue;
    +    totridges += qh_eachvoronoi(fp, printvridge, vertex,
    +                   !qh_ALL, innerouter, inorder);
    +  }
    +  return totridges;
    +} /* eachvoronoi_all */
    +
    +/*---------------------------------
    +
    +  qh_facet2point( facet, point0, point1, mindist )
    +    return two projected temporary vertices for a 2-d facet
    +    may be non-simplicial
    +
    +  returns:
    +    point0 and point1 oriented and projected to the facet
    +    returns mindist (maximum distance below plane)
    +*/
    +void qh_facet2point(facetT *facet, pointT **point0, pointT **point1, realT *mindist) {
    +  vertexT *vertex0, *vertex1;
    +  realT dist;
    +
    +  if (facet->toporient ^ qh_ORIENTclock) {
    +    vertex0= SETfirstt_(facet->vertices, vertexT);
    +    vertex1= SETsecondt_(facet->vertices, vertexT);
    +  }else {
    +    vertex1= SETfirstt_(facet->vertices, vertexT);
    +    vertex0= SETsecondt_(facet->vertices, vertexT);
    +  }
    +  zadd_(Zdistio, 2);
    +  qh_distplane(vertex0->point, facet, &dist);
    +  *mindist= dist;
    +  *point0= qh_projectpoint(vertex0->point, facet, dist);
    +  qh_distplane(vertex1->point, facet, &dist);
    +  minimize_(*mindist, dist);
    +  *point1= qh_projectpoint(vertex1->point, facet, dist);
    +} /* facet2point */
    +
    +
    +/*---------------------------------
    +
    +  qh_facetvertices( facetlist, facets, allfacets )
    +    returns temporary set of vertices in a set and/or list of facets
    +    if allfacets, ignores qh_skipfacet()
    +
    +  returns:
    +    vertices with qh.vertex_visit
    +
    +  notes:
    +    optimized for allfacets of facet_list
    +
    +  design:
    +    if allfacets of facet_list
    +      create vertex set from vertex_list
    +    else
    +      for each selected facet in facets or facetlist
    +        append unvisited vertices to vertex set
    +*/
    +setT *qh_facetvertices(facetT *facetlist, setT *facets, boolT allfacets) {
    +  setT *vertices;
    +  facetT *facet, **facetp;
    +  vertexT *vertex, **vertexp;
    +
    +  qh vertex_visit++;
    +  if (facetlist == qh facet_list && allfacets && !facets) {
    +    vertices= qh_settemp(qh num_vertices);
    +    FORALLvertices {
    +      vertex->visitid= qh vertex_visit;
    +      qh_setappend(&vertices, vertex);
    +    }
    +  }else {
    +    vertices= qh_settemp(qh TEMPsize);
    +    FORALLfacet_(facetlist) {
    +      if (!allfacets && qh_skipfacet(facet))
    +        continue;
    +      FOREACHvertex_(facet->vertices) {
    +        if (vertex->visitid != qh vertex_visit) {
    +          vertex->visitid= qh vertex_visit;
    +          qh_setappend(&vertices, vertex);
    +        }
    +      }
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (!allfacets && qh_skipfacet(facet))
    +      continue;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh vertex_visit) {
    +        vertex->visitid= qh vertex_visit;
    +        qh_setappend(&vertices, vertex);
    +      }
    +    }
    +  }
    +  return vertices;
    +} /* facetvertices */
    +
    +/*---------------------------------
    +
    +  qh_geomplanes( facet, outerplane, innerplane )
    +    return outer and inner planes for Geomview
    +    qh.PRINTradius is size of vertices and points (includes qh.JOGGLEmax)
    +
    +  notes:
    +    assume precise calculations in io.c with roundoff covered by qh_GEOMepsilon
    +*/
    +void qh_geomplanes(facetT *facet, realT *outerplane, realT *innerplane) {
    +  realT radius;
    +
    +  if (qh MERGING || qh JOGGLEmax < REALmax/2) {
    +    qh_outerinner(facet, outerplane, innerplane);
    +    radius= qh PRINTradius;
    +    if (qh JOGGLEmax < REALmax/2)
    +      radius -= qh JOGGLEmax * sqrt((realT)qh hull_dim);  /* already accounted for in qh_outerinner() */
    +    *outerplane += radius;
    +    *innerplane -= radius;
    +    if (qh PRINTcoplanar || qh PRINTspheres) {
    +      *outerplane += qh MAXabs_coord * qh_GEOMepsilon;
    +      *innerplane -= qh MAXabs_coord * qh_GEOMepsilon;
    +    }
    +  }else
    +    *innerplane= *outerplane= 0;
    +} /* geomplanes */
    +
    +
    +/*---------------------------------
    +
    +  qh_markkeep( facetlist )
    +    mark good facets that meet qh.KEEParea, qh.KEEPmerge, and qh.KEEPminArea
    +    ignores visible facets (!part of convex hull)
    +
    +  returns:
    +    may clear facet->good
    +    recomputes qh.num_good
    +
    +  design:
    +    get set of good facets
    +    if qh.KEEParea
    +      sort facets by area
    +      clear facet->good for all but n largest facets
    +    if qh.KEEPmerge
    +      sort facets by merge count
    +      clear facet->good for all but n most merged facets
    +    if qh.KEEPminarea
    +      clear facet->good if area too small
    +    update qh.num_good
    +*/
    +void qh_markkeep(facetT *facetlist) {
    +  facetT *facet, **facetp;
    +  setT *facets= qh_settemp(qh num_facets);
    +  int size, count;
    +
    +  trace2((qh ferr, 2006, "qh_markkeep: only keep %d largest and/or %d most merged facets and/or min area %.2g\n",
    +          qh KEEParea, qh KEEPmerge, qh KEEPminArea));
    +  FORALLfacet_(facetlist) {
    +    if (!facet->visible && facet->good)
    +      qh_setappend(&facets, facet);
    +  }
    +  size= qh_setsize(facets);
    +  if (qh KEEParea) {
    +    qsort(SETaddr_(facets, facetT), (size_t)size,
    +             sizeof(facetT *), qh_compare_facetarea);
    +    if ((count= size - qh KEEParea) > 0) {
    +      FOREACHfacet_(facets) {
    +        facet->good= False;
    +        if (--count == 0)
    +          break;
    +      }
    +    }
    +  }
    +  if (qh KEEPmerge) {
    +    qsort(SETaddr_(facets, facetT), (size_t)size,
    +             sizeof(facetT *), qh_compare_facetmerge);
    +    if ((count= size - qh KEEPmerge) > 0) {
    +      FOREACHfacet_(facets) {
    +        facet->good= False;
    +        if (--count == 0)
    +          break;
    +      }
    +    }
    +  }
    +  if (qh KEEPminArea < REALmax/2) {
    +    FOREACHfacet_(facets) {
    +      if (!facet->isarea || facet->f.area < qh KEEPminArea)
    +        facet->good= False;
    +    }
    +  }
    +  qh_settempfree(&facets);
    +  count= 0;
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      count++;
    +  }
    +  qh num_good= count;
    +} /* markkeep */
    +
    +
    +/*---------------------------------
    +
    +  qh_markvoronoi( facetlist, facets, printall, isLower, numcenters )
    +    mark voronoi vertices for printing by site pairs
    +
    +  returns:
    +    temporary set of vertices indexed by pointid
    +    isLower set if printing lower hull (i.e., at least one facet is lower hull)
    +    numcenters= total number of Voronoi vertices
    +    bumps qh.printoutnum for vertex-at-infinity
    +    clears all facet->seen and sets facet->seen2
    +
    +    if selected
    +      facet->visitid= Voronoi vertex id
    +    else if upper hull (or 'Qu' and lower hull)
    +      facet->visitid= 0
    +    else
    +      facet->visitid >= qh num_facets
    +
    +  notes:
    +    ignores qh.ATinfinity, if defined
    +*/
    +setT *qh_markvoronoi(facetT *facetlist, setT *facets, boolT printall, boolT *isLowerp, int *numcentersp) {
    +  int numcenters=0;
    +  facetT *facet, **facetp;
    +  setT *vertices;
    +  boolT isLower= False;
    +
    +  qh printoutnum++;
    +  qh_clearcenters(qh_ASvoronoi);  /* in case, qh_printvdiagram2 called by user */
    +  qh_vertexneighbors();
    +  vertices= qh_pointvertex();
    +  if (qh ATinfinity)
    +    SETelem_(vertices, qh num_points-1)= NULL;
    +  qh visit_id++;
    +  maximize_(qh visit_id, (unsigned) qh num_facets);
    +  FORALLfacet_(facetlist) {
    +    if (printall || !qh_skipfacet(facet)) {
    +      if (!facet->upperdelaunay) {
    +        isLower= True;
    +        break;
    +      }
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (printall || !qh_skipfacet(facet)) {
    +      if (!facet->upperdelaunay) {
    +        isLower= True;
    +        break;
    +      }
    +    }
    +  }
    +  FORALLfacets {
    +    if (facet->normal && (facet->upperdelaunay == isLower))
    +      facet->visitid= 0;  /* facetlist or facets may overwrite */
    +    else
    +      facet->visitid= qh visit_id;
    +    facet->seen= False;
    +    facet->seen2= True;
    +  }
    +  numcenters++;  /* qh_INFINITE */
    +  FORALLfacet_(facetlist) {
    +    if (printall || !qh_skipfacet(facet))
    +      facet->visitid= numcenters++;
    +  }
    +  FOREACHfacet_(facets) {
    +    if (printall || !qh_skipfacet(facet))
    +      facet->visitid= numcenters++;
    +  }
    +  *isLowerp= isLower;
    +  *numcentersp= numcenters;
    +  trace2((qh ferr, 2007, "qh_markvoronoi: isLower %d numcenters %d\n", isLower, numcenters));
    +  return vertices;
    +} /* markvoronoi */
    +
    +/*---------------------------------
    +
    +  qh_order_vertexneighbors( vertex )
    +    order facet neighbors of a 2-d or 3-d vertex by adjacency
    +
    +  notes:
    +    does not orient the neighbors
    +
    +  design:
    +    initialize a new neighbor set with the first facet in vertex->neighbors
    +    while vertex->neighbors non-empty
    +      select next neighbor in the previous facet's neighbor set
    +    set vertex->neighbors to the new neighbor set
    +*/
    +void qh_order_vertexneighbors(vertexT *vertex) {
    +  setT *newset;
    +  facetT *facet, *neighbor, **neighborp;
    +
    +  trace4((qh ferr, 4018, "qh_order_vertexneighbors: order neighbors of v%d for 3-d\n", vertex->id));
    +  newset= qh_settemp(qh_setsize(vertex->neighbors));
    +  facet= (facetT*)qh_setdellast(vertex->neighbors);
    +  qh_setappend(&newset, facet);
    +  while (qh_setsize(vertex->neighbors)) {
    +    FOREACHneighbor_(vertex) {
    +      if (qh_setin(facet->neighbors, neighbor)) {
    +        qh_setdel(vertex->neighbors, neighbor);
    +        qh_setappend(&newset, neighbor);
    +        facet= neighbor;
    +        break;
    +      }
    +    }
    +    if (!neighbor) {
    +      qh_fprintf(qh ferr, 6066, "qhull internal error (qh_order_vertexneighbors): no neighbor of v%d for f%d\n",
    +        vertex->id, facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +  }
    +  qh_setfree(&vertex->neighbors);
    +  qh_settemppop();
    +  vertex->neighbors= newset;
    +} /* order_vertexneighbors */
    +
    +/*---------------------------------
    +
    +  qh_prepare_output( )
    +    prepare for qh_produce_output2() according to
    +      qh.KEEPminArea, KEEParea, KEEPmerge, GOODvertex, GOODthreshold, GOODpoint, ONLYgood, SPLITthresholds
    +    does not reset facet->good
    +
    +  notes
    +    except for PRINTstatistics, no-op if previously called with same options
    +*/
    +void qh_prepare_output(void) {
    +  if (qh VORONOI) {
    +    qh_clearcenters(qh_ASvoronoi);  /* must be before qh_triangulate */
    +    qh_vertexneighbors();
    +  }
    +  if (qh TRIangulate && !qh hasTriangulation) {
    +    qh_triangulate();
    +    if (qh VERIFYoutput && !qh CHECKfrequently)
    +      qh_checkpolygon(qh facet_list);
    +  }
    +  qh_findgood_all(qh facet_list);
    +  if (qh GETarea)
    +    qh_getarea(qh facet_list);
    +  if (qh KEEParea || qh KEEPmerge || qh KEEPminArea < REALmax/2)
    +    qh_markkeep(qh facet_list);
    +  if (qh PRINTstatistics)
    +    qh_collectstatistics();
    +}
    +
    +/*---------------------------------
    +
    +  qh_printafacet( fp, format, facet, printall )
    +    print facet to fp in given output format (see qh.PRINTout)
    +
    +  returns:
    +    nop if !printall and qh_skipfacet()
    +    nop if visible facet and NEWfacets and format != PRINTfacets
    +    must match qh_countfacets
    +
    +  notes
    +    preserves qh.visit_id
    +    facet->normal may be null if PREmerge/MERGEexact and STOPcone before merge
    +
    +  see
    +    qh_printbegin() and qh_printend()
    +
    +  design:
    +    test for printing facet
    +    call appropriate routine for format
    +    or output results directly
    +*/
    +void qh_printafacet(FILE *fp, qh_PRINT format, facetT *facet, boolT printall) {
    +  realT color[4], offset, dist, outerplane, innerplane;
    +  boolT zerodiv;
    +  coordT *point, *normp, *coordp, **pointp, *feasiblep;
    +  int k;
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +
    +  if (!printall && qh_skipfacet(facet))
    +    return;
    +  if (facet->visible && qh NEWfacets && format != qh_PRINTfacets)
    +    return;
    +  qh printoutnum++;
    +  switch (format) {
    +  case qh_PRINTarea:
    +    if (facet->isarea) {
    +      qh_fprintf(fp, 9009, qh_REAL_1, facet->f.area);
    +      qh_fprintf(fp, 9010, "\n");
    +    }else
    +      qh_fprintf(fp, 9011, "0\n");
    +    break;
    +  case qh_PRINTcoplanars:
    +    qh_fprintf(fp, 9012, "%d", qh_setsize(facet->coplanarset));
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_fprintf(fp, 9013, " %d", qh_pointid(point));
    +    qh_fprintf(fp, 9014, "\n");
    +    break;
    +  case qh_PRINTcentrums:
    +    qh_printcenter(fp, format, NULL, facet);
    +    break;
    +  case qh_PRINTfacets:
    +    qh_printfacet(fp, facet);
    +    break;
    +  case qh_PRINTfacets_xridge:
    +    qh_printfacetheader(fp, facet);
    +    break;
    +  case qh_PRINTgeom:  /* either 2 , 3, or 4-d by qh_printbegin */
    +    if (!facet->normal)
    +      break;
    +    for (k=qh hull_dim; k--; ) {
    +      color[k]= (facet->normal[k]+1.0)/2.0;
    +      maximize_(color[k], -1.0);
    +      minimize_(color[k], +1.0);
    +    }
    +    qh_projectdim3(color, color);
    +    if (qh PRINTdim != qh hull_dim)
    +      qh_normalize2(color, 3, True, NULL, NULL);
    +    if (qh hull_dim <= 2)
    +      qh_printfacet2geom(fp, facet, color);
    +    else if (qh hull_dim == 3) {
    +      if (facet->simplicial)
    +        qh_printfacet3geom_simplicial(fp, facet, color);
    +      else
    +        qh_printfacet3geom_nonsimplicial(fp, facet, color);
    +    }else {
    +      if (facet->simplicial)
    +        qh_printfacet4geom_simplicial(fp, facet, color);
    +      else
    +        qh_printfacet4geom_nonsimplicial(fp, facet, color);
    +    }
    +    break;
    +  case qh_PRINTids:
    +    qh_fprintf(fp, 9015, "%d\n", facet->id);
    +    break;
    +  case qh_PRINTincidences:
    +  case qh_PRINToff:
    +  case qh_PRINTtriangles:
    +    if (qh hull_dim == 3 && format != qh_PRINTtriangles)
    +      qh_printfacet3vertex(fp, facet, format);
    +    else if (facet->simplicial || qh hull_dim == 2 || format == qh_PRINToff)
    +      qh_printfacetNvertex_simplicial(fp, facet, format);
    +    else
    +      qh_printfacetNvertex_nonsimplicial(fp, facet, qh printoutvar++, format);
    +    break;
    +  case qh_PRINTinner:
    +    qh_outerinner(facet, NULL, &innerplane);
    +    offset= facet->offset - innerplane;
    +    goto LABELprintnorm;
    +    break; /* prevent warning */
    +  case qh_PRINTmerges:
    +    qh_fprintf(fp, 9016, "%d\n", facet->nummerge);
    +    break;
    +  case qh_PRINTnormals:
    +    offset= facet->offset;
    +    goto LABELprintnorm;
    +    break; /* prevent warning */
    +  case qh_PRINTouter:
    +    qh_outerinner(facet, &outerplane, NULL);
    +    offset= facet->offset - outerplane;
    +  LABELprintnorm:
    +    if (!facet->normal) {
    +      qh_fprintf(fp, 9017, "no normal for facet f%d\n", facet->id);
    +      break;
    +    }
    +    if (qh CDDoutput) {
    +      qh_fprintf(fp, 9018, qh_REAL_1, -offset);
    +      for (k=0; k < qh hull_dim; k++)
    +        qh_fprintf(fp, 9019, qh_REAL_1, -facet->normal[k]);
    +    }else {
    +      for (k=0; k < qh hull_dim; k++)
    +        qh_fprintf(fp, 9020, qh_REAL_1, facet->normal[k]);
    +      qh_fprintf(fp, 9021, qh_REAL_1, offset);
    +    }
    +    qh_fprintf(fp, 9022, "\n");
    +    break;
    +  case qh_PRINTmathematica:  /* either 2 or 3-d by qh_printbegin */
    +  case qh_PRINTmaple:
    +    if (qh hull_dim == 2)
    +      qh_printfacet2math(fp, facet, format, qh printoutvar++);
    +    else
    +      qh_printfacet3math(fp, facet, format, qh printoutvar++);
    +    break;
    +  case qh_PRINTneighbors:
    +    qh_fprintf(fp, 9023, "%d", qh_setsize(facet->neighbors));
    +    FOREACHneighbor_(facet)
    +      qh_fprintf(fp, 9024, " %d",
    +               neighbor->visitid ? neighbor->visitid - 1: 0 - neighbor->id);
    +    qh_fprintf(fp, 9025, "\n");
    +    break;
    +  case qh_PRINTpointintersect:
    +    if (!qh feasible_point) {
    +      qh_fprintf(qh ferr, 6067, "qhull input error (qh_printafacet): option 'Fp' needs qh feasible_point\n");
    +      qh_errexit( qh_ERRinput, NULL, NULL);
    +    }
    +    if (facet->offset > 0)
    +      goto LABELprintinfinite;
    +    point= coordp= (coordT*)qh_memalloc(qh normal_size);
    +    normp= facet->normal;
    +    feasiblep= qh feasible_point;
    +    if (facet->offset < -qh MINdenom) {
    +      for (k=qh hull_dim; k--; )
    +        *(coordp++)= (*(normp++) / - facet->offset) + *(feasiblep++);
    +    }else {
    +      for (k=qh hull_dim; k--; ) {
    +        *(coordp++)= qh_divzero(*(normp++), facet->offset, qh MINdenom_1,
    +                                 &zerodiv) + *(feasiblep++);
    +        if (zerodiv) {
    +          qh_memfree(point, qh normal_size);
    +          goto LABELprintinfinite;
    +        }
    +      }
    +    }
    +    qh_printpoint(fp, NULL, point);
    +    qh_memfree(point, qh normal_size);
    +    break;
    +  LABELprintinfinite:
    +    for (k=qh hull_dim; k--; )
    +      qh_fprintf(fp, 9026, qh_REAL_1, qh_INFINITE);
    +    qh_fprintf(fp, 9027, "\n");
    +    break;
    +  case qh_PRINTpointnearest:
    +    FOREACHpoint_(facet->coplanarset) {
    +      int id, id2;
    +      vertex= qh_nearvertex(facet, point, &dist);
    +      id= qh_pointid(vertex->point);
    +      id2= qh_pointid(point);
    +      qh_fprintf(fp, 9028, "%d %d %d " qh_REAL_1 "\n", id, id2, facet->id, dist);
    +    }
    +    break;
    +  case qh_PRINTpoints:  /* VORONOI only by qh_printbegin */
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9029, "1 ");
    +    qh_printcenter(fp, format, NULL, facet);
    +    break;
    +  case qh_PRINTvertices:
    +    qh_fprintf(fp, 9030, "%d", qh_setsize(facet->vertices));
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(fp, 9031, " %d", qh_pointid(vertex->point));
    +    qh_fprintf(fp, 9032, "\n");
    +    break;
    +  default:
    +    break;
    +  }
    +} /* printafacet */
    +
    +/*---------------------------------
    +
    +  qh_printbegin(  )
    +    prints header for all output formats
    +
    +  returns:
    +    checks for valid format
    +
    +  notes:
    +    uses qh.visit_id for 3/4off
    +    changes qh.interior_point if printing centrums
    +    qh_countfacets clears facet->visitid for non-good facets
    +
    +  see
    +    qh_printend() and qh_printafacet()
    +
    +  design:
    +    count facets and related statistics
    +    print header for format
    +*/
    +void qh_printbegin(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numcoplanars, numtricoplanars;
    +  int i, num;
    +  facetT *facet, **facetp;
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +  pointT *point, **pointp, *pointtemp;
    +
    +  qh printoutnum= 0;
    +  qh_countfacets(facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);
    +  switch (format) {
    +  case qh_PRINTnone:
    +    break;
    +  case qh_PRINTarea:
    +    qh_fprintf(fp, 9033, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTcoplanars:
    +    qh_fprintf(fp, 9034, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTcentrums:
    +    if (qh CENTERtype == qh_ASnone)
    +      qh_clearcenters(qh_AScentrum);
    +    qh_fprintf(fp, 9035, "%d\n%d\n", qh hull_dim, numfacets);
    +    break;
    +  case qh_PRINTfacets:
    +  case qh_PRINTfacets_xridge:
    +    if (facetlist)
    +      qh_printvertexlist(fp, "Vertices and facets:\n", facetlist, facets, printall);
    +    break;
    +  case qh_PRINTgeom:
    +    if (qh hull_dim > 4)  /* qh_initqhull_globals also checks */
    +      goto LABELnoformat;
    +    if (qh VORONOI && qh hull_dim > 3)  /* PRINTdim == DROPdim == hull_dim-1 */
    +      goto LABELnoformat;
    +    if (qh hull_dim == 2 && (qh PRINTridges || qh DOintersections))
    +      qh_fprintf(qh ferr, 7049, "qhull warning: output for ridges and intersections not implemented in 2-d\n");
    +    if (qh hull_dim == 4 && (qh PRINTinner || qh PRINTouter ||
    +                             (qh PRINTdim == 4 && qh PRINTcentrums)))
    +      qh_fprintf(qh ferr, 7050, "qhull warning: output for outer/inner planes and centrums not implemented in 4-d\n");
    +    if (qh PRINTdim == 4 && (qh PRINTspheres))
    +      qh_fprintf(qh ferr, 7051, "qhull warning: output for vertices not implemented in 4-d\n");
    +    if (qh PRINTdim == 4 && qh DOintersections && qh PRINTnoplanes)
    +      qh_fprintf(qh ferr, 7052, "qhull warning: 'Gnh' generates no output in 4-d\n");
    +    if (qh PRINTdim == 2) {
    +      qh_fprintf(fp, 9036, "{appearance {linewidth 3} LIST # %s | %s\n",
    +              qh rbox_command, qh qhull_command);
    +    }else if (qh PRINTdim == 3) {
    +      qh_fprintf(fp, 9037, "{appearance {+edge -evert linewidth 2} LIST # %s | %s\n",
    +              qh rbox_command, qh qhull_command);
    +    }else if (qh PRINTdim == 4) {
    +      qh visit_id++;
    +      num= 0;
    +      FORALLfacet_(facetlist)    /* get number of ridges to be printed */
    +        qh_printend4geom(NULL, facet, &num, printall);
    +      FOREACHfacet_(facets)
    +        qh_printend4geom(NULL, facet, &num, printall);
    +      qh ridgeoutnum= num;
    +      qh printoutvar= 0;  /* counts number of ridges in output */
    +      qh_fprintf(fp, 9038, "LIST # %s | %s\n", qh rbox_command, qh qhull_command);
    +    }
    +
    +    if (qh PRINTdots) {
    +      qh printoutnum++;
    +      num= qh num_points + qh_setsize(qh other_points);
    +      if (qh DELAUNAY && qh ATinfinity)
    +        num--;
    +      if (qh PRINTdim == 4)
    +        qh_fprintf(fp, 9039, "4VECT %d %d 1\n", num, num);
    +      else
    +        qh_fprintf(fp, 9040, "VECT %d %d 1\n", num, num);
    +
    +      for (i=num; i--; ) {
    +        if (i % 20 == 0)
    +          qh_fprintf(fp, 9041, "\n");
    +        qh_fprintf(fp, 9042, "1 ");
    +      }
    +      qh_fprintf(fp, 9043, "# 1 point per line\n1 ");
    +      for (i=num-1; i--; ) { /* num at least 3 for D2 */
    +        if (i % 20 == 0)
    +          qh_fprintf(fp, 9044, "\n");
    +        qh_fprintf(fp, 9045, "0 ");
    +      }
    +      qh_fprintf(fp, 9046, "# 1 color for all\n");
    +      FORALLpoints {
    +        if (!qh DELAUNAY || !qh ATinfinity || qh_pointid(point) != qh num_points-1) {
    +          if (qh PRINTdim == 4)
    +            qh_printpoint(fp, NULL, point);
    +            else
    +              qh_printpoint3(fp, point);
    +        }
    +      }
    +      FOREACHpoint_(qh other_points) {
    +        if (qh PRINTdim == 4)
    +          qh_printpoint(fp, NULL, point);
    +        else
    +          qh_printpoint3(fp, point);
    +      }
    +      qh_fprintf(fp, 9047, "0 1 1 1  # color of points\n");
    +    }
    +
    +    if (qh PRINTdim == 4  && !qh PRINTnoplanes)
    +      /* 4dview loads up multiple 4OFF objects slowly */
    +      qh_fprintf(fp, 9048, "4OFF %d %d 1\n", 3*qh ridgeoutnum, qh ridgeoutnum);
    +    qh PRINTcradius= 2 * qh DISTround;  /* include test DISTround */
    +    if (qh PREmerge) {
    +      maximize_(qh PRINTcradius, qh premerge_centrum + qh DISTround);
    +    }else if (qh POSTmerge)
    +      maximize_(qh PRINTcradius, qh postmerge_centrum + qh DISTround);
    +    qh PRINTradius= qh PRINTcradius;
    +    if (qh PRINTspheres + qh PRINTcoplanar)
    +      maximize_(qh PRINTradius, qh MAXabs_coord * qh_MINradius);
    +    if (qh premerge_cos < REALmax/2) {
    +      maximize_(qh PRINTradius, (1- qh premerge_cos) * qh MAXabs_coord);
    +    }else if (!qh PREmerge && qh POSTmerge && qh postmerge_cos < REALmax/2) {
    +      maximize_(qh PRINTradius, (1- qh postmerge_cos) * qh MAXabs_coord);
    +    }
    +    maximize_(qh PRINTradius, qh MINvisible);
    +    if (qh JOGGLEmax < REALmax/2)
    +      qh PRINTradius += qh JOGGLEmax * sqrt((realT)qh hull_dim);
    +    if (qh PRINTdim != 4 &&
    +        (qh PRINTcoplanar || qh PRINTspheres || qh PRINTcentrums)) {
    +      vertices= qh_facetvertices(facetlist, facets, printall);
    +      if (qh PRINTspheres && qh PRINTdim <= 3)
    +        qh_printspheres(fp, vertices, qh PRINTradius);
    +      if (qh PRINTcoplanar || qh PRINTcentrums) {
    +        qh firstcentrum= True;
    +        if (qh PRINTcoplanar&& !qh PRINTspheres) {
    +          FOREACHvertex_(vertices)
    +            qh_printpointvect2(fp, vertex->point, NULL, qh interior_point, qh PRINTradius);
    +        }
    +        FORALLfacet_(facetlist) {
    +          if (!printall && qh_skipfacet(facet))
    +            continue;
    +          if (!facet->normal)
    +            continue;
    +          if (qh PRINTcentrums && qh PRINTdim <= 3)
    +            qh_printcentrum(fp, facet, qh PRINTcradius);
    +          if (!qh PRINTcoplanar)
    +            continue;
    +          FOREACHpoint_(facet->coplanarset)
    +            qh_printpointvect2(fp, point, facet->normal, NULL, qh PRINTradius);
    +          FOREACHpoint_(facet->outsideset)
    +            qh_printpointvect2(fp, point, facet->normal, NULL, qh PRINTradius);
    +        }
    +        FOREACHfacet_(facets) {
    +          if (!printall && qh_skipfacet(facet))
    +            continue;
    +          if (!facet->normal)
    +            continue;
    +          if (qh PRINTcentrums && qh PRINTdim <= 3)
    +            qh_printcentrum(fp, facet, qh PRINTcradius);
    +          if (!qh PRINTcoplanar)
    +            continue;
    +          FOREACHpoint_(facet->coplanarset)
    +            qh_printpointvect2(fp, point, facet->normal, NULL, qh PRINTradius);
    +          FOREACHpoint_(facet->outsideset)
    +            qh_printpointvect2(fp, point, facet->normal, NULL, qh PRINTradius);
    +        }
    +      }
    +      qh_settempfree(&vertices);
    +    }
    +    qh visit_id++; /* for printing hyperplane intersections */
    +    break;
    +  case qh_PRINTids:
    +    qh_fprintf(fp, 9049, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTincidences:
    +    if (qh VORONOI && qh PRINTprecision)
    +      qh_fprintf(qh ferr, 7053, "qhull warning: writing Delaunay.  Use 'p' or 'o' for Voronoi centers\n");
    +    qh printoutvar= qh vertex_id;  /* centrum id for non-simplicial facets */
    +    if (qh hull_dim <= 3)
    +      qh_fprintf(fp, 9050, "%d\n", numfacets);
    +    else
    +      qh_fprintf(fp, 9051, "%d\n", numsimplicial+numridges);
    +    break;
    +  case qh_PRINTinner:
    +  case qh_PRINTnormals:
    +  case qh_PRINTouter:
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9052, "%s | %s\nbegin\n    %d %d real\n", qh rbox_command,
    +            qh qhull_command, numfacets, qh hull_dim+1);
    +    else
    +      qh_fprintf(fp, 9053, "%d\n%d\n", qh hull_dim+1, numfacets);
    +    break;
    +  case qh_PRINTmathematica:
    +  case qh_PRINTmaple:
    +    if (qh hull_dim > 3)  /* qh_initbuffers also checks */
    +      goto LABELnoformat;
    +    if (qh VORONOI)
    +      qh_fprintf(qh ferr, 7054, "qhull warning: output is the Delaunay triangulation\n");
    +    if (format == qh_PRINTmaple) {
    +      if (qh hull_dim == 2)
    +        qh_fprintf(fp, 9054, "PLOT(CURVES(\n");
    +      else
    +        qh_fprintf(fp, 9055, "PLOT3D(POLYGONS(\n");
    +    }else
    +      qh_fprintf(fp, 9056, "{\n");
    +    qh printoutvar= 0;   /* counts number of facets for notfirst */
    +    break;
    +  case qh_PRINTmerges:
    +    qh_fprintf(fp, 9057, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTpointintersect:
    +    qh_fprintf(fp, 9058, "%d\n%d\n", qh hull_dim, numfacets);
    +    break;
    +  case qh_PRINTneighbors:
    +    qh_fprintf(fp, 9059, "%d\n", numfacets);
    +    break;
    +  case qh_PRINToff:
    +  case qh_PRINTtriangles:
    +    if (qh VORONOI)
    +      goto LABELnoformat;
    +    num = qh hull_dim;
    +    if (format == qh_PRINToff || qh hull_dim == 2)
    +      qh_fprintf(fp, 9060, "%d\n%d %d %d\n", num,
    +        qh num_points+qh_setsize(qh other_points), numfacets, totneighbors/2);
    +    else { /* qh_PRINTtriangles */
    +      qh printoutvar= qh num_points+qh_setsize(qh other_points); /* first centrum */
    +      if (qh DELAUNAY)
    +        num--;  /* drop last dimension */
    +      qh_fprintf(fp, 9061, "%d\n%d %d %d\n", num, qh printoutvar
    +        + numfacets - numsimplicial, numsimplicial + numridges, totneighbors/2);
    +    }
    +    FORALLpoints
    +      qh_printpointid(qh fout, NULL, num, point, qh_IDunknown);
    +    FOREACHpoint_(qh other_points)
    +      qh_printpointid(qh fout, NULL, num, point, qh_IDunknown);
    +    if (format == qh_PRINTtriangles && qh hull_dim > 2) {
    +      FORALLfacets {
    +        if (!facet->simplicial && facet->visitid)
    +          qh_printcenter(qh fout, format, NULL, facet);
    +      }
    +    }
    +    break;
    +  case qh_PRINTpointnearest:
    +    qh_fprintf(fp, 9062, "%d\n", numcoplanars);
    +    break;
    +  case qh_PRINTpoints:
    +    if (!qh VORONOI)
    +      goto LABELnoformat;
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9063, "%s | %s\nbegin\n%d %d real\n", qh rbox_command,
    +           qh qhull_command, numfacets, qh hull_dim);
    +    else
    +      qh_fprintf(fp, 9064, "%d\n%d\n", qh hull_dim-1, numfacets);
    +    break;
    +  case qh_PRINTvertices:
    +    qh_fprintf(fp, 9065, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTsummary:
    +  default:
    +  LABELnoformat:
    +    qh_fprintf(qh ferr, 6068, "qhull internal error (qh_printbegin): can not use this format for dimension %d\n",
    +         qh hull_dim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +} /* printbegin */
    +
    +/*---------------------------------
    +
    +  qh_printcenter( fp, string, facet )
    +    print facet->center as centrum or Voronoi center
    +    string may be NULL.  Don't include '%' codes.
    +    nop if qh CENTERtype neither CENTERvoronoi nor CENTERcentrum
    +    if upper envelope of Delaunay triangulation and point at-infinity
    +      prints qh_INFINITE instead;
    +
    +  notes:
    +    defines facet->center if needed
    +    if format=PRINTgeom, adds a 0 if would otherwise be 2-d
    +    Same as QhullFacet::printCenter
    +*/
    +void qh_printcenter(FILE *fp, qh_PRINT format, const char *string, facetT *facet) {
    +  int k, num;
    +
    +  if (qh CENTERtype != qh_ASvoronoi && qh CENTERtype != qh_AScentrum)
    +    return;
    +  if (string)
    +    qh_fprintf(fp, 9066, string);
    +  if (qh CENTERtype == qh_ASvoronoi) {
    +    num= qh hull_dim-1;
    +    if (!facet->normal || !facet->upperdelaunay || !qh ATinfinity) {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(facet->vertices);
    +      for (k=0; k < num; k++)
    +        qh_fprintf(fp, 9067, qh_REAL_1, facet->center[k]);
    +    }else {
    +      for (k=0; k < num; k++)
    +        qh_fprintf(fp, 9068, qh_REAL_1, qh_INFINITE);
    +    }
    +  }else /* qh.CENTERtype == qh_AScentrum */ {
    +    num= qh hull_dim;
    +    if (format == qh_PRINTtriangles && qh DELAUNAY)
    +      num--;
    +    if (!facet->center)
    +      facet->center= qh_getcentrum(facet);
    +    for (k=0; k < num; k++)
    +      qh_fprintf(fp, 9069, qh_REAL_1, facet->center[k]);
    +  }
    +  if (format == qh_PRINTgeom && num == 2)
    +    qh_fprintf(fp, 9070, " 0\n");
    +  else
    +    qh_fprintf(fp, 9071, "\n");
    +} /* printcenter */
    +
    +/*---------------------------------
    +
    +  qh_printcentrum( fp, facet, radius )
    +    print centrum for a facet in OOGL format
    +    radius defines size of centrum
    +    2-d or 3-d only
    +
    +  returns:
    +    defines facet->center if needed
    +*/
    +void qh_printcentrum(FILE *fp, facetT *facet, realT radius) {
    +  pointT *centrum, *projpt;
    +  boolT tempcentrum= False;
    +  realT xaxis[4], yaxis[4], normal[4], dist;
    +  realT green[3]={0, 1, 0};
    +  vertexT *apex;
    +  int k;
    +
    +  if (qh CENTERtype == qh_AScentrum) {
    +    if (!facet->center)
    +      facet->center= qh_getcentrum(facet);
    +    centrum= facet->center;
    +  }else {
    +    centrum= qh_getcentrum(facet);
    +    tempcentrum= True;
    +  }
    +  qh_fprintf(fp, 9072, "{appearance {-normal -edge normscale 0} ");
    +  if (qh firstcentrum) {
    +    qh firstcentrum= False;
    +    qh_fprintf(fp, 9073, "{INST geom { define centrum CQUAD  # f%d\n\
    +-0.3 -0.3 0.0001     0 0 1 1\n\
    + 0.3 -0.3 0.0001     0 0 1 1\n\
    + 0.3  0.3 0.0001     0 0 1 1\n\
    +-0.3  0.3 0.0001     0 0 1 1 } transform { \n", facet->id);
    +  }else
    +    qh_fprintf(fp, 9074, "{INST geom { : centrum } transform { # f%d\n", facet->id);
    +  apex= SETfirstt_(facet->vertices, vertexT);
    +  qh_distplane(apex->point, facet, &dist);
    +  projpt= qh_projectpoint(apex->point, facet, dist);
    +  for (k=qh hull_dim; k--; ) {
    +    xaxis[k]= projpt[k] - centrum[k];
    +    normal[k]= facet->normal[k];
    +  }
    +  if (qh hull_dim == 2) {
    +    xaxis[2]= 0;
    +    normal[2]= 0;
    +  }else if (qh hull_dim == 4) {
    +    qh_projectdim3(xaxis, xaxis);
    +    qh_projectdim3(normal, normal);
    +    qh_normalize2(normal, qh PRINTdim, True, NULL, NULL);
    +  }
    +  qh_crossproduct(3, xaxis, normal, yaxis);
    +  qh_fprintf(fp, 9075, "%8.4g %8.4g %8.4g 0\n", xaxis[0], xaxis[1], xaxis[2]);
    +  qh_fprintf(fp, 9076, "%8.4g %8.4g %8.4g 0\n", yaxis[0], yaxis[1], yaxis[2]);
    +  qh_fprintf(fp, 9077, "%8.4g %8.4g %8.4g 0\n", normal[0], normal[1], normal[2]);
    +  qh_printpoint3(fp, centrum);
    +  qh_fprintf(fp, 9078, "1 }}}\n");
    +  qh_memfree(projpt, qh normal_size);
    +  qh_printpointvect(fp, centrum, facet->normal, NULL, radius, green);
    +  if (tempcentrum)
    +    qh_memfree(centrum, qh normal_size);
    +} /* printcentrum */
    +
    +/*---------------------------------
    +
    +  qh_printend( fp, format )
    +    prints trailer for all output formats
    +
    +  see:
    +    qh_printbegin() and qh_printafacet()
    +
    +*/
    +void qh_printend(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int num;
    +  facetT *facet, **facetp;
    +
    +  if (!qh printoutnum)
    +    qh_fprintf(qh ferr, 7055, "qhull warning: no facets printed\n");
    +  switch (format) {
    +  case qh_PRINTgeom:
    +    if (qh hull_dim == 4 && qh DROPdim < 0  && !qh PRINTnoplanes) {
    +      qh visit_id++;
    +      num= 0;
    +      FORALLfacet_(facetlist)
    +        qh_printend4geom(fp, facet,&num, printall);
    +      FOREACHfacet_(facets)
    +        qh_printend4geom(fp, facet, &num, printall);
    +      if (num != qh ridgeoutnum || qh printoutvar != qh ridgeoutnum) {
    +        qh_fprintf(qh ferr, 6069, "qhull internal error (qh_printend): number of ridges %d != number printed %d and at end %d\n", qh ridgeoutnum, qh printoutvar, num);
    +        qh_errexit(qh_ERRqhull, NULL, NULL);
    +      }
    +    }else
    +      qh_fprintf(fp, 9079, "}\n");
    +    break;
    +  case qh_PRINTinner:
    +  case qh_PRINTnormals:
    +  case qh_PRINTouter:
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9080, "end\n");
    +    break;
    +  case qh_PRINTmaple:
    +    qh_fprintf(fp, 9081, "));\n");
    +    break;
    +  case qh_PRINTmathematica:
    +    qh_fprintf(fp, 9082, "}\n");
    +    break;
    +  case qh_PRINTpoints:
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9083, "end\n");
    +    break;
    +  default:
    +    break;
    +  }
    +} /* printend */
    +
    +/*---------------------------------
    +
    +  qh_printend4geom( fp, facet, numridges, printall )
    +    helper function for qh_printbegin/printend
    +
    +  returns:
    +    number of printed ridges
    +
    +  notes:
    +    just counts printed ridges if fp=NULL
    +    uses facet->visitid
    +    must agree with qh_printfacet4geom...
    +
    +  design:
    +    computes color for facet from its normal
    +    prints each ridge of facet
    +*/
    +void qh_printend4geom(FILE *fp, facetT *facet, int *nump, boolT printall) {
    +  realT color[3];
    +  int i, num= *nump;
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (!printall && qh_skipfacet(facet))
    +    return;
    +  if (qh PRINTnoplanes || (facet->visible && qh NEWfacets))
    +    return;
    +  if (!facet->normal)
    +    return;
    +  if (fp) {
    +    for (i=0; i < 3; i++) {
    +      color[i]= (facet->normal[i]+1.0)/2.0;
    +      maximize_(color[i], -1.0);
    +      minimize_(color[i], +1.0);
    +    }
    +  }
    +  facet->visitid= qh visit_id;
    +  if (facet->simplicial) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh visit_id) {
    +        if (fp)
    +          qh_fprintf(fp, 9084, "3 %d %d %d %8.4g %8.4g %8.4g 1 # f%d f%d\n",
    +                 3*num, 3*num+1, 3*num+2, color[0], color[1], color[2],
    +                 facet->id, neighbor->id);
    +        num++;
    +      }
    +    }
    +  }else {
    +    FOREACHridge_(facet->ridges) {
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->visitid != qh visit_id) {
    +        if (fp)
    +          qh_fprintf(fp, 9085, "3 %d %d %d %8.4g %8.4g %8.4g 1 #r%d f%d f%d\n",
    +                 3*num, 3*num+1, 3*num+2, color[0], color[1], color[2],
    +                 ridge->id, facet->id, neighbor->id);
    +        num++;
    +      }
    +    }
    +  }
    +  *nump= num;
    +} /* printend4geom */
    +
    +/*---------------------------------
    +
    +  qh_printextremes( fp, facetlist, facets, printall )
    +    print extreme points for convex hulls or halfspace intersections
    +
    +  notes:
    +    #points, followed by ids, one per line
    +
    +    sorted by id
    +    same order as qh_printpoints_out if no coplanar/interior points
    +*/
    +void qh_printextremes(FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices, *points;
    +  pointT *point;
    +  vertexT *vertex, **vertexp;
    +  int id;
    +  int numpoints=0, point_i, point_n;
    +  int allpoints= qh num_points + qh_setsize(qh other_points);
    +
    +  points= qh_settemp(allpoints);
    +  qh_setzero(points, 0, allpoints);
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  FOREACHvertex_(vertices) {
    +    id= qh_pointid(vertex->point);
    +    if (id >= 0) {
    +      SETelem_(points, id)= vertex->point;
    +      numpoints++;
    +    }
    +  }
    +  qh_settempfree(&vertices);
    +  qh_fprintf(fp, 9086, "%d\n", numpoints);
    +  FOREACHpoint_i_(points) {
    +    if (point)
    +      qh_fprintf(fp, 9087, "%d\n", point_i);
    +  }
    +  qh_settempfree(&points);
    +} /* printextremes */
    +
    +/*---------------------------------
    +
    +  qh_printextremes_2d( fp, facetlist, facets, printall )
    +    prints point ids for facets in qh_ORIENTclock order
    +
    +  notes:
    +    #points, followed by ids, one per line
    +    if facetlist/facets are disjoint than the output includes skips
    +    errors if facets form a loop
    +    does not print coplanar points
    +*/
    +void qh_printextremes_2d(FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numridges, totneighbors, numcoplanars, numsimplicial, numtricoplanars;
    +  setT *vertices;
    +  facetT *facet, *startfacet, *nextfacet;
    +  vertexT *vertexA, *vertexB;
    +
    +  qh_countfacets(facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars); /* marks qh visit_id */
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  qh_fprintf(fp, 9088, "%d\n", qh_setsize(vertices));
    +  qh_settempfree(&vertices);
    +  if (!numfacets)
    +    return;
    +  facet= startfacet= facetlist ? facetlist : SETfirstt_(facets, facetT);
    +  qh vertex_visit++;
    +  qh visit_id++;
    +  do {
    +    if (facet->toporient ^ qh_ORIENTclock) {
    +      vertexA= SETfirstt_(facet->vertices, vertexT);
    +      vertexB= SETsecondt_(facet->vertices, vertexT);
    +      nextfacet= SETfirstt_(facet->neighbors, facetT);
    +    }else {
    +      vertexA= SETsecondt_(facet->vertices, vertexT);
    +      vertexB= SETfirstt_(facet->vertices, vertexT);
    +      nextfacet= SETsecondt_(facet->neighbors, facetT);
    +    }
    +    if (facet->visitid == qh visit_id) {
    +      qh_fprintf(qh ferr, 6218, "Qhull internal error (qh_printextremes_2d): loop in facet list.  facet %d nextfacet %d\n",
    +                 facet->id, nextfacet->id);
    +      qh_errexit2(qh_ERRqhull, facet, nextfacet);
    +    }
    +    if (facet->visitid) {
    +      if (vertexA->visitid != qh vertex_visit) {
    +        vertexA->visitid= qh vertex_visit;
    +        qh_fprintf(fp, 9089, "%d\n", qh_pointid(vertexA->point));
    +      }
    +      if (vertexB->visitid != qh vertex_visit) {
    +        vertexB->visitid= qh vertex_visit;
    +        qh_fprintf(fp, 9090, "%d\n", qh_pointid(vertexB->point));
    +      }
    +    }
    +    facet->visitid= qh visit_id;
    +    facet= nextfacet;
    +  }while (facet && facet != startfacet);
    +} /* printextremes_2d */
    +
    +/*---------------------------------
    +
    +  qh_printextremes_d( fp, facetlist, facets, printall )
    +    print extreme points of input sites for Delaunay triangulations
    +
    +  notes:
    +    #points, followed by ids, one per line
    +
    +    unordered
    +*/
    +void qh_printextremes_d(FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices;
    +  vertexT *vertex, **vertexp;
    +  boolT upperseen, lowerseen;
    +  facetT *neighbor, **neighborp;
    +  int numpoints=0;
    +
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  qh_vertexneighbors();
    +  FOREACHvertex_(vertices) {
    +    upperseen= lowerseen= False;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay)
    +        upperseen= True;
    +      else
    +        lowerseen= True;
    +    }
    +    if (upperseen && lowerseen) {
    +      vertex->seen= True;
    +      numpoints++;
    +    }else
    +      vertex->seen= False;
    +  }
    +  qh_fprintf(fp, 9091, "%d\n", numpoints);
    +  FOREACHvertex_(vertices) {
    +    if (vertex->seen)
    +      qh_fprintf(fp, 9092, "%d\n", qh_pointid(vertex->point));
    +  }
    +  qh_settempfree(&vertices);
    +} /* printextremes_d */
    +
    +/*---------------------------------
    +
    +  qh_printfacet( fp, facet )
    +    prints all fields of a facet to fp
    +
    +  notes:
    +    ridges printed in neighbor order
    +*/
    +void qh_printfacet(FILE *fp, facetT *facet) {
    +
    +  qh_printfacetheader(fp, facet);
    +  if (facet->ridges)
    +    qh_printfacetridges(fp, facet);
    +} /* printfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet2geom( fp, facet, color )
    +    print facet as part of a 2-d VECT for Geomview
    +
    +    notes:
    +      assume precise calculations in io.c with roundoff covered by qh_GEOMepsilon
    +      mindist is calculated within io.c.  maxoutside is calculated elsewhere
    +      so a DISTround error may have occurred.
    +*/
    +void qh_printfacet2geom(FILE *fp, facetT *facet, realT color[3]) {
    +  pointT *point0, *point1;
    +  realT mindist, innerplane, outerplane;
    +  int k;
    +
    +  qh_facet2point(facet, &point0, &point1, &mindist);
    +  qh_geomplanes(facet, &outerplane, &innerplane);
    +  if (qh PRINTouter || (!qh PRINTnoplanes && !qh PRINTinner))
    +    qh_printfacet2geom_points(fp, point0, point1, facet, outerplane, color);
    +  if (qh PRINTinner || (!qh PRINTnoplanes && !qh PRINTouter &&
    +                outerplane - innerplane > 2 * qh MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet2geom_points(fp, point0, point1, facet, innerplane, color);
    +  }
    +  qh_memfree(point1, qh normal_size);
    +  qh_memfree(point0, qh normal_size);
    +} /* printfacet2geom */
    +
    +/*---------------------------------
    +
    +  qh_printfacet2geom_points( fp, point1, point2, facet, offset, color )
    +    prints a 2-d facet as a VECT with 2 points at some offset.
    +    The points are on the facet's plane.
    +*/
    +void qh_printfacet2geom_points(FILE *fp, pointT *point1, pointT *point2,
    +                               facetT *facet, realT offset, realT color[3]) {
    +  pointT *p1= point1, *p2= point2;
    +
    +  qh_fprintf(fp, 9093, "VECT 1 2 1 2 1 # f%d\n", facet->id);
    +  if (offset != 0.0) {
    +    p1= qh_projectpoint(p1, facet, -offset);
    +    p2= qh_projectpoint(p2, facet, -offset);
    +  }
    +  qh_fprintf(fp, 9094, "%8.4g %8.4g %8.4g\n%8.4g %8.4g %8.4g\n",
    +           p1[0], p1[1], 0.0, p2[0], p2[1], 0.0);
    +  if (offset != 0.0) {
    +    qh_memfree(p1, qh normal_size);
    +    qh_memfree(p2, qh normal_size);
    +  }
    +  qh_fprintf(fp, 9095, "%8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +} /* printfacet2geom_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet2math( fp, facet, format, notfirst )
    +    print 2-d Maple or Mathematica output for a facet
    +    may be non-simplicial
    +
    +  notes:
    +    use %16.8f since Mathematica 2.2 does not handle exponential format
    +    see qh_printfacet3math
    +*/
    +void qh_printfacet2math(FILE *fp, facetT *facet, qh_PRINT format, int notfirst) {
    +  pointT *point0, *point1;
    +  realT mindist;
    +  const char *pointfmt;
    +
    +  qh_facet2point(facet, &point0, &point1, &mindist);
    +  if (notfirst)
    +    qh_fprintf(fp, 9096, ",");
    +  if (format == qh_PRINTmaple)
    +    pointfmt= "[[%16.8f, %16.8f], [%16.8f, %16.8f]]\n";
    +  else
    +    pointfmt= "Line[{{%16.8f, %16.8f}, {%16.8f, %16.8f}}]\n";
    +  qh_fprintf(fp, 9097, pointfmt, point0[0], point0[1], point1[0], point1[1]);
    +  qh_memfree(point1, qh normal_size);
    +  qh_memfree(point0, qh normal_size);
    +} /* printfacet2math */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_nonsimplicial( fp, facet, color )
    +    print Geomview OFF for a 3-d nonsimplicial facet.
    +    if DOintersections, prints ridges to unvisited neighbors(qh visit_id)
    +
    +  notes
    +    uses facet->visitid for intersections and ridges
    +*/
    +void qh_printfacet3geom_nonsimplicial(FILE *fp, facetT *facet, realT color[3]) {
    +  ridgeT *ridge, **ridgep;
    +  setT *projectedpoints, *vertices;
    +  vertexT *vertex, **vertexp, *vertexA, *vertexB;
    +  pointT *projpt, *point, **pointp;
    +  facetT *neighbor;
    +  realT dist, outerplane, innerplane;
    +  int cntvertices, k;
    +  realT black[3]={0, 0, 0}, green[3]={0, 1, 0};
    +
    +  qh_geomplanes(facet, &outerplane, &innerplane);
    +  vertices= qh_facet3vertex(facet); /* oriented */
    +  cntvertices= qh_setsize(vertices);
    +  projectedpoints= qh_settemp(cntvertices);
    +  FOREACHvertex_(vertices) {
    +    zinc_(Zdistio);
    +    qh_distplane(vertex->point, facet, &dist);
    +    projpt= qh_projectpoint(vertex->point, facet, dist);
    +    qh_setappend(&projectedpoints, projpt);
    +  }
    +  if (qh PRINTouter || (!qh PRINTnoplanes && !qh PRINTinner))
    +    qh_printfacet3geom_points(fp, projectedpoints, facet, outerplane, color);
    +  if (qh PRINTinner || (!qh PRINTnoplanes && !qh PRINTouter &&
    +                outerplane - innerplane > 2 * qh MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet3geom_points(fp, projectedpoints, facet, innerplane, color);
    +  }
    +  FOREACHpoint_(projectedpoints)
    +    qh_memfree(point, qh normal_size);
    +  qh_settempfree(&projectedpoints);
    +  qh_settempfree(&vertices);
    +  if ((qh DOintersections || qh PRINTridges)
    +  && (!facet->visible || !qh NEWfacets)) {
    +    facet->visitid= qh visit_id;
    +    FOREACHridge_(facet->ridges) {
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->visitid != qh visit_id) {
    +        if (qh DOintersections)
    +          qh_printhyperplaneintersection(fp, facet, neighbor, ridge->vertices, black);
    +        if (qh PRINTridges) {
    +          vertexA= SETfirstt_(ridge->vertices, vertexT);
    +          vertexB= SETsecondt_(ridge->vertices, vertexT);
    +          qh_printline3geom(fp, vertexA->point, vertexB->point, green);
    +        }
    +      }
    +    }
    +  }
    +} /* printfacet3geom_nonsimplicial */
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_points( fp, points, facet, offset )
    +    prints a 3-d facet as OFF Geomview object.
    +    offset is relative to the facet's hyperplane
    +    Facet is determined as a list of points
    +*/
    +void qh_printfacet3geom_points(FILE *fp, setT *points, facetT *facet, realT offset, realT color[3]) {
    +  int k, n= qh_setsize(points), i;
    +  pointT *point, **pointp;
    +  setT *printpoints;
    +
    +  qh_fprintf(fp, 9098, "{ OFF %d 1 1 # f%d\n", n, facet->id);
    +  if (offset != 0.0) {
    +    printpoints= qh_settemp(n);
    +    FOREACHpoint_(points)
    +      qh_setappend(&printpoints, qh_projectpoint(point, facet, -offset));
    +  }else
    +    printpoints= points;
    +  FOREACHpoint_(printpoints) {
    +    for (k=0; k < qh hull_dim; k++) {
    +      if (k == qh DROPdim)
    +        qh_fprintf(fp, 9099, "0 ");
    +      else
    +        qh_fprintf(fp, 9100, "%8.4g ", point[k]);
    +    }
    +    if (printpoints != points)
    +      qh_memfree(point, qh normal_size);
    +    qh_fprintf(fp, 9101, "\n");
    +  }
    +  if (printpoints != points)
    +    qh_settempfree(&printpoints);
    +  qh_fprintf(fp, 9102, "%d ", n);
    +  for (i=0; i < n; i++)
    +    qh_fprintf(fp, 9103, "%d ", i);
    +  qh_fprintf(fp, 9104, "%8.4g %8.4g %8.4g 1.0 }\n", color[0], color[1], color[2]);
    +} /* printfacet3geom_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_simplicial(  )
    +    print Geomview OFF for a 3-d simplicial facet.
    +
    +  notes:
    +    may flip color
    +    uses facet->visitid for intersections and ridges
    +
    +    assume precise calculations in io.c with roundoff covered by qh_GEOMepsilon
    +    innerplane may be off by qh DISTround.  Maxoutside is calculated elsewhere
    +    so a DISTround error may have occurred.
    +*/
    +void qh_printfacet3geom_simplicial(FILE *fp, facetT *facet, realT color[3]) {
    +  setT *points, *vertices;
    +  vertexT *vertex, **vertexp, *vertexA, *vertexB;
    +  facetT *neighbor, **neighborp;
    +  realT outerplane, innerplane;
    +  realT black[3]={0, 0, 0}, green[3]={0, 1, 0};
    +  int k;
    +
    +  qh_geomplanes(facet, &outerplane, &innerplane);
    +  vertices= qh_facet3vertex(facet);
    +  points= qh_settemp(qh TEMPsize);
    +  FOREACHvertex_(vertices)
    +    qh_setappend(&points, vertex->point);
    +  if (qh PRINTouter || (!qh PRINTnoplanes && !qh PRINTinner))
    +    qh_printfacet3geom_points(fp, points, facet, outerplane, color);
    +  if (qh PRINTinner || (!qh PRINTnoplanes && !qh PRINTouter &&
    +              outerplane - innerplane > 2 * qh MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet3geom_points(fp, points, facet, innerplane, color);
    +  }
    +  qh_settempfree(&points);
    +  qh_settempfree(&vertices);
    +  if ((qh DOintersections || qh PRINTridges)
    +  && (!facet->visible || !qh NEWfacets)) {
    +    facet->visitid= qh visit_id;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh visit_id) {
    +        vertices= qh_setnew_delnthsorted(facet->vertices, qh hull_dim,
    +                          SETindex_(facet->neighbors, neighbor), 0);
    +        if (qh DOintersections)
    +           qh_printhyperplaneintersection(fp, facet, neighbor, vertices, black);
    +        if (qh PRINTridges) {
    +          vertexA= SETfirstt_(vertices, vertexT);
    +          vertexB= SETsecondt_(vertices, vertexT);
    +          qh_printline3geom(fp, vertexA->point, vertexB->point, green);
    +        }
    +        qh_setfree(&vertices);
    +      }
    +    }
    +  }
    +} /* printfacet3geom_simplicial */
    +
    +/*---------------------------------
    +
    +  qh_printfacet3math( fp, facet, notfirst )
    +    print 3-d Maple or Mathematica output for a facet
    +
    +  notes:
    +    may be non-simplicial
    +    use %16.8f since Mathematica 2.2 does not handle exponential format
    +    see qh_printfacet2math
    +*/
    +void qh_printfacet3math(FILE *fp, facetT *facet, qh_PRINT format, int notfirst) {
    +  vertexT *vertex, **vertexp;
    +  setT *points, *vertices;
    +  pointT *point, **pointp;
    +  boolT firstpoint= True;
    +  realT dist;
    +  const char *pointfmt, *endfmt;
    +
    +  if (notfirst)
    +    qh_fprintf(fp, 9105, ",\n");
    +  vertices= qh_facet3vertex(facet);
    +  points= qh_settemp(qh_setsize(vertices));
    +  FOREACHvertex_(vertices) {
    +    zinc_(Zdistio);
    +    qh_distplane(vertex->point, facet, &dist);
    +    point= qh_projectpoint(vertex->point, facet, dist);
    +    qh_setappend(&points, point);
    +  }
    +  if (format == qh_PRINTmaple) {
    +    qh_fprintf(fp, 9106, "[");
    +    pointfmt= "[%16.8f, %16.8f, %16.8f]";
    +    endfmt= "]";
    +  }else {
    +    qh_fprintf(fp, 9107, "Polygon[{");
    +    pointfmt= "{%16.8f, %16.8f, %16.8f}";
    +    endfmt= "}]";
    +  }
    +  FOREACHpoint_(points) {
    +    if (firstpoint)
    +      firstpoint= False;
    +    else
    +      qh_fprintf(fp, 9108, ",\n");
    +    qh_fprintf(fp, 9109, pointfmt, point[0], point[1], point[2]);
    +  }
    +  FOREACHpoint_(points)
    +    qh_memfree(point, qh normal_size);
    +  qh_settempfree(&points);
    +  qh_settempfree(&vertices);
    +  qh_fprintf(fp, 9110, "%s", endfmt);
    +} /* printfacet3math */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3vertex( fp, facet, format )
    +    print vertices in a 3-d facet as point ids
    +
    +  notes:
    +    prints number of vertices first if format == qh_PRINToff
    +    the facet may be non-simplicial
    +*/
    +void qh_printfacet3vertex(FILE *fp, facetT *facet, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +
    +  vertices= qh_facet3vertex(facet);
    +  if (format == qh_PRINToff)
    +    qh_fprintf(fp, 9111, "%d ", qh_setsize(vertices));
    +  FOREACHvertex_(vertices)
    +    qh_fprintf(fp, 9112, "%d ", qh_pointid(vertex->point));
    +  qh_fprintf(fp, 9113, "\n");
    +  qh_settempfree(&vertices);
    +} /* printfacet3vertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet4geom_nonsimplicial(  )
    +    print Geomview 4OFF file for a 4d nonsimplicial facet
    +    prints all ridges to unvisited neighbors (qh.visit_id)
    +    if qh.DROPdim
    +      prints in OFF format
    +
    +  notes:
    +    must agree with printend4geom()
    +*/
    +void qh_printfacet4geom_nonsimplicial(FILE *fp, facetT *facet, realT color[3]) {
    +  facetT *neighbor;
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +  pointT *point;
    +  int k;
    +  realT dist;
    +
    +  facet->visitid= qh visit_id;
    +  if (qh PRINTnoplanes || (facet->visible && qh NEWfacets))
    +    return;
    +  FOREACHridge_(facet->ridges) {
    +    neighbor= otherfacet_(ridge, facet);
    +    if (neighbor->visitid == qh visit_id)
    +      continue;
    +    if (qh PRINTtransparent && !neighbor->good)
    +      continue;
    +    if (qh DOintersections)
    +      qh_printhyperplaneintersection(fp, facet, neighbor, ridge->vertices, color);
    +    else {
    +      if (qh DROPdim >= 0)
    +        qh_fprintf(fp, 9114, "OFF 3 1 1 # f%d\n", facet->id);
    +      else {
    +        qh printoutvar++;
    +        qh_fprintf(fp, 9115, "# r%d between f%d f%d\n", ridge->id, facet->id, neighbor->id);
    +      }
    +      FOREACHvertex_(ridge->vertices) {
    +        zinc_(Zdistio);
    +        qh_distplane(vertex->point,facet, &dist);
    +        point=qh_projectpoint(vertex->point,facet, dist);
    +        for (k=0; k < qh hull_dim; k++) {
    +          if (k != qh DROPdim)
    +            qh_fprintf(fp, 9116, "%8.4g ", point[k]);
    +        }
    +        qh_fprintf(fp, 9117, "\n");
    +        qh_memfree(point, qh normal_size);
    +      }
    +      if (qh DROPdim >= 0)
    +        qh_fprintf(fp, 9118, "3 0 1 2 %8.4g %8.4g %8.4g\n", color[0], color[1], color[2]);
    +    }
    +  }
    +} /* printfacet4geom_nonsimplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet4geom_simplicial( fp, facet, color )
    +    print Geomview 4OFF file for a 4d simplicial facet
    +    prints triangles for unvisited neighbors (qh.visit_id)
    +
    +  notes:
    +    must agree with printend4geom()
    +*/
    +void qh_printfacet4geom_simplicial(FILE *fp, facetT *facet, realT color[3]) {
    +  setT *vertices;
    +  facetT *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  int k;
    +
    +  facet->visitid= qh visit_id;
    +  if (qh PRINTnoplanes || (facet->visible && qh NEWfacets))
    +    return;
    +  FOREACHneighbor_(facet) {
    +    if (neighbor->visitid == qh visit_id)
    +      continue;
    +    if (qh PRINTtransparent && !neighbor->good)
    +      continue;
    +    vertices= qh_setnew_delnthsorted(facet->vertices, qh hull_dim,
    +                          SETindex_(facet->neighbors, neighbor), 0);
    +    if (qh DOintersections)
    +      qh_printhyperplaneintersection(fp, facet, neighbor, vertices, color);
    +    else {
    +      if (qh DROPdim >= 0)
    +        qh_fprintf(fp, 9119, "OFF 3 1 1 # ridge between f%d f%d\n",
    +                facet->id, neighbor->id);
    +      else {
    +        qh printoutvar++;
    +        qh_fprintf(fp, 9120, "# ridge between f%d f%d\n", facet->id, neighbor->id);
    +      }
    +      FOREACHvertex_(vertices) {
    +        for (k=0; k < qh hull_dim; k++) {
    +          if (k != qh DROPdim)
    +            qh_fprintf(fp, 9121, "%8.4g ", vertex->point[k]);
    +        }
    +        qh_fprintf(fp, 9122, "\n");
    +      }
    +      if (qh DROPdim >= 0)
    +        qh_fprintf(fp, 9123, "3 0 1 2 %8.4g %8.4g %8.4g\n", color[0], color[1], color[2]);
    +    }
    +    qh_setfree(&vertices);
    +  }
    +} /* printfacet4geom_simplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetNvertex_nonsimplicial( fp, facet, id, format )
    +    print vertices for an N-d non-simplicial facet
    +    triangulates each ridge to the id
    +*/
    +void qh_printfacetNvertex_nonsimplicial(FILE *fp, facetT *facet, int id, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (facet->visible && qh NEWfacets)
    +    return;
    +  FOREACHridge_(facet->ridges) {
    +    if (format == qh_PRINTtriangles)
    +      qh_fprintf(fp, 9124, "%d ", qh hull_dim);
    +    qh_fprintf(fp, 9125, "%d ", id);
    +    if ((ridge->top == facet) ^ qh_ORIENTclock) {
    +      FOREACHvertex_(ridge->vertices)
    +        qh_fprintf(fp, 9126, "%d ", qh_pointid(vertex->point));
    +    }else {
    +      FOREACHvertexreverse12_(ridge->vertices)
    +        qh_fprintf(fp, 9127, "%d ", qh_pointid(vertex->point));
    +    }
    +    qh_fprintf(fp, 9128, "\n");
    +  }
    +} /* printfacetNvertex_nonsimplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetNvertex_simplicial( fp, facet, format )
    +    print vertices for an N-d simplicial facet
    +    prints vertices for non-simplicial facets
    +      2-d facets (orientation preserved by qh_mergefacet2d)
    +      PRINToff ('o') for 4-d and higher
    +*/
    +void qh_printfacetNvertex_simplicial(FILE *fp, facetT *facet, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +
    +  if (format == qh_PRINToff || format == qh_PRINTtriangles)
    +    qh_fprintf(fp, 9129, "%d ", qh_setsize(facet->vertices));
    +  if ((facet->toporient ^ qh_ORIENTclock)
    +  || (qh hull_dim > 2 && !facet->simplicial)) {
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(fp, 9130, "%d ", qh_pointid(vertex->point));
    +  }else {
    +    FOREACHvertexreverse12_(facet->vertices)
    +      qh_fprintf(fp, 9131, "%d ", qh_pointid(vertex->point));
    +  }
    +  qh_fprintf(fp, 9132, "\n");
    +} /* printfacetNvertex_simplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetheader( fp, facet )
    +    prints header fields of a facet to fp
    +
    +  notes:
    +    for 'f' output and debugging
    +    Same as QhullFacet::printHeader()
    +*/
    +void qh_printfacetheader(FILE *fp, facetT *facet) {
    +  pointT *point, **pointp, *furthest;
    +  facetT *neighbor, **neighborp;
    +  realT dist;
    +
    +  if (facet == qh_MERGEridge) {
    +    qh_fprintf(fp, 9133, " MERGEridge\n");
    +    return;
    +  }else if (facet == qh_DUPLICATEridge) {
    +    qh_fprintf(fp, 9134, " DUPLICATEridge\n");
    +    return;
    +  }else if (!facet) {
    +    qh_fprintf(fp, 9135, " NULLfacet\n");
    +    return;
    +  }
    +  qh old_randomdist= qh RANDOMdist;
    +  qh RANDOMdist= False;
    +  qh_fprintf(fp, 9136, "- f%d\n", facet->id);
    +  qh_fprintf(fp, 9137, "    - flags:");
    +  if (facet->toporient)
    +    qh_fprintf(fp, 9138, " top");
    +  else
    +    qh_fprintf(fp, 9139, " bottom");
    +  if (facet->simplicial)
    +    qh_fprintf(fp, 9140, " simplicial");
    +  if (facet->tricoplanar)
    +    qh_fprintf(fp, 9141, " tricoplanar");
    +  if (facet->upperdelaunay)
    +    qh_fprintf(fp, 9142, " upperDelaunay");
    +  if (facet->visible)
    +    qh_fprintf(fp, 9143, " visible");
    +  if (facet->newfacet)
    +    qh_fprintf(fp, 9144, " new");
    +  if (facet->tested)
    +    qh_fprintf(fp, 9145, " tested");
    +  if (!facet->good)
    +    qh_fprintf(fp, 9146, " notG");
    +  if (facet->seen)
    +    qh_fprintf(fp, 9147, " seen");
    +  if (facet->coplanar)
    +    qh_fprintf(fp, 9148, " coplanar");
    +  if (facet->mergehorizon)
    +    qh_fprintf(fp, 9149, " mergehorizon");
    +  if (facet->keepcentrum)
    +    qh_fprintf(fp, 9150, " keepcentrum");
    +  if (facet->dupridge)
    +    qh_fprintf(fp, 9151, " dupridge");
    +  if (facet->mergeridge && !facet->mergeridge2)
    +    qh_fprintf(fp, 9152, " mergeridge1");
    +  if (facet->mergeridge2)
    +    qh_fprintf(fp, 9153, " mergeridge2");
    +  if (facet->newmerge)
    +    qh_fprintf(fp, 9154, " newmerge");
    +  if (facet->flipped)
    +    qh_fprintf(fp, 9155, " flipped");
    +  if (facet->notfurthest)
    +    qh_fprintf(fp, 9156, " notfurthest");
    +  if (facet->degenerate)
    +    qh_fprintf(fp, 9157, " degenerate");
    +  if (facet->redundant)
    +    qh_fprintf(fp, 9158, " redundant");
    +  qh_fprintf(fp, 9159, "\n");
    +  if (facet->isarea)
    +    qh_fprintf(fp, 9160, "    - area: %2.2g\n", facet->f.area);
    +  else if (qh NEWfacets && facet->visible && facet->f.replace)
    +    qh_fprintf(fp, 9161, "    - replacement: f%d\n", facet->f.replace->id);
    +  else if (facet->newfacet) {
    +    if (facet->f.samecycle && facet->f.samecycle != facet)
    +      qh_fprintf(fp, 9162, "    - shares same visible/horizon as f%d\n", facet->f.samecycle->id);
    +  }else if (facet->tricoplanar /* !isarea */) {
    +    if (facet->f.triowner)
    +      qh_fprintf(fp, 9163, "    - owner of normal & centrum is facet f%d\n", facet->f.triowner->id);
    +  }else if (facet->f.newcycle)
    +    qh_fprintf(fp, 9164, "    - was horizon to f%d\n", facet->f.newcycle->id);
    +  if (facet->nummerge)
    +    qh_fprintf(fp, 9165, "    - merges: %d\n", facet->nummerge);
    +  qh_printpointid(fp, "    - normal: ", qh hull_dim, facet->normal, qh_IDunknown);
    +  qh_fprintf(fp, 9166, "    - offset: %10.7g\n", facet->offset);
    +  if (qh CENTERtype == qh_ASvoronoi || facet->center)
    +    qh_printcenter(fp, qh_PRINTfacets, "    - center: ", facet);
    +#if qh_MAXoutside
    +  if (facet->maxoutside > qh DISTround)
    +    qh_fprintf(fp, 9167, "    - maxoutside: %10.7g\n", facet->maxoutside);
    +#endif
    +  if (!SETempty_(facet->outsideset)) {
    +    furthest= (pointT*)qh_setlast(facet->outsideset);
    +    if (qh_setsize(facet->outsideset) < 6) {
    +      qh_fprintf(fp, 9168, "    - outside set(furthest p%d):\n", qh_pointid(furthest));
    +      FOREACHpoint_(facet->outsideset)
    +        qh_printpoint(fp, "     ", point);
    +    }else if (qh_setsize(facet->outsideset) < 21) {
    +      qh_printpoints(fp, "    - outside set:", facet->outsideset);
    +    }else {
    +      qh_fprintf(fp, 9169, "    - outside set:  %d points.", qh_setsize(facet->outsideset));
    +      qh_printpoint(fp, "  Furthest", furthest);
    +    }
    +#if !qh_COMPUTEfurthest
    +    qh_fprintf(fp, 9170, "    - furthest distance= %2.2g\n", facet->furthestdist);
    +#endif
    +  }
    +  if (!SETempty_(facet->coplanarset)) {
    +    furthest= (pointT*)qh_setlast(facet->coplanarset);
    +    if (qh_setsize(facet->coplanarset) < 6) {
    +      qh_fprintf(fp, 9171, "    - coplanar set(furthest p%d):\n", qh_pointid(furthest));
    +      FOREACHpoint_(facet->coplanarset)
    +        qh_printpoint(fp, "     ", point);
    +    }else if (qh_setsize(facet->coplanarset) < 21) {
    +      qh_printpoints(fp, "    - coplanar set:", facet->coplanarset);
    +    }else {
    +      qh_fprintf(fp, 9172, "    - coplanar set:  %d points.", qh_setsize(facet->coplanarset));
    +      qh_printpoint(fp, "  Furthest", furthest);
    +    }
    +    zinc_(Zdistio);
    +    qh_distplane(furthest, facet, &dist);
    +    qh_fprintf(fp, 9173, "      furthest distance= %2.2g\n", dist);
    +  }
    +  qh_printvertices(fp, "    - vertices:", facet->vertices);
    +  qh_fprintf(fp, 9174, "    - neighboring facets:");
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      qh_fprintf(fp, 9175, " MERGE");
    +    else if (neighbor == qh_DUPLICATEridge)
    +      qh_fprintf(fp, 9176, " DUP");
    +    else
    +      qh_fprintf(fp, 9177, " f%d", neighbor->id);
    +  }
    +  qh_fprintf(fp, 9178, "\n");
    +  qh RANDOMdist= qh old_randomdist;
    +} /* printfacetheader */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetridges( fp, facet )
    +    prints ridges of a facet to fp
    +
    +  notes:
    +    ridges printed in neighbor order
    +    assumes the ridges exist
    +    for 'f' output
    +    same as QhullFacet::printRidges
    +*/
    +void qh_printfacetridges(FILE *fp, facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int numridges= 0;
    +
    +
    +  if (facet->visible && qh NEWfacets) {
    +    qh_fprintf(fp, 9179, "    - ridges(ids may be garbage):");
    +    FOREACHridge_(facet->ridges)
    +      qh_fprintf(fp, 9180, " r%d", ridge->id);
    +    qh_fprintf(fp, 9181, "\n");
    +  }else {
    +    qh_fprintf(fp, 9182, "    - ridges:\n");
    +    FOREACHridge_(facet->ridges)
    +      ridge->seen= False;
    +    if (qh hull_dim == 3) {
    +      ridge= SETfirstt_(facet->ridges, ridgeT);
    +      while (ridge && !ridge->seen) {
    +        ridge->seen= True;
    +        qh_printridge(fp, ridge);
    +        numridges++;
    +        ridge= qh_nextridge3d(ridge, facet, NULL);
    +        }
    +    }else {
    +      FOREACHneighbor_(facet) {
    +        FOREACHridge_(facet->ridges) {
    +          if (otherfacet_(ridge,facet) == neighbor) {
    +            ridge->seen= True;
    +            qh_printridge(fp, ridge);
    +            numridges++;
    +          }
    +        }
    +      }
    +    }
    +    if (numridges != qh_setsize(facet->ridges)) {
    +      qh_fprintf(fp, 9183, "     - all ridges:");
    +      FOREACHridge_(facet->ridges)
    +        qh_fprintf(fp, 9184, " r%d", ridge->id);
    +        qh_fprintf(fp, 9185, "\n");
    +    }
    +    FOREACHridge_(facet->ridges) {
    +      if (!ridge->seen)
    +        qh_printridge(fp, ridge);
    +    }
    +  }
    +} /* printfacetridges */
    +
    +/*---------------------------------
    +
    +  qh_printfacets( fp, format, facetlist, facets, printall )
    +    prints facetlist and/or facet set in output format
    +
    +  notes:
    +    also used for specialized formats ('FO' and summary)
    +    turns off 'Rn' option since want actual numbers
    +*/
    +void qh_printfacets(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numcoplanars, numtricoplanars;
    +  facetT *facet, **facetp;
    +  setT *vertices;
    +  coordT *center;
    +  realT outerplane, innerplane;
    +
    +  qh old_randomdist= qh RANDOMdist;
    +  qh RANDOMdist= False;
    +  if (qh CDDoutput && (format == qh_PRINTcentrums || format == qh_PRINTpointintersect || format == qh_PRINToff))
    +    qh_fprintf(qh ferr, 7056, "qhull warning: CDD format is not available for centrums, halfspace\nintersections, and OFF file format.\n");
    +  if (format == qh_PRINTnone)
    +    ; /* print nothing */
    +  else if (format == qh_PRINTaverage) {
    +    vertices= qh_facetvertices(facetlist, facets, printall);
    +    center= qh_getcenter(vertices);
    +    qh_fprintf(fp, 9186, "%d 1\n", qh hull_dim);
    +    qh_printpointid(fp, NULL, qh hull_dim, center, qh_IDunknown);
    +    qh_memfree(center, qh normal_size);
    +    qh_settempfree(&vertices);
    +  }else if (format == qh_PRINTextremes) {
    +    if (qh DELAUNAY)
    +      qh_printextremes_d(fp, facetlist, facets, printall);
    +    else if (qh hull_dim == 2)
    +      qh_printextremes_2d(fp, facetlist, facets, printall);
    +    else
    +      qh_printextremes(fp, facetlist, facets, printall);
    +  }else if (format == qh_PRINToptions)
    +    qh_fprintf(fp, 9187, "Options selected for Qhull %s:\n%s\n", qh_version, qh qhull_options);
    +  else if (format == qh_PRINTpoints && !qh VORONOI)
    +    qh_printpoints_out(fp, facetlist, facets, printall);
    +  else if (format == qh_PRINTqhull)
    +    qh_fprintf(fp, 9188, "%s | %s\n", qh rbox_command, qh qhull_command);
    +  else if (format == qh_PRINTsize) {
    +    qh_fprintf(fp, 9189, "0\n2 ");
    +    qh_fprintf(fp, 9190, qh_REAL_1, qh totarea);
    +    qh_fprintf(fp, 9191, qh_REAL_1, qh totvol);
    +    qh_fprintf(fp, 9192, "\n");
    +  }else if (format == qh_PRINTsummary) {
    +    qh_countfacets(facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);
    +    vertices= qh_facetvertices(facetlist, facets, printall);
    +    qh_fprintf(fp, 9193, "10 %d %d %d %d %d %d %d %d %d %d\n2 ", qh hull_dim,
    +                qh num_points + qh_setsize(qh other_points),
    +                qh num_vertices, qh num_facets - qh num_visible,
    +                qh_setsize(vertices), numfacets, numcoplanars,
    +                numfacets - numsimplicial, zzval_(Zdelvertextot),
    +                numtricoplanars);
    +    qh_settempfree(&vertices);
    +    qh_outerinner(NULL, &outerplane, &innerplane);
    +    qh_fprintf(fp, 9194, qh_REAL_2n, outerplane, innerplane);
    +  }else if (format == qh_PRINTvneighbors)
    +    qh_printvneighbors(fp, facetlist, facets, printall);
    +  else if (qh VORONOI && format == qh_PRINToff)
    +    qh_printvoronoi(fp, format, facetlist, facets, printall);
    +  else if (qh VORONOI && format == qh_PRINTgeom) {
    +    qh_printbegin(fp, format, facetlist, facets, printall);
    +    qh_printvoronoi(fp, format, facetlist, facets, printall);
    +    qh_printend(fp, format, facetlist, facets, printall);
    +  }else if (qh VORONOI
    +  && (format == qh_PRINTvertices || format == qh_PRINTinner || format == qh_PRINTouter))
    +    qh_printvdiagram(fp, format, facetlist, facets, printall);
    +  else {
    +    qh_printbegin(fp, format, facetlist, facets, printall);
    +    FORALLfacet_(facetlist)
    +      qh_printafacet(fp, format, facet, printall);
    +    FOREACHfacet_(facets)
    +      qh_printafacet(fp, format, facet, printall);
    +    qh_printend(fp, format, facetlist, facets, printall);
    +  }
    +  qh RANDOMdist= qh old_randomdist;
    +} /* printfacets */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhyperplaneintersection( fp, facet1, facet2, vertices, color )
    +    print Geomview OFF or 4OFF for the intersection of two hyperplanes in 3-d or 4-d
    +*/
    +void qh_printhyperplaneintersection(FILE *fp, facetT *facet1, facetT *facet2,
    +                   setT *vertices, realT color[3]) {
    +  realT costheta, denominator, dist1, dist2, s, t, mindenom, p[4];
    +  vertexT *vertex, **vertexp;
    +  int i, k;
    +  boolT nearzero1, nearzero2;
    +
    +  costheta= qh_getangle(facet1->normal, facet2->normal);
    +  denominator= 1 - costheta * costheta;
    +  i= qh_setsize(vertices);
    +  if (qh hull_dim == 3)
    +    qh_fprintf(fp, 9195, "VECT 1 %d 1 %d 1 ", i, i);
    +  else if (qh hull_dim == 4 && qh DROPdim >= 0)
    +    qh_fprintf(fp, 9196, "OFF 3 1 1 ");
    +  else
    +    qh printoutvar++;
    +  qh_fprintf(fp, 9197, "# intersect f%d f%d\n", facet1->id, facet2->id);
    +  mindenom= 1 / (10.0 * qh MAXabs_coord);
    +  FOREACHvertex_(vertices) {
    +    zadd_(Zdistio, 2);
    +    qh_distplane(vertex->point, facet1, &dist1);
    +    qh_distplane(vertex->point, facet2, &dist2);
    +    s= qh_divzero(-dist1 + costheta * dist2, denominator,mindenom,&nearzero1);
    +    t= qh_divzero(-dist2 + costheta * dist1, denominator,mindenom,&nearzero2);
    +    if (nearzero1 || nearzero2)
    +      s= t= 0.0;
    +    for (k=qh hull_dim; k--; )
    +      p[k]= vertex->point[k] + facet1->normal[k] * s + facet2->normal[k] * t;
    +    if (qh PRINTdim <= 3) {
    +      qh_projectdim3(p, p);
    +      qh_fprintf(fp, 9198, "%8.4g %8.4g %8.4g # ", p[0], p[1], p[2]);
    +    }else
    +      qh_fprintf(fp, 9199, "%8.4g %8.4g %8.4g %8.4g # ", p[0], p[1], p[2], p[3]);
    +    if (nearzero1+nearzero2)
    +      qh_fprintf(fp, 9200, "p%d(coplanar facets)\n", qh_pointid(vertex->point));
    +    else
    +      qh_fprintf(fp, 9201, "projected p%d\n", qh_pointid(vertex->point));
    +  }
    +  if (qh hull_dim == 3)
    +    qh_fprintf(fp, 9202, "%8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +  else if (qh hull_dim == 4 && qh DROPdim >= 0)
    +    qh_fprintf(fp, 9203, "3 0 1 2 %8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +} /* printhyperplaneintersection */
    +
    +/*---------------------------------
    +
    +  qh_printline3geom( fp, pointA, pointB, color )
    +    prints a line as a VECT
    +    prints 0's for qh.DROPdim
    +
    +  notes:
    +    if pointA == pointB,
    +      it's a 1 point VECT
    +*/
    +void qh_printline3geom(FILE *fp, pointT *pointA, pointT *pointB, realT color[3]) {
    +  int k;
    +  realT pA[4], pB[4];
    +
    +  qh_projectdim3(pointA, pA);
    +  qh_projectdim3(pointB, pB);
    +  if ((fabs(pA[0] - pB[0]) > 1e-3) ||
    +      (fabs(pA[1] - pB[1]) > 1e-3) ||
    +      (fabs(pA[2] - pB[2]) > 1e-3)) {
    +    qh_fprintf(fp, 9204, "VECT 1 2 1 2 1\n");
    +    for (k=0; k < 3; k++)
    +       qh_fprintf(fp, 9205, "%8.4g ", pB[k]);
    +    qh_fprintf(fp, 9206, " # p%d\n", qh_pointid(pointB));
    +  }else
    +    qh_fprintf(fp, 9207, "VECT 1 1 1 1 1\n");
    +  for (k=0; k < 3; k++)
    +    qh_fprintf(fp, 9208, "%8.4g ", pA[k]);
    +  qh_fprintf(fp, 9209, " # p%d\n", qh_pointid(pointA));
    +  qh_fprintf(fp, 9210, "%8.4g %8.4g %8.4g 1\n", color[0], color[1], color[2]);
    +}
    +
    +/*---------------------------------
    +
    +  qh_printneighborhood( fp, format, facetA, facetB, printall )
    +    print neighborhood of one or two facets
    +
    +  notes:
    +    calls qh_findgood_all()
    +    bumps qh.visit_id
    +*/
    +void qh_printneighborhood(FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall) {
    +  facetT *neighbor, **neighborp, *facet;
    +  setT *facets;
    +
    +  if (format == qh_PRINTnone)
    +    return;
    +  qh_findgood_all(qh facet_list);
    +  if (facetA == facetB)
    +    facetB= NULL;
    +  facets= qh_settemp(2*(qh_setsize(facetA->neighbors)+1));
    +  qh visit_id++;
    +  for (facet= facetA; facet; facet= ((facet == facetA) ? facetB : NULL)) {
    +    if (facet->visitid != qh visit_id) {
    +      facet->visitid= qh visit_id;
    +      qh_setappend(&facets, facet);
    +    }
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == qh visit_id)
    +        continue;
    +      neighbor->visitid= qh visit_id;
    +      if (printall || !qh_skipfacet(neighbor))
    +        qh_setappend(&facets, neighbor);
    +    }
    +  }
    +  qh_printfacets(fp, format, NULL, facets, printall);
    +  qh_settempfree(&facets);
    +} /* printneighborhood */
    +
    +/*---------------------------------
    +
    +  qh_printpoint( fp, string, point )
    +  qh_printpointid( fp, string, dim, point, id )
    +    prints the coordinates of a point
    +
    +  returns:
    +    if string is defined
    +      prints 'string p%d'.  Skips p%d if id=qh_IDunknown(-1) or qh_IDnone(-3)
    +
    +  notes:
    +    nop if point is NULL
    +    Same as QhullPoint's printPoint
    +*/
    +void qh_printpoint(FILE *fp, const char *string, pointT *point) {
    +  int id= qh_pointid( point);
    +
    +  qh_printpointid( fp, string, qh hull_dim, point, id);
    +} /* printpoint */
    +
    +void qh_printpointid(FILE *fp, const char *string, int dim, pointT *point, int id) {
    +  int k;
    +  realT r; /*bug fix*/
    +
    +  if (!point)
    +    return;
    +  if (string) {
    +    qh_fprintf(fp, 9211, "%s", string);
    +    if (id != qh_IDunknown && id != qh_IDnone)
    +      qh_fprintf(fp, 9212, " p%d: ", id);
    +  }
    +  for (k=dim; k--; ) {
    +    r= *point++;
    +    if (string)
    +      qh_fprintf(fp, 9213, " %8.4g", r);
    +    else
    +      qh_fprintf(fp, 9214, qh_REAL_1, r);
    +  }
    +  qh_fprintf(fp, 9215, "\n");
    +} /* printpointid */
    +
    +/*---------------------------------
    +
    +  qh_printpoint3( fp, point )
    +    prints 2-d, 3-d, or 4-d point as Geomview 3-d coordinates
    +*/
    +void qh_printpoint3(FILE *fp, pointT *point) {
    +  int k;
    +  realT p[4];
    +
    +  qh_projectdim3(point, p);
    +  for (k=0; k < 3; k++)
    +    qh_fprintf(fp, 9216, "%8.4g ", p[k]);
    +  qh_fprintf(fp, 9217, " # p%d\n", qh_pointid(point));
    +} /* printpoint3 */
    +
    +/*----------------------------------------
    +-printpoints- print pointids for a set of points starting at index
    +   see geom.c
    +*/
    +
    +/*---------------------------------
    +
    +  qh_printpoints_out( fp, facetlist, facets, printall )
    +    prints vertices, coplanar/inside points, for facets by their point coordinates
    +    allows qh.CDDoutput
    +
    +  notes:
    +    same format as qhull input
    +    if no coplanar/interior points,
    +      same order as qh_printextremes
    +*/
    +void qh_printpoints_out(FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  int allpoints= qh num_points + qh_setsize(qh other_points);
    +  int numpoints=0, point_i, point_n;
    +  setT *vertices, *points;
    +  facetT *facet, **facetp;
    +  pointT *point, **pointp;
    +  vertexT *vertex, **vertexp;
    +  int id;
    +
    +  points= qh_settemp(allpoints);
    +  qh_setzero(points, 0, allpoints);
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  FOREACHvertex_(vertices) {
    +    id= qh_pointid(vertex->point);
    +    if (id >= 0)
    +      SETelem_(points, id)= vertex->point;
    +  }
    +  if (qh KEEPinside || qh KEEPcoplanar || qh KEEPnearinside) {
    +    FORALLfacet_(facetlist) {
    +      if (!printall && qh_skipfacet(facet))
    +        continue;
    +      FOREACHpoint_(facet->coplanarset) {
    +        id= qh_pointid(point);
    +        if (id >= 0)
    +          SETelem_(points, id)= point;
    +      }
    +    }
    +    FOREACHfacet_(facets) {
    +      if (!printall && qh_skipfacet(facet))
    +        continue;
    +      FOREACHpoint_(facet->coplanarset) {
    +        id= qh_pointid(point);
    +        if (id >= 0)
    +          SETelem_(points, id)= point;
    +      }
    +    }
    +  }
    +  qh_settempfree(&vertices);
    +  FOREACHpoint_i_(points) {
    +    if (point)
    +      numpoints++;
    +  }
    +  if (qh CDDoutput)
    +    qh_fprintf(fp, 9218, "%s | %s\nbegin\n%d %d real\n", qh rbox_command,
    +             qh qhull_command, numpoints, qh hull_dim + 1);
    +  else
    +    qh_fprintf(fp, 9219, "%d\n%d\n", qh hull_dim, numpoints);
    +  FOREACHpoint_i_(points) {
    +    if (point) {
    +      if (qh CDDoutput)
    +        qh_fprintf(fp, 9220, "1 ");
    +      qh_printpoint(fp, NULL, point);
    +    }
    +  }
    +  if (qh CDDoutput)
    +    qh_fprintf(fp, 9221, "end\n");
    +  qh_settempfree(&points);
    +} /* printpoints_out */
    +
    +
    +/*---------------------------------
    +
    +  qh_printpointvect( fp, point, normal, center, radius, color )
    +    prints a 2-d, 3-d, or 4-d point as 3-d VECT's relative to normal or to center point
    +*/
    +void qh_printpointvect(FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius, realT color[3]) {
    +  realT diff[4], pointA[4];
    +  int k;
    +
    +  for (k=qh hull_dim; k--; ) {
    +    if (center)
    +      diff[k]= point[k]-center[k];
    +    else if (normal)
    +      diff[k]= normal[k];
    +    else
    +      diff[k]= 0;
    +  }
    +  if (center)
    +    qh_normalize2(diff, qh hull_dim, True, NULL, NULL);
    +  for (k=qh hull_dim; k--; )
    +    pointA[k]= point[k]+diff[k] * radius;
    +  qh_printline3geom(fp, point, pointA, color);
    +} /* printpointvect */
    +
    +/*---------------------------------
    +
    +  qh_printpointvect2( fp, point, normal, center, radius )
    +    prints a 2-d, 3-d, or 4-d point as 2 3-d VECT's for an imprecise point
    +*/
    +void qh_printpointvect2(FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius) {
    +  realT red[3]={1, 0, 0}, yellow[3]={1, 1, 0};
    +
    +  qh_printpointvect(fp, point, normal, center, radius, red);
    +  qh_printpointvect(fp, point, normal, center, -radius, yellow);
    +} /* printpointvect2 */
    +
    +/*---------------------------------
    +
    +  qh_printridge( fp, ridge )
    +    prints the information in a ridge
    +
    +  notes:
    +    for qh_printfacetridges()
    +    same as operator<< [QhullRidge.cpp]
    +*/
    +void qh_printridge(FILE *fp, ridgeT *ridge) {
    +
    +  qh_fprintf(fp, 9222, "     - r%d", ridge->id);
    +  if (ridge->tested)
    +    qh_fprintf(fp, 9223, " tested");
    +  if (ridge->nonconvex)
    +    qh_fprintf(fp, 9224, " nonconvex");
    +  qh_fprintf(fp, 9225, "\n");
    +  qh_printvertices(fp, "           vertices:", ridge->vertices);
    +  if (ridge->top && ridge->bottom)
    +    qh_fprintf(fp, 9226, "           between f%d and f%d\n",
    +            ridge->top->id, ridge->bottom->id);
    +} /* printridge */
    +
    +/*---------------------------------
    +
    +  qh_printspheres( fp, vertices, radius )
    +    prints 3-d vertices as OFF spheres
    +
    +  notes:
    +    inflated octahedron from Stuart Levy earth/mksphere2
    +*/
    +void qh_printspheres(FILE *fp, setT *vertices, realT radius) {
    +  vertexT *vertex, **vertexp;
    +
    +  qh printoutnum++;
    +  qh_fprintf(fp, 9227, "{appearance {-edge -normal normscale 0} {\n\
    +INST geom {define vsphere OFF\n\
    +18 32 48\n\
    +\n\
    +0 0 1\n\
    +1 0 0\n\
    +0 1 0\n\
    +-1 0 0\n\
    +0 -1 0\n\
    +0 0 -1\n\
    +0.707107 0 0.707107\n\
    +0 -0.707107 0.707107\n\
    +0.707107 -0.707107 0\n\
    +-0.707107 0 0.707107\n\
    +-0.707107 -0.707107 0\n\
    +0 0.707107 0.707107\n\
    +-0.707107 0.707107 0\n\
    +0.707107 0.707107 0\n\
    +0.707107 0 -0.707107\n\
    +0 0.707107 -0.707107\n\
    +-0.707107 0 -0.707107\n\
    +0 -0.707107 -0.707107\n\
    +\n\
    +3 0 6 11\n\
    +3 0 7 6 \n\
    +3 0 9 7 \n\
    +3 0 11 9\n\
    +3 1 6 8 \n\
    +3 1 8 14\n\
    +3 1 13 6\n\
    +3 1 14 13\n\
    +3 2 11 13\n\
    +3 2 12 11\n\
    +3 2 13 15\n\
    +3 2 15 12\n\
    +3 3 9 12\n\
    +3 3 10 9\n\
    +3 3 12 16\n\
    +3 3 16 10\n\
    +3 4 7 10\n\
    +3 4 8 7\n\
    +3 4 10 17\n\
    +3 4 17 8\n\
    +3 5 14 17\n\
    +3 5 15 14\n\
    +3 5 16 15\n\
    +3 5 17 16\n\
    +3 6 13 11\n\
    +3 7 8 6\n\
    +3 9 10 7\n\
    +3 11 12 9\n\
    +3 14 8 17\n\
    +3 15 13 14\n\
    +3 16 12 15\n\
    +3 17 10 16\n} transforms { TLIST\n");
    +  FOREACHvertex_(vertices) {
    +    qh_fprintf(fp, 9228, "%8.4g 0 0 0 # v%d\n 0 %8.4g 0 0\n0 0 %8.4g 0\n",
    +      radius, vertex->id, radius, radius);
    +    qh_printpoint3(fp, vertex->point);
    +    qh_fprintf(fp, 9229, "1\n");
    +  }
    +  qh_fprintf(fp, 9230, "}}}\n");
    +} /* printspheres */
    +
    +
    +/*----------------------------------------------
    +-printsummary-
    +                see libqhull.c
    +*/
    +
    +/*---------------------------------
    +
    +  qh_printvdiagram( fp, format, facetlist, facets, printall )
    +    print voronoi diagram
    +      # of pairs of input sites
    +      #indices site1 site2 vertex1 ...
    +
    +    sites indexed by input point id
    +      point 0 is the first input point
    +    vertices indexed by 'o' and 'p' order
    +      vertex 0 is the 'vertex-at-infinity'
    +      vertex 1 is the first Voronoi vertex
    +
    +  see:
    +    qh_printvoronoi()
    +    qh_eachvoronoi_all()
    +
    +  notes:
    +    if all facets are upperdelaunay,
    +      prints upper hull (furthest-site Voronoi diagram)
    +*/
    +void qh_printvdiagram(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices;
    +  int totcount, numcenters;
    +  boolT isLower;
    +  qh_RIDGE innerouter= qh_RIDGEall;
    +  printvridgeT printvridge= NULL;
    +
    +  if (format == qh_PRINTvertices) {
    +    innerouter= qh_RIDGEall;
    +    printvridge= qh_printvridge;
    +  }else if (format == qh_PRINTinner) {
    +    innerouter= qh_RIDGEinner;
    +    printvridge= qh_printvnorm;
    +  }else if (format == qh_PRINTouter) {
    +    innerouter= qh_RIDGEouter;
    +    printvridge= qh_printvnorm;
    +  }else {
    +    qh_fprintf(qh ferr, 6219, "Qhull internal error (qh_printvdiagram): unknown print format %d.\n", format);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  vertices= qh_markvoronoi(facetlist, facets, printall, &isLower, &numcenters);
    +  totcount= qh_printvdiagram2(NULL, NULL, vertices, innerouter, False);
    +  qh_fprintf(fp, 9231, "%d\n", totcount);
    +  totcount= qh_printvdiagram2(fp, printvridge, vertices, innerouter, True /* inorder*/);
    +  qh_settempfree(&vertices);
    +#if 0  /* for testing qh_eachvoronoi_all */
    +  qh_fprintf(fp, 9232, "\n");
    +  totcount= qh_eachvoronoi_all(fp, printvridge, qh UPPERdelaunay, innerouter, True /* inorder*/);
    +  qh_fprintf(fp, 9233, "%d\n", totcount);
    +#endif
    +} /* printvdiagram */
    +
    +/*---------------------------------
    +
    +  qh_printvdiagram2( fp, printvridge, vertices, innerouter, inorder )
    +    visit all pairs of input sites (vertices) for selected Voronoi vertices
    +    vertices may include NULLs
    +
    +  innerouter:
    +    qh_RIDGEall   print inner ridges(bounded) and outer ridges(unbounded)
    +    qh_RIDGEinner print only inner ridges
    +    qh_RIDGEouter print only outer ridges
    +
    +  inorder:
    +    print 3-d Voronoi vertices in order
    +
    +  assumes:
    +    qh_markvoronoi marked facet->visitid for Voronoi vertices
    +    all facet->seen= False
    +    all facet->seen2= True
    +
    +  returns:
    +    total number of Voronoi ridges
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers) for each ridge
    +      [see qh_eachvoronoi()]
    +
    +  see:
    +    qh_eachvoronoi_all()
    +*/
    +int qh_printvdiagram2(FILE *fp, printvridgeT printvridge, setT *vertices, qh_RIDGE innerouter, boolT inorder) {
    +  int totcount= 0;
    +  int vertex_i, vertex_n;
    +  vertexT *vertex;
    +
    +  FORALLvertices
    +    vertex->seen= False;
    +  FOREACHvertex_i_(vertices) {
    +    if (vertex) {
    +      if (qh GOODvertex > 0 && qh_pointid(vertex->point)+1 != qh GOODvertex)
    +        continue;
    +      totcount += qh_eachvoronoi(fp, printvridge, vertex, !qh_ALL, innerouter, inorder);
    +    }
    +  }
    +  return totcount;
    +} /* printvdiagram2 */
    +
    +/*---------------------------------
    +
    +  qh_printvertex( fp, vertex )
    +    prints the information in a vertex
    +    Duplicated as operator<< [QhullVertex.cpp]
    +*/
    +void qh_printvertex(FILE *fp, vertexT *vertex) {
    +  pointT *point;
    +  int k, count= 0;
    +  facetT *neighbor, **neighborp;
    +  realT r; /*bug fix*/
    +
    +  if (!vertex) {
    +    qh_fprintf(fp, 9234, "  NULLvertex\n");
    +    return;
    +  }
    +  qh_fprintf(fp, 9235, "- p%d(v%d):", qh_pointid(vertex->point), vertex->id);
    +  point= vertex->point;
    +  if (point) {
    +    for (k=qh hull_dim; k--; ) {
    +      r= *point++;
    +      qh_fprintf(fp, 9236, " %5.2g", r);
    +    }
    +  }
    +  if (vertex->deleted)
    +    qh_fprintf(fp, 9237, " deleted");
    +  if (vertex->delridge)
    +    qh_fprintf(fp, 9238, " ridgedeleted");
    +  qh_fprintf(fp, 9239, "\n");
    +  if (vertex->neighbors) {
    +    qh_fprintf(fp, 9240, "  neighbors:");
    +    FOREACHneighbor_(vertex) {
    +      if (++count % 100 == 0)
    +        qh_fprintf(fp, 9241, "\n     ");
    +      qh_fprintf(fp, 9242, " f%d", neighbor->id);
    +    }
    +    qh_fprintf(fp, 9243, "\n");
    +  }
    +} /* printvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_printvertexlist( fp, string, facetlist, facets, printall )
    +    prints vertices used by a facetlist or facet set
    +    tests qh_skipfacet() if !printall
    +*/
    +void qh_printvertexlist(FILE *fp, const char* string, facetT *facetlist,
    +                         setT *facets, boolT printall) {
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  qh_fprintf(fp, 9244, "%s", string);
    +  FOREACHvertex_(vertices)
    +    qh_printvertex(fp, vertex);
    +  qh_settempfree(&vertices);
    +} /* printvertexlist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printvertices( fp, string, vertices )
    +    prints vertices in a set
    +    duplicated as printVertexSet [QhullVertex.cpp]
    +*/
    +void qh_printvertices(FILE *fp, const char* string, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  qh_fprintf(fp, 9245, "%s", string);
    +  FOREACHvertex_(vertices)
    +    qh_fprintf(fp, 9246, " p%d(v%d)", qh_pointid(vertex->point), vertex->id);
    +  qh_fprintf(fp, 9247, "\n");
    +} /* printvertices */
    +
    +/*---------------------------------
    +
    +  qh_printvneighbors( fp, facetlist, facets, printall )
    +    print vertex neighbors of vertices in facetlist and facets ('FN')
    +
    +  notes:
    +    qh_countfacets clears facet->visitid for non-printed facets
    +
    +  design:
    +    collect facet count and related statistics
    +    if necessary, build neighbor sets for each vertex
    +    collect vertices in facetlist and facets
    +    build a point array for point->vertex and point->coplanar facet
    +    for each point
    +      list vertex neighbors or coplanar facet
    +*/
    +void qh_printvneighbors(FILE *fp, facetT* facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numneighbors, numcoplanars, numtricoplanars;
    +  setT *vertices, *vertex_points, *coplanar_points;
    +  int numpoints= qh num_points + qh_setsize(qh other_points);
    +  vertexT *vertex, **vertexp;
    +  int vertex_i, vertex_n;
    +  facetT *facet, **facetp, *neighbor, **neighborp;
    +  pointT *point, **pointp;
    +
    +  qh_countfacets(facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);  /* sets facet->visitid */
    +  qh_fprintf(fp, 9248, "%d\n", numpoints);
    +  qh_vertexneighbors();
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  vertex_points= qh_settemp(numpoints);
    +  coplanar_points= qh_settemp(numpoints);
    +  qh_setzero(vertex_points, 0, numpoints);
    +  qh_setzero(coplanar_points, 0, numpoints);
    +  FOREACHvertex_(vertices)
    +    qh_point_add(vertex_points, vertex->point, vertex);
    +  FORALLfacet_(facetlist) {
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(coplanar_points, point, facet);
    +  }
    +  FOREACHfacet_(facets) {
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(coplanar_points, point, facet);
    +  }
    +  FOREACHvertex_i_(vertex_points) {
    +    if (vertex) {
    +      numneighbors= qh_setsize(vertex->neighbors);
    +      qh_fprintf(fp, 9249, "%d", numneighbors);
    +      if (qh hull_dim == 3)
    +        qh_order_vertexneighbors(vertex);
    +      else if (qh hull_dim >= 4)
    +        qsort(SETaddr_(vertex->neighbors, facetT), (size_t)numneighbors,
    +             sizeof(facetT *), qh_compare_facetvisit);
    +      FOREACHneighbor_(vertex)
    +        qh_fprintf(fp, 9250, " %d",
    +                 neighbor->visitid ? neighbor->visitid - 1 : 0 - neighbor->id);
    +      qh_fprintf(fp, 9251, "\n");
    +    }else if ((facet= SETelemt_(coplanar_points, vertex_i, facetT)))
    +      qh_fprintf(fp, 9252, "1 %d\n",
    +                  facet->visitid ? facet->visitid - 1 : 0 - facet->id);
    +    else
    +      qh_fprintf(fp, 9253, "0\n");
    +  }
    +  qh_settempfree(&coplanar_points);
    +  qh_settempfree(&vertex_points);
    +  qh_settempfree(&vertices);
    +} /* printvneighbors */
    +
    +/*---------------------------------
    +
    +  qh_printvoronoi( fp, format, facetlist, facets, printall )
    +    print voronoi diagram in 'o' or 'G' format
    +    for 'o' format
    +      prints voronoi centers for each facet and for infinity
    +      for each vertex, lists ids of printed facets or infinity
    +      assumes facetlist and facets are disjoint
    +    for 'G' format
    +      prints an OFF object
    +      adds a 0 coordinate to center
    +      prints infinity but does not list in vertices
    +
    +  see:
    +    qh_printvdiagram()
    +
    +  notes:
    +    if 'o',
    +      prints a line for each point except "at-infinity"
    +    if all facets are upperdelaunay,
    +      reverses lower and upper hull
    +*/
    +void qh_printvoronoi(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int k, numcenters, numvertices= 0, numneighbors, numinf, vid=1, vertex_i, vertex_n;
    +  facetT *facet, **facetp, *neighbor, **neighborp;
    +  setT *vertices;
    +  vertexT *vertex;
    +  boolT isLower;
    +  unsigned int numfacets= (unsigned int) qh num_facets;
    +
    +  vertices= qh_markvoronoi(facetlist, facets, printall, &isLower, &numcenters);
    +  FOREACHvertex_i_(vertices) {
    +    if (vertex) {
    +      numvertices++;
    +      numneighbors = numinf = 0;
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visitid == 0)
    +          numinf= 1;
    +        else if (neighbor->visitid < numfacets)
    +          numneighbors++;
    +      }
    +      if (numinf && !numneighbors) {
    +        SETelem_(vertices, vertex_i)= NULL;
    +        numvertices--;
    +      }
    +    }
    +  }
    +  if (format == qh_PRINTgeom)
    +    qh_fprintf(fp, 9254, "{appearance {+edge -face} OFF %d %d 1 # Voronoi centers and cells\n",
    +                numcenters, numvertices);
    +  else
    +    qh_fprintf(fp, 9255, "%d\n%d %d 1\n", qh hull_dim-1, numcenters, qh_setsize(vertices));
    +  if (format == qh_PRINTgeom) {
    +    for (k=qh hull_dim-1; k--; )
    +      qh_fprintf(fp, 9256, qh_REAL_1, 0.0);
    +    qh_fprintf(fp, 9257, " 0 # infinity not used\n");
    +  }else {
    +    for (k=qh hull_dim-1; k--; )
    +      qh_fprintf(fp, 9258, qh_REAL_1, qh_INFINITE);
    +    qh_fprintf(fp, 9259, "\n");
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->visitid && facet->visitid < numfacets) {
    +      if (format == qh_PRINTgeom)
    +        qh_fprintf(fp, 9260, "# %d f%d\n", vid++, facet->id);
    +      qh_printcenter(fp, format, NULL, facet);
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (facet->visitid && facet->visitid < numfacets) {
    +      if (format == qh_PRINTgeom)
    +        qh_fprintf(fp, 9261, "# %d f%d\n", vid++, facet->id);
    +      qh_printcenter(fp, format, NULL, facet);
    +    }
    +  }
    +  FOREACHvertex_i_(vertices) {
    +    numneighbors= 0;
    +    numinf=0;
    +    if (vertex) {
    +      if (qh hull_dim == 3)
    +        qh_order_vertexneighbors(vertex);
    +      else if (qh hull_dim >= 4)
    +        qsort(SETaddr_(vertex->neighbors, facetT),
    +             (size_t)qh_setsize(vertex->neighbors),
    +             sizeof(facetT *), qh_compare_facetvisit);
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visitid == 0)
    +          numinf= 1;
    +        else if (neighbor->visitid < numfacets)
    +          numneighbors++;
    +      }
    +    }
    +    if (format == qh_PRINTgeom) {
    +      if (vertex) {
    +        qh_fprintf(fp, 9262, "%d", numneighbors);
    +        FOREACHneighbor_(vertex) {
    +          if (neighbor->visitid && neighbor->visitid < numfacets)
    +            qh_fprintf(fp, 9263, " %d", neighbor->visitid);
    +        }
    +        qh_fprintf(fp, 9264, " # p%d(v%d)\n", vertex_i, vertex->id);
    +      }else
    +        qh_fprintf(fp, 9265, " # p%d is coplanar or isolated\n", vertex_i);
    +    }else {
    +      if (numinf)
    +        numneighbors++;
    +      qh_fprintf(fp, 9266, "%d", numneighbors);
    +      if (vertex) {
    +        FOREACHneighbor_(vertex) {
    +          if (neighbor->visitid == 0) {
    +            if (numinf) {
    +              numinf= 0;
    +              qh_fprintf(fp, 9267, " %d", neighbor->visitid);
    +            }
    +          }else if (neighbor->visitid < numfacets)
    +            qh_fprintf(fp, 9268, " %d", neighbor->visitid);
    +        }
    +      }
    +      qh_fprintf(fp, 9269, "\n");
    +    }
    +  }
    +  if (format == qh_PRINTgeom)
    +    qh_fprintf(fp, 9270, "}\n");
    +  qh_settempfree(&vertices);
    +} /* printvoronoi */
    +
    +/*---------------------------------
    +
    +  qh_printvnorm( fp, vertex, vertexA, centers, unbounded )
    +    print one separating plane of the Voronoi diagram for a pair of input sites
    +    unbounded==True if centers includes vertex-at-infinity
    +
    +  assumes:
    +    qh_ASvoronoi and qh_vertexneighbors() already set
    +
    +  note:
    +    parameter unbounded is UNUSED by this callback
    +
    +  see:
    +    qh_printvdiagram()
    +    qh_eachvoronoi()
    +*/
    +void qh_printvnorm(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded) {
    +  pointT *normal;
    +  realT offset;
    +  int k;
    +  QHULL_UNUSED(unbounded);
    +
    +  normal= qh_detvnorm(vertex, vertexA, centers, &offset);
    +  qh_fprintf(fp, 9271, "%d %d %d ",
    +      2+qh hull_dim, qh_pointid(vertex->point), qh_pointid(vertexA->point));
    +  for (k=0; k< qh hull_dim-1; k++)
    +    qh_fprintf(fp, 9272, qh_REAL_1, normal[k]);
    +  qh_fprintf(fp, 9273, qh_REAL_1, offset);
    +  qh_fprintf(fp, 9274, "\n");
    +} /* printvnorm */
    +
    +/*---------------------------------
    +
    +  qh_printvridge( fp, vertex, vertexA, centers, unbounded )
    +    print one ridge of the Voronoi diagram for a pair of input sites
    +    unbounded==True if centers includes vertex-at-infinity
    +
    +  see:
    +    qh_printvdiagram()
    +
    +  notes:
    +    the user may use a different function
    +    parameter unbounded is UNUSED
    +*/
    +void qh_printvridge(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded) {
    +  facetT *facet, **facetp;
    +  QHULL_UNUSED(unbounded);
    +
    +  qh_fprintf(fp, 9275, "%d %d %d", qh_setsize(centers)+2,
    +       qh_pointid(vertex->point), qh_pointid(vertexA->point));
    +  FOREACHfacet_(centers)
    +    qh_fprintf(fp, 9276, " %d", facet->visitid);
    +  qh_fprintf(fp, 9277, "\n");
    +} /* printvridge */
    +
    +/*---------------------------------
    +
    +  qh_projectdim3( source, destination )
    +    project 2-d 3-d or 4-d point to a 3-d point
    +    uses qh.DROPdim and qh.hull_dim
    +    source and destination may be the same
    +
    +  notes:
    +    allocate 4 elements to destination just in case
    +*/
    +void qh_projectdim3(pointT *source, pointT *destination) {
    +  int i,k;
    +
    +  for (k=0, i=0; k < qh hull_dim; k++) {
    +    if (qh hull_dim == 4) {
    +      if (k != qh DROPdim)
    +        destination[i++]= source[k];
    +    }else if (k == qh DROPdim)
    +      destination[i++]= 0;
    +    else
    +      destination[i++]= source[k];
    +  }
    +  while (i < 3)
    +    destination[i++]= 0.0;
    +} /* projectdim3 */
    +
    +/*---------------------------------
    +
    +  qh_readfeasible( dim, curline )
    +    read feasible point from current line and qh.fin
    +
    +  returns:
    +    number of lines read from qh.fin
    +    sets qh.feasible_point with malloc'd coordinates
    +
    +  notes:
    +    checks for qh.HALFspace
    +    assumes dim > 1
    +
    +  see:
    +    qh_setfeasible
    +*/
    +int qh_readfeasible(int dim, const char *curline) {
    +  boolT isfirst= True;
    +  int linecount= 0, tokcount= 0;
    +  const char *s;
    +  char *t, firstline[qh_MAXfirst+1];
    +  coordT *coords, value;
    +
    +  if (!qh HALFspace) {
    +    qh_fprintf(qh ferr, 6070, "qhull input error: feasible point(dim 1 coords) is only valid for halfspace intersection\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh feasible_string)
    +    qh_fprintf(qh ferr, 7057, "qhull input warning: feasible point(dim 1 coords) overrides 'Hn,n,n' feasible point for halfspace intersection\n");
    +  if (!(qh feasible_point= (coordT*)qh_malloc(dim* sizeof(coordT)))) {
    +    qh_fprintf(qh ferr, 6071, "qhull error: insufficient memory for feasible point\n");
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  coords= qh feasible_point;
    +  while ((s= (isfirst ?  curline : fgets(firstline, qh_MAXfirst, qh fin)))) {
    +    if (isfirst)
    +      isfirst= False;
    +    else
    +      linecount++;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      value= qh_strtod(s, &t);
    +      if (s == t)
    +        break;
    +      s= t;
    +      *(coords++)= value;
    +      if (++tokcount == dim) {
    +        while (isspace(*s))
    +          s++;
    +        qh_strtod(s, &t);
    +        if (s != t) {
    +          qh_fprintf(qh ferr, 6072, "qhull input error: coordinates for feasible point do not finish out the line: %s\n",
    +               s);
    +          qh_errexit(qh_ERRinput, NULL, NULL);
    +        }
    +        return linecount;
    +      }
    +    }
    +  }
    +  qh_fprintf(qh ferr, 6073, "qhull input error: only %d coordinates.  Could not read %d-d feasible point.\n",
    +           tokcount, dim);
    +  qh_errexit(qh_ERRinput, NULL, NULL);
    +  return 0;
    +} /* readfeasible */
    +
    +/*---------------------------------
    +
    +  qh_readpoints( numpoints, dimension, ismalloc )
    +    read points from qh.fin into qh.first_point, qh.num_points
    +    qh.fin is lines of coordinates, one per vertex, first line number of points
    +    if 'rbox D4',
    +      gives message
    +    if qh.ATinfinity,
    +      adds point-at-infinity for Delaunay triangulations
    +
    +  returns:
    +    number of points, array of point coordinates, dimension, ismalloc True
    +    if qh.DELAUNAY & !qh.PROJECTinput, projects points to paraboloid
    +        and clears qh.PROJECTdelaunay
    +    if qh.HALFspace, reads optional feasible point, reads halfspaces,
    +        converts to dual.
    +
    +  for feasible point in "cdd format" in 3-d:
    +    3 1
    +    coordinates
    +    comments
    +    begin
    +    n 4 real/integer
    +    ...
    +    end
    +
    +  notes:
    +    dimension will change in qh_initqhull_globals if qh.PROJECTinput
    +    uses malloc() since qh_mem not initialized
    +    FIXUP QH11012: qh_readpoints needs rewriting, too long
    +*/
    +coordT *qh_readpoints(int *numpoints, int *dimension, boolT *ismalloc) {
    +  coordT *points, *coords, *infinity= NULL;
    +  realT paraboloid, maxboloid= -REALmax, value;
    +  realT *coordp= NULL, *offsetp= NULL, *normalp= NULL;
    +  char *s= 0, *t, firstline[qh_MAXfirst+1];
    +  int diminput=0, numinput=0, dimfeasible= 0, newnum, k, tempi;
    +  int firsttext=0, firstshort=0, firstlong=0, firstpoint=0;
    +  int tokcount= 0, linecount=0, maxcount, coordcount=0;
    +  boolT islong, isfirst= True, wasbegin= False;
    +  boolT isdelaunay= qh DELAUNAY && !qh PROJECTinput;
    +
    +  if (qh CDDinput) {
    +    while ((s= fgets(firstline, qh_MAXfirst, qh fin))) {
    +      linecount++;
    +      if (qh HALFspace && linecount == 1 && isdigit(*s)) {
    +        dimfeasible= qh_strtol(s, &s);
    +        while (isspace(*s))
    +          s++;
    +        if (qh_strtol(s, &s) == 1)
    +          linecount += qh_readfeasible(dimfeasible, s);
    +        else
    +          dimfeasible= 0;
    +      }else if (!memcmp(firstline, "begin", (size_t)5) || !memcmp(firstline, "BEGIN", (size_t)5))
    +        break;
    +      else if (!*qh rbox_command)
    +        strncat(qh rbox_command, s, sizeof(qh rbox_command)-1);
    +    }
    +    if (!s) {
    +      qh_fprintf(qh ferr, 6074, "qhull input error: missing \"begin\" for cdd-formated input\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +  }
    +  while (!numinput && (s= fgets(firstline, qh_MAXfirst, qh fin))) {
    +    linecount++;
    +    if (!memcmp(s, "begin", (size_t)5) || !memcmp(s, "BEGIN", (size_t)5))
    +      wasbegin= True;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      if (!*s)
    +        break;
    +      if (!isdigit(*s)) {
    +        if (!*qh rbox_command) {
    +          strncat(qh rbox_command, s, sizeof(qh rbox_command)-1);
    +          firsttext= linecount;
    +        }
    +        break;
    +      }
    +      if (!diminput)
    +        diminput= qh_strtol(s, &s);
    +      else {
    +        numinput= qh_strtol(s, &s);
    +        if (numinput == 1 && diminput >= 2 && qh HALFspace && !qh CDDinput) {
    +          linecount += qh_readfeasible(diminput, s); /* checks if ok */
    +          dimfeasible= diminput;
    +          diminput= numinput= 0;
    +        }else
    +          break;
    +      }
    +    }
    +  }
    +  if (!s) {
    +    qh_fprintf(qh ferr, 6075, "qhull input error: short input file.  Did not find dimension and number of points\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (diminput > numinput) {
    +    tempi= diminput;    /* exchange dim and n, e.g., for cdd input format */
    +    diminput= numinput;
    +    numinput= tempi;
    +  }
    +  if (diminput < 2) {
    +    qh_fprintf(qh ferr, 6220,"qhull input error: dimension %d(first number) should be at least 2\n",
    +            diminput);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (isdelaunay) {
    +    qh PROJECTdelaunay= False;
    +    if (qh CDDinput)
    +      *dimension= diminput;
    +    else
    +      *dimension= diminput+1;
    +    *numpoints= numinput;
    +    if (qh ATinfinity)
    +      (*numpoints)++;
    +  }else if (qh HALFspace) {
    +    *dimension= diminput - 1;
    +    *numpoints= numinput;
    +    if (diminput < 3) {
    +      qh_fprintf(qh ferr, 6221,"qhull input error: dimension %d(first number, includes offset) should be at least 3 for halfspaces\n",
    +            diminput);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (dimfeasible) {
    +      if (dimfeasible != *dimension) {
    +        qh_fprintf(qh ferr, 6222,"qhull input error: dimension %d of feasible point is not one less than dimension %d for halfspaces\n",
    +          dimfeasible, diminput);
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +    }else
    +      qh_setfeasible(*dimension);
    +  }else {
    +    if (qh CDDinput)
    +      *dimension= diminput-1;
    +    else
    +      *dimension= diminput;
    +    *numpoints= numinput;
    +  }
    +  qh normal_size= *dimension * sizeof(coordT); /* for tracing with qh_printpoint */
    +  if (qh HALFspace) {
    +    qh half_space= coordp= (coordT*)qh_malloc(qh normal_size + sizeof(coordT));
    +    if (qh CDDinput) {
    +      offsetp= qh half_space;
    +      normalp= offsetp + 1;
    +    }else {
    +      normalp= qh half_space;
    +      offsetp= normalp + *dimension;
    +    }
    +  }
    +  qh maxline= diminput * (qh_REALdigits + 5);
    +  maximize_(qh maxline, 500);
    +  qh line= (char*)qh_malloc((qh maxline+1) * sizeof(char));
    +  *ismalloc= True;  /* use malloc since memory not setup */
    +  coords= points= qh temp_malloc=  /* numinput and diminput >=2 by QH6220 */
    +        (coordT*)qh_malloc((*numpoints)*(*dimension)*sizeof(coordT));
    +  if (!coords || !qh line || (qh HALFspace && !qh half_space)) {
    +    qh_fprintf(qh ferr, 6076, "qhull error: insufficient memory to read %d points\n",
    +            numinput);
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  if (isdelaunay && qh ATinfinity) {
    +    infinity= points + numinput * (*dimension);
    +    for (k= (*dimension) - 1; k--; )
    +      infinity[k]= 0.0;
    +  }
    +  maxcount= numinput * diminput;
    +  paraboloid= 0.0;
    +  while ((s= (isfirst ?  s : fgets(qh line, qh maxline, qh fin)))) {
    +    if (!isfirst) {
    +      linecount++;
    +      if (*s == 'e' || *s == 'E') {
    +        if (!memcmp(s, "end", (size_t)3) || !memcmp(s, "END", (size_t)3)) {
    +          if (qh CDDinput )
    +            break;
    +          else if (wasbegin)
    +            qh_fprintf(qh ferr, 7058, "qhull input warning: the input appears to be in cdd format.  If so, use 'Fd'\n");
    +        }
    +      }
    +    }
    +    islong= False;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      value= qh_strtod(s, &t);
    +      if (s == t) {
    +        if (!*qh rbox_command)
    +         strncat(qh rbox_command, s, sizeof(qh rbox_command)-1);
    +        if (*s && !firsttext)
    +          firsttext= linecount;
    +        if (!islong && !firstshort && coordcount)
    +          firstshort= linecount;
    +        break;
    +      }
    +      if (!firstpoint)
    +        firstpoint= linecount;
    +      s= t;
    +      if (++tokcount > maxcount)
    +        continue;
    +      if (qh HALFspace) {
    +        if (qh CDDinput)
    +          *(coordp++)= -value; /* both coefficients and offset */
    +        else
    +          *(coordp++)= value;
    +      }else {
    +        *(coords++)= value;
    +        if (qh CDDinput && !coordcount) {
    +          if (value != 1.0) {
    +            qh_fprintf(qh ferr, 6077, "qhull input error: for cdd format, point at line %d does not start with '1'\n",
    +                   linecount);
    +            qh_errexit(qh_ERRinput, NULL, NULL);
    +          }
    +          coords--;
    +        }else if (isdelaunay) {
    +          paraboloid += value * value;
    +          if (qh ATinfinity) {
    +            if (qh CDDinput)
    +              infinity[coordcount-1] += value;
    +            else
    +              infinity[coordcount] += value;
    +          }
    +        }
    +      }
    +      if (++coordcount == diminput) {
    +        coordcount= 0;
    +        if (isdelaunay) {
    +          *(coords++)= paraboloid;
    +          maximize_(maxboloid, paraboloid);
    +          paraboloid= 0.0;
    +        }else if (qh HALFspace) {
    +          if (!qh_sethalfspace(*dimension, coords, &coords, normalp, offsetp, qh feasible_point)) {
    +            qh_fprintf(qh ferr, 8048, "The halfspace was on line %d\n", linecount);
    +            if (wasbegin)
    +              qh_fprintf(qh ferr, 8049, "The input appears to be in cdd format.  If so, you should use option 'Fd'\n");
    +            qh_errexit(qh_ERRinput, NULL, NULL);
    +          }
    +          coordp= qh half_space;
    +        }
    +        while (isspace(*s))
    +          s++;
    +        if (*s) {
    +          islong= True;
    +          if (!firstlong)
    +            firstlong= linecount;
    +        }
    +      }
    +    }
    +    if (!islong && !firstshort && coordcount)
    +      firstshort= linecount;
    +    if (!isfirst && s - qh line >= qh maxline) {
    +      qh_fprintf(qh ferr, 6078, "qhull input error: line %d contained more than %d characters\n",
    +              linecount, (int) (s - qh line));   /* WARN64 */
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    isfirst= False;
    +  }
    +  if (tokcount != maxcount) {
    +    newnum= fmin_(numinput, tokcount/diminput);
    +    qh_fprintf(qh ferr, 7073,"\
    +qhull warning: instead of %d %d-dimensional points, input contains\n\
    +%d points and %d extra coordinates.  Line %d is the first\npoint",
    +       numinput, diminput, tokcount/diminput, tokcount % diminput, firstpoint);
    +    if (firsttext)
    +      qh_fprintf(qh ferr, 8051, ", line %d is the first comment", firsttext);
    +    if (firstshort)
    +      qh_fprintf(qh ferr, 8052, ", line %d is the first short\nline", firstshort);
    +    if (firstlong)
    +      qh_fprintf(qh ferr, 8053, ", line %d is the first long line", firstlong);
    +    qh_fprintf(qh ferr, 8054, ".  Continue with %d points.\n", newnum);
    +    numinput= newnum;
    +    if (isdelaunay && qh ATinfinity) {
    +      for (k= tokcount % diminput; k--; )
    +        infinity[k] -= *(--coords);
    +      *numpoints= newnum+1;
    +    }else {
    +      coords -= tokcount % diminput;
    +      *numpoints= newnum;
    +    }
    +  }
    +  if (isdelaunay && qh ATinfinity) {
    +    for (k= (*dimension) -1; k--; )
    +      infinity[k] /= numinput;
    +    if (coords == infinity)
    +      coords += (*dimension) -1;
    +    else {
    +      for (k=0; k < (*dimension) -1; k++)
    +        *(coords++)= infinity[k];
    +    }
    +    *(coords++)= maxboloid * 1.1;
    +  }
    +  if (qh rbox_command[0]) {
    +    qh rbox_command[strlen(qh rbox_command)-1]= '\0';
    +    if (!strcmp(qh rbox_command, "./rbox D4"))
    +      qh_fprintf(qh ferr, 8055, "\n\
    +This is the qhull test case.  If any errors or core dumps occur,\n\
    +recompile qhull with 'make new'.  If errors still occur, there is\n\
    +an incompatibility.  You should try a different compiler.  You can also\n\
    +change the choices in user.h.  If you discover the source of the problem,\n\
    +please send mail to qhull_bug@qhull.org.\n\
    +\n\
    +Type 'qhull' for a short list of options.\n");
    +  }
    +  qh_free(qh line);
    +  qh line= NULL;
    +  if (qh half_space) {
    +    qh_free(qh half_space);
    +    qh half_space= NULL;
    +  }
    +  qh temp_malloc= NULL;
    +  trace1((qh ferr, 1008,"qh_readpoints: read in %d %d-dimensional points\n",
    +          numinput, diminput));
    +  return(points);
    +} /* readpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfeasible( dim )
    +    set qh.feasible_point from qh.feasible_string in "n,n,n" or "n n n" format
    +
    +  notes:
    +    "n,n,n" already checked by qh_initflags()
    +    see qh_readfeasible()
    +    called only once from qh_new_qhull, otherwise leaks memory
    +*/
    +void qh_setfeasible(int dim) {
    +  int tokcount= 0;
    +  char *s;
    +  coordT *coords, value;
    +
    +  if (!(s= qh feasible_string)) {
    +    qh_fprintf(qh ferr, 6223, "\
    +qhull input error: halfspace intersection needs a feasible point.\n\
    +Either prepend the input with 1 point or use 'Hn,n,n'.  See manual.\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (!(qh feasible_point= (pointT*)qh_malloc(dim * sizeof(coordT)))) {
    +    qh_fprintf(qh ferr, 6079, "qhull error: insufficient memory for 'Hn,n,n'\n");
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  coords= qh feasible_point;
    +  while (*s) {
    +    value= qh_strtod(s, &s);
    +    if (++tokcount > dim) {
    +      qh_fprintf(qh ferr, 7059, "qhull input warning: more coordinates for 'H%s' than dimension %d\n",
    +          qh feasible_string, dim);
    +      break;
    +    }
    +    *(coords++)= value;
    +    if (*s)
    +      s++;
    +  }
    +  while (++tokcount <= dim)
    +    *(coords++)= 0.0;
    +} /* setfeasible */
    +
    +/*---------------------------------
    +
    +  qh_skipfacet( facet )
    +    returns 'True' if this facet is not to be printed
    +
    +  notes:
    +    based on the user provided slice thresholds and 'good' specifications
    +*/
    +boolT qh_skipfacet(facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +
    +  if (qh PRINTneighbors) {
    +    if (facet->good)
    +      return !qh PRINTgood;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->good)
    +        return False;
    +    }
    +    return True;
    +  }else if (qh PRINTgood)
    +    return !facet->good;
    +  else if (!facet->normal)
    +    return True;
    +  return(!qh_inthresholds(facet->normal, NULL));
    +} /* skipfacet */
    +
    +/*---------------------------------
    +
    +  qh_skipfilename( string )
    +    returns pointer to character after filename
    +
    +  notes:
    +    skips leading spaces
    +    ends with spacing or eol
    +    if starts with ' or " ends with the same, skipping \' or \"
    +    For qhull, qh_argv_to_command() only uses double quotes
    +*/
    +char *qh_skipfilename(char *filename) {
    +  char *s= filename;  /* non-const due to return */
    +  char c;
    +
    +  while (*s && isspace(*s))
    +    s++;
    +  c= *s++;
    +  if (c == '\0') {
    +    qh_fprintf(qh ferr, 6204, "qhull input error: filename expected, none found.\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (c == '\'' || c == '"') {
    +    while (*s !=c || s[-1] == '\\') {
    +      if (!*s) {
    +        qh_fprintf(qh ferr, 6203, "qhull input error: missing quote after filename -- %s\n", filename);
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +      s++;
    +    }
    +    s++;
    +  }
    +  else while (*s && !isspace(*s))
    +      s++;
    +  return s;
    +} /* skipfilename */
    +
    diff --git a/xs/src/qhull/src/libqhull/io.h b/xs/src/qhull/src/libqhull/io.h
    new file mode 100644
    index 0000000000..eca0369d30
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/io.h
    @@ -0,0 +1,159 @@
    +/*
      ---------------------------------
    +
    +   io.h
    +   declarations of Input/Output functions
    +
    +   see README, libqhull.h and io.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/io.h#1 $$Change: 1981 $
    +   $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFio
    +#define qhDEFio 1
    +
    +#include "libqhull.h"
    +
    +/*============ constants and flags ==================*/
    +
    +/*----------------------------------
    +
    +  qh_MAXfirst
    +    maximum length of first two lines of stdin
    +*/
    +#define qh_MAXfirst  200
    +
    +/*----------------------------------
    +
    +  qh_MINradius
    +    min radius for Gp and Gv, fraction of maxcoord
    +*/
    +#define qh_MINradius 0.02
    +
    +/*----------------------------------
    +
    +  qh_GEOMepsilon
    +    adjust outer planes for 'lines closer' and geomview roundoff.
    +    This prevents bleed through.
    +*/
    +#define qh_GEOMepsilon 2e-3
    +
    +/*----------------------------------
    +
    +  qh_WHITESPACE
    +    possible values of white space
    +*/
    +#define qh_WHITESPACE " \n\t\v\r\f"
    +
    +
    +/*----------------------------------
    +
    +  qh_RIDGE
    +    to select which ridges to print in qh_eachvoronoi
    +*/
    +typedef enum
    +{
    +    qh_RIDGEall = 0, qh_RIDGEinner, qh_RIDGEouter
    +}
    +qh_RIDGE;
    +
    +/*----------------------------------
    +
    +  printvridgeT
    +    prints results of qh_printvdiagram
    +
    +  see:
    +    qh_printvridge for an example
    +*/
    +typedef void (*printvridgeT)(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +
    +/*============== -prototypes in alphabetical order =========*/
    +
    +void    qh_dfacet(unsigned id);
    +void    qh_dvertex(unsigned id);
    +int     qh_compare_facetarea(const void *p1, const void *p2);
    +int     qh_compare_facetmerge(const void *p1, const void *p2);
    +int     qh_compare_facetvisit(const void *p1, const void *p2);
    +int     qh_compare_vertexpoint(const void *p1, const void *p2); /* not used, not in libqhull_r.h */
    +void    qh_copyfilename(char *filename, int size, const char* source, int length);
    +void    qh_countfacets(facetT *facetlist, setT *facets, boolT printall,
    +              int *numfacetsp, int *numsimplicialp, int *totneighborsp,
    +              int *numridgesp, int *numcoplanarsp, int *numnumtricoplanarsp);
    +pointT *qh_detvnorm(vertexT *vertex, vertexT *vertexA, setT *centers, realT *offsetp);
    +setT   *qh_detvridge(vertexT *vertex);
    +setT   *qh_detvridge3(vertexT *atvertex, vertexT *vertex);
    +int     qh_eachvoronoi(FILE *fp, printvridgeT printvridge, vertexT *atvertex, boolT visitall, qh_RIDGE innerouter, boolT inorder);
    +int     qh_eachvoronoi_all(FILE *fp, printvridgeT printvridge, boolT isUpper, qh_RIDGE innerouter, boolT inorder);
    +void    qh_facet2point(facetT *facet, pointT **point0, pointT **point1, realT *mindist);
    +setT   *qh_facetvertices(facetT *facetlist, setT *facets, boolT allfacets);
    +void    qh_geomplanes(facetT *facet, realT *outerplane, realT *innerplane);
    +void    qh_markkeep(facetT *facetlist);
    +setT   *qh_markvoronoi(facetT *facetlist, setT *facets, boolT printall, boolT *isLowerp, int *numcentersp);
    +void    qh_order_vertexneighbors(vertexT *vertex);
    +void    qh_prepare_output(void);
    +void    qh_printafacet(FILE *fp, qh_PRINT format, facetT *facet, boolT printall);
    +void    qh_printbegin(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printcenter(FILE *fp, qh_PRINT format, const char *string, facetT *facet);
    +void    qh_printcentrum(FILE *fp, facetT *facet, realT radius);
    +void    qh_printend(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printend4geom(FILE *fp, facetT *facet, int *num, boolT printall);
    +void    qh_printextremes(FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printextremes_2d(FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printextremes_d(FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printfacet(FILE *fp, facetT *facet);
    +void    qh_printfacet2math(FILE *fp, facetT *facet, qh_PRINT format, int notfirst);
    +void    qh_printfacet2geom(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet2geom_points(FILE *fp, pointT *point1, pointT *point2,
    +                               facetT *facet, realT offset, realT color[3]);
    +void    qh_printfacet3math(FILE *fp, facetT *facet, qh_PRINT format, int notfirst);
    +void    qh_printfacet3geom_nonsimplicial(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet3geom_points(FILE *fp, setT *points, facetT *facet, realT offset, realT color[3]);
    +void    qh_printfacet3geom_simplicial(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet3vertex(FILE *fp, facetT *facet, qh_PRINT format);
    +void    qh_printfacet4geom_nonsimplicial(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet4geom_simplicial(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacetNvertex_nonsimplicial(FILE *fp, facetT *facet, int id, qh_PRINT format);
    +void    qh_printfacetNvertex_simplicial(FILE *fp, facetT *facet, qh_PRINT format);
    +void    qh_printfacetheader(FILE *fp, facetT *facet);
    +void    qh_printfacetridges(FILE *fp, facetT *facet);
    +void    qh_printfacets(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printhyperplaneintersection(FILE *fp, facetT *facet1, facetT *facet2,
    +                   setT *vertices, realT color[3]);
    +void    qh_printneighborhood(FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall);
    +void    qh_printline3geom(FILE *fp, pointT *pointA, pointT *pointB, realT color[3]);
    +void    qh_printpoint(FILE *fp, const char *string, pointT *point);
    +void    qh_printpointid(FILE *fp, const char *string, int dim, pointT *point, int id);
    +void    qh_printpoint3(FILE *fp, pointT *point);
    +void    qh_printpoints_out(FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printpointvect(FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius, realT color[3]);
    +void    qh_printpointvect2(FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius);
    +void    qh_printridge(FILE *fp, ridgeT *ridge);
    +void    qh_printspheres(FILE *fp, setT *vertices, realT radius);
    +void    qh_printvdiagram(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +int     qh_printvdiagram2(FILE *fp, printvridgeT printvridge, setT *vertices, qh_RIDGE innerouter, boolT inorder);
    +void    qh_printvertex(FILE *fp, vertexT *vertex);
    +void    qh_printvertexlist(FILE *fp, const char* string, facetT *facetlist,
    +                         setT *facets, boolT printall);
    +void    qh_printvertices(FILE *fp, const char* string, setT *vertices);
    +void    qh_printvneighbors(FILE *fp, facetT* facetlist, setT *facets, boolT printall);
    +void    qh_printvoronoi(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printvnorm(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +void    qh_printvridge(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +void    qh_produce_output(void);
    +void    qh_produce_output2(void);
    +void    qh_projectdim3(pointT *source, pointT *destination);
    +int     qh_readfeasible(int dim, const char *curline);
    +coordT *qh_readpoints(int *numpoints, int *dimension, boolT *ismalloc);
    +void    qh_setfeasible(int dim);
    +boolT   qh_skipfacet(facetT *facet);
    +char   *qh_skipfilename(char *filename);
    +
    +#endif /* qhDEFio */
    diff --git a/xs/src/qhull/src/libqhull/libqhull.c b/xs/src/qhull/src/libqhull/libqhull.c
    new file mode 100644
    index 0000000000..7696a8a9fe
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/libqhull.c
    @@ -0,0 +1,1403 @@
    +/*
      ---------------------------------
    +
    +   libqhull.c
    +   Quickhull algorithm for convex hulls
    +
    +   qhull() and top-level routines
    +
    +   see qh-qhull.htm, libqhull.h, unix.c
    +
    +   see qhull_a.h for internal functions
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/libqhull.c#3 $$Change: 2047 $
    +   $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*============= functions in alphabetic order after qhull() =======*/
    +
    +/*---------------------------------
    +
    +  qh_qhull()
    +    compute DIM3 convex hull of qh.num_points starting at qh.first_point
    +    qh contains all global options and variables
    +
    +  returns:
    +    returns polyhedron
    +      qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices,
    +
    +    returns global variables
    +      qh.hulltime, qh.max_outside, qh.interior_point, qh.max_vertex, qh.min_vertex
    +
    +    returns precision constants
    +      qh.ANGLEround, centrum_radius, cos_max, DISTround, MAXabs_coord, ONEmerge
    +
    +  notes:
    +    unless needed for output
    +      qh.max_vertex and qh.min_vertex are max/min due to merges
    +
    +  see:
    +    to add individual points to either qh.num_points
    +      use qh_addpoint()
    +
    +    if qh.GETarea
    +      qh_produceoutput() returns qh.totarea and qh.totvol via qh_getarea()
    +
    +  design:
    +    record starting time
    +    initialize hull and partition points
    +    build convex hull
    +    unless early termination
    +      update facet->maxoutside for vertices, coplanar, and near-inside points
    +    error if temporary sets exist
    +    record end time
    +*/
    +
    +void qh_qhull(void) {
    +  int numoutside;
    +
    +  qh hulltime= qh_CPUclock;
    +  if (qh RERUN || qh JOGGLEmax < REALmax/2)
    +    qh_build_withrestart();
    +  else {
    +    qh_initbuild();
    +    qh_buildhull();
    +  }
    +  if (!qh STOPpoint && !qh STOPcone) {
    +    if (qh ZEROall_ok && !qh TESTvneighbors && qh MERGEexact)
    +      qh_checkzero( qh_ALL);
    +    if (qh ZEROall_ok && !qh TESTvneighbors && !qh WAScoplanar) {
    +      trace2((qh ferr, 2055, "qh_qhull: all facets are clearly convex and no coplanar points.  Post-merging and check of maxout not needed.\n"));
    +      qh DOcheckmax= False;
    +    }else {
    +      if (qh MERGEexact || (qh hull_dim > qh_DIMreduceBuild && qh PREmerge))
    +        qh_postmerge("First post-merge", qh premerge_centrum, qh premerge_cos,
    +             (qh POSTmerge ? False : qh TESTvneighbors));
    +      else if (!qh POSTmerge && qh TESTvneighbors)
    +        qh_postmerge("For testing vertex neighbors", qh premerge_centrum,
    +             qh premerge_cos, True);
    +      if (qh POSTmerge)
    +        qh_postmerge("For post-merging", qh postmerge_centrum,
    +             qh postmerge_cos, qh TESTvneighbors);
    +      if (qh visible_list == qh facet_list) { /* i.e., merging done */
    +        qh findbestnew= True;
    +        qh_partitionvisible(/*qh.visible_list*/ !qh_ALL, &numoutside);
    +        qh findbestnew= False;
    +        qh_deletevisible(/*qh.visible_list*/);
    +        qh_resetlists(False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +      }
    +    }
    +    if (qh DOcheckmax){
    +      if (qh REPORTfreq) {
    +        qh_buildtracing(NULL, NULL);
    +        qh_fprintf(qh ferr, 8115, "\nTesting all coplanar points.\n");
    +      }
    +      qh_check_maxout();
    +    }
    +    if (qh KEEPnearinside && !qh maxoutdone)
    +      qh_nearcoplanar();
    +  }
    +  if (qh_setsize(qhmem.tempstack) != 0) {
    +    qh_fprintf(qh ferr, 6164, "qhull internal error (qh_qhull): temporary sets not empty(%d)\n",
    +             qh_setsize(qhmem.tempstack));
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh hulltime= qh_CPUclock - qh hulltime;
    +  qh QHULLfinished= True;
    +  trace1((qh ferr, 1036, "Qhull: algorithm completed\n"));
    +} /* qhull */
    +
    +/*---------------------------------
    +
    +  qh_addpoint( furthest, facet, checkdist )
    +    add point (usually furthest point) above facet to hull
    +    if checkdist,
    +      check that point is above facet.
    +      if point is not outside of the hull, uses qh_partitioncoplanar()
    +      assumes that facet is defined by qh_findbestfacet()
    +    else if facet specified,
    +      assumes that point is above facet (major damage if below)
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    returns False if user requested an early termination
    +     qh.visible_list, newfacet_list, delvertex_list, NEWfacets may be defined
    +    updates qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices
    +    clear qh.maxoutdone (will need to call qh_check_maxout() for facet->maxoutside)
    +    if unknown point, adds a pointer to qh.other_points
    +      do not deallocate the point's coordinates
    +
    +  notes:
    +    assumes point is near its best facet and not at a local minimum of a lens
    +      distributions.  Use qh_findbestfacet to avoid this case.
    +    uses qh.visible_list, qh.newfacet_list, qh.delvertex_list, qh.NEWfacets
    +
    +  see also:
    +    qh_triangulate() -- triangulate non-simplicial facets
    +
    +  design:
    +    add point to other_points if needed
    +    if checkdist
    +      if point not above facet
    +        partition coplanar point
    +        exit
    +    exit if pre STOPpoint requested
    +    find horizon and visible facets for point
    +    make new facets for point to horizon
    +    make hyperplanes for point
    +    compute balance statistics
    +    match neighboring new facets
    +    update vertex neighbors and delete interior vertices
    +    exit if STOPcone requested
    +    merge non-convex new facets
    +    if merge found, many merges, or 'Qf'
    +       use qh_findbestnew() instead of qh_findbest()
    +    partition outside points from visible facets
    +    delete visible facets
    +    check polyhedron if requested
    +    exit if post STOPpoint requested
    +    reset working lists of facets and vertices
    +*/
    +boolT qh_addpoint(pointT *furthest, facetT *facet, boolT checkdist) {
    +  int goodvisible, goodhorizon;
    +  vertexT *vertex;
    +  facetT *newfacet;
    +  realT dist, newbalance, pbalance;
    +  boolT isoutside= False;
    +  int numpart, numpoints, numnew, firstnew;
    +
    +  qh maxoutdone= False;
    +  if (qh_pointid(furthest) == qh_IDunknown)
    +    qh_setappend(&qh other_points, furthest);
    +  if (!facet) {
    +    qh_fprintf(qh ferr, 6213, "qhull internal error (qh_addpoint): NULL facet.  Need to call qh_findbestfacet first\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (checkdist) {
    +    facet= qh_findbest(furthest, facet, !qh_ALL, !qh_ISnewfacets, !qh_NOupper,
    +                        &dist, &isoutside, &numpart);
    +    zzadd_(Zpartition, numpart);
    +    if (!isoutside) {
    +      zinc_(Znotmax);  /* last point of outsideset is no longer furthest. */
    +      facet->notfurthest= True;
    +      qh_partitioncoplanar(furthest, facet, &dist);
    +      return True;
    +    }
    +  }
    +  qh_buildtracing(furthest, facet);
    +  if (qh STOPpoint < 0 && qh furthest_id == -qh STOPpoint-1) {
    +    facet->notfurthest= True;
    +    return False;
    +  }
    +  qh_findhorizon(furthest, facet, &goodvisible, &goodhorizon);
    +  if (qh ONLYgood && !(goodvisible+goodhorizon) && !qh GOODclosest) {
    +    zinc_(Znotgood);
    +    facet->notfurthest= True;
    +    /* last point of outsideset is no longer furthest.  This is ok
    +       since all points of the outside are likely to be bad */
    +    qh_resetlists(False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +    return True;
    +  }
    +  zzinc_(Zprocessed);
    +  firstnew= qh facet_id;
    +  vertex= qh_makenewfacets(furthest /*visible_list, attaches if !ONLYgood */);
    +  qh_makenewplanes(/* newfacet_list */);
    +  numnew= qh facet_id - firstnew;
    +  newbalance= numnew - (realT) (qh num_facets-qh num_visible)
    +                         * qh hull_dim/qh num_vertices;
    +  wadd_(Wnewbalance, newbalance);
    +  wadd_(Wnewbalance2, newbalance * newbalance);
    +  if (qh ONLYgood
    +  && !qh_findgood(qh newfacet_list, goodhorizon) && !qh GOODclosest) {
    +    FORALLnew_facets
    +      qh_delfacet(newfacet);
    +    qh_delvertex(vertex);
    +    qh_resetlists(True, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +    zinc_(Znotgoodnew);
    +    facet->notfurthest= True;
    +    return True;
    +  }
    +  if (qh ONLYgood)
    +    qh_attachnewfacets(/*visible_list*/);
    +  qh_matchnewfacets();
    +  qh_updatevertices();
    +  if (qh STOPcone && qh furthest_id == qh STOPcone-1) {
    +    facet->notfurthest= True;
    +    return False;  /* visible_list etc. still defined */
    +  }
    +  qh findbestnew= False;
    +  if (qh PREmerge || qh MERGEexact) {
    +    qh_premerge(vertex, qh premerge_centrum, qh premerge_cos);
    +    if (qh_USEfindbestnew)
    +      qh findbestnew= True;
    +    else {
    +      FORALLnew_facets {
    +        if (!newfacet->simplicial) {
    +          qh findbestnew= True;  /* use qh_findbestnew instead of qh_findbest*/
    +          break;
    +        }
    +      }
    +    }
    +  }else if (qh BESToutside)
    +    qh findbestnew= True;
    +  qh_partitionvisible(/*qh.visible_list*/ !qh_ALL, &numpoints);
    +  qh findbestnew= False;
    +  qh findbest_notsharp= False;
    +  zinc_(Zpbalance);
    +  pbalance= numpoints - (realT) qh hull_dim /* assumes all points extreme */
    +                * (qh num_points - qh num_vertices)/qh num_vertices;
    +  wadd_(Wpbalance, pbalance);
    +  wadd_(Wpbalance2, pbalance * pbalance);
    +  qh_deletevisible(/*qh.visible_list*/);
    +  zmax_(Zmaxvertex, qh num_vertices);
    +  qh NEWfacets= False;
    +  if (qh IStracing >= 4) {
    +    if (qh num_facets < 2000)
    +      qh_printlists();
    +    qh_printfacetlist(qh newfacet_list, NULL, True);
    +    qh_checkpolygon(qh facet_list);
    +  }else if (qh CHECKfrequently) {
    +    if (qh num_facets < 50)
    +      qh_checkpolygon(qh facet_list);
    +    else
    +      qh_checkpolygon(qh newfacet_list);
    +  }
    +  if (qh STOPpoint > 0 && qh furthest_id == qh STOPpoint-1)
    +    return False;
    +  qh_resetlists(True, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +  /* qh_triangulate(); to test qh.TRInormals */
    +  trace2((qh ferr, 2056, "qh_addpoint: added p%d new facets %d new balance %2.2g point balance %2.2g\n",
    +    qh_pointid(furthest), numnew, newbalance, pbalance));
    +  return True;
    +} /* addpoint */
    +
    +/*---------------------------------
    +
    +  qh_build_withrestart()
    +    allow restarts due to qh.JOGGLEmax while calling qh_buildhull()
    +       qh_errexit always undoes qh_build_withrestart()
    +    qh.FIRSTpoint/qh.NUMpoints is point array
    +        it may be moved by qh_joggleinput()
    +*/
    +void qh_build_withrestart(void) {
    +  int restart;
    +
    +  qh ALLOWrestart= True;
    +  while (True) {
    +    restart= setjmp(qh restartexit); /* simple statement for CRAY J916 */
    +    if (restart) {       /* only from qh_precision() */
    +      zzinc_(Zretry);
    +      wmax_(Wretrymax, qh JOGGLEmax);
    +      /* QH7078 warns about using 'TCn' with 'QJn' */
    +      qh STOPcone= qh_IDunknown; /* if break from joggle, prevents normal output */
    +    }
    +    if (!qh RERUN && qh JOGGLEmax < REALmax/2) {
    +      if (qh build_cnt > qh_JOGGLEmaxretry) {
    +        qh_fprintf(qh ferr, 6229, "qhull precision error: %d attempts to construct a convex hull\n\
    +        with joggled input.  Increase joggle above 'QJ%2.2g'\n\
    +        or modify qh_JOGGLE... parameters in user.h\n",
    +           qh build_cnt, qh JOGGLEmax);
    +        qh_errexit(qh_ERRqhull, NULL, NULL);
    +      }
    +      if (qh build_cnt && !restart)
    +        break;
    +    }else if (qh build_cnt && qh build_cnt >= qh RERUN)
    +      break;
    +    qh STOPcone= 0;
    +    qh_freebuild(True);  /* first call is a nop */
    +    qh build_cnt++;
    +    if (!qh qhull_optionsiz)
    +      qh qhull_optionsiz= (int)strlen(qh qhull_options);   /* WARN64 */
    +    else {
    +      qh qhull_options [qh qhull_optionsiz]= '\0';
    +      qh qhull_optionlen= qh_OPTIONline;  /* starts a new line */
    +    }
    +    qh_option("_run", &qh build_cnt, NULL);
    +    if (qh build_cnt == qh RERUN) {
    +      qh IStracing= qh TRACElastrun;  /* duplicated from qh_initqhull_globals */
    +      if (qh TRACEpoint != qh_IDunknown || qh TRACEdist < REALmax/2 || qh TRACEmerge) {
    +        qh TRACElevel= (qh IStracing? qh IStracing : 3);
    +        qh IStracing= 0;
    +      }
    +      qhmem.IStracing= qh IStracing;
    +    }
    +    if (qh JOGGLEmax < REALmax/2)
    +      qh_joggleinput();
    +    qh_initbuild();
    +    qh_buildhull();
    +    if (qh JOGGLEmax < REALmax/2 && !qh MERGING)
    +      qh_checkconvex(qh facet_list, qh_ALGORITHMfault);
    +  }
    +  qh ALLOWrestart= False;
    +} /* qh_build_withrestart */
    +
    +/*---------------------------------
    +
    +  qh_buildhull()
    +    construct a convex hull by adding outside points one at a time
    +
    +  returns:
    +
    +  notes:
    +    may be called multiple times
    +    checks facet and vertex lists for incorrect flags
    +    to recover from STOPcone, call qh_deletevisible and qh_resetlists
    +
    +  design:
    +    check visible facet and newfacet flags
    +    check newlist vertex flags and qh.STOPcone/STOPpoint
    +    for each facet with a furthest outside point
    +      add point to facet
    +      exit if qh.STOPcone or qh.STOPpoint requested
    +    if qh.NARROWhull for initial simplex
    +      partition remaining outside points to coplanar sets
    +*/
    +void qh_buildhull(void) {
    +  facetT *facet;
    +  pointT *furthest;
    +  vertexT *vertex;
    +  int id;
    +
    +  trace1((qh ferr, 1037, "qh_buildhull: start build hull\n"));
    +  FORALLfacets {
    +    if (facet->visible || facet->newfacet) {
    +      qh_fprintf(qh ferr, 6165, "qhull internal error (qh_buildhull): visible or new facet f%d in facet list\n",
    +                   facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +  }
    +  FORALLvertices {
    +    if (vertex->newlist) {
    +      qh_fprintf(qh ferr, 6166, "qhull internal error (qh_buildhull): new vertex f%d in vertex list\n",
    +                   vertex->id);
    +      qh_errprint("ERRONEOUS", NULL, NULL, NULL, vertex);
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +    id= qh_pointid(vertex->point);
    +    if ((qh STOPpoint>0 && id == qh STOPpoint-1) ||
    +        (qh STOPpoint<0 && id == -qh STOPpoint-1) ||
    +        (qh STOPcone>0 && id == qh STOPcone-1)) {
    +      trace1((qh ferr, 1038,"qh_buildhull: stop point or cone P%d in initial hull\n", id));
    +      return;
    +    }
    +  }
    +  qh facet_next= qh facet_list;      /* advance facet when processed */
    +  while ((furthest= qh_nextfurthest(&facet))) {
    +    qh num_outside--;  /* if ONLYmax, furthest may not be outside */
    +    if (!qh_addpoint(furthest, facet, qh ONLYmax))
    +      break;
    +  }
    +  if (qh NARROWhull) /* move points from outsideset to coplanarset */
    +    qh_outcoplanar( /* facet_list */ );
    +  if (qh num_outside && !furthest) {
    +    qh_fprintf(qh ferr, 6167, "qhull internal error (qh_buildhull): %d outside points were never processed.\n", qh num_outside);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  trace1((qh ferr, 1039, "qh_buildhull: completed the hull construction\n"));
    +} /* buildhull */
    +
    +
    +/*---------------------------------
    +
    +  qh_buildtracing( furthest, facet )
    +    trace an iteration of qh_buildhull() for furthest point and facet
    +    if !furthest, prints progress message
    +
    +  returns:
    +    tracks progress with qh.lastreport
    +    updates qh.furthest_id (-3 if furthest is NULL)
    +    also resets visit_id, vertext_visit on wrap around
    +
    +  see:
    +    qh_tracemerging()
    +
    +  design:
    +    if !furthest
    +      print progress message
    +      exit
    +    if 'TFn' iteration
    +      print progress message
    +    else if tracing
    +      trace furthest point and facet
    +    reset qh.visit_id and qh.vertex_visit if overflow may occur
    +    set qh.furthest_id for tracing
    +*/
    +void qh_buildtracing(pointT *furthest, facetT *facet) {
    +  realT dist= 0;
    +  float cpu;
    +  int total, furthestid;
    +  time_t timedata;
    +  struct tm *tp;
    +  vertexT *vertex;
    +
    +  qh old_randomdist= qh RANDOMdist;
    +  qh RANDOMdist= False;
    +  if (!furthest) {
    +    time(&timedata);
    +    tp= localtime(&timedata);
    +    cpu= (float)qh_CPUclock - (float)qh hulltime;
    +    cpu /= (float)qh_SECticks;
    +    total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +    qh_fprintf(qh ferr, 8118, "\n\
    +At %02d:%02d:%02d & %2.5g CPU secs, qhull has created %d facets and merged %d.\n\
    + The current hull contains %d facets and %d vertices.  Last point was p%d\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, qh facet_id -1,
    +      total, qh num_facets, qh num_vertices, qh furthest_id);
    +    return;
    +  }
    +  furthestid= qh_pointid(furthest);
    +  if (qh TRACEpoint == furthestid) {
    +    qh IStracing= qh TRACElevel;
    +    qhmem.IStracing= qh TRACElevel;
    +  }else if (qh TRACEpoint != qh_IDunknown && qh TRACEdist < REALmax/2) {
    +    qh IStracing= 0;
    +    qhmem.IStracing= 0;
    +  }
    +  if (qh REPORTfreq && (qh facet_id-1 > qh lastreport+qh REPORTfreq)) {
    +    qh lastreport= qh facet_id-1;
    +    time(&timedata);
    +    tp= localtime(&timedata);
    +    cpu= (float)qh_CPUclock - (float)qh hulltime;
    +    cpu /= (float)qh_SECticks;
    +    total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +    zinc_(Zdistio);
    +    qh_distplane(furthest, facet, &dist);
    +    qh_fprintf(qh ferr, 8119, "\n\
    +At %02d:%02d:%02d & %2.5g CPU secs, qhull has created %d facets and merged %d.\n\
    + The current hull contains %d facets and %d vertices.  There are %d\n\
    + outside points.  Next is point p%d(v%d), %2.2g above f%d.\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, qh facet_id -1,
    +      total, qh num_facets, qh num_vertices, qh num_outside+1,
    +      furthestid, qh vertex_id, dist, getid_(facet));
    +  }else if (qh IStracing >=1) {
    +    cpu= (float)qh_CPUclock - (float)qh hulltime;
    +    cpu /= (float)qh_SECticks;
    +    qh_distplane(furthest, facet, &dist);
    +    qh_fprintf(qh ferr, 8120, "qh_addpoint: add p%d(v%d) to hull of %d facets(%2.2g above f%d) and %d outside at %4.4g CPU secs.  Previous was p%d.\n",
    +      furthestid, qh vertex_id, qh num_facets, dist,
    +      getid_(facet), qh num_outside+1, cpu, qh furthest_id);
    +  }
    +  zmax_(Zvisit2max, (int)qh visit_id/2);
    +  if (qh visit_id > (unsigned) INT_MAX) { /* 31 bits */
    +    zinc_(Zvisit);
    +    qh visit_id= 0;
    +    FORALLfacets
    +      facet->visitid= 0;
    +  }
    +  zmax_(Zvvisit2max, (int)qh vertex_visit/2);
    +  if (qh vertex_visit > (unsigned) INT_MAX) { /* 31 bits */
    +    zinc_(Zvvisit);
    +    qh vertex_visit= 0;
    +    FORALLvertices
    +      vertex->visitid= 0;
    +  }
    +  qh furthest_id= furthestid;
    +  qh RANDOMdist= qh old_randomdist;
    +} /* buildtracing */
    +
    +/*---------------------------------
    +
    +  qh_errexit2( exitcode, facet, otherfacet )
    +    return exitcode to system after an error
    +    report two facets
    +
    +  returns:
    +    assumes exitcode non-zero
    +
    +  see:
    +    normally use qh_errexit() in user.c(reports a facet and a ridge)
    +*/
    +void qh_errexit2(int exitcode, facetT *facet, facetT *otherfacet) {
    +
    +  qh_errprint("ERRONEOUS", facet, otherfacet, NULL, NULL);
    +  qh_errexit(exitcode, NULL, NULL);
    +} /* errexit2 */
    +
    +
    +/*---------------------------------
    +
    +  qh_findhorizon( point, facet, goodvisible, goodhorizon )
    +    given a visible facet, find the point's horizon and visible facets
    +    for all facets, !facet-visible
    +
    +  returns:
    +    returns qh.visible_list/num_visible with all visible facets
    +      marks visible facets with ->visible
    +    updates count of good visible and good horizon facets
    +    updates qh.max_outside, qh.max_vertex, facet->maxoutside
    +
    +  see:
    +    similar to qh_delpoint()
    +
    +  design:
    +    move facet to qh.visible_list at end of qh.facet_list
    +    for all visible facets
    +     for each unvisited neighbor of a visible facet
    +       compute distance of point to neighbor
    +       if point above neighbor
    +         move neighbor to end of qh.visible_list
    +       else if point is coplanar with neighbor
    +         update qh.max_outside, qh.max_vertex, neighbor->maxoutside
    +         mark neighbor coplanar (will create a samecycle later)
    +         update horizon statistics
    +*/
    +void qh_findhorizon(pointT *point, facetT *facet, int *goodvisible, int *goodhorizon) {
    +  facetT *neighbor, **neighborp, *visible;
    +  int numhorizon= 0, coplanar= 0;
    +  realT dist;
    +
    +  trace1((qh ferr, 1040,"qh_findhorizon: find horizon for point p%d facet f%d\n",qh_pointid(point),facet->id));
    +  *goodvisible= *goodhorizon= 0;
    +  zinc_(Ztotvisible);
    +  qh_removefacet(facet);  /* visible_list at end of qh facet_list */
    +  qh_appendfacet(facet);
    +  qh num_visible= 1;
    +  if (facet->good)
    +    (*goodvisible)++;
    +  qh visible_list= facet;
    +  facet->visible= True;
    +  facet->f.replace= NULL;
    +  if (qh IStracing >=4)
    +    qh_errprint("visible", facet, NULL, NULL, NULL);
    +  qh visit_id++;
    +  FORALLvisible_facets {
    +    if (visible->tricoplanar && !qh TRInormals) {
    +      qh_fprintf(qh ferr, 6230, "Qhull internal error (qh_findhorizon): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit(qh_ERRqhull, visible, NULL);
    +    }
    +    visible->visitid= qh visit_id;
    +    FOREACHneighbor_(visible) {
    +      if (neighbor->visitid == qh visit_id)
    +        continue;
    +      neighbor->visitid= qh visit_id;
    +      zzinc_(Znumvisibility);
    +      qh_distplane(point, neighbor, &dist);
    +      if (dist > qh MINvisible) {
    +        zinc_(Ztotvisible);
    +        qh_removefacet(neighbor);  /* append to end of qh visible_list */
    +        qh_appendfacet(neighbor);
    +        neighbor->visible= True;
    +        neighbor->f.replace= NULL;
    +        qh num_visible++;
    +        if (neighbor->good)
    +          (*goodvisible)++;
    +        if (qh IStracing >=4)
    +          qh_errprint("visible", neighbor, NULL, NULL, NULL);
    +      }else {
    +        if (dist > - qh MAXcoplanar) {
    +          neighbor->coplanar= True;
    +          zzinc_(Zcoplanarhorizon);
    +          qh_precision("coplanar horizon");
    +          coplanar++;
    +          if (qh MERGING) {
    +            if (dist > 0) {
    +              maximize_(qh max_outside, dist);
    +              maximize_(qh max_vertex, dist);
    +#if qh_MAXoutside
    +              maximize_(neighbor->maxoutside, dist);
    +#endif
    +            }else
    +              minimize_(qh min_vertex, dist);  /* due to merge later */
    +          }
    +          trace2((qh ferr, 2057, "qh_findhorizon: point p%d is coplanar to horizon f%d, dist=%2.7g < qh MINvisible(%2.7g)\n",
    +              qh_pointid(point), neighbor->id, dist, qh MINvisible));
    +        }else
    +          neighbor->coplanar= False;
    +        zinc_(Ztothorizon);
    +        numhorizon++;
    +        if (neighbor->good)
    +          (*goodhorizon)++;
    +        if (qh IStracing >=4)
    +          qh_errprint("horizon", neighbor, NULL, NULL, NULL);
    +      }
    +    }
    +  }
    +  if (!numhorizon) {
    +    qh_precision("empty horizon");
    +    qh_fprintf(qh ferr, 6168, "qhull precision error (qh_findhorizon): empty horizon\n\
    +QhullPoint p%d was above all facets.\n", qh_pointid(point));
    +    qh_printfacetlist(qh facet_list, NULL, True);
    +    qh_errexit(qh_ERRprec, NULL, NULL);
    +  }
    +  trace1((qh ferr, 1041, "qh_findhorizon: %d horizon facets(good %d), %d visible(good %d), %d coplanar\n",
    +       numhorizon, *goodhorizon, qh num_visible, *goodvisible, coplanar));
    +  if (qh IStracing >= 4 && qh num_facets < 50)
    +    qh_printlists();
    +} /* findhorizon */
    +
    +/*---------------------------------
    +
    +  qh_nextfurthest( visible )
    +    returns next furthest point and visible facet for qh_addpoint()
    +    starts search at qh.facet_next
    +
    +  returns:
    +    removes furthest point from outside set
    +    NULL if none available
    +    advances qh.facet_next over facets with empty outside sets
    +
    +  design:
    +    for each facet from qh.facet_next
    +      if empty outside set
    +        advance qh.facet_next
    +      else if qh.NARROWhull
    +        determine furthest outside point
    +        if furthest point is not outside
    +          advance qh.facet_next(point will be coplanar)
    +    remove furthest point from outside set
    +*/
    +pointT *qh_nextfurthest(facetT **visible) {
    +  facetT *facet;
    +  int size, idx;
    +  realT randr, dist;
    +  pointT *furthest;
    +
    +  while ((facet= qh facet_next) != qh facet_tail) {
    +    if (!facet->outsideset) {
    +      qh facet_next= facet->next;
    +      continue;
    +    }
    +    SETreturnsize_(facet->outsideset, size);
    +    if (!size) {
    +      qh_setfree(&facet->outsideset);
    +      qh facet_next= facet->next;
    +      continue;
    +    }
    +    if (qh NARROWhull) {
    +      if (facet->notfurthest)
    +        qh_furthestout(facet);
    +      furthest= (pointT*)qh_setlast(facet->outsideset);
    +#if qh_COMPUTEfurthest
    +      qh_distplane(furthest, facet, &dist);
    +      zinc_(Zcomputefurthest);
    +#else
    +      dist= facet->furthestdist;
    +#endif
    +      if (dist < qh MINoutside) { /* remainder of outside set is coplanar for qh_outcoplanar */
    +        qh facet_next= facet->next;
    +        continue;
    +      }
    +    }
    +    if (!qh RANDOMoutside && !qh VIRTUALmemory) {
    +      if (qh PICKfurthest) {
    +        qh_furthestnext(/* qh.facet_list */);
    +        facet= qh facet_next;
    +      }
    +      *visible= facet;
    +      return((pointT*)qh_setdellast(facet->outsideset));
    +    }
    +    if (qh RANDOMoutside) {
    +      int outcoplanar = 0;
    +      if (qh NARROWhull) {
    +        FORALLfacets {
    +          if (facet == qh facet_next)
    +            break;
    +          if (facet->outsideset)
    +            outcoplanar += qh_setsize( facet->outsideset);
    +        }
    +      }
    +      randr= qh_RANDOMint;
    +      randr= randr/(qh_RANDOMmax+1);
    +      idx= (int)floor((qh num_outside - outcoplanar) * randr);
    +      FORALLfacet_(qh facet_next) {
    +        if (facet->outsideset) {
    +          SETreturnsize_(facet->outsideset, size);
    +          if (!size)
    +            qh_setfree(&facet->outsideset);
    +          else if (size > idx) {
    +            *visible= facet;
    +            return((pointT*)qh_setdelnth(facet->outsideset, idx));
    +          }else
    +            idx -= size;
    +        }
    +      }
    +      qh_fprintf(qh ferr, 6169, "qhull internal error (qh_nextfurthest): num_outside %d is too low\nby at least %d, or a random real %g >= 1.0\n",
    +              qh num_outside, idx+1, randr);
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }else { /* VIRTUALmemory */
    +      facet= qh facet_tail->previous;
    +      if (!(furthest= (pointT*)qh_setdellast(facet->outsideset))) {
    +        if (facet->outsideset)
    +          qh_setfree(&facet->outsideset);
    +        qh_removefacet(facet);
    +        qh_prependfacet(facet, &qh facet_list);
    +        continue;
    +      }
    +      *visible= facet;
    +      return furthest;
    +    }
    +  }
    +  return NULL;
    +} /* nextfurthest */
    +
    +/*---------------------------------
    +
    +  qh_partitionall( vertices, points, numpoints )
    +    partitions all points in points/numpoints to the outsidesets of facets
    +    vertices= vertices in qh.facet_list(!partitioned)
    +
    +  returns:
    +    builds facet->outsideset
    +    does not partition qh.GOODpoint
    +    if qh.ONLYgood && !qh.MERGING,
    +      does not partition qh.GOODvertex
    +
    +  notes:
    +    faster if qh.facet_list sorted by anticipated size of outside set
    +
    +  design:
    +    initialize pointset with all points
    +    remove vertices from pointset
    +    remove qh.GOODpointp from pointset (unless it's qh.STOPcone or qh.STOPpoint)
    +    for all facets
    +      for all remaining points in pointset
    +        compute distance from point to facet
    +        if point is outside facet
    +          remove point from pointset (by not reappending)
    +          update bestpoint
    +          append point or old bestpoint to facet's outside set
    +      append bestpoint to facet's outside set (furthest)
    +    for all points remaining in pointset
    +      partition point into facets' outside sets and coplanar sets
    +*/
    +void qh_partitionall(setT *vertices, pointT *points, int numpoints){
    +  setT *pointset;
    +  vertexT *vertex, **vertexp;
    +  pointT *point, **pointp, *bestpoint;
    +  int size, point_i, point_n, point_end, remaining, i, id;
    +  facetT *facet;
    +  realT bestdist= -REALmax, dist, distoutside;
    +
    +  trace1((qh ferr, 1042, "qh_partitionall: partition all points into outside sets\n"));
    +  pointset= qh_settemp(numpoints);
    +  qh num_outside= 0;
    +  pointp= SETaddr_(pointset, pointT);
    +  for (i=numpoints, point= points; i--; point += qh hull_dim)
    +    *(pointp++)= point;
    +  qh_settruncate(pointset, numpoints);
    +  FOREACHvertex_(vertices) {
    +    if ((id= qh_pointid(vertex->point)) >= 0)
    +      SETelem_(pointset, id)= NULL;
    +  }
    +  id= qh_pointid(qh GOODpointp);
    +  if (id >=0 && qh STOPcone-1 != id && -qh STOPpoint-1 != id)
    +    SETelem_(pointset, id)= NULL;
    +  if (qh GOODvertexp && qh ONLYgood && !qh MERGING) { /* matches qhull()*/
    +    if ((id= qh_pointid(qh GOODvertexp)) >= 0)
    +      SETelem_(pointset, id)= NULL;
    +  }
    +  if (!qh BESToutside) {  /* matches conditional for qh_partitionpoint below */
    +    distoutside= qh_DISToutside; /* multiple of qh.MINoutside & qh.max_outside, see user.h */
    +    zval_(Ztotpartition)= qh num_points - qh hull_dim - 1; /*misses GOOD... */
    +    remaining= qh num_facets;
    +    point_end= numpoints;
    +    FORALLfacets {
    +      size= point_end/(remaining--) + 100;
    +      facet->outsideset= qh_setnew(size);
    +      bestpoint= NULL;
    +      point_end= 0;
    +      FOREACHpoint_i_(pointset) {
    +        if (point) {
    +          zzinc_(Zpartitionall);
    +          qh_distplane(point, facet, &dist);
    +          if (dist < distoutside)
    +            SETelem_(pointset, point_end++)= point;
    +          else {
    +            qh num_outside++;
    +            if (!bestpoint) {
    +              bestpoint= point;
    +              bestdist= dist;
    +            }else if (dist > bestdist) {
    +              qh_setappend(&facet->outsideset, bestpoint);
    +              bestpoint= point;
    +              bestdist= dist;
    +            }else
    +              qh_setappend(&facet->outsideset, point);
    +          }
    +        }
    +      }
    +      if (bestpoint) {
    +        qh_setappend(&facet->outsideset, bestpoint);
    +#if !qh_COMPUTEfurthest
    +        facet->furthestdist= bestdist;
    +#endif
    +      }else
    +        qh_setfree(&facet->outsideset);
    +      qh_settruncate(pointset, point_end);
    +    }
    +  }
    +  /* if !qh BESToutside, pointset contains points not assigned to outsideset */
    +  if (qh BESToutside || qh MERGING || qh KEEPcoplanar || qh KEEPinside) {
    +    qh findbestnew= True;
    +    FOREACHpoint_i_(pointset) {
    +      if (point)
    +        qh_partitionpoint(point, qh facet_list);
    +    }
    +    qh findbestnew= False;
    +  }
    +  zzadd_(Zpartitionall, zzval_(Zpartition));
    +  zzval_(Zpartition)= 0;
    +  qh_settempfree(&pointset);
    +  if (qh IStracing >= 4)
    +    qh_printfacetlist(qh facet_list, NULL, True);
    +} /* partitionall */
    +
    +
    +/*---------------------------------
    +
    +  qh_partitioncoplanar( point, facet, dist )
    +    partition coplanar point to a facet
    +    dist is distance from point to facet
    +    if dist NULL,
    +      searches for bestfacet and does nothing if inside
    +    if qh.findbestnew set,
    +      searches new facets instead of using qh_findbest()
    +
    +  returns:
    +    qh.max_ouside updated
    +    if qh.KEEPcoplanar or qh.KEEPinside
    +      point assigned to best coplanarset
    +
    +  notes:
    +    facet->maxoutside is updated at end by qh_check_maxout
    +
    +  design:
    +    if dist undefined
    +      find best facet for point
    +      if point sufficiently below facet (depends on qh.NEARinside and qh.KEEPinside)
    +        exit
    +    if keeping coplanar/nearinside/inside points
    +      if point is above furthest coplanar point
    +        append point to coplanar set (it is the new furthest)
    +        update qh.max_outside
    +      else
    +        append point one before end of coplanar set
    +    else if point is clearly outside of qh.max_outside and bestfacet->coplanarset
    +    and bestfacet is more than perpendicular to facet
    +      repartition the point using qh_findbest() -- it may be put on an outsideset
    +    else
    +      update qh.max_outside
    +*/
    +void qh_partitioncoplanar(pointT *point, facetT *facet, realT *dist) {
    +  facetT *bestfacet;
    +  pointT *oldfurthest;
    +  realT bestdist, dist2= 0, angle;
    +  int numpart= 0, oldfindbest;
    +  boolT isoutside;
    +
    +  qh WAScoplanar= True;
    +  if (!dist) {
    +    if (qh findbestnew)
    +      bestfacet= qh_findbestnew(point, facet, &bestdist, qh_ALL, &isoutside, &numpart);
    +    else
    +      bestfacet= qh_findbest(point, facet, qh_ALL, !qh_ISnewfacets, qh DELAUNAY,
    +                          &bestdist, &isoutside, &numpart);
    +    zinc_(Ztotpartcoplanar);
    +    zzadd_(Zpartcoplanar, numpart);
    +    if (!qh DELAUNAY && !qh KEEPinside) { /*  for 'd', bestdist skips upperDelaunay facets */
    +      if (qh KEEPnearinside) {
    +        if (bestdist < -qh NEARinside) {
    +          zinc_(Zcoplanarinside);
    +          trace4((qh ferr, 4062, "qh_partitioncoplanar: point p%d is more than near-inside facet f%d dist %2.2g findbestnew %d\n",
    +                  qh_pointid(point), bestfacet->id, bestdist, qh findbestnew));
    +          return;
    +        }
    +      }else if (bestdist < -qh MAXcoplanar) {
    +          trace4((qh ferr, 4063, "qh_partitioncoplanar: point p%d is inside facet f%d dist %2.2g findbestnew %d\n",
    +                  qh_pointid(point), bestfacet->id, bestdist, qh findbestnew));
    +        zinc_(Zcoplanarinside);
    +        return;
    +      }
    +    }
    +  }else {
    +    bestfacet= facet;
    +    bestdist= *dist;
    +  }
    +  if (bestdist > qh max_outside) {
    +    if (!dist && facet != bestfacet) {
    +      zinc_(Zpartangle);
    +      angle= qh_getangle(facet->normal, bestfacet->normal);
    +      if (angle < 0) {
    +        /* typically due to deleted vertex and coplanar facets, e.g.,
    +             RBOX 1000 s Z1 G1e-13 t1001185205 | QHULL Tv */
    +        zinc_(Zpartflip);
    +        trace2((qh ferr, 2058, "qh_partitioncoplanar: repartition point p%d from f%d.  It is above flipped facet f%d dist %2.2g\n",
    +                qh_pointid(point), facet->id, bestfacet->id, bestdist));
    +        oldfindbest= qh findbestnew;
    +        qh findbestnew= False;
    +        qh_partitionpoint(point, bestfacet);
    +        qh findbestnew= oldfindbest;
    +        return;
    +      }
    +    }
    +    qh max_outside= bestdist;
    +    if (bestdist > qh TRACEdist) {
    +      qh_fprintf(qh ferr, 8122, "qh_partitioncoplanar: ====== p%d from f%d increases max_outside to %2.2g of f%d last p%d\n",
    +                     qh_pointid(point), facet->id, bestdist, bestfacet->id, qh furthest_id);
    +      qh_errprint("DISTANT", facet, bestfacet, NULL, NULL);
    +    }
    +  }
    +  if (qh KEEPcoplanar + qh KEEPinside + qh KEEPnearinside) {
    +    oldfurthest= (pointT*)qh_setlast(bestfacet->coplanarset);
    +    if (oldfurthest) {
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(oldfurthest, bestfacet, &dist2);
    +    }
    +    if (!oldfurthest || dist2 < bestdist)
    +      qh_setappend(&bestfacet->coplanarset, point);
    +    else
    +      qh_setappend2ndlast(&bestfacet->coplanarset, point);
    +  }
    +  trace4((qh ferr, 4064, "qh_partitioncoplanar: point p%d is coplanar with facet f%d(or inside) dist %2.2g\n",
    +          qh_pointid(point), bestfacet->id, bestdist));
    +} /* partitioncoplanar */
    +
    +/*---------------------------------
    +
    +  qh_partitionpoint( point, facet )
    +    assigns point to an outside set, coplanar set, or inside set (i.e., dropt)
    +    if qh.findbestnew
    +      uses qh_findbestnew() to search all new facets
    +    else
    +      uses qh_findbest()
    +
    +  notes:
    +    after qh_distplane(), this and qh_findbest() are most expensive in 3-d
    +
    +  design:
    +    find best facet for point
    +      (either exhaustive search of new facets or directed search from facet)
    +    if qh.NARROWhull
    +      retain coplanar and nearinside points as outside points
    +    if point is outside bestfacet
    +      if point above furthest point for bestfacet
    +        append point to outside set (it becomes the new furthest)
    +        if outside set was empty
    +          move bestfacet to end of qh.facet_list (i.e., after qh.facet_next)
    +        update bestfacet->furthestdist
    +      else
    +        append point one before end of outside set
    +    else if point is coplanar to bestfacet
    +      if keeping coplanar points or need to update qh.max_outside
    +        partition coplanar point into bestfacet
    +    else if near-inside point
    +      partition as coplanar point into bestfacet
    +    else is an inside point
    +      if keeping inside points
    +        partition as coplanar point into bestfacet
    +*/
    +void qh_partitionpoint(pointT *point, facetT *facet) {
    +  realT bestdist;
    +  boolT isoutside;
    +  facetT *bestfacet;
    +  int numpart;
    +#if qh_COMPUTEfurthest
    +  realT dist;
    +#endif
    +
    +  if (qh findbestnew)
    +    bestfacet= qh_findbestnew(point, facet, &bestdist, qh BESToutside, &isoutside, &numpart);
    +  else
    +    bestfacet= qh_findbest(point, facet, qh BESToutside, qh_ISnewfacets, !qh_NOupper,
    +                          &bestdist, &isoutside, &numpart);
    +  zinc_(Ztotpartition);
    +  zzadd_(Zpartition, numpart);
    +  if (qh NARROWhull) {
    +    if (qh DELAUNAY && !isoutside && bestdist >= -qh MAXcoplanar)
    +      qh_precision("nearly incident point(narrow hull)");
    +    if (qh KEEPnearinside) {
    +      if (bestdist >= -qh NEARinside)
    +        isoutside= True;
    +    }else if (bestdist >= -qh MAXcoplanar)
    +      isoutside= True;
    +  }
    +
    +  if (isoutside) {
    +    if (!bestfacet->outsideset
    +    || !qh_setlast(bestfacet->outsideset)) {
    +      qh_setappend(&(bestfacet->outsideset), point);
    +      if (!bestfacet->newfacet) {
    +        qh_removefacet(bestfacet);  /* make sure it's after qh facet_next */
    +        qh_appendfacet(bestfacet);
    +      }
    +#if !qh_COMPUTEfurthest
    +      bestfacet->furthestdist= bestdist;
    +#endif
    +    }else {
    +#if qh_COMPUTEfurthest
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(oldfurthest, bestfacet, &dist);
    +      if (dist < bestdist)
    +        qh_setappend(&(bestfacet->outsideset), point);
    +      else
    +        qh_setappend2ndlast(&(bestfacet->outsideset), point);
    +#else
    +      if (bestfacet->furthestdist < bestdist) {
    +        qh_setappend(&(bestfacet->outsideset), point);
    +        bestfacet->furthestdist= bestdist;
    +      }else
    +        qh_setappend2ndlast(&(bestfacet->outsideset), point);
    +#endif
    +    }
    +    qh num_outside++;
    +    trace4((qh ferr, 4065, "qh_partitionpoint: point p%d is outside facet f%d new? %d (or narrowhull)\n",
    +          qh_pointid(point), bestfacet->id, bestfacet->newfacet));
    +  }else if (qh DELAUNAY || bestdist >= -qh MAXcoplanar) { /* for 'd', bestdist skips upperDelaunay facets */
    +    zzinc_(Zcoplanarpart);
    +    if (qh DELAUNAY)
    +      qh_precision("nearly incident point");
    +    if ((qh KEEPcoplanar + qh KEEPnearinside) || bestdist > qh max_outside)
    +      qh_partitioncoplanar(point, bestfacet, &bestdist);
    +    else {
    +      trace4((qh ferr, 4066, "qh_partitionpoint: point p%d is coplanar to facet f%d (dropped)\n",
    +          qh_pointid(point), bestfacet->id));
    +    }
    +  }else if (qh KEEPnearinside && bestdist > -qh NEARinside) {
    +    zinc_(Zpartnear);
    +    qh_partitioncoplanar(point, bestfacet, &bestdist);
    +  }else {
    +    zinc_(Zpartinside);
    +    trace4((qh ferr, 4067, "qh_partitionpoint: point p%d is inside all facets, closest to f%d dist %2.2g\n",
    +          qh_pointid(point), bestfacet->id, bestdist));
    +    if (qh KEEPinside)
    +      qh_partitioncoplanar(point, bestfacet, &bestdist);
    +  }
    +} /* partitionpoint */
    +
    +/*---------------------------------
    +
    +  qh_partitionvisible( allpoints, numoutside )
    +    partitions points in visible facets to qh.newfacet_list
    +    qh.visible_list= visible facets
    +    for visible facets
    +      1st neighbor (if any) points to a horizon facet or a new facet
    +    if allpoints(!used),
    +      repartitions coplanar points
    +
    +  returns:
    +    updates outside sets and coplanar sets of qh.newfacet_list
    +    updates qh.num_outside (count of outside points)
    +
    +  notes:
    +    qh.findbest_notsharp should be clear (extra work if set)
    +
    +  design:
    +    for all visible facets with outside set or coplanar set
    +      select a newfacet for visible facet
    +      if outside set
    +        partition outside set into new facets
    +      if coplanar set and keeping coplanar/near-inside/inside points
    +        if allpoints
    +          partition coplanar set into new facets, may be assigned outside
    +        else
    +          partition coplanar set into coplanar sets of new facets
    +    for each deleted vertex
    +      if allpoints
    +        partition vertex into new facets, may be assigned outside
    +      else
    +        partition vertex into coplanar sets of new facets
    +*/
    +void qh_partitionvisible(/*qh.visible_list*/ boolT allpoints, int *numoutside) {
    +  facetT *visible, *newfacet;
    +  pointT *point, **pointp;
    +  int coplanar=0, size;
    +  unsigned count;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh ONLYmax)
    +    maximize_(qh MINoutside, qh max_vertex);
    +  *numoutside= 0;
    +  FORALLvisible_facets {
    +    if (!visible->outsideset && !visible->coplanarset)
    +      continue;
    +    newfacet= visible->f.replace;
    +    count= 0;
    +    while (newfacet && newfacet->visible) {
    +      newfacet= newfacet->f.replace;
    +      if (count++ > qh facet_id)
    +        qh_infiniteloop(visible);
    +    }
    +    if (!newfacet)
    +      newfacet= qh newfacet_list;
    +    if (newfacet == qh facet_tail) {
    +      qh_fprintf(qh ferr, 6170, "qhull precision error (qh_partitionvisible): all new facets deleted as\n        degenerate facets. Can not continue.\n");
    +      qh_errexit(qh_ERRprec, NULL, NULL);
    +    }
    +    if (visible->outsideset) {
    +      size= qh_setsize(visible->outsideset);
    +      *numoutside += size;
    +      qh num_outside -= size;
    +      FOREACHpoint_(visible->outsideset)
    +        qh_partitionpoint(point, newfacet);
    +    }
    +    if (visible->coplanarset && (qh KEEPcoplanar + qh KEEPinside + qh KEEPnearinside)) {
    +      size= qh_setsize(visible->coplanarset);
    +      coplanar += size;
    +      FOREACHpoint_(visible->coplanarset) {
    +        if (allpoints) /* not used */
    +          qh_partitionpoint(point, newfacet);
    +        else
    +          qh_partitioncoplanar(point, newfacet, NULL);
    +      }
    +    }
    +  }
    +  FOREACHvertex_(qh del_vertices) {
    +    if (vertex->point) {
    +      if (allpoints) /* not used */
    +        qh_partitionpoint(vertex->point, qh newfacet_list);
    +      else
    +        qh_partitioncoplanar(vertex->point, qh newfacet_list, NULL);
    +    }
    +  }
    +  trace1((qh ferr, 1043,"qh_partitionvisible: partitioned %d points from outsidesets and %d points from coplanarsets\n", *numoutside, coplanar));
    +} /* partitionvisible */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_precision( reason )
    +    restart on precision errors if not merging and if 'QJn'
    +*/
    +void qh_precision(const char *reason) {
    +
    +  if (qh ALLOWrestart && !qh PREmerge && !qh MERGEexact) {
    +    if (qh JOGGLEmax < REALmax/2) {
    +      trace0((qh ferr, 26, "qh_precision: qhull restart because of %s\n", reason));
    +      /* May be called repeatedly if qh->ALLOWrestart */
    +      longjmp(qh restartexit, qh_ERRprec);
    +    }
    +  }
    +} /* qh_precision */
    +
    +/*---------------------------------
    +
    +  qh_printsummary( fp )
    +    prints summary to fp
    +
    +  notes:
    +    not in io.c so that user_eg.c can prevent io.c from loading
    +    qh_printsummary and qh_countfacets must match counts
    +
    +  design:
    +    determine number of points, vertices, and coplanar points
    +    print summary
    +*/
    +void qh_printsummary(FILE *fp) {
    +  realT ratio, outerplane, innerplane;
    +  float cpu;
    +  int size, id, nummerged, numvertices, numcoplanars= 0, nonsimplicial=0;
    +  int goodused;
    +  facetT *facet;
    +  const char *s;
    +  int numdel= zzval_(Zdelvertextot);
    +  int numtricoplanars= 0;
    +
    +  size= qh num_points + qh_setsize(qh other_points);
    +  numvertices= qh num_vertices - qh_setsize(qh del_vertices);
    +  id= qh_pointid(qh GOODpointp);
    +  FORALLfacets {
    +    if (facet->coplanarset)
    +      numcoplanars += qh_setsize( facet->coplanarset);
    +    if (facet->good) {
    +      if (facet->simplicial) {
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else if (qh_setsize(facet->vertices) != qh hull_dim)
    +        nonsimplicial++;
    +    }
    +  }
    +  if (id >=0 && qh STOPcone-1 != id && -qh STOPpoint-1 != id)
    +    size--;
    +  if (qh STOPcone || qh STOPpoint)
    +      qh_fprintf(fp, 9288, "\nAt a premature exit due to 'TVn', 'TCn', 'TRn', or precision error with 'QJn'.");
    +  if (qh UPPERdelaunay)
    +    goodused= qh GOODvertex + qh GOODpoint + qh SPLITthresholds;
    +  else if (qh DELAUNAY)
    +    goodused= qh GOODvertex + qh GOODpoint + qh GOODthreshold;
    +  else
    +    goodused= qh num_good;
    +  nummerged= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +  if (qh VORONOI) {
    +    if (qh UPPERdelaunay)
    +      qh_fprintf(fp, 9289, "\n\
    +Furthest-site Voronoi vertices by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    else
    +      qh_fprintf(fp, 9290, "\n\
    +Voronoi diagram by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    qh_fprintf(fp, 9291, "  Number of Voronoi regions%s: %d\n",
    +              qh ATinfinity ? " and at-infinity" : "", numvertices);
    +    if (numdel)
    +      qh_fprintf(fp, 9292, "  Total number of deleted points due to merging: %d\n", numdel);
    +    if (numcoplanars - numdel > 0)
    +      qh_fprintf(fp, 9293, "  Number of nearly incident points: %d\n", numcoplanars - numdel);
    +    else if (size - numvertices - numdel > 0)
    +      qh_fprintf(fp, 9294, "  Total number of nearly incident points: %d\n", size - numvertices - numdel);
    +    qh_fprintf(fp, 9295, "  Number of%s Voronoi vertices: %d\n",
    +              goodused ? " 'good'" : "", qh num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(fp, 9296, "  Number of%s non-simplicial Voronoi vertices: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else if (qh DELAUNAY) {
    +    if (qh UPPERdelaunay)
    +      qh_fprintf(fp, 9297, "\n\
    +Furthest-site Delaunay triangulation by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    else
    +      qh_fprintf(fp, 9298, "\n\
    +Delaunay triangulation by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    qh_fprintf(fp, 9299, "  Number of input sites%s: %d\n",
    +              qh ATinfinity ? " and at-infinity" : "", numvertices);
    +    if (numdel)
    +      qh_fprintf(fp, 9300, "  Total number of deleted points due to merging: %d\n", numdel);
    +    if (numcoplanars - numdel > 0)
    +      qh_fprintf(fp, 9301, "  Number of nearly incident points: %d\n", numcoplanars - numdel);
    +    else if (size - numvertices - numdel > 0)
    +      qh_fprintf(fp, 9302, "  Total number of nearly incident points: %d\n", size - numvertices - numdel);
    +    qh_fprintf(fp, 9303, "  Number of%s Delaunay regions: %d\n",
    +              goodused ? " 'good'" : "", qh num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(fp, 9304, "  Number of%s non-simplicial Delaunay regions: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else if (qh HALFspace) {
    +    qh_fprintf(fp, 9305, "\n\
    +Halfspace intersection by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    qh_fprintf(fp, 9306, "  Number of halfspaces: %d\n", size);
    +    qh_fprintf(fp, 9307, "  Number of non-redundant halfspaces: %d\n", numvertices);
    +    if (numcoplanars) {
    +      if (qh KEEPinside && qh KEEPcoplanar)
    +        s= "similar and redundant";
    +      else if (qh KEEPinside)
    +        s= "redundant";
    +      else
    +        s= "similar";
    +      qh_fprintf(fp, 9308, "  Number of %s halfspaces: %d\n", s, numcoplanars);
    +    }
    +    qh_fprintf(fp, 9309, "  Number of intersection points: %d\n", qh num_facets - qh num_visible);
    +    if (goodused)
    +      qh_fprintf(fp, 9310, "  Number of 'good' intersection points: %d\n", qh num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(fp, 9311, "  Number of%s non-simplicial intersection points: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else {
    +    qh_fprintf(fp, 9312, "\n\
    +Convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    qh_fprintf(fp, 9313, "  Number of vertices: %d\n", numvertices);
    +    if (numcoplanars) {
    +      if (qh KEEPinside && qh KEEPcoplanar)
    +        s= "coplanar and interior";
    +      else if (qh KEEPinside)
    +        s= "interior";
    +      else
    +        s= "coplanar";
    +      qh_fprintf(fp, 9314, "  Number of %s points: %d\n", s, numcoplanars);
    +    }
    +    qh_fprintf(fp, 9315, "  Number of facets: %d\n", qh num_facets - qh num_visible);
    +    if (goodused)
    +      qh_fprintf(fp, 9316, "  Number of 'good' facets: %d\n", qh num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(fp, 9317, "  Number of%s non-simplicial facets: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }
    +  if (numtricoplanars)
    +      qh_fprintf(fp, 9318, "  Number of triangulated facets: %d\n", numtricoplanars);
    +  qh_fprintf(fp, 9319, "\nStatistics for: %s | %s",
    +                      qh rbox_command, qh qhull_command);
    +  if (qh ROTATErandom != INT_MIN)
    +    qh_fprintf(fp, 9320, " QR%d\n\n", qh ROTATErandom);
    +  else
    +    qh_fprintf(fp, 9321, "\n\n");
    +  qh_fprintf(fp, 9322, "  Number of points processed: %d\n", zzval_(Zprocessed));
    +  qh_fprintf(fp, 9323, "  Number of hyperplanes created: %d\n", zzval_(Zsetplane));
    +  if (qh DELAUNAY)
    +    qh_fprintf(fp, 9324, "  Number of facets in hull: %d\n", qh num_facets - qh num_visible);
    +  qh_fprintf(fp, 9325, "  Number of distance tests for qhull: %d\n", zzval_(Zpartition)+
    +      zzval_(Zpartitionall)+zzval_(Znumvisibility)+zzval_(Zpartcoplanar));
    +#if 0  /* NOTE: must print before printstatistics() */
    +  {realT stddev, ave;
    +  qh_fprintf(fp, 9326, "  average new facet balance: %2.2g\n",
    +          wval_(Wnewbalance)/zval_(Zprocessed));
    +  stddev= qh_stddev(zval_(Zprocessed), wval_(Wnewbalance),
    +                                 wval_(Wnewbalance2), &ave);
    +  qh_fprintf(fp, 9327, "  new facet standard deviation: %2.2g\n", stddev);
    +  qh_fprintf(fp, 9328, "  average partition balance: %2.2g\n",
    +          wval_(Wpbalance)/zval_(Zpbalance));
    +  stddev= qh_stddev(zval_(Zpbalance), wval_(Wpbalance),
    +                                 wval_(Wpbalance2), &ave);
    +  qh_fprintf(fp, 9329, "  partition standard deviation: %2.2g\n", stddev);
    +  }
    +#endif
    +  if (nummerged) {
    +    qh_fprintf(fp, 9330,"  Number of distance tests for merging: %d\n",zzval_(Zbestdist)+
    +          zzval_(Zcentrumtests)+zzval_(Zdistconvex)+zzval_(Zdistcheck)+
    +          zzval_(Zdistzero));
    +    qh_fprintf(fp, 9331,"  Number of distance tests for checking: %d\n",zzval_(Zcheckpart));
    +    qh_fprintf(fp, 9332,"  Number of merged facets: %d\n", nummerged);
    +  }
    +  if (!qh RANDOMoutside && qh QHULLfinished) {
    +    cpu= (float)qh hulltime;
    +    cpu /= (float)qh_SECticks;
    +    wval_(Wcpu)= cpu;
    +    qh_fprintf(fp, 9333, "  CPU seconds to compute hull (after input): %2.4g\n", cpu);
    +  }
    +  if (qh RERUN) {
    +    if (!qh PREmerge && !qh MERGEexact)
    +      qh_fprintf(fp, 9334, "  Percentage of runs with precision errors: %4.1f\n",
    +           zzval_(Zretry)*100.0/qh build_cnt);  /* careful of order */
    +  }else if (qh JOGGLEmax < REALmax/2) {
    +    if (zzval_(Zretry))
    +      qh_fprintf(fp, 9335, "  After %d retries, input joggled by: %2.2g\n",
    +         zzval_(Zretry), qh JOGGLEmax);
    +    else
    +      qh_fprintf(fp, 9336, "  Input joggled by: %2.2g\n", qh JOGGLEmax);
    +  }
    +  if (qh totarea != 0.0)
    +    qh_fprintf(fp, 9337, "  %s facet area:   %2.8g\n",
    +            zzval_(Ztotmerge) ? "Approximate" : "Total", qh totarea);
    +  if (qh totvol != 0.0)
    +    qh_fprintf(fp, 9338, "  %s volume:       %2.8g\n",
    +            zzval_(Ztotmerge) ? "Approximate" : "Total", qh totvol);
    +  if (qh MERGING) {
    +    qh_outerinner(NULL, &outerplane, &innerplane);
    +    if (outerplane > 2 * qh DISTround) {
    +      qh_fprintf(fp, 9339, "  Maximum distance of %spoint above facet: %2.2g",
    +            (qh QHULLfinished ? "" : "merged "), outerplane);
    +      ratio= outerplane/(qh ONEmerge + qh DISTround);
    +      /* don't report ratio if MINoutside is large */
    +      if (ratio > 0.05 && 2* qh ONEmerge > qh MINoutside && qh JOGGLEmax > REALmax/2)
    +        qh_fprintf(fp, 9340, " (%.1fx)\n", ratio);
    +      else
    +        qh_fprintf(fp, 9341, "\n");
    +    }
    +    if (innerplane < -2 * qh DISTround) {
    +      qh_fprintf(fp, 9342, "  Maximum distance of %svertex below facet: %2.2g",
    +            (qh QHULLfinished ? "" : "merged "), innerplane);
    +      ratio= -innerplane/(qh ONEmerge+qh DISTround);
    +      if (ratio > 0.05 && qh JOGGLEmax > REALmax/2)
    +        qh_fprintf(fp, 9343, " (%.1fx)\n", ratio);
    +      else
    +        qh_fprintf(fp, 9344, "\n");
    +    }
    +  }
    +  qh_fprintf(fp, 9345, "\n");
    +} /* printsummary */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/libqhull.h b/xs/src/qhull/src/libqhull/libqhull.h
    new file mode 100644
    index 0000000000..677085808d
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/libqhull.h
    @@ -0,0 +1,1140 @@
    +/*
      ---------------------------------
    +
    +   libqhull.h
    +   user-level header file for using qhull.a library
    +
    +   see qh-qhull.htm, qhull_a.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/libqhull.h#7 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +
    +   NOTE: access to qh_qh is via the 'qh' macro.  This allows
    +   qh_qh to be either a pointer or a structure.  An example
    +   of using qh is "qh.DROPdim" which accesses the DROPdim
    +   field of qh_qh.  Similarly, access to qh_qhstat is via
    +   the 'qhstat' macro.
    +
    +   includes function prototypes for libqhull.c, geom.c, global.c, io.c, user.c
    +
    +   use mem.h for mem.c
    +   use qset.h for qset.c
    +
    +   see unix.c for an example of using libqhull.h
    +
    +   recompile qhull if you change this file
    +*/
    +
    +#ifndef qhDEFlibqhull
    +#define qhDEFlibqhull 1
    +
    +/*=========================== -included files ==============*/
    +
    +/* user_r.h first for QHULL_CRTDBG */
    +#include "user.h"      /* user definable constants (e.g., qh_QHpointer) */
    +
    +#include "mem.h"   /* Needed qhT in libqhull_r.h.  Here for compatibility */
    +#include "qset.h"   /* Needed for QHULL_LIB_CHECK */
    +/* include stat_r.h after defining boolT.  Needed for qhT in libqhull_r.h.  Here for compatibility and statT */
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __MWERKS__ && __POWERPC__
    +#include  
    +#include  
    +#include        
    +#endif
    +
    +#ifndef __STDC__
    +#ifndef __cplusplus
    +#if     !_MSC_VER
    +#error  Neither __STDC__ nor __cplusplus is defined.  Please use strict ANSI C or C++ to compile
    +#error  Qhull.  You may need to turn off compiler extensions in your project configuration.  If
    +#error  your compiler is a standard C compiler, you can delete this warning from libqhull.h
    +#endif
    +#endif
    +#endif
    +
    +/*============ constants and basic types ====================*/
    +
    +extern const char qh_version[]; /* defined in global.c */
    +extern const char qh_version2[]; /* defined in global.c */
    +
    +/*----------------------------------
    +
    +  coordT
    +    coordinates and coefficients are stored as realT (i.e., double)
    +
    +  notes:
    +    Qhull works well if realT is 'float'.  If so joggle (QJ) is not effective.
    +
    +    Could use 'float' for data and 'double' for calculations (realT vs. coordT)
    +      This requires many type casts, and adjusted error bounds.
    +      Also C compilers may do expressions in double anyway.
    +*/
    +#define coordT realT
    +
    +/*----------------------------------
    +
    +  pointT
    +    a point is an array of coordinates, usually qh.hull_dim
    +    qh_pointid returns
    +      qh_IDnone if point==0 or qh is undefined
    +      qh_IDinterior for qh.interior_point
    +      qh_IDunknown if point is neither in qh.first_point... nor qh.other_points
    +
    +  notes:
    +    qh.STOPcone and qh.STOPpoint assume that qh_IDunknown==-1 (other negative numbers indicate points)
    +    qh_IDunknown is also returned by getid_() for unknown facet, ridge, or vertex
    +*/
    +#define pointT coordT
    +typedef enum
    +{
    +    qh_IDnone = -3, qh_IDinterior = -2, qh_IDunknown = -1
    +}
    +qh_pointT;
    +
    +/*----------------------------------
    +
    +  flagT
    +    Boolean flag as a bit
    +*/
    +#define flagT unsigned int
    +
    +/*----------------------------------
    +
    +  boolT
    +    boolean value, either True or False
    +
    +  notes:
    +    needed for portability
    +    Use qh_False/qh_True as synonyms
    +*/
    +#define boolT unsigned int
    +#ifdef False
    +#undef False
    +#endif
    +#ifdef True
    +#undef True
    +#endif
    +#define False 0
    +#define True 1
    +#define qh_False 0
    +#define qh_True 1
    +
    +#include "stat.h"  /* after define of boolT */
    +
    +/*----------------------------------
    +
    +  qh_CENTER
    +    to distinguish facet->center
    +*/
    +typedef enum
    +{
    +    qh_ASnone = 0,   /* If not MERGING and not VORONOI */
    +    qh_ASvoronoi,    /* Set by qh_clearcenters on qh_prepare_output, or if not MERGING and VORONOI */
    +    qh_AScentrum     /* If MERGING (assumed during merging) */
    +}
    +qh_CENTER;
    +
    +/*----------------------------------
    +
    +  qh_PRINT
    +    output formats for printing (qh.PRINTout).
    +    'Fa' 'FV' 'Fc' 'FC'
    +
    +
    +   notes:
    +   some of these names are similar to qhT names.  The similar names are only
    +   used in switch statements in qh_printbegin() etc.
    +*/
    +typedef enum {qh_PRINTnone= 0,
    +  qh_PRINTarea, qh_PRINTaverage,           /* 'Fa' 'FV' 'Fc' 'FC' */
    +  qh_PRINTcoplanars, qh_PRINTcentrums,
    +  qh_PRINTfacets, qh_PRINTfacets_xridge,   /* 'f' 'FF' 'G' 'FI' 'Fi' 'Fn' */
    +  qh_PRINTgeom, qh_PRINTids, qh_PRINTinner, qh_PRINTneighbors,
    +  qh_PRINTnormals, qh_PRINTouter, qh_PRINTmaple, /* 'n' 'Fo' 'i' 'm' 'Fm' 'FM', 'o' */
    +  qh_PRINTincidences, qh_PRINTmathematica, qh_PRINTmerges, qh_PRINToff,
    +  qh_PRINToptions, qh_PRINTpointintersect, /* 'FO' 'Fp' 'FP' 'p' 'FQ' 'FS' */
    +  qh_PRINTpointnearest, qh_PRINTpoints, qh_PRINTqhull, qh_PRINTsize,
    +  qh_PRINTsummary, qh_PRINTtriangles,      /* 'Fs' 'Ft' 'Fv' 'FN' 'Fx' */
    +  qh_PRINTvertices, qh_PRINTvneighbors, qh_PRINTextremes,
    +  qh_PRINTEND} qh_PRINT;
    +
    +/*----------------------------------
    +
    +  qh_ALL
    +    argument flag for selecting everything
    +*/
    +#define qh_ALL      True
    +#define qh_NOupper  True     /* argument for qh_findbest */
    +#define qh_IScheckmax  True     /* argument for qh_findbesthorizon */
    +#define qh_ISnewfacets  True     /* argument for qh_findbest */
    +#define qh_RESETvisible  True     /* argument for qh_resetlists */
    +
    +/*----------------------------------
    +
    +  qh_ERR
    +    Qhull exit codes, for indicating errors
    +    See: MSG_ERROR and MSG_WARNING [user.h]
    +*/
    +#define qh_ERRnone  0    /* no error occurred during qhull */
    +#define qh_ERRinput 1    /* input inconsistency */
    +#define qh_ERRsingular 2 /* singular input data */
    +#define qh_ERRprec  3    /* precision error */
    +#define qh_ERRmem   4    /* insufficient memory, matches mem.h */
    +#define qh_ERRqhull 5    /* internal error detected, matches mem.h */
    +
    +/*----------------------------------
    +
    +qh_FILEstderr
    +Fake stderr to distinguish error output from normal output
    +For C++ interface.  Must redefine qh_fprintf_qhull
    +*/
    +#define qh_FILEstderr ((FILE*)1)
    +
    +/* ============ -structures- ====================
    +   each of the following structures is defined by a typedef
    +   all realT and coordT fields occur at the beginning of a structure
    +        (otherwise space may be wasted due to alignment)
    +   define all flags together and pack into 32-bit number
    +   DEFsetT is likewise defined in
    +   mem.h and qset.h
    +*/
    +
    +typedef struct vertexT vertexT;
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;          /* defined in qset.h */
    +#endif
    +
    +/*----------------------------------
    +
    +  facetT
    +    defines a facet
    +
    +  notes:
    +   qhull() generates the hull as a list of facets.
    +
    +  topological information:
    +    f.previous,next     doubly-linked list of facets
    +    f.vertices          set of vertices
    +    f.ridges            set of ridges
    +    f.neighbors         set of neighbors
    +    f.toporient         True if facet has top-orientation (else bottom)
    +
    +  geometric information:
    +    f.offset,normal     hyperplane equation
    +    f.maxoutside        offset to outer plane -- all points inside
    +    f.center            centrum for testing convexity
    +    f.simplicial        True if facet is simplicial
    +    f.flipped           True if facet does not include qh.interior_point
    +
    +  for constructing hull:
    +    f.visible           True if facet on list of visible facets (will be deleted)
    +    f.newfacet          True if facet on list of newly created facets
    +    f.coplanarset       set of points coplanar with this facet
    +                        (includes near-inside points for later testing)
    +    f.outsideset        set of points outside of this facet
    +    f.furthestdist      distance to furthest point of outside set
    +    f.visitid           marks visited facets during a loop
    +    f.replace           replacement facet for to-be-deleted, visible facets
    +    f.samecycle,newcycle cycle of facets for merging into horizon facet
    +
    +  see below for other flags and fields
    +*/
    +struct facetT {
    +#if !qh_COMPUTEfurthest
    +  coordT   furthestdist;/* distance to furthest point of outsideset */
    +#endif
    +#if qh_MAXoutside
    +  coordT   maxoutside;  /* max computed distance of point to facet
    +                        Before QHULLfinished this is an approximation
    +                        since maxdist not always set for mergefacet
    +                        Actual outer plane is +DISTround and
    +                        computed outer plane is +2*DISTround */
    +#endif
    +  coordT   offset;      /* exact offset of hyperplane from origin */
    +  coordT  *normal;      /* normal of hyperplane, hull_dim coefficients */
    +                        /*   if tricoplanar, shared with a neighbor */
    +  union {               /* in order of testing */
    +   realT   area;        /* area of facet, only in io.c if  ->isarea */
    +   facetT *replace;     /*  replacement facet if ->visible and NEWfacets
    +                             is NULL only if qh_mergedegen_redundant or interior */
    +   facetT *samecycle;   /*  cycle of facets from the same visible/horizon intersection,
    +                             if ->newfacet */
    +   facetT *newcycle;    /*  in horizon facet, current samecycle of new facets */
    +   facetT *trivisible;  /* visible facet for ->tricoplanar facets during qh_triangulate() */
    +   facetT *triowner;    /* owner facet for ->tricoplanar, !isarea facets w/ ->keepcentrum */
    +  }f;
    +  coordT  *center;      /* set according to qh.CENTERtype */
    +                        /*   qh_ASnone:    no center (not MERGING) */
    +                        /*   qh_AScentrum: centrum for testing convexity (qh_getcentrum) */
    +                        /*                 assumed qh_AScentrum while merging */
    +                        /*   qh_ASvoronoi: Voronoi center (qh_facetcenter) */
    +                        /* after constructing the hull, it may be changed (qh_clearcenter) */
    +                        /* if tricoplanar and !keepcentrum, shared with a neighbor */
    +  facetT  *previous;    /* previous facet in the facet_list */
    +  facetT  *next;        /* next facet in the facet_list */
    +  setT    *vertices;    /* vertices for this facet, inverse sorted by ID
    +                           if simplicial, 1st vertex was apex/furthest */
    +  setT    *ridges;      /* explicit ridges for nonsimplicial facets.
    +                           for simplicial facets, neighbors define the ridges */
    +  setT    *neighbors;   /* neighbors of the facet.  If simplicial, the kth
    +                           neighbor is opposite the kth vertex, and the first
    +                           neighbor is the horizon facet for the first vertex*/
    +  setT    *outsideset;  /* set of points outside this facet
    +                           if non-empty, last point is furthest
    +                           if NARROWhull, includes coplanars for partitioning*/
    +  setT    *coplanarset; /* set of points coplanar with this facet
    +                           > qh.min_vertex and <= facet->max_outside
    +                           a point is assigned to the furthest facet
    +                           if non-empty, last point is furthest away */
    +  unsigned visitid;     /* visit_id, for visiting all neighbors,
    +                           all uses are independent */
    +  unsigned id;          /* unique identifier from qh.facet_id */
    +  unsigned nummerge:9;  /* number of merges */
    +#define qh_MAXnummerge 511 /*     2^9-1, 32 flags total, see "flags:" in io.c */
    +  flagT    tricoplanar:1; /* True if TRIangulate and simplicial and coplanar with a neighbor */
    +                          /*   all tricoplanars share the same apex */
    +                          /*   all tricoplanars share the same ->center, ->normal, ->offset, ->maxoutside */
    +                          /*     ->keepcentrum is true for the owner.  It has the ->coplanareset */
    +                          /*   if ->degenerate, does not span facet (one logical ridge) */
    +                          /*   during qh_triangulate, f.trivisible points to original facet */
    +  flagT    newfacet:1;  /* True if facet on qh.newfacet_list (new or merged) */
    +  flagT    visible:1;   /* True if visible facet (will be deleted) */
    +  flagT    toporient:1; /* True if created with top orientation
    +                           after merging, use ridge orientation */
    +  flagT    simplicial:1;/* True if simplicial facet, ->ridges may be implicit */
    +  flagT    seen:1;      /* used to perform operations only once, like visitid */
    +  flagT    seen2:1;     /* used to perform operations only once, like visitid */
    +  flagT    flipped:1;   /* True if facet is flipped */
    +  flagT    upperdelaunay:1; /* True if facet is upper envelope of Delaunay triangulation */
    +  flagT    notfurthest:1; /* True if last point of outsideset is not furthest*/
    +
    +/*-------- flags primarily for output ---------*/
    +  flagT    good:1;      /* True if a facet marked good for output */
    +  flagT    isarea:1;    /* True if facet->f.area is defined */
    +
    +/*-------- flags for merging ------------------*/
    +  flagT    dupridge:1;  /* True if duplicate ridge in facet */
    +  flagT    mergeridge:1; /* True if facet or neighbor contains a qh_MERGEridge
    +                            ->normal defined (also defined for mergeridge2) */
    +  flagT    mergeridge2:1; /* True if neighbor contains a qh_MERGEridge (mark_dupridges */
    +  flagT    coplanar:1;  /* True if horizon facet is coplanar at last use */
    +  flagT     mergehorizon:1; /* True if will merge into horizon (->coplanar) */
    +  flagT     cycledone:1;/* True if mergecycle_all already done */
    +  flagT    tested:1;    /* True if facet convexity has been tested (false after merge */
    +  flagT    keepcentrum:1; /* True if keep old centrum after a merge, or marks owner for ->tricoplanar */
    +  flagT    newmerge:1;  /* True if facet is newly merged for reducevertices */
    +  flagT    degenerate:1; /* True if facet is degenerate (degen_mergeset or ->tricoplanar) */
    +  flagT    redundant:1;  /* True if facet is redundant (degen_mergeset) */
    +};
    +
    +
    +/*----------------------------------
    +
    +  ridgeT
    +    defines a ridge
    +
    +  notes:
    +  a ridge is hull_dim-1 simplex between two neighboring facets.  If the
    +  facets are non-simplicial, there may be more than one ridge between
    +  two facets.  E.G. a 4-d hypercube has two triangles between each pair
    +  of neighboring facets.
    +
    +  topological information:
    +    vertices            a set of vertices
    +    top,bottom          neighboring facets with orientation
    +
    +  geometric information:
    +    tested              True if ridge is clearly convex
    +    nonconvex           True if ridge is non-convex
    +*/
    +struct ridgeT {
    +  setT    *vertices;    /* vertices belonging to this ridge, inverse sorted by ID
    +                           NULL if a degen ridge (matchsame) */
    +  facetT  *top;         /* top facet this ridge is part of */
    +  facetT  *bottom;      /* bottom facet this ridge is part of */
    +  unsigned id;          /* unique identifier.  Same size as vertex_id and ridge_id */
    +  flagT    seen:1;      /* used to perform operations only once */
    +  flagT    tested:1;    /* True when ridge is tested for convexity */
    +  flagT    nonconvex:1; /* True if getmergeset detected a non-convex neighbor
    +                           only one ridge between neighbors may have nonconvex */
    +};
    +
    +/*----------------------------------
    +
    +  vertexT
    +     defines a vertex
    +
    +  topological information:
    +    next,previous       doubly-linked list of all vertices
    +    neighbors           set of adjacent facets (only if qh.VERTEXneighbors)
    +
    +  geometric information:
    +    point               array of DIM3 coordinates
    +*/
    +struct vertexT {
    +  vertexT *next;        /* next vertex in vertex_list */
    +  vertexT *previous;    /* previous vertex in vertex_list */
    +  pointT  *point;       /* hull_dim coordinates (coordT) */
    +  setT    *neighbors;   /* neighboring facets of vertex, qh_vertexneighbors()
    +                           inits in io.c or after first merge */
    +  unsigned id;          /* unique identifier.  Same size as qh.vertex_id and qh.ridge_id */
    +  unsigned visitid;     /* for use with qh.vertex_visit, size must match */
    +  flagT    seen:1;      /* used to perform operations only once */
    +  flagT    seen2:1;     /* another seen flag */
    +  flagT    delridge:1;  /* vertex was part of a deleted ridge */
    +  flagT    deleted:1;   /* true if vertex on qh.del_vertices */
    +  flagT    newlist:1;   /* true if vertex on qh.newvertex_list */
    +};
    +
    +/*======= -global variables -qh ============================*/
    +
    +/*----------------------------------
    +
    +  qh
    +   all global variables for qhull are in qh, qhmem, and qhstat
    +
    +  notes:
    +   qhmem is defined in mem.h, qhstat is defined in stat.h, qhrbox is defined in rboxpoints.h
    +   Access to qh_qh is via the "qh" macro.  See qh_QHpointer in user.h
    +
    +   All global variables for qhull are in qh, qhmem, and qhstat
    +   qh must be unique for each instance of qhull
    +   qhstat may be shared between qhull instances.
    +   qhmem may be shared across multiple instances of Qhull.
    +   Rbox uses global variables rbox_inuse and rbox, but does not persist data across calls.
    +
    +   Qhull is not multi-threaded.  Global state could be stored in thread-local storage.
    +
    +   QHULL_LIB_CHECK checks that a program and the corresponding
    +   qhull library were built with the same type of header files.
    +*/
    +
    +typedef struct qhT qhT;
    +
    +#define QHULL_NON_REENTRANT 0
    +#define QHULL_QH_POINTER 1
    +#define QHULL_REENTRANT 2
    +
    +#if qh_QHpointer_dllimport
    +#define qh qh_qh->
    +__declspec(dllimport) extern qhT *qh_qh;     /* allocated in global.c */
    +#define QHULL_LIB_TYPE QHULL_QH_POINTER
    +
    +#elif qh_QHpointer
    +#define qh qh_qh->
    +extern qhT *qh_qh;     /* allocated in global.c */
    +#define QHULL_LIB_TYPE QHULL_QH_POINTER
    +
    +#elif qh_dllimport
    +#define qh qh_qh.
    +__declspec(dllimport) extern qhT qh_qh;      /* allocated in global.c */
    +#define QHULL_LIB_TYPE QHULL_NON_REENTRANT
    +
    +#else
    +#define qh qh_qh.
    +extern qhT qh_qh;
    +#define QHULL_LIB_TYPE QHULL_NON_REENTRANT
    +#endif
    +
    +#define QHULL_LIB_CHECK qh_lib_check(QHULL_LIB_TYPE, sizeof(qhT), sizeof(vertexT), sizeof(ridgeT), sizeof(facetT), sizeof(setT), sizeof(qhmemT));
    +#define QHULL_LIB_CHECK_RBOX qh_lib_check(QHULL_LIB_TYPE, sizeof(qhT), sizeof(vertexT), sizeof(ridgeT), sizeof(facetT), 0, 0);
    +
    +struct qhT {
    +
    +/*----------------------------------
    +
    +  qh constants
    +    configuration flags and constants for Qhull
    +
    +  notes:
    +    The user configures Qhull by defining flags.  They are
    +    copied into qh by qh_setflags().  qh-quick.htm#options defines the flags.
    +*/
    +  boolT ALLpoints;        /* true 'Qs' if search all points for initial simplex */
    +  boolT ANGLEmerge;       /* true 'Qa' if sort potential merges by angle */
    +  boolT APPROXhull;       /* true 'Wn' if MINoutside set */
    +  realT   MINoutside;     /*   'Wn' min. distance for an outside point */
    +  boolT ANNOTATEoutput;   /* true 'Ta' if annotate output with message codes */
    +  boolT ATinfinity;       /* true 'Qz' if point num_points-1 is "at-infinity"
    +                             for improving precision in Delaunay triangulations */
    +  boolT AVOIDold;         /* true 'Q4' if avoid old->new merges */
    +  boolT BESToutside;      /* true 'Qf' if partition points into best outsideset */
    +  boolT CDDinput;         /* true 'Pc' if input uses CDD format (1.0/offset first) */
    +  boolT CDDoutput;        /* true 'PC' if print normals in CDD format (offset first) */
    +  boolT CHECKfrequently;  /* true 'Tc' if checking frequently */
    +  realT premerge_cos;     /*   'A-n'   cos_max when pre merging */
    +  realT postmerge_cos;    /*   'An'    cos_max when post merging */
    +  boolT DELAUNAY;         /* true 'd' if computing DELAUNAY triangulation */
    +  boolT DOintersections;  /* true 'Gh' if print hyperplane intersections */
    +  int   DROPdim;          /* drops dim 'GDn' for 4-d -> 3-d output */
    +  boolT FORCEoutput;      /* true 'Po' if forcing output despite degeneracies */
    +  int   GOODpoint;        /* 1+n for 'QGn', good facet if visible/not(-) from point n*/
    +  pointT *GOODpointp;     /*   the actual point */
    +  boolT GOODthreshold;    /* true if qh.lower_threshold/upper_threshold defined
    +                             false if qh.SPLITthreshold */
    +  int   GOODvertex;       /* 1+n, good facet if vertex for point n */
    +  pointT *GOODvertexp;     /*   the actual point */
    +  boolT HALFspace;        /* true 'Hn,n,n' if halfspace intersection */
    +  boolT ISqhullQh;        /* Set by Qhull.cpp on initialization */
    +  int   IStracing;        /* trace execution, 0=none, 1=least, 4=most, -1=events */
    +  int   KEEParea;         /* 'PAn' number of largest facets to keep */
    +  boolT KEEPcoplanar;     /* true 'Qc' if keeping nearest facet for coplanar points */
    +  boolT KEEPinside;       /* true 'Qi' if keeping nearest facet for inside points
    +                              set automatically if 'd Qc' */
    +  int   KEEPmerge;        /* 'PMn' number of facets to keep with most merges */
    +  realT KEEPminArea;      /* 'PFn' minimum facet area to keep */
    +  realT MAXcoplanar;      /* 'Un' max distance below a facet to be coplanar*/
    +  boolT MERGEexact;       /* true 'Qx' if exact merges (coplanar, degen, dupridge, flipped) */
    +  boolT MERGEindependent; /* true 'Q2' if merging independent sets */
    +  boolT MERGING;          /* true if exact-, pre- or post-merging, with angle and centrum tests */
    +  realT   premerge_centrum;  /*   'C-n' centrum_radius when pre merging.  Default is round-off */
    +  realT   postmerge_centrum; /*   'Cn' centrum_radius when post merging.  Default is round-off */
    +  boolT MERGEvertices;    /* true 'Q3' if merging redundant vertices */
    +  realT MINvisible;       /* 'Vn' min. distance for a facet to be visible */
    +  boolT NOnarrow;         /* true 'Q10' if no special processing for narrow distributions */
    +  boolT NOnearinside;     /* true 'Q8' if ignore near-inside points when partitioning */
    +  boolT NOpremerge;       /* true 'Q0' if no defaults for C-0 or Qx */
    +  boolT NOwide;           /* true 'Q12' if no error on wide merge due to duplicate ridge */
    +  boolT ONLYgood;         /* true 'Qg' if process points with good visible or horizon facets */
    +  boolT ONLYmax;          /* true 'Qm' if only process points that increase max_outside */
    +  boolT PICKfurthest;     /* true 'Q9' if process furthest of furthest points*/
    +  boolT POSTmerge;        /* true if merging after buildhull (Cn or An) */
    +  boolT PREmerge;         /* true if merging during buildhull (C-n or A-n) */
    +                        /* NOTE: some of these names are similar to qh_PRINT names */
    +  boolT PRINTcentrums;    /* true 'Gc' if printing centrums */
    +  boolT PRINTcoplanar;    /* true 'Gp' if printing coplanar points */
    +  int   PRINTdim;         /* print dimension for Geomview output */
    +  boolT PRINTdots;        /* true 'Ga' if printing all points as dots */
    +  boolT PRINTgood;        /* true 'Pg' if printing good facets */
    +  boolT PRINTinner;       /* true 'Gi' if printing inner planes */
    +  boolT PRINTneighbors;   /* true 'PG' if printing neighbors of good facets */
    +  boolT PRINTnoplanes;    /* true 'Gn' if printing no planes */
    +  boolT PRINToptions1st;  /* true 'FO' if printing options to stderr */
    +  boolT PRINTouter;       /* true 'Go' if printing outer planes */
    +  boolT PRINTprecision;   /* false 'Pp' if not reporting precision problems */
    +  qh_PRINT PRINTout[qh_PRINTEND]; /* list of output formats to print */
    +  boolT PRINTridges;      /* true 'Gr' if print ridges */
    +  boolT PRINTspheres;     /* true 'Gv' if print vertices as spheres */
    +  boolT PRINTstatistics;  /* true 'Ts' if printing statistics to stderr */
    +  boolT PRINTsummary;     /* true 's' if printing summary to stderr */
    +  boolT PRINTtransparent; /* true 'Gt' if print transparent outer ridges */
    +  boolT PROJECTdelaunay;  /* true if DELAUNAY, no readpoints() and
    +                             need projectinput() for Delaunay in qh_init_B */
    +  int   PROJECTinput;     /* number of projected dimensions 'bn:0Bn:0' */
    +  boolT QUICKhelp;        /* true if quick help message for degen input */
    +  boolT RANDOMdist;       /* true if randomly change distplane and setfacetplane */
    +  realT RANDOMfactor;     /*    maximum random perturbation */
    +  realT RANDOMa;          /*    qh_randomfactor is randr * RANDOMa + RANDOMb */
    +  realT RANDOMb;
    +  boolT RANDOMoutside;    /* true if select a random outside point */
    +  int   REPORTfreq;       /* buildtracing reports every n facets */
    +  int   REPORTfreq2;      /* tracemerging reports every REPORTfreq/2 facets */
    +  int   RERUN;            /* 'TRn' rerun qhull n times (qh.build_cnt) */
    +  int   ROTATErandom;     /* 'QRn' seed, 0 time, >= rotate input */
    +  boolT SCALEinput;       /* true 'Qbk' if scaling input */
    +  boolT SCALElast;        /* true 'Qbb' if scale last coord to max prev coord */
    +  boolT SETroundoff;      /* true 'E' if qh.DISTround is predefined */
    +  boolT SKIPcheckmax;     /* true 'Q5' if skip qh_check_maxout */
    +  boolT SKIPconvex;       /* true 'Q6' if skip convexity testing during pre-merge */
    +  boolT SPLITthresholds;  /* true if upper_/lower_threshold defines a region
    +                               used only for printing (!for qh.ONLYgood) */
    +  int   STOPcone;         /* 'TCn' 1+n for stopping after cone for point n */
    +                          /*       also used by qh_build_withresart for err exit*/
    +  int   STOPpoint;        /* 'TVn' 'TV-n' 1+n for stopping after/before(-)
    +                                        adding point n */
    +  int   TESTpoints;       /* 'QTn' num of test points after qh.num_points.  Test points always coplanar. */
    +  boolT TESTvneighbors;   /*  true 'Qv' if test vertex neighbors at end */
    +  int   TRACElevel;       /* 'Tn' conditional IStracing level */
    +  int   TRACElastrun;     /*  qh.TRACElevel applies to last qh.RERUN */
    +  int   TRACEpoint;       /* 'TPn' start tracing when point n is a vertex */
    +  realT TRACEdist;        /* 'TWn' start tracing when merge distance too big */
    +  int   TRACEmerge;       /* 'TMn' start tracing before this merge */
    +  boolT TRIangulate;      /* true 'Qt' if triangulate non-simplicial facets */
    +  boolT TRInormals;       /* true 'Q11' if triangulate duplicates ->normal and ->center (sets Qt) */
    +  boolT UPPERdelaunay;    /* true 'Qu' if computing furthest-site Delaunay */
    +  boolT USEstdout;        /* true 'Tz' if using stdout instead of stderr */
    +  boolT VERIFYoutput;     /* true 'Tv' if verify output at end of qhull */
    +  boolT VIRTUALmemory;    /* true 'Q7' if depth-first processing in buildhull */
    +  boolT VORONOI;          /* true 'v' if computing Voronoi diagram */
    +
    +  /*--------input constants ---------*/
    +  realT AREAfactor;       /* 1/(hull_dim-1)! for converting det's to area */
    +  boolT DOcheckmax;       /* true if calling qh_check_maxout (qh_initqhull_globals) */
    +  char  *feasible_string;  /* feasible point 'Hn,n,n' for halfspace intersection */
    +  coordT *feasible_point;  /*    as coordinates, both malloc'd */
    +  boolT GETarea;          /* true 'Fa', 'FA', 'FS', 'PAn', 'PFn' if compute facet area/Voronoi volume in io.c */
    +  boolT KEEPnearinside;   /* true if near-inside points in coplanarset */
    +  int   hull_dim;         /* dimension of hull, set by initbuffers */
    +  int   input_dim;        /* dimension of input, set by initbuffers */
    +  int   num_points;       /* number of input points */
    +  pointT *first_point;    /* array of input points, see POINTSmalloc */
    +  boolT POINTSmalloc;     /*   true if qh.first_point/num_points allocated */
    +  pointT *input_points;   /* copy of original qh.first_point for input points for qh_joggleinput */
    +  boolT input_malloc;     /* true if qh.input_points malloc'd */
    +  char  qhull_command[256];/* command line that invoked this program */
    +  int   qhull_commandsiz2; /*    size of qhull_command at qh_clear_outputflags */
    +  char  rbox_command[256]; /* command line that produced the input points */
    +  char  qhull_options[512];/* descriptive list of options */
    +  int   qhull_optionlen;  /*    length of last line */
    +  int   qhull_optionsiz;  /*    size of qhull_options at qh_build_withrestart */
    +  int   qhull_optionsiz2; /*    size of qhull_options at qh_clear_outputflags */
    +  int   run_id;           /* non-zero, random identifier for this instance of qhull */
    +  boolT VERTEXneighbors;  /* true if maintaining vertex neighbors */
    +  boolT ZEROcentrum;      /* true if 'C-0' or 'C-0 Qx'.  sets ZEROall_ok */
    +  realT *upper_threshold; /* don't print if facet->normal[k]>=upper_threshold[k]
    +                             must set either GOODthreshold or SPLITthreshold
    +                             if Delaunay, default is 0.0 for upper envelope */
    +  realT *lower_threshold; /* don't print if facet->normal[k] <=lower_threshold[k] */
    +  realT *upper_bound;     /* scale point[k] to new upper bound */
    +  realT *lower_bound;     /* scale point[k] to new lower bound
    +                             project if both upper_ and lower_bound == 0 */
    +
    +/*----------------------------------
    +
    +  qh precision constants
    +    precision constants for Qhull
    +
    +  notes:
    +    qh_detroundoff() computes the maximum roundoff error for distance
    +    and other computations.  It also sets default values for the
    +    qh constants above.
    +*/
    +  realT ANGLEround;       /* max round off error for angles */
    +  realT centrum_radius;   /* max centrum radius for convexity (roundoff added) */
    +  realT cos_max;          /* max cosine for convexity (roundoff added) */
    +  realT DISTround;        /* max round off error for distances, 'E' overrides qh_distround() */
    +  realT MAXabs_coord;     /* max absolute coordinate */
    +  realT MAXlastcoord;     /* max last coordinate for qh_scalelast */
    +  realT MAXsumcoord;      /* max sum of coordinates */
    +  realT MAXwidth;         /* max rectilinear width of point coordinates */
    +  realT MINdenom_1;       /* min. abs. value for 1/x */
    +  realT MINdenom;         /*    use divzero if denominator < MINdenom */
    +  realT MINdenom_1_2;     /* min. abs. val for 1/x that allows normalization */
    +  realT MINdenom_2;       /*    use divzero if denominator < MINdenom_2 */
    +  realT MINlastcoord;     /* min. last coordinate for qh_scalelast */
    +  boolT NARROWhull;       /* set in qh_initialhull if angle < qh_MAXnarrow */
    +  realT *NEARzero;        /* hull_dim array for near zero in gausselim */
    +  realT NEARinside;       /* keep points for qh_check_maxout if close to facet */
    +  realT ONEmerge;         /* max distance for merging simplicial facets */
    +  realT outside_err;      /* application's epsilon for coplanar points
    +                             qh_check_bestdist() qh_check_points() reports error if point outside */
    +  realT WIDEfacet;        /* size of wide facet for skipping ridge in
    +                             area computation and locking centrum */
    +
    +/*----------------------------------
    +
    +  qh internal constants
    +    internal constants for Qhull
    +*/
    +  char qhull[sizeof("qhull")]; /* "qhull" for checking ownership while debugging */
    +  jmp_buf errexit;        /* exit label for qh_errexit, defined by setjmp() and NOerrexit */
    +  char jmpXtra[40];       /* extra bytes in case jmp_buf is defined wrong by compiler */
    +  jmp_buf restartexit;    /* restart label for qh_errexit, defined by setjmp() and ALLOWrestart */
    +  char jmpXtra2[40];      /* extra bytes in case jmp_buf is defined wrong by compiler*/
    +  FILE *fin;              /* pointer to input file, init by qh_initqhull_start2 */
    +  FILE *fout;             /* pointer to output file */
    +  FILE *ferr;             /* pointer to error file */
    +  pointT *interior_point; /* center point of the initial simplex*/
    +  int normal_size;     /* size in bytes for facet normals and point coords*/
    +  int center_size;     /* size in bytes for Voronoi centers */
    +  int   TEMPsize;         /* size for small, temporary sets (in quick mem) */
    +
    +/*----------------------------------
    +
    +  qh facet and vertex lists
    +    defines lists of facets, new facets, visible facets, vertices, and
    +    new vertices.  Includes counts, next ids, and trace ids.
    +  see:
    +    qh_resetlists()
    +*/
    +  facetT *facet_list;     /* first facet */
    +  facetT  *facet_tail;     /* end of facet_list (dummy facet) */
    +  facetT *facet_next;     /* next facet for buildhull()
    +                             previous facets do not have outside sets
    +                             NARROWhull: previous facets may have coplanar outside sets for qh_outcoplanar */
    +  facetT *newfacet_list;  /* list of new facets to end of facet_list */
    +  facetT *visible_list;   /* list of visible facets preceding newfacet_list,
    +                             facet->visible set */
    +  int       num_visible;  /* current number of visible facets */
    +  unsigned tracefacet_id;  /* set at init, then can print whenever */
    +  facetT *tracefacet;     /*   set in newfacet/mergefacet, undone in delfacet*/
    +  unsigned tracevertex_id;  /* set at buildtracing, can print whenever */
    +  vertexT *tracevertex;     /*   set in newvertex, undone in delvertex*/
    +  vertexT *vertex_list;     /* list of all vertices, to vertex_tail */
    +  vertexT  *vertex_tail;    /*      end of vertex_list (dummy vertex) */
    +  vertexT *newvertex_list; /* list of vertices in newfacet_list, to vertex_tail
    +                             all vertices have 'newlist' set */
    +  int   num_facets;       /* number of facets in facet_list
    +                             includes visible faces (num_visible) */
    +  int   num_vertices;     /* number of vertices in facet_list */
    +  int   num_outside;      /* number of points in outsidesets (for tracing and RANDOMoutside)
    +                               includes coplanar outsideset points for NARROWhull/qh_outcoplanar() */
    +  int   num_good;         /* number of good facets (after findgood_all) */
    +  unsigned facet_id;      /* ID of next, new facet from newfacet() */
    +  unsigned ridge_id;      /* ID of next, new ridge from newridge() */
    +  unsigned vertex_id;     /* ID of next, new vertex from newvertex() */
    +
    +/*----------------------------------
    +
    +  qh global variables
    +    defines minimum and maximum distances, next visit ids, several flags,
    +    and other global variables.
    +    initialize in qh_initbuild or qh_maxmin if used in qh_buildhull
    +*/
    +  unsigned long hulltime; /* ignore time to set up input and randomize */
    +                          /*   use unsigned to avoid wrap-around errors */
    +  boolT ALLOWrestart;     /* true if qh_precision can use qh.restartexit */
    +  int   build_cnt;        /* number of calls to qh_initbuild */
    +  qh_CENTER CENTERtype;   /* current type of facet->center, qh_CENTER */
    +  int   furthest_id;      /* pointid of furthest point, for tracing */
    +  facetT *GOODclosest;    /* closest facet to GOODthreshold in qh_findgood */
    +  boolT hasAreaVolume;    /* true if totarea, totvol was defined by qh_getarea */
    +  boolT hasTriangulation; /* true if triangulation created by qh_triangulate */
    +  realT JOGGLEmax;        /* set 'QJn' if randomly joggle input */
    +  boolT maxoutdone;       /* set qh_check_maxout(), cleared by qh_addpoint() */
    +  realT max_outside;      /* maximum distance from a point to a facet,
    +                               before roundoff, not simplicial vertices
    +                               actual outer plane is +DISTround and
    +                               computed outer plane is +2*DISTround */
    +  realT max_vertex;       /* maximum distance (>0) from vertex to a facet,
    +                               before roundoff, due to a merge */
    +  realT min_vertex;       /* minimum distance (<0) from vertex to a facet,
    +                               before roundoff, due to a merge
    +                               if qh.JOGGLEmax, qh_makenewplanes sets it
    +                               recomputed if qh.DOcheckmax, default -qh.DISTround */
    +  boolT NEWfacets;        /* true while visible facets invalid due to new or merge
    +                              from makecone/attachnewfacets to deletevisible */
    +  boolT findbestnew;      /* true if partitioning calls qh_findbestnew */
    +  boolT findbest_notsharp; /* true if new facets are at least 90 degrees */
    +  boolT NOerrexit;        /* true if qh.errexit is not available, cleared after setjmp */
    +  realT PRINTcradius;     /* radius for printing centrums */
    +  realT PRINTradius;      /* radius for printing vertex spheres and points */
    +  boolT POSTmerging;      /* true when post merging */
    +  int   printoutvar;      /* temporary variable for qh_printbegin, etc. */
    +  int   printoutnum;      /* number of facets printed */
    +  boolT QHULLfinished;    /* True after qhull() is finished */
    +  realT totarea;          /* 'FA': total facet area computed by qh_getarea, hasAreaVolume */
    +  realT totvol;           /* 'FA': total volume computed by qh_getarea, hasAreaVolume */
    +  unsigned int visit_id;  /* unique ID for searching neighborhoods, */
    +  unsigned int vertex_visit; /* unique ID for searching vertices, reset with qh_buildtracing */
    +  boolT ZEROall_ok;       /* True if qh_checkzero always succeeds */
    +  boolT WAScoplanar;      /* True if qh_partitioncoplanar (qh_check_maxout) */
    +
    +/*----------------------------------
    +
    +  qh global sets
    +    defines sets for merging, initial simplex, hashing, extra input points,
    +    and deleted vertices
    +*/
    +  setT *facet_mergeset;   /* temporary set of merges to be done */
    +  setT *degen_mergeset;   /* temporary set of degenerate and redundant merges */
    +  setT *hash_table;       /* hash table for matching ridges in qh_matchfacets
    +                             size is setsize() */
    +  setT *other_points;     /* additional points */
    +  setT *del_vertices;     /* vertices to partition and delete with visible
    +                             facets.  Have deleted set for checkfacet */
    +
    +/*----------------------------------
    +
    +  qh global buffers
    +    defines buffers for maxtrix operations, input, and error messages
    +*/
    +  coordT *gm_matrix;      /* (dim+1)Xdim matrix for geom.c */
    +  coordT **gm_row;        /* array of gm_matrix rows */
    +  char* line;             /* malloc'd input line of maxline+1 chars */
    +  int maxline;
    +  coordT *half_space;     /* malloc'd input array for halfspace (qh normal_size+coordT) */
    +  coordT *temp_malloc;    /* malloc'd input array for points */
    +
    +/*----------------------------------
    +
    +  qh static variables
    +    defines static variables for individual functions
    +
    +  notes:
    +    do not use 'static' within a function.  Multiple instances of qhull
    +    may exist.
    +
    +    do not assume zero initialization, 'QPn' may cause a restart
    +*/
    +  boolT ERREXITcalled;    /* true during qh_errexit (prevents duplicate calls */
    +  boolT firstcentrum;     /* for qh_printcentrum */
    +  boolT old_randomdist;   /* save RANDOMdist flag during io, tracing, or statistics */
    +  setT *coplanarfacetset;  /* set of coplanar facets for searching qh_findbesthorizon() */
    +  realT last_low;         /* qh_scalelast parameters for qh_setdelaunay */
    +  realT last_high;
    +  realT last_newhigh;
    +  unsigned lastreport;    /* for qh_buildtracing */
    +  int mergereport;        /* for qh_tracemerging */
    +  qhstatT *old_qhstat;    /* for saving qh_qhstat in save_qhull() and UsingLibQhull.  Free with qh_free() */
    +  setT *old_tempstack;    /* for saving qhmem.tempstack in save_qhull */
    +  int   ridgeoutnum;      /* number of ridges for 4OFF output (qh_printbegin,etc) */
    +};
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  otherfacet_(ridge, facet)
    +    return neighboring facet for a ridge in facet
    +*/
    +#define otherfacet_(ridge, facet) \
    +                        (((ridge)->top == (facet)) ? (ridge)->bottom : (ridge)->top)
    +
    +/*----------------------------------
    +
    +  getid_(p)
    +    return int ID for facet, ridge, or vertex
    +    return qh_IDunknown(-1) if NULL
    +*/
    +#define getid_(p)       ((p) ? (int)((p)->id) : qh_IDunknown)
    +
    +/*============== FORALL macros ===================*/
    +
    +/*----------------------------------
    +
    +  FORALLfacets { ... }
    +    assign 'facet' to each facet in qh.facet_list
    +
    +  notes:
    +    uses 'facetT *facet;'
    +    assumes last facet is a sentinel
    +
    +  see:
    +    FORALLfacet_( facetlist )
    +*/
    +#define FORALLfacets for (facet=qh facet_list;facet && facet->next;facet=facet->next)
    +
    +/*----------------------------------
    +
    +  FORALLpoints { ... }
    +    assign 'point' to each point in qh.first_point, qh.num_points
    +
    +  declare:
    +    coordT *point, *pointtemp;
    +*/
    +#define FORALLpoints FORALLpoint_(qh first_point, qh num_points)
    +
    +/*----------------------------------
    +
    +  FORALLpoint_( points, num) { ... }
    +    assign 'point' to each point in points array of num points
    +
    +  declare:
    +    coordT *point, *pointtemp;
    +*/
    +#define FORALLpoint_(points, num) for (point= (points), \
    +      pointtemp= (points)+qh hull_dim*(num); point < pointtemp; point += qh hull_dim)
    +
    +/*----------------------------------
    +
    +  FORALLvertices { ... }
    +    assign 'vertex' to each vertex in qh.vertex_list
    +
    +  declare:
    +    vertexT *vertex;
    +
    +  notes:
    +    assumes qh.vertex_list terminated with a sentinel
    +*/
    +#define FORALLvertices for (vertex=qh vertex_list;vertex && vertex->next;vertex= vertex->next)
    +
    +/*----------------------------------
    +
    +  FOREACHfacet_( facets ) { ... }
    +    assign 'facet' to each facet in facets
    +
    +  declare:
    +    facetT *facet, **facetp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHfacet_(facets)    FOREACHsetelement_(facetT, facets, facet)
    +
    +/*----------------------------------
    +
    +  FOREACHneighbor_( facet ) { ... }
    +    assign 'neighbor' to each neighbor in facet->neighbors
    +
    +  FOREACHneighbor_( vertex ) { ... }
    +    assign 'neighbor' to each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighbor, **neighborp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHneighbor_(facet)  FOREACHsetelement_(facetT, facet->neighbors, neighbor)
    +
    +/*----------------------------------
    +
    +  FOREACHpoint_( points ) { ... }
    +    assign 'point' to each point in points set
    +
    +  declare:
    +    pointT *point, **pointp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHpoint_(points)    FOREACHsetelement_(pointT, points, point)
    +
    +/*----------------------------------
    +
    +  FOREACHridge_( ridges ) { ... }
    +    assign 'ridge' to each ridge in ridges set
    +
    +  declare:
    +    ridgeT *ridge, **ridgep;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHridge_(ridges)    FOREACHsetelement_(ridgeT, ridges, ridge)
    +
    +/*----------------------------------
    +
    +  FOREACHvertex_( vertices ) { ... }
    +    assign 'vertex' to each vertex in vertices set
    +
    +  declare:
    +    vertexT *vertex, **vertexp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHvertex_(vertices) FOREACHsetelement_(vertexT, vertices,vertex)
    +
    +/*----------------------------------
    +
    +  FOREACHfacet_i_( facets ) { ... }
    +    assign 'facet' and 'facet_i' for each facet in facets set
    +
    +  declare:
    +    facetT *facet;
    +    int     facet_n, facet_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHfacet_i_(facets)    FOREACHsetelement_i_(facetT, facets, facet)
    +
    +/*----------------------------------
    +
    +  FOREACHneighbor_i_( facet ) { ... }
    +    assign 'neighbor' and 'neighbor_i' for each neighbor in facet->neighbors
    +
    +  FOREACHneighbor_i_( vertex ) { ... }
    +    assign 'neighbor' and 'neighbor_i' for each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighbor;
    +    int     neighbor_n, neighbor_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHneighbor_i_(facet)  FOREACHsetelement_i_(facetT, facet->neighbors, neighbor)
    +
    +/*----------------------------------
    +
    +  FOREACHpoint_i_( points ) { ... }
    +    assign 'point' and 'point_i' for each point in points set
    +
    +  declare:
    +    pointT *point;
    +    int     point_n, point_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHpoint_i_(points)    FOREACHsetelement_i_(pointT, points, point)
    +
    +/*----------------------------------
    +
    +  FOREACHridge_i_( ridges ) { ... }
    +    assign 'ridge' and 'ridge_i' for each ridge in ridges set
    +
    +  declare:
    +    ridgeT *ridge;
    +    int     ridge_n, ridge_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHridge_i_(ridges)    FOREACHsetelement_i_(ridgeT, ridges, ridge)
    +
    +/*----------------------------------
    +
    +  FOREACHvertex_i_( vertices ) { ... }
    +    assign 'vertex' and 'vertex_i' for each vertex in vertices set
    +
    +  declare:
    +    vertexT *vertex;
    +    int     vertex_n, vertex_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHvertex_i_(vertices) FOREACHsetelement_i_(vertexT, vertices,vertex)
    +
    +/********* -libqhull.c prototypes (duplicated from qhull_a.h) **********************/
    +
    +void    qh_qhull(void);
    +boolT   qh_addpoint(pointT *furthest, facetT *facet, boolT checkdist);
    +void    qh_printsummary(FILE *fp);
    +
    +/********* -user.c prototypes (alphabetical) **********************/
    +
    +void    qh_errexit(int exitcode, facetT *facet, ridgeT *ridge);
    +void    qh_errprint(const char* string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex);
    +int     qh_new_qhull(int dim, int numpoints, coordT *points, boolT ismalloc,
    +                char *qhull_cmd, FILE *outfile, FILE *errfile);
    +void    qh_printfacetlist(facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printhelp_degenerate(FILE *fp);
    +void    qh_printhelp_narrowhull(FILE *fp, realT minangle);
    +void    qh_printhelp_singular(FILE *fp);
    +void    qh_user_memsizes(void);
    +
    +/********* -usermem.c prototypes (alphabetical) **********************/
    +void    qh_exit(int exitcode);
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +
    +/********* -userprintf.c and userprintf_rbox.c prototypes **********************/
    +void    qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_fprintf_rbox(FILE *fp, int msgcode, const char *fmt, ... );
    +
    +/***** -geom.c/geom2.c/random.c prototypes (duplicated from geom.h, random.h) ****************/
    +
    +facetT *qh_findbest(pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT newfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart);
    +facetT *qh_findbestnew(pointT *point, facetT *startfacet,
    +                     realT *dist, boolT bestoutside, boolT *isoutside, int *numpart);
    +boolT   qh_gram_schmidt(int dim, realT **rows);
    +void    qh_outerinner(facetT *facet, realT *outerplane, realT *innerplane);
    +void    qh_printsummary(FILE *fp);
    +void    qh_projectinput(void);
    +void    qh_randommatrix(realT *buffer, int dim, realT **row);
    +void    qh_rotateinput(realT **rows);
    +void    qh_scaleinput(void);
    +void    qh_setdelaunay(int dim, int count, pointT *points);
    +coordT  *qh_sethalfspace_all(int dim, int count, coordT *halfspaces, pointT *feasible);
    +
    +/***** -global.c prototypes (alphabetical) ***********************/
    +
    +unsigned long qh_clock(void);
    +void    qh_checkflags(char *command, char *hiddenflags);
    +void    qh_clear_outputflags(void);
    +void    qh_freebuffers(void);
    +void    qh_freeqhull(boolT allmem);
    +void    qh_freeqhull2(boolT allmem);
    +void    qh_init_A(FILE *infile, FILE *outfile, FILE *errfile, int argc, char *argv[]);
    +void    qh_init_B(coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_init_qhull_command(int argc, char *argv[]);
    +void    qh_initbuffers(coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_initflags(char *command);
    +void    qh_initqhull_buffers(void);
    +void    qh_initqhull_globals(coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_initqhull_mem(void);
    +void    qh_initqhull_outputflags(void);
    +void    qh_initqhull_start(FILE *infile, FILE *outfile, FILE *errfile);
    +void    qh_initqhull_start2(FILE *infile, FILE *outfile, FILE *errfile);
    +void    qh_initthresholds(char *command);
    +void    qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeTsize, int facetTsize, int setTsize, int qhmemTsize);
    +void    qh_option(const char *option, int *i, realT *r);
    +#if qh_QHpointer
    +void    qh_restore_qhull(qhT **oldqh);
    +qhT    *qh_save_qhull(void);
    +#endif
    +
    +/***** -io.c prototypes (duplicated from io.h) ***********************/
    +
    +void    qh_dfacet(unsigned id);
    +void    qh_dvertex(unsigned id);
    +void    qh_printneighborhood(FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall);
    +void    qh_produce_output(void);
    +coordT *qh_readpoints(int *numpoints, int *dimension, boolT *ismalloc);
    +
    +
    +/********* -mem.c prototypes (duplicated from mem.h) **********************/
    +
    +void qh_meminit(FILE *ferr);
    +void qh_memfreeshort(int *curlong, int *totlong);
    +
    +/********* -poly.c/poly2.c prototypes (duplicated from poly.h) **********************/
    +
    +void    qh_check_output(void);
    +void    qh_check_points(void);
    +setT   *qh_facetvertices(facetT *facetlist, setT *facets, boolT allfacets);
    +facetT *qh_findbestfacet(pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside);
    +vertexT *qh_nearvertex(facetT *facet, pointT *point, realT *bestdistp);
    +pointT *qh_point(int id);
    +setT   *qh_pointfacet(void /*qh.facet_list*/);
    +int     qh_pointid(pointT *point);
    +setT   *qh_pointvertex(void /*qh.facet_list*/);
    +void    qh_setvoronoi_all(void);
    +void    qh_triangulate(void /*qh.facet_list*/);
    +
    +/********* -rboxlib.c prototypes **********************/
    +int     qh_rboxpoints(FILE* fout, FILE* ferr, char* rbox_command);
    +void    qh_errexit_rbox(int exitcode);
    +
    +/********* -stat.c prototypes (duplicated from stat.h) **********************/
    +
    +void    qh_collectstatistics(void);
    +void    qh_printallstatistics(FILE *fp, const char *string);
    +
    +#endif /* qhDEFlibqhull */
    diff --git a/xs/src/qhull/src/libqhull/libqhull.pro b/xs/src/qhull/src/libqhull/libqhull.pro
    new file mode 100644
    index 0000000000..18005da59d
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/libqhull.pro
    @@ -0,0 +1,67 @@
    +# -------------------------------------------------
    +# libqhull.pro -- Qt project for Qhull shared library
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +DESTDIR = ../../lib
    +DLLDESTDIR = ../../bin
    +TEMPLATE = lib
    +CONFIG += shared warn_on
    +CONFIG -= qt
    +
    +build_pass:CONFIG(debug, debug|release):{
    +    TARGET = qhull_d
    +    OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +    TARGET = qhull
    +    OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +win32-msvc* : DEF_FILE += ../../src/libqhull/qhull-exports.def
    +
    +# Order object files by frequency of execution.  Small files at end.
    +
    +# libqhull/libqhull.pro and ../qhull-libqhull-src.pri have the same SOURCES and HEADERS
    +SOURCES += ../libqhull/global.c
    +SOURCES += ../libqhull/stat.c
    +SOURCES += ../libqhull/geom2.c
    +SOURCES += ../libqhull/poly2.c
    +SOURCES += ../libqhull/merge.c
    +SOURCES += ../libqhull/libqhull.c
    +SOURCES += ../libqhull/geom.c
    +SOURCES += ../libqhull/poly.c
    +SOURCES += ../libqhull/qset.c
    +SOURCES += ../libqhull/mem.c
    +SOURCES += ../libqhull/random.c
    +SOURCES += ../libqhull/usermem.c
    +SOURCES += ../libqhull/userprintf.c
    +SOURCES += ../libqhull/io.c
    +SOURCES += ../libqhull/user.c
    +SOURCES += ../libqhull/rboxlib.c
    +SOURCES += ../libqhull/userprintf_rbox.c
    +
    +HEADERS += ../libqhull/geom.h
    +HEADERS += ../libqhull/io.h
    +HEADERS += ../libqhull/libqhull.h
    +HEADERS += ../libqhull/mem.h
    +HEADERS += ../libqhull/merge.h
    +HEADERS += ../libqhull/poly.h
    +HEADERS += ../libqhull/random.h
    +HEADERS += ../libqhull/qhull_a.h
    +HEADERS += ../libqhull/qset.h
    +HEADERS += ../libqhull/stat.h
    +HEADERS += ../libqhull/user.h
    +
    +OTHER_FILES += Mborland
    +OTHER_FILES += qh-geom.htm
    +OTHER_FILES += qh-globa.htm
    +OTHER_FILES += qh-io.htm
    +OTHER_FILES += qh-mem.htm
    +OTHER_FILES += qh-merge.htm
    +OTHER_FILES += qh-poly.htm
    +OTHER_FILES += qh-qhull.htm
    +OTHER_FILES += qh-set.htm
    +OTHER_FILES += qh-stat.htm
    +OTHER_FILES += qh-user.htm
    diff --git a/xs/src/qhull/src/libqhull/mem.c b/xs/src/qhull/src/libqhull/mem.c
    new file mode 100644
    index 0000000000..db72bb4e19
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/mem.c
    @@ -0,0 +1,576 @@
    +/*
      ---------------------------------
    +
    +  mem.c
    +    memory management routines for qhull
    +
    +  This is a standalone program.
    +
    +  To initialize memory:
    +
    +    qh_meminit(stderr);
    +    qh_meminitbuffers(qh IStracing, qh_MEMalign, 7, qh_MEMbufsize,qh_MEMinitbuf);
    +    qh_memsize((int)sizeof(facetT));
    +    qh_memsize((int)sizeof(facetT));
    +    ...
    +    qh_memsetup();
    +
    +  To free up all memory buffers:
    +    qh_memfreeshort(&curlong, &totlong);
    +
    +  if qh_NOmem,
    +    malloc/free is used instead of mem.c
    +
    +  notes:
    +    uses Quickfit algorithm (freelists for commonly allocated sizes)
    +    assumes small sizes for freelists (it discards the tail of memory buffers)
    +
    +  see:
    +    qh-mem.htm and mem.h
    +    global.c (qh_initbuffers) for an example of using mem.c
    +
    +  Copyright (c) 1993-2015 The Geometry Center.
    +  $Id: //main/2015/qhull/src/libqhull/mem.c#7 $$Change: 2065 $
    +  $DateTime: 2016/01/18 13:51:04 $$Author: bbarber $
    +*/
    +
    +#include "user.h"  /* for QHULL_CRTDBG */
    +#include "mem.h"
    +#include 
    +#include 
    +#include 
    +
    +#ifndef qhDEFlibqhull
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4127)  /* conditional expression is constant */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#endif
    +void    qh_errexit(int exitcode, facetT *, ridgeT *);
    +void    qh_exit(int exitcode);
    +void    qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +#endif
    +
    +/*============ -global data structure ==============
    +    see mem.h for definition
    +*/
    +
    +qhmemT qhmem= {0,0,0,0,0,0,0,0,0,0,0,
    +               0,0,0,0,0,0,0,0,0,0,0,
    +               0,0,0,0,0,0,0};     /* remove "= {0}" if this causes a compiler error */
    +
    +#ifndef qh_NOmem
    +
    +/*============= internal functions ==============*/
    +
    +static int qh_intcompare(const void *i, const void *j);
    +
    +/*========== functions in alphabetical order ======== */
    +
    +/*---------------------------------
    +
    +  qh_intcompare( i, j )
    +    used by qsort and bsearch to compare two integers
    +*/
    +static int qh_intcompare(const void *i, const void *j) {
    +  return(*((const int *)i) - *((const int *)j));
    +} /* intcompare */
    +
    +
    +/*----------------------------------
    +
    +  qh_memalloc( insize )
    +    returns object of insize bytes
    +    qhmem is the global memory structure
    +
    +  returns:
    +    pointer to allocated memory
    +    errors if insufficient memory
    +
    +  notes:
    +    use explicit type conversion to avoid type warnings on some compilers
    +    actual object may be larger than insize
    +    use qh_memalloc_() for inline code for quick allocations
    +    logs allocations if 'T5'
    +    caller is responsible for freeing the memory.
    +    short memory is freed on shutdown by qh_memfreeshort unless qh_NOmem
    +
    +  design:
    +    if size < qhmem.LASTsize
    +      if qhmem.freelists[size] non-empty
    +        return first object on freelist
    +      else
    +        round up request to size of qhmem.freelists[size]
    +        allocate new allocation buffer if necessary
    +        allocate object from allocation buffer
    +    else
    +      allocate object with qh_malloc() in user.c
    +*/
    +void *qh_memalloc(int insize) {
    +  void **freelistp, *newbuffer;
    +  int idx, size, n;
    +  int outsize, bufsize;
    +  void *object;
    +
    +  if (insize<0) {
    +      qh_fprintf(qhmem.ferr, 6235, "qhull error (qh_memalloc): negative request size (%d).  Did int overflow due to high-D?\n", insize); /* WARN64 */
    +      qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (insize>=0 && insize <= qhmem.LASTsize) {
    +    idx= qhmem.indextable[insize];
    +    outsize= qhmem.sizetable[idx];
    +    qhmem.totshort += outsize;
    +    freelistp= qhmem.freelists+idx;
    +    if ((object= *freelistp)) {
    +      qhmem.cntquick++;
    +      qhmem.totfree -= outsize;
    +      *freelistp= *((void **)*freelistp);  /* replace freelist with next object */
    +#ifdef qh_TRACEshort
    +      n= qhmem.cntshort+qhmem.cntquick+qhmem.freeshort;
    +      if (qhmem.IStracing >= 5)
    +          qh_fprintf(qhmem.ferr, 8141, "qh_mem %p n %8d alloc quick: %d bytes (tot %d cnt %d)\n", object, n, outsize, qhmem.totshort, qhmem.cntshort+qhmem.cntquick-qhmem.freeshort);
    +#endif
    +      return(object);
    +    }else {
    +      qhmem.cntshort++;
    +      if (outsize > qhmem.freesize) {
    +        qhmem.totdropped += qhmem.freesize;
    +        if (!qhmem.curbuffer)
    +          bufsize= qhmem.BUFinit;
    +        else
    +          bufsize= qhmem.BUFsize;
    +        if (!(newbuffer= qh_malloc((size_t)bufsize))) {
    +          qh_fprintf(qhmem.ferr, 6080, "qhull error (qh_memalloc): insufficient memory to allocate short memory buffer (%d bytes)\n", bufsize);
    +          qh_errexit(qhmem_ERRmem, NULL, NULL);
    +        }
    +        *((void **)newbuffer)= qhmem.curbuffer;  /* prepend newbuffer to curbuffer
    +                                                    list.  newbuffer!=0 by QH6080 */
    +        qhmem.curbuffer= newbuffer;
    +        size= (sizeof(void **) + qhmem.ALIGNmask) & ~qhmem.ALIGNmask;
    +        qhmem.freemem= (void *)((char *)newbuffer+size);
    +        qhmem.freesize= bufsize - size;
    +        qhmem.totbuffer += bufsize - size; /* easier to check */
    +        /* Periodically test totbuffer.  It matches at beginning and exit of every call */
    +        n = qhmem.totshort + qhmem.totfree + qhmem.totdropped + qhmem.freesize - outsize;
    +        if (qhmem.totbuffer != n) {
    +            qh_fprintf(qhmem.ferr, 6212, "qh_memalloc internal error: short totbuffer %d != totshort+totfree... %d\n", qhmem.totbuffer, n);
    +            qh_errexit(qhmem_ERRmem, NULL, NULL);
    +        }
    +      }
    +      object= qhmem.freemem;
    +      qhmem.freemem= (void *)((char *)qhmem.freemem + outsize);
    +      qhmem.freesize -= outsize;
    +      qhmem.totunused += outsize - insize;
    +#ifdef qh_TRACEshort
    +      n= qhmem.cntshort+qhmem.cntquick+qhmem.freeshort;
    +      if (qhmem.IStracing >= 5)
    +          qh_fprintf(qhmem.ferr, 8140, "qh_mem %p n %8d alloc short: %d bytes (tot %d cnt %d)\n", object, n, outsize, qhmem.totshort, qhmem.cntshort+qhmem.cntquick-qhmem.freeshort);
    +#endif
    +      return object;
    +    }
    +  }else {                     /* long allocation */
    +    if (!qhmem.indextable) {
    +      qh_fprintf(qhmem.ferr, 6081, "qhull internal error (qh_memalloc): qhmem has not been initialized.\n");
    +      qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +    }
    +    outsize= insize;
    +    qhmem.cntlong++;
    +    qhmem.totlong += outsize;
    +    if (qhmem.maxlong < qhmem.totlong)
    +      qhmem.maxlong= qhmem.totlong;
    +    if (!(object= qh_malloc((size_t)outsize))) {
    +      qh_fprintf(qhmem.ferr, 6082, "qhull error (qh_memalloc): insufficient memory to allocate %d bytes\n", outsize);
    +      qh_errexit(qhmem_ERRmem, NULL, NULL);
    +    }
    +    if (qhmem.IStracing >= 5)
    +      qh_fprintf(qhmem.ferr, 8057, "qh_mem %p n %8d alloc long: %d bytes (tot %d cnt %d)\n", object, qhmem.cntlong+qhmem.freelong, outsize, qhmem.totlong, qhmem.cntlong-qhmem.freelong);
    +  }
    +  return(object);
    +} /* memalloc */
    +
    +
    +/*----------------------------------
    +
    +  qh_memcheck( )
    +*/
    +void qh_memcheck(void) {
    +  int i, count, totfree= 0;
    +  void *object;
    +
    +  if (qhmem.ferr == 0 || qhmem.IStracing < 0 || qhmem.IStracing > 10 || (((qhmem.ALIGNmask+1) & qhmem.ALIGNmask) != 0)) {
    +    qh_fprintf_stderr(6244, "qh_memcheck error: either qhmem is overwritten or qhmem is not initialized.  Call qh_meminit() or qh_new_qhull() before calling qh_mem routines.  ferr 0x%x IsTracing %d ALIGNmask 0x%x", qhmem.ferr, qhmem.IStracing, qhmem.ALIGNmask);
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (qhmem.IStracing != 0)
    +    qh_fprintf(qhmem.ferr, 8143, "qh_memcheck: check size of freelists on qhmem\nqh_memcheck: A segmentation fault indicates an overwrite of qhmem\n");
    +  for (i=0; i < qhmem.TABLEsize; i++) {
    +    count=0;
    +    for (object= qhmem.freelists[i]; object; object= *((void **)object))
    +      count++;
    +    totfree += qhmem.sizetable[i] * count;
    +  }
    +  if (totfree != qhmem.totfree) {
    +    qh_fprintf(qhmem.ferr, 6211, "Qhull internal error (qh_memcheck): totfree %d not equal to freelist total %d\n", qhmem.totfree, totfree);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  if (qhmem.IStracing != 0)
    +    qh_fprintf(qhmem.ferr, 8144, "qh_memcheck: total size of freelists totfree is the same as qhmem.totfree\n", totfree);
    +} /* memcheck */
    +
    +/*----------------------------------
    +
    +  qh_memfree( object, insize )
    +    free up an object of size bytes
    +    size is insize from qh_memalloc
    +
    +  notes:
    +    object may be NULL
    +    type checking warns if using (void **)object
    +    use qh_memfree_() for quick free's of small objects
    +
    +  design:
    +    if size <= qhmem.LASTsize
    +      append object to corresponding freelist
    +    else
    +      call qh_free(object)
    +*/
    +void qh_memfree(void *object, int insize) {
    +  void **freelistp;
    +  int idx, outsize;
    +
    +  if (!object)
    +    return;
    +  if (insize <= qhmem.LASTsize) {
    +    qhmem.freeshort++;
    +    idx= qhmem.indextable[insize];
    +    outsize= qhmem.sizetable[idx];
    +    qhmem.totfree += outsize;
    +    qhmem.totshort -= outsize;
    +    freelistp= qhmem.freelists + idx;
    +    *((void **)object)= *freelistp;
    +    *freelistp= object;
    +#ifdef qh_TRACEshort
    +    idx= qhmem.cntshort+qhmem.cntquick+qhmem.freeshort;
    +    if (qhmem.IStracing >= 5)
    +        qh_fprintf(qhmem.ferr, 8142, "qh_mem %p n %8d free short: %d bytes (tot %d cnt %d)\n", object, idx, outsize, qhmem.totshort, qhmem.cntshort+qhmem.cntquick-qhmem.freeshort);
    +#endif
    +  }else {
    +    qhmem.freelong++;
    +    qhmem.totlong -= insize;
    +    if (qhmem.IStracing >= 5)
    +      qh_fprintf(qhmem.ferr, 8058, "qh_mem %p n %8d free long: %d bytes (tot %d cnt %d)\n", object, qhmem.cntlong+qhmem.freelong, insize, qhmem.totlong, qhmem.cntlong-qhmem.freelong);
    +    qh_free(object);
    +  }
    +} /* memfree */
    +
    +
    +/*---------------------------------
    +
    +  qh_memfreeshort( curlong, totlong )
    +    frees up all short and qhmem memory allocations
    +
    +  returns:
    +    number and size of current long allocations
    +
    +  see:
    +    qh_freeqhull(allMem)
    +    qh_memtotal(curlong, totlong, curshort, totshort, maxlong, totbuffer);
    +*/
    +void qh_memfreeshort(int *curlong, int *totlong) {
    +  void *buffer, *nextbuffer;
    +  FILE *ferr;
    +
    +  *curlong= qhmem.cntlong - qhmem.freelong;
    +  *totlong= qhmem.totlong;
    +  for (buffer= qhmem.curbuffer; buffer; buffer= nextbuffer) {
    +    nextbuffer= *((void **) buffer);
    +    qh_free(buffer);
    +  }
    +  qhmem.curbuffer= NULL;
    +  if (qhmem.LASTsize) {
    +    qh_free(qhmem.indextable);
    +    qh_free(qhmem.freelists);
    +    qh_free(qhmem.sizetable);
    +  }
    +  ferr= qhmem.ferr;
    +  memset((char *)&qhmem, 0, sizeof(qhmem));  /* every field is 0, FALSE, NULL */
    +  qhmem.ferr= ferr;
    +} /* memfreeshort */
    +
    +
    +/*----------------------------------
    +
    +  qh_meminit( ferr )
    +    initialize qhmem and test sizeof( void*)
    +    Does not throw errors.  qh_exit on failure
    +*/
    +void qh_meminit(FILE *ferr) {
    +
    +  memset((char *)&qhmem, 0, sizeof(qhmem));  /* every field is 0, FALSE, NULL */
    +  if (ferr)
    +    qhmem.ferr= ferr;
    +  else
    +    qhmem.ferr= stderr;
    +  if (sizeof(void*) < sizeof(int)) {
    +    qh_fprintf(qhmem.ferr, 6083, "qhull internal error (qh_meminit): sizeof(void*) %d < sizeof(int) %d.  qset.c will not work\n", (int)sizeof(void*), (int)sizeof(int));
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (sizeof(void*) > sizeof(ptr_intT)) {
    +      qh_fprintf(qhmem.ferr, 6084, "qhull internal error (qh_meminit): sizeof(void*) %d > sizeof(ptr_intT) %d. Change ptr_intT in mem.h to 'long long'\n", (int)sizeof(void*), (int)sizeof(ptr_intT));
    +      qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  qh_memcheck();
    +} /* meminit */
    +
    +/*---------------------------------
    +
    +  qh_meminitbuffers( tracelevel, alignment, numsizes, bufsize, bufinit )
    +    initialize qhmem
    +    if tracelevel >= 5, trace memory allocations
    +    alignment= desired address alignment for memory allocations
    +    numsizes= number of freelists
    +    bufsize=  size of additional memory buffers for short allocations
    +    bufinit=  size of initial memory buffer for short allocations
    +*/
    +void qh_meminitbuffers(int tracelevel, int alignment, int numsizes, int bufsize, int bufinit) {
    +
    +  qhmem.IStracing= tracelevel;
    +  qhmem.NUMsizes= numsizes;
    +  qhmem.BUFsize= bufsize;
    +  qhmem.BUFinit= bufinit;
    +  qhmem.ALIGNmask= alignment-1;
    +  if (qhmem.ALIGNmask & ~qhmem.ALIGNmask) {
    +    qh_fprintf(qhmem.ferr, 6085, "qhull internal error (qh_meminit): memory alignment %d is not a power of 2\n", alignment);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qhmem.sizetable= (int *) calloc((size_t)numsizes, sizeof(int));
    +  qhmem.freelists= (void **) calloc((size_t)numsizes, sizeof(void *));
    +  if (!qhmem.sizetable || !qhmem.freelists) {
    +    qh_fprintf(qhmem.ferr, 6086, "qhull error (qh_meminit): insufficient memory\n");
    +    qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (qhmem.IStracing >= 1)
    +    qh_fprintf(qhmem.ferr, 8059, "qh_meminitbuffers: memory initialized with alignment %d\n", alignment);
    +} /* meminitbuffers */
    +
    +/*---------------------------------
    +
    +  qh_memsetup()
    +    set up memory after running memsize()
    +*/
    +void qh_memsetup(void) {
    +  int k,i;
    +
    +  qsort(qhmem.sizetable, (size_t)qhmem.TABLEsize, sizeof(int), qh_intcompare);
    +  qhmem.LASTsize= qhmem.sizetable[qhmem.TABLEsize-1];
    +  if (qhmem.LASTsize >= qhmem.BUFsize || qhmem.LASTsize >= qhmem.BUFinit) {
    +    qh_fprintf(qhmem.ferr, 6087, "qhull error (qh_memsetup): largest mem size %d is >= buffer size %d or initial buffer size %d\n",
    +            qhmem.LASTsize, qhmem.BUFsize, qhmem.BUFinit);
    +    qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (!(qhmem.indextable= (int *)qh_malloc((qhmem.LASTsize+1) * sizeof(int)))) {
    +    qh_fprintf(qhmem.ferr, 6088, "qhull error (qh_memsetup): insufficient memory\n");
    +    qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  for (k=qhmem.LASTsize+1; k--; )
    +    qhmem.indextable[k]= k;
    +  i= 0;
    +  for (k=0; k <= qhmem.LASTsize; k++) {
    +    if (qhmem.indextable[k] <= qhmem.sizetable[i])
    +      qhmem.indextable[k]= i;
    +    else
    +      qhmem.indextable[k]= ++i;
    +  }
    +} /* memsetup */
    +
    +/*---------------------------------
    +
    +  qh_memsize( size )
    +    define a free list for this size
    +*/
    +void qh_memsize(int size) {
    +  int k;
    +
    +  if (qhmem.LASTsize) {
    +    qh_fprintf(qhmem.ferr, 6089, "qhull error (qh_memsize): called after qhmem_setup\n");
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  size= (size + qhmem.ALIGNmask) & ~qhmem.ALIGNmask;
    +  for (k=qhmem.TABLEsize; k--; ) {
    +    if (qhmem.sizetable[k] == size)
    +      return;
    +  }
    +  if (qhmem.TABLEsize < qhmem.NUMsizes)
    +    qhmem.sizetable[qhmem.TABLEsize++]= size;
    +  else
    +    qh_fprintf(qhmem.ferr, 7060, "qhull warning (memsize): free list table has room for only %d sizes\n", qhmem.NUMsizes);
    +} /* memsize */
    +
    +
    +/*---------------------------------
    +
    +  qh_memstatistics( fp )
    +    print out memory statistics
    +
    +    Verifies that qhmem.totfree == sum of freelists
    +*/
    +void qh_memstatistics(FILE *fp) {
    +  int i;
    +  int count;
    +  void *object;
    +
    +  qh_memcheck();
    +  qh_fprintf(fp, 9278, "\nmemory statistics:\n\
    +%7d quick allocations\n\
    +%7d short allocations\n\
    +%7d long allocations\n\
    +%7d short frees\n\
    +%7d long frees\n\
    +%7d bytes of short memory in use\n\
    +%7d bytes of short memory in freelists\n\
    +%7d bytes of dropped short memory\n\
    +%7d bytes of unused short memory (estimated)\n\
    +%7d bytes of long memory allocated (max, except for input)\n\
    +%7d bytes of long memory in use (in %d pieces)\n\
    +%7d bytes of short memory buffers (minus links)\n\
    +%7d bytes per short memory buffer (initially %d bytes)\n",
    +           qhmem.cntquick, qhmem.cntshort, qhmem.cntlong,
    +           qhmem.freeshort, qhmem.freelong,
    +           qhmem.totshort, qhmem.totfree,
    +           qhmem.totdropped + qhmem.freesize, qhmem.totunused,
    +           qhmem.maxlong, qhmem.totlong, qhmem.cntlong - qhmem.freelong,
    +           qhmem.totbuffer, qhmem.BUFsize, qhmem.BUFinit);
    +  if (qhmem.cntlarger) {
    +    qh_fprintf(fp, 9279, "%7d calls to qh_setlarger\n%7.2g     average copy size\n",
    +           qhmem.cntlarger, ((float)qhmem.totlarger)/(float)qhmem.cntlarger);
    +    qh_fprintf(fp, 9280, "  freelists(bytes->count):");
    +  }
    +  for (i=0; i < qhmem.TABLEsize; i++) {
    +    count=0;
    +    for (object= qhmem.freelists[i]; object; object= *((void **)object))
    +      count++;
    +    qh_fprintf(fp, 9281, " %d->%d", qhmem.sizetable[i], count);
    +  }
    +  qh_fprintf(fp, 9282, "\n\n");
    +} /* memstatistics */
    +
    +
    +/*---------------------------------
    +
    +  qh_NOmem
    +    turn off quick-fit memory allocation
    +
    +  notes:
    +    uses qh_malloc() and qh_free() instead
    +*/
    +#else /* qh_NOmem */
    +
    +void *qh_memalloc(int insize) {
    +  void *object;
    +
    +  if (!(object= qh_malloc((size_t)insize))) {
    +    qh_fprintf(qhmem.ferr, 6090, "qhull error (qh_memalloc): insufficient memory\n");
    +    qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  qhmem.cntlong++;
    +  qhmem.totlong += insize;
    +  if (qhmem.maxlong < qhmem.totlong)
    +      qhmem.maxlong= qhmem.totlong;
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8060, "qh_mem %p n %8d alloc long: %d bytes (tot %d cnt %d)\n", object, qhmem.cntlong+qhmem.freelong, insize, qhmem.totlong, qhmem.cntlong-qhmem.freelong);
    +  return object;
    +}
    +
    +void qh_memfree(void *object, int insize) {
    +
    +  if (!object)
    +    return;
    +  qh_free(object);
    +  qhmem.freelong++;
    +  qhmem.totlong -= insize;
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8061, "qh_mem %p n %8d free long: %d bytes (tot %d cnt %d)\n", object, qhmem.cntlong+qhmem.freelong, insize, qhmem.totlong, qhmem.cntlong-qhmem.freelong);
    +}
    +
    +void qh_memfreeshort(int *curlong, int *totlong) {
    +  *totlong= qhmem.totlong;
    +  *curlong= qhmem.cntlong - qhmem.freelong;
    +  memset((char *)&qhmem, 0, sizeof(qhmem));  /* every field is 0, FALSE, NULL */
    +}
    +
    +void qh_meminit(FILE *ferr) {
    +
    +  memset((char *)&qhmem, 0, sizeof(qhmem));  /* every field is 0, FALSE, NULL */
    +  if (ferr)
    +      qhmem.ferr= ferr;
    +  else
    +      qhmem.ferr= stderr;
    +  if (sizeof(void*) < sizeof(int)) {
    +    qh_fprintf(qhmem.ferr, 6091, "qhull internal error (qh_meminit): sizeof(void*) %d < sizeof(int) %d.  qset.c will not work\n", (int)sizeof(void*), (int)sizeof(int));
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +}
    +
    +void qh_meminitbuffers(int tracelevel, int alignment, int numsizes, int bufsize, int bufinit) {
    +
    +  qhmem.IStracing= tracelevel;
    +}
    +
    +void qh_memsetup(void) {
    +
    +}
    +
    +void qh_memsize(int size) {
    +
    +}
    +
    +void qh_memstatistics(FILE *fp) {
    +
    +  qh_fprintf(fp, 9409, "\nmemory statistics:\n\
    +%7d long allocations\n\
    +%7d long frees\n\
    +%7d bytes of long memory allocated (max, except for input)\n\
    +%7d bytes of long memory in use (in %d pieces)\n",
    +           qhmem.cntlong,
    +           qhmem.freelong,
    +           qhmem.maxlong, qhmem.totlong, qhmem.cntlong - qhmem.freelong);
    +}
    +
    +#endif /* qh_NOmem */
    +
    +/*---------------------------------
    +
    +  qh_memtotal( totlong, curlong, totshort, curshort, maxlong, totbuffer )
    +    Return the total, allocated long and short memory
    +
    +  returns:
    +    Returns the total current bytes of long and short allocations
    +    Returns the current count of long and short allocations
    +    Returns the maximum long memory and total short buffer (minus one link per buffer)
    +    Does not error (UsingLibQhull.cpp)
    +*/
    +void qh_memtotal(int *totlong, int *curlong, int *totshort, int *curshort, int *maxlong, int *totbuffer) {
    +    *totlong= qhmem.totlong;
    +    *curlong= qhmem.cntlong - qhmem.freelong;
    +    *totshort= qhmem.totshort;
    +    *curshort= qhmem.cntshort + qhmem.cntquick - qhmem.freeshort;
    +    *maxlong= qhmem.maxlong;
    +    *totbuffer= qhmem.totbuffer;
    +} /* memtotlong */
    +
    diff --git a/xs/src/qhull/src/libqhull/mem.h b/xs/src/qhull/src/libqhull/mem.h
    new file mode 100644
    index 0000000000..453f319df3
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/mem.h
    @@ -0,0 +1,222 @@
    +/*
      ---------------------------------
    +
    +   mem.h
    +     prototypes for memory management functions
    +
    +   see qh-mem.htm, mem.c and qset.h
    +
    +   for error handling, writes message and calls
    +     qh_errexit(qhmem_ERRmem, NULL, NULL) if insufficient memory
    +       and
    +     qh_errexit(qhmem_ERRqhull, NULL, NULL) otherwise
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/mem.h#2 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFmem
    +#define qhDEFmem 1
    +
    +#include 
    +
    +/*---------------------------------
    +
    +  qh_NOmem
    +    turn off quick-fit memory allocation
    +
    +  notes:
    +    mem.c implements Quickfit memory allocation for about 20% time
    +    savings.  If it fails on your machine, try to locate the
    +    problem, and send the answer to qhull@qhull.org.  If this can
    +    not be done, define qh_NOmem to use malloc/free instead.
    +
    +   #define qh_NOmem
    +*/
    +
    +/*---------------------------------
    +
    +qh_TRACEshort
    +Trace short and quick memory allocations at T5
    +
    +*/
    +#define qh_TRACEshort
    +
    +/*-------------------------------------------
    +    to avoid bus errors, memory allocation must consider alignment requirements.
    +    malloc() automatically takes care of alignment.   Since mem.c manages
    +    its own memory, we need to explicitly specify alignment in
    +    qh_meminitbuffers().
    +
    +    A safe choice is sizeof(double).  sizeof(float) may be used if doubles
    +    do not occur in data structures and pointers are the same size.  Be careful
    +    of machines (e.g., DEC Alpha) with large pointers.  If gcc is available,
    +    use __alignof__(double) or fmax_(__alignof__(float), __alignof__(void *)).
    +
    +   see qh_MEMalign in user.h for qhull's alignment
    +*/
    +
    +#define qhmem_ERRmem 4    /* matches qh_ERRmem in libqhull.h */
    +#define qhmem_ERRqhull 5  /* matches qh_ERRqhull in libqhull.h */
    +
    +/*----------------------------------
    +
    +  ptr_intT
    +    for casting a void * to an integer-type that holds a pointer
    +    Used for integer expressions (e.g., computing qh_gethash() in poly.c)
    +
    +  notes:
    +    WARN64 -- these notes indicate 64-bit issues
    +    On 64-bit machines, a pointer may be larger than an 'int'.
    +    qh_meminit()/mem.c checks that 'ptr_intT' holds a 'void*'
    +    ptr_intT is typically a signed value, but not necessarily so
    +    size_t is typically unsigned, but should match the parameter type
    +    Qhull uses int instead of size_t except for system calls such as malloc, qsort, qh_malloc, etc.
    +    This matches Qt convention and is easier to work with.
    +*/
    +#if (defined(__MINGW64__)) && defined(_WIN64)
    +typedef long long ptr_intT;
    +#elif (_MSC_VER) && defined(_WIN64)
    +typedef long long ptr_intT;
    +#else
    +typedef long ptr_intT;
    +#endif
    +
    +/*----------------------------------
    +
    +  qhmemT
    +    global memory structure for mem.c
    +
    + notes:
    +   users should ignore qhmem except for writing extensions
    +   qhmem is allocated in mem.c
    +
    +   qhmem could be swapable like qh and qhstat, but then
    +   multiple qh's and qhmem's would need to keep in synch.
    +   A swapable qhmem would also waste memory buffers.  As long
    +   as memory operations are atomic, there is no problem with
    +   multiple qh structures being active at the same time.
    +   If you need separate address spaces, you can swap the
    +   contents of qhmem.
    +*/
    +typedef struct qhmemT qhmemT;
    +extern qhmemT qhmem;
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;          /* defined in qset.h */
    +#endif
    +
    +/* Update qhmem in mem.c if add or remove fields */
    +struct qhmemT {               /* global memory management variables */
    +  int      BUFsize;           /* size of memory allocation buffer */
    +  int      BUFinit;           /* initial size of memory allocation buffer */
    +  int      TABLEsize;         /* actual number of sizes in free list table */
    +  int      NUMsizes;          /* maximum number of sizes in free list table */
    +  int      LASTsize;          /* last size in free list table */
    +  int      ALIGNmask;         /* worst-case alignment, must be 2^n-1 */
    +  void   **freelists;          /* free list table, linked by offset 0 */
    +  int     *sizetable;         /* size of each freelist */
    +  int     *indextable;        /* size->index table */
    +  void    *curbuffer;         /* current buffer, linked by offset 0 */
    +  void    *freemem;           /*   free memory in curbuffer */
    +  int      freesize;          /*   size of freemem in bytes */
    +  setT    *tempstack;         /* stack of temporary memory, managed by users */
    +  FILE    *ferr;              /* file for reporting errors when 'qh' may be undefined */
    +  int      IStracing;         /* =5 if tracing memory allocations */
    +  int      cntquick;          /* count of quick allocations */
    +                              /* Note: removing statistics doesn't effect speed */
    +  int      cntshort;          /* count of short allocations */
    +  int      cntlong;           /* count of long allocations */
    +  int      freeshort;         /* count of short memfrees */
    +  int      freelong;          /* count of long memfrees */
    +  int      totbuffer;         /* total short memory buffers minus buffer links */
    +  int      totdropped;        /* total dropped memory at end of short memory buffers (e.g., freesize) */
    +  int      totfree;           /* total size of free, short memory on freelists */
    +  int      totlong;           /* total size of long memory in use */
    +  int      maxlong;           /*   maximum totlong */
    +  int      totshort;          /* total size of short memory in use */
    +  int      totunused;         /* total unused short memory (estimated, short size - request size of first allocations) */
    +  int      cntlarger;         /* count of setlarger's */
    +  int      totlarger;         /* total copied by setlarger */
    +};
    +
    +
    +/*==================== -macros ====================*/
    +
    +/*----------------------------------
    +
    +  qh_memalloc_(insize, freelistp, object, type)
    +    returns object of size bytes
    +        assumes size<=qhmem.LASTsize and void **freelistp is a temp
    +*/
    +
    +#if defined qh_NOmem
    +#define qh_memalloc_(insize, freelistp, object, type) {\
    +  object= (type*)qh_memalloc(insize); }
    +#elif defined qh_TRACEshort
    +#define qh_memalloc_(insize, freelistp, object, type) {\
    +    freelistp= NULL; /* Avoid warnings */ \
    +    object= (type*)qh_memalloc(insize); }
    +#else /* !qh_NOmem */
    +
    +#define qh_memalloc_(insize, freelistp, object, type) {\
    +  freelistp= qhmem.freelists + qhmem.indextable[insize];\
    +  if ((object= (type*)*freelistp)) {\
    +    qhmem.totshort += qhmem.sizetable[qhmem.indextable[insize]]; \
    +    qhmem.totfree -= qhmem.sizetable[qhmem.indextable[insize]]; \
    +    qhmem.cntquick++;  \
    +    *freelistp= *((void **)*freelistp);\
    +  }else object= (type*)qh_memalloc(insize);}
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_memfree_(object, insize, freelistp)
    +    free up an object
    +
    +  notes:
    +    object may be NULL
    +    assumes size<=qhmem.LASTsize and void **freelistp is a temp
    +*/
    +#if defined qh_NOmem
    +#define qh_memfree_(object, insize, freelistp) {\
    +  qh_memfree(object, insize); }
    +#elif defined qh_TRACEshort
    +#define qh_memfree_(object, insize, freelistp) {\
    +    freelistp= NULL; /* Avoid warnings */ \
    +    qh_memfree(object, insize); }
    +#else /* !qh_NOmem */
    +
    +#define qh_memfree_(object, insize, freelistp) {\
    +  if (object) { \
    +    qhmem.freeshort++;\
    +    freelistp= qhmem.freelists + qhmem.indextable[insize];\
    +    qhmem.totshort -= qhmem.sizetable[qhmem.indextable[insize]]; \
    +    qhmem.totfree += qhmem.sizetable[qhmem.indextable[insize]]; \
    +    *((void **)object)= *freelistp;\
    +    *freelistp= object;}}
    +#endif
    +
    +/*=============== prototypes in alphabetical order ============*/
    +
    +void *qh_memalloc(int insize);
    +void qh_memcheck(void);
    +void qh_memfree(void *object, int insize);
    +void qh_memfreeshort(int *curlong, int *totlong);
    +void qh_meminit(FILE *ferr);
    +void qh_meminitbuffers(int tracelevel, int alignment, int numsizes,
    +                        int bufsize, int bufinit);
    +void qh_memsetup(void);
    +void qh_memsize(int size);
    +void qh_memstatistics(FILE *fp);
    +void qh_memtotal(int *totlong, int *curlong, int *totshort, int *curshort, int *maxlong, int *totbuffer);
    +
    +#endif /* qhDEFmem */
    diff --git a/xs/src/qhull/src/libqhull/merge.c b/xs/src/qhull/src/libqhull/merge.c
    new file mode 100644
    index 0000000000..22104dc031
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/merge.c
    @@ -0,0 +1,3628 @@
    +/*
      ---------------------------------
    +
    +   merge.c
    +   merges non-convex facets
    +
    +   see qh-merge.htm and merge.h
    +
    +   other modules call qh_premerge() and qh_postmerge()
    +
    +   the user may call qh_postmerge() to perform additional merges.
    +
    +   To remove deleted facets and vertices (qhull() in libqhull.c):
    +     qh_partitionvisible(!qh_ALL, &numoutside);  // visible_list, newfacet_list
    +     qh_deletevisible();         // qh.visible_list
    +     qh_resetlists(False, qh_RESETvisible);       // qh.visible_list newvertex_list newfacet_list
    +
    +   assumes qh.CENTERtype= centrum
    +
    +   merges occur in qh_mergefacet and in qh_mergecycle
    +   vertex->neighbors not set until the first merge occurs
    +
    +   Copyright (c) 1993-2015 C.B. Barber.
    +   $Id: //main/2015/qhull/src/libqhull/merge.c#4 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +#ifndef qh_NOmerge
    +
    +/*===== functions(alphabetical after premerge and postmerge) ======*/
    +
    +/*---------------------------------
    +
    +  qh_premerge( apex, maxcentrum )
    +    pre-merge nonconvex facets in qh.newfacet_list for apex
    +    maxcentrum defines coplanar and concave (qh_test_appendmerge)
    +
    +  returns:
    +    deleted facets added to qh.visible_list with facet->visible set
    +
    +  notes:
    +    uses globals, qh.MERGEexact, qh.PREmerge
    +
    +  design:
    +    mark duplicate ridges in qh.newfacet_list
    +    merge facet cycles in qh.newfacet_list
    +    merge duplicate ridges and concave facets in qh.newfacet_list
    +    check merged facet cycles for degenerate and redundant facets
    +    merge degenerate and redundant facets
    +    collect coplanar and concave facets
    +    merge concave, coplanar, degenerate, and redundant facets
    +*/
    +void qh_premerge(vertexT *apex, realT maxcentrum, realT maxangle) {
    +  boolT othermerge= False;
    +  facetT *newfacet;
    +
    +  if (qh ZEROcentrum && qh_checkzero(!qh_ALL))
    +    return;
    +  trace2((qh ferr, 2008, "qh_premerge: premerge centrum %2.2g angle %2.2g for apex v%d facetlist f%d\n",
    +            maxcentrum, maxangle, apex->id, getid_(qh newfacet_list)));
    +  if (qh IStracing >= 4 && qh num_facets < 50)
    +    qh_printlists();
    +  qh centrum_radius= maxcentrum;
    +  qh cos_max= maxangle;
    +  qh degen_mergeset= qh_settemp(qh TEMPsize);
    +  qh facet_mergeset= qh_settemp(qh TEMPsize);
    +  if (qh hull_dim >=3) {
    +    qh_mark_dupridges(qh newfacet_list); /* facet_mergeset */
    +    qh_mergecycle_all(qh newfacet_list, &othermerge);
    +    qh_forcedmerges(&othermerge /* qh.facet_mergeset */);
    +    FORALLnew_facets {  /* test samecycle merges */
    +      if (!newfacet->simplicial && !newfacet->mergeridge)
    +        qh_degen_redundant_neighbors(newfacet, NULL);
    +    }
    +    if (qh_merge_degenredundant())
    +      othermerge= True;
    +  }else /* qh.hull_dim == 2 */
    +    qh_mergecycle_all(qh newfacet_list, &othermerge);
    +  qh_flippedmerges(qh newfacet_list, &othermerge);
    +  if (!qh MERGEexact || zzval_(Ztotmerge)) {
    +    zinc_(Zpremergetot);
    +    qh POSTmerging= False;
    +    qh_getmergeset_initial(qh newfacet_list);
    +    qh_all_merges(othermerge, False);
    +  }
    +  qh_settempfree(&qh facet_mergeset);
    +  qh_settempfree(&qh degen_mergeset);
    +} /* premerge */
    +
    +/*---------------------------------
    +
    +  qh_postmerge( reason, maxcentrum, maxangle, vneighbors )
    +    post-merge nonconvex facets as defined by maxcentrum and maxangle
    +    'reason' is for reporting progress
    +    if vneighbors,
    +      calls qh_test_vneighbors at end of qh_all_merge
    +    if firstmerge,
    +      calls qh_reducevertices before qh_getmergeset
    +
    +  returns:
    +    if first call (qh.visible_list != qh.facet_list),
    +      builds qh.facet_newlist, qh.newvertex_list
    +    deleted facets added to qh.visible_list with facet->visible
    +    qh.visible_list == qh.facet_list
    +
    +  notes:
    +
    +
    +  design:
    +    if first call
    +      set qh.visible_list and qh.newfacet_list to qh.facet_list
    +      add all facets to qh.newfacet_list
    +      mark non-simplicial facets, facet->newmerge
    +      set qh.newvertext_list to qh.vertex_list
    +      add all vertices to qh.newvertex_list
    +      if a pre-merge occured
    +        set vertex->delridge {will retest the ridge}
    +        if qh.MERGEexact
    +          call qh_reducevertices()
    +      if no pre-merging
    +        merge flipped facets
    +    determine non-convex facets
    +    merge all non-convex facets
    +*/
    +void qh_postmerge(const char *reason, realT maxcentrum, realT maxangle,
    +                      boolT vneighbors) {
    +  facetT *newfacet;
    +  boolT othermerges= False;
    +  vertexT *vertex;
    +
    +  if (qh REPORTfreq || qh IStracing) {
    +    qh_buildtracing(NULL, NULL);
    +    qh_printsummary(qh ferr);
    +    if (qh PRINTstatistics)
    +      qh_printallstatistics(qh ferr, "reason");
    +    qh_fprintf(qh ferr, 8062, "\n%s with 'C%.2g' and 'A%.2g'\n",
    +        reason, maxcentrum, maxangle);
    +  }
    +  trace2((qh ferr, 2009, "qh_postmerge: postmerge.  test vneighbors? %d\n",
    +            vneighbors));
    +  qh centrum_radius= maxcentrum;
    +  qh cos_max= maxangle;
    +  qh POSTmerging= True;
    +  qh degen_mergeset= qh_settemp(qh TEMPsize);
    +  qh facet_mergeset= qh_settemp(qh TEMPsize);
    +  if (qh visible_list != qh facet_list) {  /* first call */
    +    qh NEWfacets= True;
    +    qh visible_list= qh newfacet_list= qh facet_list;
    +    FORALLnew_facets {
    +      newfacet->newfacet= True;
    +       if (!newfacet->simplicial)
    +        newfacet->newmerge= True;
    +     zinc_(Zpostfacets);
    +    }
    +    qh newvertex_list= qh vertex_list;
    +    FORALLvertices
    +      vertex->newlist= True;
    +    if (qh VERTEXneighbors) { /* a merge has occurred */
    +      FORALLvertices
    +        vertex->delridge= True; /* test for redundant, needed? */
    +      if (qh MERGEexact) {
    +        if (qh hull_dim <= qh_DIMreduceBuild)
    +          qh_reducevertices(); /* was skipped during pre-merging */
    +      }
    +    }
    +    if (!qh PREmerge && !qh MERGEexact)
    +      qh_flippedmerges(qh newfacet_list, &othermerges);
    +  }
    +  qh_getmergeset_initial(qh newfacet_list);
    +  qh_all_merges(False, vneighbors);
    +  qh_settempfree(&qh facet_mergeset);
    +  qh_settempfree(&qh degen_mergeset);
    +} /* post_merge */
    +
    +/*---------------------------------
    +
    +  qh_all_merges( othermerge, vneighbors )
    +    merge all non-convex facets
    +
    +    set othermerge if already merged facets (for qh_reducevertices)
    +    if vneighbors
    +      tests vertex neighbors for convexity at end
    +    qh.facet_mergeset lists the non-convex ridges in qh_newfacet_list
    +    qh.degen_mergeset is defined
    +    if qh.MERGEexact && !qh.POSTmerging,
    +      does not merge coplanar facets
    +
    +  returns:
    +    deleted facets added to qh.visible_list with facet->visible
    +    deleted vertices added qh.delvertex_list with vertex->delvertex
    +
    +  notes:
    +    unless !qh.MERGEindependent,
    +      merges facets in independent sets
    +    uses qh.newfacet_list as argument since merges call qh_removefacet()
    +
    +  design:
    +    while merges occur
    +      for each merge in qh.facet_mergeset
    +        unless one of the facets was already merged in this pass
    +          merge the facets
    +        test merged facets for additional merges
    +        add merges to qh.facet_mergeset
    +      if vertices record neighboring facets
    +        rename redundant vertices
    +          update qh.facet_mergeset
    +    if vneighbors ??
    +      tests vertex neighbors for convexity at end
    +*/
    +void qh_all_merges(boolT othermerge, boolT vneighbors) {
    +  facetT *facet1, *facet2;
    +  mergeT *merge;
    +  boolT wasmerge= True, isreduce;
    +  void **freelistp;  /* used if !qh_NOmem by qh_memfree_() */
    +  vertexT *vertex;
    +  mergeType mergetype;
    +  int numcoplanar=0, numconcave=0, numdegenredun= 0, numnewmerges= 0;
    +
    +  trace2((qh ferr, 2010, "qh_all_merges: starting to merge facets beginning from f%d\n",
    +            getid_(qh newfacet_list)));
    +  while (True) {
    +    wasmerge= False;
    +    while (qh_setsize(qh facet_mergeset)) {
    +      while ((merge= (mergeT*)qh_setdellast(qh facet_mergeset))) {
    +        facet1= merge->facet1;
    +        facet2= merge->facet2;
    +        mergetype= merge->type;
    +        qh_memfree_(merge, (int)sizeof(mergeT), freelistp);
    +        if (facet1->visible || facet2->visible) /*deleted facet*/
    +          continue;
    +        if ((facet1->newfacet && !facet1->tested)
    +                || (facet2->newfacet && !facet2->tested)) {
    +          if (qh MERGEindependent && mergetype <= MRGanglecoplanar)
    +            continue;      /* perform independent sets of merges */
    +        }
    +        qh_merge_nonconvex(facet1, facet2, mergetype);
    +        numdegenredun += qh_merge_degenredundant();
    +        numnewmerges++;
    +        wasmerge= True;
    +        if (mergetype == MRGconcave)
    +          numconcave++;
    +        else /* MRGcoplanar or MRGanglecoplanar */
    +          numcoplanar++;
    +      } /* while setdellast */
    +      if (qh POSTmerging && qh hull_dim <= qh_DIMreduceBuild
    +      && numnewmerges > qh_MAXnewmerges) {
    +        numnewmerges= 0;
    +        qh_reducevertices();  /* otherwise large post merges too slow */
    +      }
    +      qh_getmergeset(qh newfacet_list); /* facet_mergeset */
    +    } /* while mergeset */
    +    if (qh VERTEXneighbors) {
    +      isreduce= False;
    +      if (qh hull_dim >=4 && qh POSTmerging) {
    +        FORALLvertices
    +          vertex->delridge= True;
    +        isreduce= True;
    +      }
    +      if ((wasmerge || othermerge) && (!qh MERGEexact || qh POSTmerging)
    +          && qh hull_dim <= qh_DIMreduceBuild) {
    +        othermerge= False;
    +        isreduce= True;
    +      }
    +      if (isreduce) {
    +        if (qh_reducevertices()) {
    +          qh_getmergeset(qh newfacet_list); /* facet_mergeset */
    +          continue;
    +        }
    +      }
    +    }
    +    if (vneighbors && qh_test_vneighbors(/* qh.newfacet_list */))
    +      continue;
    +    break;
    +  } /* while (True) */
    +  if (qh CHECKfrequently && !qh MERGEexact) {
    +    qh old_randomdist= qh RANDOMdist;
    +    qh RANDOMdist= False;
    +    qh_checkconvex(qh newfacet_list, qh_ALGORITHMfault);
    +    /* qh_checkconnect(); [this is slow and it changes the facet order] */
    +    qh RANDOMdist= qh old_randomdist;
    +  }
    +  trace1((qh ferr, 1009, "qh_all_merges: merged %d coplanar facets %d concave facets and %d degen or redundant facets.\n",
    +    numcoplanar, numconcave, numdegenredun));
    +  if (qh IStracing >= 4 && qh num_facets < 50)
    +    qh_printlists();
    +} /* all_merges */
    +
    +
    +/*---------------------------------
    +
    +  qh_appendmergeset( facet, neighbor, mergetype, angle )
    +    appends an entry to qh.facet_mergeset or qh.degen_mergeset
    +
    +    angle ignored if NULL or !qh.ANGLEmerge
    +
    +  returns:
    +    merge appended to facet_mergeset or degen_mergeset
    +      sets ->degenerate or ->redundant if degen_mergeset
    +
    +  see:
    +    qh_test_appendmerge()
    +
    +  design:
    +    allocate merge entry
    +    if regular merge
    +      append to qh.facet_mergeset
    +    else if degenerate merge and qh.facet_mergeset is all degenerate
    +      append to qh.degen_mergeset
    +    else if degenerate merge
    +      prepend to qh.degen_mergeset
    +    else if redundant merge
    +      append to qh.degen_mergeset
    +*/
    +void qh_appendmergeset(facetT *facet, facetT *neighbor, mergeType mergetype, realT *angle) {
    +  mergeT *merge, *lastmerge;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  if (facet->redundant)
    +    return;
    +  if (facet->degenerate && mergetype == MRGdegen)
    +    return;
    +  qh_memalloc_((int)sizeof(mergeT), freelistp, merge, mergeT);
    +  merge->facet1= facet;
    +  merge->facet2= neighbor;
    +  merge->type= mergetype;
    +  if (angle && qh ANGLEmerge)
    +    merge->angle= *angle;
    +  if (mergetype < MRGdegen)
    +    qh_setappend(&(qh facet_mergeset), merge);
    +  else if (mergetype == MRGdegen) {
    +    facet->degenerate= True;
    +    if (!(lastmerge= (mergeT*)qh_setlast(qh degen_mergeset))
    +    || lastmerge->type == MRGdegen)
    +      qh_setappend(&(qh degen_mergeset), merge);
    +    else
    +      qh_setaddnth(&(qh degen_mergeset), 0, merge);
    +  }else if (mergetype == MRGredundant) {
    +    facet->redundant= True;
    +    qh_setappend(&(qh degen_mergeset), merge);
    +  }else /* mergetype == MRGmirror */ {
    +    if (facet->redundant || neighbor->redundant) {
    +      qh_fprintf(qh ferr, 6092, "qhull error (qh_appendmergeset): facet f%d or f%d is already a mirrored facet\n",
    +           facet->id, neighbor->id);
    +      qh_errexit2(qh_ERRqhull, facet, neighbor);
    +    }
    +    if (!qh_setequal(facet->vertices, neighbor->vertices)) {
    +      qh_fprintf(qh ferr, 6093, "qhull error (qh_appendmergeset): mirrored facets f%d and f%d do not have the same vertices\n",
    +           facet->id, neighbor->id);
    +      qh_errexit2(qh_ERRqhull, facet, neighbor);
    +    }
    +    facet->redundant= True;
    +    neighbor->redundant= True;
    +    qh_setappend(&(qh degen_mergeset), merge);
    +  }
    +} /* appendmergeset */
    +
    +
    +/*---------------------------------
    +
    +  qh_basevertices( samecycle )
    +    return temporary set of base vertices for samecycle
    +    samecycle is first facet in the cycle
    +    assumes apex is SETfirst_( samecycle->vertices )
    +
    +  returns:
    +    vertices(settemp)
    +    all ->seen are cleared
    +
    +  notes:
    +    uses qh_vertex_visit;
    +
    +  design:
    +    for each facet in samecycle
    +      for each unseen vertex in facet->vertices
    +        append to result
    +*/
    +setT *qh_basevertices(facetT *samecycle) {
    +  facetT *same;
    +  vertexT *apex, *vertex, **vertexp;
    +  setT *vertices= qh_settemp(qh TEMPsize);
    +
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  apex->visitid= ++qh vertex_visit;
    +  FORALLsame_cycle_(samecycle) {
    +    if (same->mergeridge)
    +      continue;
    +    FOREACHvertex_(same->vertices) {
    +      if (vertex->visitid != qh vertex_visit) {
    +        qh_setappend(&vertices, vertex);
    +        vertex->visitid= qh vertex_visit;
    +        vertex->seen= False;
    +      }
    +    }
    +  }
    +  trace4((qh ferr, 4019, "qh_basevertices: found %d vertices\n",
    +         qh_setsize(vertices)));
    +  return vertices;
    +} /* basevertices */
    +
    +/*---------------------------------
    +
    +  qh_checkconnect()
    +    check that new facets are connected
    +    new facets are on qh.newfacet_list
    +
    +  notes:
    +    this is slow and it changes the order of the facets
    +    uses qh.visit_id
    +
    +  design:
    +    move first new facet to end of qh.facet_list
    +    for all newly appended facets
    +      append unvisited neighbors to end of qh.facet_list
    +    for all new facets
    +      report error if unvisited
    +*/
    +void qh_checkconnect(void /* qh.newfacet_list */) {
    +  facetT *facet, *newfacet, *errfacet= NULL, *neighbor, **neighborp;
    +
    +  facet= qh newfacet_list;
    +  qh_removefacet(facet);
    +  qh_appendfacet(facet);
    +  facet->visitid= ++qh visit_id;
    +  FORALLfacet_(facet) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh visit_id) {
    +        qh_removefacet(neighbor);
    +        qh_appendfacet(neighbor);
    +        neighbor->visitid= qh visit_id;
    +      }
    +    }
    +  }
    +  FORALLnew_facets {
    +    if (newfacet->visitid == qh visit_id)
    +      break;
    +    qh_fprintf(qh ferr, 6094, "qhull error: f%d is not attached to the new facets\n",
    +         newfacet->id);
    +    errfacet= newfacet;
    +  }
    +  if (errfacet)
    +    qh_errexit(qh_ERRqhull, errfacet, NULL);
    +} /* checkconnect */
    +
    +/*---------------------------------
    +
    +  qh_checkzero( testall )
    +    check that facets are clearly convex for qh.DISTround with qh.MERGEexact
    +
    +    if testall,
    +      test all facets for qh.MERGEexact post-merging
    +    else
    +      test qh.newfacet_list
    +
    +    if qh.MERGEexact,
    +      allows coplanar ridges
    +      skips convexity test while qh.ZEROall_ok
    +
    +  returns:
    +    True if all facets !flipped, !dupridge, normal
    +         if all horizon facets are simplicial
    +         if all vertices are clearly below neighbor
    +         if all opposite vertices of horizon are below
    +    clears qh.ZEROall_ok if any problems or coplanar facets
    +
    +  notes:
    +    uses qh.vertex_visit
    +    horizon facets may define multiple new facets
    +
    +  design:
    +    for all facets in qh.newfacet_list or qh.facet_list
    +      check for flagged faults (flipped, etc.)
    +    for all facets in qh.newfacet_list or qh.facet_list
    +      for each neighbor of facet
    +        skip horizon facets for qh.newfacet_list
    +        test the opposite vertex
    +      if qh.newfacet_list
    +        test the other vertices in the facet's horizon facet
    +*/
    +boolT qh_checkzero(boolT testall) {
    +  facetT *facet, *neighbor, **neighborp;
    +  facetT *horizon, *facetlist;
    +  int neighbor_i;
    +  vertexT *vertex, **vertexp;
    +  realT dist;
    +
    +  if (testall)
    +    facetlist= qh facet_list;
    +  else {
    +    facetlist= qh newfacet_list;
    +    FORALLfacet_(facetlist) {
    +      horizon= SETfirstt_(facet->neighbors, facetT);
    +      if (!horizon->simplicial)
    +        goto LABELproblem;
    +      if (facet->flipped || facet->dupridge || !facet->normal)
    +        goto LABELproblem;
    +    }
    +    if (qh MERGEexact && qh ZEROall_ok) {
    +      trace2((qh ferr, 2011, "qh_checkzero: skip convexity check until first pre-merge\n"));
    +      return True;
    +    }
    +  }
    +  FORALLfacet_(facetlist) {
    +    qh vertex_visit++;
    +    neighbor_i= 0;
    +    horizon= NULL;
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor_i && !testall) {
    +        horizon= neighbor;
    +        neighbor_i++;
    +        continue; /* horizon facet tested in qh_findhorizon */
    +      }
    +      vertex= SETelemt_(facet->vertices, neighbor_i++, vertexT);
    +      vertex->visitid= qh vertex_visit;
    +      zzinc_(Zdistzero);
    +      qh_distplane(vertex->point, neighbor, &dist);
    +      if (dist >= -qh DISTround) {
    +        qh ZEROall_ok= False;
    +        if (!qh MERGEexact || testall || dist > qh DISTround)
    +          goto LABELnonconvex;
    +      }
    +    }
    +    if (!testall && horizon) {
    +      FOREACHvertex_(horizon->vertices) {
    +        if (vertex->visitid != qh vertex_visit) {
    +          zzinc_(Zdistzero);
    +          qh_distplane(vertex->point, facet, &dist);
    +          if (dist >= -qh DISTround) {
    +            qh ZEROall_ok= False;
    +            if (!qh MERGEexact || dist > qh DISTround)
    +              goto LABELnonconvex;
    +          }
    +          break;
    +        }
    +      }
    +    }
    +  }
    +  trace2((qh ferr, 2012, "qh_checkzero: testall %d, facets are %s\n", testall,
    +        (qh MERGEexact && !testall) ?
    +           "not concave, flipped, or duplicate ridged" : "clearly convex"));
    +  return True;
    +
    + LABELproblem:
    +  qh ZEROall_ok= False;
    +  trace2((qh ferr, 2013, "qh_checkzero: facet f%d needs pre-merging\n",
    +       facet->id));
    +  return False;
    +
    + LABELnonconvex:
    +  trace2((qh ferr, 2014, "qh_checkzero: facet f%d and f%d are not clearly convex.  v%d dist %.2g\n",
    +         facet->id, neighbor->id, vertex->id, dist));
    +  return False;
    +} /* checkzero */
    +
    +/*---------------------------------
    +
    +  qh_compareangle( angle1, angle2 )
    +    used by qsort() to order merges by angle
    +*/
    +int qh_compareangle(const void *p1, const void *p2) {
    +  const mergeT *a= *((mergeT *const*)p1), *b= *((mergeT *const*)p2);
    +
    +  return((a->angle > b->angle) ? 1 : -1);
    +} /* compareangle */
    +
    +/*---------------------------------
    +
    +  qh_comparemerge( merge1, merge2 )
    +    used by qsort() to order merges
    +*/
    +int qh_comparemerge(const void *p1, const void *p2) {
    +  const mergeT *a= *((mergeT *const*)p1), *b= *((mergeT *const*)p2);
    +
    +  return(a->type - b->type);
    +} /* comparemerge */
    +
    +/*---------------------------------
    +
    +  qh_comparevisit( vertex1, vertex2 )
    +    used by qsort() to order vertices by their visitid
    +*/
    +int qh_comparevisit(const void *p1, const void *p2) {
    +  const vertexT *a= *((vertexT *const*)p1), *b= *((vertexT *const*)p2);
    +
    +  return(a->visitid - b->visitid);
    +} /* comparevisit */
    +
    +/*---------------------------------
    +
    +  qh_copynonconvex( atridge )
    +    set non-convex flag on other ridges (if any) between same neighbors
    +
    +  notes:
    +    may be faster if use smaller ridge set
    +
    +  design:
    +    for each ridge of atridge's top facet
    +      if ridge shares the same neighbor
    +        set nonconvex flag
    +*/
    +void qh_copynonconvex(ridgeT *atridge) {
    +  facetT *facet, *otherfacet;
    +  ridgeT *ridge, **ridgep;
    +
    +  facet= atridge->top;
    +  otherfacet= atridge->bottom;
    +  FOREACHridge_(facet->ridges) {
    +    if (otherfacet == otherfacet_(ridge, facet) && ridge != atridge) {
    +      ridge->nonconvex= True;
    +      trace4((qh ferr, 4020, "qh_copynonconvex: moved nonconvex flag from r%d to r%d\n",
    +              atridge->id, ridge->id));
    +      break;
    +    }
    +  }
    +} /* copynonconvex */
    +
    +/*---------------------------------
    +
    +  qh_degen_redundant_facet( facet )
    +    check facet for degen. or redundancy
    +
    +  notes:
    +    bumps vertex_visit
    +    called if a facet was redundant but no longer is (qh_merge_degenredundant)
    +    qh_appendmergeset() only appends first reference to facet (i.e., redundant)
    +
    +  see:
    +    qh_degen_redundant_neighbors()
    +
    +  design:
    +    test for redundant neighbor
    +    test for degenerate facet
    +*/
    +void qh_degen_redundant_facet(facetT *facet) {
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +
    +  trace4((qh ferr, 4021, "qh_degen_redundant_facet: test facet f%d for degen/redundant\n",
    +          facet->id));
    +  FOREACHneighbor_(facet) {
    +    qh vertex_visit++;
    +    FOREACHvertex_(neighbor->vertices)
    +      vertex->visitid= qh vertex_visit;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh vertex_visit)
    +        break;
    +    }
    +    if (!vertex) {
    +      qh_appendmergeset(facet, neighbor, MRGredundant, NULL);
    +      trace2((qh ferr, 2015, "qh_degen_redundant_facet: f%d is contained in f%d.  merge\n", facet->id, neighbor->id));
    +      return;
    +    }
    +  }
    +  if (qh_setsize(facet->neighbors) < qh hull_dim) {
    +    qh_appendmergeset(facet, facet, MRGdegen, NULL);
    +    trace2((qh ferr, 2016, "qh_degen_redundant_neighbors: f%d is degenerate.\n", facet->id));
    +  }
    +} /* degen_redundant_facet */
    +
    +
    +/*---------------------------------
    +
    +  qh_degen_redundant_neighbors( facet, delfacet,  )
    +    append degenerate and redundant neighbors to facet_mergeset
    +    if delfacet,
    +      only checks neighbors of both delfacet and facet
    +    also checks current facet for degeneracy
    +
    +  notes:
    +    bumps vertex_visit
    +    called for each qh_mergefacet() and qh_mergecycle()
    +    merge and statistics occur in merge_nonconvex
    +    qh_appendmergeset() only appends first reference to facet (i.e., redundant)
    +      it appends redundant facets after degenerate ones
    +
    +    a degenerate facet has fewer than hull_dim neighbors
    +    a redundant facet's vertices is a subset of its neighbor's vertices
    +    tests for redundant merges first (appendmergeset is nop for others)
    +    in a merge, only needs to test neighbors of merged facet
    +
    +  see:
    +    qh_merge_degenredundant() and qh_degen_redundant_facet()
    +
    +  design:
    +    test for degenerate facet
    +    test for redundant neighbor
    +    test for degenerate neighbor
    +*/
    +void qh_degen_redundant_neighbors(facetT *facet, facetT *delfacet) {
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +  int size;
    +
    +  trace4((qh ferr, 4022, "qh_degen_redundant_neighbors: test neighbors of f%d with delfacet f%d\n",
    +          facet->id, getid_(delfacet)));
    +  if ((size= qh_setsize(facet->neighbors)) < qh hull_dim) {
    +    qh_appendmergeset(facet, facet, MRGdegen, NULL);
    +    trace2((qh ferr, 2017, "qh_degen_redundant_neighbors: f%d is degenerate with %d neighbors.\n", facet->id, size));
    +  }
    +  if (!delfacet)
    +    delfacet= facet;
    +  qh vertex_visit++;
    +  FOREACHvertex_(facet->vertices)
    +    vertex->visitid= qh vertex_visit;
    +  FOREACHneighbor_(delfacet) {
    +    /* uses early out instead of checking vertex count */
    +    if (neighbor == facet)
    +      continue;
    +    FOREACHvertex_(neighbor->vertices) {
    +      if (vertex->visitid != qh vertex_visit)
    +        break;
    +    }
    +    if (!vertex) {
    +      qh_appendmergeset(neighbor, facet, MRGredundant, NULL);
    +      trace2((qh ferr, 2018, "qh_degen_redundant_neighbors: f%d is contained in f%d.  merge\n", neighbor->id, facet->id));
    +    }
    +  }
    +  FOREACHneighbor_(delfacet) {   /* redundant merges occur first */
    +    if (neighbor == facet)
    +      continue;
    +    if ((size= qh_setsize(neighbor->neighbors)) < qh hull_dim) {
    +      qh_appendmergeset(neighbor, neighbor, MRGdegen, NULL);
    +      trace2((qh ferr, 2019, "qh_degen_redundant_neighbors: f%d is degenerate with %d neighbors.  Neighbor of f%d.\n", neighbor->id, size, facet->id));
    +    }
    +  }
    +} /* degen_redundant_neighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_find_newvertex( oldvertex, vertices, ridges )
    +    locate new vertex for renaming old vertex
    +    vertices is a set of possible new vertices
    +      vertices sorted by number of deleted ridges
    +
    +  returns:
    +    newvertex or NULL
    +      each ridge includes both vertex and oldvertex
    +    vertices sorted by number of deleted ridges
    +
    +  notes:
    +    modifies vertex->visitid
    +    new vertex is in one of the ridges
    +    renaming will not cause a duplicate ridge
    +    renaming will minimize the number of deleted ridges
    +    newvertex may not be adjacent in the dual (though unlikely)
    +
    +  design:
    +    for each vertex in vertices
    +      set vertex->visitid to number of references in ridges
    +    remove unvisited vertices
    +    set qh.vertex_visit above all possible values
    +    sort vertices by number of references in ridges
    +    add each ridge to qh.hash_table
    +    for each vertex in vertices
    +      look for a vertex that would not cause a duplicate ridge after a rename
    +*/
    +vertexT *qh_find_newvertex(vertexT *oldvertex, setT *vertices, setT *ridges) {
    +  vertexT *vertex, **vertexp;
    +  setT *newridges;
    +  ridgeT *ridge, **ridgep;
    +  int size, hashsize;
    +  int hash;
    +
    +#ifndef qh_NOtrace
    +  if (qh IStracing >= 4) {
    +    qh_fprintf(qh ferr, 8063, "qh_find_newvertex: find new vertex for v%d from ",
    +             oldvertex->id);
    +    FOREACHvertex_(vertices)
    +      qh_fprintf(qh ferr, 8064, "v%d ", vertex->id);
    +    FOREACHridge_(ridges)
    +      qh_fprintf(qh ferr, 8065, "r%d ", ridge->id);
    +    qh_fprintf(qh ferr, 8066, "\n");
    +  }
    +#endif
    +  FOREACHvertex_(vertices)
    +    vertex->visitid= 0;
    +  FOREACHridge_(ridges) {
    +    FOREACHvertex_(ridge->vertices)
    +      vertex->visitid++;
    +  }
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->visitid) {
    +      qh_setdelnth(vertices, SETindex_(vertices,vertex));
    +      vertexp--; /* repeat since deleted this vertex */
    +    }
    +  }
    +  qh vertex_visit += (unsigned int)qh_setsize(ridges);
    +  if (!qh_setsize(vertices)) {
    +    trace4((qh ferr, 4023, "qh_find_newvertex: vertices not in ridges for v%d\n",
    +            oldvertex->id));
    +    return NULL;
    +  }
    +  qsort(SETaddr_(vertices, vertexT), (size_t)qh_setsize(vertices),
    +                sizeof(vertexT *), qh_comparevisit);
    +  /* can now use qh vertex_visit */
    +  if (qh PRINTstatistics) {
    +    size= qh_setsize(vertices);
    +    zinc_(Zintersect);
    +    zadd_(Zintersecttot, size);
    +    zmax_(Zintersectmax, size);
    +  }
    +  hashsize= qh_newhashtable(qh_setsize(ridges));
    +  FOREACHridge_(ridges)
    +    qh_hashridge(qh hash_table, hashsize, ridge, oldvertex);
    +  FOREACHvertex_(vertices) {
    +    newridges= qh_vertexridges(vertex);
    +    FOREACHridge_(newridges) {
    +      if (qh_hashridge_find(qh hash_table, hashsize, ridge, vertex, oldvertex, &hash)) {
    +        zinc_(Zdupridge);
    +        break;
    +      }
    +    }
    +    qh_settempfree(&newridges);
    +    if (!ridge)
    +      break;  /* found a rename */
    +  }
    +  if (vertex) {
    +    /* counted in qh_renamevertex */
    +    trace2((qh ferr, 2020, "qh_find_newvertex: found v%d for old v%d from %d vertices and %d ridges.\n",
    +      vertex->id, oldvertex->id, qh_setsize(vertices), qh_setsize(ridges)));
    +  }else {
    +    zinc_(Zfindfail);
    +    trace0((qh ferr, 14, "qh_find_newvertex: no vertex for renaming v%d(all duplicated ridges) during p%d\n",
    +      oldvertex->id, qh furthest_id));
    +  }
    +  qh_setfree(&qh hash_table);
    +  return vertex;
    +} /* find_newvertex */
    +
    +/*---------------------------------
    +
    +  qh_findbest_test( testcentrum, facet, neighbor, bestfacet, dist, mindist, maxdist )
    +    test neighbor of facet for qh_findbestneighbor()
    +    if testcentrum,
    +      tests centrum (assumes it is defined)
    +    else
    +      tests vertices
    +
    +  returns:
    +    if a better facet (i.e., vertices/centrum of facet closer to neighbor)
    +      updates bestfacet, dist, mindist, and maxdist
    +*/
    +void qh_findbest_test(boolT testcentrum, facetT *facet, facetT *neighbor,
    +      facetT **bestfacet, realT *distp, realT *mindistp, realT *maxdistp) {
    +  realT dist, mindist, maxdist;
    +
    +  if (testcentrum) {
    +    zzinc_(Zbestdist);
    +    qh_distplane(facet->center, neighbor, &dist);
    +    dist *= qh hull_dim; /* estimate furthest vertex */
    +    if (dist < 0) {
    +      maxdist= 0;
    +      mindist= dist;
    +      dist= -dist;
    +    }else {
    +      mindist= 0;
    +      maxdist= dist;
    +    }
    +  }else
    +    dist= qh_getdistance(facet, neighbor, &mindist, &maxdist);
    +  if (dist < *distp) {
    +    *bestfacet= neighbor;
    +    *mindistp= mindist;
    +    *maxdistp= maxdist;
    +    *distp= dist;
    +  }
    +} /* findbest_test */
    +
    +/*---------------------------------
    +
    +  qh_findbestneighbor( facet, dist, mindist, maxdist )
    +    finds best neighbor (least dist) of a facet for merging
    +
    +  returns:
    +    returns min and max distances and their max absolute value
    +
    +  notes:
    +    error if qh_ASvoronoi
    +    avoids merging old into new
    +    assumes ridge->nonconvex only set on one ridge between a pair of facets
    +    could use an early out predicate but not worth it
    +
    +  design:
    +    if a large facet
    +      will test centrum
    +    else
    +      will test vertices
    +    if a large facet
    +      test nonconvex neighbors for best merge
    +    else
    +      test all neighbors for the best merge
    +    if testing centrum
    +      get distance information
    +*/
    +facetT *qh_findbestneighbor(facetT *facet, realT *distp, realT *mindistp, realT *maxdistp) {
    +  facetT *neighbor, **neighborp, *bestfacet= NULL;
    +  ridgeT *ridge, **ridgep;
    +  boolT nonconvex= True, testcentrum= False;
    +  int size= qh_setsize(facet->vertices);
    +
    +  if(qh CENTERtype==qh_ASvoronoi){
    +    qh_fprintf(qh ferr, 6272, "qhull error: cannot call qh_findbestneighor for f%d while qh.CENTERtype is qh_ASvoronoi\n", facet->id);
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  *distp= REALmax;
    +  if (size > qh_BESTcentrum2 * qh hull_dim + qh_BESTcentrum) {
    +    testcentrum= True;
    +    zinc_(Zbestcentrum);
    +    if (!facet->center)
    +       facet->center= qh_getcentrum(facet);
    +  }
    +  if (size > qh hull_dim + qh_BESTnonconvex) {
    +    FOREACHridge_(facet->ridges) {
    +      if (ridge->nonconvex) {
    +        neighbor= otherfacet_(ridge, facet);
    +        qh_findbest_test(testcentrum, facet, neighbor,
    +                          &bestfacet, distp, mindistp, maxdistp);
    +      }
    +    }
    +  }
    +  if (!bestfacet) {
    +    nonconvex= False;
    +    FOREACHneighbor_(facet)
    +      qh_findbest_test(testcentrum, facet, neighbor,
    +                        &bestfacet, distp, mindistp, maxdistp);
    +  }
    +  if (!bestfacet) {
    +    qh_fprintf(qh ferr, 6095, "qhull internal error (qh_findbestneighbor): no neighbors for f%d\n", facet->id);
    +
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  if (testcentrum)
    +    qh_getdistance(facet, bestfacet, mindistp, maxdistp);
    +  trace3((qh ferr, 3002, "qh_findbestneighbor: f%d is best neighbor for f%d testcentrum? %d nonconvex? %d dist %2.2g min %2.2g max %2.2g\n",
    +     bestfacet->id, facet->id, testcentrum, nonconvex, *distp, *mindistp, *maxdistp));
    +  return(bestfacet);
    +} /* findbestneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_flippedmerges( facetlist, wasmerge )
    +    merge flipped facets into best neighbor
    +    assumes qh.facet_mergeset at top of temporary stack
    +
    +  returns:
    +    no flipped facets on facetlist
    +    sets wasmerge if merge occurred
    +    degen/redundant merges passed through
    +
    +  notes:
    +    othermerges not needed since qh.facet_mergeset is empty before & after
    +      keep it in case of change
    +
    +  design:
    +    append flipped facets to qh.facetmergeset
    +    for each flipped merge
    +      find best neighbor
    +      merge facet into neighbor
    +      merge degenerate and redundant facets
    +    remove flipped merges from qh.facet_mergeset
    +*/
    +void qh_flippedmerges(facetT *facetlist, boolT *wasmerge) {
    +  facetT *facet, *neighbor, *facet1;
    +  realT dist, mindist, maxdist;
    +  mergeT *merge, **mergep;
    +  setT *othermerges;
    +  int nummerge=0;
    +
    +  trace4((qh ferr, 4024, "qh_flippedmerges: begin\n"));
    +  FORALLfacet_(facetlist) {
    +    if (facet->flipped && !facet->visible)
    +      qh_appendmergeset(facet, facet, MRGflip, NULL);
    +  }
    +  othermerges= qh_settemppop(); /* was facet_mergeset */
    +  qh facet_mergeset= qh_settemp(qh TEMPsize);
    +  qh_settemppush(othermerges);
    +  FOREACHmerge_(othermerges) {
    +    facet1= merge->facet1;
    +    if (merge->type != MRGflip || facet1->visible)
    +      continue;
    +    if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +      qhmem.IStracing= qh IStracing= qh TRACElevel;
    +    neighbor= qh_findbestneighbor(facet1, &dist, &mindist, &maxdist);
    +    trace0((qh ferr, 15, "qh_flippedmerges: merge flipped f%d into f%d dist %2.2g during p%d\n",
    +      facet1->id, neighbor->id, dist, qh furthest_id));
    +    qh_mergefacet(facet1, neighbor, &mindist, &maxdist, !qh_MERGEapex);
    +    nummerge++;
    +    if (qh PRINTstatistics) {
    +      zinc_(Zflipped);
    +      wadd_(Wflippedtot, dist);
    +      wmax_(Wflippedmax, dist);
    +    }
    +    qh_merge_degenredundant();
    +  }
    +  FOREACHmerge_(othermerges) {
    +    if (merge->facet1->visible || merge->facet2->visible)
    +      qh_memfree(merge, (int)sizeof(mergeT));
    +    else
    +      qh_setappend(&qh facet_mergeset, merge);
    +  }
    +  qh_settempfree(&othermerges);
    +  if (nummerge)
    +    *wasmerge= True;
    +  trace1((qh ferr, 1010, "qh_flippedmerges: merged %d flipped facets into a good neighbor\n", nummerge));
    +} /* flippedmerges */
    +
    +
    +/*---------------------------------
    +
    +  qh_forcedmerges( wasmerge )
    +    merge duplicated ridges
    +
    +  returns:
    +    removes all duplicate ridges on facet_mergeset
    +    wasmerge set if merge
    +    qh.facet_mergeset may include non-forced merges(none for now)
    +    qh.degen_mergeset includes degen/redun merges
    +
    +  notes:
    +    duplicate ridges occur when the horizon is pinched,
    +        i.e. a subridge occurs in more than two horizon ridges.
    +     could rename vertices that pinch the horizon
    +    assumes qh_merge_degenredundant() has not be called
    +    othermerges isn't needed since facet_mergeset is empty afterwards
    +      keep it in case of change
    +
    +  design:
    +    for each duplicate ridge
    +      find current facets by chasing f.replace links
    +      check for wide merge due to duplicate ridge
    +      determine best direction for facet
    +      merge one facet into the other
    +      remove duplicate ridges from qh.facet_mergeset
    +*/
    +void qh_forcedmerges(boolT *wasmerge) {
    +  facetT *facet1, *facet2;
    +  mergeT *merge, **mergep;
    +  realT dist1, dist2, mindist1, mindist2, maxdist1, maxdist2;
    +  setT *othermerges;
    +  int nummerge=0, numflip=0;
    +
    +  if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +    qhmem.IStracing= qh IStracing= qh TRACElevel;
    +  trace4((qh ferr, 4025, "qh_forcedmerges: begin\n"));
    +  othermerges= qh_settemppop(); /* was facet_mergeset */
    +  qh facet_mergeset= qh_settemp(qh TEMPsize);
    +  qh_settemppush(othermerges);
    +  FOREACHmerge_(othermerges) {
    +    if (merge->type != MRGridge)
    +        continue;
    +    if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +        qhmem.IStracing= qh IStracing= qh TRACElevel;
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    while (facet1->visible)      /* must exist, no qh_merge_degenredunant */
    +      facet1= facet1->f.replace; /* previously merged facet */
    +    while (facet2->visible)
    +      facet2= facet2->f.replace; /* previously merged facet */
    +    if (facet1 == facet2)
    +      continue;
    +    if (!qh_setin(facet2->neighbors, facet1)) {
    +      qh_fprintf(qh ferr, 6096, "qhull internal error (qh_forcedmerges): f%d and f%d had a duplicate ridge but as f%d and f%d they are no longer neighbors\n",
    +               merge->facet1->id, merge->facet2->id, facet1->id, facet2->id);
    +      qh_errexit2(qh_ERRqhull, facet1, facet2);
    +    }
    +    dist1= qh_getdistance(facet1, facet2, &mindist1, &maxdist1);
    +    dist2= qh_getdistance(facet2, facet1, &mindist2, &maxdist2);
    +    qh_check_dupridge(facet1, dist1, facet2, dist2);
    +    if (dist1 < dist2)
    +      qh_mergefacet(facet1, facet2, &mindist1, &maxdist1, !qh_MERGEapex);
    +    else {
    +      qh_mergefacet(facet2, facet1, &mindist2, &maxdist2, !qh_MERGEapex);
    +      dist1= dist2;
    +      facet1= facet2;
    +    }
    +    if (facet1->flipped) {
    +      zinc_(Zmergeflipdup);
    +      numflip++;
    +    }else
    +      nummerge++;
    +    if (qh PRINTstatistics) {
    +      zinc_(Zduplicate);
    +      wadd_(Wduplicatetot, dist1);
    +      wmax_(Wduplicatemax, dist1);
    +    }
    +  }
    +  FOREACHmerge_(othermerges) {
    +    if (merge->type == MRGridge)
    +      qh_memfree(merge, (int)sizeof(mergeT));
    +    else
    +      qh_setappend(&qh facet_mergeset, merge);
    +  }
    +  qh_settempfree(&othermerges);
    +  if (nummerge)
    +    *wasmerge= True;
    +  trace1((qh ferr, 1011, "qh_forcedmerges: merged %d facets and %d flipped facets across duplicated ridges\n",
    +                nummerge, numflip));
    +} /* forcedmerges */
    +
    +
    +/*---------------------------------
    +
    +  qh_getmergeset( facetlist )
    +    determines nonconvex facets on facetlist
    +    tests !tested ridges and nonconvex ridges of !tested facets
    +
    +  returns:
    +    returns sorted qh.facet_mergeset of facet-neighbor pairs to be merged
    +    all ridges tested
    +
    +  notes:
    +    assumes no nonconvex ridges with both facets tested
    +    uses facet->tested/ridge->tested to prevent duplicate tests
    +    can not limit tests to modified ridges since the centrum changed
    +    uses qh.visit_id
    +
    +  see:
    +    qh_getmergeset_initial()
    +
    +  design:
    +    for each facet on facetlist
    +      for each ridge of facet
    +        if untested ridge
    +          test ridge for convexity
    +          if non-convex
    +            append ridge to qh.facet_mergeset
    +    sort qh.facet_mergeset by angle
    +*/
    +void qh_getmergeset(facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int nummerges;
    +
    +  nummerges= qh_setsize(qh facet_mergeset);
    +  trace4((qh ferr, 4026, "qh_getmergeset: started.\n"));
    +  qh visit_id++;
    +  FORALLfacet_(facetlist) {
    +    if (facet->tested)
    +      continue;
    +    facet->visitid= qh visit_id;
    +    facet->tested= True;  /* must be non-simplicial due to merge */
    +    FOREACHneighbor_(facet)
    +      neighbor->seen= False;
    +    FOREACHridge_(facet->ridges) {
    +      if (ridge->tested && !ridge->nonconvex)
    +        continue;
    +      /* if tested & nonconvex, need to append merge */
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->seen) {
    +        ridge->tested= True;
    +        ridge->nonconvex= False;
    +      }else if (neighbor->visitid != qh visit_id) {
    +        ridge->tested= True;
    +        ridge->nonconvex= False;
    +        neighbor->seen= True;      /* only one ridge is marked nonconvex */
    +        if (qh_test_appendmerge(facet, neighbor))
    +          ridge->nonconvex= True;
    +      }
    +    }
    +  }
    +  nummerges= qh_setsize(qh facet_mergeset);
    +  if (qh ANGLEmerge)
    +    qsort(SETaddr_(qh facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compareangle);
    +  else
    +    qsort(SETaddr_(qh facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_comparemerge);
    +  if (qh POSTmerging) {
    +    zadd_(Zmergesettot2, nummerges);
    +  }else {
    +    zadd_(Zmergesettot, nummerges);
    +    zmax_(Zmergesetmax, nummerges);
    +  }
    +  trace2((qh ferr, 2021, "qh_getmergeset: %d merges found\n", nummerges));
    +} /* getmergeset */
    +
    +
    +/*---------------------------------
    +
    +  qh_getmergeset_initial( facetlist )
    +    determine initial qh.facet_mergeset for facets
    +    tests all facet/neighbor pairs on facetlist
    +
    +  returns:
    +    sorted qh.facet_mergeset with nonconvex ridges
    +    sets facet->tested, ridge->tested, and ridge->nonconvex
    +
    +  notes:
    +    uses visit_id, assumes ridge->nonconvex is False
    +
    +  see:
    +    qh_getmergeset()
    +
    +  design:
    +    for each facet on facetlist
    +      for each untested neighbor of facet
    +        test facet and neighbor for convexity
    +        if non-convex
    +          append merge to qh.facet_mergeset
    +          mark one of the ridges as nonconvex
    +    sort qh.facet_mergeset by angle
    +*/
    +void qh_getmergeset_initial(facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int nummerges;
    +
    +  qh visit_id++;
    +  FORALLfacet_(facetlist) {
    +    facet->visitid= qh visit_id;
    +    facet->tested= True;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh visit_id) {
    +        if (qh_test_appendmerge(facet, neighbor)) {
    +          FOREACHridge_(neighbor->ridges) {
    +            if (facet == otherfacet_(ridge, neighbor)) {
    +              ridge->nonconvex= True;
    +              break;    /* only one ridge is marked nonconvex */
    +            }
    +          }
    +        }
    +      }
    +    }
    +    FOREACHridge_(facet->ridges)
    +      ridge->tested= True;
    +  }
    +  nummerges= qh_setsize(qh facet_mergeset);
    +  if (qh ANGLEmerge)
    +    qsort(SETaddr_(qh facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compareangle);
    +  else
    +    qsort(SETaddr_(qh facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_comparemerge);
    +  if (qh POSTmerging) {
    +    zadd_(Zmergeinittot2, nummerges);
    +  }else {
    +    zadd_(Zmergeinittot, nummerges);
    +    zmax_(Zmergeinitmax, nummerges);
    +  }
    +  trace2((qh ferr, 2022, "qh_getmergeset_initial: %d merges found\n", nummerges));
    +} /* getmergeset_initial */
    +
    +
    +/*---------------------------------
    +
    +  qh_hashridge( hashtable, hashsize, ridge, oldvertex )
    +    add ridge to hashtable without oldvertex
    +
    +  notes:
    +    assumes hashtable is large enough
    +
    +  design:
    +    determine hash value for ridge without oldvertex
    +    find next empty slot for ridge
    +*/
    +void qh_hashridge(setT *hashtable, int hashsize, ridgeT *ridge, vertexT *oldvertex) {
    +  int hash;
    +  ridgeT *ridgeA;
    +
    +  hash= qh_gethash(hashsize, ridge->vertices, qh hull_dim-1, 0, oldvertex);
    +  while (True) {
    +    if (!(ridgeA= SETelemt_(hashtable, hash, ridgeT))) {
    +      SETelem_(hashtable, hash)= ridge;
    +      break;
    +    }else if (ridgeA == ridge)
    +      break;
    +    if (++hash == hashsize)
    +      hash= 0;
    +  }
    +} /* hashridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_hashridge_find( hashtable, hashsize, ridge, vertex, oldvertex, hashslot )
    +    returns matching ridge without oldvertex in hashtable
    +      for ridge without vertex
    +    if oldvertex is NULL
    +      matches with any one skip
    +
    +  returns:
    +    matching ridge or NULL
    +    if no match,
    +      if ridge already in   table
    +        hashslot= -1
    +      else
    +        hashslot= next NULL index
    +
    +  notes:
    +    assumes hashtable is large enough
    +    can't match ridge to itself
    +
    +  design:
    +    get hash value for ridge without vertex
    +    for each hashslot
    +      return match if ridge matches ridgeA without oldvertex
    +*/
    +ridgeT *qh_hashridge_find(setT *hashtable, int hashsize, ridgeT *ridge,
    +              vertexT *vertex, vertexT *oldvertex, int *hashslot) {
    +  int hash;
    +  ridgeT *ridgeA;
    +
    +  *hashslot= 0;
    +  zinc_(Zhashridge);
    +  hash= qh_gethash(hashsize, ridge->vertices, qh hull_dim-1, 0, vertex);
    +  while ((ridgeA= SETelemt_(hashtable, hash, ridgeT))) {
    +    if (ridgeA == ridge)
    +      *hashslot= -1;
    +    else {
    +      zinc_(Zhashridgetest);
    +      if (qh_setequal_except(ridge->vertices, vertex, ridgeA->vertices, oldvertex))
    +        return ridgeA;
    +    }
    +    if (++hash == hashsize)
    +      hash= 0;
    +  }
    +  if (!*hashslot)
    +    *hashslot= hash;
    +  return NULL;
    +} /* hashridge_find */
    +
    +
    +/*---------------------------------
    +
    +  qh_makeridges( facet )
    +    creates explicit ridges between simplicial facets
    +
    +  returns:
    +    facet with ridges and without qh_MERGEridge
    +    ->simplicial is False
    +
    +  notes:
    +    allows qh_MERGEridge flag
    +    uses existing ridges
    +    duplicate neighbors ok if ridges already exist (qh_mergecycle_ridges)
    +
    +  see:
    +    qh_mergecycle_ridges()
    +
    +  design:
    +    look for qh_MERGEridge neighbors
    +    mark neighbors that already have ridges
    +    for each unprocessed neighbor of facet
    +      create a ridge for neighbor and facet
    +    if any qh_MERGEridge neighbors
    +      delete qh_MERGEridge flags (already handled by qh_mark_dupridges)
    +*/
    +void qh_makeridges(facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int neighbor_i, neighbor_n;
    +  boolT toporient, mergeridge= False;
    +
    +  if (!facet->simplicial)
    +    return;
    +  trace4((qh ferr, 4027, "qh_makeridges: make ridges for f%d\n", facet->id));
    +  facet->simplicial= False;
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      mergeridge= True;
    +    else
    +      neighbor->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges)
    +    otherfacet_(ridge, facet)->seen= True;
    +  FOREACHneighbor_i_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      continue;  /* fixed by qh_mark_dupridges */
    +    else if (!neighbor->seen) {  /* no current ridges */
    +      ridge= qh_newridge();
    +      ridge->vertices= qh_setnew_delnthsorted(facet->vertices, qh hull_dim,
    +                                                          neighbor_i, 0);
    +      toporient= facet->toporient ^ (neighbor_i & 0x1);
    +      if (toporient) {
    +        ridge->top= facet;
    +        ridge->bottom= neighbor;
    +      }else {
    +        ridge->top= neighbor;
    +        ridge->bottom= facet;
    +      }
    +#if 0 /* this also works */
    +      flip= (facet->toporient ^ neighbor->toporient)^(skip1 & 0x1) ^ (skip2 & 0x1);
    +      if (facet->toporient ^ (skip1 & 0x1) ^ flip) {
    +        ridge->top= neighbor;
    +        ridge->bottom= facet;
    +      }else {
    +        ridge->top= facet;
    +        ridge->bottom= neighbor;
    +      }
    +#endif
    +      qh_setappend(&(facet->ridges), ridge);
    +      qh_setappend(&(neighbor->ridges), ridge);
    +    }
    +  }
    +  if (mergeridge) {
    +    while (qh_setdel(facet->neighbors, qh_MERGEridge))
    +      ; /* delete each one */
    +  }
    +} /* makeridges */
    +
    +
    +/*---------------------------------
    +
    +  qh_mark_dupridges( facetlist )
    +    add duplicated ridges to qh.facet_mergeset
    +    facet->dupridge is true
    +
    +  returns:
    +    duplicate ridges on qh.facet_mergeset
    +    ->mergeridge/->mergeridge2 set
    +    duplicate ridges marked by qh_MERGEridge and both sides facet->dupridge
    +    no MERGEridges in neighbor sets
    +
    +  notes:
    +    duplicate ridges occur when the horizon is pinched,
    +        i.e. a subridge occurs in more than two horizon ridges.
    +    could rename vertices that pinch the horizon (thus removing subridge)
    +    uses qh.visit_id
    +
    +  design:
    +    for all facets on facetlist
    +      if facet contains a duplicate ridge
    +        for each neighbor of facet
    +          if neighbor marked qh_MERGEridge (one side of the merge)
    +            set facet->mergeridge
    +          else
    +            if neighbor contains a duplicate ridge
    +            and the back link is qh_MERGEridge
    +              append duplicate ridge to qh.facet_mergeset
    +   for each duplicate ridge
    +     make ridge sets in preparation for merging
    +     remove qh_MERGEridge from neighbor set
    +   for each duplicate ridge
    +     restore the missing neighbor from the neighbor set that was qh_MERGEridge
    +     add the missing ridge for this neighbor
    +*/
    +void qh_mark_dupridges(facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  int nummerge=0;
    +  mergeT *merge, **mergep;
    +
    +
    +  trace4((qh ferr, 4028, "qh_mark_dupridges: identify duplicate ridges\n"));
    +  FORALLfacet_(facetlist) {
    +    if (facet->dupridge) {
    +      FOREACHneighbor_(facet) {
    +        if (neighbor == qh_MERGEridge) {
    +          facet->mergeridge= True;
    +          continue;
    +        }
    +        if (neighbor->dupridge
    +        && !qh_setin(neighbor->neighbors, facet)) { /* qh_MERGEridge */
    +          qh_appendmergeset(facet, neighbor, MRGridge, NULL);
    +          facet->mergeridge2= True;
    +          facet->mergeridge= True;
    +          nummerge++;
    +        }
    +      }
    +    }
    +  }
    +  if (!nummerge)
    +    return;
    +  FORALLfacet_(facetlist) {            /* gets rid of qh_MERGEridge */
    +    if (facet->mergeridge && !facet->mergeridge2)
    +      qh_makeridges(facet);
    +  }
    +  FOREACHmerge_(qh facet_mergeset) {   /* restore the missing neighbors */
    +    if (merge->type == MRGridge) {
    +      qh_setappend(&merge->facet2->neighbors, merge->facet1);
    +      qh_makeridges(merge->facet1);   /* and the missing ridges */
    +    }
    +  }
    +  trace1((qh ferr, 1012, "qh_mark_dupridges: found %d duplicated ridges\n",
    +                nummerge));
    +} /* mark_dupridges */
    +
    +/*---------------------------------
    +
    +  qh_maydropneighbor( facet )
    +    drop neighbor relationship if no ridge between facet and neighbor
    +
    +  returns:
    +    neighbor sets updated
    +    appends degenerate facets to qh.facet_mergeset
    +
    +  notes:
    +    won't cause redundant facets since vertex inclusion is the same
    +    may drop vertex and neighbor if no ridge
    +    uses qh.visit_id
    +
    +  design:
    +    visit all neighbors with ridges
    +    for each unvisited neighbor of facet
    +      delete neighbor and facet from the neighbor sets
    +      if neighbor becomes degenerate
    +        append neighbor to qh.degen_mergeset
    +    if facet is degenerate
    +      append facet to qh.degen_mergeset
    +*/
    +void qh_maydropneighbor(facetT *facet) {
    +  ridgeT *ridge, **ridgep;
    +  realT angledegen= qh_ANGLEdegen;
    +  facetT *neighbor, **neighborp;
    +
    +  qh visit_id++;
    +  trace4((qh ferr, 4029, "qh_maydropneighbor: test f%d for no ridges to a neighbor\n",
    +          facet->id));
    +  FOREACHridge_(facet->ridges) {
    +    ridge->top->visitid= qh visit_id;
    +    ridge->bottom->visitid= qh visit_id;
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (neighbor->visitid != qh visit_id) {
    +      trace0((qh ferr, 17, "qh_maydropneighbor: facets f%d and f%d are no longer neighbors during p%d\n",
    +            facet->id, neighbor->id, qh furthest_id));
    +      zinc_(Zdropneighbor);
    +      qh_setdel(facet->neighbors, neighbor);
    +      neighborp--;  /* repeat, deleted a neighbor */
    +      qh_setdel(neighbor->neighbors, facet);
    +      if (qh_setsize(neighbor->neighbors) < qh hull_dim) {
    +        zinc_(Zdropdegen);
    +        qh_appendmergeset(neighbor, neighbor, MRGdegen, &angledegen);
    +        trace2((qh ferr, 2023, "qh_maydropneighbors: f%d is degenerate.\n", neighbor->id));
    +      }
    +    }
    +  }
    +  if (qh_setsize(facet->neighbors) < qh hull_dim) {
    +    zinc_(Zdropdegen);
    +    qh_appendmergeset(facet, facet, MRGdegen, &angledegen);
    +    trace2((qh ferr, 2024, "qh_maydropneighbors: f%d is degenerate.\n", facet->id));
    +  }
    +} /* maydropneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_merge_degenredundant()
    +    merge all degenerate and redundant facets
    +    qh.degen_mergeset contains merges from qh_degen_redundant_neighbors()
    +
    +  returns:
    +    number of merges performed
    +    resets facet->degenerate/redundant
    +    if deleted (visible) facet has no neighbors
    +      sets ->f.replace to NULL
    +
    +  notes:
    +    redundant merges happen before degenerate ones
    +    merging and renaming vertices can result in degen/redundant facets
    +
    +  design:
    +    for each merge on qh.degen_mergeset
    +      if redundant merge
    +        if non-redundant facet merged into redundant facet
    +          recheck facet for redundancy
    +        else
    +          merge redundant facet into other facet
    +*/
    +int qh_merge_degenredundant(void) {
    +  int size;
    +  mergeT *merge;
    +  facetT *bestneighbor, *facet1, *facet2;
    +  realT dist, mindist, maxdist;
    +  vertexT *vertex, **vertexp;
    +  int nummerges= 0;
    +  mergeType mergetype;
    +
    +  while ((merge= (mergeT*)qh_setdellast(qh degen_mergeset))) {
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    mergetype= merge->type;
    +    qh_memfree(merge, (int)sizeof(mergeT));
    +    if (facet1->visible)
    +      continue;
    +    facet1->degenerate= False;
    +    facet1->redundant= False;
    +    if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +      qhmem.IStracing= qh IStracing= qh TRACElevel;
    +    if (mergetype == MRGredundant) {
    +      zinc_(Zneighbor);
    +      while (facet2->visible) {
    +        if (!facet2->f.replace) {
    +          qh_fprintf(qh ferr, 6097, "qhull internal error (qh_merge_degenredunant): f%d redundant but f%d has no replacement\n",
    +               facet1->id, facet2->id);
    +          qh_errexit2(qh_ERRqhull, facet1, facet2);
    +        }
    +        facet2= facet2->f.replace;
    +      }
    +      if (facet1 == facet2) {
    +        qh_degen_redundant_facet(facet1); /* in case of others */
    +        continue;
    +      }
    +      trace2((qh ferr, 2025, "qh_merge_degenredundant: facet f%d is contained in f%d, will merge\n",
    +            facet1->id, facet2->id));
    +      qh_mergefacet(facet1, facet2, NULL, NULL, !qh_MERGEapex);
    +      /* merge distance is already accounted for */
    +      nummerges++;
    +    }else {  /* mergetype == MRGdegen, other merges may have fixed */
    +      if (!(size= qh_setsize(facet1->neighbors))) {
    +        zinc_(Zdelfacetdup);
    +        trace2((qh ferr, 2026, "qh_merge_degenredundant: facet f%d has no neighbors.  Deleted\n", facet1->id));
    +        qh_willdelete(facet1, NULL);
    +        FOREACHvertex_(facet1->vertices) {
    +          qh_setdel(vertex->neighbors, facet1);
    +          if (!SETfirst_(vertex->neighbors)) {
    +            zinc_(Zdegenvertex);
    +            trace2((qh ferr, 2027, "qh_merge_degenredundant: deleted v%d because f%d has no neighbors\n",
    +                 vertex->id, facet1->id));
    +            vertex->deleted= True;
    +            qh_setappend(&qh del_vertices, vertex);
    +          }
    +        }
    +        nummerges++;
    +      }else if (size < qh hull_dim) {
    +        bestneighbor= qh_findbestneighbor(facet1, &dist, &mindist, &maxdist);
    +        trace2((qh ferr, 2028, "qh_merge_degenredundant: facet f%d has %d neighbors, merge into f%d dist %2.2g\n",
    +              facet1->id, size, bestneighbor->id, dist));
    +        qh_mergefacet(facet1, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +        nummerges++;
    +        if (qh PRINTstatistics) {
    +          zinc_(Zdegen);
    +          wadd_(Wdegentot, dist);
    +          wmax_(Wdegenmax, dist);
    +        }
    +      } /* else, another merge fixed the degeneracy and redundancy tested */
    +    }
    +  }
    +  return nummerges;
    +} /* merge_degenredundant */
    +
    +/*---------------------------------
    +
    +  qh_merge_nonconvex( facet1, facet2, mergetype )
    +    remove non-convex ridge between facet1 into facet2
    +    mergetype gives why the facet's are non-convex
    +
    +  returns:
    +    merges one of the facets into the best neighbor
    +
    +  design:
    +    if one of the facets is a new facet
    +      prefer merging new facet into old facet
    +    find best neighbors for both facets
    +    merge the nearest facet into its best neighbor
    +    update the statistics
    +*/
    +void qh_merge_nonconvex(facetT *facet1, facetT *facet2, mergeType mergetype) {
    +  facetT *bestfacet, *bestneighbor, *neighbor;
    +  realT dist, dist2, mindist, mindist2, maxdist, maxdist2;
    +
    +  if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +    qhmem.IStracing= qh IStracing= qh TRACElevel;
    +  trace3((qh ferr, 3003, "qh_merge_nonconvex: merge #%d for f%d and f%d type %d\n",
    +      zzval_(Ztotmerge) + 1, facet1->id, facet2->id, mergetype));
    +  /* concave or coplanar */
    +  if (!facet1->newfacet) {
    +    bestfacet= facet2;   /* avoid merging old facet if new is ok */
    +    facet2= facet1;
    +    facet1= bestfacet;
    +  }else
    +    bestfacet= facet1;
    +  bestneighbor= qh_findbestneighbor(bestfacet, &dist, &mindist, &maxdist);
    +  neighbor= qh_findbestneighbor(facet2, &dist2, &mindist2, &maxdist2);
    +  if (dist < dist2) {
    +    qh_mergefacet(bestfacet, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +  }else if (qh AVOIDold && !facet2->newfacet
    +  && ((mindist >= -qh MAXcoplanar && maxdist <= qh max_outside)
    +       || dist * 1.5 < dist2)) {
    +    zinc_(Zavoidold);
    +    wadd_(Wavoidoldtot, dist);
    +    wmax_(Wavoidoldmax, dist);
    +    trace2((qh ferr, 2029, "qh_merge_nonconvex: avoid merging old facet f%d dist %2.2g.  Use f%d dist %2.2g instead\n",
    +           facet2->id, dist2, facet1->id, dist2));
    +    qh_mergefacet(bestfacet, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +  }else {
    +    qh_mergefacet(facet2, neighbor, &mindist2, &maxdist2, !qh_MERGEapex);
    +    dist= dist2;
    +  }
    +  if (qh PRINTstatistics) {
    +    if (mergetype == MRGanglecoplanar) {
    +      zinc_(Zacoplanar);
    +      wadd_(Wacoplanartot, dist);
    +      wmax_(Wacoplanarmax, dist);
    +    }else if (mergetype == MRGconcave) {
    +      zinc_(Zconcave);
    +      wadd_(Wconcavetot, dist);
    +      wmax_(Wconcavemax, dist);
    +    }else { /* MRGcoplanar */
    +      zinc_(Zcoplanar);
    +      wadd_(Wcoplanartot, dist);
    +      wmax_(Wcoplanarmax, dist);
    +    }
    +  }
    +} /* merge_nonconvex */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle( samecycle, newfacet )
    +    merge a cycle of facets starting at samecycle into a newfacet
    +    newfacet is a horizon facet with ->normal
    +    samecycle facets are simplicial from an apex
    +
    +  returns:
    +    initializes vertex neighbors on first merge
    +    samecycle deleted (placed on qh.visible_list)
    +    newfacet at end of qh.facet_list
    +    deleted vertices on qh.del_vertices
    +
    +  see:
    +    qh_mergefacet()
    +    called by qh_mergecycle_all() for multiple, same cycle facets
    +
    +  design:
    +    make vertex neighbors if necessary
    +    make ridges for newfacet
    +    merge neighbor sets of samecycle into newfacet
    +    merge ridges of samecycle into newfacet
    +    merge vertex neighbors of samecycle into newfacet
    +    make apex of samecycle the apex of newfacet
    +    if newfacet wasn't a new facet
    +      add its vertices to qh.newvertex_list
    +    delete samecycle facets a make newfacet a newfacet
    +*/
    +void qh_mergecycle(facetT *samecycle, facetT *newfacet) {
    +  int traceonce= False, tracerestore= 0;
    +  vertexT *apex;
    +#ifndef qh_NOtrace
    +  facetT *same;
    +#endif
    +
    +  if (newfacet->tricoplanar) {
    +    if (!qh TRInormals) {
    +      qh_fprintf(qh ferr, 6224, "Qhull internal error (qh_mergecycle): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit(qh_ERRqhull, newfacet, NULL);
    +    }
    +    newfacet->tricoplanar= False;
    +    newfacet->keepcentrum= False;
    +  }
    +  if (!qh VERTEXneighbors)
    +    qh_vertexneighbors();
    +  zzinc_(Ztotmerge);
    +  if (qh REPORTfreq2 && qh POSTmerging) {
    +    if (zzval_(Ztotmerge) > qh mergereport + qh REPORTfreq2)
    +      qh_tracemerging();
    +  }
    +#ifndef qh_NOtrace
    +  if (qh TRACEmerge == zzval_(Ztotmerge))
    +    qhmem.IStracing= qh IStracing= qh TRACElevel;
    +  trace2((qh ferr, 2030, "qh_mergecycle: merge #%d for facets from cycle f%d into coplanar horizon f%d\n",
    +        zzval_(Ztotmerge), samecycle->id, newfacet->id));
    +  if (newfacet == qh tracefacet) {
    +    tracerestore= qh IStracing;
    +    qh IStracing= 4;
    +    qh_fprintf(qh ferr, 8068, "qh_mergecycle: ========= trace merge %d of samecycle %d into trace f%d, furthest is p%d\n",
    +               zzval_(Ztotmerge), samecycle->id, newfacet->id,  qh furthest_id);
    +    traceonce= True;
    +  }
    +  if (qh IStracing >=4) {
    +    qh_fprintf(qh ferr, 8069, "  same cycle:");
    +    FORALLsame_cycle_(samecycle)
    +      qh_fprintf(qh ferr, 8070, " f%d", same->id);
    +    qh_fprintf(qh ferr, 8071, "\n");
    +  }
    +  if (qh IStracing >=4)
    +    qh_errprint("MERGING CYCLE", samecycle, newfacet, NULL, NULL);
    +#endif /* !qh_NOtrace */
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  qh_makeridges(newfacet);
    +  qh_mergecycle_neighbors(samecycle, newfacet);
    +  qh_mergecycle_ridges(samecycle, newfacet);
    +  qh_mergecycle_vneighbors(samecycle, newfacet);
    +  if (SETfirstt_(newfacet->vertices, vertexT) != apex)
    +    qh_setaddnth(&newfacet->vertices, 0, apex);  /* apex has last id */
    +  if (!newfacet->newfacet)
    +    qh_newvertices(newfacet->vertices);
    +  qh_mergecycle_facets(samecycle, newfacet);
    +  qh_tracemerge(samecycle, newfacet);
    +  /* check for degen_redundant_neighbors after qh_forcedmerges() */
    +  if (traceonce) {
    +    qh_fprintf(qh ferr, 8072, "qh_mergecycle: end of trace facet\n");
    +    qh IStracing= tracerestore;
    +  }
    +} /* mergecycle */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_all( facetlist, wasmerge )
    +    merge all samecycles of coplanar facets into horizon
    +    don't merge facets with ->mergeridge (these already have ->normal)
    +    all facets are simplicial from apex
    +    all facet->cycledone == False
    +
    +  returns:
    +    all newfacets merged into coplanar horizon facets
    +    deleted vertices on  qh.del_vertices
    +    sets wasmerge if any merge
    +
    +  see:
    +    calls qh_mergecycle for multiple, same cycle facets
    +
    +  design:
    +    for each facet on facetlist
    +      skip facets with duplicate ridges and normals
    +      check that facet is in a samecycle (->mergehorizon)
    +      if facet only member of samecycle
    +        sets vertex->delridge for all vertices except apex
    +        merge facet into horizon
    +      else
    +        mark all facets in samecycle
    +        remove facets with duplicate ridges from samecycle
    +        merge samecycle into horizon (deletes facets from facetlist)
    +*/
    +void qh_mergecycle_all(facetT *facetlist, boolT *wasmerge) {
    +  facetT *facet, *same, *prev, *horizon;
    +  facetT *samecycle= NULL, *nextfacet, *nextsame;
    +  vertexT *apex, *vertex, **vertexp;
    +  int cycles=0, total=0, facets, nummerge;
    +
    +  trace2((qh ferr, 2031, "qh_mergecycle_all: begin\n"));
    +  for (facet= facetlist; facet && (nextfacet= facet->next); facet= nextfacet) {
    +    if (facet->normal)
    +      continue;
    +    if (!facet->mergehorizon) {
    +      qh_fprintf(qh ferr, 6225, "Qhull internal error (qh_mergecycle_all): f%d without normal\n", facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +    horizon= SETfirstt_(facet->neighbors, facetT);
    +    if (facet->f.samecycle == facet) {
    +      zinc_(Zonehorizon);
    +      /* merge distance done in qh_findhorizon */
    +      apex= SETfirstt_(facet->vertices, vertexT);
    +      FOREACHvertex_(facet->vertices) {
    +        if (vertex != apex)
    +          vertex->delridge= True;
    +      }
    +      horizon->f.newcycle= NULL;
    +      qh_mergefacet(facet, horizon, NULL, NULL, qh_MERGEapex);
    +    }else {
    +      samecycle= facet;
    +      facets= 0;
    +      prev= facet;
    +      for (same= facet->f.samecycle; same;  /* FORALLsame_cycle_(facet) */
    +           same= (same == facet ? NULL :nextsame)) { /* ends at facet */
    +        nextsame= same->f.samecycle;
    +        if (same->cycledone || same->visible)
    +          qh_infiniteloop(same);
    +        same->cycledone= True;
    +        if (same->normal) {
    +          prev->f.samecycle= same->f.samecycle; /* unlink ->mergeridge */
    +          same->f.samecycle= NULL;
    +        }else {
    +          prev= same;
    +          facets++;
    +        }
    +      }
    +      while (nextfacet && nextfacet->cycledone)  /* will delete samecycle */
    +        nextfacet= nextfacet->next;
    +      horizon->f.newcycle= NULL;
    +      qh_mergecycle(samecycle, horizon);
    +      nummerge= horizon->nummerge + facets;
    +      if (nummerge > qh_MAXnummerge)
    +        horizon->nummerge= qh_MAXnummerge;
    +      else
    +        horizon->nummerge= (short unsigned int)nummerge;
    +      zzinc_(Zcyclehorizon);
    +      total += facets;
    +      zzadd_(Zcyclefacettot, facets);
    +      zmax_(Zcyclefacetmax, facets);
    +    }
    +    cycles++;
    +  }
    +  if (cycles)
    +    *wasmerge= True;
    +  trace1((qh ferr, 1013, "qh_mergecycle_all: merged %d same cycles or facets into coplanar horizons\n", cycles));
    +} /* mergecycle_all */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_facets( samecycle, newfacet )
    +    finish merge of samecycle into newfacet
    +
    +  returns:
    +    samecycle prepended to visible_list for later deletion and partitioning
    +      each facet->f.replace == newfacet
    +
    +    newfacet moved to end of qh.facet_list
    +      makes newfacet a newfacet (get's facet1->id if it was old)
    +      sets newfacet->newmerge
    +      clears newfacet->center (unless merging into a large facet)
    +      clears newfacet->tested and ridge->tested for facet1
    +
    +    adds neighboring facets to facet_mergeset if redundant or degenerate
    +
    +  design:
    +    make newfacet a new facet and set its flags
    +    move samecycle facets to qh.visible_list for later deletion
    +    unless newfacet is large
    +      remove its centrum
    +*/
    +void qh_mergecycle_facets(facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *next;
    +
    +  trace4((qh ferr, 4030, "qh_mergecycle_facets: make newfacet new and samecycle deleted\n"));
    +  qh_removefacet(newfacet);  /* append as a newfacet to end of qh facet_list */
    +  qh_appendfacet(newfacet);
    +  newfacet->newfacet= True;
    +  newfacet->simplicial= False;
    +  newfacet->newmerge= True;
    +
    +  for (same= samecycle->f.samecycle; same; same= (same == samecycle ?  NULL : next)) {
    +    next= same->f.samecycle;  /* reused by willdelete */
    +    qh_willdelete(same, newfacet);
    +  }
    +  if (newfacet->center
    +      && qh_setsize(newfacet->vertices) <= qh hull_dim + qh_MAXnewcentrum) {
    +    qh_memfree(newfacet->center, qh normal_size);
    +    newfacet->center= NULL;
    +  }
    +  trace3((qh ferr, 3004, "qh_mergecycle_facets: merged facets from cycle f%d into f%d\n",
    +             samecycle->id, newfacet->id));
    +} /* mergecycle_facets */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_neighbors( samecycle, newfacet )
    +    add neighbors for samecycle facets to newfacet
    +
    +  returns:
    +    newfacet with updated neighbors and vice-versa
    +    newfacet has ridges
    +    all neighbors of newfacet marked with qh.visit_id
    +    samecycle facets marked with qh.visit_id-1
    +    ridges updated for simplicial neighbors of samecycle with a ridge
    +
    +  notes:
    +    assumes newfacet not in samecycle
    +    usually, samecycle facets are new, simplicial facets without internal ridges
    +      not so if horizon facet is coplanar to two different samecycles
    +
    +  see:
    +    qh_mergeneighbors()
    +
    +  design:
    +    check samecycle
    +    delete neighbors from newfacet that are also in samecycle
    +    for each neighbor of a facet in samecycle
    +      if neighbor is simplicial
    +        if first visit
    +          move the neighbor relation to newfacet
    +          update facet links for its ridges
    +        else
    +          make ridges for neighbor
    +          remove samecycle reference
    +      else
    +        update neighbor sets
    +*/
    +void qh_mergecycle_neighbors(facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *neighbor, **neighborp;
    +  int delneighbors= 0, newneighbors= 0;
    +  unsigned int samevisitid;
    +  ridgeT *ridge, **ridgep;
    +
    +  samevisitid= ++qh visit_id;
    +  FORALLsame_cycle_(samecycle) {
    +    if (same->visitid == samevisitid || same->visible)
    +      qh_infiniteloop(samecycle);
    +    same->visitid= samevisitid;
    +  }
    +  newfacet->visitid= ++qh visit_id;
    +  trace4((qh ferr, 4031, "qh_mergecycle_neighbors: delete shared neighbors from newfacet\n"));
    +  FOREACHneighbor_(newfacet) {
    +    if (neighbor->visitid == samevisitid) {
    +      SETref_(neighbor)= NULL;  /* samecycle neighbors deleted */
    +      delneighbors++;
    +    }else
    +      neighbor->visitid= qh visit_id;
    +  }
    +  qh_setcompact(newfacet->neighbors);
    +
    +  trace4((qh ferr, 4032, "qh_mergecycle_neighbors: update neighbors\n"));
    +  FORALLsame_cycle_(samecycle) {
    +    FOREACHneighbor_(same) {
    +      if (neighbor->visitid == samevisitid)
    +        continue;
    +      if (neighbor->simplicial) {
    +        if (neighbor->visitid != qh visit_id) {
    +          qh_setappend(&newfacet->neighbors, neighbor);
    +          qh_setreplace(neighbor->neighbors, same, newfacet);
    +          newneighbors++;
    +          neighbor->visitid= qh visit_id;
    +          FOREACHridge_(neighbor->ridges) { /* update ridge in case of qh_makeridges */
    +            if (ridge->top == same) {
    +              ridge->top= newfacet;
    +              break;
    +            }else if (ridge->bottom == same) {
    +              ridge->bottom= newfacet;
    +              break;
    +            }
    +          }
    +        }else {
    +          qh_makeridges(neighbor);
    +          qh_setdel(neighbor->neighbors, same);
    +          /* same can't be horizon facet for neighbor */
    +        }
    +      }else { /* non-simplicial neighbor */
    +        qh_setdel(neighbor->neighbors, same);
    +        if (neighbor->visitid != qh visit_id) {
    +          qh_setappend(&neighbor->neighbors, newfacet);
    +          qh_setappend(&newfacet->neighbors, neighbor);
    +          neighbor->visitid= qh visit_id;
    +          newneighbors++;
    +        }
    +      }
    +    }
    +  }
    +  trace2((qh ferr, 2032, "qh_mergecycle_neighbors: deleted %d neighbors and added %d\n",
    +             delneighbors, newneighbors));
    +} /* mergecycle_neighbors */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_ridges( samecycle, newfacet )
    +    add ridges/neighbors for facets in samecycle to newfacet
    +    all new/old neighbors of newfacet marked with qh.visit_id
    +    facets in samecycle marked with qh.visit_id-1
    +    newfacet marked with qh.visit_id
    +
    +  returns:
    +    newfacet has merged ridges
    +
    +  notes:
    +    ridge already updated for simplicial neighbors of samecycle with a ridge
    +
    +  see:
    +    qh_mergeridges()
    +    qh_makeridges()
    +
    +  design:
    +    remove ridges between newfacet and samecycle
    +    for each facet in samecycle
    +      for each ridge in facet
    +        update facet pointers in ridge
    +        skip ridges processed in qh_mergecycle_neighors
    +        free ridges between newfacet and samecycle
    +        free ridges between facets of samecycle (on 2nd visit)
    +        append remaining ridges to newfacet
    +      if simpilicial facet
    +        for each neighbor of facet
    +          if simplicial facet
    +          and not samecycle facet or newfacet
    +            make ridge between neighbor and newfacet
    +*/
    +void qh_mergecycle_ridges(facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *neighbor= NULL;
    +  int numold=0, numnew=0;
    +  int neighbor_i, neighbor_n;
    +  unsigned int samevisitid;
    +  ridgeT *ridge, **ridgep;
    +  boolT toporient;
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  trace4((qh ferr, 4033, "qh_mergecycle_ridges: delete shared ridges from newfacet\n"));
    +  samevisitid= qh visit_id -1;
    +  FOREACHridge_(newfacet->ridges) {
    +    neighbor= otherfacet_(ridge, newfacet);
    +    if (neighbor->visitid == samevisitid)
    +      SETref_(ridge)= NULL; /* ridge free'd below */
    +  }
    +  qh_setcompact(newfacet->ridges);
    +
    +  trace4((qh ferr, 4034, "qh_mergecycle_ridges: add ridges to newfacet\n"));
    +  FORALLsame_cycle_(samecycle) {
    +    FOREACHridge_(same->ridges) {
    +      if (ridge->top == same) {
    +        ridge->top= newfacet;
    +        neighbor= ridge->bottom;
    +      }else if (ridge->bottom == same) {
    +        ridge->bottom= newfacet;
    +        neighbor= ridge->top;
    +      }else if (ridge->top == newfacet || ridge->bottom == newfacet) {
    +        qh_setappend(&newfacet->ridges, ridge);
    +        numold++;  /* already set by qh_mergecycle_neighbors */
    +        continue;
    +      }else {
    +        qh_fprintf(qh ferr, 6098, "qhull internal error (qh_mergecycle_ridges): bad ridge r%d\n", ridge->id);
    +        qh_errexit(qh_ERRqhull, NULL, ridge);
    +      }
    +      if (neighbor == newfacet) {
    +        qh_setfree(&(ridge->vertices));
    +        qh_memfree_(ridge, (int)sizeof(ridgeT), freelistp);
    +        numold++;
    +      }else if (neighbor->visitid == samevisitid) {
    +        qh_setdel(neighbor->ridges, ridge);
    +        qh_setfree(&(ridge->vertices));
    +        qh_memfree_(ridge, (int)sizeof(ridgeT), freelistp);
    +        numold++;
    +      }else {
    +        qh_setappend(&newfacet->ridges, ridge);
    +        numold++;
    +      }
    +    }
    +    if (same->ridges)
    +      qh_settruncate(same->ridges, 0);
    +    if (!same->simplicial)
    +      continue;
    +    FOREACHneighbor_i_(same) {       /* note: !newfact->simplicial */
    +      if (neighbor->visitid != samevisitid && neighbor->simplicial) {
    +        ridge= qh_newridge();
    +        ridge->vertices= qh_setnew_delnthsorted(same->vertices, qh hull_dim,
    +                                                          neighbor_i, 0);
    +        toporient= same->toporient ^ (neighbor_i & 0x1);
    +        if (toporient) {
    +          ridge->top= newfacet;
    +          ridge->bottom= neighbor;
    +        }else {
    +          ridge->top= neighbor;
    +          ridge->bottom= newfacet;
    +        }
    +        qh_setappend(&(newfacet->ridges), ridge);
    +        qh_setappend(&(neighbor->ridges), ridge);
    +        numnew++;
    +      }
    +    }
    +  }
    +
    +  trace2((qh ferr, 2033, "qh_mergecycle_ridges: found %d old ridges and %d new ones\n",
    +             numold, numnew));
    +} /* mergecycle_ridges */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_vneighbors( samecycle, newfacet )
    +    create vertex neighbors for newfacet from vertices of facets in samecycle
    +    samecycle marked with visitid == qh.visit_id - 1
    +
    +  returns:
    +    newfacet vertices with updated neighbors
    +    marks newfacet with qh.visit_id-1
    +    deletes vertices that are merged away
    +    sets delridge on all vertices (faster here than in mergecycle_ridges)
    +
    +  see:
    +    qh_mergevertex_neighbors()
    +
    +  design:
    +    for each vertex of samecycle facet
    +      set vertex->delridge
    +      delete samecycle facets from vertex neighbors
    +      append newfacet to vertex neighbors
    +      if vertex only in newfacet
    +        delete it from newfacet
    +        add it to qh.del_vertices for later deletion
    +*/
    +void qh_mergecycle_vneighbors(facetT *samecycle, facetT *newfacet) {
    +  facetT *neighbor, **neighborp;
    +  unsigned int mergeid;
    +  vertexT *vertex, **vertexp, *apex;
    +  setT *vertices;
    +
    +  trace4((qh ferr, 4035, "qh_mergecycle_vneighbors: update vertex neighbors for newfacet\n"));
    +  mergeid= qh visit_id - 1;
    +  newfacet->visitid= mergeid;
    +  vertices= qh_basevertices(samecycle); /* temp */
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  qh_setappend(&vertices, apex);
    +  FOREACHvertex_(vertices) {
    +    vertex->delridge= True;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->visitid == mergeid)
    +        SETref_(neighbor)= NULL;
    +    }
    +    qh_setcompact(vertex->neighbors);
    +    qh_setappend(&vertex->neighbors, newfacet);
    +    if (!SETsecond_(vertex->neighbors)) {
    +      zinc_(Zcyclevertex);
    +      trace2((qh ferr, 2034, "qh_mergecycle_vneighbors: deleted v%d when merging cycle f%d into f%d\n",
    +        vertex->id, samecycle->id, newfacet->id));
    +      qh_setdelsorted(newfacet->vertices, vertex);
    +      vertex->deleted= True;
    +      qh_setappend(&qh del_vertices, vertex);
    +    }
    +  }
    +  qh_settempfree(&vertices);
    +  trace3((qh ferr, 3005, "qh_mergecycle_vneighbors: merged vertices from cycle f%d into f%d\n",
    +             samecycle->id, newfacet->id));
    +} /* mergecycle_vneighbors */
    +
    +/*---------------------------------
    +
    +  qh_mergefacet( facet1, facet2, mindist, maxdist, mergeapex )
    +    merges facet1 into facet2
    +    mergeapex==qh_MERGEapex if merging new facet into coplanar horizon
    +
    +  returns:
    +    qh.max_outside and qh.min_vertex updated
    +    initializes vertex neighbors on first merge
    +
    +  returns:
    +    facet2 contains facet1's vertices, neighbors, and ridges
    +      facet2 moved to end of qh.facet_list
    +      makes facet2 a newfacet
    +      sets facet2->newmerge set
    +      clears facet2->center (unless merging into a large facet)
    +      clears facet2->tested and ridge->tested for facet1
    +
    +    facet1 prepended to visible_list for later deletion and partitioning
    +      facet1->f.replace == facet2
    +
    +    adds neighboring facets to facet_mergeset if redundant or degenerate
    +
    +  notes:
    +    mindist/maxdist may be NULL (only if both NULL)
    +    traces merge if fmax_(maxdist,-mindist) > TRACEdist
    +
    +  see:
    +    qh_mergecycle()
    +
    +  design:
    +    trace merge and check for degenerate simplex
    +    make ridges for both facets
    +    update qh.max_outside, qh.max_vertex, qh.min_vertex
    +    update facet2->maxoutside and keepcentrum
    +    update facet2->nummerge
    +    update tested flags for facet2
    +    if facet1 is simplicial
    +      merge facet1 into facet2
    +    else
    +      merge facet1's neighbors into facet2
    +      merge facet1's ridges into facet2
    +      merge facet1's vertices into facet2
    +      merge facet1's vertex neighbors into facet2
    +      add facet2's vertices to qh.new_vertexlist
    +      unless qh_MERGEapex
    +        test facet2 for degenerate or redundant neighbors
    +      move facet1 to qh.visible_list for later deletion
    +      move facet2 to end of qh.newfacet_list
    +*/
    +void qh_mergefacet(facetT *facet1, facetT *facet2, realT *mindist, realT *maxdist, boolT mergeapex) {
    +  boolT traceonce= False;
    +  vertexT *vertex, **vertexp;
    +  int tracerestore=0, nummerge;
    +
    +  if (facet1->tricoplanar || facet2->tricoplanar) {
    +    if (!qh TRInormals) {
    +      qh_fprintf(qh ferr, 6226, "Qhull internal error (qh_mergefacet): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit2(qh_ERRqhull, facet1, facet2);
    +    }
    +    if (facet2->tricoplanar) {
    +      facet2->tricoplanar= False;
    +      facet2->keepcentrum= False;
    +    }
    +  }
    +  zzinc_(Ztotmerge);
    +  if (qh REPORTfreq2 && qh POSTmerging) {
    +    if (zzval_(Ztotmerge) > qh mergereport + qh REPORTfreq2)
    +      qh_tracemerging();
    +  }
    +#ifndef qh_NOtrace
    +  if (qh build_cnt >= qh RERUN) {
    +    if (mindist && (-*mindist > qh TRACEdist || *maxdist > qh TRACEdist)) {
    +      tracerestore= 0;
    +      qh IStracing= qh TRACElevel;
    +      traceonce= True;
    +      qh_fprintf(qh ferr, 8075, "qh_mergefacet: ========= trace wide merge #%d(%2.2g) for f%d into f%d, last point was p%d\n", zzval_(Ztotmerge),
    +             fmax_(-*mindist, *maxdist), facet1->id, facet2->id, qh furthest_id);
    +    }else if (facet1 == qh tracefacet || facet2 == qh tracefacet) {
    +      tracerestore= qh IStracing;
    +      qh IStracing= 4;
    +      traceonce= True;
    +      qh_fprintf(qh ferr, 8076, "qh_mergefacet: ========= trace merge #%d involving f%d, furthest is p%d\n",
    +                 zzval_(Ztotmerge), qh tracefacet_id,  qh furthest_id);
    +    }
    +  }
    +  if (qh IStracing >= 2) {
    +    realT mergemin= -2;
    +    realT mergemax= -2;
    +
    +    if (mindist) {
    +      mergemin= *mindist;
    +      mergemax= *maxdist;
    +    }
    +    qh_fprintf(qh ferr, 8077, "qh_mergefacet: #%d merge f%d into f%d, mindist= %2.2g, maxdist= %2.2g\n",
    +    zzval_(Ztotmerge), facet1->id, facet2->id, mergemin, mergemax);
    +  }
    +#endif /* !qh_NOtrace */
    +  if (facet1 == facet2 || facet1->visible || facet2->visible) {
    +    qh_fprintf(qh ferr, 6099, "qhull internal error (qh_mergefacet): either f%d and f%d are the same or one is a visible facet\n",
    +             facet1->id, facet2->id);
    +    qh_errexit2(qh_ERRqhull, facet1, facet2);
    +  }
    +  if (qh num_facets - qh num_visible <= qh hull_dim + 1) {
    +    qh_fprintf(qh ferr, 6227, "\n\
    +qhull precision error: Only %d facets remain.  Can not merge another\n\
    +pair.  The input is too degenerate or the convexity constraints are\n\
    +too strong.\n", qh hull_dim+1);
    +    if (qh hull_dim >= 5 && !qh MERGEexact)
    +      qh_fprintf(qh ferr, 8079, "Option 'Qx' may avoid this problem.\n");
    +    qh_errexit(qh_ERRprec, NULL, NULL);
    +  }
    +  if (!qh VERTEXneighbors)
    +    qh_vertexneighbors();
    +  qh_makeridges(facet1);
    +  qh_makeridges(facet2);
    +  if (qh IStracing >=4)
    +    qh_errprint("MERGING", facet1, facet2, NULL, NULL);
    +  if (mindist) {
    +    maximize_(qh max_outside, *maxdist);
    +    maximize_(qh max_vertex, *maxdist);
    +#if qh_MAXoutside
    +    maximize_(facet2->maxoutside, *maxdist);
    +#endif
    +    minimize_(qh min_vertex, *mindist);
    +    if (!facet2->keepcentrum
    +    && (*maxdist > qh WIDEfacet || *mindist < -qh WIDEfacet)) {
    +      facet2->keepcentrum= True;
    +      zinc_(Zwidefacet);
    +    }
    +  }
    +  nummerge= facet1->nummerge + facet2->nummerge + 1;
    +  if (nummerge >= qh_MAXnummerge)
    +    facet2->nummerge= qh_MAXnummerge;
    +  else
    +    facet2->nummerge= (short unsigned int)nummerge;
    +  facet2->newmerge= True;
    +  facet2->dupridge= False;
    +  qh_updatetested(facet1, facet2);
    +  if (qh hull_dim > 2 && qh_setsize(facet1->vertices) == qh hull_dim)
    +    qh_mergesimplex(facet1, facet2, mergeapex);
    +  else {
    +    qh vertex_visit++;
    +    FOREACHvertex_(facet2->vertices)
    +      vertex->visitid= qh vertex_visit;
    +    if (qh hull_dim == 2)
    +      qh_mergefacet2d(facet1, facet2);
    +    else {
    +      qh_mergeneighbors(facet1, facet2);
    +      qh_mergevertices(facet1->vertices, &facet2->vertices);
    +    }
    +    qh_mergeridges(facet1, facet2);
    +    qh_mergevertex_neighbors(facet1, facet2);
    +    if (!facet2->newfacet)
    +      qh_newvertices(facet2->vertices);
    +  }
    +  if (!mergeapex)
    +    qh_degen_redundant_neighbors(facet2, facet1);
    +  if (facet2->coplanar || !facet2->newfacet) {
    +    zinc_(Zmergeintohorizon);
    +  }else if (!facet1->newfacet && facet2->newfacet) {
    +    zinc_(Zmergehorizon);
    +  }else {
    +    zinc_(Zmergenew);
    +  }
    +  qh_willdelete(facet1, facet2);
    +  qh_removefacet(facet2);  /* append as a newfacet to end of qh facet_list */
    +  qh_appendfacet(facet2);
    +  facet2->newfacet= True;
    +  facet2->tested= False;
    +  qh_tracemerge(facet1, facet2);
    +  if (traceonce) {
    +    qh_fprintf(qh ferr, 8080, "qh_mergefacet: end of wide tracing\n");
    +    qh IStracing= tracerestore;
    +  }
    +} /* mergefacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergefacet2d( facet1, facet2 )
    +    in 2d, merges neighbors and vertices of facet1 into facet2
    +
    +  returns:
    +    build ridges for neighbors if necessary
    +    facet2 looks like a simplicial facet except for centrum, ridges
    +      neighbors are opposite the corresponding vertex
    +      maintains orientation of facet2
    +
    +  notes:
    +    qh_mergefacet() retains non-simplicial structures
    +      they are not needed in 2d, but later routines may use them
    +    preserves qh.vertex_visit for qh_mergevertex_neighbors()
    +
    +  design:
    +    get vertices and neighbors
    +    determine new vertices and neighbors
    +    set new vertices and neighbors and adjust orientation
    +    make ridges for new neighbor if needed
    +*/
    +void qh_mergefacet2d(facetT *facet1, facetT *facet2) {
    +  vertexT *vertex1A, *vertex1B, *vertex2A, *vertex2B, *vertexA, *vertexB;
    +  facetT *neighbor1A, *neighbor1B, *neighbor2A, *neighbor2B, *neighborA, *neighborB;
    +
    +  vertex1A= SETfirstt_(facet1->vertices, vertexT);
    +  vertex1B= SETsecondt_(facet1->vertices, vertexT);
    +  vertex2A= SETfirstt_(facet2->vertices, vertexT);
    +  vertex2B= SETsecondt_(facet2->vertices, vertexT);
    +  neighbor1A= SETfirstt_(facet1->neighbors, facetT);
    +  neighbor1B= SETsecondt_(facet1->neighbors, facetT);
    +  neighbor2A= SETfirstt_(facet2->neighbors, facetT);
    +  neighbor2B= SETsecondt_(facet2->neighbors, facetT);
    +  if (vertex1A == vertex2A) {
    +    vertexA= vertex1B;
    +    vertexB= vertex2B;
    +    neighborA= neighbor2A;
    +    neighborB= neighbor1A;
    +  }else if (vertex1A == vertex2B) {
    +    vertexA= vertex1B;
    +    vertexB= vertex2A;
    +    neighborA= neighbor2B;
    +    neighborB= neighbor1A;
    +  }else if (vertex1B == vertex2A) {
    +    vertexA= vertex1A;
    +    vertexB= vertex2B;
    +    neighborA= neighbor2A;
    +    neighborB= neighbor1B;
    +  }else { /* 1B == 2B */
    +    vertexA= vertex1A;
    +    vertexB= vertex2A;
    +    neighborA= neighbor2B;
    +    neighborB= neighbor1B;
    +  }
    +  /* vertexB always from facet2, neighborB always from facet1 */
    +  if (vertexA->id > vertexB->id) {
    +    SETfirst_(facet2->vertices)= vertexA;
    +    SETsecond_(facet2->vertices)= vertexB;
    +    if (vertexB == vertex2A)
    +      facet2->toporient= !facet2->toporient;
    +    SETfirst_(facet2->neighbors)= neighborA;
    +    SETsecond_(facet2->neighbors)= neighborB;
    +  }else {
    +    SETfirst_(facet2->vertices)= vertexB;
    +    SETsecond_(facet2->vertices)= vertexA;
    +    if (vertexB == vertex2B)
    +      facet2->toporient= !facet2->toporient;
    +    SETfirst_(facet2->neighbors)= neighborB;
    +    SETsecond_(facet2->neighbors)= neighborA;
    +  }
    +  qh_makeridges(neighborB);
    +  qh_setreplace(neighborB->neighbors, facet1, facet2);
    +  trace4((qh ferr, 4036, "qh_mergefacet2d: merged v%d and neighbor f%d of f%d into f%d\n",
    +       vertexA->id, neighborB->id, facet1->id, facet2->id));
    +} /* mergefacet2d */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergeneighbors( facet1, facet2 )
    +    merges the neighbors of facet1 into facet2
    +
    +  see:
    +    qh_mergecycle_neighbors()
    +
    +  design:
    +    for each neighbor of facet1
    +      if neighbor is also a neighbor of facet2
    +        if neighbor is simpilicial
    +          make ridges for later deletion as a degenerate facet
    +        update its neighbor set
    +      else
    +        move the neighbor relation to facet2
    +    remove the neighbor relation for facet1 and facet2
    +*/
    +void qh_mergeneighbors(facetT *facet1, facetT *facet2) {
    +  facetT *neighbor, **neighborp;
    +
    +  trace4((qh ferr, 4037, "qh_mergeneighbors: merge neighbors of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  qh visit_id++;
    +  FOREACHneighbor_(facet2) {
    +    neighbor->visitid= qh visit_id;
    +  }
    +  FOREACHneighbor_(facet1) {
    +    if (neighbor->visitid == qh visit_id) {
    +      if (neighbor->simplicial)    /* is degen, needs ridges */
    +        qh_makeridges(neighbor);
    +      if (SETfirstt_(neighbor->neighbors, facetT) != facet1) /*keep newfacet->horizon*/
    +        qh_setdel(neighbor->neighbors, facet1);
    +      else {
    +        qh_setdel(neighbor->neighbors, facet2);
    +        qh_setreplace(neighbor->neighbors, facet1, facet2);
    +      }
    +    }else if (neighbor != facet2) {
    +      qh_setappend(&(facet2->neighbors), neighbor);
    +      qh_setreplace(neighbor->neighbors, facet1, facet2);
    +    }
    +  }
    +  qh_setdel(facet1->neighbors, facet2);  /* here for makeridges */
    +  qh_setdel(facet2->neighbors, facet1);
    +} /* mergeneighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergeridges( facet1, facet2 )
    +    merges the ridge set of facet1 into facet2
    +
    +  returns:
    +    may delete all ridges for a vertex
    +    sets vertex->delridge on deleted ridges
    +
    +  see:
    +    qh_mergecycle_ridges()
    +
    +  design:
    +    delete ridges between facet1 and facet2
    +      mark (delridge) vertices on these ridges for later testing
    +    for each remaining ridge
    +      rename facet1 to facet2
    +*/
    +void qh_mergeridges(facetT *facet1, facetT *facet2) {
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +
    +  trace4((qh ferr, 4038, "qh_mergeridges: merge ridges of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  FOREACHridge_(facet2->ridges) {
    +    if ((ridge->top == facet1) || (ridge->bottom == facet1)) {
    +      FOREACHvertex_(ridge->vertices)
    +        vertex->delridge= True;
    +      qh_delridge(ridge);  /* expensive in high-d, could rebuild */
    +      ridgep--; /*repeat*/
    +    }
    +  }
    +  FOREACHridge_(facet1->ridges) {
    +    if (ridge->top == facet1)
    +      ridge->top= facet2;
    +    else
    +      ridge->bottom= facet2;
    +    qh_setappend(&(facet2->ridges), ridge);
    +  }
    +} /* mergeridges */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergesimplex( facet1, facet2, mergeapex )
    +    merge simplicial facet1 into facet2
    +    mergeapex==qh_MERGEapex if merging samecycle into horizon facet
    +      vertex id is latest (most recently created)
    +    facet1 may be contained in facet2
    +    ridges exist for both facets
    +
    +  returns:
    +    facet2 with updated vertices, ridges, neighbors
    +    updated neighbors for facet1's vertices
    +    facet1 not deleted
    +    sets vertex->delridge on deleted ridges
    +
    +  notes:
    +    special case code since this is the most common merge
    +    called from qh_mergefacet()
    +
    +  design:
    +    if qh_MERGEapex
    +      add vertices of facet2 to qh.new_vertexlist if necessary
    +      add apex to facet2
    +    else
    +      for each ridge between facet1 and facet2
    +        set vertex->delridge
    +      determine the apex for facet1 (i.e., vertex to be merged)
    +      unless apex already in facet2
    +        insert apex into vertices for facet2
    +      add vertices of facet2 to qh.new_vertexlist if necessary
    +      add apex to qh.new_vertexlist if necessary
    +      for each vertex of facet1
    +        if apex
    +          rename facet1 to facet2 in its vertex neighbors
    +        else
    +          delete facet1 from vertex neighors
    +          if only in facet2
    +            add vertex to qh.del_vertices for later deletion
    +      for each ridge of facet1
    +        delete ridges between facet1 and facet2
    +        append other ridges to facet2 after renaming facet to facet2
    +*/
    +void qh_mergesimplex(facetT *facet1, facetT *facet2, boolT mergeapex) {
    +  vertexT *vertex, **vertexp, *apex;
    +  ridgeT *ridge, **ridgep;
    +  boolT issubset= False;
    +  int vertex_i= -1, vertex_n;
    +  facetT *neighbor, **neighborp, *otherfacet;
    +
    +  if (mergeapex) {
    +    if (!facet2->newfacet)
    +      qh_newvertices(facet2->vertices);  /* apex is new */
    +    apex= SETfirstt_(facet1->vertices, vertexT);
    +    if (SETfirstt_(facet2->vertices, vertexT) != apex)
    +      qh_setaddnth(&facet2->vertices, 0, apex);  /* apex has last id */
    +    else
    +      issubset= True;
    +  }else {
    +    zinc_(Zmergesimplex);
    +    FOREACHvertex_(facet1->vertices)
    +      vertex->seen= False;
    +    FOREACHridge_(facet1->ridges) {
    +      if (otherfacet_(ridge, facet1) == facet2) {
    +        FOREACHvertex_(ridge->vertices) {
    +          vertex->seen= True;
    +          vertex->delridge= True;
    +        }
    +        break;
    +      }
    +    }
    +    FOREACHvertex_(facet1->vertices) {
    +      if (!vertex->seen)
    +        break;  /* must occur */
    +    }
    +    apex= vertex;
    +    trace4((qh ferr, 4039, "qh_mergesimplex: merge apex v%d of f%d into facet f%d\n",
    +          apex->id, facet1->id, facet2->id));
    +    FOREACHvertex_i_(facet2->vertices) {
    +      if (vertex->id < apex->id) {
    +        break;
    +      }else if (vertex->id == apex->id) {
    +        issubset= True;
    +        break;
    +      }
    +    }
    +    if (!issubset)
    +      qh_setaddnth(&facet2->vertices, vertex_i, apex);
    +    if (!facet2->newfacet)
    +      qh_newvertices(facet2->vertices);
    +    else if (!apex->newlist) {
    +      qh_removevertex(apex);
    +      qh_appendvertex(apex);
    +    }
    +  }
    +  trace4((qh ferr, 4040, "qh_mergesimplex: update vertex neighbors of f%d\n",
    +          facet1->id));
    +  FOREACHvertex_(facet1->vertices) {
    +    if (vertex == apex && !issubset)
    +      qh_setreplace(vertex->neighbors, facet1, facet2);
    +    else {
    +      qh_setdel(vertex->neighbors, facet1);
    +      if (!SETsecond_(vertex->neighbors))
    +        qh_mergevertex_del(vertex, facet1, facet2);
    +    }
    +  }
    +  trace4((qh ferr, 4041, "qh_mergesimplex: merge ridges and neighbors of f%d into f%d\n",
    +          facet1->id, facet2->id));
    +  qh visit_id++;
    +  FOREACHneighbor_(facet2)
    +    neighbor->visitid= qh visit_id;
    +  FOREACHridge_(facet1->ridges) {
    +    otherfacet= otherfacet_(ridge, facet1);
    +    if (otherfacet == facet2) {
    +      qh_setdel(facet2->ridges, ridge);
    +      qh_setfree(&(ridge->vertices));
    +      qh_memfree(ridge, (int)sizeof(ridgeT));
    +      qh_setdel(facet2->neighbors, facet1);
    +    }else {
    +      qh_setappend(&facet2->ridges, ridge);
    +      if (otherfacet->visitid != qh visit_id) {
    +        qh_setappend(&facet2->neighbors, otherfacet);
    +        qh_setreplace(otherfacet->neighbors, facet1, facet2);
    +        otherfacet->visitid= qh visit_id;
    +      }else {
    +        if (otherfacet->simplicial)    /* is degen, needs ridges */
    +          qh_makeridges(otherfacet);
    +        if (SETfirstt_(otherfacet->neighbors, facetT) != facet1)
    +          qh_setdel(otherfacet->neighbors, facet1);
    +        else {   /*keep newfacet->neighbors->horizon*/
    +          qh_setdel(otherfacet->neighbors, facet2);
    +          qh_setreplace(otherfacet->neighbors, facet1, facet2);
    +        }
    +      }
    +      if (ridge->top == facet1) /* wait until after qh_makeridges */
    +        ridge->top= facet2;
    +      else
    +        ridge->bottom= facet2;
    +    }
    +  }
    +  SETfirst_(facet1->ridges)= NULL; /* it will be deleted */
    +  trace3((qh ferr, 3006, "qh_mergesimplex: merged simplex f%d apex v%d into facet f%d\n",
    +          facet1->id, getid_(apex), facet2->id));
    +} /* mergesimplex */
    +
    +/*---------------------------------
    +
    +  qh_mergevertex_del( vertex, facet1, facet2 )
    +    delete a vertex because of merging facet1 into facet2
    +
    +  returns:
    +    deletes vertex from facet2
    +    adds vertex to qh.del_vertices for later deletion
    +*/
    +void qh_mergevertex_del(vertexT *vertex, facetT *facet1, facetT *facet2) {
    +
    +  zinc_(Zmergevertex);
    +  trace2((qh ferr, 2035, "qh_mergevertex_del: deleted v%d when merging f%d into f%d\n",
    +          vertex->id, facet1->id, facet2->id));
    +  qh_setdelsorted(facet2->vertices, vertex);
    +  vertex->deleted= True;
    +  qh_setappend(&qh del_vertices, vertex);
    +} /* mergevertex_del */
    +
    +/*---------------------------------
    +
    +  qh_mergevertex_neighbors( facet1, facet2 )
    +    merge the vertex neighbors of facet1 to facet2
    +
    +  returns:
    +    if vertex is current qh.vertex_visit
    +      deletes facet1 from vertex->neighbors
    +    else
    +      renames facet1 to facet2 in vertex->neighbors
    +    deletes vertices if only one neighbor
    +
    +  notes:
    +    assumes vertex neighbor sets are good
    +*/
    +void qh_mergevertex_neighbors(facetT *facet1, facetT *facet2) {
    +  vertexT *vertex, **vertexp;
    +
    +  trace4((qh ferr, 4042, "qh_mergevertex_neighbors: merge vertex neighbors of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  if (qh tracevertex) {
    +    qh_fprintf(qh ferr, 8081, "qh_mergevertex_neighbors: of f%d and f%d at furthest p%d f0= %p\n",
    +             facet1->id, facet2->id, qh furthest_id, qh tracevertex->neighbors->e[0].p);
    +    qh_errprint("TRACE", NULL, NULL, NULL, qh tracevertex);
    +  }
    +  FOREACHvertex_(facet1->vertices) {
    +    if (vertex->visitid != qh vertex_visit)
    +      qh_setreplace(vertex->neighbors, facet1, facet2);
    +    else {
    +      qh_setdel(vertex->neighbors, facet1);
    +      if (!SETsecond_(vertex->neighbors))
    +        qh_mergevertex_del(vertex, facet1, facet2);
    +    }
    +  }
    +  if (qh tracevertex)
    +    qh_errprint("TRACE", NULL, NULL, NULL, qh tracevertex);
    +} /* mergevertex_neighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergevertices( vertices1, vertices2 )
    +    merges the vertex set of facet1 into facet2
    +
    +  returns:
    +    replaces vertices2 with merged set
    +    preserves vertex_visit for qh_mergevertex_neighbors
    +    updates qh.newvertex_list
    +
    +  design:
    +    create a merged set of both vertices (in inverse id order)
    +*/
    +void qh_mergevertices(setT *vertices1, setT **vertices2) {
    +  int newsize= qh_setsize(vertices1)+qh_setsize(*vertices2) - qh hull_dim + 1;
    +  setT *mergedvertices;
    +  vertexT *vertex, **vertexp, **vertex2= SETaddr_(*vertices2, vertexT);
    +
    +  mergedvertices= qh_settemp(newsize);
    +  FOREACHvertex_(vertices1) {
    +    if (!*vertex2 || vertex->id > (*vertex2)->id)
    +      qh_setappend(&mergedvertices, vertex);
    +    else {
    +      while (*vertex2 && (*vertex2)->id > vertex->id)
    +        qh_setappend(&mergedvertices, *vertex2++);
    +      if (!*vertex2 || (*vertex2)->id < vertex->id)
    +        qh_setappend(&mergedvertices, vertex);
    +      else
    +        qh_setappend(&mergedvertices, *vertex2++);
    +    }
    +  }
    +  while (*vertex2)
    +    qh_setappend(&mergedvertices, *vertex2++);
    +  if (newsize < qh_setsize(mergedvertices)) {
    +    qh_fprintf(qh ferr, 6100, "qhull internal error (qh_mergevertices): facets did not share a ridge\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh_setfree(vertices2);
    +  *vertices2= mergedvertices;
    +  qh_settemppop();
    +} /* mergevertices */
    +
    +
    +/*---------------------------------
    +
    +  qh_neighbor_intersections( vertex )
    +    return intersection of all vertices in vertex->neighbors except for vertex
    +
    +  returns:
    +    returns temporary set of vertices
    +    does not include vertex
    +    NULL if a neighbor is simplicial
    +    NULL if empty set
    +
    +  notes:
    +    used for renaming vertices
    +
    +  design:
    +    initialize the intersection set with vertices of the first two neighbors
    +    delete vertex from the intersection
    +    for each remaining neighbor
    +      intersect its vertex set with the intersection set
    +      return NULL if empty
    +    return the intersection set
    +*/
    +setT *qh_neighbor_intersections(vertexT *vertex) {
    +  facetT *neighbor, **neighborp, *neighborA, *neighborB;
    +  setT *intersect;
    +  int neighbor_i, neighbor_n;
    +
    +  FOREACHneighbor_(vertex) {
    +    if (neighbor->simplicial)
    +      return NULL;
    +  }
    +  neighborA= SETfirstt_(vertex->neighbors, facetT);
    +  neighborB= SETsecondt_(vertex->neighbors, facetT);
    +  zinc_(Zintersectnum);
    +  if (!neighborA)
    +    return NULL;
    +  if (!neighborB)
    +    intersect= qh_setcopy(neighborA->vertices, 0);
    +  else
    +    intersect= qh_vertexintersect_new(neighborA->vertices, neighborB->vertices);
    +  qh_settemppush(intersect);
    +  qh_setdelsorted(intersect, vertex);
    +  FOREACHneighbor_i_(vertex) {
    +    if (neighbor_i >= 2) {
    +      zinc_(Zintersectnum);
    +      qh_vertexintersect(&intersect, neighbor->vertices);
    +      if (!SETfirst_(intersect)) {
    +        zinc_(Zintersectfail);
    +        qh_settempfree(&intersect);
    +        return NULL;
    +      }
    +    }
    +  }
    +  trace3((qh ferr, 3007, "qh_neighbor_intersections: %d vertices in neighbor intersection of v%d\n",
    +          qh_setsize(intersect), vertex->id));
    +  return intersect;
    +} /* neighbor_intersections */
    +
    +/*---------------------------------
    +
    +  qh_newvertices( vertices )
    +    add vertices to end of qh.vertex_list (marks as new vertices)
    +
    +  returns:
    +    vertices on qh.newvertex_list
    +    vertex->newlist set
    +*/
    +void qh_newvertices(setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->newlist) {
    +      qh_removevertex(vertex);
    +      qh_appendvertex(vertex);
    +    }
    +  }
    +} /* newvertices */
    +
    +/*---------------------------------
    +
    +  qh_reducevertices()
    +    reduce extra vertices, shared vertices, and redundant vertices
    +    facet->newmerge is set if merged since last call
    +    if !qh.MERGEvertices, only removes extra vertices
    +
    +  returns:
    +    True if also merged degen_redundant facets
    +    vertices are renamed if possible
    +    clears facet->newmerge and vertex->delridge
    +
    +  notes:
    +    ignored if 2-d
    +
    +  design:
    +    merge any degenerate or redundant facets
    +    for each newly merged facet
    +      remove extra vertices
    +    if qh.MERGEvertices
    +      for each newly merged facet
    +        for each vertex
    +          if vertex was on a deleted ridge
    +            rename vertex if it is shared
    +      remove delridge flag from new vertices
    +*/
    +boolT qh_reducevertices(void) {
    +  int numshare=0, numrename= 0;
    +  boolT degenredun= False;
    +  facetT *newfacet;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh hull_dim == 2)
    +    return False;
    +  if (qh_merge_degenredundant())
    +    degenredun= True;
    + LABELrestart:
    +  FORALLnew_facets {
    +    if (newfacet->newmerge) {
    +      if (!qh MERGEvertices)
    +        newfacet->newmerge= False;
    +      qh_remove_extravertices(newfacet);
    +    }
    +  }
    +  if (!qh MERGEvertices)
    +    return False;
    +  FORALLnew_facets {
    +    if (newfacet->newmerge) {
    +      newfacet->newmerge= False;
    +      FOREACHvertex_(newfacet->vertices) {
    +        if (vertex->delridge) {
    +          if (qh_rename_sharedvertex(vertex, newfacet)) {
    +            numshare++;
    +            vertexp--; /* repeat since deleted vertex */
    +          }
    +        }
    +      }
    +    }
    +  }
    +  FORALLvertex_(qh newvertex_list) {
    +    if (vertex->delridge && !vertex->deleted) {
    +      vertex->delridge= False;
    +      if (qh hull_dim >= 4 && qh_redundant_vertex(vertex)) {
    +        numrename++;
    +        if (qh_merge_degenredundant()) {
    +          degenredun= True;
    +          goto LABELrestart;
    +        }
    +      }
    +    }
    +  }
    +  trace1((qh ferr, 1014, "qh_reducevertices: renamed %d shared vertices and %d redundant vertices. Degen? %d\n",
    +          numshare, numrename, degenredun));
    +  return degenredun;
    +} /* reducevertices */
    +
    +/*---------------------------------
    +
    +  qh_redundant_vertex( vertex )
    +    detect and rename a redundant vertex
    +    vertices have full vertex->neighbors
    +
    +  returns:
    +    returns true if find a redundant vertex
    +      deletes vertex(vertex->deleted)
    +
    +  notes:
    +    only needed if vertex->delridge and hull_dim >= 4
    +    may add degenerate facets to qh.facet_mergeset
    +    doesn't change vertex->neighbors or create redundant facets
    +
    +  design:
    +    intersect vertices of all facet neighbors of vertex
    +    determine ridges for these vertices
    +    if find a new vertex for vertex amoung these ridges and vertices
    +      rename vertex to the new vertex
    +*/
    +vertexT *qh_redundant_vertex(vertexT *vertex) {
    +  vertexT *newvertex= NULL;
    +  setT *vertices, *ridges;
    +
    +  trace3((qh ferr, 3008, "qh_redundant_vertex: check if v%d can be renamed\n", vertex->id));
    +  if ((vertices= qh_neighbor_intersections(vertex))) {
    +    ridges= qh_vertexridges(vertex);
    +    if ((newvertex= qh_find_newvertex(vertex, vertices, ridges)))
    +      qh_renamevertex(vertex, newvertex, ridges, NULL, NULL);
    +    qh_settempfree(&ridges);
    +    qh_settempfree(&vertices);
    +  }
    +  return newvertex;
    +} /* redundant_vertex */
    +
    +/*---------------------------------
    +
    +  qh_remove_extravertices( facet )
    +    remove extra vertices from non-simplicial facets
    +
    +  returns:
    +    returns True if it finds them
    +
    +  design:
    +    for each vertex in facet
    +      if vertex not in a ridge (i.e., no longer used)
    +        delete vertex from facet
    +        delete facet from vertice's neighbors
    +        unless vertex in another facet
    +          add vertex to qh.del_vertices for later deletion
    +*/
    +boolT qh_remove_extravertices(facetT *facet) {
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +  boolT foundrem= False;
    +
    +  trace4((qh ferr, 4043, "qh_remove_extravertices: test f%d for extra vertices\n",
    +          facet->id));
    +  FOREACHvertex_(facet->vertices)
    +    vertex->seen= False;
    +  FOREACHridge_(facet->ridges) {
    +    FOREACHvertex_(ridge->vertices)
    +      vertex->seen= True;
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    if (!vertex->seen) {
    +      foundrem= True;
    +      zinc_(Zremvertex);
    +      qh_setdelsorted(facet->vertices, vertex);
    +      qh_setdel(vertex->neighbors, facet);
    +      if (!qh_setsize(vertex->neighbors)) {
    +        vertex->deleted= True;
    +        qh_setappend(&qh del_vertices, vertex);
    +        zinc_(Zremvertexdel);
    +        trace2((qh ferr, 2036, "qh_remove_extravertices: v%d deleted because it's lost all ridges\n", vertex->id));
    +      }else
    +        trace3((qh ferr, 3009, "qh_remove_extravertices: v%d removed from f%d because it's lost all ridges\n", vertex->id, facet->id));
    +      vertexp--; /*repeat*/
    +    }
    +  }
    +  return foundrem;
    +} /* remove_extravertices */
    +
    +/*---------------------------------
    +
    +  qh_rename_sharedvertex( vertex, facet )
    +    detect and rename if shared vertex in facet
    +    vertices have full ->neighbors
    +
    +  returns:
    +    newvertex or NULL
    +    the vertex may still exist in other facets (i.e., a neighbor was pinched)
    +    does not change facet->neighbors
    +    updates vertex->neighbors
    +
    +  notes:
    +    a shared vertex for a facet is only in ridges to one neighbor
    +    this may undo a pinched facet
    +
    +    it does not catch pinches involving multiple facets.  These appear
    +      to be difficult to detect, since an exhaustive search is too expensive.
    +
    +  design:
    +    if vertex only has two neighbors
    +      determine the ridges that contain the vertex
    +      determine the vertices shared by both neighbors
    +      if can find a new vertex in this set
    +        rename the vertex to the new vertex
    +*/
    +vertexT *qh_rename_sharedvertex(vertexT *vertex, facetT *facet) {
    +  facetT *neighbor, **neighborp, *neighborA= NULL;
    +  setT *vertices, *ridges;
    +  vertexT *newvertex;
    +
    +  if (qh_setsize(vertex->neighbors) == 2) {
    +    neighborA= SETfirstt_(vertex->neighbors, facetT);
    +    if (neighborA == facet)
    +      neighborA= SETsecondt_(vertex->neighbors, facetT);
    +  }else if (qh hull_dim == 3)
    +    return NULL;
    +  else {
    +    qh visit_id++;
    +    FOREACHneighbor_(facet)
    +      neighbor->visitid= qh visit_id;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->visitid == qh visit_id) {
    +        if (neighborA)
    +          return NULL;
    +        neighborA= neighbor;
    +      }
    +    }
    +    if (!neighborA) {
    +      qh_fprintf(qh ferr, 6101, "qhull internal error (qh_rename_sharedvertex): v%d's neighbors not in f%d\n",
    +        vertex->id, facet->id);
    +      qh_errprint("ERRONEOUS", facet, NULL, NULL, vertex);
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +  }
    +  /* the vertex is shared by facet and neighborA */
    +  ridges= qh_settemp(qh TEMPsize);
    +  neighborA->visitid= ++qh visit_id;
    +  qh_vertexridges_facet(vertex, facet, &ridges);
    +  trace2((qh ferr, 2037, "qh_rename_sharedvertex: p%d(v%d) is shared by f%d(%d ridges) and f%d\n",
    +    qh_pointid(vertex->point), vertex->id, facet->id, qh_setsize(ridges), neighborA->id));
    +  zinc_(Zintersectnum);
    +  vertices= qh_vertexintersect_new(facet->vertices, neighborA->vertices);
    +  qh_setdel(vertices, vertex);
    +  qh_settemppush(vertices);
    +  if ((newvertex= qh_find_newvertex(vertex, vertices, ridges)))
    +    qh_renamevertex(vertex, newvertex, ridges, facet, neighborA);
    +  qh_settempfree(&vertices);
    +  qh_settempfree(&ridges);
    +  return newvertex;
    +} /* rename_sharedvertex */
    +
    +/*---------------------------------
    +
    +  qh_renameridgevertex( ridge, oldvertex, newvertex )
    +    renames oldvertex as newvertex in ridge
    +
    +  returns:
    +
    +  design:
    +    delete oldvertex from ridge
    +    if newvertex already in ridge
    +      copy ridge->noconvex to another ridge if possible
    +      delete the ridge
    +    else
    +      insert newvertex into the ridge
    +      adjust the ridge's orientation
    +*/
    +void qh_renameridgevertex(ridgeT *ridge, vertexT *oldvertex, vertexT *newvertex) {
    +  int nth= 0, oldnth;
    +  facetT *temp;
    +  vertexT *vertex, **vertexp;
    +
    +  oldnth= qh_setindex(ridge->vertices, oldvertex);
    +  qh_setdelnthsorted(ridge->vertices, oldnth);
    +  FOREACHvertex_(ridge->vertices) {
    +    if (vertex == newvertex) {
    +      zinc_(Zdelridge);
    +      if (ridge->nonconvex) /* only one ridge has nonconvex set */
    +        qh_copynonconvex(ridge);
    +      trace2((qh ferr, 2038, "qh_renameridgevertex: ridge r%d deleted.  It contained both v%d and v%d\n",
    +        ridge->id, oldvertex->id, newvertex->id));
    +      qh_delridge(ridge);
    +      return;
    +    }
    +    if (vertex->id < newvertex->id)
    +      break;
    +    nth++;
    +  }
    +  qh_setaddnth(&ridge->vertices, nth, newvertex);
    +  if (abs(oldnth - nth)%2) {
    +    trace3((qh ferr, 3010, "qh_renameridgevertex: swapped the top and bottom of ridge r%d\n",
    +            ridge->id));
    +    temp= ridge->top;
    +    ridge->top= ridge->bottom;
    +    ridge->bottom= temp;
    +  }
    +} /* renameridgevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_renamevertex( oldvertex, newvertex, ridges, oldfacet, neighborA )
    +    renames oldvertex as newvertex in ridges
    +    gives oldfacet/neighborA if oldvertex is shared between two facets
    +
    +  returns:
    +    oldvertex may still exist afterwards
    +
    +
    +  notes:
    +    can not change neighbors of newvertex (since it's a subset)
    +
    +  design:
    +    for each ridge in ridges
    +      rename oldvertex to newvertex and delete degenerate ridges
    +    if oldfacet not defined
    +      for each neighbor of oldvertex
    +        delete oldvertex from neighbor's vertices
    +        remove extra vertices from neighbor
    +      add oldvertex to qh.del_vertices
    +    else if oldvertex only between oldfacet and neighborA
    +      delete oldvertex from oldfacet and neighborA
    +      add oldvertex to qh.del_vertices
    +    else oldvertex is in oldfacet and neighborA and other facets (i.e., pinched)
    +      delete oldvertex from oldfacet
    +      delete oldfacet from oldvertice's neighbors
    +      remove extra vertices (e.g., oldvertex) from neighborA
    +*/
    +void qh_renamevertex(vertexT *oldvertex, vertexT *newvertex, setT *ridges, facetT *oldfacet, facetT *neighborA) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  boolT istrace= False;
    +
    +  if (qh IStracing >= 2 || oldvertex->id == qh tracevertex_id ||
    +        newvertex->id == qh tracevertex_id)
    +    istrace= True;
    +  FOREACHridge_(ridges)
    +    qh_renameridgevertex(ridge, oldvertex, newvertex);
    +  if (!oldfacet) {
    +    zinc_(Zrenameall);
    +    if (istrace)
    +      qh_fprintf(qh ferr, 8082, "qh_renamevertex: renamed v%d to v%d in several facets\n",
    +               oldvertex->id, newvertex->id);
    +    FOREACHneighbor_(oldvertex) {
    +      qh_maydropneighbor(neighbor);
    +      qh_setdelsorted(neighbor->vertices, oldvertex);
    +      if (qh_remove_extravertices(neighbor))
    +        neighborp--; /* neighbor may be deleted */
    +    }
    +    if (!oldvertex->deleted) {
    +      oldvertex->deleted= True;
    +      qh_setappend(&qh del_vertices, oldvertex);
    +    }
    +  }else if (qh_setsize(oldvertex->neighbors) == 2) {
    +    zinc_(Zrenameshare);
    +    if (istrace)
    +      qh_fprintf(qh ferr, 8083, "qh_renamevertex: renamed v%d to v%d in oldfacet f%d\n",
    +               oldvertex->id, newvertex->id, oldfacet->id);
    +    FOREACHneighbor_(oldvertex)
    +      qh_setdelsorted(neighbor->vertices, oldvertex);
    +    oldvertex->deleted= True;
    +    qh_setappend(&qh del_vertices, oldvertex);
    +  }else {
    +    zinc_(Zrenamepinch);
    +    if (istrace || qh IStracing)
    +      qh_fprintf(qh ferr, 8084, "qh_renamevertex: renamed pinched v%d to v%d between f%d and f%d\n",
    +               oldvertex->id, newvertex->id, oldfacet->id, neighborA->id);
    +    qh_setdelsorted(oldfacet->vertices, oldvertex);
    +    qh_setdel(oldvertex->neighbors, oldfacet);
    +    qh_remove_extravertices(neighborA);
    +  }
    +} /* renamevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_test_appendmerge( facet, neighbor )
    +    tests facet/neighbor for convexity
    +    appends to mergeset if non-convex
    +    if pre-merging,
    +      nop if qh.SKIPconvex, or qh.MERGEexact and coplanar
    +
    +  returns:
    +    true if appends facet/neighbor to mergeset
    +    sets facet->center as needed
    +    does not change facet->seen
    +
    +  design:
    +    if qh.cos_max is defined
    +      if the angle between facet normals is too shallow
    +        append an angle-coplanar merge to qh.mergeset
    +        return True
    +    make facet's centrum if needed
    +    if facet's centrum is above the neighbor
    +      set isconcave
    +    else
    +      if facet's centrum is not below the neighbor
    +        set iscoplanar
    +      make neighbor's centrum if needed
    +      if neighbor's centrum is above the facet
    +        set isconcave
    +      else if neighbor's centrum is not below the facet
    +        set iscoplanar
    +   if isconcave or iscoplanar
    +     get angle if needed
    +     append concave or coplanar merge to qh.mergeset
    +*/
    +boolT qh_test_appendmerge(facetT *facet, facetT *neighbor) {
    +  realT dist, dist2= -REALmax, angle= -REALmax;
    +  boolT isconcave= False, iscoplanar= False, okangle= False;
    +
    +  if (qh SKIPconvex && !qh POSTmerging)
    +    return False;
    +  if ((!qh MERGEexact || qh POSTmerging) && qh cos_max < REALmax/2) {
    +    angle= qh_getangle(facet->normal, neighbor->normal);
    +    zinc_(Zangletests);
    +    if (angle > qh cos_max) {
    +      zinc_(Zcoplanarangle);
    +      qh_appendmergeset(facet, neighbor, MRGanglecoplanar, &angle);
    +      trace2((qh ferr, 2039, "qh_test_appendmerge: coplanar angle %4.4g between f%d and f%d\n",
    +         angle, facet->id, neighbor->id));
    +      return True;
    +    }else
    +      okangle= True;
    +  }
    +  if (!facet->center)
    +    facet->center= qh_getcentrum(facet);
    +  zzinc_(Zcentrumtests);
    +  qh_distplane(facet->center, neighbor, &dist);
    +  if (dist > qh centrum_radius)
    +    isconcave= True;
    +  else {
    +    if (dist > -qh centrum_radius)
    +      iscoplanar= True;
    +    if (!neighbor->center)
    +      neighbor->center= qh_getcentrum(neighbor);
    +    zzinc_(Zcentrumtests);
    +    qh_distplane(neighbor->center, facet, &dist2);
    +    if (dist2 > qh centrum_radius)
    +      isconcave= True;
    +    else if (!iscoplanar && dist2 > -qh centrum_radius)
    +      iscoplanar= True;
    +  }
    +  if (!isconcave && (!iscoplanar || (qh MERGEexact && !qh POSTmerging)))
    +    return False;
    +  if (!okangle && qh ANGLEmerge) {
    +    angle= qh_getangle(facet->normal, neighbor->normal);
    +    zinc_(Zangletests);
    +  }
    +  if (isconcave) {
    +    zinc_(Zconcaveridge);
    +    if (qh ANGLEmerge)
    +      angle += qh_ANGLEconcave + 0.5;
    +    qh_appendmergeset(facet, neighbor, MRGconcave, &angle);
    +    trace0((qh ferr, 18, "qh_test_appendmerge: concave f%d to f%d dist %4.4g and reverse dist %4.4g angle %4.4g during p%d\n",
    +           facet->id, neighbor->id, dist, dist2, angle, qh furthest_id));
    +  }else /* iscoplanar */ {
    +    zinc_(Zcoplanarcentrum);
    +    qh_appendmergeset(facet, neighbor, MRGcoplanar, &angle);
    +    trace2((qh ferr, 2040, "qh_test_appendmerge: coplanar f%d to f%d dist %4.4g, reverse dist %4.4g angle %4.4g\n",
    +              facet->id, neighbor->id, dist, dist2, angle));
    +  }
    +  return True;
    +} /* test_appendmerge */
    +
    +/*---------------------------------
    +
    +  qh_test_vneighbors()
    +    test vertex neighbors for convexity
    +    tests all facets on qh.newfacet_list
    +
    +  returns:
    +    true if non-convex vneighbors appended to qh.facet_mergeset
    +    initializes vertex neighbors if needed
    +
    +  notes:
    +    assumes all facet neighbors have been tested
    +    this can be expensive
    +    this does not guarantee that a centrum is below all facets
    +      but it is unlikely
    +    uses qh.visit_id
    +
    +  design:
    +    build vertex neighbors if necessary
    +    for all new facets
    +      for all vertices
    +        for each unvisited facet neighbor of the vertex
    +          test new facet and neighbor for convexity
    +*/
    +boolT qh_test_vneighbors(void /* qh.newfacet_list */) {
    +  facetT *newfacet, *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  int nummerges= 0;
    +
    +  trace1((qh ferr, 1015, "qh_test_vneighbors: testing vertex neighbors for convexity\n"));
    +  if (!qh VERTEXneighbors)
    +    qh_vertexneighbors();
    +  FORALLnew_facets
    +    newfacet->seen= False;
    +  FORALLnew_facets {
    +    newfacet->seen= True;
    +    newfacet->visitid= qh visit_id++;
    +    FOREACHneighbor_(newfacet)
    +      newfacet->visitid= qh visit_id;
    +    FOREACHvertex_(newfacet->vertices) {
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->seen || neighbor->visitid == qh visit_id)
    +          continue;
    +        if (qh_test_appendmerge(newfacet, neighbor))
    +          nummerges++;
    +      }
    +    }
    +  }
    +  zadd_(Ztestvneighbor, nummerges);
    +  trace1((qh ferr, 1016, "qh_test_vneighbors: found %d non-convex, vertex neighbors\n",
    +           nummerges));
    +  return (nummerges > 0);
    +} /* test_vneighbors */
    +
    +/*---------------------------------
    +
    +  qh_tracemerge( facet1, facet2 )
    +    print trace message after merge
    +*/
    +void qh_tracemerge(facetT *facet1, facetT *facet2) {
    +  boolT waserror= False;
    +
    +#ifndef qh_NOtrace
    +  if (qh IStracing >= 4)
    +    qh_errprint("MERGED", facet2, NULL, NULL, NULL);
    +  if (facet2 == qh tracefacet || (qh tracevertex && qh tracevertex->newlist)) {
    +    qh_fprintf(qh ferr, 8085, "qh_tracemerge: trace facet and vertex after merge of f%d and f%d, furthest p%d\n", facet1->id, facet2->id, qh furthest_id);
    +    if (facet2 != qh tracefacet)
    +      qh_errprint("TRACE", qh tracefacet,
    +        (qh tracevertex && qh tracevertex->neighbors) ?
    +           SETfirstt_(qh tracevertex->neighbors, facetT) : NULL,
    +        NULL, qh tracevertex);
    +  }
    +  if (qh tracevertex) {
    +    if (qh tracevertex->deleted)
    +      qh_fprintf(qh ferr, 8086, "qh_tracemerge: trace vertex deleted at furthest p%d\n",
    +            qh furthest_id);
    +    else
    +      qh_checkvertex(qh tracevertex);
    +  }
    +  if (qh tracefacet) {
    +    qh_checkfacet(qh tracefacet, True, &waserror);
    +    if (waserror)
    +      qh_errexit(qh_ERRqhull, qh tracefacet, NULL);
    +  }
    +#endif /* !qh_NOtrace */
    +  if (qh CHECKfrequently || qh IStracing >= 4) { /* can't check polygon here */
    +    qh_checkfacet(facet2, True, &waserror);
    +    if (waserror)
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +} /* tracemerge */
    +
    +/*---------------------------------
    +
    +  qh_tracemerging()
    +    print trace message during POSTmerging
    +
    +  returns:
    +    updates qh.mergereport
    +
    +  notes:
    +    called from qh_mergecycle() and qh_mergefacet()
    +
    +  see:
    +    qh_buildtracing()
    +*/
    +void qh_tracemerging(void) {
    +  realT cpu;
    +  int total;
    +  time_t timedata;
    +  struct tm *tp;
    +
    +  qh mergereport= zzval_(Ztotmerge);
    +  time(&timedata);
    +  tp= localtime(&timedata);
    +  cpu= qh_CPUclock;
    +  cpu /= qh_SECticks;
    +  total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +  qh_fprintf(qh ferr, 8087, "\n\
    +At %d:%d:%d & %2.5g CPU secs, qhull has merged %d facets.  The hull\n\
    +  contains %d facets and %d vertices.\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu,
    +      total, qh num_facets - qh num_visible,
    +      qh num_vertices-qh_setsize(qh del_vertices));
    +} /* tracemerging */
    +
    +/*---------------------------------
    +
    +  qh_updatetested( facet1, facet2 )
    +    clear facet2->tested and facet1->ridge->tested for merge
    +
    +  returns:
    +    deletes facet2->center unless it's already large
    +      if so, clears facet2->ridge->tested
    +
    +  design:
    +    clear facet2->tested
    +    clear ridge->tested for facet1's ridges
    +    if facet2 has a centrum
    +      if facet2 is large
    +        set facet2->keepcentrum
    +      else if facet2 has 3 vertices due to many merges, or not large and post merging
    +        clear facet2->keepcentrum
    +      unless facet2->keepcentrum
    +        clear facet2->center to recompute centrum later
    +        clear ridge->tested for facet2's ridges
    +*/
    +void qh_updatetested(facetT *facet1, facetT *facet2) {
    +  ridgeT *ridge, **ridgep;
    +  int size;
    +
    +  facet2->tested= False;
    +  FOREACHridge_(facet1->ridges)
    +    ridge->tested= False;
    +  if (!facet2->center)
    +    return;
    +  size= qh_setsize(facet2->vertices);
    +  if (!facet2->keepcentrum) {
    +    if (size > qh hull_dim + qh_MAXnewcentrum) {
    +      facet2->keepcentrum= True;
    +      zinc_(Zwidevertices);
    +    }
    +  }else if (size <= qh hull_dim + qh_MAXnewcentrum) {
    +    /* center and keepcentrum was set */
    +    if (size == qh hull_dim || qh POSTmerging)
    +      facet2->keepcentrum= False; /* if many merges need to recompute centrum */
    +  }
    +  if (!facet2->keepcentrum) {
    +    qh_memfree(facet2->center, qh normal_size);
    +    facet2->center= NULL;
    +    FOREACHridge_(facet2->ridges)
    +      ridge->tested= False;
    +  }
    +} /* updatetested */
    +
    +/*---------------------------------
    +
    +  qh_vertexridges( vertex )
    +    return temporary set of ridges adjacent to a vertex
    +    vertex->neighbors defined
    +
    +  ntoes:
    +    uses qh.visit_id
    +    does not include implicit ridges for simplicial facets
    +
    +  design:
    +    for each neighbor of vertex
    +      add ridges that include the vertex to ridges
    +*/
    +setT *qh_vertexridges(vertexT *vertex) {
    +  facetT *neighbor, **neighborp;
    +  setT *ridges= qh_settemp(qh TEMPsize);
    +  int size;
    +
    +  qh visit_id++;
    +  FOREACHneighbor_(vertex)
    +    neighbor->visitid= qh visit_id;
    +  FOREACHneighbor_(vertex) {
    +    if (*neighborp)   /* no new ridges in last neighbor */
    +      qh_vertexridges_facet(vertex, neighbor, &ridges);
    +  }
    +  if (qh PRINTstatistics || qh IStracing) {
    +    size= qh_setsize(ridges);
    +    zinc_(Zvertexridge);
    +    zadd_(Zvertexridgetot, size);
    +    zmax_(Zvertexridgemax, size);
    +    trace3((qh ferr, 3011, "qh_vertexridges: found %d ridges for v%d\n",
    +             size, vertex->id));
    +  }
    +  return ridges;
    +} /* vertexridges */
    +
    +/*---------------------------------
    +
    +  qh_vertexridges_facet( vertex, facet, ridges )
    +    add adjacent ridges for vertex in facet
    +    neighbor->visitid==qh.visit_id if it hasn't been visited
    +
    +  returns:
    +    ridges updated
    +    sets facet->visitid to qh.visit_id-1
    +
    +  design:
    +    for each ridge of facet
    +      if ridge of visited neighbor (i.e., unprocessed)
    +        if vertex in ridge
    +          append ridge to vertex
    +    mark facet processed
    +*/
    +void qh_vertexridges_facet(vertexT *vertex, facetT *facet, setT **ridges) {
    +  ridgeT *ridge, **ridgep;
    +  facetT *neighbor;
    +
    +  FOREACHridge_(facet->ridges) {
    +    neighbor= otherfacet_(ridge, facet);
    +    if (neighbor->visitid == qh visit_id
    +    && qh_setin(ridge->vertices, vertex))
    +      qh_setappend(ridges, ridge);
    +  }
    +  facet->visitid= qh visit_id-1;
    +} /* vertexridges_facet */
    +
    +/*---------------------------------
    +
    +  qh_willdelete( facet, replace )
    +    moves facet to visible list
    +    sets facet->f.replace to replace (may be NULL)
    +
    +  returns:
    +    bumps qh.num_visible
    +*/
    +void qh_willdelete(facetT *facet, facetT *replace) {
    +
    +  qh_removefacet(facet);
    +  qh_prependfacet(facet, &qh visible_list);
    +  qh num_visible++;
    +  facet->visible= True;
    +  facet->f.replace= replace;
    +} /* willdelete */
    +
    +#else /* qh_NOmerge */
    +void qh_premerge(vertexT *apex, realT maxcentrum, realT maxangle) {
    +}
    +void qh_postmerge(const char *reason, realT maxcentrum, realT maxangle,
    +                      boolT vneighbors) {
    +}
    +boolT qh_checkzero(boolT testall) {
    +   }
    +#endif /* qh_NOmerge */
    +
    diff --git a/xs/src/qhull/src/libqhull/merge.h b/xs/src/qhull/src/libqhull/merge.h
    new file mode 100644
    index 0000000000..7f5ec3fb61
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/merge.h
    @@ -0,0 +1,178 @@
    +/*
      ---------------------------------
    +
    +   merge.h
    +   header file for merge.c
    +
    +   see qh-merge.htm and merge.c
    +
    +   Copyright (c) 1993-2015 C.B. Barber.
    +   $Id: //main/2015/qhull/src/libqhull/merge.h#1 $$Change: 1981 $
    +   $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFmerge
    +#define qhDEFmerge 1
    +
    +#include "libqhull.h"
    +
    +
    +/*============ -constants- ==============*/
    +
    +/*----------------------------------
    +
    +  qh_ANGLEredundant
    +    indicates redundant merge in mergeT->angle
    +*/
    +#define qh_ANGLEredundant 6.0
    +
    +/*----------------------------------
    +
    +  qh_ANGLEdegen
    +    indicates degenerate facet in mergeT->angle
    +*/
    +#define qh_ANGLEdegen     5.0
    +
    +/*----------------------------------
    +
    +  qh_ANGLEconcave
    +    offset to indicate concave facets in mergeT->angle
    +
    +  notes:
    +    concave facets are assigned the range of [2,4] in mergeT->angle
    +    roundoff error may make the angle less than 2
    +*/
    +#define qh_ANGLEconcave  1.5
    +
    +/*----------------------------------
    +
    +  MRG... (mergeType)
    +    indicates the type of a merge (mergeT->type)
    +*/
    +typedef enum {  /* in sort order for facet_mergeset */
    +  MRGnone= 0,
    +  MRGcoplanar,          /* centrum coplanar */
    +  MRGanglecoplanar,     /* angle coplanar */
    +                        /* could detect half concave ridges */
    +  MRGconcave,           /* concave ridge */
    +  MRGflip,              /* flipped facet. facet1 == facet2 */
    +  MRGridge,             /* duplicate ridge (qh_MERGEridge) */
    +                        /* degen and redundant go onto degen_mergeset */
    +  MRGdegen,             /* degenerate facet (!enough neighbors) facet1 == facet2 */
    +  MRGredundant,         /* redundant facet (vertex subset) */
    +                        /* merge_degenredundant assumes degen < redundant */
    +  MRGmirror,            /* mirror facet from qh_triangulate */
    +  ENDmrg
    +} mergeType;
    +
    +/*----------------------------------
    +
    +  qh_MERGEapex
    +    flag for qh_mergefacet() to indicate an apex merge
    +*/
    +#define qh_MERGEapex     True
    +
    +/*============ -structures- ====================*/
    +
    +/*----------------------------------
    +
    +  mergeT
    +    structure used to merge facets
    +*/
    +
    +typedef struct mergeT mergeT;
    +struct mergeT {         /* initialize in qh_appendmergeset */
    +  realT   angle;        /* angle between normals of facet1 and facet2 */
    +  facetT *facet1;       /* will merge facet1 into facet2 */
    +  facetT *facet2;
    +  mergeType type;
    +};
    +
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  FOREACHmerge_( merges ) {...}
    +    assign 'merge' to each merge in merges
    +
    +  notes:
    +    uses 'mergeT *merge, **mergep;'
    +    if qh_mergefacet(),
    +      restart since qh.facet_mergeset may change
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHmerge_( merges ) FOREACHsetelement_(mergeT, merges, merge)
    +
    +/*============ prototypes in alphabetical order after pre/postmerge =======*/
    +
    +void    qh_premerge(vertexT *apex, realT maxcentrum, realT maxangle);
    +void    qh_postmerge(const char *reason, realT maxcentrum, realT maxangle,
    +             boolT vneighbors);
    +void    qh_all_merges(boolT othermerge, boolT vneighbors);
    +void    qh_appendmergeset(facetT *facet, facetT *neighbor, mergeType mergetype, realT *angle);
    +setT   *qh_basevertices( facetT *samecycle);
    +void    qh_checkconnect(void /* qh.new_facets */);
    +boolT   qh_checkzero(boolT testall);
    +int     qh_compareangle(const void *p1, const void *p2);
    +int     qh_comparemerge(const void *p1, const void *p2);
    +int     qh_comparevisit(const void *p1, const void *p2);
    +void    qh_copynonconvex(ridgeT *atridge);
    +void    qh_degen_redundant_facet(facetT *facet);
    +void    qh_degen_redundant_neighbors(facetT *facet, facetT *delfacet);
    +vertexT *qh_find_newvertex(vertexT *oldvertex, setT *vertices, setT *ridges);
    +void    qh_findbest_test(boolT testcentrum, facetT *facet, facetT *neighbor,
    +           facetT **bestfacet, realT *distp, realT *mindistp, realT *maxdistp);
    +facetT *qh_findbestneighbor(facetT *facet, realT *distp, realT *mindistp, realT *maxdistp);
    +void    qh_flippedmerges(facetT *facetlist, boolT *wasmerge);
    +void    qh_forcedmerges( boolT *wasmerge);
    +void    qh_getmergeset(facetT *facetlist);
    +void    qh_getmergeset_initial(facetT *facetlist);
    +void    qh_hashridge(setT *hashtable, int hashsize, ridgeT *ridge, vertexT *oldvertex);
    +ridgeT *qh_hashridge_find(setT *hashtable, int hashsize, ridgeT *ridge,
    +              vertexT *vertex, vertexT *oldvertex, int *hashslot);
    +void    qh_makeridges(facetT *facet);
    +void    qh_mark_dupridges(facetT *facetlist);
    +void    qh_maydropneighbor(facetT *facet);
    +int     qh_merge_degenredundant(void);
    +void    qh_merge_nonconvex( facetT *facet1, facetT *facet2, mergeType mergetype);
    +void    qh_mergecycle(facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_all(facetT *facetlist, boolT *wasmerge);
    +void    qh_mergecycle_facets( facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_neighbors(facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_ridges(facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_vneighbors( facetT *samecycle, facetT *newfacet);
    +void    qh_mergefacet(facetT *facet1, facetT *facet2, realT *mindist, realT *maxdist, boolT mergeapex);
    +void    qh_mergefacet2d(facetT *facet1, facetT *facet2);
    +void    qh_mergeneighbors(facetT *facet1, facetT *facet2);
    +void    qh_mergeridges(facetT *facet1, facetT *facet2);
    +void    qh_mergesimplex(facetT *facet1, facetT *facet2, boolT mergeapex);
    +void    qh_mergevertex_del(vertexT *vertex, facetT *facet1, facetT *facet2);
    +void    qh_mergevertex_neighbors(facetT *facet1, facetT *facet2);
    +void    qh_mergevertices(setT *vertices1, setT **vertices);
    +setT   *qh_neighbor_intersections(vertexT *vertex);
    +void    qh_newvertices(setT *vertices);
    +boolT   qh_reducevertices(void);
    +vertexT *qh_redundant_vertex(vertexT *vertex);
    +boolT   qh_remove_extravertices(facetT *facet);
    +vertexT *qh_rename_sharedvertex(vertexT *vertex, facetT *facet);
    +void    qh_renameridgevertex(ridgeT *ridge, vertexT *oldvertex, vertexT *newvertex);
    +void    qh_renamevertex(vertexT *oldvertex, vertexT *newvertex, setT *ridges,
    +                        facetT *oldfacet, facetT *neighborA);
    +boolT   qh_test_appendmerge(facetT *facet, facetT *neighbor);
    +boolT   qh_test_vneighbors(void /* qh.newfacet_list */);
    +void    qh_tracemerge(facetT *facet1, facetT *facet2);
    +void    qh_tracemerging(void);
    +void    qh_updatetested( facetT *facet1, facetT *facet2);
    +setT   *qh_vertexridges(vertexT *vertex);
    +void    qh_vertexridges_facet(vertexT *vertex, facetT *facet, setT **ridges);
    +void    qh_willdelete(facetT *facet, facetT *replace);
    +
    +#endif /* qhDEFmerge */
    diff --git a/xs/src/qhull/src/libqhull/poly.c b/xs/src/qhull/src/libqhull/poly.c
    new file mode 100644
    index 0000000000..b8db6a9ef7
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/poly.c
    @@ -0,0 +1,1205 @@
    +/*
      ---------------------------------
    +
    +   poly.c
    +   implements polygons and simplices
    +
    +   see qh-poly.htm, poly.h and libqhull.h
    +
    +   infrequent code is in poly2.c
    +   (all but top 50 and their callers 12/3/95)
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/poly.c#3 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*======== functions in alphabetical order ==========*/
    +
    +/*---------------------------------
    +
    +  qh_appendfacet( facet )
    +    appends facet to end of qh.facet_list,
    +
    +  returns:
    +    updates qh.newfacet_list, facet_next, facet_list
    +    increments qh.numfacets
    +
    +  notes:
    +    assumes qh.facet_list/facet_tail is defined (createsimplex)
    +
    +  see:
    +    qh_removefacet()
    +
    +*/
    +void qh_appendfacet(facetT *facet) {
    +  facetT *tail= qh facet_tail;
    +
    +  if (tail == qh newfacet_list)
    +    qh newfacet_list= facet;
    +  if (tail == qh facet_next)
    +    qh facet_next= facet;
    +  facet->previous= tail->previous;
    +  facet->next= tail;
    +  if (tail->previous)
    +    tail->previous->next= facet;
    +  else
    +    qh facet_list= facet;
    +  tail->previous= facet;
    +  qh num_facets++;
    +  trace4((qh ferr, 4044, "qh_appendfacet: append f%d to facet_list\n", facet->id));
    +} /* appendfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_appendvertex( vertex )
    +    appends vertex to end of qh.vertex_list,
    +
    +  returns:
    +    sets vertex->newlist
    +    updates qh.vertex_list, newvertex_list
    +    increments qh.num_vertices
    +
    +  notes:
    +    assumes qh.vertex_list/vertex_tail is defined (createsimplex)
    +
    +*/
    +void qh_appendvertex(vertexT *vertex) {
    +  vertexT *tail= qh vertex_tail;
    +
    +  if (tail == qh newvertex_list)
    +    qh newvertex_list= vertex;
    +  vertex->newlist= True;
    +  vertex->previous= tail->previous;
    +  vertex->next= tail;
    +  if (tail->previous)
    +    tail->previous->next= vertex;
    +  else
    +    qh vertex_list= vertex;
    +  tail->previous= vertex;
    +  qh num_vertices++;
    +  trace4((qh ferr, 4045, "qh_appendvertex: append v%d to vertex_list\n", vertex->id));
    +} /* appendvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_attachnewfacets( )
    +    attach horizon facets to new facets in qh.newfacet_list
    +    newfacets have neighbor and ridge links to horizon but not vice versa
    +    only needed for qh.ONLYgood
    +
    +  returns:
    +    set qh.NEWfacets
    +    horizon facets linked to new facets
    +      ridges changed from visible facets to new facets
    +      simplicial ridges deleted
    +    qh.visible_list, no ridges valid
    +    facet->f.replace is a newfacet (if any)
    +
    +  design:
    +    delete interior ridges and neighbor sets by
    +      for each visible, non-simplicial facet
    +        for each ridge
    +          if last visit or if neighbor is simplicial
    +            if horizon neighbor
    +              delete ridge for horizon's ridge set
    +            delete ridge
    +        erase neighbor set
    +    attach horizon facets and new facets by
    +      for all new facets
    +        if corresponding horizon facet is simplicial
    +          locate corresponding visible facet {may be more than one}
    +          link visible facet to new facet
    +          replace visible facet with new facet in horizon
    +        else it's non-simplicial
    +          for all visible neighbors of the horizon facet
    +            link visible neighbor to new facet
    +            delete visible neighbor from horizon facet
    +          append new facet to horizon's neighbors
    +          the first ridge of the new facet is the horizon ridge
    +          link the new facet into the horizon ridge
    +*/
    +void qh_attachnewfacets(void /* qh.visible_list, newfacet_list */) {
    +  facetT *newfacet= NULL, *neighbor, **neighborp, *horizon, *visible;
    +  ridgeT *ridge, **ridgep;
    +
    +  qh NEWfacets= True;
    +  trace3((qh ferr, 3012, "qh_attachnewfacets: delete interior ridges\n"));
    +  qh visit_id++;
    +  FORALLvisible_facets {
    +    visible->visitid= qh visit_id;
    +    if (visible->ridges) {
    +      FOREACHridge_(visible->ridges) {
    +        neighbor= otherfacet_(ridge, visible);
    +        if (neighbor->visitid == qh visit_id
    +            || (!neighbor->visible && neighbor->simplicial)) {
    +          if (!neighbor->visible)  /* delete ridge for simplicial horizon */
    +            qh_setdel(neighbor->ridges, ridge);
    +          qh_setfree(&(ridge->vertices)); /* delete on 2nd visit */
    +          qh_memfree(ridge, (int)sizeof(ridgeT));
    +        }
    +      }
    +      SETfirst_(visible->ridges)= NULL;
    +    }
    +    SETfirst_(visible->neighbors)= NULL;
    +  }
    +  trace1((qh ferr, 1017, "qh_attachnewfacets: attach horizon facets to new facets\n"));
    +  FORALLnew_facets {
    +    horizon= SETfirstt_(newfacet->neighbors, facetT);
    +    if (horizon->simplicial) {
    +      visible= NULL;
    +      FOREACHneighbor_(horizon) {   /* may have more than one horizon ridge */
    +        if (neighbor->visible) {
    +          if (visible) {
    +            if (qh_setequal_skip(newfacet->vertices, 0, horizon->vertices,
    +                                  SETindex_(horizon->neighbors, neighbor))) {
    +              visible= neighbor;
    +              break;
    +            }
    +          }else
    +            visible= neighbor;
    +        }
    +      }
    +      if (visible) {
    +        visible->f.replace= newfacet;
    +        qh_setreplace(horizon->neighbors, visible, newfacet);
    +      }else {
    +        qh_fprintf(qh ferr, 6102, "qhull internal error (qh_attachnewfacets): couldn't find visible facet for horizon f%d of newfacet f%d\n",
    +                 horizon->id, newfacet->id);
    +        qh_errexit2(qh_ERRqhull, horizon, newfacet);
    +      }
    +    }else { /* non-simplicial, with a ridge for newfacet */
    +      FOREACHneighbor_(horizon) {    /* may hold for many new facets */
    +        if (neighbor->visible) {
    +          neighbor->f.replace= newfacet;
    +          qh_setdelnth(horizon->neighbors,
    +                        SETindex_(horizon->neighbors, neighbor));
    +          neighborp--; /* repeat */
    +        }
    +      }
    +      qh_setappend(&horizon->neighbors, newfacet);
    +      ridge= SETfirstt_(newfacet->ridges, ridgeT);
    +      if (ridge->top == horizon)
    +        ridge->bottom= newfacet;
    +      else
    +        ridge->top= newfacet;
    +      }
    +  } /* newfacets */
    +  if (qh PRINTstatistics) {
    +    FORALLvisible_facets {
    +      if (!visible->f.replace)
    +        zinc_(Zinsidevisible);
    +    }
    +  }
    +} /* attachnewfacets */
    +
    +/*---------------------------------
    +
    +  qh_checkflipped( facet, dist, allerror )
    +    checks facet orientation to interior point
    +
    +    if allerror set,
    +      tests against qh.DISTround
    +    else
    +      tests against 0 since tested against DISTround before
    +
    +  returns:
    +    False if it flipped orientation (sets facet->flipped)
    +    distance if non-NULL
    +*/
    +boolT qh_checkflipped(facetT *facet, realT *distp, boolT allerror) {
    +  realT dist;
    +
    +  if (facet->flipped && !distp)
    +    return False;
    +  zzinc_(Zdistcheck);
    +  qh_distplane(qh interior_point, facet, &dist);
    +  if (distp)
    +    *distp= dist;
    +  if ((allerror && dist > -qh DISTround)|| (!allerror && dist >= 0.0)) {
    +    facet->flipped= True;
    +    zzinc_(Zflippedfacets);
    +    trace0((qh ferr, 19, "qh_checkflipped: facet f%d is flipped, distance= %6.12g during p%d\n",
    +              facet->id, dist, qh furthest_id));
    +    qh_precision("flipped facet");
    +    return False;
    +  }
    +  return True;
    +} /* checkflipped */
    +
    +/*---------------------------------
    +
    +  qh_delfacet( facet )
    +    removes facet from facet_list and frees up its memory
    +
    +  notes:
    +    assumes vertices and ridges already freed
    +*/
    +void qh_delfacet(facetT *facet) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  trace4((qh ferr, 4046, "qh_delfacet: delete f%d\n", facet->id));
    +  if (facet == qh tracefacet)
    +    qh tracefacet= NULL;
    +  if (facet == qh GOODclosest)
    +    qh GOODclosest= NULL;
    +  qh_removefacet(facet);
    +  if (!facet->tricoplanar || facet->keepcentrum) {
    +    qh_memfree_(facet->normal, qh normal_size, freelistp);
    +    if (qh CENTERtype == qh_ASvoronoi) {   /* braces for macro calls */
    +      qh_memfree_(facet->center, qh center_size, freelistp);
    +    }else /* AScentrum */ {
    +      qh_memfree_(facet->center, qh normal_size, freelistp);
    +    }
    +  }
    +  qh_setfree(&(facet->neighbors));
    +  if (facet->ridges)
    +    qh_setfree(&(facet->ridges));
    +  qh_setfree(&(facet->vertices));
    +  if (facet->outsideset)
    +    qh_setfree(&(facet->outsideset));
    +  if (facet->coplanarset)
    +    qh_setfree(&(facet->coplanarset));
    +  qh_memfree_(facet, (int)sizeof(facetT), freelistp);
    +} /* delfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_deletevisible()
    +    delete visible facets and vertices
    +
    +  returns:
    +    deletes each facet and removes from facetlist
    +    at exit, qh.visible_list empty (== qh.newfacet_list)
    +
    +  notes:
    +    ridges already deleted
    +    horizon facets do not reference facets on qh.visible_list
    +    new facets in qh.newfacet_list
    +    uses   qh.visit_id;
    +*/
    +void qh_deletevisible(void /*qh.visible_list*/) {
    +  facetT *visible, *nextfacet;
    +  vertexT *vertex, **vertexp;
    +  int numvisible= 0, numdel= qh_setsize(qh del_vertices);
    +
    +  trace1((qh ferr, 1018, "qh_deletevisible: delete %d visible facets and %d vertices\n",
    +         qh num_visible, numdel));
    +  for (visible= qh visible_list; visible && visible->visible;
    +                visible= nextfacet) { /* deleting current */
    +    nextfacet= visible->next;
    +    numvisible++;
    +    qh_delfacet(visible);
    +  }
    +  if (numvisible != qh num_visible) {
    +    qh_fprintf(qh ferr, 6103, "qhull internal error (qh_deletevisible): qh num_visible %d is not number of visible facets %d\n",
    +             qh num_visible, numvisible);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh num_visible= 0;
    +  zadd_(Zvisfacettot, numvisible);
    +  zmax_(Zvisfacetmax, numvisible);
    +  zzadd_(Zdelvertextot, numdel);
    +  zmax_(Zdelvertexmax, numdel);
    +  FOREACHvertex_(qh del_vertices)
    +    qh_delvertex(vertex);
    +  qh_settruncate(qh del_vertices, 0);
    +} /* deletevisible */
    +
    +/*---------------------------------
    +
    +  qh_facetintersect( facetA, facetB, skipa, skipB, prepend )
    +    return vertices for intersection of two simplicial facets
    +    may include 1 prepended entry (if more, need to settemppush)
    +
    +  returns:
    +    returns set of qh.hull_dim-1 + prepend vertices
    +    returns skipped index for each test and checks for exactly one
    +
    +  notes:
    +    does not need settemp since set in quick memory
    +
    +  see also:
    +    qh_vertexintersect and qh_vertexintersect_new
    +    use qh_setnew_delnthsorted to get nth ridge (no skip information)
    +
    +  design:
    +    locate skipped vertex by scanning facet A's neighbors
    +    locate skipped vertex by scanning facet B's neighbors
    +    intersect the vertex sets
    +*/
    +setT *qh_facetintersect(facetT *facetA, facetT *facetB,
    +                         int *skipA,int *skipB, int prepend) {
    +  setT *intersect;
    +  int dim= qh hull_dim, i, j;
    +  facetT **neighborsA, **neighborsB;
    +
    +  neighborsA= SETaddr_(facetA->neighbors, facetT);
    +  neighborsB= SETaddr_(facetB->neighbors, facetT);
    +  i= j= 0;
    +  if (facetB == *neighborsA++)
    +    *skipA= 0;
    +  else if (facetB == *neighborsA++)
    +    *skipA= 1;
    +  else if (facetB == *neighborsA++)
    +    *skipA= 2;
    +  else {
    +    for (i=3; i < dim; i++) {
    +      if (facetB == *neighborsA++) {
    +        *skipA= i;
    +        break;
    +      }
    +    }
    +  }
    +  if (facetA == *neighborsB++)
    +    *skipB= 0;
    +  else if (facetA == *neighborsB++)
    +    *skipB= 1;
    +  else if (facetA == *neighborsB++)
    +    *skipB= 2;
    +  else {
    +    for (j=3; j < dim; j++) {
    +      if (facetA == *neighborsB++) {
    +        *skipB= j;
    +        break;
    +      }
    +    }
    +  }
    +  if (i >= dim || j >= dim) {
    +    qh_fprintf(qh ferr, 6104, "qhull internal error (qh_facetintersect): f%d or f%d not in others neighbors\n",
    +            facetA->id, facetB->id);
    +    qh_errexit2(qh_ERRqhull, facetA, facetB);
    +  }
    +  intersect= qh_setnew_delnthsorted(facetA->vertices, qh hull_dim, *skipA, prepend);
    +  trace4((qh ferr, 4047, "qh_facetintersect: f%d skip %d matches f%d skip %d\n",
    +          facetA->id, *skipA, facetB->id, *skipB));
    +  return(intersect);
    +} /* facetintersect */
    +
    +/*---------------------------------
    +
    +  qh_gethash( hashsize, set, size, firstindex, skipelem )
    +    return hashvalue for a set with firstindex and skipelem
    +
    +  notes:
    +    returned hash is in [0,hashsize)
    +    assumes at least firstindex+1 elements
    +    assumes skipelem is NULL, in set, or part of hash
    +
    +    hashes memory addresses which may change over different runs of the same data
    +    using sum for hash does badly in high d
    +*/
    +int qh_gethash(int hashsize, setT *set, int size, int firstindex, void *skipelem) {
    +  void **elemp= SETelemaddr_(set, firstindex, void);
    +  ptr_intT hash = 0, elem;
    +  unsigned result;
    +  int i;
    +#ifdef _MSC_VER                   /* Microsoft Visual C++ -- warn about 64-bit issues */
    +#pragma warning( push)            /* WARN64 -- ptr_intT holds a 64-bit pointer */
    +#pragma warning( disable : 4311)  /* 'type cast': pointer truncation from 'void*' to 'ptr_intT' */
    +#endif
    +
    +  switch (size-firstindex) {
    +  case 1:
    +    hash= (ptr_intT)(*elemp) - (ptr_intT) skipelem;
    +    break;
    +  case 2:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] - (ptr_intT) skipelem;
    +    break;
    +  case 3:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      - (ptr_intT) skipelem;
    +    break;
    +  case 4:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] - (ptr_intT) skipelem;
    +    break;
    +  case 5:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] + (ptr_intT)elemp[4] - (ptr_intT) skipelem;
    +    break;
    +  case 6:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] + (ptr_intT)elemp[4]+ (ptr_intT)elemp[5]
    +      - (ptr_intT) skipelem;
    +    break;
    +  default:
    +    hash= 0;
    +    i= 3;
    +    do {     /* this is about 10% in 10-d */
    +      if ((elem= (ptr_intT)*elemp++) != (ptr_intT)skipelem) {
    +        hash ^= (elem << i) + (elem >> (32-i));
    +        i += 3;
    +        if (i >= 32)
    +          i -= 32;
    +      }
    +    }while (*elemp);
    +    break;
    +  }
    +  if (hashsize<0) {
    +    qh_fprintf(qh ferr, 6202, "qhull internal error: negative hashsize %d passed to qh_gethash [poly.c]\n", hashsize);
    +    qh_errexit2(qh_ERRqhull, NULL, NULL);
    +  }
    +  result= (unsigned)hash;
    +  result %= (unsigned)hashsize;
    +  /* result= 0; for debugging */
    +  return result;
    +#ifdef _MSC_VER
    +#pragma warning( pop)
    +#endif
    +} /* gethash */
    +
    +/*---------------------------------
    +
    +  qh_makenewfacet( vertices, toporient, horizon )
    +    creates a toporient? facet from vertices
    +
    +  returns:
    +    returns newfacet
    +      adds newfacet to qh.facet_list
    +      newfacet->vertices= vertices
    +      if horizon
    +        newfacet->neighbor= horizon, but not vice versa
    +    newvertex_list updated with vertices
    +*/
    +facetT *qh_makenewfacet(setT *vertices, boolT toporient,facetT *horizon) {
    +  facetT *newfacet;
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->newlist) {
    +      qh_removevertex(vertex);
    +      qh_appendvertex(vertex);
    +    }
    +  }
    +  newfacet= qh_newfacet();
    +  newfacet->vertices= vertices;
    +  newfacet->toporient= (unsigned char)toporient;
    +  if (horizon)
    +    qh_setappend(&(newfacet->neighbors), horizon);
    +  qh_appendfacet(newfacet);
    +  return(newfacet);
    +} /* makenewfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_makenewplanes()
    +    make new hyperplanes for facets on qh.newfacet_list
    +
    +  returns:
    +    all facets have hyperplanes or are marked for   merging
    +    doesn't create hyperplane if horizon is coplanar (will merge)
    +    updates qh.min_vertex if qh.JOGGLEmax
    +
    +  notes:
    +    facet->f.samecycle is defined for facet->mergehorizon facets
    +*/
    +void qh_makenewplanes(void /* newfacet_list */) {
    +  facetT *newfacet;
    +
    +  FORALLnew_facets {
    +    if (!newfacet->mergehorizon)
    +      qh_setfacetplane(newfacet);
    +  }
    +  if (qh JOGGLEmax < REALmax/2)
    +    minimize_(qh min_vertex, -wwval_(Wnewvertexmax));
    +} /* makenewplanes */
    +
    +/*---------------------------------
    +
    +  qh_makenew_nonsimplicial( visible, apex, numnew )
    +    make new facets for ridges of a visible facet
    +
    +  returns:
    +    first newfacet, bumps numnew as needed
    +    attaches new facets if !qh.ONLYgood
    +    marks ridge neighbors for simplicial visible
    +    if (qh.ONLYgood)
    +      ridges on newfacet, horizon, and visible
    +    else
    +      ridge and neighbors between newfacet and   horizon
    +      visible facet's ridges are deleted
    +
    +  notes:
    +    qh.visit_id if visible has already been processed
    +    sets neighbor->seen for building f.samecycle
    +      assumes all 'seen' flags initially false
    +
    +  design:
    +    for each ridge of visible facet
    +      get neighbor of visible facet
    +      if neighbor was already processed
    +        delete the ridge (will delete all visible facets later)
    +      if neighbor is a horizon facet
    +        create a new facet
    +        if neighbor coplanar
    +          adds newfacet to f.samecycle for later merging
    +        else
    +          updates neighbor's neighbor set
    +          (checks for non-simplicial facet with multiple ridges to visible facet)
    +        updates neighbor's ridge set
    +        (checks for simplicial neighbor to non-simplicial visible facet)
    +        (deletes ridge if neighbor is simplicial)
    +
    +*/
    +#ifndef qh_NOmerge
    +facetT *qh_makenew_nonsimplicial(facetT *visible, vertexT *apex, int *numnew) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +  ridgeT *ridge, **ridgep;
    +  facetT *neighbor, *newfacet= NULL, *samecycle;
    +  setT *vertices;
    +  boolT toporient;
    +  int ridgeid;
    +
    +  FOREACHridge_(visible->ridges) {
    +    ridgeid= ridge->id;
    +    neighbor= otherfacet_(ridge, visible);
    +    if (neighbor->visible) {
    +      if (!qh ONLYgood) {
    +        if (neighbor->visitid == qh visit_id) {
    +          qh_setfree(&(ridge->vertices));  /* delete on 2nd visit */
    +          qh_memfree_(ridge, (int)sizeof(ridgeT), freelistp);
    +        }
    +      }
    +    }else {  /* neighbor is an horizon facet */
    +      toporient= (ridge->top == visible);
    +      vertices= qh_setnew(qh hull_dim); /* makes sure this is quick */
    +      qh_setappend(&vertices, apex);
    +      qh_setappend_set(&vertices, ridge->vertices);
    +      newfacet= qh_makenewfacet(vertices, toporient, neighbor);
    +      (*numnew)++;
    +      if (neighbor->coplanar) {
    +        newfacet->mergehorizon= True;
    +        if (!neighbor->seen) {
    +          newfacet->f.samecycle= newfacet;
    +          neighbor->f.newcycle= newfacet;
    +        }else {
    +          samecycle= neighbor->f.newcycle;
    +          newfacet->f.samecycle= samecycle->f.samecycle;
    +          samecycle->f.samecycle= newfacet;
    +        }
    +      }
    +      if (qh ONLYgood) {
    +        if (!neighbor->simplicial)
    +          qh_setappend(&(newfacet->ridges), ridge);
    +      }else {  /* qh_attachnewfacets */
    +        if (neighbor->seen) {
    +          if (neighbor->simplicial) {
    +            qh_fprintf(qh ferr, 6105, "qhull internal error (qh_makenew_nonsimplicial): simplicial f%d sharing two ridges with f%d\n",
    +                   neighbor->id, visible->id);
    +            qh_errexit2(qh_ERRqhull, neighbor, visible);
    +          }
    +          qh_setappend(&(neighbor->neighbors), newfacet);
    +        }else
    +          qh_setreplace(neighbor->neighbors, visible, newfacet);
    +        if (neighbor->simplicial) {
    +          qh_setdel(neighbor->ridges, ridge);
    +          qh_setfree(&(ridge->vertices));
    +          qh_memfree(ridge, (int)sizeof(ridgeT));
    +        }else {
    +          qh_setappend(&(newfacet->ridges), ridge);
    +          if (toporient)
    +            ridge->top= newfacet;
    +          else
    +            ridge->bottom= newfacet;
    +        }
    +      trace4((qh ferr, 4048, "qh_makenew_nonsimplicial: created facet f%d from v%d and r%d of horizon f%d\n",
    +            newfacet->id, apex->id, ridgeid, neighbor->id));
    +      }
    +    }
    +    neighbor->seen= True;
    +  } /* for each ridge */
    +  if (!qh ONLYgood)
    +    SETfirst_(visible->ridges)= NULL;
    +  return newfacet;
    +} /* makenew_nonsimplicial */
    +#else /* qh_NOmerge */
    +facetT *qh_makenew_nonsimplicial(facetT *visible, vertexT *apex, int *numnew) {
    +  return NULL;
    +}
    +#endif /* qh_NOmerge */
    +
    +/*---------------------------------
    +
    +  qh_makenew_simplicial( visible, apex, numnew )
    +    make new facets for simplicial visible facet and apex
    +
    +  returns:
    +    attaches new facets if (!qh.ONLYgood)
    +      neighbors between newfacet and horizon
    +
    +  notes:
    +    nop if neighbor->seen or neighbor->visible(see qh_makenew_nonsimplicial)
    +
    +  design:
    +    locate neighboring horizon facet for visible facet
    +    determine vertices and orientation
    +    create new facet
    +    if coplanar,
    +      add new facet to f.samecycle
    +    update horizon facet's neighbor list
    +*/
    +facetT *qh_makenew_simplicial(facetT *visible, vertexT *apex, int *numnew) {
    +  facetT *neighbor, **neighborp, *newfacet= NULL;
    +  setT *vertices;
    +  boolT flip, toporient;
    +  int horizonskip= 0, visibleskip= 0;
    +
    +  FOREACHneighbor_(visible) {
    +    if (!neighbor->seen && !neighbor->visible) {
    +      vertices= qh_facetintersect(neighbor,visible, &horizonskip, &visibleskip, 1);
    +      SETfirst_(vertices)= apex;
    +      flip= ((horizonskip & 0x1) ^ (visibleskip & 0x1));
    +      if (neighbor->toporient)
    +        toporient= horizonskip & 0x1;
    +      else
    +        toporient= (horizonskip & 0x1) ^ 0x1;
    +      newfacet= qh_makenewfacet(vertices, toporient, neighbor);
    +      (*numnew)++;
    +      if (neighbor->coplanar && (qh PREmerge || qh MERGEexact)) {
    +#ifndef qh_NOmerge
    +        newfacet->f.samecycle= newfacet;
    +        newfacet->mergehorizon= True;
    +#endif
    +      }
    +      if (!qh ONLYgood)
    +        SETelem_(neighbor->neighbors, horizonskip)= newfacet;
    +      trace4((qh ferr, 4049, "qh_makenew_simplicial: create facet f%d top %d from v%d and horizon f%d skip %d top %d and visible f%d skip %d, flip? %d\n",
    +            newfacet->id, toporient, apex->id, neighbor->id, horizonskip,
    +              neighbor->toporient, visible->id, visibleskip, flip));
    +    }
    +  }
    +  return newfacet;
    +} /* makenew_simplicial */
    +
    +/*---------------------------------
    +
    +  qh_matchneighbor( newfacet, newskip, hashsize, hashcount )
    +    either match subridge of newfacet with neighbor or add to hash_table
    +
    +  returns:
    +    duplicate ridges are unmatched and marked by qh_DUPLICATEridge
    +
    +  notes:
    +    ridge is newfacet->vertices w/o newskip vertex
    +    do not allocate memory (need to free hash_table cleanly)
    +    uses linear hash chains
    +
    +  see also:
    +    qh_matchduplicates
    +
    +  design:
    +    for each possible matching facet in qh.hash_table
    +      if vertices match
    +        set ismatch, if facets have opposite orientation
    +        if ismatch and matching facet doesn't have a match
    +          match the facets by updating their neighbor sets
    +        else
    +          indicate a duplicate ridge
    +          set facet hyperplane for later testing
    +          add facet to hashtable
    +          unless the other facet was already a duplicate ridge
    +            mark both facets with a duplicate ridge
    +            add other facet (if defined) to hash table
    +*/
    +void qh_matchneighbor(facetT *newfacet, int newskip, int hashsize, int *hashcount) {
    +  boolT newfound= False;   /* True, if new facet is already in hash chain */
    +  boolT same, ismatch;
    +  int hash, scan;
    +  facetT *facet, *matchfacet;
    +  int skip, matchskip;
    +
    +  hash= qh_gethash(hashsize, newfacet->vertices, qh hull_dim, 1,
    +                     SETelem_(newfacet->vertices, newskip));
    +  trace4((qh ferr, 4050, "qh_matchneighbor: newfacet f%d skip %d hash %d hashcount %d\n",
    +          newfacet->id, newskip, hash, *hashcount));
    +  zinc_(Zhashlookup);
    +  for (scan= hash; (facet= SETelemt_(qh hash_table, scan, facetT));
    +       scan= (++scan >= hashsize ? 0 : scan)) {
    +    if (facet == newfacet) {
    +      newfound= True;
    +      continue;
    +    }
    +    zinc_(Zhashtests);
    +    if (qh_matchvertices(1, newfacet->vertices, newskip, facet->vertices, &skip, &same)) {
    +      if (SETelem_(newfacet->vertices, newskip) ==
    +          SETelem_(facet->vertices, skip)) {
    +        qh_precision("two facets with the same vertices");
    +        qh_fprintf(qh ferr, 6106, "qhull precision error: Vertex sets are the same for f%d and f%d.  Can not force output.\n",
    +          facet->id, newfacet->id);
    +        qh_errexit2(qh_ERRprec, facet, newfacet);
    +      }
    +      ismatch= (same == (boolT)((newfacet->toporient ^ facet->toporient)));
    +      matchfacet= SETelemt_(facet->neighbors, skip, facetT);
    +      if (ismatch && !matchfacet) {
    +        SETelem_(facet->neighbors, skip)= newfacet;
    +        SETelem_(newfacet->neighbors, newskip)= facet;
    +        (*hashcount)--;
    +        trace4((qh ferr, 4051, "qh_matchneighbor: f%d skip %d matched with new f%d skip %d\n",
    +           facet->id, skip, newfacet->id, newskip));
    +        return;
    +      }
    +      if (!qh PREmerge && !qh MERGEexact) {
    +        qh_precision("a ridge with more than two neighbors");
    +        qh_fprintf(qh ferr, 6107, "qhull precision error: facets f%d, f%d and f%d meet at a ridge with more than 2 neighbors.  Can not continue.\n",
    +                 facet->id, newfacet->id, getid_(matchfacet));
    +        qh_errexit2(qh_ERRprec, facet, newfacet);
    +      }
    +      SETelem_(newfacet->neighbors, newskip)= qh_DUPLICATEridge;
    +      newfacet->dupridge= True;
    +      if (!newfacet->normal)
    +        qh_setfacetplane(newfacet);
    +      qh_addhash(newfacet, qh hash_table, hashsize, hash);
    +      (*hashcount)++;
    +      if (!facet->normal)
    +        qh_setfacetplane(facet);
    +      if (matchfacet != qh_DUPLICATEridge) {
    +        SETelem_(facet->neighbors, skip)= qh_DUPLICATEridge;
    +        facet->dupridge= True;
    +        if (!facet->normal)
    +          qh_setfacetplane(facet);
    +        if (matchfacet) {
    +          matchskip= qh_setindex(matchfacet->neighbors, facet);
    +          if (matchskip<0) {
    +              qh_fprintf(qh ferr, 6260, "qhull internal error (qh_matchneighbor): matchfacet f%d is in f%d neighbors but not vice versa.  Can not continue.\n",
    +                  matchfacet->id, facet->id);
    +              qh_errexit2(qh_ERRqhull, matchfacet, facet);
    +          }
    +          SETelem_(matchfacet->neighbors, matchskip)= qh_DUPLICATEridge; /* matchskip>=0 by QH6260 */
    +          matchfacet->dupridge= True;
    +          if (!matchfacet->normal)
    +            qh_setfacetplane(matchfacet);
    +          qh_addhash(matchfacet, qh hash_table, hashsize, hash);
    +          *hashcount += 2;
    +        }
    +      }
    +      trace4((qh ferr, 4052, "qh_matchneighbor: new f%d skip %d duplicates ridge for f%d skip %d matching f%d ismatch %d at hash %d\n",
    +           newfacet->id, newskip, facet->id, skip,
    +           (matchfacet == qh_DUPLICATEridge ? -2 : getid_(matchfacet)),
    +           ismatch, hash));
    +      return; /* end of duplicate ridge */
    +    }
    +  }
    +  if (!newfound)
    +    SETelem_(qh hash_table, scan)= newfacet;  /* same as qh_addhash */
    +  (*hashcount)++;
    +  trace4((qh ferr, 4053, "qh_matchneighbor: no match for f%d skip %d at hash %d\n",
    +           newfacet->id, newskip, hash));
    +} /* matchneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_matchnewfacets()
    +    match newfacets in qh.newfacet_list to their newfacet neighbors
    +
    +  returns:
    +    qh.newfacet_list with full neighbor sets
    +      get vertices with nth neighbor by deleting nth vertex
    +    if qh.PREmerge/MERGEexact or qh.FORCEoutput
    +      sets facet->flippped if flipped normal (also prevents point partitioning)
    +    if duplicate ridges and qh.PREmerge/MERGEexact
    +      sets facet->dupridge
    +      missing neighbor links identifies extra ridges to be merging (qh_MERGEridge)
    +
    +  notes:
    +    newfacets already have neighbor[0] (horizon facet)
    +    assumes qh.hash_table is NULL
    +    vertex->neighbors has not been updated yet
    +    do not allocate memory after qh.hash_table (need to free it cleanly)
    +
    +  design:
    +    delete neighbor sets for all new facets
    +    initialize a hash table
    +    for all new facets
    +      match facet with neighbors
    +    if unmatched facets (due to duplicate ridges)
    +      for each new facet with a duplicate ridge
    +        match it with a facet
    +    check for flipped facets
    +*/
    +void qh_matchnewfacets(void /* qh.newfacet_list */) {
    +  int numnew=0, hashcount=0, newskip;
    +  facetT *newfacet, *neighbor;
    +  int dim= qh hull_dim, hashsize, neighbor_i, neighbor_n;
    +  setT *neighbors;
    +#ifndef qh_NOtrace
    +  int facet_i, facet_n, numfree= 0;
    +  facetT *facet;
    +#endif
    +
    +  trace1((qh ferr, 1019, "qh_matchnewfacets: match neighbors for new facets.\n"));
    +  FORALLnew_facets {
    +    numnew++;
    +    {  /* inline qh_setzero(newfacet->neighbors, 1, qh hull_dim); */
    +      neighbors= newfacet->neighbors;
    +      neighbors->e[neighbors->maxsize].i= dim+1; /*may be overwritten*/
    +      memset((char *)SETelemaddr_(neighbors, 1, void), 0, dim * SETelemsize);
    +    }
    +  }
    +
    +  qh_newhashtable(numnew*(qh hull_dim-1)); /* twice what is normally needed,
    +                                     but every ridge could be DUPLICATEridge */
    +  hashsize= qh_setsize(qh hash_table);
    +  FORALLnew_facets {
    +    for (newskip=1; newskip0 because hull_dim>1 and numnew>0 */
    +      qh_matchneighbor(newfacet, newskip, hashsize, &hashcount);
    +#if 0   /* use the following to trap hashcount errors */
    +    {
    +      int count= 0, k;
    +      facetT *facet, *neighbor;
    +
    +      count= 0;
    +      FORALLfacet_(qh newfacet_list) {  /* newfacet already in use */
    +        for (k=1; k < qh hull_dim; k++) {
    +          neighbor= SETelemt_(facet->neighbors, k, facetT);
    +          if (!neighbor || neighbor == qh_DUPLICATEridge)
    +            count++;
    +        }
    +        if (facet == newfacet)
    +          break;
    +      }
    +      if (count != hashcount) {
    +        qh_fprintf(qh ferr, 8088, "qh_matchnewfacets: after adding facet %d, hashcount %d != count %d\n",
    +                 newfacet->id, hashcount, count);
    +        qh_errexit(qh_ERRqhull, newfacet, NULL);
    +      }
    +    }
    +#endif  /* end of trap code */
    +  }
    +  if (hashcount) {
    +    FORALLnew_facets {
    +      if (newfacet->dupridge) {
    +        FOREACHneighbor_i_(newfacet) {
    +          if (neighbor == qh_DUPLICATEridge) {
    +            qh_matchduplicates(newfacet, neighbor_i, hashsize, &hashcount);
    +                    /* this may report MERGEfacet */
    +          }
    +        }
    +      }
    +    }
    +  }
    +  if (hashcount) {
    +    qh_fprintf(qh ferr, 6108, "qhull internal error (qh_matchnewfacets): %d neighbors did not match up\n",
    +        hashcount);
    +    qh_printhashtable(qh ferr);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +#ifndef qh_NOtrace
    +  if (qh IStracing >= 2) {
    +    FOREACHfacet_i_(qh hash_table) {
    +      if (!facet)
    +        numfree++;
    +    }
    +    qh_fprintf(qh ferr, 8089, "qh_matchnewfacets: %d new facets, %d unused hash entries .  hashsize %d\n",
    +             numnew, numfree, qh_setsize(qh hash_table));
    +  }
    +#endif /* !qh_NOtrace */
    +  qh_setfree(&qh hash_table);
    +  if (qh PREmerge || qh MERGEexact) {
    +    if (qh IStracing >= 4)
    +      qh_printfacetlist(qh newfacet_list, NULL, qh_ALL);
    +    FORALLnew_facets {
    +      if (newfacet->normal)
    +        qh_checkflipped(newfacet, NULL, qh_ALL);
    +    }
    +  }else if (qh FORCEoutput)
    +    qh_checkflipped_all(qh newfacet_list);  /* prints warnings for flipped */
    +} /* matchnewfacets */
    +
    +
    +/*---------------------------------
    +
    +  qh_matchvertices( firstindex, verticesA, skipA, verticesB, skipB, same )
    +    tests whether vertices match with a single skip
    +    starts match at firstindex since all new facets have a common vertex
    +
    +  returns:
    +    true if matched vertices
    +    skip index for each set
    +    sets same iff vertices have the same orientation
    +
    +  notes:
    +    assumes skipA is in A and both sets are the same size
    +
    +  design:
    +    set up pointers
    +    scan both sets checking for a match
    +    test orientation
    +*/
    +boolT qh_matchvertices(int firstindex, setT *verticesA, int skipA,
    +       setT *verticesB, int *skipB, boolT *same) {
    +  vertexT **elemAp, **elemBp, **skipBp=NULL, **skipAp;
    +
    +  elemAp= SETelemaddr_(verticesA, firstindex, vertexT);
    +  elemBp= SETelemaddr_(verticesB, firstindex, vertexT);
    +  skipAp= SETelemaddr_(verticesA, skipA, vertexT);
    +  do if (elemAp != skipAp) {
    +    while (*elemAp != *elemBp++) {
    +      if (skipBp)
    +        return False;
    +      skipBp= elemBp;  /* one extra like FOREACH */
    +    }
    +  }while (*(++elemAp));
    +  if (!skipBp)
    +    skipBp= ++elemBp;
    +  *skipB= SETindex_(verticesB, skipB); /* i.e., skipBp - verticesB */
    +  *same= !((skipA & 0x1) ^ (*skipB & 0x1)); /* result is 0 or 1 */
    +  trace4((qh ferr, 4054, "qh_matchvertices: matched by skip %d(v%d) and skip %d(v%d) same? %d\n",
    +          skipA, (*skipAp)->id, *skipB, (*(skipBp-1))->id, *same));
    +  return(True);
    +} /* matchvertices */
    +
    +/*---------------------------------
    +
    +  qh_newfacet()
    +    return a new facet
    +
    +  returns:
    +    all fields initialized or cleared   (NULL)
    +    preallocates neighbors set
    +*/
    +facetT *qh_newfacet(void) {
    +  facetT *facet;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_((int)sizeof(facetT), freelistp, facet, facetT);
    +  memset((char *)facet, (size_t)0, sizeof(facetT));
    +  if (qh facet_id == qh tracefacet_id)
    +    qh tracefacet= facet;
    +  facet->id= qh facet_id++;
    +  facet->neighbors= qh_setnew(qh hull_dim);
    +#if !qh_COMPUTEfurthest
    +  facet->furthestdist= 0.0;
    +#endif
    +#if qh_MAXoutside
    +  if (qh FORCEoutput && qh APPROXhull)
    +    facet->maxoutside= qh MINoutside;
    +  else
    +    facet->maxoutside= qh DISTround;
    +#endif
    +  facet->simplicial= True;
    +  facet->good= True;
    +  facet->newfacet= True;
    +  trace4((qh ferr, 4055, "qh_newfacet: created facet f%d\n", facet->id));
    +  return(facet);
    +} /* newfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_newridge()
    +    return a new ridge
    +*/
    +ridgeT *qh_newridge(void) {
    +  ridgeT *ridge;
    +  void **freelistp;   /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_((int)sizeof(ridgeT), freelistp, ridge, ridgeT);
    +  memset((char *)ridge, (size_t)0, sizeof(ridgeT));
    +  zinc_(Ztotridges);
    +  if (qh ridge_id == UINT_MAX) {
    +    qh_fprintf(qh ferr, 7074, "\
    +qhull warning: more than 2^32 ridges.  Qhull results are OK.  Since the ridge ID wraps around to 0, two ridges may have the same identifier.\n");
    +  }
    +  ridge->id= qh ridge_id++;
    +  trace4((qh ferr, 4056, "qh_newridge: created ridge r%d\n", ridge->id));
    +  return(ridge);
    +} /* newridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_pointid(  point )
    +    return id for a point,
    +    returns qh_IDnone(-3) if null, qh_IDinterior(-2) if interior, or qh_IDunknown(-1) if not known
    +
    +  alternative code if point is in qh.first_point...
    +    unsigned long id;
    +    id= ((unsigned long)point - (unsigned long)qh.first_point)/qh.normal_size;
    +
    +  notes:
    +    Valid points are non-negative
    +    WARN64 -- id truncated to 32-bits, at most 2G points
    +    NOerrors returned (QhullPoint::id)
    +    if point not in point array
    +      the code does a comparison of unrelated pointers.
    +*/
    +int qh_pointid(pointT *point) {
    +  ptr_intT offset, id;
    +
    +  if (!point)
    +    return qh_IDnone;
    +  else if (point == qh interior_point)
    +    return qh_IDinterior;
    +  else if (point >= qh first_point
    +  && point < qh first_point + qh num_points * qh hull_dim) {
    +    offset= (ptr_intT)(point - qh first_point);
    +    id= offset / qh hull_dim;
    +  }else if ((id= qh_setindex(qh other_points, point)) != -1)
    +    id += qh num_points;
    +  else
    +    return qh_IDunknown;
    +  return (int)id;
    +} /* pointid */
    +
    +/*---------------------------------
    +
    +  qh_removefacet( facet )
    +    unlinks facet from qh.facet_list,
    +
    +  returns:
    +    updates qh.facet_list .newfacet_list .facet_next visible_list
    +    decrements qh.num_facets
    +
    +  see:
    +    qh_appendfacet
    +*/
    +void qh_removefacet(facetT *facet) {
    +  facetT *next= facet->next, *previous= facet->previous;
    +
    +  if (facet == qh newfacet_list)
    +    qh newfacet_list= next;
    +  if (facet == qh facet_next)
    +    qh facet_next= next;
    +  if (facet == qh visible_list)
    +    qh visible_list= next;
    +  if (previous) {
    +    previous->next= next;
    +    next->previous= previous;
    +  }else {  /* 1st facet in qh facet_list */
    +    qh facet_list= next;
    +    qh facet_list->previous= NULL;
    +  }
    +  qh num_facets--;
    +  trace4((qh ferr, 4057, "qh_removefacet: remove f%d from facet_list\n", facet->id));
    +} /* removefacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_removevertex( vertex )
    +    unlinks vertex from qh.vertex_list,
    +
    +  returns:
    +    updates qh.vertex_list .newvertex_list
    +    decrements qh.num_vertices
    +*/
    +void qh_removevertex(vertexT *vertex) {
    +  vertexT *next= vertex->next, *previous= vertex->previous;
    +
    +  if (vertex == qh newvertex_list)
    +    qh newvertex_list= next;
    +  if (previous) {
    +    previous->next= next;
    +    next->previous= previous;
    +  }else {  /* 1st vertex in qh vertex_list */
    +    qh vertex_list= vertex->next;
    +    qh vertex_list->previous= NULL;
    +  }
    +  qh num_vertices--;
    +  trace4((qh ferr, 4058, "qh_removevertex: remove v%d from vertex_list\n", vertex->id));
    +} /* removevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_updatevertices()
    +    update vertex neighbors and delete interior vertices
    +
    +  returns:
    +    if qh.VERTEXneighbors, updates neighbors for each vertex
    +      if qh.newvertex_list,
    +         removes visible neighbors  from vertex neighbors
    +      if qh.newfacet_list
    +         adds new facets to vertex neighbors
    +    if qh.visible_list
    +       interior vertices added to qh.del_vertices for later partitioning
    +
    +  design:
    +    if qh.VERTEXneighbors
    +      deletes references to visible facets from vertex neighbors
    +      appends new facets to the neighbor list for each vertex
    +      checks all vertices of visible facets
    +        removes visible facets from neighbor lists
    +        marks unused vertices for deletion
    +*/
    +void qh_updatevertices(void /*qh.newvertex_list, newfacet_list, visible_list*/) {
    +  facetT *newfacet= NULL, *neighbor, **neighborp, *visible;
    +  vertexT *vertex, **vertexp;
    +
    +  trace3((qh ferr, 3013, "qh_updatevertices: delete interior vertices and update vertex->neighbors\n"));
    +  if (qh VERTEXneighbors) {
    +    FORALLvertex_(qh newvertex_list) {
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visible)
    +          SETref_(neighbor)= NULL;
    +      }
    +      qh_setcompact(vertex->neighbors);
    +    }
    +    FORALLnew_facets {
    +      FOREACHvertex_(newfacet->vertices)
    +        qh_setappend(&vertex->neighbors, newfacet);
    +    }
    +    FORALLvisible_facets {
    +      FOREACHvertex_(visible->vertices) {
    +        if (!vertex->newlist && !vertex->deleted) {
    +          FOREACHneighbor_(vertex) { /* this can happen under merging */
    +            if (!neighbor->visible)
    +              break;
    +          }
    +          if (neighbor)
    +            qh_setdel(vertex->neighbors, visible);
    +          else {
    +            vertex->deleted= True;
    +            qh_setappend(&qh del_vertices, vertex);
    +            trace2((qh ferr, 2041, "qh_updatevertices: delete vertex p%d(v%d) in f%d\n",
    +                  qh_pointid(vertex->point), vertex->id, visible->id));
    +          }
    +        }
    +      }
    +    }
    +  }else {  /* !VERTEXneighbors */
    +    FORALLvisible_facets {
    +      FOREACHvertex_(visible->vertices) {
    +        if (!vertex->newlist && !vertex->deleted) {
    +          vertex->deleted= True;
    +          qh_setappend(&qh del_vertices, vertex);
    +          trace2((qh ferr, 2042, "qh_updatevertices: delete vertex p%d(v%d) in f%d\n",
    +                  qh_pointid(vertex->point), vertex->id, visible->id));
    +        }
    +      }
    +    }
    +  }
    +} /* updatevertices */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/poly.h b/xs/src/qhull/src/libqhull/poly.h
    new file mode 100644
    index 0000000000..af8b42077f
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/poly.h
    @@ -0,0 +1,296 @@
    +/*
      ---------------------------------
    +
    +   poly.h
    +   header file for poly.c and poly2.c
    +
    +   see qh-poly.htm, libqhull.h and poly.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/poly.h#3 $$Change: 2047 $
    +   $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFpoly
    +#define qhDEFpoly 1
    +
    +#include "libqhull.h"
    +
    +/*===============   constants ========================== */
    +
    +/*----------------------------------
    +
    +  ALGORITHMfault
    +    use as argument to checkconvex() to report errors during buildhull
    +*/
    +#define qh_ALGORITHMfault 0
    +
    +/*----------------------------------
    +
    +  DATAfault
    +    use as argument to checkconvex() to report errors during initialhull
    +*/
    +#define qh_DATAfault 1
    +
    +/*----------------------------------
    +
    +  DUPLICATEridge
    +    special value for facet->neighbor to indicate a duplicate ridge
    +
    +  notes:
    +    set by matchneighbor, used by matchmatch and mark_dupridge
    +*/
    +#define qh_DUPLICATEridge (facetT *)1L
    +
    +/*----------------------------------
    +
    +  MERGEridge       flag in facet
    +    special value for facet->neighbor to indicate a merged ridge
    +
    +  notes:
    +    set by matchneighbor, used by matchmatch and mark_dupridge
    +*/
    +#define qh_MERGEridge (facetT *)2L
    +
    +
    +/*============ -structures- ====================*/
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  FORALLfacet_( facetlist ) { ... }
    +    assign 'facet' to each facet in facetlist
    +
    +  notes:
    +    uses 'facetT *facet;'
    +    assumes last facet is a sentinel
    +
    +  see:
    +    FORALLfacets
    +*/
    +#define FORALLfacet_( facetlist ) if (facetlist ) for ( facet=( facetlist ); facet && facet->next; facet= facet->next )
    +
    +/*----------------------------------
    +
    +  FORALLnew_facets { ... }
    +    assign 'newfacet' to each facet in qh.newfacet_list
    +
    +  notes:
    +    uses 'facetT *newfacet;'
    +    at exit, newfacet==NULL
    +*/
    +#define FORALLnew_facets for ( newfacet=qh newfacet_list;newfacet && newfacet->next;newfacet=newfacet->next )
    +
    +/*----------------------------------
    +
    +  FORALLvertex_( vertexlist ) { ... }
    +    assign 'vertex' to each vertex in vertexlist
    +
    +  notes:
    +    uses 'vertexT *vertex;'
    +    at exit, vertex==NULL
    +*/
    +#define FORALLvertex_( vertexlist ) for (vertex=( vertexlist );vertex && vertex->next;vertex= vertex->next )
    +
    +/*----------------------------------
    +
    +  FORALLvisible_facets { ... }
    +    assign 'visible' to each visible facet in qh.visible_list
    +
    +  notes:
    +    uses 'vacetT *visible;'
    +    at exit, visible==NULL
    +*/
    +#define FORALLvisible_facets for (visible=qh visible_list; visible && visible->visible; visible= visible->next)
    +
    +/*----------------------------------
    +
    +  FORALLsame_( newfacet ) { ... }
    +    assign 'same' to each facet in newfacet->f.samecycle
    +
    +  notes:
    +    uses 'facetT *same;'
    +    stops when it returns to newfacet
    +*/
    +#define FORALLsame_(newfacet) for (same= newfacet->f.samecycle; same != newfacet; same= same->f.samecycle)
    +
    +/*----------------------------------
    +
    +  FORALLsame_cycle_( newfacet ) { ... }
    +    assign 'same' to each facet in newfacet->f.samecycle
    +
    +  notes:
    +    uses 'facetT *same;'
    +    at exit, same == NULL
    +*/
    +#define FORALLsame_cycle_(newfacet) \
    +     for (same= newfacet->f.samecycle; \
    +         same; same= (same == newfacet ?  NULL : same->f.samecycle))
    +
    +/*----------------------------------
    +
    +  FOREACHneighborA_( facet ) { ... }
    +    assign 'neighborA' to each neighbor in facet->neighbors
    +
    +  FOREACHneighborA_( vertex ) { ... }
    +    assign 'neighborA' to each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighborA, **neighborAp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHneighborA_(facet)  FOREACHsetelement_(facetT, facet->neighbors, neighborA)
    +
    +/*----------------------------------
    +
    +  FOREACHvisible_( facets ) { ... }
    +    assign 'visible' to each facet in facets
    +
    +  notes:
    +    uses 'facetT *facet, *facetp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvisible_(facets) FOREACHsetelement_(facetT, facets, visible)
    +
    +/*----------------------------------
    +
    +  FOREACHnewfacet_( facets ) { ... }
    +    assign 'newfacet' to each facet in facets
    +
    +  notes:
    +    uses 'facetT *newfacet, *newfacetp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHnewfacet_(facets) FOREACHsetelement_(facetT, facets, newfacet)
    +
    +/*----------------------------------
    +
    +  FOREACHvertexA_( vertices ) { ... }
    +    assign 'vertexA' to each vertex in vertices
    +
    +  notes:
    +    uses 'vertexT *vertexA, *vertexAp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvertexA_(vertices) FOREACHsetelement_(vertexT, vertices, vertexA)
    +
    +/*----------------------------------
    +
    +  FOREACHvertexreverse12_( vertices ) { ... }
    +    assign 'vertex' to each vertex in vertices
    +    reverse order of first two vertices
    +
    +  notes:
    +    uses 'vertexT *vertex, *vertexp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvertexreverse12_(vertices) FOREACHsetelementreverse12_(vertexT, vertices, vertex)
    +
    +
    +/*=============== prototypes poly.c in alphabetical order ================*/
    +
    +void    qh_appendfacet(facetT *facet);
    +void    qh_appendvertex(vertexT *vertex);
    +void    qh_attachnewfacets(void /* qh.visible_list, qh.newfacet_list */);
    +boolT   qh_checkflipped(facetT *facet, realT *dist, boolT allerror);
    +void    qh_delfacet(facetT *facet);
    +void    qh_deletevisible(void /*qh.visible_list, qh.horizon_list*/);
    +setT   *qh_facetintersect(facetT *facetA, facetT *facetB, int *skipAp,int *skipBp, int extra);
    +int     qh_gethash(int hashsize, setT *set, int size, int firstindex, void *skipelem);
    +facetT *qh_makenewfacet(setT *vertices, boolT toporient, facetT *facet);
    +void    qh_makenewplanes(void /* newfacet_list */);
    +facetT *qh_makenew_nonsimplicial(facetT *visible, vertexT *apex, int *numnew);
    +facetT *qh_makenew_simplicial(facetT *visible, vertexT *apex, int *numnew);
    +void    qh_matchneighbor(facetT *newfacet, int newskip, int hashsize,
    +                          int *hashcount);
    +void    qh_matchnewfacets(void);
    +boolT   qh_matchvertices(int firstindex, setT *verticesA, int skipA,
    +                          setT *verticesB, int *skipB, boolT *same);
    +facetT *qh_newfacet(void);
    +ridgeT *qh_newridge(void);
    +int     qh_pointid(pointT *point);
    +void    qh_removefacet(facetT *facet);
    +void    qh_removevertex(vertexT *vertex);
    +void    qh_updatevertices(void);
    +
    +
    +/*========== -prototypes poly2.c in alphabetical order ===========*/
    +
    +void    qh_addhash(void* newelem, setT *hashtable, int hashsize, int hash);
    +void    qh_check_bestdist(void);
    +void    qh_check_dupridge(facetT *facet1, realT dist1, facetT *facet2, realT dist2);
    +void    qh_check_maxout(void);
    +void    qh_check_output(void);
    +void    qh_check_point(pointT *point, facetT *facet, realT *maxoutside, realT *maxdist, facetT **errfacet1, facetT **errfacet2);
    +void    qh_check_points(void);
    +void    qh_checkconvex(facetT *facetlist, int fault);
    +void    qh_checkfacet(facetT *facet, boolT newmerge, boolT *waserrorp);
    +void    qh_checkflipped_all(facetT *facetlist);
    +void    qh_checkpolygon(facetT *facetlist);
    +void    qh_checkvertex(vertexT *vertex);
    +void    qh_clearcenters(qh_CENTER type);
    +void    qh_createsimplex(setT *vertices);
    +void    qh_delridge(ridgeT *ridge);
    +void    qh_delvertex(vertexT *vertex);
    +setT   *qh_facet3vertex(facetT *facet);
    +facetT *qh_findbestfacet(pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside);
    +facetT *qh_findbestlower(facetT *upperfacet, pointT *point, realT *bestdistp, int *numpart);
    +facetT *qh_findfacet_all(pointT *point, realT *bestdist, boolT *isoutside,
    +                          int *numpart);
    +int     qh_findgood(facetT *facetlist, int goodhorizon);
    +void    qh_findgood_all(facetT *facetlist);
    +void    qh_furthestnext(void /* qh.facet_list */);
    +void    qh_furthestout(facetT *facet);
    +void    qh_infiniteloop(facetT *facet);
    +void    qh_initbuild(void);
    +void    qh_initialhull(setT *vertices);
    +setT   *qh_initialvertices(int dim, setT *maxpoints, pointT *points, int numpoints);
    +vertexT *qh_isvertex(pointT *point, setT *vertices);
    +vertexT *qh_makenewfacets(pointT *point /*horizon_list, visible_list*/);
    +void    qh_matchduplicates(facetT *atfacet, int atskip, int hashsize, int *hashcount);
    +void    qh_nearcoplanar(void /* qh.facet_list */);
    +vertexT *qh_nearvertex(facetT *facet, pointT *point, realT *bestdistp);
    +int     qh_newhashtable(int newsize);
    +vertexT *qh_newvertex(pointT *point);
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp);
    +void    qh_outcoplanar(void /* facet_list */);
    +pointT *qh_point(int id);
    +void    qh_point_add(setT *set, pointT *point, void *elem);
    +setT   *qh_pointfacet(void /*qh.facet_list*/);
    +setT   *qh_pointvertex(void /*qh.facet_list*/);
    +void    qh_prependfacet(facetT *facet, facetT **facetlist);
    +void    qh_printhashtable(FILE *fp);
    +void    qh_printlists(void);
    +void    qh_resetlists(boolT stats, boolT resetVisible /*qh.newvertex_list qh.newfacet_list qh.visible_list*/);
    +void    qh_setvoronoi_all(void);
    +void    qh_triangulate(void /*qh.facet_list*/);
    +void    qh_triangulate_facet(facetT *facetA, vertexT **first_vertex);
    +void    qh_triangulate_link(facetT *oldfacetA, facetT *facetA, facetT *oldfacetB, facetT *facetB);
    +void    qh_triangulate_mirror(facetT *facetA, facetT *facetB);
    +void    qh_triangulate_null(facetT *facetA);
    +void    qh_vertexintersect(setT **vertexsetA,setT *vertexsetB);
    +setT   *qh_vertexintersect_new(setT *vertexsetA,setT *vertexsetB);
    +void    qh_vertexneighbors(void /*qh.facet_list*/);
    +boolT   qh_vertexsubset(setT *vertexsetA, setT *vertexsetB);
    +
    +
    +#endif /* qhDEFpoly */
    diff --git a/xs/src/qhull/src/libqhull/poly2.c b/xs/src/qhull/src/libqhull/poly2.c
    new file mode 100644
    index 0000000000..de3e6ad0bb
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/poly2.c
    @@ -0,0 +1,3222 @@
    +/*
      ---------------------------------
    +
    +   poly2.c
    +   implements polygons and simplices
    +
    +   see qh-poly.htm, poly.h and libqhull.h
    +
    +   frequently used code is in poly.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/poly2.c#11 $$Change: 2069 $
    +   $DateTime: 2016/01/18 22:05:03 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*======== functions in alphabetical order ==========*/
    +
    +/*---------------------------------
    +
    +  qh_addhash( newelem, hashtable, hashsize, hash )
    +    add newelem to linear hash table at hash if not already there
    +*/
    +void qh_addhash(void* newelem, setT *hashtable, int hashsize, int hash) {
    +  int scan;
    +  void *elem;
    +
    +  for (scan= (int)hash; (elem= SETelem_(hashtable, scan));
    +       scan= (++scan >= hashsize ? 0 : scan)) {
    +    if (elem == newelem)
    +      break;
    +  }
    +  /* loop terminates because qh_HASHfactor >= 1.1 by qh_initbuffers */
    +  if (!elem)
    +    SETelem_(hashtable, scan)= newelem;
    +} /* addhash */
    +
    +/*---------------------------------
    +
    +  qh_check_bestdist()
    +    check that all points are within max_outside of the nearest facet
    +    if qh.ONLYgood,
    +      ignores !good facets
    +
    +  see:
    +    qh_check_maxout(), qh_outerinner()
    +
    +  notes:
    +    only called from qh_check_points()
    +      seldom used since qh.MERGING is almost always set
    +    if notverified>0 at end of routine
    +      some points were well inside the hull.  If the hull contains
    +      a lens-shaped component, these points were not verified.  Use
    +      options 'Qi Tv' to verify all points.  (Exhaustive check also verifies)
    +
    +  design:
    +    determine facet for each point (if any)
    +    for each point
    +      start with the assigned facet or with the first facet
    +      find the best facet for the point and check all coplanar facets
    +      error if point is outside of facet
    +*/
    +void qh_check_bestdist(void) {
    +  boolT waserror= False, unassigned;
    +  facetT *facet, *bestfacet, *errfacet1= NULL, *errfacet2= NULL;
    +  facetT *facetlist;
    +  realT dist, maxoutside, maxdist= -REALmax;
    +  pointT *point;
    +  int numpart= 0, facet_i, facet_n, notgood= 0, notverified= 0;
    +  setT *facets;
    +
    +  trace1((qh ferr, 1020, "qh_check_bestdist: check points below nearest facet.  Facet_list f%d\n",
    +      qh facet_list->id));
    +  maxoutside= qh_maxouter();
    +  maxoutside += qh DISTround;
    +  /* one more qh.DISTround for check computation */
    +  trace1((qh ferr, 1021, "qh_check_bestdist: check that all points are within %2.2g of best facet\n", maxoutside));
    +  facets= qh_pointfacet(/*qh.facet_list*/);
    +  if (!qh_QUICKhelp && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 8091, "\n\
    +qhull output completed.  Verifying that %d points are\n\
    +below %2.2g of the nearest %sfacet.\n",
    +             qh_setsize(facets), maxoutside, (qh ONLYgood ?  "good " : ""));
    +  FOREACHfacet_i_(facets) {  /* for each point with facet assignment */
    +    if (facet)
    +      unassigned= False;
    +    else {
    +      unassigned= True;
    +      facet= qh facet_list;
    +    }
    +    point= qh_point(facet_i);
    +    if (point == qh GOODpointp)
    +      continue;
    +    qh_distplane(point, facet, &dist);
    +    numpart++;
    +    bestfacet= qh_findbesthorizon(!qh_IScheckmax, point, facet, qh_NOupper, &dist, &numpart);
    +    /* occurs after statistics reported */
    +    maximize_(maxdist, dist);
    +    if (dist > maxoutside) {
    +      if (qh ONLYgood && !bestfacet->good
    +          && !((bestfacet= qh_findgooddist(point, bestfacet, &dist, &facetlist))
    +               && dist > maxoutside))
    +        notgood++;
    +      else {
    +        waserror= True;
    +        qh_fprintf(qh ferr, 6109, "qhull precision error: point p%d is outside facet f%d, distance= %6.8g maxoutside= %6.8g\n",
    +                facet_i, bestfacet->id, dist, maxoutside);
    +        if (errfacet1 != bestfacet) {
    +          errfacet2= errfacet1;
    +          errfacet1= bestfacet;
    +        }
    +      }
    +    }else if (unassigned && dist < -qh MAXcoplanar)
    +      notverified++;
    +  }
    +  qh_settempfree(&facets);
    +  if (notverified && !qh DELAUNAY && !qh_QUICKhelp && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 8092, "\n%d points were well inside the hull.  If the hull contains\n\
    +a lens-shaped component, these points were not verified.  Use\n\
    +options 'Qci Tv' to verify all points.\n", notverified);
    +  if (maxdist > qh outside_err) {
    +    qh_fprintf(qh ferr, 6110, "qhull precision error (qh_check_bestdist): a coplanar point is %6.2g from convex hull.  The maximum value(qh.outside_err) is %6.2g\n",
    +              maxdist, qh outside_err);
    +    qh_errexit2(qh_ERRprec, errfacet1, errfacet2);
    +  }else if (waserror && qh outside_err > REALmax/2)
    +    qh_errexit2(qh_ERRprec, errfacet1, errfacet2);
    +  /* else if waserror, the error was logged to qh.ferr but does not effect the output */
    +  trace0((qh ferr, 20, "qh_check_bestdist: max distance outside %2.2g\n", maxdist));
    +} /* check_bestdist */
    +
    +/*---------------------------------
    +
    +  qh_check_dupridge(facet1, dist1, facet2, dist2)
    +    Check duplicate ridge between facet1 and facet2 for wide merge
    +    dist1 is the maximum distance of facet1's vertices to facet2
    +    dist2 is the maximum distance of facet2's vertices to facet1
    +
    +  Returns
    +    Level 1 log of the duplicate ridge with the minimum distance between vertices
    +    Throws error if the merge will increase the maximum facet width by qh_WIDEduplicate (100x)
    +
    +  called from:
    +    qh_forcedmerges()
    +*/
    +#ifndef qh_NOmerge
    +void qh_check_dupridge(facetT *facet1, realT dist1, facetT *facet2, realT dist2) {
    +  vertexT *vertex, **vertexp, *vertexA, **vertexAp;
    +  realT dist, innerplane, mergedist, outerplane, prevdist, ratio;
    +  realT minvertex= REALmax;
    +
    +  mergedist= fmin_(dist1, dist2);
    +  qh_outerinner(NULL, &outerplane, &innerplane);  /* ratio from qh_printsummary */
    +  prevdist= fmax_(outerplane, innerplane);
    +  maximize_(prevdist, qh ONEmerge + qh DISTround);
    +  maximize_(prevdist, qh MINoutside + qh DISTround);
    +  ratio= mergedist/prevdist;
    +  FOREACHvertex_(facet1->vertices) {     /* The duplicate ridge is between facet1 and facet2, so either facet can be tested */
    +    FOREACHvertexA_(facet1->vertices) {
    +      if (vertex > vertexA){   /* Test each pair once */
    +        dist= qh_pointdist(vertex->point, vertexA->point, qh hull_dim);
    +        minimize_(minvertex, dist);
    +      }
    +    }
    +  }
    +  trace0((qh ferr, 16, "qh_check_dupridge: duplicate ridge between f%d and f%d due to nearly-coincident vertices (%2.2g), dist %2.2g, reverse dist %2.2g, ratio %2.2g while processing p%d\n",
    +        facet1->id, facet2->id, minvertex, dist1, dist2, ratio, qh furthest_id));
    +  if (ratio > qh_WIDEduplicate) {
    +    qh_fprintf(qh ferr, 6271, "qhull precision error (qh_check_dupridge): wide merge (%.0f times wider) due to duplicate ridge with nearly coincident points (%2.2g) between f%d and f%d, merge dist %2.2g, while processing p%d\n- Ignore error with option 'Q12'\n- To be fixed in a later version of Qhull\n",
    +          ratio, minvertex, facet1->id, facet2->id, mergedist, qh furthest_id);
    +    if (qh DELAUNAY)
    +      qh_fprintf(qh ferr, 8145, "- A bounding box for the input sites may alleviate this error.\n");
    +    if(minvertex > qh_WIDEduplicate*prevdist)
    +      qh_fprintf(qh ferr, 8146, "- Vertex distance %2.2g is greater than %d times maximum distance %2.2g\n  Please report to bradb@shore.net with steps to reproduce and all output\n",
    +          minvertex, qh_WIDEduplicate, prevdist);
    +    if (!qh NOwide)
    +      qh_errexit2(qh_ERRqhull, facet1, facet2);
    +  }
    +} /* check_dupridge */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_check_maxout()
    +    updates qh.max_outside by checking all points against bestfacet
    +    if qh.ONLYgood, ignores !good facets
    +
    +  returns:
    +    updates facet->maxoutside via qh_findbesthorizon()
    +    sets qh.maxoutdone
    +    if printing qh.min_vertex (qh_outerinner),
    +      it is updated to the current vertices
    +    removes inside/coplanar points from coplanarset as needed
    +
    +  notes:
    +    defines coplanar as min_vertex instead of MAXcoplanar
    +    may not need to check near-inside points because of qh.MAXcoplanar
    +      and qh.KEEPnearinside (before it was -DISTround)
    +
    +  see also:
    +    qh_check_bestdist()
    +
    +  design:
    +    if qh.min_vertex is needed
    +      for all neighbors of all vertices
    +        test distance from vertex to neighbor
    +    determine facet for each point (if any)
    +    for each point with an assigned facet
    +      find the best facet for the point and check all coplanar facets
    +        (updates outer planes)
    +    remove near-inside points from coplanar sets
    +*/
    +#ifndef qh_NOmerge
    +void qh_check_maxout(void) {
    +  facetT *facet, *bestfacet, *neighbor, **neighborp, *facetlist;
    +  realT dist, maxoutside, minvertex, old_maxoutside;
    +  pointT *point;
    +  int numpart= 0, facet_i, facet_n, notgood= 0;
    +  setT *facets, *vertices;
    +  vertexT *vertex;
    +
    +  trace1((qh ferr, 1022, "qh_check_maxout: check and update maxoutside for each facet.\n"));
    +  maxoutside= minvertex= 0;
    +  if (qh VERTEXneighbors
    +  && (qh PRINTsummary || qh KEEPinside || qh KEEPcoplanar
    +        || qh TRACElevel || qh PRINTstatistics
    +        || qh PRINTout[0] == qh_PRINTsummary || qh PRINTout[0] == qh_PRINTnone)) {
    +    trace1((qh ferr, 1023, "qh_check_maxout: determine actual maxoutside and minvertex\n"));
    +    vertices= qh_pointvertex(/*qh.facet_list*/);
    +    FORALLvertices {
    +      FOREACHneighbor_(vertex) {
    +        zinc_(Zdistvertex);  /* distance also computed by main loop below */
    +        qh_distplane(vertex->point, neighbor, &dist);
    +        minimize_(minvertex, dist);
    +        if (-dist > qh TRACEdist || dist > qh TRACEdist
    +        || neighbor == qh tracefacet || vertex == qh tracevertex)
    +          qh_fprintf(qh ferr, 8093, "qh_check_maxout: p%d(v%d) is %.2g from f%d\n",
    +                    qh_pointid(vertex->point), vertex->id, dist, neighbor->id);
    +      }
    +    }
    +    if (qh MERGING) {
    +      wmin_(Wminvertex, qh min_vertex);
    +    }
    +    qh min_vertex= minvertex;
    +    qh_settempfree(&vertices);
    +  }
    +  facets= qh_pointfacet(/*qh.facet_list*/);
    +  do {
    +    old_maxoutside= fmax_(qh max_outside, maxoutside);
    +    FOREACHfacet_i_(facets) {     /* for each point with facet assignment */
    +      if (facet) {
    +        point= qh_point(facet_i);
    +        if (point == qh GOODpointp)
    +          continue;
    +        zzinc_(Ztotcheck);
    +        qh_distplane(point, facet, &dist);
    +        numpart++;
    +        bestfacet= qh_findbesthorizon(qh_IScheckmax, point, facet, !qh_NOupper, &dist, &numpart);
    +        if (bestfacet && dist > maxoutside) {
    +          if (qh ONLYgood && !bestfacet->good
    +          && !((bestfacet= qh_findgooddist(point, bestfacet, &dist, &facetlist))
    +               && dist > maxoutside))
    +            notgood++;
    +          else
    +            maxoutside= dist;
    +        }
    +        if (dist > qh TRACEdist || (bestfacet && bestfacet == qh tracefacet))
    +          qh_fprintf(qh ferr, 8094, "qh_check_maxout: p%d is %.2g above f%d\n",
    +                     qh_pointid(point), dist, (bestfacet ? bestfacet->id : UINT_MAX));
    +      }
    +    }
    +  }while
    +    (maxoutside > 2*old_maxoutside);
    +    /* if qh.maxoutside increases substantially, qh_SEARCHdist is not valid
    +          e.g., RBOX 5000 s Z1 G1e-13 t1001200614 | qhull */
    +  zzadd_(Zcheckpart, numpart);
    +  qh_settempfree(&facets);
    +  wval_(Wmaxout)= maxoutside - qh max_outside;
    +  wmax_(Wmaxoutside, qh max_outside);
    +  qh max_outside= maxoutside;
    +  qh_nearcoplanar(/*qh.facet_list*/);
    +  qh maxoutdone= True;
    +  trace1((qh ferr, 1024, "qh_check_maxout: maxoutside %2.2g, min_vertex %2.2g, outside of not good %d\n",
    +       maxoutside, qh min_vertex, notgood));
    +} /* check_maxout */
    +#else /* qh_NOmerge */
    +void qh_check_maxout(void) {
    +}
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_check_output()
    +    performs the checks at the end of qhull algorithm
    +    Maybe called after voronoi output.  Will recompute otherwise centrums are Voronoi centers instead
    +*/
    +void qh_check_output(void) {
    +  int i;
    +
    +  if (qh STOPcone)
    +    return;
    +  if (qh VERIFYoutput | qh IStracing | qh CHECKfrequently) {
    +    qh_checkpolygon(qh facet_list);
    +    qh_checkflipped_all(qh facet_list);
    +    qh_checkconvex(qh facet_list, qh_ALGORITHMfault);
    +  }else if (!qh MERGING && qh_newstats(qhstat precision, &i)) {
    +    qh_checkflipped_all(qh facet_list);
    +    qh_checkconvex(qh facet_list, qh_ALGORITHMfault);
    +  }
    +} /* check_output */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_check_point( point, facet, maxoutside, maxdist, errfacet1, errfacet2 )
    +    check that point is less than maxoutside from facet
    +*/
    +void qh_check_point(pointT *point, facetT *facet, realT *maxoutside, realT *maxdist, facetT **errfacet1, facetT **errfacet2) {
    +  realT dist;
    +
    +  /* occurs after statistics reported */
    +  qh_distplane(point, facet, &dist);
    +  if (dist > *maxoutside) {
    +    if (*errfacet1 != facet) {
    +      *errfacet2= *errfacet1;
    +      *errfacet1= facet;
    +    }
    +    qh_fprintf(qh ferr, 6111, "qhull precision error: point p%d is outside facet f%d, distance= %6.8g maxoutside= %6.8g\n",
    +              qh_pointid(point), facet->id, dist, *maxoutside);
    +  }
    +  maximize_(*maxdist, dist);
    +} /* qh_check_point */
    +
    +
    +/*---------------------------------
    +
    +  qh_check_points()
    +    checks that all points are inside all facets
    +
    +  notes:
    +    if many points and qh_check_maxout not called (i.e., !qh.MERGING),
    +       calls qh_findbesthorizon (seldom done).
    +    ignores flipped facets
    +    maxoutside includes 2 qh.DISTrounds
    +      one qh.DISTround for the computed distances in qh_check_points
    +    qh_printafacet and qh_printsummary needs only one qh.DISTround
    +    the computation for qh.VERIFYdirect does not account for qh.other_points
    +
    +  design:
    +    if many points
    +      use qh_check_bestdist()
    +    else
    +      for all facets
    +        for all points
    +          check that point is inside facet
    +*/
    +void qh_check_points(void) {
    +  facetT *facet, *errfacet1= NULL, *errfacet2= NULL;
    +  realT total, maxoutside, maxdist= -REALmax;
    +  pointT *point, **pointp, *pointtemp;
    +  boolT testouter;
    +
    +  maxoutside= qh_maxouter();
    +  maxoutside += qh DISTround;
    +  /* one more qh.DISTround for check computation */
    +  trace1((qh ferr, 1025, "qh_check_points: check all points below %2.2g of all facet planes\n",
    +          maxoutside));
    +  if (qh num_good)   /* miss counts other_points and !good facets */
    +     total= (float)qh num_good * (float)qh num_points;
    +  else
    +     total= (float)qh num_facets * (float)qh num_points;
    +  if (total >= qh_VERIFYdirect && !qh maxoutdone) {
    +    if (!qh_QUICKhelp && qh SKIPcheckmax && qh MERGING)
    +      qh_fprintf(qh ferr, 7075, "qhull input warning: merging without checking outer planes('Q5' or 'Po').\n\
    +Verify may report that a point is outside of a facet.\n");
    +    qh_check_bestdist();
    +  }else {
    +    if (qh_MAXoutside && qh maxoutdone)
    +      testouter= True;
    +    else
    +      testouter= False;
    +    if (!qh_QUICKhelp) {
    +      if (qh MERGEexact)
    +        qh_fprintf(qh ferr, 7076, "qhull input warning: exact merge ('Qx').  Verify may report that a point\n\
    +is outside of a facet.  See qh-optq.htm#Qx\n");
    +      else if (qh SKIPcheckmax || qh NOnearinside)
    +        qh_fprintf(qh ferr, 7077, "qhull input warning: no outer plane check ('Q5') or no processing of\n\
    +near-inside points ('Q8').  Verify may report that a point is outside\n\
    +of a facet.\n");
    +    }
    +    if (qh PRINTprecision) {
    +      if (testouter)
    +        qh_fprintf(qh ferr, 8098, "\n\
    +Output completed.  Verifying that all points are below outer planes of\n\
    +all %sfacets.  Will make %2.0f distance computations.\n",
    +              (qh ONLYgood ?  "good " : ""), total);
    +      else
    +        qh_fprintf(qh ferr, 8099, "\n\
    +Output completed.  Verifying that all points are below %2.2g of\n\
    +all %sfacets.  Will make %2.0f distance computations.\n",
    +              maxoutside, (qh ONLYgood ?  "good " : ""), total);
    +    }
    +    FORALLfacets {
    +      if (!facet->good && qh ONLYgood)
    +        continue;
    +      if (facet->flipped)
    +        continue;
    +      if (!facet->normal) {
    +        qh_fprintf(qh ferr, 7061, "qhull warning (qh_check_points): missing normal for facet f%d\n", facet->id);
    +        continue;
    +      }
    +      if (testouter) {
    +#if qh_MAXoutside
    +        maxoutside= facet->maxoutside + 2* qh DISTround;
    +        /* one DISTround to actual point and another to computed point */
    +#endif
    +      }
    +      FORALLpoints {
    +        if (point != qh GOODpointp)
    +          qh_check_point(point, facet, &maxoutside, &maxdist, &errfacet1, &errfacet2);
    +      }
    +      FOREACHpoint_(qh other_points) {
    +        if (point != qh GOODpointp)
    +          qh_check_point(point, facet, &maxoutside, &maxdist, &errfacet1, &errfacet2);
    +      }
    +    }
    +    if (maxdist > qh outside_err) {
    +      qh_fprintf(qh ferr, 6112, "qhull precision error (qh_check_points): a coplanar point is %6.2g from convex hull.  The maximum value(qh.outside_err) is %6.2g\n",
    +                maxdist, qh outside_err );
    +      qh_errexit2( qh_ERRprec, errfacet1, errfacet2 );
    +    }else if (errfacet1 && qh outside_err > REALmax/2)
    +        qh_errexit2( qh_ERRprec, errfacet1, errfacet2 );
    +    /* else if errfacet1, the error was logged to qh.ferr but does not effect the output */
    +    trace0((qh ferr, 21, "qh_check_points: max distance outside %2.2g\n", maxdist));
    +  }
    +} /* check_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkconvex( facetlist, fault )
    +    check that each ridge in facetlist is convex
    +    fault = qh_DATAfault if reporting errors
    +          = qh_ALGORITHMfault otherwise
    +
    +  returns:
    +    counts Zconcaveridges and Zcoplanarridges
    +    errors if concaveridge or if merging an coplanar ridge
    +
    +  note:
    +    if not merging,
    +      tests vertices for neighboring simplicial facets
    +    else if ZEROcentrum,
    +      tests vertices for neighboring simplicial   facets
    +    else
    +      tests centrums of neighboring facets
    +
    +  design:
    +    for all facets
    +      report flipped facets
    +      if ZEROcentrum and simplicial neighbors
    +        test vertices for neighboring simplicial facets
    +      else
    +        test centrum against all neighbors
    +*/
    +void qh_checkconvex(facetT *facetlist, int fault) {
    +  facetT *facet, *neighbor, **neighborp, *errfacet1=NULL, *errfacet2=NULL;
    +  vertexT *vertex;
    +  realT dist;
    +  pointT *centrum;
    +  boolT waserror= False, centrum_warning= False, tempcentrum= False, allsimplicial;
    +  int neighbor_i;
    +
    +  trace1((qh ferr, 1026, "qh_checkconvex: check all ridges are convex\n"));
    +  if (!qh RERUN) {
    +    zzval_(Zconcaveridges)= 0;
    +    zzval_(Zcoplanarridges)= 0;
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->flipped) {
    +      qh_precision("flipped facet");
    +      qh_fprintf(qh ferr, 6113, "qhull precision error: f%d is flipped(interior point is outside)\n",
    +               facet->id);
    +      errfacet1= facet;
    +      waserror= True;
    +      continue;
    +    }
    +    if (qh MERGING && (!qh ZEROcentrum || !facet->simplicial || facet->tricoplanar))
    +      allsimplicial= False;
    +    else {
    +      allsimplicial= True;
    +      neighbor_i= 0;
    +      FOREACHneighbor_(facet) {
    +        vertex= SETelemt_(facet->vertices, neighbor_i++, vertexT);
    +        if (!neighbor->simplicial || neighbor->tricoplanar) {
    +          allsimplicial= False;
    +          continue;
    +        }
    +        qh_distplane(vertex->point, neighbor, &dist);
    +        if (dist > -qh DISTround) {
    +          if (fault == qh_DATAfault) {
    +            qh_precision("coplanar or concave ridge");
    +            qh_fprintf(qh ferr, 6114, "qhull precision error: initial simplex is not convex. Distance=%.2g\n", dist);
    +            qh_errexit(qh_ERRsingular, NULL, NULL);
    +          }
    +          if (dist > qh DISTround) {
    +            zzinc_(Zconcaveridges);
    +            qh_precision("concave ridge");
    +            qh_fprintf(qh ferr, 6115, "qhull precision error: f%d is concave to f%d, since p%d(v%d) is %6.4g above\n",
    +              facet->id, neighbor->id, qh_pointid(vertex->point), vertex->id, dist);
    +            errfacet1= facet;
    +            errfacet2= neighbor;
    +            waserror= True;
    +          }else if (qh ZEROcentrum) {
    +            if (dist > 0) {     /* qh_checkzero checks that dist < - qh DISTround */
    +              zzinc_(Zcoplanarridges);
    +              qh_precision("coplanar ridge");
    +              qh_fprintf(qh ferr, 6116, "qhull precision error: f%d is clearly not convex to f%d, since p%d(v%d) is %6.4g above\n",
    +                facet->id, neighbor->id, qh_pointid(vertex->point), vertex->id, dist);
    +              errfacet1= facet;
    +              errfacet2= neighbor;
    +              waserror= True;
    +            }
    +          }else {
    +            zzinc_(Zcoplanarridges);
    +            qh_precision("coplanar ridge");
    +            trace0((qh ferr, 22, "qhull precision error: f%d may be coplanar to f%d, since p%d(v%d) is within %6.4g during p%d\n",
    +              facet->id, neighbor->id, qh_pointid(vertex->point), vertex->id, dist, qh furthest_id));
    +          }
    +        }
    +      }
    +    }
    +    if (!allsimplicial) {
    +      if (qh CENTERtype == qh_AScentrum) {
    +        if (!facet->center)
    +          facet->center= qh_getcentrum(facet);
    +        centrum= facet->center;
    +      }else {
    +        if (!centrum_warning && (!facet->simplicial || facet->tricoplanar)) {
    +           centrum_warning= True;
    +           qh_fprintf(qh ferr, 7062, "qhull warning: recomputing centrums for convexity test.  This may lead to false, precision errors.\n");
    +        }
    +        centrum= qh_getcentrum(facet);
    +        tempcentrum= True;
    +      }
    +      FOREACHneighbor_(facet) {
    +        if (qh ZEROcentrum && facet->simplicial && neighbor->simplicial)
    +          continue;
    +        if (facet->tricoplanar || neighbor->tricoplanar)
    +          continue;
    +        zzinc_(Zdistconvex);
    +        qh_distplane(centrum, neighbor, &dist);
    +        if (dist > qh DISTround) {
    +          zzinc_(Zconcaveridges);
    +          qh_precision("concave ridge");
    +          qh_fprintf(qh ferr, 6117, "qhull precision error: f%d is concave to f%d.  Centrum of f%d is %6.4g above f%d\n",
    +            facet->id, neighbor->id, facet->id, dist, neighbor->id);
    +          errfacet1= facet;
    +          errfacet2= neighbor;
    +          waserror= True;
    +        }else if (dist >= 0.0) {   /* if arithmetic always rounds the same,
    +                                     can test against centrum radius instead */
    +          zzinc_(Zcoplanarridges);
    +          qh_precision("coplanar ridge");
    +          qh_fprintf(qh ferr, 6118, "qhull precision error: f%d is coplanar or concave to f%d.  Centrum of f%d is %6.4g above f%d\n",
    +            facet->id, neighbor->id, facet->id, dist, neighbor->id);
    +          errfacet1= facet;
    +          errfacet2= neighbor;
    +          waserror= True;
    +        }
    +      }
    +      if (tempcentrum)
    +        qh_memfree(centrum, qh normal_size);
    +    }
    +  }
    +  if (waserror && !qh FORCEoutput)
    +    qh_errexit2(qh_ERRprec, errfacet1, errfacet2);
    +} /* checkconvex */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkfacet( facet, newmerge, waserror )
    +    checks for consistency errors in facet
    +    newmerge set if from merge.c
    +
    +  returns:
    +    sets waserror if any error occurs
    +
    +  checks:
    +    vertex ids are inverse sorted
    +    unless newmerge, at least hull_dim neighbors and vertices (exactly if simplicial)
    +    if non-simplicial, at least as many ridges as neighbors
    +    neighbors are not duplicated
    +    ridges are not duplicated
    +    in 3-d, ridges=verticies
    +    (qh.hull_dim-1) ridge vertices
    +    neighbors are reciprocated
    +    ridge neighbors are facet neighbors and a ridge for every neighbor
    +    simplicial neighbors match facetintersect
    +    vertex intersection matches vertices of common ridges
    +    vertex neighbors and facet vertices agree
    +    all ridges have distinct vertex sets
    +
    +  notes:
    +    uses neighbor->seen
    +
    +  design:
    +    check sets
    +    check vertices
    +    check sizes of neighbors and vertices
    +    check for qh_MERGEridge and qh_DUPLICATEridge flags
    +    check neighbor set
    +    check ridge set
    +    check ridges, neighbors, and vertices
    +*/
    +void qh_checkfacet(facetT *facet, boolT newmerge, boolT *waserrorp) {
    +  facetT *neighbor, **neighborp, *errother=NULL;
    +  ridgeT *ridge, **ridgep, *errridge= NULL, *ridge2;
    +  vertexT *vertex, **vertexp;
    +  unsigned previousid= INT_MAX;
    +  int numneighbors, numvertices, numridges=0, numRvertices=0;
    +  boolT waserror= False;
    +  int skipA, skipB, ridge_i, ridge_n, i;
    +  setT *intersection;
    +
    +  if (facet->visible) {
    +    qh_fprintf(qh ferr, 6119, "qhull internal error (qh_checkfacet): facet f%d is on the visible_list\n",
    +      facet->id);
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  if (!facet->normal) {
    +    qh_fprintf(qh ferr, 6120, "qhull internal error (qh_checkfacet): facet f%d does not have  a normal\n",
    +      facet->id);
    +    waserror= True;
    +  }
    +  qh_setcheck(facet->vertices, "vertices for f", facet->id);
    +  qh_setcheck(facet->ridges, "ridges for f", facet->id);
    +  qh_setcheck(facet->outsideset, "outsideset for f", facet->id);
    +  qh_setcheck(facet->coplanarset, "coplanarset for f", facet->id);
    +  qh_setcheck(facet->neighbors, "neighbors for f", facet->id);
    +  FOREACHvertex_(facet->vertices) {
    +    if (vertex->deleted) {
    +      qh_fprintf(qh ferr, 6121, "qhull internal error (qh_checkfacet): deleted vertex v%d in f%d\n", vertex->id, facet->id);
    +      qh_errprint("ERRONEOUS", NULL, NULL, NULL, vertex);
    +      waserror= True;
    +    }
    +    if (vertex->id >= previousid) {
    +      qh_fprintf(qh ferr, 6122, "qhull internal error (qh_checkfacet): vertices of f%d are not in descending id order at v%d\n", facet->id, vertex->id);
    +      waserror= True;
    +      break;
    +    }
    +    previousid= vertex->id;
    +  }
    +  numneighbors= qh_setsize(facet->neighbors);
    +  numvertices= qh_setsize(facet->vertices);
    +  numridges= qh_setsize(facet->ridges);
    +  if (facet->simplicial) {
    +    if (numvertices+numneighbors != 2*qh hull_dim
    +    && !facet->degenerate && !facet->redundant) {
    +      qh_fprintf(qh ferr, 6123, "qhull internal error (qh_checkfacet): for simplicial facet f%d, #vertices %d + #neighbors %d != 2*qh hull_dim\n",
    +                facet->id, numvertices, numneighbors);
    +      qh_setprint(qh ferr, "", facet->neighbors);
    +      waserror= True;
    +    }
    +  }else { /* non-simplicial */
    +    if (!newmerge
    +    &&(numvertices < qh hull_dim || numneighbors < qh hull_dim)
    +    && !facet->degenerate && !facet->redundant) {
    +      qh_fprintf(qh ferr, 6124, "qhull internal error (qh_checkfacet): for facet f%d, #vertices %d or #neighbors %d < qh hull_dim\n",
    +         facet->id, numvertices, numneighbors);
    +       waserror= True;
    +    }
    +    /* in 3-d, can get a vertex twice in an edge list, e.g., RBOX 1000 s W1e-13 t995849315 D2 | QHULL d Tc Tv TP624 TW1e-13 T4 */
    +    if (numridges < numneighbors
    +    ||(qh hull_dim == 3 && numvertices > numridges && !qh NEWfacets)
    +    ||(qh hull_dim == 2 && numridges + numvertices + numneighbors != 6)) {
    +      if (!facet->degenerate && !facet->redundant) {
    +        qh_fprintf(qh ferr, 6125, "qhull internal error (qh_checkfacet): for facet f%d, #ridges %d < #neighbors %d or(3-d) > #vertices %d or(2-d) not all 2\n",
    +            facet->id, numridges, numneighbors, numvertices);
    +        waserror= True;
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge || neighbor == qh_DUPLICATEridge) {
    +      qh_fprintf(qh ferr, 6126, "qhull internal error (qh_checkfacet): facet f%d still has a MERGE or DUP neighbor\n", facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +    neighbor->seen= True;
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (!qh_setin(neighbor->neighbors, facet)) {
    +      qh_fprintf(qh ferr, 6127, "qhull internal error (qh_checkfacet): facet f%d has neighbor f%d, but f%d does not have neighbor f%d\n",
    +              facet->id, neighbor->id, neighbor->id, facet->id);
    +      errother= neighbor;
    +      waserror= True;
    +    }
    +    if (!neighbor->seen) {
    +      qh_fprintf(qh ferr, 6128, "qhull internal error (qh_checkfacet): facet f%d has a duplicate neighbor f%d\n",
    +              facet->id, neighbor->id);
    +      errother= neighbor;
    +      waserror= True;
    +    }
    +    neighbor->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges) {
    +    qh_setcheck(ridge->vertices, "vertices for r", ridge->id);
    +    ridge->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges) {
    +    if (ridge->seen) {
    +      qh_fprintf(qh ferr, 6129, "qhull internal error (qh_checkfacet): facet f%d has a duplicate ridge r%d\n",
    +              facet->id, ridge->id);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +    ridge->seen= True;
    +    numRvertices= qh_setsize(ridge->vertices);
    +    if (numRvertices != qh hull_dim - 1) {
    +      qh_fprintf(qh ferr, 6130, "qhull internal error (qh_checkfacet): ridge between f%d and f%d has %d vertices\n",
    +                ridge->top->id, ridge->bottom->id, numRvertices);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +    neighbor= otherfacet_(ridge, facet);
    +    neighbor->seen= True;
    +    if (!qh_setin(facet->neighbors, neighbor)) {
    +      qh_fprintf(qh ferr, 6131, "qhull internal error (qh_checkfacet): for facet f%d, neighbor f%d of ridge r%d not in facet\n",
    +           facet->id, neighbor->id, ridge->id);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +  }
    +  if (!facet->simplicial) {
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->seen) {
    +        qh_fprintf(qh ferr, 6132, "qhull internal error (qh_checkfacet): facet f%d does not have a ridge for neighbor f%d\n",
    +              facet->id, neighbor->id);
    +        errother= neighbor;
    +        waserror= True;
    +      }
    +      intersection= qh_vertexintersect_new(facet->vertices, neighbor->vertices);
    +      qh_settemppush(intersection);
    +      FOREACHvertex_(facet->vertices) {
    +        vertex->seen= False;
    +        vertex->seen2= False;
    +      }
    +      FOREACHvertex_(intersection)
    +        vertex->seen= True;
    +      FOREACHridge_(facet->ridges) {
    +        if (neighbor != otherfacet_(ridge, facet))
    +            continue;
    +        FOREACHvertex_(ridge->vertices) {
    +          if (!vertex->seen) {
    +            qh_fprintf(qh ferr, 6133, "qhull internal error (qh_checkfacet): vertex v%d in r%d not in f%d intersect f%d\n",
    +                  vertex->id, ridge->id, facet->id, neighbor->id);
    +            qh_errexit(qh_ERRqhull, facet, ridge);
    +          }
    +          vertex->seen2= True;
    +        }
    +      }
    +      if (!newmerge) {
    +        FOREACHvertex_(intersection) {
    +          if (!vertex->seen2) {
    +            if (qh IStracing >=3 || !qh MERGING) {
    +              qh_fprintf(qh ferr, 6134, "qhull precision error (qh_checkfacet): vertex v%d in f%d intersect f%d but\n\
    + not in a ridge.  This is ok under merging.  Last point was p%d\n",
    +                     vertex->id, facet->id, neighbor->id, qh furthest_id);
    +              if (!qh FORCEoutput && !qh MERGING) {
    +                qh_errprint("ERRONEOUS", facet, neighbor, NULL, vertex);
    +                if (!qh MERGING)
    +                  qh_errexit(qh_ERRqhull, NULL, NULL);
    +              }
    +            }
    +          }
    +        }
    +      }
    +      qh_settempfree(&intersection);
    +    }
    +  }else { /* simplicial */
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->simplicial) {
    +        skipA= SETindex_(facet->neighbors, neighbor);
    +        skipB= qh_setindex(neighbor->neighbors, facet);
    +        if (skipA<0 || skipB<0 || !qh_setequal_skip(facet->vertices, skipA, neighbor->vertices, skipB)) {
    +          qh_fprintf(qh ferr, 6135, "qhull internal error (qh_checkfacet): facet f%d skip %d and neighbor f%d skip %d do not match \n",
    +                   facet->id, skipA, neighbor->id, skipB);
    +          errother= neighbor;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (qh hull_dim < 5 && (qh IStracing > 2 || qh CHECKfrequently)) {
    +    FOREACHridge_i_(facet->ridges) {           /* expensive */
    +      for (i=ridge_i+1; i < ridge_n; i++) {
    +        ridge2= SETelemt_(facet->ridges, i, ridgeT);
    +        if (qh_setequal(ridge->vertices, ridge2->vertices)) {
    +          qh_fprintf(qh ferr, 6227, "Qhull internal error (qh_checkfacet): ridges r%d and r%d have the same vertices\n",
    +                  ridge->id, ridge2->id);
    +          errridge= ridge;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_errprint("ERRONEOUS", facet, errother, errridge, NULL);
    +    *waserrorp= True;
    +  }
    +} /* checkfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkflipped_all( facetlist )
    +    checks orientation of facets in list against interior point
    +*/
    +void qh_checkflipped_all(facetT *facetlist) {
    +  facetT *facet;
    +  boolT waserror= False;
    +  realT dist;
    +
    +  if (facetlist == qh facet_list)
    +    zzval_(Zflippedfacets)= 0;
    +  FORALLfacet_(facetlist) {
    +    if (facet->normal && !qh_checkflipped(facet, &dist, !qh_ALL)) {
    +      qh_fprintf(qh ferr, 6136, "qhull precision error: facet f%d is flipped, distance= %6.12g\n",
    +              facet->id, dist);
    +      if (!qh FORCEoutput) {
    +        qh_errprint("ERRONEOUS", facet, NULL, NULL, NULL);
    +        waserror= True;
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_fprintf(qh ferr, 8101, "\n\
    +A flipped facet occurs when its distance to the interior point is\n\
    +greater than %2.2g, the maximum roundoff error.\n", -qh DISTround);
    +    qh_errexit(qh_ERRprec, NULL, NULL);
    +  }
    +} /* checkflipped_all */
    +
    +/*---------------------------------
    +
    +  qh_checkpolygon( facetlist )
    +    checks the correctness of the structure
    +
    +  notes:
    +    call with either qh.facet_list or qh.newfacet_list
    +    checks num_facets and num_vertices if qh.facet_list
    +
    +  design:
    +    for each facet
    +      checks facet and outside set
    +    initializes vertexlist
    +    for each facet
    +      checks vertex set
    +    if checking all facets(qh.facetlist)
    +      check facet count
    +      if qh.VERTEXneighbors
    +        check vertex neighbors and count
    +      check vertex count
    +*/
    +void qh_checkpolygon(facetT *facetlist) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp, *vertexlist;
    +  int numfacets= 0, numvertices= 0, numridges= 0;
    +  int totvneighbors= 0, totvertices= 0;
    +  boolT waserror= False, nextseen= False, visibleseen= False;
    +
    +  trace1((qh ferr, 1027, "qh_checkpolygon: check all facets from f%d\n", facetlist->id));
    +  if (facetlist != qh facet_list || qh ONLYgood)
    +    nextseen= True;
    +  FORALLfacet_(facetlist) {
    +    if (facet == qh visible_list)
    +      visibleseen= True;
    +    if (!facet->visible) {
    +      if (!nextseen) {
    +        if (facet == qh facet_next)
    +          nextseen= True;
    +        else if (qh_setsize(facet->outsideset)) {
    +          if (!qh NARROWhull
    +#if !qh_COMPUTEfurthest
    +               || facet->furthestdist >= qh MINoutside
    +#endif
    +                        ) {
    +            qh_fprintf(qh ferr, 6137, "qhull internal error (qh_checkpolygon): f%d has outside points before qh facet_next\n",
    +                     facet->id);
    +            qh_errexit(qh_ERRqhull, facet, NULL);
    +          }
    +        }
    +      }
    +      numfacets++;
    +      qh_checkfacet(facet, False, &waserror);
    +    }
    +  }
    +  if (qh visible_list && !visibleseen && facetlist == qh facet_list) {
    +    qh_fprintf(qh ferr, 6138, "qhull internal error (qh_checkpolygon): visible list f%d no longer on facet list\n", qh visible_list->id);
    +    qh_printlists();
    +    qh_errexit(qh_ERRqhull, qh visible_list, NULL);
    +  }
    +  if (facetlist == qh facet_list)
    +    vertexlist= qh vertex_list;
    +  else if (facetlist == qh newfacet_list)
    +    vertexlist= qh newvertex_list;
    +  else
    +    vertexlist= NULL;
    +  FORALLvertex_(vertexlist) {
    +    vertex->seen= False;
    +    vertex->visitid= 0;
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->visible)
    +      continue;
    +    if (facet->simplicial)
    +      numridges += qh hull_dim;
    +    else
    +      numridges += qh_setsize(facet->ridges);
    +    FOREACHvertex_(facet->vertices) {
    +      vertex->visitid++;
    +      if (!vertex->seen) {
    +        vertex->seen= True;
    +        numvertices++;
    +        if (qh_pointid(vertex->point) == qh_IDunknown) {
    +          qh_fprintf(qh ferr, 6139, "qhull internal error (qh_checkpolygon): unknown point %p for vertex v%d first_point %p\n",
    +                   vertex->point, vertex->id, qh first_point);
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  qh vertex_visit += (unsigned int)numfacets;
    +  if (facetlist == qh facet_list) {
    +    if (numfacets != qh num_facets - qh num_visible) {
    +      qh_fprintf(qh ferr, 6140, "qhull internal error (qh_checkpolygon): actual number of facets is %d, cumulative facet count is %d - %d visible facets\n",
    +              numfacets, qh num_facets, qh num_visible);
    +      waserror= True;
    +    }
    +    qh vertex_visit++;
    +    if (qh VERTEXneighbors) {
    +      FORALLvertices {
    +        qh_setcheck(vertex->neighbors, "neighbors for v", vertex->id);
    +        if (vertex->deleted)
    +          continue;
    +        totvneighbors += qh_setsize(vertex->neighbors);
    +      }
    +      FORALLfacet_(facetlist)
    +        totvertices += qh_setsize(facet->vertices);
    +      if (totvneighbors != totvertices) {
    +        qh_fprintf(qh ferr, 6141, "qhull internal error (qh_checkpolygon): vertex neighbors inconsistent.  Totvneighbors %d, totvertices %d\n",
    +                totvneighbors, totvertices);
    +        waserror= True;
    +      }
    +    }
    +    if (numvertices != qh num_vertices - qh_setsize(qh del_vertices)) {
    +      qh_fprintf(qh ferr, 6142, "qhull internal error (qh_checkpolygon): actual number of vertices is %d, cumulative vertex count is %d\n",
    +              numvertices, qh num_vertices - qh_setsize(qh del_vertices));
    +      waserror= True;
    +    }
    +    if (qh hull_dim == 2 && numvertices != numfacets) {
    +      qh_fprintf(qh ferr, 6143, "qhull internal error (qh_checkpolygon): #vertices %d != #facets %d\n",
    +        numvertices, numfacets);
    +      waserror= True;
    +    }
    +    if (qh hull_dim == 3 && numvertices + numfacets - numridges/2 != 2) {
    +      qh_fprintf(qh ferr, 7063, "qhull warning: #vertices %d + #facets %d - #edges %d != 2\n\
    +        A vertex appears twice in a edge list.  May occur during merging.",
    +        numvertices, numfacets, numridges/2);
    +      /* occurs if lots of merging and a vertex ends up twice in an edge list.  e.g., RBOX 1000 s W1e-13 t995849315 D2 | QHULL d Tc Tv */
    +    }
    +  }
    +  if (waserror)
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +} /* checkpolygon */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkvertex( vertex )
    +    check vertex for consistency
    +    checks vertex->neighbors
    +
    +  notes:
    +    neighbors checked efficiently in checkpolygon
    +*/
    +void qh_checkvertex(vertexT *vertex) {
    +  boolT waserror= False;
    +  facetT *neighbor, **neighborp, *errfacet=NULL;
    +
    +  if (qh_pointid(vertex->point) == qh_IDunknown) {
    +    qh_fprintf(qh ferr, 6144, "qhull internal error (qh_checkvertex): unknown point id %p\n", vertex->point);
    +    waserror= True;
    +  }
    +  if (vertex->id >= qh vertex_id) {
    +    qh_fprintf(qh ferr, 6145, "qhull internal error (qh_checkvertex): unknown vertex id %d\n", vertex->id);
    +    waserror= True;
    +  }
    +  if (!waserror && !vertex->deleted) {
    +    if (qh_setsize(vertex->neighbors)) {
    +      FOREACHneighbor_(vertex) {
    +        if (!qh_setin(neighbor->vertices, vertex)) {
    +          qh_fprintf(qh ferr, 6146, "qhull internal error (qh_checkvertex): neighbor f%d does not contain v%d\n", neighbor->id, vertex->id);
    +          errfacet= neighbor;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_errprint("ERRONEOUS", NULL, NULL, NULL, vertex);
    +    qh_errexit(qh_ERRqhull, errfacet, NULL);
    +  }
    +} /* checkvertex */
    +
    +/*---------------------------------
    +
    +  qh_clearcenters( type )
    +    clear old data from facet->center
    +
    +  notes:
    +    sets new centertype
    +    nop if CENTERtype is the same
    +*/
    +void qh_clearcenters(qh_CENTER type) {
    +  facetT *facet;
    +
    +  if (qh CENTERtype != type) {
    +    FORALLfacets {
    +      if (facet->tricoplanar && !facet->keepcentrum)
    +          facet->center= NULL;  /* center is owned by the ->keepcentrum facet */
    +      else if (qh CENTERtype == qh_ASvoronoi){
    +        if (facet->center) {
    +          qh_memfree(facet->center, qh center_size);
    +          facet->center= NULL;
    +        }
    +      }else /* qh.CENTERtype == qh_AScentrum */ {
    +        if (facet->center) {
    +          qh_memfree(facet->center, qh normal_size);
    +          facet->center= NULL;
    +        }
    +      }
    +    }
    +    qh CENTERtype= type;
    +  }
    +  trace2((qh ferr, 2043, "qh_clearcenters: switched to center type %d\n", type));
    +} /* clearcenters */
    +
    +/*---------------------------------
    +
    +  qh_createsimplex( vertices )
    +    creates a simplex from a set of vertices
    +
    +  returns:
    +    initializes qh.facet_list to the simplex
    +    initializes qh.newfacet_list, .facet_tail
    +    initializes qh.vertex_list, .newvertex_list, .vertex_tail
    +
    +  design:
    +    initializes lists
    +    for each vertex
    +      create a new facet
    +    for each new facet
    +      create its neighbor set
    +*/
    +void qh_createsimplex(setT *vertices) {
    +  facetT *facet= NULL, *newfacet;
    +  boolT toporient= True;
    +  int vertex_i, vertex_n, nth;
    +  setT *newfacets= qh_settemp(qh hull_dim+1);
    +  vertexT *vertex;
    +
    +  qh facet_list= qh newfacet_list= qh facet_tail= qh_newfacet();
    +  qh num_facets= qh num_vertices= qh num_visible= 0;
    +  qh vertex_list= qh newvertex_list= qh vertex_tail= qh_newvertex(NULL);
    +  FOREACHvertex_i_(vertices) {
    +    newfacet= qh_newfacet();
    +    newfacet->vertices= qh_setnew_delnthsorted(vertices, vertex_n,
    +                                                vertex_i, 0);
    +    newfacet->toporient= (unsigned char)toporient;
    +    qh_appendfacet(newfacet);
    +    newfacet->newfacet= True;
    +    qh_appendvertex(vertex);
    +    qh_setappend(&newfacets, newfacet);
    +    toporient ^= True;
    +  }
    +  FORALLnew_facets {
    +    nth= 0;
    +    FORALLfacet_(qh newfacet_list) {
    +      if (facet != newfacet)
    +        SETelem_(newfacet->neighbors, nth++)= facet;
    +    }
    +    qh_settruncate(newfacet->neighbors, qh hull_dim);
    +  }
    +  qh_settempfree(&newfacets);
    +  trace1((qh ferr, 1028, "qh_createsimplex: created simplex\n"));
    +} /* createsimplex */
    +
    +/*---------------------------------
    +
    +  qh_delridge( ridge )
    +    deletes ridge from data structures it belongs to
    +    frees up its memory
    +
    +  notes:
    +    in merge.c, caller sets vertex->delridge for each vertex
    +    ridges also freed in qh_freeqhull
    +*/
    +void qh_delridge(ridgeT *ridge) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  qh_setdel(ridge->top->ridges, ridge);
    +  qh_setdel(ridge->bottom->ridges, ridge);
    +  qh_setfree(&(ridge->vertices));
    +  qh_memfree_(ridge, (int)sizeof(ridgeT), freelistp);
    +} /* delridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_delvertex( vertex )
    +    deletes a vertex and frees its memory
    +
    +  notes:
    +    assumes vertex->adjacencies have been updated if needed
    +    unlinks from vertex_list
    +*/
    +void qh_delvertex(vertexT *vertex) {
    +
    +  if (vertex == qh tracevertex)
    +    qh tracevertex= NULL;
    +  qh_removevertex(vertex);
    +  qh_setfree(&vertex->neighbors);
    +  qh_memfree(vertex, (int)sizeof(vertexT));
    +} /* delvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_facet3vertex(  )
    +    return temporary set of 3-d vertices in qh_ORIENTclock order
    +
    +  design:
    +    if simplicial facet
    +      build set from facet->vertices with facet->toporient
    +    else
    +      for each ridge in order
    +        build set from ridge's vertices
    +*/
    +setT *qh_facet3vertex(facetT *facet) {
    +  ridgeT *ridge, *firstridge;
    +  vertexT *vertex;
    +  int cntvertices, cntprojected=0;
    +  setT *vertices;
    +
    +  cntvertices= qh_setsize(facet->vertices);
    +  vertices= qh_settemp(cntvertices);
    +  if (facet->simplicial) {
    +    if (cntvertices != 3) {
    +      qh_fprintf(qh ferr, 6147, "qhull internal error (qh_facet3vertex): only %d vertices for simplicial facet f%d\n",
    +                  cntvertices, facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +    qh_setappend(&vertices, SETfirst_(facet->vertices));
    +    if (facet->toporient ^ qh_ORIENTclock)
    +      qh_setappend(&vertices, SETsecond_(facet->vertices));
    +    else
    +      qh_setaddnth(&vertices, 0, SETsecond_(facet->vertices));
    +    qh_setappend(&vertices, SETelem_(facet->vertices, 2));
    +  }else {
    +    ridge= firstridge= SETfirstt_(facet->ridges, ridgeT);   /* no infinite */
    +    while ((ridge= qh_nextridge3d(ridge, facet, &vertex))) {
    +      qh_setappend(&vertices, vertex);
    +      if (++cntprojected > cntvertices || ridge == firstridge)
    +        break;
    +    }
    +    if (!ridge || cntprojected != cntvertices) {
    +      qh_fprintf(qh ferr, 6148, "qhull internal error (qh_facet3vertex): ridges for facet %d don't match up.  got at least %d\n",
    +                  facet->id, cntprojected);
    +      qh_errexit(qh_ERRqhull, facet, ridge);
    +    }
    +  }
    +  return vertices;
    +} /* facet3vertex */
    +
    +/*---------------------------------
    +
    +  qh_findbestfacet( point, bestoutside, bestdist, isoutside )
    +    find facet that is furthest below a point
    +
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    if bestoutside is set (e.g., qh_ALL)
    +      returns best facet that is not upperdelaunay
    +      if Delaunay and inside, point is outside circumsphere of bestfacet
    +    else
    +      returns first facet below point
    +      if point is inside, returns nearest, !upperdelaunay facet
    +    distance to facet
    +    isoutside set if outside of facet
    +
    +  notes:
    +    For tricoplanar facets, this finds one of the tricoplanar facets closest
    +    to the point.  For Delaunay triangulations, the point may be inside a
    +    different tricoplanar facet. See locate a facet with qh_findbestfacet()
    +
    +    If inside, qh_findbestfacet performs an exhaustive search
    +       this may be too conservative.  Sometimes it is clearly required.
    +
    +    qh_findbestfacet is not used by qhull.
    +    uses qh.visit_id and qh.coplanarset
    +
    +  see:
    +    qh_findbest
    +*/
    +facetT *qh_findbestfacet(pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside) {
    +  facetT *bestfacet= NULL;
    +  int numpart, totpart= 0;
    +
    +  bestfacet= qh_findbest(point, qh facet_list,
    +                            bestoutside, !qh_ISnewfacets, bestoutside /* qh_NOupper */,
    +                            bestdist, isoutside, &totpart);
    +  if (*bestdist < -qh DISTround) {
    +    bestfacet= qh_findfacet_all(point, bestdist, isoutside, &numpart);
    +    totpart += numpart;
    +    if ((isoutside && *isoutside && bestoutside)
    +    || (isoutside && !*isoutside && bestfacet->upperdelaunay)) {
    +      bestfacet= qh_findbest(point, bestfacet,
    +                            bestoutside, False, bestoutside,
    +                            bestdist, isoutside, &totpart);
    +      totpart += numpart;
    +    }
    +  }
    +  trace3((qh ferr, 3014, "qh_findbestfacet: f%d dist %2.2g isoutside %d totpart %d\n",
    +      bestfacet->id, *bestdist, (isoutside ? *isoutside : UINT_MAX), totpart));
    +  return bestfacet;
    +} /* findbestfacet */
    +
    +/*---------------------------------
    +
    +  qh_findbestlower( facet, point, bestdist, numpart )
    +    returns best non-upper, non-flipped neighbor of facet for point
    +    if needed, searches vertex neighbors
    +
    +  returns:
    +    returns bestdist and updates numpart
    +
    +  notes:
    +    if Delaunay and inside, point is outside of circumsphere of bestfacet
    +    called by qh_findbest() for points above an upperdelaunay facet
    +
    +*/
    +facetT *qh_findbestlower(facetT *upperfacet, pointT *point, realT *bestdistp, int *numpart) {
    +  facetT *neighbor, **neighborp, *bestfacet= NULL;
    +  realT bestdist= -REALmax/2 /* avoid underflow */;
    +  realT dist;
    +  vertexT *vertex;
    +  boolT isoutside= False;  /* not used */
    +
    +  zinc_(Zbestlower);
    +  FOREACHneighbor_(upperfacet) {
    +    if (neighbor->upperdelaunay || neighbor->flipped)
    +      continue;
    +    (*numpart)++;
    +    qh_distplane(point, neighbor, &dist);
    +    if (dist > bestdist) {
    +      bestfacet= neighbor;
    +      bestdist= dist;
    +    }
    +  }
    +  if (!bestfacet) {
    +    zinc_(Zbestlowerv);
    +    /* rarely called, numpart does not count nearvertex computations */
    +    vertex= qh_nearvertex(upperfacet, point, &dist);
    +    qh_vertexneighbors();
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay || neighbor->flipped)
    +        continue;
    +      (*numpart)++;
    +      qh_distplane(point, neighbor, &dist);
    +      if (dist > bestdist) {
    +        bestfacet= neighbor;
    +        bestdist= dist;
    +      }
    +    }
    +  }
    +  if (!bestfacet) {
    +    zinc_(Zbestlowerall);  /* invoked once per point in outsideset */
    +    zmax_(Zbestloweralln, qh num_facets);
    +    /* [dec'15] Previously reported as QH6228 */
    +    trace3((qh ferr, 3025, "qh_findbestlower: all neighbors of facet %d are flipped or upper Delaunay.  Search all facets\n",
    +        upperfacet->id));
    +    /* rarely called */
    +    bestfacet= qh_findfacet_all(point, &bestdist, &isoutside, numpart);
    +  }
    +  *bestdistp= bestdist;
    +  trace3((qh ferr, 3015, "qh_findbestlower: f%d dist %2.2g for f%d p%d\n",
    +          bestfacet->id, bestdist, upperfacet->id, qh_pointid(point)));
    +  return bestfacet;
    +} /* findbestlower */
    +
    +/*---------------------------------
    +
    +  qh_findfacet_all( point, bestdist, isoutside, numpart )
    +    exhaustive search for facet below a point
    +
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    returns first facet below point
    +    if point is inside,
    +      returns nearest facet
    +    distance to facet
    +    isoutside if point is outside of the hull
    +    number of distance tests
    +
    +  notes:
    +    primarily for library users, rarely used by Qhull
    +*/
    +facetT *qh_findfacet_all(pointT *point, realT *bestdist, boolT *isoutside,
    +                          int *numpart) {
    +  facetT *bestfacet= NULL, *facet;
    +  realT dist;
    +  int totpart= 0;
    +
    +  *bestdist= -REALmax;
    +  *isoutside= False;
    +  FORALLfacets {
    +    if (facet->flipped || !facet->normal)
    +      continue;
    +    totpart++;
    +    qh_distplane(point, facet, &dist);
    +    if (dist > *bestdist) {
    +      *bestdist= dist;
    +      bestfacet= facet;
    +      if (dist > qh MINoutside) {
    +        *isoutside= True;
    +        break;
    +      }
    +    }
    +  }
    +  *numpart= totpart;
    +  trace3((qh ferr, 3016, "qh_findfacet_all: f%d dist %2.2g isoutside %d totpart %d\n",
    +          getid_(bestfacet), *bestdist, *isoutside, totpart));
    +  return bestfacet;
    +} /* findfacet_all */
    +
    +/*---------------------------------
    +
    +  qh_findgood( facetlist, goodhorizon )
    +    identify good facets for qh.PRINTgood
    +    if qh.GOODvertex>0
    +      facet includes point as vertex
    +      if !match, returns goodhorizon
    +      inactive if qh.MERGING
    +    if qh.GOODpoint
    +      facet is visible or coplanar (>0) or not visible (<0)
    +    if qh.GOODthreshold
    +      facet->normal matches threshold
    +    if !goodhorizon and !match,
    +      selects facet with closest angle
    +      sets GOODclosest
    +
    +  returns:
    +    number of new, good facets found
    +    determines facet->good
    +    may update qh.GOODclosest
    +
    +  notes:
    +    qh_findgood_all further reduces the good region
    +
    +  design:
    +    count good facets
    +    mark good facets for qh.GOODpoint
    +    mark good facets for qh.GOODthreshold
    +    if necessary
    +      update qh.GOODclosest
    +*/
    +int qh_findgood(facetT *facetlist, int goodhorizon) {
    +  facetT *facet, *bestfacet= NULL;
    +  realT angle, bestangle= REALmax, dist;
    +  int  numgood=0;
    +
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      numgood++;
    +  }
    +  if (qh GOODvertex>0 && !qh MERGING) {
    +    FORALLfacet_(facetlist) {
    +      if (!qh_isvertex(qh GOODvertexp, facet->vertices)) {
    +        facet->good= False;
    +        numgood--;
    +      }
    +    }
    +  }
    +  if (qh GOODpoint && numgood) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && facet->normal) {
    +        zinc_(Zdistgood);
    +        qh_distplane(qh GOODpointp, facet, &dist);
    +        if ((qh GOODpoint > 0) ^ (dist > 0.0)) {
    +          facet->good= False;
    +          numgood--;
    +        }
    +      }
    +    }
    +  }
    +  if (qh GOODthreshold && (numgood || goodhorizon || qh GOODclosest)) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && facet->normal) {
    +        if (!qh_inthresholds(facet->normal, &angle)) {
    +          facet->good= False;
    +          numgood--;
    +          if (angle < bestangle) {
    +            bestangle= angle;
    +            bestfacet= facet;
    +          }
    +        }
    +      }
    +    }
    +    if (!numgood && (!goodhorizon || qh GOODclosest)) {
    +      if (qh GOODclosest) {
    +        if (qh GOODclosest->visible)
    +          qh GOODclosest= NULL;
    +        else {
    +          qh_inthresholds(qh GOODclosest->normal, &angle);
    +          if (angle < bestangle)
    +            bestfacet= qh GOODclosest;
    +        }
    +      }
    +      if (bestfacet && bestfacet != qh GOODclosest) {
    +        if (qh GOODclosest)
    +          qh GOODclosest->good= False;
    +        qh GOODclosest= bestfacet;
    +        bestfacet->good= True;
    +        numgood++;
    +        trace2((qh ferr, 2044, "qh_findgood: f%d is closest(%2.2g) to thresholds\n",
    +           bestfacet->id, bestangle));
    +        return numgood;
    +      }
    +    }else if (qh GOODclosest) { /* numgood > 0 */
    +      qh GOODclosest->good= False;
    +      qh GOODclosest= NULL;
    +    }
    +  }
    +  zadd_(Zgoodfacet, numgood);
    +  trace2((qh ferr, 2045, "qh_findgood: found %d good facets with %d good horizon\n",
    +               numgood, goodhorizon));
    +  if (!numgood && qh GOODvertex>0 && !qh MERGING)
    +    return goodhorizon;
    +  return numgood;
    +} /* findgood */
    +
    +/*---------------------------------
    +
    +  qh_findgood_all( facetlist )
    +    apply other constraints for good facets (used by qh.PRINTgood)
    +    if qh.GOODvertex
    +      facet includes (>0) or doesn't include (<0) point as vertex
    +      if last good facet and ONLYgood, prints warning and continues
    +    if qh.SPLITthresholds
    +      facet->normal matches threshold, or if none, the closest one
    +    calls qh_findgood
    +    nop if good not used
    +
    +  returns:
    +    clears facet->good if not good
    +    sets qh.num_good
    +
    +  notes:
    +    this is like qh_findgood but more restrictive
    +
    +  design:
    +    uses qh_findgood to mark good facets
    +    marks facets for qh.GOODvertex
    +    marks facets for qh.SPLITthreholds
    +*/
    +void qh_findgood_all(facetT *facetlist) {
    +  facetT *facet, *bestfacet=NULL;
    +  realT angle, bestangle= REALmax;
    +  int  numgood=0, startgood;
    +
    +  if (!qh GOODvertex && !qh GOODthreshold && !qh GOODpoint
    +  && !qh SPLITthresholds)
    +    return;
    +  if (!qh ONLYgood)
    +    qh_findgood(qh facet_list, 0);
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      numgood++;
    +  }
    +  if (qh GOODvertex <0 || (qh GOODvertex > 0 && qh MERGING)) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && ((qh GOODvertex > 0) ^ !!qh_isvertex(qh GOODvertexp, facet->vertices))) {
    +        if (!--numgood) {
    +          if (qh ONLYgood) {
    +            qh_fprintf(qh ferr, 7064, "qhull warning: good vertex p%d does not match last good facet f%d.  Ignored.\n",
    +               qh_pointid(qh GOODvertexp), facet->id);
    +            return;
    +          }else if (qh GOODvertex > 0)
    +            qh_fprintf(qh ferr, 7065, "qhull warning: point p%d is not a vertex('QV%d').\n",
    +                qh GOODvertex-1, qh GOODvertex-1);
    +          else
    +            qh_fprintf(qh ferr, 7066, "qhull warning: point p%d is a vertex for every facet('QV-%d').\n",
    +                -qh GOODvertex - 1, -qh GOODvertex - 1);
    +        }
    +        facet->good= False;
    +      }
    +    }
    +  }
    +  startgood= numgood;
    +  if (qh SPLITthresholds) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good) {
    +        if (!qh_inthresholds(facet->normal, &angle)) {
    +          facet->good= False;
    +          numgood--;
    +          if (angle < bestangle) {
    +            bestangle= angle;
    +            bestfacet= facet;
    +          }
    +        }
    +      }
    +    }
    +    if (!numgood && bestfacet) {
    +      bestfacet->good= True;
    +      numgood++;
    +      trace0((qh ferr, 23, "qh_findgood_all: f%d is closest(%2.2g) to thresholds\n",
    +           bestfacet->id, bestangle));
    +      return;
    +    }
    +  }
    +  qh num_good= numgood;
    +  trace0((qh ferr, 24, "qh_findgood_all: %d good facets remain out of %d facets\n",
    +        numgood, startgood));
    +} /* findgood_all */
    +
    +/*---------------------------------
    +
    +  qh_furthestnext()
    +    set qh.facet_next to facet with furthest of all furthest points
    +    searches all facets on qh.facet_list
    +
    +  notes:
    +    this may help avoid precision problems
    +*/
    +void qh_furthestnext(void /* qh.facet_list */) {
    +  facetT *facet, *bestfacet= NULL;
    +  realT dist, bestdist= -REALmax;
    +
    +  FORALLfacets {
    +    if (facet->outsideset) {
    +#if qh_COMPUTEfurthest
    +      pointT *furthest;
    +      furthest= (pointT*)qh_setlast(facet->outsideset);
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(furthest, facet, &dist);
    +#else
    +      dist= facet->furthestdist;
    +#endif
    +      if (dist > bestdist) {
    +        bestfacet= facet;
    +        bestdist= dist;
    +      }
    +    }
    +  }
    +  if (bestfacet) {
    +    qh_removefacet(bestfacet);
    +    qh_prependfacet(bestfacet, &qh facet_next);
    +    trace1((qh ferr, 1029, "qh_furthestnext: made f%d next facet(dist %.2g)\n",
    +            bestfacet->id, bestdist));
    +  }
    +} /* furthestnext */
    +
    +/*---------------------------------
    +
    +  qh_furthestout( facet )
    +    make furthest outside point the last point of outsideset
    +
    +  returns:
    +    updates facet->outsideset
    +    clears facet->notfurthest
    +    sets facet->furthestdist
    +
    +  design:
    +    determine best point of outsideset
    +    make it the last point of outsideset
    +*/
    +void qh_furthestout(facetT *facet) {
    +  pointT *point, **pointp, *bestpoint= NULL;
    +  realT dist, bestdist= -REALmax;
    +
    +  FOREACHpoint_(facet->outsideset) {
    +    qh_distplane(point, facet, &dist);
    +    zinc_(Zcomputefurthest);
    +    if (dist > bestdist) {
    +      bestpoint= point;
    +      bestdist= dist;
    +    }
    +  }
    +  if (bestpoint) {
    +    qh_setdel(facet->outsideset, point);
    +    qh_setappend(&facet->outsideset, point);
    +#if !qh_COMPUTEfurthest
    +    facet->furthestdist= bestdist;
    +#endif
    +  }
    +  facet->notfurthest= False;
    +  trace3((qh ferr, 3017, "qh_furthestout: p%d is furthest outside point of f%d\n",
    +          qh_pointid(point), facet->id));
    +} /* furthestout */
    +
    +
    +/*---------------------------------
    +
    +  qh_infiniteloop( facet )
    +    report infinite loop error due to facet
    +*/
    +void qh_infiniteloop(facetT *facet) {
    +
    +  qh_fprintf(qh ferr, 6149, "qhull internal error (qh_infiniteloop): potential infinite loop detected\n");
    +  qh_errexit(qh_ERRqhull, facet, NULL);
    +} /* qh_infiniteloop */
    +
    +/*---------------------------------
    +
    +  qh_initbuild()
    +    initialize hull and outside sets with point array
    +    qh.FIRSTpoint/qh.NUMpoints is point array
    +    if qh.GOODpoint
    +      adds qh.GOODpoint to initial hull
    +
    +  returns:
    +    qh_facetlist with initial hull
    +    points partioned into outside sets, coplanar sets, or inside
    +    initializes qh.GOODpointp, qh.GOODvertexp,
    +
    +  design:
    +    initialize global variables used during qh_buildhull
    +    determine precision constants and points with max/min coordinate values
    +      if qh.SCALElast, scale last coordinate(for 'd')
    +    build initial simplex
    +    partition input points into facets of initial simplex
    +    set up lists
    +    if qh.ONLYgood
    +      check consistency
    +      add qh.GOODvertex if defined
    +*/
    +void qh_initbuild( void) {
    +  setT *maxpoints, *vertices;
    +  facetT *facet;
    +  int i, numpart;
    +  realT dist;
    +  boolT isoutside;
    +
    +  qh furthest_id= qh_IDunknown;
    +  qh lastreport= 0;
    +  qh facet_id= qh vertex_id= qh ridge_id= 0;
    +  qh visit_id= qh vertex_visit= 0;
    +  qh maxoutdone= False;
    +
    +  if (qh GOODpoint > 0)
    +    qh GOODpointp= qh_point(qh GOODpoint-1);
    +  else if (qh GOODpoint < 0)
    +    qh GOODpointp= qh_point(-qh GOODpoint-1);
    +  if (qh GOODvertex > 0)
    +    qh GOODvertexp= qh_point(qh GOODvertex-1);
    +  else if (qh GOODvertex < 0)
    +    qh GOODvertexp= qh_point(-qh GOODvertex-1);
    +  if ((qh GOODpoint
    +       && (qh GOODpointp < qh first_point  /* also catches !GOODpointp */
    +           || qh GOODpointp > qh_point(qh num_points-1)))
    +    || (qh GOODvertex
    +        && (qh GOODvertexp < qh first_point  /* also catches !GOODvertexp */
    +            || qh GOODvertexp > qh_point(qh num_points-1)))) {
    +    qh_fprintf(qh ferr, 6150, "qhull input error: either QGn or QVn point is > p%d\n",
    +             qh num_points-1);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  maxpoints= qh_maxmin(qh first_point, qh num_points, qh hull_dim);
    +  if (qh SCALElast)
    +    qh_scalelast(qh first_point, qh num_points, qh hull_dim,
    +               qh MINlastcoord, qh MAXlastcoord, qh MAXwidth);
    +  qh_detroundoff();
    +  if (qh DELAUNAY && qh upper_threshold[qh hull_dim-1] > REALmax/2
    +                  && qh lower_threshold[qh hull_dim-1] < -REALmax/2) {
    +    for (i=qh_PRINTEND; i--; ) {
    +      if (qh PRINTout[i] == qh_PRINTgeom && qh DROPdim < 0
    +          && !qh GOODthreshold && !qh SPLITthresholds)
    +        break;  /* in this case, don't set upper_threshold */
    +    }
    +    if (i < 0) {
    +      if (qh UPPERdelaunay) { /* matches qh.upperdelaunay in qh_setfacetplane */
    +        qh lower_threshold[qh hull_dim-1]= qh ANGLEround * qh_ZEROdelaunay;
    +        qh GOODthreshold= True;
    +      }else {
    +        qh upper_threshold[qh hull_dim-1]= -qh ANGLEround * qh_ZEROdelaunay;
    +        if (!qh GOODthreshold)
    +          qh SPLITthresholds= True; /* build upper-convex hull even if Qg */
    +          /* qh_initqhull_globals errors if Qg without Pdk/etc. */
    +      }
    +    }
    +  }
    +  vertices= qh_initialvertices(qh hull_dim, maxpoints, qh first_point, qh num_points);
    +  qh_initialhull(vertices);  /* initial qh facet_list */
    +  qh_partitionall(vertices, qh first_point, qh num_points);
    +  if (qh PRINToptions1st || qh TRACElevel || qh IStracing) {
    +    if (qh TRACElevel || qh IStracing)
    +      qh_fprintf(qh ferr, 8103, "\nTrace level %d for %s | %s\n",
    +         qh IStracing ? qh IStracing : qh TRACElevel, qh rbox_command, qh qhull_command);
    +    qh_fprintf(qh ferr, 8104, "Options selected for Qhull %s:\n%s\n", qh_version, qh qhull_options);
    +  }
    +  qh_resetlists(False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +  qh facet_next= qh facet_list;
    +  qh_furthestnext(/* qh.facet_list */);
    +  if (qh PREmerge) {
    +    qh cos_max= qh premerge_cos;
    +    qh centrum_radius= qh premerge_centrum;
    +  }
    +  if (qh ONLYgood) {
    +    if (qh GOODvertex > 0 && qh MERGING) {
    +      qh_fprintf(qh ferr, 6151, "qhull input error: 'Qg QVn' (only good vertex) does not work with merging.\nUse 'QJ' to joggle the input or 'Q0' to turn off merging.\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (!(qh GOODthreshold || qh GOODpoint
    +         || (!qh MERGEexact && !qh PREmerge && qh GOODvertexp))) {
    +      qh_fprintf(qh ferr, 6152, "qhull input error: 'Qg' (ONLYgood) needs a good threshold('Pd0D0'), a\n\
    +good point(QGn or QG-n), or a good vertex with 'QJ' or 'Q0' (QVn).\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh GOODvertex > 0  && !qh MERGING  /* matches qh_partitionall */
    +        && !qh_isvertex(qh GOODvertexp, vertices)) {
    +      facet= qh_findbestnew(qh GOODvertexp, qh facet_list,
    +                          &dist, !qh_ALL, &isoutside, &numpart);
    +      zadd_(Zdistgood, numpart);
    +      if (!isoutside) {
    +        qh_fprintf(qh ferr, 6153, "qhull input error: point for QV%d is inside initial simplex.  It can not be made a vertex.\n",
    +               qh_pointid(qh GOODvertexp));
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +      if (!qh_addpoint(qh GOODvertexp, facet, False)) {
    +        qh_settempfree(&vertices);
    +        qh_settempfree(&maxpoints);
    +        return;
    +      }
    +    }
    +    qh_findgood(qh facet_list, 0);
    +  }
    +  qh_settempfree(&vertices);
    +  qh_settempfree(&maxpoints);
    +  trace1((qh ferr, 1030, "qh_initbuild: initial hull created and points partitioned\n"));
    +} /* initbuild */
    +
    +/*---------------------------------
    +
    +  qh_initialhull( vertices )
    +    constructs the initial hull as a DIM3 simplex of vertices
    +
    +  design:
    +    creates a simplex (initializes lists)
    +    determines orientation of simplex
    +    sets hyperplanes for facets
    +    doubles checks orientation (in case of axis-parallel facets with Gaussian elimination)
    +    checks for flipped facets and qh.NARROWhull
    +    checks the result
    +*/
    +void qh_initialhull(setT *vertices) {
    +  facetT *facet, *firstfacet, *neighbor, **neighborp;
    +  realT dist, angle, minangle= REALmax;
    +#ifndef qh_NOtrace
    +  int k;
    +#endif
    +
    +  qh_createsimplex(vertices);  /* qh.facet_list */
    +  qh_resetlists(False, qh_RESETvisible);
    +  qh facet_next= qh facet_list;      /* advance facet when processed */
    +  qh interior_point= qh_getcenter(vertices);
    +  firstfacet= qh facet_list;
    +  qh_setfacetplane(firstfacet);
    +  zinc_(Znumvisibility); /* needs to be in printsummary */
    +  qh_distplane(qh interior_point, firstfacet, &dist);
    +  if (dist > 0) {
    +    FORALLfacets
    +      facet->toporient ^= (unsigned char)True;
    +  }
    +  FORALLfacets
    +    qh_setfacetplane(facet);
    +  FORALLfacets {
    +    if (!qh_checkflipped(facet, NULL, qh_ALL)) {/* due to axis-parallel facet */
    +      trace1((qh ferr, 1031, "qh_initialhull: initial orientation incorrect.  Correct all facets\n"));
    +      facet->flipped= False;
    +      FORALLfacets {
    +        facet->toporient ^= (unsigned char)True;
    +        qh_orientoutside(facet);
    +      }
    +      break;
    +    }
    +  }
    +  FORALLfacets {
    +    if (!qh_checkflipped(facet, NULL, !qh_ALL)) {  /* can happen with 'R0.1' */
    +      if (qh DELAUNAY && ! qh ATinfinity) {
    +        if (qh UPPERdelaunay)
    +          qh_fprintf(qh ferr, 6240, "Qhull precision error: Initial simplex is cocircular or cospherical.  Option 'Qs' searches all points.  Can not compute the upper Delaunay triangulation or upper Voronoi diagram of cocircular/cospherical points.\n");
    +        else
    +          qh_fprintf(qh ferr, 6239, "Qhull precision error: Initial simplex is cocircular or cospherical.  Use option 'Qz' for the Delaunay triangulation or Voronoi diagram of cocircular/cospherical points.  Option 'Qz' adds a point \"at infinity\".    Use option 'Qs' to search all points for the initial simplex.\n");
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +      qh_precision("initial simplex is flat");
    +      qh_fprintf(qh ferr, 6154, "Qhull precision error: Initial simplex is flat (facet %d is coplanar with the interior point)\n",
    +                 facet->id);
    +      qh_errexit(qh_ERRsingular, NULL, NULL);  /* calls qh_printhelp_singular */
    +    }
    +    FOREACHneighbor_(facet) {
    +      angle= qh_getangle(facet->normal, neighbor->normal);
    +      minimize_( minangle, angle);
    +    }
    +  }
    +  if (minangle < qh_MAXnarrow && !qh NOnarrow) {
    +    realT diff= 1.0 + minangle;
    +
    +    qh NARROWhull= True;
    +    qh_option("_narrow-hull", NULL, &diff);
    +    if (minangle < qh_WARNnarrow && !qh RERUN && qh PRINTprecision)
    +      qh_printhelp_narrowhull(qh ferr, minangle);
    +  }
    +  zzval_(Zprocessed)= qh hull_dim+1;
    +  qh_checkpolygon(qh facet_list);
    +  qh_checkconvex(qh facet_list,   qh_DATAfault);
    +#ifndef qh_NOtrace
    +  if (qh IStracing >= 1) {
    +    qh_fprintf(qh ferr, 8105, "qh_initialhull: simplex constructed, interior point:");
    +    for (k=0; k < qh hull_dim; k++)
    +      qh_fprintf(qh ferr, 8106, " %6.4g", qh interior_point[k]);
    +    qh_fprintf(qh ferr, 8107, "\n");
    +  }
    +#endif
    +} /* initialhull */
    +
    +/*---------------------------------
    +
    +  qh_initialvertices( dim, maxpoints, points, numpoints )
    +    determines a non-singular set of initial vertices
    +    maxpoints may include duplicate points
    +
    +  returns:
    +    temporary set of dim+1 vertices in descending order by vertex id
    +    if qh.RANDOMoutside && !qh.ALLpoints
    +      picks random points
    +    if dim >= qh_INITIALmax,
    +      uses min/max x and max points with non-zero determinants
    +
    +  notes:
    +    unless qh.ALLpoints,
    +      uses maxpoints as long as determinate is non-zero
    +*/
    +setT *qh_initialvertices(int dim, setT *maxpoints, pointT *points, int numpoints) {
    +  pointT *point, **pointp;
    +  setT *vertices, *simplex, *tested;
    +  realT randr;
    +  int idx, point_i, point_n, k;
    +  boolT nearzero= False;
    +
    +  vertices= qh_settemp(dim + 1);
    +  simplex= qh_settemp(dim+1);
    +  if (qh ALLpoints)
    +    qh_maxsimplex(dim, NULL, points, numpoints, &simplex);
    +  else if (qh RANDOMoutside) {
    +    while (qh_setsize(simplex) != dim+1) {
    +      randr= qh_RANDOMint;
    +      randr= randr/(qh_RANDOMmax+1);
    +      idx= (int)floor(qh num_points * randr);
    +      while (qh_setin(simplex, qh_point(idx))) {
    +        idx++; /* in case qh_RANDOMint always returns the same value */
    +        idx= idx < qh num_points ? idx : 0;
    +      }
    +      qh_setappend(&simplex, qh_point(idx));
    +    }
    +  }else if (qh hull_dim >= qh_INITIALmax) {
    +    tested= qh_settemp(dim+1);
    +    qh_setappend(&simplex, SETfirst_(maxpoints));   /* max and min X coord */
    +    qh_setappend(&simplex, SETsecond_(maxpoints));
    +    qh_maxsimplex(fmin_(qh_INITIALsearch, dim), maxpoints, points, numpoints, &simplex);
    +    k= qh_setsize(simplex);
    +    FOREACHpoint_i_(maxpoints) {
    +      if (point_i & 0x1) {     /* first pick up max. coord. points */
    +        if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +          qh_detsimplex(point, simplex, k, &nearzero);
    +          if (nearzero)
    +            qh_setappend(&tested, point);
    +          else {
    +            qh_setappend(&simplex, point);
    +            if (++k == dim)  /* use search for last point */
    +              break;
    +          }
    +        }
    +      }
    +    }
    +    while (k != dim && (point= (pointT*)qh_setdellast(maxpoints))) {
    +      if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +        qh_detsimplex(point, simplex, k, &nearzero);
    +        if (nearzero)
    +          qh_setappend(&tested, point);
    +        else {
    +          qh_setappend(&simplex, point);
    +          k++;
    +        }
    +      }
    +    }
    +    idx= 0;
    +    while (k != dim && (point= qh_point(idx++))) {
    +      if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +        qh_detsimplex(point, simplex, k, &nearzero);
    +        if (!nearzero){
    +          qh_setappend(&simplex, point);
    +          k++;
    +        }
    +      }
    +    }
    +    qh_settempfree(&tested);
    +    qh_maxsimplex(dim, maxpoints, points, numpoints, &simplex);
    +  }else
    +    qh_maxsimplex(dim, maxpoints, points, numpoints, &simplex);
    +  FOREACHpoint_(simplex)
    +    qh_setaddnth(&vertices, 0, qh_newvertex(point)); /* descending order */
    +  qh_settempfree(&simplex);
    +  return vertices;
    +} /* initialvertices */
    +
    +
    +/*---------------------------------
    +
    +  qh_isvertex( point, vertices )
    +    returns vertex if point is in vertex set, else returns NULL
    +
    +  notes:
    +    for qh.GOODvertex
    +*/
    +vertexT *qh_isvertex(pointT *point, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (vertex->point == point)
    +      return vertex;
    +  }
    +  return NULL;
    +} /* isvertex */
    +
    +/*---------------------------------
    +
    +  qh_makenewfacets( point )
    +    make new facets from point and qh.visible_list
    +
    +  returns:
    +    qh.newfacet_list= list of new facets with hyperplanes and ->newfacet
    +    qh.newvertex_list= list of vertices in new facets with ->newlist set
    +
    +    if (qh.ONLYgood)
    +      newfacets reference horizon facets, but not vice versa
    +      ridges reference non-simplicial horizon ridges, but not vice versa
    +      does not change existing facets
    +    else
    +      sets qh.NEWfacets
    +      new facets attached to horizon facets and ridges
    +      for visible facets,
    +        visible->r.replace is corresponding new facet
    +
    +  see also:
    +    qh_makenewplanes() -- make hyperplanes for facets
    +    qh_attachnewfacets() -- attachnewfacets if not done here(qh ONLYgood)
    +    qh_matchnewfacets() -- match up neighbors
    +    qh_updatevertices() -- update vertex neighbors and delvertices
    +    qh_deletevisible() -- delete visible facets
    +    qh_checkpolygon() --check the result
    +    qh_triangulate() -- triangulate a non-simplicial facet
    +
    +  design:
    +    for each visible facet
    +      make new facets to its horizon facets
    +      update its f.replace
    +      clear its neighbor set
    +*/
    +vertexT *qh_makenewfacets(pointT *point /*visible_list*/) {
    +  facetT *visible, *newfacet= NULL, *newfacet2= NULL, *neighbor, **neighborp;
    +  vertexT *apex;
    +  int numnew=0;
    +
    +  qh newfacet_list= qh facet_tail;
    +  qh newvertex_list= qh vertex_tail;
    +  apex= qh_newvertex(point);
    +  qh_appendvertex(apex);
    +  qh visit_id++;
    +  if (!qh ONLYgood)
    +    qh NEWfacets= True;
    +  FORALLvisible_facets {
    +    FOREACHneighbor_(visible)
    +      neighbor->seen= False;
    +    if (visible->ridges) {
    +      visible->visitid= qh visit_id;
    +      newfacet2= qh_makenew_nonsimplicial(visible, apex, &numnew);
    +    }
    +    if (visible->simplicial)
    +      newfacet= qh_makenew_simplicial(visible, apex, &numnew);
    +    if (!qh ONLYgood) {
    +      if (newfacet2)  /* newfacet is null if all ridges defined */
    +        newfacet= newfacet2;
    +      if (newfacet)
    +        visible->f.replace= newfacet;
    +      else
    +        zinc_(Zinsidevisible);
    +      SETfirst_(visible->neighbors)= NULL;
    +    }
    +  }
    +  trace1((qh ferr, 1032, "qh_makenewfacets: created %d new facets from point p%d to horizon\n",
    +          numnew, qh_pointid(point)));
    +  if (qh IStracing >= 4)
    +    qh_printfacetlist(qh newfacet_list, NULL, qh_ALL);
    +  return apex;
    +} /* makenewfacets */
    +
    +/*---------------------------------
    +
    +  qh_matchduplicates( atfacet, atskip, hashsize, hashcount )
    +    match duplicate ridges in qh.hash_table for atfacet/atskip
    +    duplicates marked with ->dupridge and qh_DUPLICATEridge
    +
    +  returns:
    +    picks match with worst merge (min distance apart)
    +    updates hashcount
    +
    +  see also:
    +    qh_matchneighbor
    +
    +  notes:
    +
    +  design:
    +    compute hash value for atfacet and atskip
    +    repeat twice -- once to make best matches, once to match the rest
    +      for each possible facet in qh.hash_table
    +        if it is a matching facet and pass 2
    +          make match
    +          unless tricoplanar, mark match for merging (qh_MERGEridge)
    +          [e.g., tricoplanar RBOX s 1000 t993602376 | QHULL C-1e-3 d Qbb FA Qt]
    +        if it is a matching facet and pass 1
    +          test if this is a better match
    +      if pass 1,
    +        make best match (it will not be merged)
    +*/
    +#ifndef qh_NOmerge
    +void qh_matchduplicates(facetT *atfacet, int atskip, int hashsize, int *hashcount) {
    +  boolT same, ismatch;
    +  int hash, scan;
    +  facetT *facet, *newfacet, *maxmatch= NULL, *maxmatch2= NULL, *nextfacet;
    +  int skip, newskip, nextskip= 0, maxskip= 0, maxskip2= 0, makematch;
    +  realT maxdist= -REALmax, mindist, dist2, low, high;
    +
    +  hash= qh_gethash(hashsize, atfacet->vertices, qh hull_dim, 1,
    +                     SETelem_(atfacet->vertices, atskip));
    +  trace2((qh ferr, 2046, "qh_matchduplicates: find duplicate matches for f%d skip %d hash %d hashcount %d\n",
    +          atfacet->id, atskip, hash, *hashcount));
    +  for (makematch= 0; makematch < 2; makematch++) {
    +    qh visit_id++;
    +    for (newfacet= atfacet, newskip= atskip; newfacet; newfacet= nextfacet, newskip= nextskip) {
    +      zinc_(Zhashlookup);
    +      nextfacet= NULL;
    +      newfacet->visitid= qh visit_id;
    +      for (scan= hash; (facet= SETelemt_(qh hash_table, scan, facetT));
    +           scan= (++scan >= hashsize ? 0 : scan)) {
    +        if (!facet->dupridge || facet->visitid == qh visit_id)
    +          continue;
    +        zinc_(Zhashtests);
    +        if (qh_matchvertices(1, newfacet->vertices, newskip, facet->vertices, &skip, &same)) {
    +          ismatch= (same == (boolT)(newfacet->toporient ^ facet->toporient));
    +          if (SETelemt_(facet->neighbors, skip, facetT) != qh_DUPLICATEridge) {
    +            if (!makematch) {
    +              qh_fprintf(qh ferr, 6155, "qhull internal error (qh_matchduplicates): missing dupridge at f%d skip %d for new f%d skip %d hash %d\n",
    +                     facet->id, skip, newfacet->id, newskip, hash);
    +              qh_errexit2(qh_ERRqhull, facet, newfacet);
    +            }
    +          }else if (ismatch && makematch) {
    +            if (SETelemt_(newfacet->neighbors, newskip, facetT) == qh_DUPLICATEridge) {
    +              SETelem_(facet->neighbors, skip)= newfacet;
    +              if (newfacet->tricoplanar)
    +                SETelem_(newfacet->neighbors, newskip)= facet;
    +              else
    +                SETelem_(newfacet->neighbors, newskip)= qh_MERGEridge;
    +              *hashcount -= 2; /* removed two unmatched facets */
    +              trace4((qh ferr, 4059, "qh_matchduplicates: duplicate f%d skip %d matched with new f%d skip %d merge\n",
    +                    facet->id, skip, newfacet->id, newskip));
    +            }
    +          }else if (ismatch) {
    +            mindist= qh_getdistance(facet, newfacet, &low, &high);
    +            dist2= qh_getdistance(newfacet, facet, &low, &high);
    +            minimize_(mindist, dist2);
    +            if (mindist > maxdist) {
    +              maxdist= mindist;
    +              maxmatch= facet;
    +              maxskip= skip;
    +              maxmatch2= newfacet;
    +              maxskip2= newskip;
    +            }
    +            trace3((qh ferr, 3018, "qh_matchduplicates: duplicate f%d skip %d new f%d skip %d at dist %2.2g, max is now f%d f%d\n",
    +                    facet->id, skip, newfacet->id, newskip, mindist,
    +                    maxmatch->id, maxmatch2->id));
    +          }else { /* !ismatch */
    +            nextfacet= facet;
    +            nextskip= skip;
    +          }
    +        }
    +        if (makematch && !facet
    +        && SETelemt_(facet->neighbors, skip, facetT) == qh_DUPLICATEridge) {
    +          qh_fprintf(qh ferr, 6156, "qhull internal error (qh_matchduplicates): no MERGEridge match for duplicate f%d skip %d at hash %d\n",
    +                     newfacet->id, newskip, hash);
    +          qh_errexit(qh_ERRqhull, newfacet, NULL);
    +        }
    +      }
    +    } /* end of for each new facet at hash */
    +    if (!makematch) {
    +      if (!maxmatch) {
    +        qh_fprintf(qh ferr, 6157, "qhull internal error (qh_matchduplicates): no maximum match at duplicate f%d skip %d at hash %d\n",
    +                     atfacet->id, atskip, hash);
    +        qh_errexit(qh_ERRqhull, atfacet, NULL);
    +      }
    +      SETelem_(maxmatch->neighbors, maxskip)= maxmatch2; /* maxmatch!=0 by QH6157 */
    +      SETelem_(maxmatch2->neighbors, maxskip2)= maxmatch;
    +      *hashcount -= 2; /* removed two unmatched facets */
    +      zzinc_(Zmultiridge);
    +      trace0((qh ferr, 25, "qh_matchduplicates: duplicate f%d skip %d matched with new f%d skip %d keep\n",
    +              maxmatch->id, maxskip, maxmatch2->id, maxskip2));
    +      qh_precision("ridge with multiple neighbors");
    +      if (qh IStracing >= 4)
    +        qh_errprint("DUPLICATED/MATCH", maxmatch, maxmatch2, NULL, NULL);
    +    }
    +  }
    +} /* matchduplicates */
    +
    +/*---------------------------------
    +
    +  qh_nearcoplanar()
    +    for all facets, remove near-inside points from facet->coplanarset
    +    coplanar points defined by innerplane from qh_outerinner()
    +
    +  returns:
    +    if qh KEEPcoplanar && !qh KEEPinside
    +      facet->coplanarset only contains coplanar points
    +    if qh.JOGGLEmax
    +      drops inner plane by another qh.JOGGLEmax diagonal since a
    +        vertex could shift out while a coplanar point shifts in
    +
    +  notes:
    +    used for qh.PREmerge and qh.JOGGLEmax
    +    must agree with computation of qh.NEARcoplanar in qh_detroundoff()
    +  design:
    +    if not keeping coplanar or inside points
    +      free all coplanar sets
    +    else if not keeping both coplanar and inside points
    +      remove !coplanar or !inside points from coplanar sets
    +*/
    +void qh_nearcoplanar(void /* qh.facet_list */) {
    +  facetT *facet;
    +  pointT *point, **pointp;
    +  int numpart;
    +  realT dist, innerplane;
    +
    +  if (!qh KEEPcoplanar && !qh KEEPinside) {
    +    FORALLfacets {
    +      if (facet->coplanarset)
    +        qh_setfree( &facet->coplanarset);
    +    }
    +  }else if (!qh KEEPcoplanar || !qh KEEPinside) {
    +    qh_outerinner(NULL, NULL, &innerplane);
    +    if (qh JOGGLEmax < REALmax/2)
    +      innerplane -= qh JOGGLEmax * sqrt((realT)qh hull_dim);
    +    numpart= 0;
    +    FORALLfacets {
    +      if (facet->coplanarset) {
    +        FOREACHpoint_(facet->coplanarset) {
    +          numpart++;
    +          qh_distplane(point, facet, &dist);
    +          if (dist < innerplane) {
    +            if (!qh KEEPinside)
    +              SETref_(point)= NULL;
    +          }else if (!qh KEEPcoplanar)
    +            SETref_(point)= NULL;
    +        }
    +        qh_setcompact(facet->coplanarset);
    +      }
    +    }
    +    zzadd_(Zcheckpart, numpart);
    +  }
    +} /* nearcoplanar */
    +
    +/*---------------------------------
    +
    +  qh_nearvertex( facet, point, bestdist )
    +    return nearest vertex in facet to point
    +
    +  returns:
    +    vertex and its distance
    +
    +  notes:
    +    if qh.DELAUNAY
    +      distance is measured in the input set
    +    searches neighboring tricoplanar facets (requires vertexneighbors)
    +      Slow implementation.  Recomputes vertex set for each point.
    +    The vertex set could be stored in the qh.keepcentrum facet.
    +*/
    +vertexT *qh_nearvertex(facetT *facet, pointT *point, realT *bestdistp) {
    +  realT bestdist= REALmax, dist;
    +  vertexT *bestvertex= NULL, *vertex, **vertexp, *apex;
    +  coordT *center;
    +  facetT *neighbor, **neighborp;
    +  setT *vertices;
    +  int dim= qh hull_dim;
    +
    +  if (qh DELAUNAY)
    +    dim--;
    +  if (facet->tricoplanar) {
    +    if (!qh VERTEXneighbors || !facet->center) {
    +      qh_fprintf(qh ferr, 6158, "qhull internal error (qh_nearvertex): qh.VERTEXneighbors and facet->center required for tricoplanar facets\n");
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +    vertices= qh_settemp(qh TEMPsize);
    +    apex= SETfirstt_(facet->vertices, vertexT);
    +    center= facet->center;
    +    FOREACHneighbor_(apex) {
    +      if (neighbor->center == center) {
    +        FOREACHvertex_(neighbor->vertices)
    +          qh_setappend(&vertices, vertex);
    +      }
    +    }
    +  }else
    +    vertices= facet->vertices;
    +  FOREACHvertex_(vertices) {
    +    dist= qh_pointdist(vertex->point, point, -dim);
    +    if (dist < bestdist) {
    +      bestdist= dist;
    +      bestvertex= vertex;
    +    }
    +  }
    +  if (facet->tricoplanar)
    +    qh_settempfree(&vertices);
    +  *bestdistp= sqrt(bestdist);
    +  if (!bestvertex) {
    +      qh_fprintf(qh ferr, 6261, "qhull internal error (qh_nearvertex): did not find bestvertex for f%d p%d\n", facet->id, qh_pointid(point));
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  trace3((qh ferr, 3019, "qh_nearvertex: v%d dist %2.2g for f%d p%d\n",
    +        bestvertex->id, *bestdistp, facet->id, qh_pointid(point))); /* bestvertex!=0 by QH2161 */
    +  return bestvertex;
    +} /* nearvertex */
    +
    +/*---------------------------------
    +
    +  qh_newhashtable( newsize )
    +    returns size of qh.hash_table of at least newsize slots
    +
    +  notes:
    +    assumes qh.hash_table is NULL
    +    qh_HASHfactor determines the number of extra slots
    +    size is not divisible by 2, 3, or 5
    +*/
    +int qh_newhashtable(int newsize) {
    +  int size;
    +
    +  size= ((newsize+1)*qh_HASHfactor) | 0x1;  /* odd number */
    +  while (True) {
    +    if (newsize<0 || size<0) {
    +        qh_fprintf(qhmem.ferr, 6236, "qhull error (qh_newhashtable): negative request (%d) or size (%d).  Did int overflow due to high-D?\n", newsize, size); /* WARN64 */
    +        qh_errexit(qhmem_ERRmem, NULL, NULL);
    +    }
    +    if ((size%3) && (size%5))
    +      break;
    +    size += 2;
    +    /* loop terminates because there is an infinite number of primes */
    +  }
    +  qh hash_table= qh_setnew(size);
    +  qh_setzero(qh hash_table, 0, size);
    +  return size;
    +} /* newhashtable */
    +
    +/*---------------------------------
    +
    +  qh_newvertex( point )
    +    returns a new vertex for point
    +*/
    +vertexT *qh_newvertex(pointT *point) {
    +  vertexT *vertex;
    +
    +  zinc_(Ztotvertices);
    +  vertex= (vertexT *)qh_memalloc((int)sizeof(vertexT));
    +  memset((char *) vertex, (size_t)0, sizeof(vertexT));
    +  if (qh vertex_id == UINT_MAX) {
    +    qh_memfree(vertex, (int)sizeof(vertexT));
    +    qh_fprintf(qh ferr, 6159, "qhull error: more than 2^32 vertices.  vertexT.id field overflows.  Vertices would not be sorted correctly.\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (qh vertex_id == qh tracevertex_id)
    +    qh tracevertex= vertex;
    +  vertex->id= qh vertex_id++;
    +  vertex->point= point;
    +  trace4((qh ferr, 4060, "qh_newvertex: vertex p%d(v%d) created\n", qh_pointid(vertex->point),
    +          vertex->id));
    +  return(vertex);
    +} /* newvertex */
    +
    +/*---------------------------------
    +
    +  qh_nextridge3d( atridge, facet, vertex )
    +    return next ridge and vertex for a 3d facet
    +    returns NULL on error
    +    [for QhullFacet::nextRidge3d] Does not call qh_errexit nor access qh_qh.
    +
    +  notes:
    +    in qh_ORIENTclock order
    +    this is a O(n^2) implementation to trace all ridges
    +    be sure to stop on any 2nd visit
    +    same as QhullRidge::nextRidge3d
    +    does not use qh_qh or qh_errexit [QhullFacet.cpp]
    +
    +  design:
    +    for each ridge
    +      exit if it is the ridge after atridge
    +*/
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp) {
    +  vertexT *atvertex, *vertex, *othervertex;
    +  ridgeT *ridge, **ridgep;
    +
    +  if ((atridge->top == facet) ^ qh_ORIENTclock)
    +    atvertex= SETsecondt_(atridge->vertices, vertexT);
    +  else
    +    atvertex= SETfirstt_(atridge->vertices, vertexT);
    +  FOREACHridge_(facet->ridges) {
    +    if (ridge == atridge)
    +      continue;
    +    if ((ridge->top == facet) ^ qh_ORIENTclock) {
    +      othervertex= SETsecondt_(ridge->vertices, vertexT);
    +      vertex= SETfirstt_(ridge->vertices, vertexT);
    +    }else {
    +      vertex= SETsecondt_(ridge->vertices, vertexT);
    +      othervertex= SETfirstt_(ridge->vertices, vertexT);
    +    }
    +    if (vertex == atvertex) {
    +      if (vertexp)
    +        *vertexp= othervertex;
    +      return ridge;
    +    }
    +  }
    +  return NULL;
    +} /* nextridge3d */
    +#else /* qh_NOmerge */
    +void qh_matchduplicates(facetT *atfacet, int atskip, int hashsize, int *hashcount) {
    +}
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp) {
    +
    +  return NULL;
    +}
    +#endif /* qh_NOmerge */
    +
    +/*---------------------------------
    +
    +  qh_outcoplanar()
    +    move points from all facets' outsidesets to their coplanarsets
    +
    +  notes:
    +    for post-processing under qh.NARROWhull
    +
    +  design:
    +    for each facet
    +      for each outside point for facet
    +        partition point into coplanar set
    +*/
    +void qh_outcoplanar(void /* facet_list */) {
    +  pointT *point, **pointp;
    +  facetT *facet;
    +  realT dist;
    +
    +  trace1((qh ferr, 1033, "qh_outcoplanar: move outsideset to coplanarset for qh NARROWhull\n"));
    +  FORALLfacets {
    +    FOREACHpoint_(facet->outsideset) {
    +      qh num_outside--;
    +      if (qh KEEPcoplanar || qh KEEPnearinside) {
    +        qh_distplane(point, facet, &dist);
    +        zinc_(Zpartition);
    +        qh_partitioncoplanar(point, facet, &dist);
    +      }
    +    }
    +    qh_setfree(&facet->outsideset);
    +  }
    +} /* outcoplanar */
    +
    +/*---------------------------------
    +
    +  qh_point( id )
    +    return point for a point id, or NULL if unknown
    +
    +  alternative code:
    +    return((pointT *)((unsigned   long)qh.first_point
    +           + (unsigned long)((id)*qh.normal_size)));
    +*/
    +pointT *qh_point(int id) {
    +
    +  if (id < 0)
    +    return NULL;
    +  if (id < qh num_points)
    +    return qh first_point + id * qh hull_dim;
    +  id -= qh num_points;
    +  if (id < qh_setsize(qh other_points))
    +    return SETelemt_(qh other_points, id, pointT);
    +  return NULL;
    +} /* point */
    +
    +/*---------------------------------
    +
    +  qh_point_add( set, point, elem )
    +    stores elem at set[point.id]
    +
    +  returns:
    +    access function for qh_pointfacet and qh_pointvertex
    +
    +  notes:
    +    checks point.id
    +*/
    +void qh_point_add(setT *set, pointT *point, void *elem) {
    +  int id, size;
    +
    +  SETreturnsize_(set, size);
    +  if ((id= qh_pointid(point)) < 0)
    +    qh_fprintf(qh ferr, 7067, "qhull internal warning (point_add): unknown point %p id %d\n",
    +      point, id);
    +  else if (id >= size) {
    +    qh_fprintf(qh ferr, 6160, "qhull internal errror(point_add): point p%d is out of bounds(%d)\n",
    +             id, size);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }else
    +    SETelem_(set, id)= elem;
    +} /* point_add */
    +
    +
    +/*---------------------------------
    +
    +  qh_pointfacet()
    +    return temporary set of facet for each point
    +    the set is indexed by point id
    +
    +  notes:
    +    vertices assigned to one of the facets
    +    coplanarset assigned to the facet
    +    outside set assigned to the facet
    +    NULL if no facet for point (inside)
    +      includes qh.GOODpointp
    +
    +  access:
    +    FOREACHfacet_i_(facets) { ... }
    +    SETelem_(facets, i)
    +
    +  design:
    +    for each facet
    +      add each vertex
    +      add each coplanar point
    +      add each outside point
    +*/
    +setT *qh_pointfacet(void /*qh.facet_list*/) {
    +  int numpoints= qh num_points + qh_setsize(qh other_points);
    +  setT *facets;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  pointT *point, **pointp;
    +
    +  facets= qh_settemp(numpoints);
    +  qh_setzero(facets, 0, numpoints);
    +  qh vertex_visit++;
    +  FORALLfacets {
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh vertex_visit) {
    +        vertex->visitid= qh vertex_visit;
    +        qh_point_add(facets, vertex->point, facet);
    +      }
    +    }
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(facets, point, facet);
    +    FOREACHpoint_(facet->outsideset)
    +      qh_point_add(facets, point, facet);
    +  }
    +  return facets;
    +} /* pointfacet */
    +
    +/*---------------------------------
    +
    +  qh_pointvertex(  )
    +    return temporary set of vertices indexed by point id
    +    entry is NULL if no vertex for a point
    +      this will include qh.GOODpointp
    +
    +  access:
    +    FOREACHvertex_i_(vertices) { ... }
    +    SETelem_(vertices, i)
    +*/
    +setT *qh_pointvertex(void /*qh.facet_list*/) {
    +  int numpoints= qh num_points + qh_setsize(qh other_points);
    +  setT *vertices;
    +  vertexT *vertex;
    +
    +  vertices= qh_settemp(numpoints);
    +  qh_setzero(vertices, 0, numpoints);
    +  FORALLvertices
    +    qh_point_add(vertices, vertex->point, vertex);
    +  return vertices;
    +} /* pointvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_prependfacet( facet, facetlist )
    +    prepend facet to the start of a facetlist
    +
    +  returns:
    +    increments qh.numfacets
    +    updates facetlist, qh.facet_list, facet_next
    +
    +  notes:
    +    be careful of prepending since it can lose a pointer.
    +      e.g., can lose _next by deleting and then prepending before _next
    +*/
    +void qh_prependfacet(facetT *facet, facetT **facetlist) {
    +  facetT *prevfacet, *list;
    +
    +
    +  trace4((qh ferr, 4061, "qh_prependfacet: prepend f%d before f%d\n",
    +          facet->id, getid_(*facetlist)));
    +  if (!*facetlist)
    +    (*facetlist)= qh facet_tail;
    +  list= *facetlist;
    +  prevfacet= list->previous;
    +  facet->previous= prevfacet;
    +  if (prevfacet)
    +    prevfacet->next= facet;
    +  list->previous= facet;
    +  facet->next= *facetlist;
    +  if (qh facet_list == list)  /* this may change *facetlist */
    +    qh facet_list= facet;
    +  if (qh facet_next == list)
    +    qh facet_next= facet;
    +  *facetlist= facet;
    +  qh num_facets++;
    +} /* prependfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhashtable( fp )
    +    print hash table to fp
    +
    +  notes:
    +    not in I/O to avoid bringing io.c in
    +
    +  design:
    +    for each hash entry
    +      if defined
    +        if unmatched or will merge (NULL, qh_MERGEridge, qh_DUPLICATEridge)
    +          print entry and neighbors
    +*/
    +void qh_printhashtable(FILE *fp) {
    +  facetT *facet, *neighbor;
    +  int id, facet_i, facet_n, neighbor_i= 0, neighbor_n= 0;
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHfacet_i_(qh hash_table) {
    +    if (facet) {
    +      FOREACHneighbor_i_(facet) {
    +        if (!neighbor || neighbor == qh_MERGEridge || neighbor == qh_DUPLICATEridge)
    +          break;
    +      }
    +      if (neighbor_i == neighbor_n)
    +        continue;
    +      qh_fprintf(fp, 9283, "hash %d f%d ", facet_i, facet->id);
    +      FOREACHvertex_(facet->vertices)
    +        qh_fprintf(fp, 9284, "v%d ", vertex->id);
    +      qh_fprintf(fp, 9285, "\n neighbors:");
    +      FOREACHneighbor_i_(facet) {
    +        if (neighbor == qh_MERGEridge)
    +          id= -3;
    +        else if (neighbor == qh_DUPLICATEridge)
    +          id= -2;
    +        else
    +          id= getid_(neighbor);
    +        qh_fprintf(fp, 9286, " %d", id);
    +      }
    +      qh_fprintf(fp, 9287, "\n");
    +    }
    +  }
    +} /* printhashtable */
    +
    +
    +/*---------------------------------
    +
    +  qh_printlists( fp )
    +    print out facet and vertex list for debugging (without 'f/v' tags)
    +*/
    +void qh_printlists(void) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  int count= 0;
    +
    +  qh_fprintf(qh ferr, 8108, "qh_printlists: facets:");
    +  FORALLfacets {
    +    if (++count % 100 == 0)
    +      qh_fprintf(qh ferr, 8109, "\n     ");
    +    qh_fprintf(qh ferr, 8110, " %d", facet->id);
    +  }
    +  qh_fprintf(qh ferr, 8111, "\n  new facets %d visible facets %d next facet for qh_addpoint %d\n  vertices(new %d):",
    +     getid_(qh newfacet_list), getid_(qh visible_list), getid_(qh facet_next),
    +     getid_(qh newvertex_list));
    +  count = 0;
    +  FORALLvertices {
    +    if (++count % 100 == 0)
    +      qh_fprintf(qh ferr, 8112, "\n     ");
    +    qh_fprintf(qh ferr, 8113, " %d", vertex->id);
    +  }
    +  qh_fprintf(qh ferr, 8114, "\n");
    +} /* printlists */
    +
    +/*---------------------------------
    +
    +  qh_resetlists( stats, qh_RESETvisible )
    +    reset newvertex_list, newfacet_list, visible_list
    +    if stats,
    +      maintains statistics
    +
    +  returns:
    +    visible_list is empty if qh_deletevisible was called
    +*/
    +void qh_resetlists(boolT stats, boolT resetVisible /*qh.newvertex_list newfacet_list visible_list*/) {
    +  vertexT *vertex;
    +  facetT *newfacet, *visible;
    +  int totnew=0, totver=0;
    +
    +  if (stats) {
    +    FORALLvertex_(qh newvertex_list)
    +      totver++;
    +    FORALLnew_facets
    +      totnew++;
    +    zadd_(Zvisvertextot, totver);
    +    zmax_(Zvisvertexmax, totver);
    +    zadd_(Znewfacettot, totnew);
    +    zmax_(Znewfacetmax, totnew);
    +  }
    +  FORALLvertex_(qh newvertex_list)
    +    vertex->newlist= False;
    +  qh newvertex_list= NULL;
    +  FORALLnew_facets
    +    newfacet->newfacet= False;
    +  qh newfacet_list= NULL;
    +  if (resetVisible) {
    +    FORALLvisible_facets {
    +      visible->f.replace= NULL;
    +      visible->visible= False;
    +    }
    +    qh num_visible= 0;
    +  }
    +  qh visible_list= NULL; /* may still have visible facets via qh_triangulate */
    +  qh NEWfacets= False;
    +} /* resetlists */
    +
    +/*---------------------------------
    +
    +  qh_setvoronoi_all()
    +    compute Voronoi centers for all facets
    +    includes upperDelaunay facets if qh.UPPERdelaunay ('Qu')
    +
    +  returns:
    +    facet->center is the Voronoi center
    +
    +  notes:
    +    this is unused/untested code
    +      please email bradb@shore.net if this works ok for you
    +
    +  use:
    +    FORALLvertices {...} to locate the vertex for a point.
    +    FOREACHneighbor_(vertex) {...} to visit the Voronoi centers for a Voronoi cell.
    +*/
    +void qh_setvoronoi_all(void) {
    +  facetT *facet;
    +
    +  qh_clearcenters(qh_ASvoronoi);
    +  qh_vertexneighbors();
    +
    +  FORALLfacets {
    +    if (!facet->normal || !facet->upperdelaunay || qh UPPERdelaunay) {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(facet->vertices);
    +    }
    +  }
    +} /* setvoronoi_all */
    +
    +#ifndef qh_NOmerge
    +
    +/*---------------------------------
    +
    +  qh_triangulate()
    +    triangulate non-simplicial facets on qh.facet_list,
    +    if qh VORONOI, sets Voronoi centers of non-simplicial facets
    +    nop if hasTriangulation
    +
    +  returns:
    +    all facets simplicial
    +    each tricoplanar facet has ->f.triowner == owner of ->center,normal,etc.
    +
    +  notes:
    +    call after qh_check_output since may switch to Voronoi centers
    +    Output may overwrite ->f.triowner with ->f.area
    +*/
    +void qh_triangulate(void /*qh.facet_list*/) {
    +  facetT *facet, *nextfacet, *owner;
    +  int onlygood= qh ONLYgood;
    +  facetT *neighbor, *visible= NULL, *facet1, *facet2, *new_facet_list= NULL;
    +  facetT *orig_neighbor= NULL, *otherfacet;
    +  vertexT *new_vertex_list= NULL;
    +  mergeT *merge;
    +  mergeType mergetype;
    +  int neighbor_i, neighbor_n;
    +
    +  if (qh hasTriangulation)
    +      return;
    +  trace1((qh ferr, 1034, "qh_triangulate: triangulate non-simplicial facets\n"));
    +  if (qh hull_dim == 2)
    +    return;
    +  if (qh VORONOI) {  /* otherwise lose Voronoi centers [could rebuild vertex set from tricoplanar] */
    +    qh_clearcenters(qh_ASvoronoi);
    +    qh_vertexneighbors();
    +  }
    +  qh ONLYgood= False; /* for makenew_nonsimplicial */
    +  qh visit_id++;
    +  qh NEWfacets= True;
    +  qh degen_mergeset= qh_settemp(qh TEMPsize);
    +  qh newvertex_list= qh vertex_tail;
    +  for (facet= qh facet_list; facet && facet->next; facet= nextfacet) { /* non-simplicial facets moved to end */
    +    nextfacet= facet->next;
    +    if (facet->visible || facet->simplicial)
    +      continue;
    +    /* triangulate all non-simplicial facets, otherwise merging does not work, e.g., RBOX c P-0.1 P+0.1 P+0.1 D3 | QHULL d Qt Tv */
    +    if (!new_facet_list)
    +      new_facet_list= facet;  /* will be moved to end */
    +    qh_triangulate_facet(facet, &new_vertex_list);
    +  }
    +  trace2((qh ferr, 2047, "qh_triangulate: delete null facets from f%d -- apex same as second vertex\n", getid_(new_facet_list)));
    +  for (facet= new_facet_list; facet && facet->next; facet= nextfacet) { /* null facets moved to end */
    +    nextfacet= facet->next;
    +    if (facet->visible)
    +      continue;
    +    if (facet->ridges) {
    +      if (qh_setsize(facet->ridges) > 0) {
    +        qh_fprintf(qh ferr, 6161, "qhull error (qh_triangulate): ridges still defined for f%d\n", facet->id);
    +        qh_errexit(qh_ERRqhull, facet, NULL);
    +      }
    +      qh_setfree(&facet->ridges);
    +    }
    +    if (SETfirst_(facet->vertices) == SETsecond_(facet->vertices)) {
    +      zinc_(Ztrinull);
    +      qh_triangulate_null(facet);
    +    }
    +  }
    +  trace2((qh ferr, 2048, "qh_triangulate: delete %d or more mirror facets -- same vertices and neighbors\n", qh_setsize(qh degen_mergeset)));
    +  qh visible_list= qh facet_tail;
    +  while ((merge= (mergeT*)qh_setdellast(qh degen_mergeset))) {
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    mergetype= merge->type;
    +    qh_memfree(merge, (int)sizeof(mergeT));
    +    if (mergetype == MRGmirror) {
    +      zinc_(Ztrimirror);
    +      qh_triangulate_mirror(facet1, facet2);
    +    }
    +  }
    +  qh_settempfree(&qh degen_mergeset);
    +  trace2((qh ferr, 2049, "qh_triangulate: update neighbor lists for vertices from v%d\n", getid_(new_vertex_list)));
    +  qh newvertex_list= new_vertex_list;  /* all vertices of new facets */
    +  qh visible_list= NULL;
    +  qh_updatevertices(/*qh.newvertex_list, empty newfacet_list and visible_list*/);
    +  qh_resetlists(False, !qh_RESETvisible /*qh.newvertex_list, empty newfacet_list and visible_list*/);
    +
    +  trace2((qh ferr, 2050, "qh_triangulate: identify degenerate tricoplanar facets from f%d\n", getid_(new_facet_list)));
    +  trace2((qh ferr, 2051, "qh_triangulate: and replace facet->f.triowner with tricoplanar facets that own center, normal, etc.\n"));
    +  FORALLfacet_(new_facet_list) {
    +    if (facet->tricoplanar && !facet->visible) {
    +      FOREACHneighbor_i_(facet) {
    +        if (neighbor_i == 0) {  /* first iteration */
    +          if (neighbor->tricoplanar)
    +            orig_neighbor= neighbor->f.triowner;
    +          else
    +            orig_neighbor= neighbor;
    +        }else {
    +          if (neighbor->tricoplanar)
    +            otherfacet= neighbor->f.triowner;
    +          else
    +            otherfacet= neighbor;
    +          if (orig_neighbor == otherfacet) {
    +            zinc_(Ztridegen);
    +            facet->degenerate= True;
    +            break;
    +          }
    +        }
    +      }
    +    }
    +  }
    +
    +  trace2((qh ferr, 2052, "qh_triangulate: delete visible facets -- non-simplicial, null, and mirrored facets\n"));
    +  owner= NULL;
    +  visible= NULL;
    +  for (facet= new_facet_list; facet && facet->next; facet= nextfacet) { /* may delete facet */
    +    nextfacet= facet->next;
    +    if (facet->visible) {
    +      if (facet->tricoplanar) { /* a null or mirrored facet */
    +        qh_delfacet(facet);
    +        qh num_visible--;
    +      }else {  /* a non-simplicial facet followed by its tricoplanars */
    +        if (visible && !owner) {
    +          /*  RBOX 200 s D5 t1001471447 | QHULL Qt C-0.01 Qx Qc Tv Qt -- f4483 had 6 vertices/neighbors and 8 ridges */
    +          trace2((qh ferr, 2053, "qh_triangulate: all tricoplanar facets degenerate for non-simplicial facet f%d\n",
    +                       visible->id));
    +          qh_delfacet(visible);
    +          qh num_visible--;
    +        }
    +        visible= facet;
    +        owner= NULL;
    +      }
    +    }else if (facet->tricoplanar) {
    +      if (facet->f.triowner != visible || visible==NULL) {
    +        qh_fprintf(qh ferr, 6162, "qhull error (qh_triangulate): tricoplanar facet f%d not owned by its visible, non-simplicial facet f%d\n", facet->id, getid_(visible));
    +        qh_errexit2(qh_ERRqhull, facet, visible);
    +      }
    +      if (owner)
    +        facet->f.triowner= owner;
    +      else if (!facet->degenerate) {
    +        owner= facet;
    +        nextfacet= visible->next; /* rescan tricoplanar facets with owner, visible!=0 by QH6162 */
    +        facet->keepcentrum= True;  /* one facet owns ->normal, etc. */
    +        facet->coplanarset= visible->coplanarset;
    +        facet->outsideset= visible->outsideset;
    +        visible->coplanarset= NULL;
    +        visible->outsideset= NULL;
    +        if (!qh TRInormals) { /* center and normal copied to tricoplanar facets */
    +          visible->center= NULL;
    +          visible->normal= NULL;
    +        }
    +        qh_delfacet(visible);
    +        qh num_visible--;
    +      }
    +    }
    +  }
    +  if (visible && !owner) {
    +    trace2((qh ferr, 2054, "qh_triangulate: all tricoplanar facets degenerate for last non-simplicial facet f%d\n",
    +                 visible->id));
    +    qh_delfacet(visible);
    +    qh num_visible--;
    +  }
    +  qh NEWfacets= False;
    +  qh ONLYgood= onlygood; /* restore value */
    +  if (qh CHECKfrequently)
    +    qh_checkpolygon(qh facet_list);
    +  qh hasTriangulation= True;
    +} /* triangulate */
    +
    +
    +/*---------------------------------
    +
    +  qh_triangulate_facet(qh, facetA, &firstVertex )
    +    triangulate a non-simplicial facet
    +      if qh.CENTERtype=qh_ASvoronoi, sets its Voronoi center
    +  returns:
    +    qh.newfacet_list == simplicial facets
    +      facet->tricoplanar set and ->keepcentrum false
    +      facet->degenerate set if duplicated apex
    +      facet->f.trivisible set to facetA
    +      facet->center copied from facetA (created if qh_ASvoronoi)
    +        qh_eachvoronoi, qh_detvridge, qh_detvridge3 assume centers copied
    +      facet->normal,offset,maxoutside copied from facetA
    +
    +  notes:
    +      only called by qh_triangulate
    +      qh_makenew_nonsimplicial uses neighbor->seen for the same
    +      if qh.TRInormals, newfacet->normal will need qh_free
    +        if qh.TRInormals and qh_AScentrum, newfacet->center will need qh_free
    +        keepcentrum is also set on Zwidefacet in qh_mergefacet
    +        freed by qh_clearcenters
    +
    +  see also:
    +      qh_addpoint() -- add a point
    +      qh_makenewfacets() -- construct a cone of facets for a new vertex
    +
    +  design:
    +      if qh_ASvoronoi,
    +         compute Voronoi center (facet->center)
    +      select first vertex (highest ID to preserve ID ordering of ->vertices)
    +      triangulate from vertex to ridges
    +      copy facet->center, normal, offset
    +      update vertex neighbors
    +*/
    +void qh_triangulate_facet(facetT *facetA, vertexT **first_vertex) {
    +  facetT *newfacet;
    +  facetT *neighbor, **neighborp;
    +  vertexT *apex;
    +  int numnew=0;
    +
    +  trace3((qh ferr, 3020, "qh_triangulate_facet: triangulate facet f%d\n", facetA->id));
    +
    +  if (qh IStracing >= 4)
    +    qh_printfacet(qh ferr, facetA);
    +  FOREACHneighbor_(facetA) {
    +    neighbor->seen= False;
    +    neighbor->coplanar= False;
    +  }
    +  if (qh CENTERtype == qh_ASvoronoi && !facetA->center  /* matches upperdelaunay in qh_setfacetplane() */
    +        && fabs_(facetA->normal[qh hull_dim -1]) >= qh ANGLEround * qh_ZEROdelaunay) {
    +    facetA->center= qh_facetcenter(facetA->vertices);
    +  }
    +  qh_willdelete(facetA, NULL);
    +  qh newfacet_list= qh facet_tail;
    +  facetA->visitid= qh visit_id;
    +  apex= SETfirstt_(facetA->vertices, vertexT);
    +  qh_makenew_nonsimplicial(facetA, apex, &numnew);
    +  SETfirst_(facetA->neighbors)= NULL;
    +  FORALLnew_facets {
    +    newfacet->tricoplanar= True;
    +    newfacet->f.trivisible= facetA;
    +    newfacet->degenerate= False;
    +    newfacet->upperdelaunay= facetA->upperdelaunay;
    +    newfacet->good= facetA->good;
    +    if (qh TRInormals) { /* 'Q11' triangulate duplicates ->normal and ->center */
    +      newfacet->keepcentrum= True;
    +      if(facetA->normal){
    +        newfacet->normal= qh_memalloc(qh normal_size);
    +        memcpy((char *)newfacet->normal, facetA->normal, qh normal_size);
    +      }
    +      if (qh CENTERtype == qh_AScentrum)
    +        newfacet->center= qh_getcentrum(newfacet);
    +      else if (qh CENTERtype == qh_ASvoronoi && facetA->center){
    +        newfacet->center= qh_memalloc(qh center_size);
    +        memcpy((char *)newfacet->center, facetA->center, qh center_size);
    +      }
    +    }else {
    +      newfacet->keepcentrum= False;
    +      /* one facet will have keepcentrum=True at end of qh_triangulate */
    +      newfacet->normal= facetA->normal;
    +      newfacet->center= facetA->center;
    +    }
    +    newfacet->offset= facetA->offset;
    +#if qh_MAXoutside
    +    newfacet->maxoutside= facetA->maxoutside;
    +#endif
    +  }
    +  qh_matchnewfacets(/*qh.newfacet_list*/);
    +  zinc_(Ztricoplanar);
    +  zadd_(Ztricoplanartot, numnew);
    +  zmax_(Ztricoplanarmax, numnew);
    +  qh visible_list= NULL;
    +  if (!(*first_vertex))
    +    (*first_vertex)= qh newvertex_list;
    +  qh newvertex_list= NULL;
    +  qh_updatevertices(/*qh.newfacet_list, qh.empty visible_list and qh.newvertex_list*/);
    +  qh_resetlists(False, !qh_RESETvisible /*qh.newfacet_list, qh.empty visible_list and qh.newvertex_list*/);
    +} /* triangulate_facet */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_link(oldfacetA, facetA, oldfacetB, facetB)
    +    relink facetA to facetB via oldfacets
    +  returns:
    +    adds mirror facets to qh degen_mergeset (4-d and up only)
    +  design:
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_link(facetT *oldfacetA, facetT *facetA, facetT *oldfacetB, facetT *facetB) {
    +  int errmirror= False;
    +
    +  trace3((qh ferr, 3021, "qh_triangulate_link: relink old facets f%d and f%d between neighbors f%d and f%d\n",
    +         oldfacetA->id, oldfacetB->id, facetA->id, facetB->id));
    +  if (qh_setin(facetA->neighbors, facetB)) {
    +    if (!qh_setin(facetB->neighbors, facetA))
    +      errmirror= True;
    +    else
    +      qh_appendmergeset(facetA, facetB, MRGmirror, NULL);
    +  }else if (qh_setin(facetB->neighbors, facetA))
    +    errmirror= True;
    +  if (errmirror) {
    +    qh_fprintf(qh ferr, 6163, "qhull error (qh_triangulate_link): mirror facets f%d and f%d do not match for old facets f%d and f%d\n",
    +       facetA->id, facetB->id, oldfacetA->id, oldfacetB->id);
    +    qh_errexit2(qh_ERRqhull, facetA, facetB);
    +  }
    +  qh_setreplace(facetB->neighbors, oldfacetB, facetA);
    +  qh_setreplace(facetA->neighbors, oldfacetA, facetB);
    +} /* triangulate_link */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_mirror(facetA, facetB)
    +    delete mirrored facets from qh_triangulate_null() and qh_triangulate_mirror
    +      a mirrored facet shares the same vertices of a logical ridge
    +  design:
    +    since a null facet duplicates the first two vertices, the opposing neighbors absorb the null facet
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_mirror(facetT *facetA, facetT *facetB) {
    +  facetT *neighbor, *neighborB;
    +  int neighbor_i, neighbor_n;
    +
    +  trace3((qh ferr, 3022, "qh_triangulate_mirror: delete mirrored facets f%d and f%d\n",
    +         facetA->id, facetB->id));
    +  FOREACHneighbor_i_(facetA) {
    +    neighborB= SETelemt_(facetB->neighbors, neighbor_i, facetT);
    +    if (neighbor == neighborB)
    +      continue; /* occurs twice */
    +    qh_triangulate_link(facetA, neighbor, facetB, neighborB);
    +  }
    +  qh_willdelete(facetA, NULL);
    +  qh_willdelete(facetB, NULL);
    +} /* triangulate_mirror */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_null(facetA)
    +    remove null facetA from qh_triangulate_facet()
    +      a null facet has vertex #1 (apex) == vertex #2
    +  returns:
    +    adds facetA to ->visible for deletion after qh_updatevertices
    +    qh degen_mergeset contains mirror facets (4-d and up only)
    +  design:
    +    since a null facet duplicates the first two vertices, the opposing neighbors absorb the null facet
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_null(facetT *facetA) {
    +  facetT *neighbor, *otherfacet;
    +
    +  trace3((qh ferr, 3023, "qh_triangulate_null: delete null facet f%d\n", facetA->id));
    +  neighbor= SETfirstt_(facetA->neighbors, facetT);
    +  otherfacet= SETsecondt_(facetA->neighbors, facetT);
    +  qh_triangulate_link(facetA, neighbor, facetA, otherfacet);
    +  qh_willdelete(facetA, NULL);
    +} /* triangulate_null */
    +
    +#else /* qh_NOmerge */
    +void qh_triangulate(void) {
    +}
    +#endif /* qh_NOmerge */
    +
    +   /*---------------------------------
    +
    +  qh_vertexintersect( vertexsetA, vertexsetB )
    +    intersects two vertex sets (inverse id ordered)
    +    vertexsetA is a temporary set at the top of qhmem.tempstack
    +
    +  returns:
    +    replaces vertexsetA with the intersection
    +
    +  notes:
    +    could overwrite vertexsetA if currently too slow
    +*/
    +void qh_vertexintersect(setT **vertexsetA,setT *vertexsetB) {
    +  setT *intersection;
    +
    +  intersection= qh_vertexintersect_new(*vertexsetA, vertexsetB);
    +  qh_settempfree(vertexsetA);
    +  *vertexsetA= intersection;
    +  qh_settemppush(intersection);
    +} /* vertexintersect */
    +
    +/*---------------------------------
    +
    +  qh_vertexintersect_new(  )
    +    intersects two vertex sets (inverse id ordered)
    +
    +  returns:
    +    a new set
    +*/
    +setT *qh_vertexintersect_new(setT *vertexsetA,setT *vertexsetB) {
    +  setT *intersection= qh_setnew(qh hull_dim - 1);
    +  vertexT **vertexA= SETaddr_(vertexsetA, vertexT);
    +  vertexT **vertexB= SETaddr_(vertexsetB, vertexT);
    +
    +  while (*vertexA && *vertexB) {
    +    if (*vertexA  == *vertexB) {
    +      qh_setappend(&intersection, *vertexA);
    +      vertexA++; vertexB++;
    +    }else {
    +      if ((*vertexA)->id > (*vertexB)->id)
    +        vertexA++;
    +      else
    +        vertexB++;
    +    }
    +  }
    +  return intersection;
    +} /* vertexintersect_new */
    +
    +/*---------------------------------
    +
    +  qh_vertexneighbors()
    +    for each vertex in qh.facet_list,
    +      determine its neighboring facets
    +
    +  returns:
    +    sets qh.VERTEXneighbors
    +      nop if qh.VERTEXneighbors already set
    +      qh_addpoint() will maintain them
    +
    +  notes:
    +    assumes all vertex->neighbors are NULL
    +
    +  design:
    +    for each facet
    +      for each vertex
    +        append facet to vertex->neighbors
    +*/
    +void qh_vertexneighbors(void /*qh.facet_list*/) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh VERTEXneighbors)
    +    return;
    +  trace1((qh ferr, 1035, "qh_vertexneighbors: determining neighboring facets for each vertex\n"));
    +  qh vertex_visit++;
    +  FORALLfacets {
    +    if (facet->visible)
    +      continue;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh vertex_visit) {
    +        vertex->visitid= qh vertex_visit;
    +        vertex->neighbors= qh_setnew(qh hull_dim);
    +      }
    +      qh_setappend(&vertex->neighbors, facet);
    +    }
    +  }
    +  qh VERTEXneighbors= True;
    +} /* vertexneighbors */
    +
    +/*---------------------------------
    +
    +  qh_vertexsubset( vertexsetA, vertexsetB )
    +    returns True if vertexsetA is a subset of vertexsetB
    +    assumes vertexsets are sorted
    +
    +  note:
    +    empty set is a subset of any other set
    +*/
    +boolT qh_vertexsubset(setT *vertexsetA, setT *vertexsetB) {
    +  vertexT **vertexA= (vertexT **) SETaddr_(vertexsetA, vertexT);
    +  vertexT **vertexB= (vertexT **) SETaddr_(vertexsetB, vertexT);
    +
    +  while (True) {
    +    if (!*vertexA)
    +      return True;
    +    if (!*vertexB)
    +      return False;
    +    if ((*vertexA)->id > (*vertexB)->id)
    +      return False;
    +    if (*vertexA  == *vertexB)
    +      vertexA++;
    +    vertexB++;
    +  }
    +  return False; /* avoid warnings */
    +} /* vertexsubset */
    diff --git a/xs/src/qhull/src/libqhull/qh-geom.htm b/xs/src/qhull/src/libqhull/qh-geom.htm
    new file mode 100644
    index 0000000000..6dc7465ebe
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/qh-geom.htm
    @@ -0,0 +1,295 @@
    +
    +
    +
    +
    +geom.c, geom2.c -- geometric and floating point routines
    +
    +
    +
    +
    +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + + +

    geom.c, geom2.c, random.c -- geometric and floating point routines

    +
    +

    Geometrically, a vertex is a point with d coordinates +and a facet is a halfspace. A halfspace is defined by an +oriented hyperplane through the facet's vertices. A hyperplane +is defined by d normalized coefficients and an offset. A +point is above a facet if its distance to the facet is +positive.

    + +

    Qhull uses floating point coordinates for input points, +vertices, halfspace equations, centrums, and an interior point.

    + +

    Qhull may be configured for single precision or double +precision floating point arithmetic (see realT +).

    + +

    Each floating point operation may incur round-off error (see +Merge). The maximum error for distance +computations is determined at initialization. The roundoff error +in halfspace computation is accounted for by computing the +distance from vertices to the halfspace.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly • +QhullSet • +StatUser

    + +

    Index to geom.c, +geom2.c, geom.h, +random.c, random.h +

    + + + +

    »geometric data types +and constants

    + +
      +
    • coordT coordinates and +coefficients are stored as realT
    • +
    • pointT a point is an array +of DIM3 coordinates
    • +
    + +

    »mathematical macros

    + +
      +
    • fabs_ returns the absolute +value of a
    • +
    • fmax_ returns the maximum +value of a and b
    • +
    • fmin_ returns the minimum +value of a and b
    • +
    • maximize_ maximize a value +
    • +
    • minimize_ minimize a value +
    • +
    • det2_ compute a 2-d +determinate
    • +
    • det3_ compute a 3-d +determinate
    • +
    • dX, dY, dZ compute the difference +between two coordinates
    • +
    + +

    »mathematical functions

    + + + +

    »computational geometry functions

    + + + +

    »point array functions

    + + +

    »geometric facet functions

    + + +

    »geometric roundoff functions

    +
      +
    • qh_detjoggle determine +default joggle for points and distance roundoff error
    • +
    • qh_detroundoff +determine maximum roundoff error and other precision constants
    • +
    • qh_distround compute +maximum roundoff error due to a distance computation to a +normalized hyperplane
    • +
    • qh_divzero divide by a +number that is nearly zero
    • +
    • qh_maxouter return maximum outer +plane
    • +
    • qh_outerinner return actual +outer and inner planes +
    + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    + + +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-globa.htm b/xs/src/qhull/src/libqhull/qh-globa.htm new file mode 100644 index 0000000000..c87508b663 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-globa.htm @@ -0,0 +1,165 @@ + + + + +global.c -- global variables and their functions + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + + +

    global.c -- global variables and their functions

    +
    +

    Qhull uses a global data structure, qh, to store +globally defined constants, lists, sets, and variables. This +allows multiple instances of Qhull to execute at the same time. +The structure may be statically allocated or +dynamically allocated with malloc(). See +QHpointer. +

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly • +QhullSet • +StatUser

    + +

    Index to global.c and +libqhull.h

    + + + +

    »Qhull's global +variables

    + + + +

    »Global variable and +initialization routines

    + + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-io.htm b/xs/src/qhull/src/libqhull/qh-io.htm new file mode 100644 index 0000000000..5cb591d877 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-io.htm @@ -0,0 +1,305 @@ + + + + +io.c -- input and output operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    io.c -- input and output operations

    +
    + +

    Qhull provides a wide range of input +and output options. To organize the code, most output formats use +the same driver:

    + +
    +    qh_printbegin( fp, format, facetlist, facets, printall );
    +
    +    FORALLfacet_( facetlist )
    +      qh_printafacet( fp, format, facet, printall );
    +
    +    FOREACHfacet_( facets )
    +      qh_printafacet( fp, format, facet, printall );
    +
    +    qh_printend( fp, format );
    +
    + +

    Note the 'printall' flag. It selects whether or not +qh_skipfacet() is tested.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom +GlobalIo • +MemMerge • +PolyQhull • +SetStat • +User

    + +

    Index to io.c and io.h

    + + + +

    »io.h constants and types

    + +
      +
    • qh_MAXfirst maximum length +of first two lines of stdin
    • +
    • qh_WHITESPACE possible +values of white space
    • +
    • printvridgeT function to +print results of qh_printvdiagram or qh_eachvoronoi
    • +
    + +

    »User level functions

    + + + +

    »Print functions for all +output formats

    + + + +

    »Text output functions

    + + +

    »Text utility functions

    + + +

    »Geomview output functions

    + +

    »Geomview utility functions

    + +

    +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-mem.htm b/xs/src/qhull/src/libqhull/qh-mem.htm new file mode 100644 index 0000000000..b993b22297 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-mem.htm @@ -0,0 +1,145 @@ + + + + +mem.c -- memory operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    mem.c -- memory operations

    +
    +

    Qhull uses quick-fit memory allocation. It maintains a +set of free lists for a variety of small allocations. A +small request returns a block from the best fitting free +list. If the free list is empty, Qhull allocates a block +from a reserved buffer.

    +

    Use 'T5' to trace memory allocations.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to mem.c and +mem.h

    + +

    »mem.h data types and constants

    +
      +
    • ptr_intT for casting +a void* to an integer-type
    • +
    • qhmemT global memory +structure for mem.c
    • +
    • qh_NOmem disable memory allocation
    • +
    +

    »mem.h macros

    + +

    »User level +functions

    + + +

    »Initialization and +termination functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-merge.htm b/xs/src/qhull/src/libqhull/qh-merge.htm new file mode 100644 index 0000000000..54b97c88ee --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-merge.htm @@ -0,0 +1,366 @@ + + + + +merge.c -- facet merge operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    merge.c -- facet merge operations

    +
    +

    Qhull handles precision problems by merged facets or joggled input. +Except for redundant vertices, it corrects a problem by +merging two facets. When done, all facets are clearly +convex. See Imprecision in Qhull +for further information.

    +

    Users may joggle the input ('QJn') +instead of merging facets.

    +

    Qhull detects and corrects the following problems:

    +
      +
    • More than two facets meeting at a ridge. When +Qhull creates facets, it creates an even number +of facets for each ridge. A convex hull always +has two facets for each ridge. More than two +facets may be created if non-adjacent facets +share a vertex. This is called a duplicate +ridge. In 2-d, a duplicate ridge would +create a loop of facets.
    • +
    +
      +
    • A facet contained in another facet. Facet +merging may leave all vertices of one facet as a +subset of the vertices of another facet. This is +called a redundant facet.
    • +
    +
      +
    • A facet with fewer than three neighbors. Facet +merging may leave a facet with one or two +neighbors. This is called a degenerate facet. +
    • +
    +
      +
    • A facet with flipped orientation. A +facet's hyperplane may define a halfspace that +does not include the interior point.This is +called a flipped facet.
    • +
    +
      +
    • A coplanar horizon facet. A +newly processed point may be coplanar with an +horizon facet. Qhull creates a new facet without +a hyperplane. It links new facets for the same +horizon facet together. This is called a samecycle. +The new facet or samecycle is merged into the +horizon facet.
    • +
    +
      +
    • Concave facets. A facet's centrum may be +above a neighboring facet. If so, the facets meet +at a concave angle.
    • +
    +
      +
    • Coplanar facets. A facet's centrum may be +coplanar with a neighboring facet (i.e., it is +neither clearly below nor clearly above the +facet's hyperplane). Qhull removes coplanar +facets in independent sets sorted by angle.
    • +
    +
      +
    • Redundant vertex. A vertex may have fewer +than three neighboring facets. If so, it is +redundant and may be renamed to an adjacent +vertex without changing the topological +structure.This is called a redundant vertex. +
    • +
    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to merge.c and +merge.h

    + + +

    »merge.h data +types, macros, and global sets

    +
      +
    • mergeT structure to +identify a merge of two facets
    • +
    • FOREACHmerge_ +assign 'merge' to each merge in merges
    • +
    • qh global sets +qh.facet_mergeset contains non-convex merges +while qh.degen_mergeset contains degenerate and +redundant facets
    • +
    +

    »merge.h +constants

    + +

    »top-level merge +functions

    + + +

    »functions for +identifying merges

    + + +

    »functions for +determining the best merge

    + + +

    »functions for +merging facets

    + + +

    »functions for +merging a cycle of facets

    +

    If a point is coplanar with an horizon facet, the +corresponding new facets are linked together (a samecycle) +for merging.

    + +

    »functions +for renaming a vertex

    + + +

    »functions +for identifying vertices for renaming

    + + +

    »functions for check and +trace

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-poly.htm b/xs/src/qhull/src/libqhull/qh-poly.htm new file mode 100644 index 0000000000..c8f6b38b0d --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-poly.htm @@ -0,0 +1,485 @@ + + + + +poly.c, poly2.c -- polyhedron operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    poly.c, poly2.c -- polyhedron operations

    +
    + +

    Qhull uses dimension-free terminology. Qhull builds a +polyhedron in dimension d. A polyhedron is a +simplicial complex of faces with geometric information for the +top and bottom-level faces. A (d-1)-face is a facet, +a (d-2)-face is a ridge, and a 0-face +is a vertex. For example in 3-d, a facet is a polygon +and a ridge is an edge. A facet is built from a ridge (the base) +and a vertex (the apex). See +Qhull's data structures.

    + +

    Qhull's primary data structure is a polyhedron. A +polyhedron is a list of facets. Each facet has a set of +neighboring facets and a set of vertices. Each facet has a +hyperplane. For example, a tetrahedron has four facets. +If its vertices are a, b, c, d, and its facets +are 1, 2, 3, 4, the tetrahedron is

    +
    +
      +
    • facet 1
        +
      • vertices: b c d
      • +
      • neighbors: 2 3 4
      • +
      +
    • +
    • facet 2
        +
      • vertices: a c d
      • +
      • neighbors: 1 3 4
      • +
      +
    • +
    • facet 3
        +
      • vertices: a b d
      • +
      • neighbors: 1 2 4
      • +
      +
    • +
    • facet 4
        +
      • vertices: a b c
      • +
      • neighbors: 1 2 3
      • +
      +
    • +
    +
    +

    A facet may be simplicial or non-simplicial. In 3-d, a +simplicial facet has three vertices and three +neighbors. A nonsimplicial facet has more than +three vertices and more than three neighbors. A +nonsimplicial facet has a set of ridges and a centrum.

    +

    +A simplicial facet has an orientation. An orientation +is either top or bottom. +The flag, facet->toporient, +defines the orientation of the facet's vertices. For example in 3-d, +'top' is left-handed orientation (i.e., the vertex order follows the direction +of the left-hand fingers when the thumb is pointing away from the center). +Except for axis-parallel facets in 5-d and higher, topological orientation +determines the geometric orientation of the facet's hyperplane. + +

    A nonsimplicial facet is due to merging two or more +facets. The facet's ridge set determine a simplicial +decomposition of the facet. Each ridge is a 1-face (i.e., +it has two vertices and two neighboring facets). The +orientation of a ridge is determined by the order of the +neighboring facets. The flag, facet->toporient,is +ignored.

    +

    A nonsimplicial facet has a centrum for testing +convexity. A centrum is a point on the facet's +hyperplane that is near the center of the facet. Except +for large facets, it is the arithmetic average of the +facet's vertices.

    +

    A nonsimplicial facet is an approximation that is +defined by offsets from the facet's hyperplane. When +Qhull finishes, the outer plane is above all +points while the inner plane is below the facet's +vertices. This guarantees that any exact convex hull +passes between the inner and outer planes. The outer +plane is defined by facet->maxoutside while +the inner plane is computed from the facet's vertices.

    + +

    Qhull 3.1 includes triangulation of non-simplicial facets +('Qt'). +These facets, +called tricoplanar, share the same normal. centrum, and Voronoi center. +One facet (keepcentrum) owns these data structures. +While tricoplanar facets are more accurate than the simplicial facets from +joggled input, they +may have zero area or flipped orientation. + +

    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to poly.c, +poly2.c, poly.h, +and libqhull.h

    + +

    »Data +types and global lists for polyhedrons

    + +

    »poly.h constants

    +
      +
    • ALGORITHMfault +flag to not report errors in qh_checkconvex()
    • +
    • DATAfault flag to +report errors in qh_checkconvex()
    • +
    • DUPLICATEridge +special value for facet->neighbor to indicate +a duplicate ridge
    • +
    • MERGEridge +special value for facet->neighbor to indicate +a merged ridge
    • +
    +

    »Global FORALL +macros

    + +

    »FORALL macros

    + +

    »FOREACH macros

    + +

    »Indexed +FOREACH macros

    +
      +
    • FOREACHfacet_i_ +assign 'facet' and 'facet_i' to each facet in +facet set
    • +
    • FOREACHneighbor_i_ +assign 'neighbor' and 'neighbor_i' to each facet +in facet->neighbors or vertex->neighbors
    • +
    • FOREACHpoint_i_ +assign 'point' and 'point_i' to each point in +points set
    • +
    • FOREACHridge_i_ +assign 'ridge' and 'ridge_i' to each ridge in +ridges set
    • +
    • FOREACHvertex_i_ +assign 'vertex' and 'vertex_i' to each vertex in +vertices set
    • +
    • FOREACHvertexreverse12_ +assign 'vertex' to each vertex in vertex set; +reverse the order of first two vertices
    • +
    +

    »Other macros for polyhedrons

    +
      +
    • getid_ return ID for +a facet, ridge, or vertex
    • +
    • otherfacet_ +return neighboring facet for a ridge in a facet
    • +
    +

    »Facetlist +functions

    + +

    »Facet +functions

    + +

    »Vertex, +ridge, and point functions

    + +

    »Hashtable functions

    + +

    »Allocation and +deallocation functions

    + +

    »Check +functions

    + + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-qhull.htm b/xs/src/qhull/src/libqhull/qh-qhull.htm new file mode 100644 index 0000000000..5212c64226 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-qhull.htm @@ -0,0 +1,279 @@ + + + + +libqhull.c -- top-level functions and basic data types + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    libqhull.c -- top-level functions and basic data types

    +
    +

    Qhull implements the Quickhull algorithm for computing +the convex hull. The Quickhull algorithm combines two +well-known algorithms: the 2-d quickhull algorithm and +the n-d beneath-beyond algorithm. See +Description of Qhull.

    +

    This section provides an index to the top-level +functions and base data types. The top-level header file, libqhull.h, +contains prototypes for these functions.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to libqhull.c, +libqhull.h, and +unix.c

    + + +

    »libqhull.h and unix.c +data types and constants

    +
      +
    • flagT Boolean flag as +a bit
    • +
    • boolT boolean value, +either True or False
    • +
    • CENTERtype to +distinguish facet->center
    • +
    • qh_PRINT output +formats for printing (qh.PRINTout)
    • +
    • qh_ALL argument flag +for selecting everything
    • +
    • qh_ERR Qhull exit +codes for indicating errors
    • +
    • qh_FILEstderr Fake stderr +to distinguish error output from normal output [C++ only]
    • +
    • qh_prompt version and long prompt for Qhull
    • +
    • qh_prompt2 synopsis for Qhull
    • +
    • qh_prompt3 concise prompt for Qhull
    • +
    • qh_version version stamp
    • +
    + +

    »libqhull.h other +macros

    +
      +
    • traceN print trace +message if qh.IStracing >= N.
    • +
    • QHULL_UNUSED declare an + unused variable to avoid warnings.
    • +
    + +

    »Quickhull +routines in call order

    + + +

    »Top-level routines for initializing and terminating Qhull (in other modules)

    + + +

    »Top-level routines for reading and modifying the input (in other modules)

    + + +

    »Top-level routines for calling Qhull (in other modules)

    + + +

    »Top-level routines for returning results (in other modules)

    + + +

    »Top-level routines for testing and debugging (in other modules)

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-set.htm b/xs/src/qhull/src/libqhull/qh-set.htm new file mode 100644 index 0000000000..06e71bbc92 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-set.htm @@ -0,0 +1,308 @@ + + + + +qset.c -- set data type and operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    qset.c -- set data type and operations

    +
    +

    Qhull's data structures are constructed from sets. The +functions and macros in qset.c construct, iterate, and +modify these sets. They are the most frequently called +functions in Qhull. For this reason, efficiency is the +primary concern.

    +

    In Qhull, a set is represented by an unordered +array of pointers with a maximum size and a NULL +terminator (setT). +Most sets correspond to mathematical sets +(i.e., the pointers are unique). Some sets are sorted to +enforce uniqueness. Some sets are ordered. For example, +the order of vertices in a ridge determine the ridge's +orientation. If you reverse the order of adjacent +vertices, the orientation reverses. Some sets are not +mathematical sets. They may be indexed as an array and +they may include NULL pointers.

    +

    The most common operation on a set is to iterate its +members. This is done with a 'FOREACH...' macro. Each set +has a custom macro. For example, 'FOREACHvertex_' +iterates over a set of vertices. Each vertex is assigned +to the variable 'vertex' from the pointer 'vertexp'.

    +

    Most sets are constructed by appending elements to the +set. The last element of a set is either NULL or the +index of the terminating NULL for a partially full set. +If a set is full, appending an element copies the set to +a larger array.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly +• QhullSet +• StatUser +

    +

    Index to qset.c and +qset.h

    + +

    »Data types and +constants

    +
      +
    • SETelemsize size +of a set element in bytes
    • +
    • setT a set with a +maximum size and a current size
    • +
    • qh global sets +global sets for temporary sets, etc.
    • +
    +

    »FOREACH macros

    + +

    »Access and +size macros

    + +

    »Internal macros

    +
      +
    • SETsizeaddr_ +return pointer to end element of a set (indicates +current size)
    • +
    + +

    »address macros

    +
      +
    • SETaddr_ return +address of a set's elements
    • +
    • SETelemaddr_ +return address of the n'th element of a set
    • +
    • SETref_ l.h.s. for +modifying the current element in a FOREACH +iteration
    • +
    + +

    »Allocation and +deallocation functions

    + + +

    »Access and +predicate functions

    + + +

    »Add functions

    + + +

    »Check and print functions

    + + +

    »Copy, compact, and zero functions

    + + +

    »Delete functions

    + + +

    »Temporary set functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-stat.htm b/xs/src/qhull/src/libqhull/qh-stat.htm new file mode 100644 index 0000000000..b968540312 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-stat.htm @@ -0,0 +1,163 @@ + + + + +stat.c -- statistical operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    stat.c -- statistical operations

    +
    +

    Qhull records many statistics. These functions and +macros make it inexpensive to add a statistic. +

    As with Qhull's global variables, the statistics data structure is +accessed by a macro, 'qhstat'. If qh_QHpointer is defined, the macro +is 'qh_qhstat->', otherwise the macro is 'qh_qhstat.'. +Statistics +may be turned off in user.h. If so, all but the 'zz' +statistics are ignored.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to stat.c and +stat.h

    + + +

    »stat.h types

    +
      +
    • intrealT union of +integer and real
    • +
    • qhstat global data +structure for statistics
    • +
    +

    »stat.h +constants

    +
      +
    • qh_KEEPstatistics 0 turns off most statistics
    • +
    • Z..., W... integer (Z) and real (W) statistics +
    • +
    • ZZstat Z.../W... statistics that +remain defined if qh_KEEPstatistics=0 +
    • +
    • ztype zdoc, zinc, etc. +for definining statistics
    • +
    +

    »stat.h macros

    +
      +
    • MAYdebugx called +frequently for error trapping
    • +
    • zadd_/wadd_ add value +to an integer or real statistic
    • +
    • zdef_ define a +statistic
    • +
    • zinc_ increment an +integer statistic
    • +
    • zmax_/wmax_ update +integer or real maximum statistic
    • +
    • zmin_/wmin_ update +integer or real minimum statistic
    • +
    • zval_/wval_ set or +return value of a statistic
    • +
    + +

    »stat.c +functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-user.htm b/xs/src/qhull/src/libqhull/qh-user.htm new file mode 100644 index 0000000000..6682f4b2fb --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-user.htm @@ -0,0 +1,271 @@ + + + + +user.c -- user-definable operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    +

    user.c -- user-definable operations

    +
    +

    This section contains functions and constants that the +user may want to change.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to user.c, usermem.c, userprintf.c, userprintf_rbox.c and +user.h

    + + +

    »Qhull library constants

    + + + +

    »user.h data +types and configuration macros

    + + +

    »definition constants

    +
      +
    • qh_DEFAULTbox +define default box size for rbox, 'Qbb', and 'QbB' (Geomview expects 0.5)
    • +
    • qh_INFINITE on +output, indicates Voronoi center at infinity
    • +
    • qh_ORIENTclock +define convention for orienting facets
    • +
    • qh_ZEROdelaunay +define facets that are ignored in Delaunay triangulations
    • +
    + +

    »joggle constants

    + + +

    »performance +related constants

    + + +

    »memory constants

    + + +

    »conditional compilation

    +
      +
    • compiler defined symbols, +e.g., _STDC_ and _cplusplus + +
    • qh_COMPUTEfurthest + compute furthest distance to an outside point instead of storing it with the facet +
    • qh_KEEPstatistics + enable statistic gathering and reporting with option 'Ts' +
    • qh_MAXoutside +record outer plane for each facet +
    • qh_NOmerge +disable facet merging +
    • qh_NOtrace +disable tracing with option 'T4' +
    • qh_QHpointer +access global data with pointer or static structure +
    • qh_QUICKhelp +use abbreviated help messages, e.g., for degenerate inputs +
    + +

    »merge +constants

    + + +

    »user.c +functions

    + + +

    »usermem.c +functions

    +
      +
    • qh_exit exit program, same as exit(). May be redefined as throw "QH10003.." by libqhullcpp/usermem_r-cpp.cpp
    • +
    • qh_fprintf_stderr print to stderr when qh.ferr is not defined.
    • +
    • qh_free free memory, same as free().
    • +
    • qh_malloc allocate memory, same as malloc()
    • +
    + +

    »userprintf.c + and userprintf_rbox,c functions

    +
      +
    • qh_fprintf print +information from Qhull, sames as fprintf().
    • +
    • qh_fprintf_rbox print +information from Rbox, sames as fprintf().
    • +
    + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qhull-exports.def b/xs/src/qhull/src/libqhull/qhull-exports.def new file mode 100644 index 0000000000..11a42b57ee --- /dev/null +++ b/xs/src/qhull/src/libqhull/qhull-exports.def @@ -0,0 +1,417 @@ +; qhull-exports.def -- msvc module-definition file +; +; Generated from depends.exe by cut-and-paste of exported symbols by mingw gcc +; [mar'11] 399 symbols +; Annotate as DATA qh_last_random qh_qh qh_qhstat qhmem rbox rbox_inuse +; Annotate as __declspec for outside access in win32 -- qh_qh qh_qhstat +; Same as ../libqhullp/qhull_p-exports.def without qh_save_qhull and qh_restore_qhull +; +; $Id: //main/2015/qhull/src/libqhull/qhull-exports.def#3 $$Change: 2047 $ +; $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $ +; +; Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, and qhull-warn.pri +VERSION 7.0 +EXPORTS +qh_addhash +qh_addpoint +qh_all_merges +qh_allstatA +qh_allstatB +qh_allstatC +qh_allstatD +qh_allstatE +qh_allstatE2 +qh_allstatF +qh_allstatG +qh_allstatH +qh_allstatI +qh_allstatistics +qh_appendfacet +qh_appendmergeset +qh_appendprint +qh_appendvertex +qh_argv_to_command +qh_argv_to_command_size +qh_attachnewfacets +qh_backnormal +qh_basevertices +qh_build_withrestart +qh_buildhull +qh_buildtracing +qh_check_bestdist +qh_check_dupridge +qh_check_maxout +qh_check_output +qh_check_point +qh_check_points +qh_checkconnect +qh_checkconvex +qh_checkfacet +qh_checkflags +qh_checkflipped +qh_checkflipped_all +qh_checkpolygon +qh_checkvertex +qh_checkzero +qh_clear_outputflags +qh_clearcenters +qh_clock +qh_collectstatistics +qh_compare_facetarea +qh_compare_facetmerge +qh_compare_facetvisit +qh_compare_vertexpoint +qh_compareangle +qh_comparemerge +qh_comparevisit +qh_copyfilename +qh_copynonconvex +qh_copypoints +qh_countfacets +qh_createsimplex +qh_crossproduct +qh_degen_redundant_facet +qh_degen_redundant_neighbors +qh_deletevisible +qh_delfacet +qh_delridge +qh_delvertex +qh_determinant +qh_detjoggle +qh_detroundoff +qh_detsimplex +qh_detvnorm +qh_detvridge +qh_detvridge3 +qh_dfacet +qh_distnorm +qh_distplane +qh_distround +qh_divzero +qh_dvertex +qh_eachvoronoi +qh_eachvoronoi_all +qh_errexit +qh_errexit2 +qh_errexit_rbox +qh_errprint +qh_exit +qh_facet2point +qh_facet3vertex +qh_facetarea +qh_facetarea_simplex +qh_facetcenter +qh_facetintersect +qh_facetvertices +qh_find_newvertex +qh_findbest +qh_findbest_test +qh_findbestfacet +qh_findbesthorizon +qh_findbestlower +qh_findbestneighbor +qh_findbestnew +qh_findfacet_all +qh_findgood +qh_findgood_all +qh_findgooddist +qh_findhorizon +qh_flippedmerges +qh_forcedmerges +qh_fprintf +qh_fprintf_rbox +qh_fprintf_stderr +qh_free +qh_freebuffers +qh_freebuild +qh_freeqhull +qh_freeqhull2 +qh_freestatistics +qh_furthestnext +qh_furthestout +qh_gausselim +qh_geomplanes +qh_getangle +qh_getarea +qh_getcenter +qh_getcentrum +qh_getdistance +qh_gethash +qh_getmergeset +qh_getmergeset_initial +qh_gram_schmidt +qh_hashridge +qh_hashridge_find +qh_infiniteloop +qh_init_A +qh_init_B +qh_init_qhull_command +qh_initbuild +qh_initflags +qh_initialhull +qh_initialvertices +qh_initqhull_buffers +qh_initqhull_globals +qh_initqhull_mem +qh_initqhull_outputflags +qh_initqhull_start +qh_initqhull_start2 +qh_initstatistics +qh_initthresholds +qh_inthresholds +qh_isvertex +qh_joggleinput +; Mark as DATA, otherwise links a separate qh_last_random. No __declspec. +qh_last_random DATA +qh_lib_check +qh_makenew_nonsimplicial +qh_makenew_simplicial +qh_makenewfacet +qh_makenewfacets +qh_makenewplanes +qh_makeridges +qh_malloc +qh_mark_dupridges +qh_markkeep +qh_markvoronoi +qh_matchduplicates +qh_matchneighbor +qh_matchnewfacets +qh_matchvertices +qh_maxabsval +qh_maxmin +qh_maxouter +qh_maxsimplex +qh_maydropneighbor +qh_memalloc +qh_memfree +qh_memfreeshort +qh_meminit +qh_meminitbuffers +qh_memsetup +qh_memsize +qh_memstatistics +qh_memtotal +qh_merge_degenredundant +qh_merge_nonconvex +qh_mergecycle +qh_mergecycle_all +qh_mergecycle_facets +qh_mergecycle_neighbors +qh_mergecycle_ridges +qh_mergecycle_vneighbors +qh_mergefacet +qh_mergefacet2d +qh_mergeneighbors +qh_mergeridges +qh_mergesimplex +qh_mergevertex_del +qh_mergevertex_neighbors +qh_mergevertices +qh_minabsval +qh_mindiff +qh_nearcoplanar +qh_nearvertex +qh_neighbor_intersections +qh_new_qhull +qh_newfacet +qh_newhashtable +qh_newridge +qh_newstats +qh_newvertex +qh_newvertices +qh_nextfurthest +qh_nextridge3d +qh_normalize +qh_normalize2 +qh_nostatistic +qh_option +qh_order_vertexneighbors +qh_orientoutside +qh_out1 +qh_out2n +qh_out3n +qh_outcoplanar +qh_outerinner +qh_partitionall +qh_partitioncoplanar +qh_partitionpoint +qh_partitionvisible +qh_point +qh_point_add +qh_pointdist +qh_pointfacet +qh_pointid +qh_pointvertex +qh_postmerge +qh_precision +qh_premerge +qh_prepare_output +qh_prependfacet +qh_printafacet +qh_printallstatistics +qh_printbegin +qh_printcenter +qh_printcentrum +qh_printend +qh_printend4geom +qh_printextremes +qh_printextremes_2d +qh_printextremes_d +qh_printfacet +qh_printfacet2geom +qh_printfacet2geom_points +qh_printfacet2math +qh_printfacet3geom_nonsimplicial +qh_printfacet3geom_points +qh_printfacet3geom_simplicial +qh_printfacet3math +qh_printfacet3vertex +qh_printfacet4geom_nonsimplicial +qh_printfacet4geom_simplicial +qh_printfacetNvertex_nonsimplicial +qh_printfacetNvertex_simplicial +qh_printfacetheader +qh_printfacetlist +qh_printfacetridges +qh_printfacets +qh_printhashtable +qh_printhelp_degenerate +qh_printhelp_narrowhull +qh_printhelp_singular +qh_printhyperplaneintersection +qh_printline3geom +qh_printlists +qh_printmatrix +qh_printneighborhood +qh_printpoint +qh_printpoint3 +qh_printpointid +qh_printpoints +qh_printpoints_out +qh_printpointvect +qh_printpointvect2 +qh_printridge +qh_printspheres +qh_printstatistics +qh_printstatlevel +qh_printstats +qh_printsummary +qh_printvdiagram +qh_printvdiagram2 +qh_printvertex +qh_printvertexlist +qh_printvertices +qh_printvneighbors +qh_printvnorm +qh_printvoronoi +qh_printvridge +qh_produce_output +qh_produce_output2 +qh_projectdim3 +qh_projectinput +qh_projectpoint +qh_projectpoints +; Mark as DATA, otherwise links a separate qh_qh. qh_qh and qh_qhstat requires __declspec +qh_qh DATA +qh_qhstat DATA +qh_qhull +qh_rand +qh_randomfactor +qh_randommatrix +qh_rboxpoints +qh_readfeasible +qh_readpoints +qh_reducevertices +qh_redundant_vertex +qh_remove_extravertices +qh_removefacet +qh_removevertex +qh_rename_sharedvertex +qh_renameridgevertex +qh_renamevertex +qh_resetlists +qh_rotateinput +qh_rotatepoints +qh_roundi +qh_scaleinput +qh_scalelast +qh_scalepoints +qh_setaddnth +qh_setaddsorted +qh_setappend +qh_setappend2ndlast +qh_setappend_set +qh_setcheck +qh_setcompact +qh_setcopy +qh_setdel +qh_setdelaunay +qh_setdellast +qh_setdelnth +qh_setdelnthsorted +qh_setdelsorted +qh_setduplicate +qh_setequal +qh_setequal_except +qh_setequal_skip +qh_setfacetplane +qh_setfeasible +qh_setfree +qh_setfree2 +qh_setfreelong +qh_sethalfspace +qh_sethalfspace_all +qh_sethyperplane_det +qh_sethyperplane_gauss +qh_setin +qh_setindex +qh_setlarger +qh_setlast +qh_setnew +qh_setnew_delnthsorted +qh_setprint +qh_setreplace +qh_setsize +qh_settemp +qh_settempfree +qh_settempfree_all +qh_settemppop +qh_settemppush +qh_settruncate +qh_setunique +qh_setvoronoi_all +qh_setzero +qh_sharpnewfacets +qh_skipfacet +qh_skipfilename +qh_srand +qh_stddev +qh_strtod +qh_strtol +qh_test_appendmerge +qh_test_vneighbors +qh_tracemerge +qh_tracemerging +qh_triangulate +qh_triangulate_facet +qh_triangulate_link +qh_triangulate_mirror +qh_triangulate_null +qh_updatetested +qh_updatevertices +qh_user_memsizes +qh_version +qh_version2 +qh_vertexintersect +qh_vertexintersect_new +qh_vertexneighbors +qh_vertexridges +qh_vertexridges_facet +qh_vertexsubset +qh_voronoi_center +qh_willdelete +; Mark as DATA, otherwise links a separate qhmem. No __declspec +qhmem DATA +rbox DATA +rbox_inuse DATA diff --git a/xs/src/qhull/src/libqhull/qhull_a.h b/xs/src/qhull/src/libqhull/qhull_a.h new file mode 100644 index 0000000000..729b723276 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qhull_a.h @@ -0,0 +1,150 @@ +/*
      ---------------------------------
    +
    +   qhull_a.h
    +   all header files for compiling qhull with non-reentrant code
    +   included before C++ headers for user_r.h:QHULL_CRTDBG
    +
    +   see qh-qhull.htm
    +
    +   see libqhull.h for user-level definitions
    +
    +   see user.h for user-definable constants
    +
    +   defines internal functions for libqhull.c global.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/qhull_a.h#4 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +
    +   Notes:  grep for ((" and (" to catch fprintf("lkasdjf");
    +           full parens around (x?y:z)
    +           use '#include "libqhull/qhull_a.h"' to avoid name clashes
    +*/
    +
    +#ifndef qhDEFqhulla
    +#define qhDEFqhulla 1
    +
    +#include "libqhull.h"  /* Includes user_r.h and data types */
    +
    +#include "stat.h"
    +#include "random.h"
    +#include "mem.h"
    +#include "qset.h"
    +#include "geom.h"
    +#include "merge.h"
    +#include "poly.h"
    +#include "io.h"
    +
    +#include 
    +#include 
    +#include 
    +#include     /* some compilers will not need float.h */
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +/*** uncomment here and qset.c
    +     if string.h does not define memcpy()
    +#include 
    +*/
    +
    +#if qh_CLOCKtype == 2  /* defined in user.h from libqhull.h */
    +#include 
    +#include 
    +#include 
    +#endif
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4100)  /* unreferenced formal parameter */
    +#pragma warning( disable : 4127)  /* conditional expression is constant */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#pragma warning( disable : 4996)  /* function was declared deprecated(strcpy, localtime, etc.) */
    +#endif
    +
    +/* ======= -macros- =========== */
    +
    +/*----------------------------------
    +
    +  traceN((qh ferr, 0Nnnn, "format\n", vars));
    +    calls qh_fprintf if qh.IStracing >= N
    +
    +    Add debugging traps to the end of qh_fprintf
    +
    +  notes:
    +    removing tracing reduces code size but doesn't change execution speed
    +*/
    +#ifndef qh_NOtrace
    +#define trace0(args) {if (qh IStracing) qh_fprintf args;}
    +#define trace1(args) {if (qh IStracing >= 1) qh_fprintf args;}
    +#define trace2(args) {if (qh IStracing >= 2) qh_fprintf args;}
    +#define trace3(args) {if (qh IStracing >= 3) qh_fprintf args;}
    +#define trace4(args) {if (qh IStracing >= 4) qh_fprintf args;}
    +#define trace5(args) {if (qh IStracing >= 5) qh_fprintf args;}
    +#else /* qh_NOtrace */
    +#define trace0(args) {}
    +#define trace1(args) {}
    +#define trace2(args) {}
    +#define trace3(args) {}
    +#define trace4(args) {}
    +#define trace5(args) {}
    +#endif /* qh_NOtrace */
    +
    +/*----------------------------------
    +
    +  Define an unused variable to avoid compiler warnings
    +
    +  Derived from Qt's corelib/global/qglobal.h
    +
    +*/
    +
    +#if defined(__cplusplus) && defined(__INTEL_COMPILER) && !defined(QHULL_OS_WIN)
    +template 
    +inline void qhullUnused(T &x) { (void)x; }
    +#  define QHULL_UNUSED(x) qhullUnused(x);
    +#else
    +#  define QHULL_UNUSED(x) (void)x;
    +#endif
    +
    +/***** -libqhull.c prototypes (alphabetical after qhull) ********************/
    +
    +void    qh_qhull(void);
    +boolT   qh_addpoint(pointT *furthest, facetT *facet, boolT checkdist);
    +void    qh_buildhull(void);
    +void    qh_buildtracing(pointT *furthest, facetT *facet);
    +void    qh_build_withrestart(void);
    +void    qh_errexit2(int exitcode, facetT *facet, facetT *otherfacet);
    +void    qh_findhorizon(pointT *point, facetT *facet, int *goodvisible,int *goodhorizon);
    +pointT *qh_nextfurthest(facetT **visible);
    +void    qh_partitionall(setT *vertices, pointT *points,int npoints);
    +void    qh_partitioncoplanar(pointT *point, facetT *facet, realT *dist);
    +void    qh_partitionpoint(pointT *point, facetT *facet);
    +void    qh_partitionvisible(boolT allpoints, int *numpoints);
    +void    qh_precision(const char *reason);
    +void    qh_printsummary(FILE *fp);
    +
    +/***** -global.c internal prototypes (alphabetical) ***********************/
    +
    +void    qh_appendprint(qh_PRINT format);
    +void    qh_freebuild(boolT allmem);
    +void    qh_freebuffers(void);
    +void    qh_initbuffers(coordT *points, int numpoints, int dim, boolT ismalloc);
    +
    +/***** -stat.c internal prototypes (alphabetical) ***********************/
    +
    +void    qh_allstatA(void);
    +void    qh_allstatB(void);
    +void    qh_allstatC(void);
    +void    qh_allstatD(void);
    +void    qh_allstatE(void);
    +void    qh_allstatE2(void);
    +void    qh_allstatF(void);
    +void    qh_allstatG(void);
    +void    qh_allstatH(void);
    +void    qh_freebuffers(void);
    +void    qh_initbuffers(coordT *points, int numpoints, int dim, boolT ismalloc);
    +
    +#endif /* qhDEFqhulla */
    diff --git a/xs/src/qhull/src/libqhull/qhull_p-exports.def b/xs/src/qhull/src/libqhull/qhull_p-exports.def
    new file mode 100644
    index 0000000000..cadf8a4fa2
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/qhull_p-exports.def
    @@ -0,0 +1,418 @@
    +; qhull_p-exports.def -- msvc module-definition file
    +;
    +;   Generated from depends.exe by cut-and-paste of exported symbols by mingw gcc
    +;   [mar'11] 399 symbols [jan'15] added 3 symbols
    +;   Annotate as DATA qh_last_random qh_qh qh_qhstat qhmem rbox rbox_inuse
    +;   Annotate as __declspec for outside access in win32 -- qh_qh qh_qhstat
    +;
    +; $Id: //main/2011/qhull/src/libqhull/qhull-exports.def#2 $$Change: 1368 $
    +; $DateTime: 2011/04/16 08:12:32 $$Author: bbarber $
    +;
    +; Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, and qhull-warn.pri
    +VERSION 7.0
    +EXPORTS
    +qh_addhash
    +qh_addpoint
    +qh_all_merges
    +qh_allstatA
    +qh_allstatB
    +qh_allstatC
    +qh_allstatD
    +qh_allstatE
    +qh_allstatE2
    +qh_allstatF
    +qh_allstatG
    +qh_allstatH
    +qh_allstatI
    +qh_allstatistics
    +qh_appendfacet
    +qh_appendmergeset
    +qh_appendprint
    +qh_appendvertex
    +qh_argv_to_command
    +qh_argv_to_command_size
    +qh_attachnewfacets
    +qh_backnormal
    +qh_basevertices
    +qh_build_withrestart
    +qh_buildhull
    +qh_buildtracing
    +qh_check_bestdist
    +qh_check_dupridge
    +qh_check_maxout
    +qh_check_output
    +qh_check_point
    +qh_check_points
    +qh_checkconnect
    +qh_checkconvex
    +qh_checkfacet
    +qh_checkflags
    +qh_checkflipped
    +qh_checkflipped_all
    +qh_checkpolygon
    +qh_checkvertex
    +qh_checkzero
    +qh_clear_outputflags
    +qh_clearcenters
    +qh_clock
    +qh_collectstatistics
    +qh_compare_facetarea
    +qh_compare_facetmerge
    +qh_compare_facetvisit
    +qh_compare_vertexpoint
    +qh_compareangle
    +qh_comparemerge
    +qh_comparevisit
    +qh_copyfilename
    +qh_copynonconvex
    +qh_copypoints
    +qh_countfacets
    +qh_createsimplex
    +qh_crossproduct
    +qh_degen_redundant_facet
    +qh_degen_redundant_neighbors
    +qh_deletevisible
    +qh_delfacet
    +qh_delridge
    +qh_delvertex
    +qh_determinant
    +qh_detjoggle
    +qh_detroundoff
    +qh_detsimplex
    +qh_detvnorm
    +qh_detvridge
    +qh_detvridge3
    +qh_dfacet
    +qh_distnorm
    +qh_distplane
    +qh_distround
    +qh_divzero
    +qh_dvertex
    +qh_eachvoronoi
    +qh_eachvoronoi_all
    +qh_errexit
    +qh_errexit2
    +qh_errexit_rbox
    +qh_errprint
    +qh_exit
    +qh_facet2point
    +qh_facet3vertex
    +qh_facetarea
    +qh_facetarea_simplex
    +qh_facetcenter
    +qh_facetintersect
    +qh_facetvertices
    +qh_find_newvertex
    +qh_findbest
    +qh_findbest_test
    +qh_findbestfacet
    +qh_findbesthorizon
    +qh_findbestlower
    +qh_findbestneighbor
    +qh_findbestnew
    +qh_findfacet_all
    +qh_findgood
    +qh_findgood_all
    +qh_findgooddist
    +qh_findhorizon
    +qh_flippedmerges
    +qh_forcedmerges
    +qh_fprintf
    +qh_fprintf_rbox
    +qh_fprintf_stderr
    +qh_free
    +qh_freebuffers
    +qh_freebuild
    +qh_freeqhull
    +qh_freeqhull2
    +qh_freestatistics
    +qh_furthestnext
    +qh_furthestout
    +qh_gausselim
    +qh_geomplanes
    +qh_getangle
    +qh_getarea
    +qh_getcenter
    +qh_getcentrum
    +qh_getdistance
    +qh_gethash
    +qh_getmergeset
    +qh_getmergeset_initial
    +qh_gram_schmidt
    +qh_hashridge
    +qh_hashridge_find
    +qh_infiniteloop
    +qh_init_A
    +qh_init_B
    +qh_init_qhull_command
    +qh_initbuild
    +qh_initflags
    +qh_initialhull
    +qh_initialvertices
    +qh_initqhull_buffers
    +qh_initqhull_globals
    +qh_initqhull_mem
    +qh_initqhull_outputflags
    +qh_initqhull_start
    +qh_initqhull_start2
    +qh_initstatistics
    +qh_initthresholds
    +qh_inthresholds
    +qh_isvertex
    +qh_joggleinput
    +; Mark as DATA, otherwise links a separate qh_last_random.  No __declspec.
    +qh_last_random      DATA
    +qh_lib_check
    +qh_makenew_nonsimplicial
    +qh_makenew_simplicial
    +qh_makenewfacet
    +qh_makenewfacets
    +qh_makenewplanes
    +qh_makeridges
    +qh_malloc
    +qh_mark_dupridges
    +qh_markkeep
    +qh_markvoronoi
    +qh_matchduplicates
    +qh_matchneighbor
    +qh_matchnewfacets
    +qh_matchvertices
    +qh_maxabsval
    +qh_maxmin
    +qh_maxouter
    +qh_maxsimplex
    +qh_maydropneighbor
    +qh_memalloc
    +qh_memfree
    +qh_memfreeshort
    +qh_meminit
    +qh_meminitbuffers
    +qh_memsetup
    +qh_memsize
    +qh_memstatistics
    +qh_memtotal
    +qh_merge_degenredundant
    +qh_merge_nonconvex
    +qh_mergecycle
    +qh_mergecycle_all
    +qh_mergecycle_facets
    +qh_mergecycle_neighbors
    +qh_mergecycle_ridges
    +qh_mergecycle_vneighbors
    +qh_mergefacet
    +qh_mergefacet2d
    +qh_mergeneighbors
    +qh_mergeridges
    +qh_mergesimplex
    +qh_mergevertex_del
    +qh_mergevertex_neighbors
    +qh_mergevertices
    +qh_minabsval
    +qh_mindiff
    +qh_nearcoplanar
    +qh_nearvertex
    +qh_neighbor_intersections
    +qh_new_qhull
    +qh_newfacet
    +qh_newhashtable
    +qh_newridge
    +qh_newstats
    +qh_newvertex
    +qh_newvertices
    +qh_nextfurthest
    +qh_nextridge3d
    +qh_normalize
    +qh_normalize2
    +qh_nostatistic
    +qh_option
    +qh_order_vertexneighbors
    +qh_orientoutside
    +qh_out1
    +qh_out2n
    +qh_out3n
    +qh_outcoplanar
    +qh_outerinner
    +qh_partitionall
    +qh_partitioncoplanar
    +qh_partitionpoint
    +qh_partitionvisible
    +qh_point
    +qh_point_add
    +qh_pointdist
    +qh_pointfacet
    +qh_pointid
    +qh_pointvertex
    +qh_postmerge
    +qh_precision
    +qh_premerge
    +qh_prepare_output
    +qh_prependfacet
    +qh_printafacet
    +qh_printallstatistics
    +qh_printbegin
    +qh_printcenter
    +qh_printcentrum
    +qh_printend
    +qh_printend4geom
    +qh_printextremes
    +qh_printextremes_2d
    +qh_printextremes_d
    +qh_printfacet
    +qh_printfacet2geom
    +qh_printfacet2geom_points
    +qh_printfacet2math
    +qh_printfacet3geom_nonsimplicial
    +qh_printfacet3geom_points
    +qh_printfacet3geom_simplicial
    +qh_printfacet3math
    +qh_printfacet3vertex
    +qh_printfacet4geom_nonsimplicial
    +qh_printfacet4geom_simplicial
    +qh_printfacetNvertex_nonsimplicial
    +qh_printfacetNvertex_simplicial
    +qh_printfacetheader
    +qh_printfacetlist
    +qh_printfacetridges
    +qh_printfacets
    +qh_printhashtable
    +qh_printhelp_degenerate
    +qh_printhelp_narrowhull
    +qh_printhelp_singular
    +qh_printhyperplaneintersection
    +qh_printline3geom
    +qh_printlists
    +qh_printmatrix
    +qh_printneighborhood
    +qh_printpoint
    +qh_printpoint3
    +qh_printpointid
    +qh_printpoints
    +qh_printpoints_out
    +qh_printpointvect
    +qh_printpointvect2
    +qh_printridge
    +qh_printspheres
    +qh_printstatistics
    +qh_printstatlevel
    +qh_printstats
    +qh_printsummary
    +qh_printvdiagram
    +qh_printvdiagram2
    +qh_printvertex
    +qh_printvertexlist
    +qh_printvertices
    +qh_printvneighbors
    +qh_printvnorm
    +qh_printvoronoi
    +qh_printvridge
    +qh_produce_output
    +qh_produce_output2
    +qh_projectdim3
    +qh_projectinput
    +qh_projectpoint
    +qh_projectpoints
    +; Mark as DATA, otherwise links a separate qh_qh.  qh_qh and qh_qhstat requires __declspec
    +qh_qh               DATA
    +qh_qhstat           DATA
    +qh_qhull
    +qh_rand
    +qh_randomfactor
    +qh_randommatrix
    +qh_rboxpoints
    +qh_readfeasible
    +qh_readpoints
    +qh_reducevertices
    +qh_redundant_vertex
    +qh_remove_extravertices
    +qh_removefacet
    +qh_removevertex
    +qh_rename_sharedvertex
    +qh_renameridgevertex
    +qh_renamevertex
    +qh_resetlists
    +qh_restore_qhull
    +qh_rotateinput
    +qh_rotatepoints
    +qh_roundi
    +qh_save_qhull
    +qh_scaleinput
    +qh_scalelast
    +qh_scalepoints
    +qh_setaddnth
    +qh_setaddsorted
    +qh_setappend
    +qh_setappend2ndlast
    +qh_setappend_set
    +qh_setcheck
    +qh_setcompact
    +qh_setcopy
    +qh_setdel
    +qh_setdelaunay
    +qh_setdellast
    +qh_setdelnth
    +qh_setdelnthsorted
    +qh_setdelsorted
    +qh_setduplicate
    +qh_setequal
    +qh_setequal_except
    +qh_setequal_skip
    +qh_setfacetplane
    +qh_setfeasible
    +qh_setfree
    +qh_setfree2
    +qh_setfreelong
    +qh_sethalfspace
    +qh_sethalfspace_all
    +qh_sethyperplane_det
    +qh_sethyperplane_gauss
    +qh_setin
    +qh_setindex
    +qh_setlarger
    +qh_setlast
    +qh_setnew
    +qh_setnew_delnthsorted
    +qh_setprint
    +qh_setreplace
    +qh_setsize
    +qh_settemp
    +qh_settempfree
    +qh_settempfree_all
    +qh_settemppop
    +qh_settemppush
    +qh_settruncate
    +qh_setunique
    +qh_setvoronoi_all
    +qh_setzero
    +qh_sharpnewfacets
    +qh_skipfacet
    +qh_skipfilename
    +qh_srand
    +qh_stddev
    +qh_strtod
    +qh_strtol
    +qh_test_appendmerge
    +qh_test_vneighbors
    +qh_tracemerge
    +qh_tracemerging
    +qh_triangulate
    +qh_triangulate_facet
    +qh_triangulate_link
    +qh_triangulate_mirror
    +qh_triangulate_null
    +qh_updatetested
    +qh_updatevertices
    +qh_user_memsizes
    +qh_version
    +qh_version2
    +qh_vertexintersect
    +qh_vertexintersect_new
    +qh_vertexneighbors
    +qh_vertexridges
    +qh_vertexridges_facet
    +qh_vertexsubset
    +qh_voronoi_center
    +qh_willdelete
    +; Mark as DATA, otherwise links a separate qhmem.  No __declspec
    +qhmem                   DATA
    +rbox			DATA
    +rbox_inuse              DATA
    diff --git a/xs/src/qhull/src/libqhull/qset.c b/xs/src/qhull/src/libqhull/qset.c
    new file mode 100644
    index 0000000000..a969252a75
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/qset.c
    @@ -0,0 +1,1340 @@
    +/*
      ---------------------------------
    +
    +   qset.c
    +   implements set manipulations needed for quickhull
    +
    +   see qh-set.htm and qset.h
    +
    +   Be careful of strict aliasing (two pointers of different types
    +   that reference the same location).  The last slot of a set is
    +   either the actual size of the set plus 1, or the NULL terminator
    +   of the set (i.e., setelemT).
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/qset.c#3 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "user.h" /* for QHULL_CRTDBG */
    +#include "qset.h"
    +#include "mem.h"
    +#include 
    +#include 
    +/*** uncomment here and qhull_a.h
    +     if string.h does not define memcpy()
    +#include 
    +*/
    +
    +#ifndef qhDEFlibqhull
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +void    qh_errexit(int exitcode, facetT *, ridgeT *);
    +void    qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... );
    +#  ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#  pragma warning( disable : 4127)  /* conditional expression is constant */
    +#  pragma warning( disable : 4706)  /* assignment within conditional function */
    +#  endif
    +#endif
    +
    +/*=============== internal macros ===========================*/
    +
    +/*============ functions in alphabetical order ===================*/
    +
    +/*----------------------------------
    +
    +  qh_setaddnth( setp, nth, newelem)
    +    adds newelem as n'th element of sorted or unsorted *setp
    +
    +  notes:
    +    *setp and newelem must be defined
    +    *setp may be a temp set
    +    nth=0 is first element
    +    errors if nth is out of bounds
    +
    +  design:
    +    expand *setp if empty or full
    +    move tail of *setp up one
    +    insert newelem
    +*/
    +void qh_setaddnth(setT **setp, int nth, void *newelem) {
    +  int oldsize, i;
    +  setelemT *sizep;          /* avoid strict aliasing */
    +  setelemT *oldp, *newp;
    +
    +  if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +    qh_setlarger(setp);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  oldsize= sizep->i - 1;
    +  if (nth < 0 || nth > oldsize) {
    +    qh_fprintf(qhmem.ferr, 6171, "qhull internal error (qh_setaddnth): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qhmem.ferr, "", *setp);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  sizep->i++;
    +  oldp= (setelemT *)SETelemaddr_(*setp, oldsize, void);   /* NULL */
    +  newp= oldp+1;
    +  for (i=oldsize-nth+1; i--; )  /* move at least NULL  */
    +    (newp--)->p= (oldp--)->p;       /* may overwrite *sizep */
    +  newp->p= newelem;
    +} /* setaddnth */
    +
    +
    +/*----------------------------------
    +
    +  setaddsorted( setp, newelem )
    +    adds an newelem into sorted *setp
    +
    +  notes:
    +    *setp and newelem must be defined
    +    *setp may be a temp set
    +    nop if newelem already in set
    +
    +  design:
    +    find newelem's position in *setp
    +    insert newelem
    +*/
    +void qh_setaddsorted(setT **setp, void *newelem) {
    +  int newindex=0;
    +  void *elem, **elemp;
    +
    +  FOREACHelem_(*setp) {          /* could use binary search instead */
    +    if (elem < newelem)
    +      newindex++;
    +    else if (elem == newelem)
    +      return;
    +    else
    +      break;
    +  }
    +  qh_setaddnth(setp, newindex, newelem);
    +} /* setaddsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setappend( setp, newelem)
    +    append newelem to *setp
    +
    +  notes:
    +    *setp may be a temp set
    +    *setp and newelem may be NULL
    +
    +  design:
    +    expand *setp if empty or full
    +    append newelem to *setp
    +
    +*/
    +void qh_setappend(setT **setp, void *newelem) {
    +  setelemT *sizep;  /* Avoid strict aliasing.  Writing to *endp may overwrite *sizep */
    +  setelemT *endp;
    +  int count;
    +
    +  if (!newelem)
    +    return;
    +  if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +    qh_setlarger(setp);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  count= (sizep->i)++ - 1;
    +  endp= (setelemT *)SETelemaddr_(*setp, count, void);
    +  (endp++)->p= newelem;
    +  endp->p= NULL;
    +} /* setappend */
    +
    +/*---------------------------------
    +
    +  qh_setappend_set( setp, setA)
    +    appends setA to *setp
    +
    +  notes:
    +    *setp can not be a temp set
    +    *setp and setA may be NULL
    +
    +  design:
    +    setup for copy
    +    expand *setp if it is too small
    +    append all elements of setA to *setp
    +*/
    +void qh_setappend_set(setT **setp, setT *setA) {
    +  int sizeA, size;
    +  setT *oldset;
    +  setelemT *sizep;
    +
    +  if (!setA)
    +    return;
    +  SETreturnsize_(setA, sizeA);
    +  if (!*setp)
    +    *setp= qh_setnew(sizeA);
    +  sizep= SETsizeaddr_(*setp);
    +  if (!(size= sizep->i))
    +    size= (*setp)->maxsize;
    +  else
    +    size--;
    +  if (size + sizeA > (*setp)->maxsize) {
    +    oldset= *setp;
    +    *setp= qh_setcopy(oldset, sizeA);
    +    qh_setfree(&oldset);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  if (sizeA > 0) {
    +    sizep->i= size+sizeA+1;   /* memcpy may overwrite */
    +    memcpy((char *)&((*setp)->e[size].p), (char *)&(setA->e[0].p), (size_t)(sizeA+1) * SETelemsize);
    +  }
    +} /* setappend_set */
    +
    +
    +/*---------------------------------
    +
    +  qh_setappend2ndlast( setp, newelem )
    +    makes newelem the next to the last element in *setp
    +
    +  notes:
    +    *setp must have at least one element
    +    newelem must be defined
    +    *setp may be a temp set
    +
    +  design:
    +    expand *setp if empty or full
    +    move last element of *setp up one
    +    insert newelem
    +*/
    +void qh_setappend2ndlast(setT **setp, void *newelem) {
    +    setelemT *sizep;  /* Avoid strict aliasing.  Writing to *endp may overwrite *sizep */
    +    setelemT *endp, *lastp;
    +    int count;
    +
    +    if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +        qh_setlarger(setp);
    +        sizep= SETsizeaddr_(*setp);
    +    }
    +    count= (sizep->i)++ - 1;
    +    endp= (setelemT *)SETelemaddr_(*setp, count, void); /* NULL */
    +    lastp= endp-1;
    +    *(endp++)= *lastp;
    +    endp->p= NULL;    /* may overwrite *sizep */
    +    lastp->p= newelem;
    +} /* setappend2ndlast */
    +
    +/*---------------------------------
    +
    +  qh_setcheck( set, typename, id )
    +    check set for validity
    +    report errors with typename and id
    +
    +  design:
    +    checks that maxsize, actual size, and NULL terminator agree
    +*/
    +void qh_setcheck(setT *set, const char *tname, unsigned id) {
    +  int maxsize, size;
    +  int waserr= 0;
    +
    +  if (!set)
    +    return;
    +  SETreturnsize_(set, size);
    +  maxsize= set->maxsize;
    +  if (size > maxsize || !maxsize) {
    +    qh_fprintf(qhmem.ferr, 6172, "qhull internal error (qh_setcheck): actual size %d of %s%d is greater than max size %d\n",
    +             size, tname, id, maxsize);
    +    waserr= 1;
    +  }else if (set->e[size].p) {
    +    qh_fprintf(qhmem.ferr, 6173, "qhull internal error (qh_setcheck): %s%d(size %d max %d) is not null terminated.\n",
    +             tname, id, size-1, maxsize);
    +    waserr= 1;
    +  }
    +  if (waserr) {
    +    qh_setprint(qhmem.ferr, "ERRONEOUS", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +} /* setcheck */
    +
    +
    +/*---------------------------------
    +
    +  qh_setcompact( set )
    +    remove internal NULLs from an unsorted set
    +
    +  returns:
    +    updated set
    +
    +  notes:
    +    set may be NULL
    +    it would be faster to swap tail of set into holes, like qh_setdel
    +
    +  design:
    +    setup pointers into set
    +    skip NULLs while copying elements to start of set
    +    update the actual size
    +*/
    +void qh_setcompact(setT *set) {
    +  int size;
    +  void **destp, **elemp, **endp, **firstp;
    +
    +  if (!set)
    +    return;
    +  SETreturnsize_(set, size);
    +  destp= elemp= firstp= SETaddr_(set, void);
    +  endp= destp + size;
    +  while (1) {
    +    if (!(*destp++ = *elemp++)) {
    +      destp--;
    +      if (elemp > endp)
    +        break;
    +    }
    +  }
    +  qh_settruncate(set, (int)(destp-firstp));   /* WARN64 */
    +} /* setcompact */
    +
    +
    +/*---------------------------------
    +
    +  qh_setcopy( set, extra )
    +    make a copy of a sorted or unsorted set with extra slots
    +
    +  returns:
    +    new set
    +
    +  design:
    +    create a newset with extra slots
    +    copy the elements to the newset
    +
    +*/
    +setT *qh_setcopy(setT *set, int extra) {
    +  setT *newset;
    +  int size;
    +
    +  if (extra < 0)
    +    extra= 0;
    +  SETreturnsize_(set, size);
    +  newset= qh_setnew(size+extra);
    +  SETsizeaddr_(newset)->i= size+1;    /* memcpy may overwrite */
    +  memcpy((char *)&(newset->e[0].p), (char *)&(set->e[0].p), (size_t)(size+1) * SETelemsize);
    +  return(newset);
    +} /* setcopy */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdel( set, oldelem )
    +    delete oldelem from an unsorted set
    +
    +  returns:
    +    returns oldelem if found
    +    returns NULL otherwise
    +
    +  notes:
    +    set may be NULL
    +    oldelem must not be NULL;
    +    only deletes one copy of oldelem in set
    +
    +  design:
    +    locate oldelem
    +    update actual size if it was full
    +    move the last element to the oldelem's location
    +*/
    +void *qh_setdel(setT *set, void *oldelem) {
    +  setelemT *sizep;
    +  setelemT *elemp;
    +  setelemT *lastp;
    +
    +  if (!set)
    +    return NULL;
    +  elemp= (setelemT *)SETaddr_(set, void);
    +  while (elemp->p != oldelem && elemp->p)
    +    elemp++;
    +  if (elemp->p) {
    +    sizep= SETsizeaddr_(set);
    +    if (!(sizep->i)--)         /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (maxsize-1)+ 1 */
    +    lastp= (setelemT *)SETelemaddr_(set, sizep->i-1, void);
    +    elemp->p= lastp->p;      /* may overwrite itself */
    +    lastp->p= NULL;
    +    return oldelem;
    +  }
    +  return NULL;
    +} /* setdel */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdellast( set)
    +    return last element of set or NULL
    +
    +  notes:
    +    deletes element from set
    +    set may be NULL
    +
    +  design:
    +    return NULL if empty
    +    if full set
    +      delete last element and set actual size
    +    else
    +      delete last element and update actual size
    +*/
    +void *qh_setdellast(setT *set) {
    +  int setsize;  /* actually, actual_size + 1 */
    +  int maxsize;
    +  setelemT *sizep;
    +  void *returnvalue;
    +
    +  if (!set || !(set->e[0].p))
    +    return NULL;
    +  sizep= SETsizeaddr_(set);
    +  if ((setsize= sizep->i)) {
    +    returnvalue= set->e[setsize - 2].p;
    +    set->e[setsize - 2].p= NULL;
    +    sizep->i--;
    +  }else {
    +    maxsize= set->maxsize;
    +    returnvalue= set->e[maxsize - 1].p;
    +    set->e[maxsize - 1].p= NULL;
    +    sizep->i= maxsize;
    +  }
    +  return returnvalue;
    +} /* setdellast */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelnth( set, nth )
    +    deletes nth element from unsorted set
    +    0 is first element
    +
    +  returns:
    +    returns the element (needs type conversion)
    +
    +  notes:
    +    errors if nth invalid
    +
    +  design:
    +    setup points and check nth
    +    delete nth element and overwrite with last element
    +*/
    +void *qh_setdelnth(setT *set, int nth) {
    +  void *elem;
    +  setelemT *sizep;
    +  setelemT *elemp, *lastp;
    +
    +  sizep= SETsizeaddr_(set);
    +  if ((sizep->i--)==0)         /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (maxsize-1)+ 1 */
    +  if (nth < 0 || nth >= sizep->i) {
    +    qh_fprintf(qhmem.ferr, 6174, "qhull internal error (qh_setdelnth): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  elemp= (setelemT *)SETelemaddr_(set, nth, void); /* nth valid by QH6174 */
    +  lastp= (setelemT *)SETelemaddr_(set, sizep->i-1, void);
    +  elem= elemp->p;
    +  elemp->p= lastp->p;      /* may overwrite itself */
    +  lastp->p= NULL;
    +  return elem;
    +} /* setdelnth */
    +
    +/*---------------------------------
    +
    +  qh_setdelnthsorted( set, nth )
    +    deletes nth element from sorted set
    +
    +  returns:
    +    returns the element (use type conversion)
    +
    +  notes:
    +    errors if nth invalid
    +
    +  see also:
    +    setnew_delnthsorted
    +
    +  design:
    +    setup points and check nth
    +    copy remaining elements down one
    +    update actual size
    +*/
    +void *qh_setdelnthsorted(setT *set, int nth) {
    +  void *elem;
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  sizep= SETsizeaddr_(set);
    +  if (nth < 0 || (sizep->i && nth >= sizep->i-1) || nth >= set->maxsize) {
    +    qh_fprintf(qhmem.ferr, 6175, "qhull internal error (qh_setdelnthsorted): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  newp= (setelemT *)SETelemaddr_(set, nth, void);
    +  elem= newp->p;
    +  oldp= newp+1;
    +  while (((newp++)->p= (oldp++)->p))
    +    ; /* copy remaining elements and NULL */
    +  if ((sizep->i--)==0)         /*  if was a full set */
    +    sizep->i= set->maxsize;  /*     *sizep= (max size-1)+ 1 */
    +  return elem;
    +} /* setdelnthsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelsorted( set, oldelem )
    +    deletes oldelem from sorted set
    +
    +  returns:
    +    returns oldelem if it was deleted
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    locate oldelem in set
    +    copy remaining elements down one
    +    update actual size
    +*/
    +void *qh_setdelsorted(setT *set, void *oldelem) {
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  if (!set)
    +    return NULL;
    +  newp= (setelemT *)SETaddr_(set, void);
    +  while(newp->p != oldelem && newp->p)
    +    newp++;
    +  if (newp->p) {
    +    oldp= newp+1;
    +    while (((newp++)->p= (oldp++)->p))
    +      ; /* copy remaining elements */
    +    sizep= SETsizeaddr_(set);
    +    if ((sizep->i--)==0)    /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (max size-1)+ 1 */
    +    return oldelem;
    +  }
    +  return NULL;
    +} /* setdelsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setduplicate( set, elemsize )
    +    duplicate a set of elemsize elements
    +
    +  notes:
    +    use setcopy if retaining old elements
    +
    +  design:
    +    create a new set
    +    for each elem of the old set
    +      create a newelem
    +      append newelem to newset
    +*/
    +setT *qh_setduplicate(setT *set, int elemsize) {
    +  void          *elem, **elemp, *newElem;
    +  setT          *newSet;
    +  int           size;
    +
    +  if (!(size= qh_setsize(set)))
    +    return NULL;
    +  newSet= qh_setnew(size);
    +  FOREACHelem_(set) {
    +    newElem= qh_memalloc(elemsize);
    +    memcpy(newElem, elem, (size_t)elemsize);
    +    qh_setappend(&newSet, newElem);
    +  }
    +  return newSet;
    +} /* setduplicate */
    +
    +
    +/*---------------------------------
    +
    +  qh_setendpointer( set )
    +    Returns pointer to NULL terminator of a set's elements
    +    set can not be NULL
    +
    +*/
    +void **qh_setendpointer(setT *set) {
    +
    +  setelemT *sizep= SETsizeaddr_(set);
    +  int n= sizep->i;
    +  return (n ? &set->e[n-1].p : &sizep->p);
    +} /* qh_setendpointer */
    +
    +/*---------------------------------
    +
    +  qh_setequal( setA, setB )
    +    returns 1 if two sorted sets are equal, otherwise returns 0
    +
    +  notes:
    +    either set may be NULL
    +
    +  design:
    +    check size of each set
    +    setup pointers
    +    compare elements of each set
    +*/
    +int qh_setequal(setT *setA, setT *setB) {
    +  void **elemAp, **elemBp;
    +  int sizeA= 0, sizeB= 0;
    +
    +  if (setA) {
    +    SETreturnsize_(setA, sizeA);
    +  }
    +  if (setB) {
    +    SETreturnsize_(setB, sizeB);
    +  }
    +  if (sizeA != sizeB)
    +    return 0;
    +  if (!sizeA)
    +    return 1;
    +  elemAp= SETaddr_(setA, void);
    +  elemBp= SETaddr_(setB, void);
    +  if (!memcmp((char *)elemAp, (char *)elemBp, sizeA*SETelemsize))
    +    return 1;
    +  return 0;
    +} /* setequal */
    +
    +
    +/*---------------------------------
    +
    +  qh_setequal_except( setA, skipelemA, setB, skipelemB )
    +    returns 1 if sorted setA and setB are equal except for skipelemA & B
    +
    +  returns:
    +    false if either skipelemA or skipelemB are missing
    +
    +  notes:
    +    neither set may be NULL
    +
    +    if skipelemB is NULL,
    +      can skip any one element of setB
    +
    +  design:
    +    setup pointers
    +    search for skipelemA, skipelemB, and mismatches
    +    check results
    +*/
    +int qh_setequal_except(setT *setA, void *skipelemA, setT *setB, void *skipelemB) {
    +  void **elemA, **elemB;
    +  int skip=0;
    +
    +  elemA= SETaddr_(setA, void);
    +  elemB= SETaddr_(setB, void);
    +  while (1) {
    +    if (*elemA == skipelemA) {
    +      skip++;
    +      elemA++;
    +    }
    +    if (skipelemB) {
    +      if (*elemB == skipelemB) {
    +        skip++;
    +        elemB++;
    +      }
    +    }else if (*elemA != *elemB) {
    +      skip++;
    +      if (!(skipelemB= *elemB++))
    +        return 0;
    +    }
    +    if (!*elemA)
    +      break;
    +    if (*elemA++ != *elemB++)
    +      return 0;
    +  }
    +  if (skip != 2 || *elemB)
    +    return 0;
    +  return 1;
    +} /* setequal_except */
    +
    +
    +/*---------------------------------
    +
    +  qh_setequal_skip( setA, skipA, setB, skipB )
    +    returns 1 if sorted setA and setB are equal except for elements skipA & B
    +
    +  returns:
    +    false if different size
    +
    +  notes:
    +    neither set may be NULL
    +
    +  design:
    +    setup pointers
    +    search for mismatches while skipping skipA and skipB
    +*/
    +int qh_setequal_skip(setT *setA, int skipA, setT *setB, int skipB) {
    +  void **elemA, **elemB, **skipAp, **skipBp;
    +
    +  elemA= SETaddr_(setA, void);
    +  elemB= SETaddr_(setB, void);
    +  skipAp= SETelemaddr_(setA, skipA, void);
    +  skipBp= SETelemaddr_(setB, skipB, void);
    +  while (1) {
    +    if (elemA == skipAp)
    +      elemA++;
    +    if (elemB == skipBp)
    +      elemB++;
    +    if (!*elemA)
    +      break;
    +    if (*elemA++ != *elemB++)
    +      return 0;
    +  }
    +  if (*elemB)
    +    return 0;
    +  return 1;
    +} /* setequal_skip */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfree( setp )
    +    frees the space occupied by a sorted or unsorted set
    +
    +  returns:
    +    sets setp to NULL
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    free array
    +    free set
    +*/
    +void qh_setfree(setT **setp) {
    +  int size;
    +  void **freelistp;  /* used if !qh_NOmem by qh_memfree_() */
    +
    +  if (*setp) {
    +    size= sizeof(setT) + ((*setp)->maxsize)*SETelemsize;
    +    if (size <= qhmem.LASTsize) {
    +      qh_memfree_(*setp, size, freelistp);
    +    }else
    +      qh_memfree(*setp, size);
    +    *setp= NULL;
    +  }
    +} /* setfree */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfree2( setp, elemsize )
    +    frees the space occupied by a set and its elements
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    free each element
    +    free set
    +*/
    +void qh_setfree2(setT **setp, int elemsize) {
    +  void          *elem, **elemp;
    +
    +  FOREACHelem_(*setp)
    +    qh_memfree(elem, elemsize);
    +  qh_setfree(setp);
    +} /* setfree2 */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_setfreelong( setp )
    +    frees a set only if it's in long memory
    +
    +  returns:
    +    sets setp to NULL if it is freed
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    if set is large
    +      free it
    +*/
    +void qh_setfreelong(setT **setp) {
    +  int size;
    +
    +  if (*setp) {
    +    size= sizeof(setT) + ((*setp)->maxsize)*SETelemsize;
    +    if (size > qhmem.LASTsize) {
    +      qh_memfree(*setp, size);
    +      *setp= NULL;
    +    }
    +  }
    +} /* setfreelong */
    +
    +
    +/*---------------------------------
    +
    +  qh_setin( set, setelem )
    +    returns 1 if setelem is in a set, 0 otherwise
    +
    +  notes:
    +    set may be NULL or unsorted
    +
    +  design:
    +    scans set for setelem
    +*/
    +int qh_setin(setT *set, void *setelem) {
    +  void *elem, **elemp;
    +
    +  FOREACHelem_(set) {
    +    if (elem == setelem)
    +      return 1;
    +  }
    +  return 0;
    +} /* setin */
    +
    +
    +/*---------------------------------
    +
    +  qh_setindex( set, atelem )
    +    returns the index of atelem in set.
    +    returns -1, if not in set or maxsize wrong
    +
    +  notes:
    +    set may be NULL and may contain nulls.
    +    NOerrors returned (qh_pointid, QhullPoint::id)
    +
    +  design:
    +    checks maxsize
    +    scans set for atelem
    +*/
    +int qh_setindex(setT *set, void *atelem) {
    +  void **elem;
    +  int size, i;
    +
    +  if (!set)
    +    return -1;
    +  SETreturnsize_(set, size);
    +  if (size > set->maxsize)
    +    return -1;
    +  elem= SETaddr_(set, void);
    +  for (i=0; i < size; i++) {
    +    if (*elem++ == atelem)
    +      return i;
    +  }
    +  return -1;
    +} /* setindex */
    +
    +
    +/*---------------------------------
    +
    +  qh_setlarger( oldsetp )
    +    returns a larger set that contains all elements of *oldsetp
    +
    +  notes:
    +    the set is at least twice as large
    +    if temp set, updates qhmem.tempstack
    +
    +  design:
    +    creates a new set
    +    copies the old set to the new set
    +    updates pointers in tempstack
    +    deletes the old set
    +*/
    +void qh_setlarger(setT **oldsetp) {
    +  int size= 1;
    +  setT *newset, *set, **setp, *oldset;
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  if (*oldsetp) {
    +    oldset= *oldsetp;
    +    SETreturnsize_(oldset, size);
    +    qhmem.cntlarger++;
    +    qhmem.totlarger += size+1;
    +    newset= qh_setnew(2 * size);
    +    oldp= (setelemT *)SETaddr_(oldset, void);
    +    newp= (setelemT *)SETaddr_(newset, void);
    +    memcpy((char *)newp, (char *)oldp, (size_t)(size+1) * SETelemsize);
    +    sizep= SETsizeaddr_(newset);
    +    sizep->i= size+1;
    +    FOREACHset_((setT *)qhmem.tempstack) {
    +      if (set == oldset)
    +        *(setp-1)= newset;
    +    }
    +    qh_setfree(oldsetp);
    +  }else
    +    newset= qh_setnew(3);
    +  *oldsetp= newset;
    +} /* setlarger */
    +
    +
    +/*---------------------------------
    +
    +  qh_setlast( set )
    +    return last element of set or NULL (use type conversion)
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    return last element
    +*/
    +void *qh_setlast(setT *set) {
    +  int size;
    +
    +  if (set) {
    +    size= SETsizeaddr_(set)->i;
    +    if (!size)
    +      return SETelem_(set, set->maxsize - 1);
    +    else if (size > 1)
    +      return SETelem_(set, size - 2);
    +  }
    +  return NULL;
    +} /* setlast */
    +
    +
    +/*---------------------------------
    +
    +  qh_setnew( setsize )
    +    creates and allocates space for a set
    +
    +  notes:
    +    setsize means the number of elements (!including the NULL terminator)
    +    use qh_settemp/qh_setfreetemp if set is temporary
    +
    +  design:
    +    allocate memory for set
    +    roundup memory if small set
    +    initialize as empty set
    +*/
    +setT *qh_setnew(int setsize) {
    +  setT *set;
    +  int sizereceived; /* used if !qh_NOmem */
    +  int size;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  if (!setsize)
    +    setsize++;
    +  size= sizeof(setT) + setsize * SETelemsize;
    +  if (size>0 && size <= qhmem.LASTsize) {
    +    qh_memalloc_(size, freelistp, set, setT);
    +#ifndef qh_NOmem
    +    sizereceived= qhmem.sizetable[ qhmem.indextable[size]];
    +    if (sizereceived > size)
    +      setsize += (sizereceived - size)/SETelemsize;
    +#endif
    +  }else
    +    set= (setT*)qh_memalloc(size);
    +  set->maxsize= setsize;
    +  set->e[setsize].i= 1;
    +  set->e[0].p= NULL;
    +  return(set);
    +} /* setnew */
    +
    +
    +/*---------------------------------
    +
    +  qh_setnew_delnthsorted( set, size, nth, prepend )
    +    creates a sorted set not containing nth element
    +    if prepend, the first prepend elements are undefined
    +
    +  notes:
    +    set must be defined
    +    checks nth
    +    see also: setdelnthsorted
    +
    +  design:
    +    create new set
    +    setup pointers and allocate room for prepend'ed entries
    +    append head of old set to new set
    +    append tail of old set to new set
    +*/
    +setT *qh_setnew_delnthsorted(setT *set, int size, int nth, int prepend) {
    +  setT *newset;
    +  void **oldp, **newp;
    +  int tailsize= size - nth -1, newsize;
    +
    +  if (tailsize < 0) {
    +    qh_fprintf(qhmem.ferr, 6176, "qhull internal error (qh_setnew_delnthsorted): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  newsize= size-1 + prepend;
    +  newset= qh_setnew(newsize);
    +  newset->e[newset->maxsize].i= newsize+1;  /* may be overwritten */
    +  oldp= SETaddr_(set, void);
    +  newp= SETaddr_(newset, void) + prepend;
    +  switch (nth) {
    +  case 0:
    +    break;
    +  case 1:
    +    *(newp++)= *oldp++;
    +    break;
    +  case 2:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 3:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 4:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  default:
    +    memcpy((char *)newp, (char *)oldp, (size_t)nth * SETelemsize);
    +    newp += nth;
    +    oldp += nth;
    +    break;
    +  }
    +  oldp++;
    +  switch (tailsize) {
    +  case 0:
    +    break;
    +  case 1:
    +    *(newp++)= *oldp++;
    +    break;
    +  case 2:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 3:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 4:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  default:
    +    memcpy((char *)newp, (char *)oldp, (size_t)tailsize * SETelemsize);
    +    newp += tailsize;
    +  }
    +  *newp= NULL;
    +  return(newset);
    +} /* setnew_delnthsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setprint( fp, string, set )
    +    print set elements to fp with identifying string
    +
    +  notes:
    +    never errors
    +*/
    +void qh_setprint(FILE *fp, const char* string, setT *set) {
    +  int size, k;
    +
    +  if (!set)
    +    qh_fprintf(fp, 9346, "%s set is null\n", string);
    +  else {
    +    SETreturnsize_(set, size);
    +    qh_fprintf(fp, 9347, "%s set=%p maxsize=%d size=%d elems=",
    +             string, set, set->maxsize, size);
    +    if (size > set->maxsize)
    +      size= set->maxsize+1;
    +    for (k=0; k < size; k++)
    +      qh_fprintf(fp, 9348, " %p", set->e[k].p);
    +    qh_fprintf(fp, 9349, "\n");
    +  }
    +} /* setprint */
    +
    +/*---------------------------------
    +
    +  qh_setreplace( set, oldelem, newelem )
    +    replaces oldelem in set with newelem
    +
    +  notes:
    +    errors if oldelem not in the set
    +    newelem may be NULL, but it turns the set into an indexed set (no FOREACH)
    +
    +  design:
    +    find oldelem
    +    replace with newelem
    +*/
    +void qh_setreplace(setT *set, void *oldelem, void *newelem) {
    +  void **elemp;
    +
    +  elemp= SETaddr_(set, void);
    +  while (*elemp != oldelem && *elemp)
    +    elemp++;
    +  if (*elemp)
    +    *elemp= newelem;
    +  else {
    +    qh_fprintf(qhmem.ferr, 6177, "qhull internal error (qh_setreplace): elem %p not found in set\n",
    +       oldelem);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +} /* setreplace */
    +
    +
    +/*---------------------------------
    +
    +  qh_setsize( set )
    +    returns the size of a set
    +
    +  notes:
    +    errors if set's maxsize is incorrect
    +    same as SETreturnsize_(set)
    +    same code for qh_setsize [qset.c] and QhullSetBase::count
    +
    +  design:
    +    determine actual size of set from maxsize
    +*/
    +int qh_setsize(setT *set) {
    +  int size;
    +  setelemT *sizep;
    +
    +  if (!set)
    +    return(0);
    +  sizep= SETsizeaddr_(set);
    +  if ((size= sizep->i)) {
    +    size--;
    +    if (size > set->maxsize) {
    +      qh_fprintf(qhmem.ferr, 6178, "qhull internal error (qh_setsize): current set size %d is greater than maximum size %d\n",
    +               size, set->maxsize);
    +      qh_setprint(qhmem.ferr, "set: ", set);
    +      qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +    }
    +  }else
    +    size= set->maxsize;
    +  return size;
    +} /* setsize */
    +
    +/*---------------------------------
    +
    +  qh_settemp( setsize )
    +    return a stacked, temporary set of upto setsize elements
    +
    +  notes:
    +    use settempfree or settempfree_all to release from qhmem.tempstack
    +    see also qh_setnew
    +
    +  design:
    +    allocate set
    +    append to qhmem.tempstack
    +
    +*/
    +setT *qh_settemp(int setsize) {
    +  setT *newset;
    +
    +  newset= qh_setnew(setsize);
    +  qh_setappend(&qhmem.tempstack, newset);
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8123, "qh_settemp: temp set %p of %d elements, depth %d\n",
    +       newset, newset->maxsize, qh_setsize(qhmem.tempstack));
    +  return newset;
    +} /* settemp */
    +
    +/*---------------------------------
    +
    +  qh_settempfree( set )
    +    free temporary set at top of qhmem.tempstack
    +
    +  notes:
    +    nop if set is NULL
    +    errors if set not from previous   qh_settemp
    +
    +  to locate errors:
    +    use 'T2' to find source and then find mis-matching qh_settemp
    +
    +  design:
    +    check top of qhmem.tempstack
    +    free it
    +*/
    +void qh_settempfree(setT **set) {
    +  setT *stackedset;
    +
    +  if (!*set)
    +    return;
    +  stackedset= qh_settemppop();
    +  if (stackedset != *set) {
    +    qh_settemppush(stackedset);
    +    qh_fprintf(qhmem.ferr, 6179, "qhull internal error (qh_settempfree): set %p(size %d) was not last temporary allocated(depth %d, set %p, size %d)\n",
    +             *set, qh_setsize(*set), qh_setsize(qhmem.tempstack)+1,
    +             stackedset, qh_setsize(stackedset));
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh_setfree(set);
    +} /* settempfree */
    +
    +/*---------------------------------
    +
    +  qh_settempfree_all(  )
    +    free all temporary sets in qhmem.tempstack
    +
    +  design:
    +    for each set in tempstack
    +      free set
    +    free qhmem.tempstack
    +*/
    +void qh_settempfree_all(void) {
    +  setT *set, **setp;
    +
    +  FOREACHset_(qhmem.tempstack)
    +    qh_setfree(&set);
    +  qh_setfree(&qhmem.tempstack);
    +} /* settempfree_all */
    +
    +/*---------------------------------
    +
    +  qh_settemppop(  )
    +    pop and return temporary set from qhmem.tempstack
    +
    +  notes:
    +    the returned set is permanent
    +
    +  design:
    +    pop and check top of qhmem.tempstack
    +*/
    +setT *qh_settemppop(void) {
    +  setT *stackedset;
    +
    +  stackedset= (setT*)qh_setdellast(qhmem.tempstack);
    +  if (!stackedset) {
    +    qh_fprintf(qhmem.ferr, 6180, "qhull internal error (qh_settemppop): pop from empty temporary stack\n");
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8124, "qh_settemppop: depth %d temp set %p of %d elements\n",
    +       qh_setsize(qhmem.tempstack)+1, stackedset, qh_setsize(stackedset));
    +  return stackedset;
    +} /* settemppop */
    +
    +/*---------------------------------
    +
    +  qh_settemppush( set )
    +    push temporary set unto qhmem.tempstack (makes it temporary)
    +
    +  notes:
    +    duplicates settemp() for tracing
    +
    +  design:
    +    append set to tempstack
    +*/
    +void qh_settemppush(setT *set) {
    +  if (!set) {
    +    qh_fprintf(qhmem.ferr, 6267, "qhull error (qh_settemppush): can not push a NULL temp\n");
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh_setappend(&qhmem.tempstack, set);
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8125, "qh_settemppush: depth %d temp set %p of %d elements\n",
    +      qh_setsize(qhmem.tempstack), set, qh_setsize(set));
    +} /* settemppush */
    +
    +
    +/*---------------------------------
    +
    +  qh_settruncate( set, size )
    +    truncate set to size elements
    +
    +  notes:
    +    set must be defined
    +
    +  see:
    +    SETtruncate_
    +
    +  design:
    +    check size
    +    update actual size of set
    +*/
    +void qh_settruncate(setT *set, int size) {
    +
    +  if (size < 0 || size > set->maxsize) {
    +    qh_fprintf(qhmem.ferr, 6181, "qhull internal error (qh_settruncate): size %d out of bounds for set:\n", size);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  set->e[set->maxsize].i= size+1;   /* maybe overwritten */
    +  set->e[size].p= NULL;
    +} /* settruncate */
    +
    +/*---------------------------------
    +
    +  qh_setunique( set, elem )
    +    add elem to unsorted set unless it is already in set
    +
    +  notes:
    +    returns 1 if it is appended
    +
    +  design:
    +    if elem not in set
    +      append elem to set
    +*/
    +int qh_setunique(setT **set, void *elem) {
    +
    +  if (!qh_setin(*set, elem)) {
    +    qh_setappend(set, elem);
    +    return 1;
    +  }
    +  return 0;
    +} /* setunique */
    +
    +/*---------------------------------
    +
    +  qh_setzero( set, index, size )
    +    zero elements from index on
    +    set actual size of set to size
    +
    +  notes:
    +    set must be defined
    +    the set becomes an indexed set (can not use FOREACH...)
    +
    +  see also:
    +    qh_settruncate
    +
    +  design:
    +    check index and size
    +    update actual size
    +    zero elements starting at e[index]
    +*/
    +void qh_setzero(setT *set, int idx, int size) {
    +  int count;
    +
    +  if (idx < 0 || idx >= size || size > set->maxsize) {
    +    qh_fprintf(qhmem.ferr, 6182, "qhull internal error (qh_setzero): index %d or size %d out of bounds for set:\n", idx, size);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  set->e[set->maxsize].i=  size+1;  /* may be overwritten */
    +  count= size - idx + 1;   /* +1 for NULL terminator */
    +  memset((char *)SETelemaddr_(set, idx, void), 0, (size_t)count * SETelemsize);
    +} /* setzero */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/qset.h b/xs/src/qhull/src/libqhull/qset.h
    new file mode 100644
    index 0000000000..7e4e7d14f6
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/qset.h
    @@ -0,0 +1,490 @@
    +/*
      ---------------------------------
    +
    +   qset.h
    +     header file for qset.c that implements set
    +
    +   see qh-set.htm and qset.c
    +
    +   only uses mem.c, malloc/free
    +
    +   for error handling, writes message and calls
    +      qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +
    +   set operations satisfy the following properties:
    +    - sets have a max size, the actual size (if different) is stored at the end
    +    - every set is NULL terminated
    +    - sets may be sorted or unsorted, the caller must distinguish this
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/qset.h#2 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFset
    +#define qhDEFset 1
    +
    +#include 
    +
    +/*================= -structures- ===============*/
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;   /* a set is a sorted or unsorted array of pointers */
    +#endif
    +
    +/* [jan'15] Decided not to use countT.  Most sets are small.  The code uses signed tests */
    +
    +/*------------------------------------------
    +
    +setT
    +  a set or list of pointers with maximum size and actual size.
    +
    +variations:
    +  unsorted, unique   -- a list of unique pointers with NULL terminator
    +                           user guarantees uniqueness
    +  sorted             -- a sorted list of unique pointers with NULL terminator
    +                           qset.c guarantees uniqueness
    +  unsorted           -- a list of pointers terminated with NULL
    +  indexed            -- an array of pointers with NULL elements
    +
    +structure for set of n elements:
    +
    +        --------------
    +        |  maxsize
    +        --------------
    +        |  e[0] - a pointer, may be NULL for indexed sets
    +        --------------
    +        |  e[1]
    +
    +        --------------
    +        |  ...
    +        --------------
    +        |  e[n-1]
    +        --------------
    +        |  e[n] = NULL
    +        --------------
    +        |  ...
    +        --------------
    +        |  e[maxsize] - n+1 or NULL (determines actual size of set)
    +        --------------
    +
    +*/
    +
    +/*-- setelemT -- internal type to allow both pointers and indices
    +*/
    +typedef union setelemT setelemT;
    +union setelemT {
    +  void    *p;
    +  int      i;         /* integer used for e[maxSize] */
    +};
    +
    +struct setT {
    +  int maxsize;          /* maximum number of elements (except NULL) */
    +  setelemT e[1];        /* array of pointers, tail is NULL */
    +                        /* last slot (unless NULL) is actual size+1
    +                           e[maxsize]==NULL or e[e[maxsize]-1]==NULL */
    +                        /* this may generate a warning since e[] contains
    +                           maxsize elements */
    +};
    +
    +/*=========== -constants- =========================*/
    +
    +/*-------------------------------------
    +
    +  SETelemsize
    +    size of a set element in bytes
    +*/
    +#define SETelemsize ((int)sizeof(setelemT))
    +
    +
    +/*=========== -macros- =========================*/
    +
    +/*-------------------------------------
    +
    +   FOREACHsetelement_(type, set, variable)
    +     define FOREACH iterator
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +     no space in "variable)" [DEC Alpha cc compiler]
    +
    +   each iteration:
    +     variable is set element
    +     variablep is one beyond variable.
    +
    +   to repeat an element:
    +     variablep--; / *repeat* /
    +
    +   at exit:
    +     variable is NULL at end of loop
    +
    +   example:
    +     #define FOREACHfacet_( facets ) FOREACHsetelement_( facetT, facets, facet )
    +
    +   notes:
    +     use FOREACHsetelement_i_() if need index or include NULLs
    +
    +   WARNING:
    +     nested loops can't use the same variable (define another FOREACH)
    +
    +     needs braces if nested inside another FOREACH
    +     this includes intervening blocks, e.g. FOREACH...{ if () FOREACH...} )
    +*/
    +#define FOREACHsetelement_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##p= (type **)&((set)->e[0].p); \
    +          (variable= *variable##p++);)
    +
    +/*------------------------------------------
    +
    +   FOREACHsetelement_i_(type, set, variable)
    +     define indexed FOREACH iterator
    +
    +   declare:
    +     type *variable, variable_n, variable_i;
    +
    +   each iteration:
    +     variable is set element, may be NULL
    +     variable_i is index, variable_n is qh_setsize()
    +
    +   to repeat an element:
    +     variable_i--; variable_n-- repeats for deleted element
    +
    +   at exit:
    +     variable==NULL and variable_i==variable_n
    +
    +   example:
    +     #define FOREACHfacet_i_( facets ) FOREACHsetelement_i_( facetT, facets, facet )
    +
    +   WARNING:
    +     nested loops can't use the same variable (define another FOREACH)
    +
    +     needs braces if nested inside another FOREACH
    +     this includes intervening blocks, e.g. FOREACH...{ if () FOREACH...} )
    +*/
    +#define FOREACHsetelement_i_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##_i= 0, variable= (type *)((set)->e[0].p), \
    +                   variable##_n= qh_setsize(set);\
    +          variable##_i < variable##_n;\
    +          variable= (type *)((set)->e[++variable##_i].p) )
    +
    +/*----------------------------------------
    +
    +   FOREACHsetelementreverse_(type, set, variable)-
    +     define FOREACH iterator in reverse order
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +     also declare 'int variabletemp'
    +
    +   each iteration:
    +     variable is set element
    +
    +   to repeat an element:
    +     variabletemp++; / *repeat* /
    +
    +   at exit:
    +     variable is NULL
    +
    +   example:
    +     #define FOREACHvertexreverse_( vertices ) FOREACHsetelementreverse_( vertexT, vertices, vertex )
    +
    +   notes:
    +     use FOREACHsetelementreverse12_() to reverse first two elements
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHsetelementreverse_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +           variable##temp= qh_setsize(set)-1, variable= qh_setlast(set);\
    +           variable; variable= \
    +           ((--variable##temp >= 0) ? SETelemt_(set, variable##temp, type) : NULL))
    +
    +/*-------------------------------------
    +
    +   FOREACHsetelementreverse12_(type, set, variable)-
    +     define FOREACH iterator with e[1] and e[0] reversed
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +
    +   each iteration:
    +     variable is set element
    +     variablep is one after variable.
    +
    +   to repeat an element:
    +     variablep--; / *repeat* /
    +
    +   at exit:
    +     variable is NULL at end of loop
    +
    +   example
    +     #define FOREACHvertexreverse12_( vertices ) FOREACHsetelementreverse12_( vertexT, vertices, vertex )
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHsetelementreverse12_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##p= (type **)&((set)->e[1].p); \
    +          (variable= *variable##p); \
    +          variable##p == ((type **)&((set)->e[0].p))?variable##p += 2: \
    +              (variable##p == ((type **)&((set)->e[1].p))?variable##p--:variable##p++))
    +
    +/*-------------------------------------
    +
    +   FOREACHelem_( set )-
    +     iterate elements in a set
    +
    +   declare:
    +     void *elem, *elemp;
    +
    +   each iteration:
    +     elem is set element
    +     elemp is one beyond
    +
    +   to repeat an element:
    +     elemp--; / *repeat* /
    +
    +   at exit:
    +     elem == NULL at end of loop
    +
    +   example:
    +     FOREACHelem_(set) {
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHelem_(set) FOREACHsetelement_(void, set, elem)
    +
    +/*-------------------------------------
    +
    +   FOREACHset_( set )-
    +     iterate a set of sets
    +
    +   declare:
    +     setT *set, **setp;
    +
    +   each iteration:
    +     set is set element
    +     setp is one beyond
    +
    +   to repeat an element:
    +     setp--; / *repeat* /
    +
    +   at exit:
    +     set == NULL at end of loop
    +
    +   example
    +     FOREACHset_(sets) {
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHset_(sets) FOREACHsetelement_(setT, sets, set)
    +
    +/*-------------------------------------------
    +
    +   SETindex_( set, elem )
    +     return index of elem in set
    +
    +   notes:
    +     for use with FOREACH iteration
    +     WARN64 -- Maximum set size is 2G
    +
    +   example:
    +     i= SETindex_(ridges, ridge)
    +*/
    +#define SETindex_(set, elem) ((int)((void **)elem##p - (void **)&(set)->e[1].p))
    +
    +/*-----------------------------------------
    +
    +   SETref_( elem )
    +     l.h.s. for modifying the current element in a FOREACH iteration
    +
    +   example:
    +     SETref_(ridge)= anotherridge;
    +*/
    +#define SETref_(elem) (elem##p[-1])
    +
    +/*-----------------------------------------
    +
    +   SETelem_(set, n)
    +     return the n'th element of set
    +
    +   notes:
    +      assumes that n is valid [0..size] and that set is defined
    +      use SETelemt_() for type cast
    +*/
    +#define SETelem_(set, n)           ((set)->e[n].p)
    +
    +/*-----------------------------------------
    +
    +   SETelemt_(set, n, type)
    +     return the n'th element of set as a type
    +
    +   notes:
    +      assumes that n is valid [0..size] and that set is defined
    +*/
    +#define SETelemt_(set, n, type)    ((type*)((set)->e[n].p))
    +
    +/*-----------------------------------------
    +
    +   SETelemaddr_(set, n, type)
    +     return address of the n'th element of a set
    +
    +   notes:
    +      assumes that n is valid [0..size] and set is defined
    +*/
    +#define SETelemaddr_(set, n, type) ((type **)(&((set)->e[n].p)))
    +
    +/*-----------------------------------------
    +
    +   SETfirst_(set)
    +     return first element of set
    +
    +*/
    +#define SETfirst_(set)             ((set)->e[0].p)
    +
    +/*-----------------------------------------
    +
    +   SETfirstt_(set, type)
    +     return first element of set as a type
    +
    +*/
    +#define SETfirstt_(set, type)      ((type*)((set)->e[0].p))
    +
    +/*-----------------------------------------
    +
    +   SETsecond_(set)
    +     return second element of set
    +
    +*/
    +#define SETsecond_(set)            ((set)->e[1].p)
    +
    +/*-----------------------------------------
    +
    +   SETsecondt_(set, type)
    +     return second element of set as a type
    +*/
    +#define SETsecondt_(set, type)     ((type*)((set)->e[1].p))
    +
    +/*-----------------------------------------
    +
    +   SETaddr_(set, type)
    +       return address of set's elements
    +*/
    +#define SETaddr_(set,type)         ((type **)(&((set)->e[0].p)))
    +
    +/*-----------------------------------------
    +
    +   SETreturnsize_(set, size)
    +     return size of a set
    +
    +   notes:
    +      set must be defined
    +      use qh_setsize(set) unless speed is critical
    +*/
    +#define SETreturnsize_(set, size) (((size)= ((set)->e[(set)->maxsize].i))?(--(size)):((size)= (set)->maxsize))
    +
    +/*-----------------------------------------
    +
    +   SETempty_(set)
    +     return true(1) if set is empty
    +
    +   notes:
    +      set may be NULL
    +*/
    +#define SETempty_(set)            (!set || (SETfirst_(set) ? 0 : 1))
    +
    +/*---------------------------------
    +
    +  SETsizeaddr_(set)
    +    return pointer to 'actual size+1' of set (set CANNOT be NULL!!)
    +    Its type is setelemT* for strict aliasing
    +    All SETelemaddr_ must be cast to setelemT
    +
    +
    +  notes:
    +    *SETsizeaddr==NULL or e[*SETsizeaddr-1].p==NULL
    +*/
    +#define SETsizeaddr_(set) (&((set)->e[(set)->maxsize]))
    +
    +/*-----------------------------------------
    +
    +   SETtruncate_(set, size)
    +     truncate set to size
    +
    +   see:
    +     qh_settruncate()
    +
    +*/
    +#define SETtruncate_(set, size) {set->e[set->maxsize].i= size+1; /* maybe overwritten */ \
    +      set->e[size].p= NULL;}
    +
    +/*======= prototypes in alphabetical order ============*/
    +
    +void  qh_setaddsorted(setT **setp, void *elem);
    +void  qh_setaddnth(setT **setp, int nth, void *newelem);
    +void  qh_setappend(setT **setp, void *elem);
    +void  qh_setappend_set(setT **setp, setT *setA);
    +void  qh_setappend2ndlast(setT **setp, void *elem);
    +void  qh_setcheck(setT *set, const char *tname, unsigned id);
    +void  qh_setcompact(setT *set);
    +setT *qh_setcopy(setT *set, int extra);
    +void *qh_setdel(setT *set, void *elem);
    +void *qh_setdellast(setT *set);
    +void *qh_setdelnth(setT *set, int nth);
    +void *qh_setdelnthsorted(setT *set, int nth);
    +void *qh_setdelsorted(setT *set, void *newelem);
    +setT *qh_setduplicate( setT *set, int elemsize);
    +void **qh_setendpointer(setT *set);
    +int   qh_setequal(setT *setA, setT *setB);
    +int   qh_setequal_except(setT *setA, void *skipelemA, setT *setB, void *skipelemB);
    +int   qh_setequal_skip(setT *setA, int skipA, setT *setB, int skipB);
    +void  qh_setfree(setT **set);
    +void  qh_setfree2( setT **setp, int elemsize);
    +void  qh_setfreelong(setT **set);
    +int   qh_setin(setT *set, void *setelem);
    +int   qh_setindex(setT *set, void *setelem);
    +void  qh_setlarger(setT **setp);
    +void *qh_setlast(setT *set);
    +setT *qh_setnew(int size);
    +setT *qh_setnew_delnthsorted(setT *set, int size, int nth, int prepend);
    +void  qh_setprint(FILE *fp, const char* string, setT *set);
    +void  qh_setreplace(setT *set, void *oldelem, void *newelem);
    +int   qh_setsize(setT *set);
    +setT *qh_settemp(int setsize);
    +void  qh_settempfree(setT **set);
    +void  qh_settempfree_all(void);
    +setT *qh_settemppop(void);
    +void  qh_settemppush(setT *set);
    +void  qh_settruncate(setT *set, int size);
    +int   qh_setunique(setT **set, void *elem);
    +void  qh_setzero(setT *set, int idx, int size);
    +
    +
    +#endif /* qhDEFset */
    diff --git a/xs/src/qhull/src/libqhull/random.c b/xs/src/qhull/src/libqhull/random.c
    new file mode 100644
    index 0000000000..176d697aeb
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/random.c
    @@ -0,0 +1,245 @@
    +/*
      ---------------------------------
    +
    +   random.c and utilities
    +     Park & Miller's minimimal standard random number generator
    +     argc/argv conversion
    +
    +     Used by rbox.  Do not use 'qh' 
    +*/
    +
    +#include "libqhull.h"
    +#include "random.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#pragma warning( disable : 4996)  /* function was declared deprecated(strcpy, localtime, etc.) */
    +#endif
    +
    +/*---------------------------------
    +
    + qh_argv_to_command( argc, argv, command, max_size )
    +
    +    build command from argc/argv
    +    max_size is at least
    +
    + returns:
    +    a space-delimited string of options (just as typed)
    +    returns false if max_size is too short
    +
    + notes:
    +    silently removes
    +    makes option string easy to input and output
    +    matches qh_argv_to_command_size()
    +
    +    argc may be 0
    +*/
    +int qh_argv_to_command(int argc, char *argv[], char* command, int max_size) {
    +  int i, remaining;
    +  char *s;
    +  *command= '\0';  /* max_size > 0 */
    +
    +  if (argc) {
    +    if ((s= strrchr( argv[0], '\\')) /* get filename w/o .exe extension */
    +    || (s= strrchr( argv[0], '/')))
    +        s++;
    +    else
    +        s= argv[0];
    +    if ((int)strlen(s) < max_size)   /* WARN64 */
    +        strcpy(command, s);
    +    else
    +        goto error_argv;
    +    if ((s= strstr(command, ".EXE"))
    +    ||  (s= strstr(command, ".exe")))
    +        *s= '\0';
    +  }
    +  for (i=1; i < argc; i++) {
    +    s= argv[i];
    +    remaining= max_size - (int)strlen(command) - (int)strlen(s) - 2;   /* WARN64 */
    +    if (!*s || strchr(s, ' ')) {
    +      char *t= command + strlen(command);
    +      remaining -= 2;
    +      if (remaining < 0) {
    +        goto error_argv;
    +      }
    +      *t++= ' ';
    +      *t++= '"';
    +      while (*s) {
    +        if (*s == '"') {
    +          if (--remaining < 0)
    +            goto error_argv;
    +          *t++= '\\';
    +        }
    +        *t++= *s++;
    +      }
    +      *t++= '"';
    +      *t= '\0';
    +    }else if (remaining < 0) {
    +      goto error_argv;
    +    }else
    +      strcat(command, " ");
    +      strcat(command, s);
    +  }
    +  return 1;
    +
    +error_argv:
    +  return 0;
    +} /* argv_to_command */
    +
    +/*---------------------------------
    +
    +qh_argv_to_command_size( argc, argv )
    +
    +    return size to allocate for qh_argv_to_command()
    +
    +notes:
    +    argc may be 0
    +    actual size is usually shorter
    +*/
    +int qh_argv_to_command_size(int argc, char *argv[]) {
    +    unsigned int count= 1; /* null-terminator if argc==0 */
    +    int i;
    +    char *s;
    +
    +    for (i=0; i0 && strchr(argv[i], ' ')) {
    +        count += 2;  /* quote delimiters */
    +        for (s=argv[i]; *s; s++) {
    +          if (*s == '"') {
    +            count++;
    +          }
    +        }
    +      }
    +    }
    +    return count;
    +} /* argv_to_command_size */
    +
    +/*---------------------------------
    +
    +  qh_rand()
    +  qh_srand( seed )
    +    generate pseudo-random number between 1 and 2^31 -2
    +
    +  notes:
    +    For qhull and rbox, called from qh_RANDOMint(),etc. [user.h]
    +
    +    From Park & Miller's minimal standard random number generator
    +      Communications of the ACM, 31:1192-1201, 1988.
    +    Does not use 0 or 2^31 -1
    +      this is silently enforced by qh_srand()
    +    Can make 'Rn' much faster by moving qh_rand to qh_distplane
    +*/
    +
    +/* Global variables and constants */
    +
    +int qh_last_random= 1;  /* define as global variable instead of using qh */
    +
    +#define qh_rand_a 16807
    +#define qh_rand_m 2147483647
    +#define qh_rand_q 127773  /* m div a */
    +#define qh_rand_r 2836    /* m mod a */
    +
    +int qh_rand( void) {
    +    int lo, hi, test;
    +    int seed = qh_last_random;
    +
    +    hi = seed / qh_rand_q;  /* seed div q */
    +    lo = seed % qh_rand_q;  /* seed mod q */
    +    test = qh_rand_a * lo - qh_rand_r * hi;
    +    if (test > 0)
    +        seed= test;
    +    else
    +        seed= test + qh_rand_m;
    +    qh_last_random= seed;
    +    /* seed = seed < qh_RANDOMmax/2 ? 0 : qh_RANDOMmax;  for testing */
    +    /* seed = qh_RANDOMmax;  for testing */
    +    return seed;
    +} /* rand */
    +
    +void qh_srand( int seed) {
    +    if (seed < 1)
    +        qh_last_random= 1;
    +    else if (seed >= qh_rand_m)
    +        qh_last_random= qh_rand_m - 1;
    +    else
    +        qh_last_random= seed;
    +} /* qh_srand */
    +
    +/*---------------------------------
    +
    +qh_randomfactor( scale, offset )
    +  return a random factor r * scale + offset
    +
    +notes:
    +  qh.RANDOMa/b are defined in global.c
    +*/
    +realT qh_randomfactor(realT scale, realT offset) {
    +    realT randr;
    +
    +    randr= qh_RANDOMint;
    +    return randr * scale + offset;
    +} /* randomfactor */
    +
    +/*---------------------------------
    +
    +qh_randommatrix( buffer, dim, rows )
    +  generate a random dim X dim matrix in range [-1,1]
    +  assumes buffer is [dim+1, dim]
    +
    +returns:
    +  sets buffer to random numbers
    +  sets rows to rows of buffer
    +  sets row[dim] as scratch row
    +*/
    +void qh_randommatrix(realT *buffer, int dim, realT **rows) {
    +    int i, k;
    +    realT **rowi, *coord, realr;
    +
    +    coord= buffer;
    +    rowi= rows;
    +    for (i=0; i < dim; i++) {
    +        *(rowi++)= coord;
    +        for (k=0; k < dim; k++) {
    +            realr= qh_RANDOMint;
    +            *(coord++)= 2.0 * realr/(qh_RANDOMmax+1) - 1.0;
    +        }
    +    }
    +    *rowi= coord;
    +} /* randommatrix */
    +
    +/*---------------------------------
    +
    +  qh_strtol( s, endp) qh_strtod( s, endp)
    +    internal versions of strtol() and strtod()
    +    does not skip trailing spaces
    +  notes:
    +    some implementations of strtol()/strtod() skip trailing spaces
    +*/
    +double qh_strtod(const char *s, char **endp) {
    +  double result;
    +
    +  result= strtod(s, endp);
    +  if (s < (*endp) && (*endp)[-1] == ' ')
    +    (*endp)--;
    +  return result;
    +} /* strtod */
    +
    +int qh_strtol(const char *s, char **endp) {
    +  int result;
    +
    +  result= (int) strtol(s, endp, 10);     /* WARN64 */
    +  if (s< (*endp) && (*endp)[-1] == ' ')
    +    (*endp)--;
    +  return result;
    +} /* strtol */
    diff --git a/xs/src/qhull/src/libqhull/random.h b/xs/src/qhull/src/libqhull/random.h
    new file mode 100644
    index 0000000000..0c6896b765
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/random.h
    @@ -0,0 +1,34 @@
    +/*
      ---------------------------------
    +
    +  random.h
    +    header file for random and utility routines
    +
    +   see qh-geom.htm and random.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/random.h#2 $$Change: 2026 $
    +   $DateTime: 2015/11/07 22:44:39 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFrandom
    +#define qhDEFrandom 1
    +
    +#include "libqhull.h"
    +
    +/*============= prototypes in alphabetical order ======= */
    +
    +
    +int     qh_argv_to_command(int argc, char *argv[], char* command, int max_size);
    +int     qh_argv_to_command_size(int argc, char *argv[]);
    +int     qh_rand( void);
    +void    qh_srand( int seed);
    +realT   qh_randomfactor(realT scale, realT offset);
    +void    qh_randommatrix(realT *buffer, int dim, realT **row);
    +int     qh_strtol(const char *s, char **endp);
    +double  qh_strtod(const char *s, char **endp);
    +
    +#endif /* qhDEFrandom */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/rboxlib.c b/xs/src/qhull/src/libqhull/rboxlib.c
    new file mode 100644
    index 0000000000..f945133fa0
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/rboxlib.c
    @@ -0,0 +1,870 @@
    +/*
      ---------------------------------
    +
    +   rboxlib.c
    +     Generate input points
    +
    +   notes:
    +     For documentation, see prompt[] of rbox.c
    +     50 points generated for 'rbox D4'
    +
    +   WARNING:
    +     incorrect range if qh_RANDOMmax is defined wrong (user.h)
    +*/
    +
    +#include "libqhull.h"  /* First for user.h */
    +#include "random.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ */
    +#pragma warning( disable : 4706)  /* assignment within conditional expression. */
    +#pragma warning( disable : 4996)  /* this function (strncat,sprintf,strcpy) or variable may be unsafe. */
    +#endif
    +
    +#define MAXdim 200
    +#define PI 3.1415926535897932384
    +
    +/* ------------------------------ prototypes ----------------*/
    +int qh_roundi( double a);
    +void qh_out1( double a);
    +void qh_out2n( double a, double b);
    +void qh_out3n( double a, double b, double c);
    +void qh_outcoord(int iscdd, double *coord, int dim);
    +void qh_outcoincident(int coincidentpoints, double radius, int iscdd, double *coord, int dim);
    +
    +void    qh_fprintf_rbox(FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +int     qh_rand( void);
    +void    qh_srand( int seed);
    +
    +
    +/* ------------------------------ globals -------------------*/
    +
    +/* No state is carried between rbox requests */
    +typedef struct rboxT rboxT;
    +struct rboxT {
    +  FILE *fout;
    +  FILE *ferr;
    +  int isinteger;
    +  double out_offset;
    +  jmp_buf errexit;        /* exit label for rboxpoints, defined by setjmp(), called by qh_errexit_rbox() */
    +  char  jmpXtra[40];      /* extra bytes in case jmp_buf is defined wrong by compiler */
    +};
    +
    +
    +int rbox_inuse= 0;
    +rboxT rbox;
    +
    +/*---------------------------------
    +
    +  qh_rboxpoints( fout, ferr, rbox_command )
    +    Generate points to fout according to rbox options
    +    Report errors on ferr
    +
    +  returns:
    +    0 (qh_ERRnone) on success
    +    1 (qh_ERRinput) on input error
    +    4 (qh_ERRmem) on memory error
    +    5 (qh_ERRqhull) on internal error
    +
    +  notes:
    +    To avoid using stdio, redefine qh_malloc, qh_free, and qh_fprintf_rbox (user.c)
    +
    +  design:
    +    Straight line code (consider defining a struct and functions):
    +
    +    Parse arguments into variables
    +    Determine the number of points
    +    Generate the points
    +*/
    +int qh_rboxpoints(FILE* fout, FILE* ferr, char* rbox_command) {
    +  int i,j,k;
    +  int gendim;
    +  int coincidentcount=0, coincidenttotal=0, coincidentpoints=0;
    +  int cubesize, diamondsize, seed=0, count, apex;
    +  int dim=3, numpoints=0, totpoints, addpoints=0;
    +  int issphere=0, isaxis=0,  iscdd=0, islens=0, isregular=0, iswidth=0, addcube=0;
    +  int isgap=0, isspiral=0, NOcommand=0, adddiamond=0;
    +  int israndom=0, istime=0;
    +  int isbox=0, issimplex=0, issimplex2=0, ismesh=0;
    +  double width=0.0, gap=0.0, radius=0.0, coincidentradius=0.0;
    +  double coord[MAXdim], offset, meshm=3.0, meshn=4.0, meshr=5.0;
    +  double *coordp, *simplex= NULL, *simplexp;
    +  int nthroot, mult[MAXdim];
    +  double norm, factor, randr, rangap, lensangle=0, lensbase=1;
    +  double anglediff, angle, x, y, cube=0.0, diamond=0.0;
    +  double box= qh_DEFAULTbox; /* scale all numbers before output */
    +  double randmax= qh_RANDOMmax;
    +  char command[200], seedbuf[200];
    +  char *s= command, *t, *first_point= NULL;
    +  time_t timedata;
    +  int exitcode;
    +
    +  if (rbox_inuse) {
    +    qh_fprintf_rbox(rbox.ferr, 6188, "rbox error: rbox in use by another process.  Please lock calls to rbox.\n");
    +    return qh_ERRqhull;
    +  }
    +  rbox_inuse= True;
    +  rbox.ferr= ferr;
    +  rbox.fout= fout;
    +
    +  exitcode= setjmp(rbox.errexit);
    +  if (exitcode) {
    +    /* same code for error exit and normal return.  qh.NOerrexit is set */
    +    if (simplex)
    +        qh_free(simplex);
    +    rbox_inuse= False;
    +    return exitcode;
    +  }
    +
    +  *command= '\0';
    +  strncat(command, rbox_command, sizeof(command)-strlen(command)-1);
    +
    +  while (*s && !isspace(*s))  /* skip program name */
    +    s++;
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    if (isdigit(*s)) {
    +      numpoints= qh_strtol(s, &s);
    +      continue;
    +    }
    +    /* ============= read flags =============== */
    +    switch (*s++) {
    +    case 'c':
    +      addcube= 1;
    +      t= s;
    +      while (isspace(*t))
    +        t++;
    +      if (*t == 'G')
    +        cube= qh_strtod(++t, &s);
    +      break;
    +    case 'd':
    +      adddiamond= 1;
    +      t= s;
    +      while (isspace(*t))
    +        t++;
    +      if (*t == 'G')
    +        diamond= qh_strtod(++t, &s);
    +      break;
    +    case 'h':
    +      iscdd= 1;
    +      break;
    +    case 'l':
    +      isspiral= 1;
    +      break;
    +    case 'n':
    +      NOcommand= 1;
    +      break;
    +    case 'r':
    +      isregular= 1;
    +      break;
    +    case 's':
    +      issphere= 1;
    +      break;
    +    case 't':
    +      istime= 1;
    +      if (isdigit(*s)) {
    +        seed= qh_strtol(s, &s);
    +        israndom= 0;
    +      }else
    +        israndom= 1;
    +      break;
    +    case 'x':
    +      issimplex= 1;
    +      break;
    +    case 'y':
    +      issimplex2= 1;
    +      break;
    +    case 'z':
    +      rbox.isinteger= 1;
    +      break;
    +    case 'B':
    +      box= qh_strtod(s, &s);
    +      isbox= 1;
    +      break;
    +    case 'C':
    +      if (*s)
    +        coincidentpoints=  qh_strtol(s, &s);
    +      if (*s == ',') {
    +        ++s;
    +        coincidentradius=  qh_strtod(s, &s);
    +      }
    +      if (*s == ',') {
    +        ++s;
    +        coincidenttotal=  qh_strtol(s, &s);
    +      }
    +      if (*s && !isspace(*s)) {
    +        qh_fprintf_rbox(rbox.ferr, 7080, "rbox error: arguments for 'Cn,r,m' are not 'int', 'float', and 'int'.  Remaining string is '%s'\n", s);
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      if (coincidentpoints==0){
    +        qh_fprintf_rbox(rbox.ferr, 6268, "rbox error: missing arguments for 'Cn,r,m' where n is the number of coincident points, r is the radius (default 0.0), and m is the number of points\n");
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      if (coincidentpoints<0 || coincidenttotal<0 || coincidentradius<0.0){
    +        qh_fprintf_rbox(rbox.ferr, 6269, "rbox error: negative arguments for 'Cn,m,r' where n (%d) is the number of coincident points, m (%d) is the number of points, and r (%.2g) is the radius (default 0.0)\n", coincidentpoints, coincidenttotal, coincidentradius);
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      break;
    +    case 'D':
    +      dim= qh_strtol(s, &s);
    +      if (dim < 1
    +      || dim > MAXdim) {
    +        qh_fprintf_rbox(rbox.ferr, 6189, "rbox error: dimension, D%d, out of bounds (>=%d or <=0)", dim, MAXdim);
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      break;
    +    case 'G':
    +      if (isdigit(*s))
    +        gap= qh_strtod(s, &s);
    +      else
    +        gap= 0.5;
    +      isgap= 1;
    +      break;
    +    case 'L':
    +      if (isdigit(*s))
    +        radius= qh_strtod(s, &s);
    +      else
    +        radius= 10;
    +      islens= 1;
    +      break;
    +    case 'M':
    +      ismesh= 1;
    +      if (*s)
    +        meshn= qh_strtod(s, &s);
    +      if (*s == ',') {
    +        ++s;
    +        meshm= qh_strtod(s, &s);
    +      }else
    +        meshm= 0.0;
    +      if (*s == ',') {
    +        ++s;
    +        meshr= qh_strtod(s, &s);
    +      }else
    +        meshr= sqrt(meshn*meshn + meshm*meshm);
    +      if (*s && !isspace(*s)) {
    +        qh_fprintf_rbox(rbox.ferr, 7069, "rbox warning: assuming 'M3,4,5' since mesh args are not integers or reals\n");
    +        meshn= 3.0, meshm=4.0, meshr=5.0;
    +      }
    +      break;
    +    case 'O':
    +      rbox.out_offset= qh_strtod(s, &s);
    +      break;
    +    case 'P':
    +      if (!first_point)
    +        first_point= s-1;
    +      addpoints++;
    +      while (*s && !isspace(*s))   /* read points later */
    +        s++;
    +      break;
    +    case 'W':
    +      width= qh_strtod(s, &s);
    +      iswidth= 1;
    +      break;
    +    case 'Z':
    +      if (isdigit(*s))
    +        radius= qh_strtod(s, &s);
    +      else
    +        radius= 1.0;
    +      isaxis= 1;
    +      break;
    +    default:
    +      qh_fprintf_rbox(rbox.ferr, 7070, "rbox error: unknown flag at %s.\nExecute 'rbox' without arguments for documentation.\n", s);
    +      qh_errexit_rbox(qh_ERRinput);
    +    }
    +    if (*s && !isspace(*s)) {
    +      qh_fprintf_rbox(rbox.ferr, 7071, "rbox error: missing space between flags at %s.\n", s);
    +      qh_errexit_rbox(qh_ERRinput);
    +    }
    +  }
    +
    +  /* ============= defaults, constants, and sizes =============== */
    +  if (rbox.isinteger && !isbox)
    +    box= qh_DEFAULTzbox;
    +  if (addcube) {
    +    cubesize= (int)floor(ldexp(1.0,dim)+0.5);
    +    if (cube == 0.0)
    +      cube= box;
    +  }else
    +    cubesize= 0;
    +  if (adddiamond) {
    +    diamondsize= 2*dim;
    +    if (diamond == 0.0)
    +      diamond= box;
    +  }else
    +    diamondsize= 0;
    +  if (islens) {
    +    if (isaxis) {
    +        qh_fprintf_rbox(rbox.ferr, 6190, "rbox error: can not combine 'Ln' with 'Zn'\n");
    +        qh_errexit_rbox(qh_ERRinput);
    +    }
    +    if (radius <= 1.0) {
    +        qh_fprintf_rbox(rbox.ferr, 6191, "rbox error: lens radius %.2g should be greater than 1.0\n",
    +               radius);
    +        qh_errexit_rbox(qh_ERRinput);
    +    }
    +    lensangle= asin(1.0/radius);
    +    lensbase= radius * cos(lensangle);
    +  }
    +
    +  if (!numpoints) {
    +    if (issimplex2)
    +        ; /* ok */
    +    else if (isregular + issimplex + islens + issphere + isaxis + isspiral + iswidth + ismesh) {
    +        qh_fprintf_rbox(rbox.ferr, 6192, "rbox error: missing count\n");
    +        qh_errexit_rbox(qh_ERRinput);
    +    }else if (adddiamond + addcube + addpoints)
    +        ; /* ok */
    +    else {
    +        numpoints= 50;  /* ./rbox D4 is the test case */
    +        issphere= 1;
    +    }
    +  }
    +  if ((issimplex + islens + isspiral + ismesh > 1)
    +  || (issimplex + issphere + isspiral + ismesh > 1)) {
    +    qh_fprintf_rbox(rbox.ferr, 6193, "rbox error: can only specify one of 'l', 's', 'x', 'Ln', or 'Mn,m,r' ('Ln s' is ok).\n");
    +    qh_errexit_rbox(qh_ERRinput);
    +  }
    +  if (coincidentpoints>0 && (numpoints == 0 || coincidenttotal > numpoints)) {
    +    qh_fprintf_rbox(rbox.ferr, 6270, "rbox error: 'Cn,r,m' requested n coincident points for each of m points.  Either there is no points or m (%d) is greater than the number of points (%d).\n", coincidenttotal, numpoints);
    +    qh_errexit_rbox(qh_ERRinput);
    +  }
    +  if (coincidenttotal == 0)
    +    coincidenttotal= numpoints;
    +
    +  /* ============= print header with total points =============== */
    +  if (issimplex || ismesh)
    +    totpoints= numpoints;
    +  else if (issimplex2)
    +    totpoints= numpoints+dim+1;
    +  else if (isregular) {
    +    totpoints= numpoints;
    +    if (dim == 2) {
    +        if (islens)
    +          totpoints += numpoints - 2;
    +    }else if (dim == 3) {
    +        if (islens)
    +          totpoints += 2 * numpoints;
    +      else if (isgap)
    +        totpoints += 1 + numpoints;
    +      else
    +        totpoints += 2;
    +    }
    +  }else
    +    totpoints= numpoints + isaxis;
    +  totpoints += cubesize + diamondsize + addpoints;
    +  totpoints += coincidentpoints*coincidenttotal;
    +
    +  /* ============= seed randoms =============== */
    +  if (istime == 0) {
    +    for (s=command; *s; s++) {
    +      if (issimplex2 && *s == 'y') /* make 'y' same seed as 'x' */
    +        i= 'x';
    +      else
    +        i= *s;
    +      seed= 11*seed + i;
    +    }
    +  }else if (israndom) {
    +    seed= (int)time(&timedata);
    +    sprintf(seedbuf, " t%d", seed);  /* appends an extra t, not worth removing */
    +    strncat(command, seedbuf, sizeof(command)-strlen(command)-1);
    +    t= strstr(command, " t ");
    +    if (t)
    +      strcpy(t+1, t+3); /* remove " t " */
    +  } /* else, seed explicitly set to n */
    +  qh_RANDOMseed_(seed);
    +
    +  /* ============= print header =============== */
    +
    +  if (iscdd)
    +      qh_fprintf_rbox(rbox.fout, 9391, "%s\nbegin\n        %d %d %s\n",
    +      NOcommand ? "" : command,
    +      totpoints, dim+1,
    +      rbox.isinteger ? "integer" : "real");
    +  else if (NOcommand)
    +      qh_fprintf_rbox(rbox.fout, 9392, "%d\n%d\n", dim, totpoints);
    +  else
    +      /* qh_fprintf_rbox special cases 9393 to append 'command' to the RboxPoints.comment() */
    +      qh_fprintf_rbox(rbox.fout, 9393, "%d %s\n%d\n", dim, command, totpoints);
    +
    +  /* ============= explicit points =============== */
    +  if ((s= first_point)) {
    +    while (s && *s) { /* 'P' */
    +      count= 0;
    +      if (iscdd)
    +        qh_out1( 1.0);
    +      while (*++s) {
    +        qh_out1( qh_strtod(s, &s));
    +        count++;
    +        if (isspace(*s) || !*s)
    +          break;
    +        if (*s != ',') {
    +          qh_fprintf_rbox(rbox.ferr, 6194, "rbox error: missing comma after coordinate in %s\n\n", s);
    +          qh_errexit_rbox(qh_ERRinput);
    +        }
    +      }
    +      if (count < dim) {
    +        for (k=dim-count; k--; )
    +          qh_out1( 0.0);
    +      }else if (count > dim) {
    +        qh_fprintf_rbox(rbox.ferr, 6195, "rbox error: %d coordinates instead of %d coordinates in %s\n\n",
    +                  count, dim, s);
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      qh_fprintf_rbox(rbox.fout, 9394, "\n");
    +      while ((s= strchr(s, 'P'))) {
    +        if (isspace(s[-1]))
    +          break;
    +      }
    +    }
    +  }
    +
    +  /* ============= simplex distribution =============== */
    +  if (issimplex+issimplex2) {
    +    if (!(simplex= (double*)qh_malloc( dim * (dim+1) * sizeof(double)))) {
    +      qh_fprintf_rbox(rbox.ferr, 6196, "rbox error: insufficient memory for simplex\n");
    +      qh_errexit_rbox(qh_ERRmem); /* qh_ERRmem */
    +    }
    +    simplexp= simplex;
    +    if (isregular) {
    +      for (i=0; i randmax/2)
    +          coord[dim-1]= -coord[dim-1];
    +      /* ============= project 'Wn' point toward boundary =============== */
    +      }else if (iswidth && !issphere) {
    +        j= qh_RANDOMint % gendim;
    +        if (coord[j] < 0)
    +          coord[j]= -1.0 - coord[j] * width;
    +        else
    +          coord[j]= 1.0 - coord[j] * width;
    +      }
    +      /* ============= scale point to box =============== */
    +      for (k=0; k=0; k--) {
    +        if (j & ( 1 << k))
    +          qh_out1( cube);
    +        else
    +          qh_out1( -cube);
    +      }
    +      qh_fprintf_rbox(rbox.fout, 9400, "\n");
    +    }
    +  }
    +
    +  /* ============= write diamond vertices =============== */
    +  if (adddiamond) {
    +    for (j=0; j=0; k--) {
    +        if (j/2 != k)
    +          qh_out1( 0.0);
    +        else if (j & 0x1)
    +          qh_out1( diamond);
    +        else
    +          qh_out1( -diamond);
    +      }
    +      qh_fprintf_rbox(rbox.fout, 9401, "\n");
    +    }
    +  }
    +
    +  if (iscdd)
    +    qh_fprintf_rbox(rbox.fout, 9402, "end\nhull\n");
    +
    +  /* same code for error exit and normal return */
    +  qh_free(simplex);
    +  rbox_inuse= False;
    +  return qh_ERRnone;
    +} /* rboxpoints */
    +
    +/*------------------------------------------------
    +outxxx - output functions for qh_rboxpoints
    +*/
    +int qh_roundi( double a) {
    +  if (a < 0.0) {
    +    if (a - 0.5 < INT_MIN) {
    +      qh_fprintf_rbox(rbox.ferr, 6200, "rbox input error: negative coordinate %2.2g is too large.  Reduce 'Bn'\n", a);
    +      qh_errexit_rbox(qh_ERRinput);
    +    }
    +    return (int)(a - 0.5);
    +  }else {
    +    if (a + 0.5 > INT_MAX) {
    +      qh_fprintf_rbox(rbox.ferr, 6201, "rbox input error: coordinate %2.2g is too large.  Reduce 'Bn'\n", a);
    +      qh_errexit_rbox(qh_ERRinput);
    +    }
    +    return (int)(a + 0.5);
    +  }
    +} /* qh_roundi */
    +
    +void qh_out1(double a) {
    +
    +  if (rbox.isinteger)
    +    qh_fprintf_rbox(rbox.fout, 9403, "%d ", qh_roundi( a+rbox.out_offset));
    +  else
    +    qh_fprintf_rbox(rbox.fout, 9404, qh_REAL_1, a+rbox.out_offset);
    +} /* qh_out1 */
    +
    +void qh_out2n( double a, double b) {
    +
    +  if (rbox.isinteger)
    +    qh_fprintf_rbox(rbox.fout, 9405, "%d %d\n", qh_roundi(a+rbox.out_offset), qh_roundi(b+rbox.out_offset));
    +  else
    +    qh_fprintf_rbox(rbox.fout, 9406, qh_REAL_2n, a+rbox.out_offset, b+rbox.out_offset);
    +} /* qh_out2n */
    +
    +void qh_out3n( double a, double b, double c) {
    +
    +  if (rbox.isinteger)
    +    qh_fprintf_rbox(rbox.fout, 9407, "%d %d %d\n", qh_roundi(a+rbox.out_offset), qh_roundi(b+rbox.out_offset), qh_roundi(c+rbox.out_offset));
    +  else
    +    qh_fprintf_rbox(rbox.fout, 9408, qh_REAL_3n, a+rbox.out_offset, b+rbox.out_offset, c+rbox.out_offset);
    +} /* qh_out3n */
    +
    +void qh_outcoord(int iscdd, double *coord, int dim) {
    +    double *p= coord;
    +    int k;
    +
    +    if (iscdd)
    +      qh_out1( 1.0);
    +    for (k=0; k < dim; k++)
    +      qh_out1(*(p++));
    +    qh_fprintf_rbox(rbox.fout, 9396, "\n");
    +} /* qh_outcoord */
    +
    +void qh_outcoincident(int coincidentpoints, double radius, int iscdd, double *coord, int dim) {
    +  double *p;
    +  double randr, delta;
    +  int i,k;
    +  double randmax= qh_RANDOMmax;
    +
    +  for (i= 0; i
      ---------------------------------
    +
    +   stat.c
    +   contains all statistics that are collected for qhull
    +
    +   see qh-stat.htm and stat.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/stat.c#5 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*============ global data structure ==========*/
    +
    +#if qh_QHpointer
    +qhstatT *qh_qhstat=NULL;  /* global data structure */
    +#else
    +qhstatT qh_qhstat;   /* add "={0}" if this causes a compiler error */
    +#endif
    +
    +/*========== functions in alphabetic order ================*/
    +
    +/*---------------------------------
    +
    +  qh_allstatA()
    +    define statistics in groups of 20
    +
    +  notes:
    +    (otherwise, 'gcc -O2' uses too much memory)
    +    uses qhstat.next
    +*/
    +void qh_allstatA(void) {
    +
    +   /* zdef_(type,name,doc,average) */
    +  zzdef_(zdoc, Zdoc2, "precision statistics", -1);
    +  zdef_(zinc, Znewvertex, NULL, -1);
    +  zdef_(wadd, Wnewvertex, "ave. distance of a new vertex to a facet(!0s)", Znewvertex);
    +  zzdef_(wmax, Wnewvertexmax, "max. distance of a new vertex to a facet", -1);
    +  zdef_(wmax, Wvertexmax, "max. distance of an output vertex to a facet", -1);
    +  zdef_(wmin, Wvertexmin, "min. distance of an output vertex to a facet", -1);
    +  zdef_(wmin, Wmindenom, "min. denominator in hyperplane computation", -1);
    +
    +  qhstat precision= qhstat next;  /* call qh_precision for each of these */
    +  zzdef_(zdoc, Zdoc3, "precision problems (corrected unless 'Q0' or an error)", -1);
    +  zzdef_(zinc, Zcoplanarridges, "coplanar half ridges in output", -1);
    +  zzdef_(zinc, Zconcaveridges, "concave half ridges in output", -1);
    +  zzdef_(zinc, Zflippedfacets, "flipped facets", -1);
    +  zzdef_(zinc, Zcoplanarhorizon, "coplanar horizon facets for new vertices", -1);
    +  zzdef_(zinc, Zcoplanarpart, "coplanar points during partitioning", -1);
    +  zzdef_(zinc, Zminnorm, "degenerate hyperplanes recomputed with gaussian elimination", -1);
    +  zzdef_(zinc, Znearlysingular, "nearly singular or axis-parallel hyperplanes", -1);
    +  zzdef_(zinc, Zback0, "zero divisors during back substitute", -1);
    +  zzdef_(zinc, Zgauss0, "zero divisors during gaussian elimination", -1);
    +  zzdef_(zinc, Zmultiridge, "ridges with multiple neighbors", -1);
    +}
    +void qh_allstatB(void) {
    +  zzdef_(zdoc, Zdoc1, "summary information", -1);
    +  zdef_(zinc, Zvertices, "number of vertices in output", -1);
    +  zdef_(zinc, Znumfacets, "number of facets in output", -1);
    +  zdef_(zinc, Znonsimplicial, "number of non-simplicial facets in output", -1);
    +  zdef_(zinc, Znowsimplicial, "number of simplicial facets that were merged", -1);
    +  zdef_(zinc, Znumridges, "number of ridges in output", -1);
    +  zdef_(zadd, Znumridges, "average number of ridges per facet", Znumfacets);
    +  zdef_(zmax, Zmaxridges, "maximum number of ridges", -1);
    +  zdef_(zadd, Znumneighbors, "average number of neighbors per facet", Znumfacets);
    +  zdef_(zmax, Zmaxneighbors, "maximum number of neighbors", -1);
    +  zdef_(zadd, Znumvertices, "average number of vertices per facet", Znumfacets);
    +  zdef_(zmax, Zmaxvertices, "maximum number of vertices", -1);
    +  zdef_(zadd, Znumvneighbors, "average number of neighbors per vertex", Zvertices);
    +  zdef_(zmax, Zmaxvneighbors, "maximum number of neighbors", -1);
    +  zdef_(wadd, Wcpu, "cpu seconds for qhull after input", -1);
    +  zdef_(zinc, Ztotvertices, "vertices created altogether", -1);
    +  zzdef_(zinc, Zsetplane, "facets created altogether", -1);
    +  zdef_(zinc, Ztotridges, "ridges created altogether", -1);
    +  zdef_(zinc, Zpostfacets, "facets before post merge", -1);
    +  zdef_(zadd, Znummergetot, "average merges per facet(at most 511)", Znumfacets);
    +  zdef_(zmax, Znummergemax, "  maximum merges for a facet(at most 511)", -1);
    +  zdef_(zinc, Zangle, NULL, -1);
    +  zdef_(wadd, Wangle, "average angle(cosine) of facet normals for all ridges", Zangle);
    +  zdef_(wmax, Wanglemax, "  maximum angle(cosine) of facet normals across a ridge", -1);
    +  zdef_(wmin, Wanglemin, "  minimum angle(cosine) of facet normals across a ridge", -1);
    +  zdef_(wadd, Wareatot, "total area of facets", -1);
    +  zdef_(wmax, Wareamax, "  maximum facet area", -1);
    +  zdef_(wmin, Wareamin, "  minimum facet area", -1);
    +}
    +void qh_allstatC(void) {
    +  zdef_(zdoc, Zdoc9, "build hull statistics", -1);
    +  zzdef_(zinc, Zprocessed, "points processed", -1);
    +  zzdef_(zinc, Zretry, "retries due to precision problems", -1);
    +  zdef_(wmax, Wretrymax, "  max. random joggle", -1);
    +  zdef_(zmax, Zmaxvertex, "max. vertices at any one time", -1);
    +  zdef_(zinc, Ztotvisible, "ave. visible facets per iteration", Zprocessed);
    +  zdef_(zinc, Zinsidevisible, "  ave. visible facets without an horizon neighbor", Zprocessed);
    +  zdef_(zadd, Zvisfacettot,  "  ave. facets deleted per iteration", Zprocessed);
    +  zdef_(zmax, Zvisfacetmax,  "    maximum", -1);
    +  zdef_(zadd, Zvisvertextot, "ave. visible vertices per iteration", Zprocessed);
    +  zdef_(zmax, Zvisvertexmax, "    maximum", -1);
    +  zdef_(zinc, Ztothorizon, "ave. horizon facets per iteration", Zprocessed);
    +  zdef_(zadd, Znewfacettot,  "ave. new or merged facets per iteration", Zprocessed);
    +  zdef_(zmax, Znewfacetmax,  "    maximum(includes initial simplex)", -1);
    +  zdef_(wadd, Wnewbalance, "average new facet balance", Zprocessed);
    +  zdef_(wadd, Wnewbalance2, "  standard deviation", -1);
    +  zdef_(wadd, Wpbalance, "average partition balance", Zpbalance);
    +  zdef_(wadd, Wpbalance2, "  standard deviation", -1);
    +  zdef_(zinc, Zpbalance, "  number of trials", -1);
    +  zdef_(zinc, Zsearchpoints, "searches of all points for initial simplex", -1);
    +  zdef_(zinc, Zdetsimplex, "determinants computed(area & initial hull)", -1);
    +  zdef_(zinc, Znoarea, "determinants not computed because vertex too low", -1);
    +  zdef_(zinc, Znotmax, "points ignored(!above max_outside)", -1);
    +  zdef_(zinc, Znotgood, "points ignored(!above a good facet)", -1);
    +  zdef_(zinc, Znotgoodnew, "points ignored(didn't create a good new facet)", -1);
    +  zdef_(zinc, Zgoodfacet, "good facets found", -1);
    +  zzdef_(zinc, Znumvisibility, "distance tests for facet visibility", -1);
    +  zdef_(zinc, Zdistvertex, "distance tests to report minimum vertex", -1);
    +  zzdef_(zinc, Ztotcheck, "points checked for facets' outer planes", -1);
    +  zzdef_(zinc, Zcheckpart, "  ave. distance tests per check", Ztotcheck);
    +}
    +void qh_allstatD(void) {
    +  zdef_(zinc, Zvisit, "resets of visit_id", -1);
    +  zdef_(zinc, Zvvisit, "  resets of vertex_visit", -1);
    +  zdef_(zmax, Zvisit2max, "  max visit_id/2", -1);
    +  zdef_(zmax, Zvvisit2max, "  max vertex_visit/2", -1);
    +
    +  zdef_(zdoc, Zdoc4, "partitioning statistics(see previous for outer planes)", -1);
    +  zzdef_(zadd, Zdelvertextot, "total vertices deleted", -1);
    +  zdef_(zmax, Zdelvertexmax, "    maximum vertices deleted per iteration", -1);
    +  zdef_(zinc, Zfindbest, "calls to findbest", -1);
    +  zdef_(zadd, Zfindbesttot, " ave. facets tested", Zfindbest);
    +  zdef_(zmax, Zfindbestmax, " max. facets tested", -1);
    +  zdef_(zadd, Zfindcoplanar, " ave. coplanar search", Zfindbest);
    +  zdef_(zinc, Zfindnew, "calls to findbestnew", -1);
    +  zdef_(zadd, Zfindnewtot, " ave. facets tested", Zfindnew);
    +  zdef_(zmax, Zfindnewmax, " max. facets tested", -1);
    +  zdef_(zinc, Zfindnewjump, " ave. clearly better", Zfindnew);
    +  zdef_(zinc, Zfindnewsharp, " calls due to qh_sharpnewfacets", -1);
    +  zdef_(zinc, Zfindhorizon, "calls to findhorizon", -1);
    +  zdef_(zadd, Zfindhorizontot, " ave. facets tested", Zfindhorizon);
    +  zdef_(zmax, Zfindhorizonmax, " max. facets tested", -1);
    +  zdef_(zinc, Zfindjump,       " ave. clearly better", Zfindhorizon);
    +  zdef_(zinc, Zparthorizon, " horizon facets better than bestfacet", -1);
    +  zdef_(zinc, Zpartangle, "angle tests for repartitioned coplanar points", -1);
    +  zdef_(zinc, Zpartflip, "  repartitioned coplanar points for flipped orientation", -1);
    +}
    +void qh_allstatE(void) {
    +  zdef_(zinc, Zpartinside, "inside points", -1);
    +  zdef_(zinc, Zpartnear, "  inside points kept with a facet", -1);
    +  zdef_(zinc, Zcoplanarinside, "  inside points that were coplanar with a facet", -1);
    +  zdef_(zinc, Zbestlower, "calls to findbestlower", -1);
    +  zdef_(zinc, Zbestlowerv, "  with search of vertex neighbors", -1);
    +  zdef_(zinc, Zbestlowerall, "  with rare search of all facets", -1);
    +  zdef_(zmax, Zbestloweralln, "  facets per search of all facets", -1);
    +  zdef_(wadd, Wmaxout, "difference in max_outside at final check", -1);
    +  zzdef_(zinc, Zpartitionall, "distance tests for initial partition", -1);
    +  zdef_(zinc, Ztotpartition, "partitions of a point", -1);
    +  zzdef_(zinc, Zpartition, "distance tests for partitioning", -1);
    +  zzdef_(zinc, Zdistcheck, "distance tests for checking flipped facets", -1);
    +  zzdef_(zinc, Zdistconvex, "distance tests for checking convexity", -1);
    +  zdef_(zinc, Zdistgood, "distance tests for checking good point", -1);
    +  zdef_(zinc, Zdistio, "distance tests for output", -1);
    +  zdef_(zinc, Zdiststat, "distance tests for statistics", -1);
    +  zdef_(zinc, Zdistplane, "total number of distance tests", -1);
    +  zdef_(zinc, Ztotpartcoplanar, "partitions of coplanar points or deleted vertices", -1);
    +  zzdef_(zinc, Zpartcoplanar, "   distance tests for these partitions", -1);
    +  zdef_(zinc, Zcomputefurthest, "distance tests for computing furthest", -1);
    +}
    +void qh_allstatE2(void) {
    +  zdef_(zdoc, Zdoc5, "statistics for matching ridges", -1);
    +  zdef_(zinc, Zhashlookup, "total lookups for matching ridges of new facets", -1);
    +  zdef_(zinc, Zhashtests, "average number of tests to match a ridge", Zhashlookup);
    +  zdef_(zinc, Zhashridge, "total lookups of subridges(duplicates and boundary)", -1);
    +  zdef_(zinc, Zhashridgetest, "average number of tests per subridge", Zhashridge);
    +  zdef_(zinc, Zdupsame, "duplicated ridges in same merge cycle", -1);
    +  zdef_(zinc, Zdupflip, "duplicated ridges with flipped facets", -1);
    +
    +  zdef_(zdoc, Zdoc6, "statistics for determining merges", -1);
    +  zdef_(zinc, Zangletests, "angles computed for ridge convexity", -1);
    +  zdef_(zinc, Zbestcentrum, "best merges used centrum instead of vertices",-1);
    +  zzdef_(zinc, Zbestdist, "distance tests for best merge", -1);
    +  zzdef_(zinc, Zcentrumtests, "distance tests for centrum convexity", -1);
    +  zzdef_(zinc, Zdistzero, "distance tests for checking simplicial convexity", -1);
    +  zdef_(zinc, Zcoplanarangle, "coplanar angles in getmergeset", -1);
    +  zdef_(zinc, Zcoplanarcentrum, "coplanar centrums in getmergeset", -1);
    +  zdef_(zinc, Zconcaveridge, "concave ridges in getmergeset", -1);
    +}
    +void qh_allstatF(void) {
    +  zdef_(zdoc, Zdoc7, "statistics for merging", -1);
    +  zdef_(zinc, Zpremergetot, "merge iterations", -1);
    +  zdef_(zadd, Zmergeinittot, "ave. initial non-convex ridges per iteration", Zpremergetot);
    +  zdef_(zadd, Zmergeinitmax, "  maximum", -1);
    +  zdef_(zadd, Zmergesettot, "  ave. additional non-convex ridges per iteration", Zpremergetot);
    +  zdef_(zadd, Zmergesetmax, "  maximum additional in one pass", -1);
    +  zdef_(zadd, Zmergeinittot2, "initial non-convex ridges for post merging", -1);
    +  zdef_(zadd, Zmergesettot2, "  additional non-convex ridges", -1);
    +  zdef_(wmax, Wmaxoutside, "max distance of vertex or coplanar point above facet(w/roundoff)", -1);
    +  zdef_(wmin, Wminvertex, "max distance of merged vertex below facet(or roundoff)", -1);
    +  zdef_(zinc, Zwidefacet, "centrums frozen due to a wide merge", -1);
    +  zdef_(zinc, Zwidevertices, "centrums frozen due to extra vertices", -1);
    +  zzdef_(zinc, Ztotmerge, "total number of facets or cycles of facets merged", -1);
    +  zdef_(zinc, Zmergesimplex, "merged a simplex", -1);
    +  zdef_(zinc, Zonehorizon, "simplices merged into coplanar horizon", -1);
    +  zzdef_(zinc, Zcyclehorizon, "cycles of facets merged into coplanar horizon", -1);
    +  zzdef_(zadd, Zcyclefacettot, "  ave. facets per cycle", Zcyclehorizon);
    +  zdef_(zmax, Zcyclefacetmax, "  max. facets", -1);
    +  zdef_(zinc, Zmergeintohorizon, "new facets merged into horizon", -1);
    +  zdef_(zinc, Zmergenew, "new facets merged", -1);
    +  zdef_(zinc, Zmergehorizon, "horizon facets merged into new facets", -1);
    +  zdef_(zinc, Zmergevertex, "vertices deleted by merging", -1);
    +  zdef_(zinc, Zcyclevertex, "vertices deleted by merging into coplanar horizon", -1);
    +  zdef_(zinc, Zdegenvertex, "vertices deleted by degenerate facet", -1);
    +  zdef_(zinc, Zmergeflipdup, "merges due to flipped facets in duplicated ridge", -1);
    +  zdef_(zinc, Zneighbor, "merges due to redundant neighbors", -1);
    +  zdef_(zadd, Ztestvneighbor, "non-convex vertex neighbors", -1);
    +}
    +void qh_allstatG(void) {
    +  zdef_(zinc, Zacoplanar, "merges due to angle coplanar facets", -1);
    +  zdef_(wadd, Wacoplanartot, "  average merge distance", Zacoplanar);
    +  zdef_(wmax, Wacoplanarmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zcoplanar, "merges due to coplanar facets", -1);
    +  zdef_(wadd, Wcoplanartot, "  average merge distance", Zcoplanar);
    +  zdef_(wmax, Wcoplanarmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zconcave, "merges due to concave facets", -1);
    +  zdef_(wadd, Wconcavetot, "  average merge distance", Zconcave);
    +  zdef_(wmax, Wconcavemax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zavoidold, "coplanar/concave merges due to avoiding old merge", -1);
    +  zdef_(wadd, Wavoidoldtot, "  average merge distance", Zavoidold);
    +  zdef_(wmax, Wavoidoldmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zdegen, "merges due to degenerate facets", -1);
    +  zdef_(wadd, Wdegentot, "  average merge distance", Zdegen);
    +  zdef_(wmax, Wdegenmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zflipped, "merges due to removing flipped facets", -1);
    +  zdef_(wadd, Wflippedtot, "  average merge distance", Zflipped);
    +  zdef_(wmax, Wflippedmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zduplicate, "merges due to duplicated ridges", -1);
    +  zdef_(wadd, Wduplicatetot, "  average merge distance", Zduplicate);
    +  zdef_(wmax, Wduplicatemax, "  maximum merge distance", -1);
    +}
    +void qh_allstatH(void) {
    +  zdef_(zdoc, Zdoc8, "renamed vertex statistics", -1);
    +  zdef_(zinc, Zrenameshare, "renamed vertices shared by two facets", -1);
    +  zdef_(zinc, Zrenamepinch, "renamed vertices in a pinched facet", -1);
    +  zdef_(zinc, Zrenameall, "renamed vertices shared by multiple facets", -1);
    +  zdef_(zinc, Zfindfail, "rename failures due to duplicated ridges", -1);
    +  zdef_(zinc, Zdupridge, "  duplicate ridges detected", -1);
    +  zdef_(zinc, Zdelridge, "deleted ridges due to renamed vertices", -1);
    +  zdef_(zinc, Zdropneighbor, "dropped neighbors due to renamed vertices", -1);
    +  zdef_(zinc, Zdropdegen, "degenerate facets due to dropped neighbors", -1);
    +  zdef_(zinc, Zdelfacetdup, "  facets deleted because of no neighbors", -1);
    +  zdef_(zinc, Zremvertex, "vertices removed from facets due to no ridges", -1);
    +  zdef_(zinc, Zremvertexdel, "  deleted", -1);
    +  zdef_(zinc, Zintersectnum, "vertex intersections for locating redundant vertices", -1);
    +  zdef_(zinc, Zintersectfail, "intersections failed to find a redundant vertex", -1);
    +  zdef_(zinc, Zintersect, "intersections found redundant vertices", -1);
    +  zdef_(zadd, Zintersecttot, "   ave. number found per vertex", Zintersect);
    +  zdef_(zmax, Zintersectmax, "   max. found for a vertex", -1);
    +  zdef_(zinc, Zvertexridge, NULL, -1);
    +  zdef_(zadd, Zvertexridgetot, "  ave. number of ridges per tested vertex", Zvertexridge);
    +  zdef_(zmax, Zvertexridgemax, "  max. number of ridges per tested vertex", -1);
    +
    +  zdef_(zdoc, Zdoc10, "memory usage statistics(in bytes)", -1);
    +  zdef_(zadd, Zmemfacets, "for facets and their normals, neighbor and vertex sets", -1);
    +  zdef_(zadd, Zmemvertices, "for vertices and their neighbor sets", -1);
    +  zdef_(zadd, Zmempoints, "for input points and outside and coplanar sets",-1);
    +  zdef_(zadd, Zmemridges, "for ridges and their vertex sets", -1);
    +} /* allstat */
    +
    +void qh_allstatI(void) {
    +  qhstat vridges= qhstat next;
    +  zzdef_(zdoc, Zdoc11, "Voronoi ridge statistics", -1);
    +  zzdef_(zinc, Zridge, "non-simplicial Voronoi vertices for all ridges", -1);
    +  zzdef_(wadd, Wridge, "  ave. distance to ridge", Zridge);
    +  zzdef_(wmax, Wridgemax, "  max. distance to ridge", -1);
    +  zzdef_(zinc, Zridgemid, "bounded ridges", -1);
    +  zzdef_(wadd, Wridgemid, "  ave. distance of midpoint to ridge", Zridgemid);
    +  zzdef_(wmax, Wridgemidmax, "  max. distance of midpoint to ridge", -1);
    +  zzdef_(zinc, Zridgeok, "bounded ridges with ok normal", -1);
    +  zzdef_(wadd, Wridgeok, "  ave. angle to ridge", Zridgeok);
    +  zzdef_(wmax, Wridgeokmax, "  max. angle to ridge", -1);
    +  zzdef_(zinc, Zridge0, "bounded ridges with near-zero normal", -1);
    +  zzdef_(wadd, Wridge0, "  ave. angle to ridge", Zridge0);
    +  zzdef_(wmax, Wridge0max, "  max. angle to ridge", -1);
    +
    +  zdef_(zdoc, Zdoc12, "Triangulation statistics(Qt)", -1);
    +  zdef_(zinc, Ztricoplanar, "non-simplicial facets triangulated", -1);
    +  zdef_(zadd, Ztricoplanartot, "  ave. new facets created(may be deleted)", Ztricoplanar);
    +  zdef_(zmax, Ztricoplanarmax, "  max. new facets created", -1);
    +  zdef_(zinc, Ztrinull, "null new facets deleted(duplicated vertex)", -1);
    +  zdef_(zinc, Ztrimirror, "mirrored pairs of new facets deleted(same vertices)", -1);
    +  zdef_(zinc, Ztridegen, "degenerate new facets in output(same ridge)", -1);
    +} /* allstat */
    +
    +/*---------------------------------
    +
    +  qh_allstatistics()
    +    reset printed flag for all statistics
    +*/
    +void qh_allstatistics(void) {
    +  int i;
    +
    +  for(i=ZEND; i--; )
    +    qhstat printed[i]= False;
    +} /* allstatistics */
    +
    +#if qh_KEEPstatistics
    +/*---------------------------------
    +
    +  qh_collectstatistics()
    +    collect statistics for qh.facet_list
    +
    +*/
    +void qh_collectstatistics(void) {
    +  facetT *facet, *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  realT dotproduct, dist;
    +  int sizneighbors, sizridges, sizvertices, i;
    +
    +  qh old_randomdist= qh RANDOMdist;
    +  qh RANDOMdist= False;
    +  zval_(Zmempoints)= qh num_points * qh normal_size +
    +                             sizeof(qhT) + sizeof(qhstatT);
    +  zval_(Zmemfacets)= 0;
    +  zval_(Zmemridges)= 0;
    +  zval_(Zmemvertices)= 0;
    +  zval_(Zangle)= 0;
    +  wval_(Wangle)= 0.0;
    +  zval_(Znumridges)= 0;
    +  zval_(Znumfacets)= 0;
    +  zval_(Znumneighbors)= 0;
    +  zval_(Znumvertices)= 0;
    +  zval_(Znumvneighbors)= 0;
    +  zval_(Znummergetot)= 0;
    +  zval_(Znummergemax)= 0;
    +  zval_(Zvertices)= qh num_vertices - qh_setsize(qh del_vertices);
    +  if (qh MERGING || qh APPROXhull || qh JOGGLEmax < REALmax/2)
    +    wmax_(Wmaxoutside, qh max_outside);
    +  if (qh MERGING)
    +    wmin_(Wminvertex, qh min_vertex);
    +  FORALLfacets
    +    facet->seen= False;
    +  if (qh DELAUNAY) {
    +    FORALLfacets {
    +      if (facet->upperdelaunay != qh UPPERdelaunay)
    +        facet->seen= True; /* remove from angle statistics */
    +    }
    +  }
    +  FORALLfacets {
    +    if (facet->visible && qh NEWfacets)
    +      continue;
    +    sizvertices= qh_setsize(facet->vertices);
    +    sizneighbors= qh_setsize(facet->neighbors);
    +    sizridges= qh_setsize(facet->ridges);
    +    zinc_(Znumfacets);
    +    zadd_(Znumvertices, sizvertices);
    +    zmax_(Zmaxvertices, sizvertices);
    +    zadd_(Znumneighbors, sizneighbors);
    +    zmax_(Zmaxneighbors, sizneighbors);
    +    zadd_(Znummergetot, facet->nummerge);
    +    i= facet->nummerge; /* avoid warnings */
    +    zmax_(Znummergemax, i);
    +    if (!facet->simplicial) {
    +      if (sizvertices == qh hull_dim) {
    +        zinc_(Znowsimplicial);
    +      }else {
    +        zinc_(Znonsimplicial);
    +      }
    +    }
    +    if (sizridges) {
    +      zadd_(Znumridges, sizridges);
    +      zmax_(Zmaxridges, sizridges);
    +    }
    +    zadd_(Zmemfacets, sizeof(facetT) + qh normal_size + 2*sizeof(setT)
    +       + SETelemsize * (sizneighbors + sizvertices));
    +    if (facet->ridges) {
    +      zadd_(Zmemridges,
    +         sizeof(setT) + SETelemsize * sizridges + sizridges *
    +         (sizeof(ridgeT) + sizeof(setT) + SETelemsize * (qh hull_dim-1))/2);
    +    }
    +    if (facet->outsideset)
    +      zadd_(Zmempoints, sizeof(setT) + SETelemsize * qh_setsize(facet->outsideset));
    +    if (facet->coplanarset)
    +      zadd_(Zmempoints, sizeof(setT) + SETelemsize * qh_setsize(facet->coplanarset));
    +    if (facet->seen) /* Delaunay upper envelope */
    +      continue;
    +    facet->seen= True;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor == qh_DUPLICATEridge || neighbor == qh_MERGEridge
    +          || neighbor->seen || !facet->normal || !neighbor->normal)
    +        continue;
    +      dotproduct= qh_getangle(facet->normal, neighbor->normal);
    +      zinc_(Zangle);
    +      wadd_(Wangle, dotproduct);
    +      wmax_(Wanglemax, dotproduct)
    +      wmin_(Wanglemin, dotproduct)
    +    }
    +    if (facet->normal) {
    +      FOREACHvertex_(facet->vertices) {
    +        zinc_(Zdiststat);
    +        qh_distplane(vertex->point, facet, &dist);
    +        wmax_(Wvertexmax, dist);
    +        wmin_(Wvertexmin, dist);
    +      }
    +    }
    +  }
    +  FORALLvertices {
    +    if (vertex->deleted)
    +      continue;
    +    zadd_(Zmemvertices, sizeof(vertexT));
    +    if (vertex->neighbors) {
    +      sizneighbors= qh_setsize(vertex->neighbors);
    +      zadd_(Znumvneighbors, sizneighbors);
    +      zmax_(Zmaxvneighbors, sizneighbors);
    +      zadd_(Zmemvertices, sizeof(vertexT) + SETelemsize * sizneighbors);
    +    }
    +  }
    +  qh RANDOMdist= qh old_randomdist;
    +} /* collectstatistics */
    +#endif /* qh_KEEPstatistics */
    +
    +/*---------------------------------
    +
    +  qh_freestatistics(  )
    +    free memory used for statistics
    +*/
    +void qh_freestatistics(void) {
    +
    +#if qh_QHpointer
    +  qh_free(qh_qhstat);
    +  qh_qhstat= NULL;
    +#endif
    +} /* freestatistics */
    +
    +/*---------------------------------
    +
    +  qh_initstatistics(  )
    +    allocate and initialize statistics
    +
    +  notes:
    +    uses qh_malloc() instead of qh_memalloc() since mem.c not set up yet
    +    NOerrors -- qh_initstatistics can not use qh_errexit(), qh_fprintf, or qh.ferr
    +    On first call, only qhmem.ferr is defined.  qh_memalloc is not setup.
    +    Also invoked by QhullQh().
    +*/
    +void qh_initstatistics(void) {
    +  int i;
    +  realT realx;
    +  int intx;
    +
    +#if qh_QHpointer
    +  if(qh_qhstat){  /* qh_initstatistics may be called from Qhull::resetStatistics() */
    +      qh_free(qh_qhstat);
    +      qh_qhstat= 0;
    +  }
    +  if (!(qh_qhstat= (qhstatT *)qh_malloc(sizeof(qhstatT)))) {
    +    qh_fprintf_stderr(6183, "qhull error (qh_initstatistics): insufficient memory\n");
    +    qh_exit(qh_ERRmem);  /* can not use qh_errexit() */
    +  }
    +#endif
    +
    +  qhstat next= 0;
    +  qh_allstatA();
    +  qh_allstatB();
    +  qh_allstatC();
    +  qh_allstatD();
    +  qh_allstatE();
    +  qh_allstatE2();
    +  qh_allstatF();
    +  qh_allstatG();
    +  qh_allstatH();
    +  qh_allstatI();
    +  if (qhstat next > (int)sizeof(qhstat id)) {
    +    qh_fprintf(qhmem.ferr, 6184, "qhull error (qh_initstatistics): increase size of qhstat.id[].\n\
    +      qhstat.next %d should be <= sizeof(qhstat id) %d\n", qhstat next, (int)sizeof(qhstat id));
    +#if 0 /* for locating error, Znumridges should be duplicated */
    +    for(i=0; i < ZEND; i++) {
    +      int j;
    +      for(j=i+1; j < ZEND; j++) {
    +        if (qhstat id[i] == qhstat id[j]) {
    +          qh_fprintf(qhmem.ferr, 6185, "qhull error (qh_initstatistics): duplicated statistic %d at indices %d and %d\n",
    +              qhstat id[i], i, j);
    +        }
    +      }
    +    }
    +#endif
    +    qh_exit(qh_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  qhstat init[zinc].i= 0;
    +  qhstat init[zadd].i= 0;
    +  qhstat init[zmin].i= INT_MAX;
    +  qhstat init[zmax].i= INT_MIN;
    +  qhstat init[wadd].r= 0;
    +  qhstat init[wmin].r= REALmax;
    +  qhstat init[wmax].r= -REALmax;
    +  for(i=0; i < ZEND; i++) {
    +    if (qhstat type[i] > ZTYPEreal) {
    +      realx= qhstat init[(unsigned char)(qhstat type[i])].r;
    +      qhstat stats[i].r= realx;
    +    }else if (qhstat type[i] != zdoc) {
    +      intx= qhstat init[(unsigned char)(qhstat type[i])].i;
    +      qhstat stats[i].i= intx;
    +    }
    +  }
    +} /* initstatistics */
    +
    +/*---------------------------------
    +
    +  qh_newstats(  )
    +    returns True if statistics for zdoc
    +
    +  returns:
    +    next zdoc
    +*/
    +boolT qh_newstats(int idx, int *nextindex) {
    +  boolT isnew= False;
    +  int start, i;
    +
    +  if (qhstat type[qhstat id[idx]] == zdoc)
    +    start= idx+1;
    +  else
    +    start= idx;
    +  for(i= start; i < qhstat next && qhstat type[qhstat id[i]] != zdoc; i++) {
    +    if (!qh_nostatistic(qhstat id[i]) && !qhstat printed[qhstat id[i]])
    +        isnew= True;
    +  }
    +  *nextindex= i;
    +  return isnew;
    +} /* newstats */
    +
    +/*---------------------------------
    +
    +  qh_nostatistic( index )
    +    true if no statistic to print
    +*/
    +boolT qh_nostatistic(int i) {
    +
    +  if ((qhstat type[i] > ZTYPEreal
    +       &&qhstat stats[i].r == qhstat init[(unsigned char)(qhstat type[i])].r)
    +      || (qhstat type[i] < ZTYPEreal
    +          &&qhstat stats[i].i == qhstat init[(unsigned char)(qhstat type[i])].i))
    +    return True;
    +  return False;
    +} /* nostatistic */
    +
    +#if qh_KEEPstatistics
    +/*---------------------------------
    +
    +  qh_printallstatistics( fp, string )
    +    print all statistics with header 'string'
    +*/
    +void qh_printallstatistics(FILE *fp, const char *string) {
    +
    +  qh_allstatistics();
    +  qh_collectstatistics();
    +  qh_printstatistics(fp, string);
    +  qh_memstatistics(fp);
    +}
    +
    +
    +/*---------------------------------
    +
    +  qh_printstatistics( fp, string )
    +    print statistics to a file with header 'string'
    +    skips statistics with qhstat.printed[] (reset with qh_allstatistics)
    +
    +  see:
    +    qh_printallstatistics()
    +*/
    +void qh_printstatistics(FILE *fp, const char *string) {
    +  int i, k;
    +  realT ave;
    +
    +  if (qh num_points != qh num_vertices) {
    +    wval_(Wpbalance)= 0;
    +    wval_(Wpbalance2)= 0;
    +  }else
    +    wval_(Wpbalance2)= qh_stddev(zval_(Zpbalance), wval_(Wpbalance),
    +                                 wval_(Wpbalance2), &ave);
    +  wval_(Wnewbalance2)= qh_stddev(zval_(Zprocessed), wval_(Wnewbalance),
    +                                 wval_(Wnewbalance2), &ave);
    +  qh_fprintf(fp, 9350, "\n\
    +%s\n\
    + qhull invoked by: %s | %s\n%s with options:\n%s\n", string, qh rbox_command,
    +     qh qhull_command, qh_version, qh qhull_options);
    +  qh_fprintf(fp, 9351, "\nprecision constants:\n\
    + %6.2g max. abs. coordinate in the (transformed) input('Qbd:n')\n\
    + %6.2g max. roundoff error for distance computation('En')\n\
    + %6.2g max. roundoff error for angle computations\n\
    + %6.2g min. distance for outside points ('Wn')\n\
    + %6.2g min. distance for visible facets ('Vn')\n\
    + %6.2g max. distance for coplanar facets ('Un')\n\
    + %6.2g max. facet width for recomputing centrum and area\n\
    +",
    +  qh MAXabs_coord, qh DISTround, qh ANGLEround, qh MINoutside,
    +        qh MINvisible, qh MAXcoplanar, qh WIDEfacet);
    +  if (qh KEEPnearinside)
    +    qh_fprintf(fp, 9352, "\
    + %6.2g max. distance for near-inside points\n", qh NEARinside);
    +  if (qh premerge_cos < REALmax/2) qh_fprintf(fp, 9353, "\
    + %6.2g max. cosine for pre-merge angle\n", qh premerge_cos);
    +  if (qh PREmerge) qh_fprintf(fp, 9354, "\
    + %6.2g radius of pre-merge centrum\n", qh premerge_centrum);
    +  if (qh postmerge_cos < REALmax/2) qh_fprintf(fp, 9355, "\
    + %6.2g max. cosine for post-merge angle\n", qh postmerge_cos);
    +  if (qh POSTmerge) qh_fprintf(fp, 9356, "\
    + %6.2g radius of post-merge centrum\n", qh postmerge_centrum);
    +  qh_fprintf(fp, 9357, "\
    + %6.2g max. distance for merging two simplicial facets\n\
    + %6.2g max. roundoff error for arithmetic operations\n\
    + %6.2g min. denominator for divisions\n\
    +  zero diagonal for Gauss: ", qh ONEmerge, REALepsilon, qh MINdenom);
    +  for(k=0; k < qh hull_dim; k++)
    +    qh_fprintf(fp, 9358, "%6.2e ", qh NEARzero[k]);
    +  qh_fprintf(fp, 9359, "\n\n");
    +  for(i=0 ; i < qhstat next; )
    +    qh_printstats(fp, i, &i);
    +} /* printstatistics */
    +#endif /* qh_KEEPstatistics */
    +
    +/*---------------------------------
    +
    +  qh_printstatlevel( fp, id )
    +    print level information for a statistic
    +
    +  notes:
    +    nop if id >= ZEND, printed, or same as initial value
    +*/
    +void qh_printstatlevel(FILE *fp, int id) {
    +#define NULLfield "       "
    +
    +  if (id >= ZEND || qhstat printed[id])
    +    return;
    +  if (qhstat type[id] == zdoc) {
    +    qh_fprintf(fp, 9360, "%s\n", qhstat doc[id]);
    +    return;
    +  }
    +  if (qh_nostatistic(id) || !qhstat doc[id])
    +    return;
    +  qhstat printed[id]= True;
    +  if (qhstat count[id] != -1
    +      && qhstat stats[(unsigned char)(qhstat count[id])].i == 0)
    +    qh_fprintf(fp, 9361, " *0 cnt*");
    +  else if (qhstat type[id] >= ZTYPEreal && qhstat count[id] == -1)
    +    qh_fprintf(fp, 9362, "%7.2g", qhstat stats[id].r);
    +  else if (qhstat type[id] >= ZTYPEreal && qhstat count[id] != -1)
    +    qh_fprintf(fp, 9363, "%7.2g", qhstat stats[id].r/ qhstat stats[(unsigned char)(qhstat count[id])].i);
    +  else if (qhstat type[id] < ZTYPEreal && qhstat count[id] == -1)
    +    qh_fprintf(fp, 9364, "%7d", qhstat stats[id].i);
    +  else if (qhstat type[id] < ZTYPEreal && qhstat count[id] != -1)
    +    qh_fprintf(fp, 9365, "%7.3g", (realT) qhstat stats[id].i / qhstat stats[(unsigned char)(qhstat count[id])].i);
    +  qh_fprintf(fp, 9366, " %s\n", qhstat doc[id]);
    +} /* printstatlevel */
    +
    +
    +/*---------------------------------
    +
    +  qh_printstats( fp, index, nextindex )
    +    print statistics for a zdoc group
    +
    +  returns:
    +    next zdoc if non-null
    +*/
    +void qh_printstats(FILE *fp, int idx, int *nextindex) {
    +  int j, nexti;
    +
    +  if (qh_newstats(idx, &nexti)) {
    +    qh_fprintf(fp, 9367, "\n");
    +    for (j=idx; j--------------------------------
    +
    +  qh_stddev( num, tot, tot2, ave )
    +    compute the standard deviation and average from statistics
    +
    +    tot2 is the sum of the squares
    +  notes:
    +    computes r.m.s.:
    +      (x-ave)^2
    +      == x^2 - 2x tot/num +   (tot/num)^2
    +      == tot2 - 2 tot tot/num + tot tot/num
    +      == tot2 - tot ave
    +*/
    +realT qh_stddev(int num, realT tot, realT tot2, realT *ave) {
    +  realT stddev;
    +
    +  *ave= tot/num;
    +  stddev= sqrt(tot2/num - *ave * *ave);
    +  return stddev;
    +} /* stddev */
    +
    +#endif /* qh_KEEPstatistics */
    +
    +#if !qh_KEEPstatistics
    +void    qh_collectstatistics(void) {}
    +void    qh_printallstatistics(FILE *fp, char *string) {};
    +void    qh_printstatistics(FILE *fp, char *string) {}
    +#endif
    +
    diff --git a/xs/src/qhull/src/libqhull/stat.h b/xs/src/qhull/src/libqhull/stat.h
    new file mode 100644
    index 0000000000..d86fc0a87a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/stat.h
    @@ -0,0 +1,543 @@
    +/*
      ---------------------------------
    +
    +   stat.h
    +     contains all statistics that are collected for qhull
    +
    +   see qh-stat.htm and stat.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/stat.h#4 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +
    +   recompile qhull if you change this file
    +
    +   Integer statistics are Z* while real statistics are W*.
    +
    +   define maydebugx to call a routine at every statistic event
    +
    +*/
    +
    +#ifndef qhDEFstat
    +#define qhDEFstat 1
    +
    +#include "libqhull.h"
    +
    +/*---------------------------------
    +
    +  qh_KEEPstatistics
    +    0 turns off statistic gathering (except zzdef/zzinc/zzadd/zzval/wwval)
    +*/
    +#ifndef qh_KEEPstatistics
    +#define qh_KEEPstatistics 1
    +#endif
    +
    +/*---------------------------------
    +
    +  Zxxx for integers, Wxxx for reals
    +
    +  notes:
    +    be sure that all statistics are defined in stat.c
    +      otherwise initialization may core dump
    +    can pick up all statistics by:
    +      grep '[zw].*_[(][ZW]' *.c >z.x
    +    remove trailers with query">-
    +    remove leaders with  query-replace-regexp [ ^I]+  (
    +*/
    +#if qh_KEEPstatistics
    +enum qh_statistics {     /* alphabetical after Z/W */
    +    Zacoplanar,
    +    Wacoplanarmax,
    +    Wacoplanartot,
    +    Zangle,
    +    Wangle,
    +    Wanglemax,
    +    Wanglemin,
    +    Zangletests,
    +    Wareatot,
    +    Wareamax,
    +    Wareamin,
    +    Zavoidold,
    +    Wavoidoldmax,
    +    Wavoidoldtot,
    +    Zback0,
    +    Zbestcentrum,
    +    Zbestdist,
    +    Zbestlower,
    +    Zbestlowerall,
    +    Zbestloweralln,
    +    Zbestlowerv,
    +    Zcentrumtests,
    +    Zcheckpart,
    +    Zcomputefurthest,
    +    Zconcave,
    +    Wconcavemax,
    +    Wconcavetot,
    +    Zconcaveridges,
    +    Zconcaveridge,
    +    Zcoplanar,
    +    Wcoplanarmax,
    +    Wcoplanartot,
    +    Zcoplanarangle,
    +    Zcoplanarcentrum,
    +    Zcoplanarhorizon,
    +    Zcoplanarinside,
    +    Zcoplanarpart,
    +    Zcoplanarridges,
    +    Wcpu,
    +    Zcyclefacetmax,
    +    Zcyclefacettot,
    +    Zcyclehorizon,
    +    Zcyclevertex,
    +    Zdegen,
    +    Wdegenmax,
    +    Wdegentot,
    +    Zdegenvertex,
    +    Zdelfacetdup,
    +    Zdelridge,
    +    Zdelvertextot,
    +    Zdelvertexmax,
    +    Zdetsimplex,
    +    Zdistcheck,
    +    Zdistconvex,
    +    Zdistgood,
    +    Zdistio,
    +    Zdistplane,
    +    Zdiststat,
    +    Zdistvertex,
    +    Zdistzero,
    +    Zdoc1,
    +    Zdoc2,
    +    Zdoc3,
    +    Zdoc4,
    +    Zdoc5,
    +    Zdoc6,
    +    Zdoc7,
    +    Zdoc8,
    +    Zdoc9,
    +    Zdoc10,
    +    Zdoc11,
    +    Zdoc12,
    +    Zdropdegen,
    +    Zdropneighbor,
    +    Zdupflip,
    +    Zduplicate,
    +    Wduplicatemax,
    +    Wduplicatetot,
    +    Zdupridge,
    +    Zdupsame,
    +    Zflipped,
    +    Wflippedmax,
    +    Wflippedtot,
    +    Zflippedfacets,
    +    Zfindbest,
    +    Zfindbestmax,
    +    Zfindbesttot,
    +    Zfindcoplanar,
    +    Zfindfail,
    +    Zfindhorizon,
    +    Zfindhorizonmax,
    +    Zfindhorizontot,
    +    Zfindjump,
    +    Zfindnew,
    +    Zfindnewmax,
    +    Zfindnewtot,
    +    Zfindnewjump,
    +    Zfindnewsharp,
    +    Zgauss0,
    +    Zgoodfacet,
    +    Zhashlookup,
    +    Zhashridge,
    +    Zhashridgetest,
    +    Zhashtests,
    +    Zinsidevisible,
    +    Zintersect,
    +    Zintersectfail,
    +    Zintersectmax,
    +    Zintersectnum,
    +    Zintersecttot,
    +    Zmaxneighbors,
    +    Wmaxout,
    +    Wmaxoutside,
    +    Zmaxridges,
    +    Zmaxvertex,
    +    Zmaxvertices,
    +    Zmaxvneighbors,
    +    Zmemfacets,
    +    Zmempoints,
    +    Zmemridges,
    +    Zmemvertices,
    +    Zmergeflipdup,
    +    Zmergehorizon,
    +    Zmergeinittot,
    +    Zmergeinitmax,
    +    Zmergeinittot2,
    +    Zmergeintohorizon,
    +    Zmergenew,
    +    Zmergesettot,
    +    Zmergesetmax,
    +    Zmergesettot2,
    +    Zmergesimplex,
    +    Zmergevertex,
    +    Wmindenom,
    +    Wminvertex,
    +    Zminnorm,
    +    Zmultiridge,
    +    Znearlysingular,
    +    Zneighbor,
    +    Wnewbalance,
    +    Wnewbalance2,
    +    Znewfacettot,
    +    Znewfacetmax,
    +    Znewvertex,
    +    Wnewvertex,
    +    Wnewvertexmax,
    +    Znoarea,
    +    Znonsimplicial,
    +    Znowsimplicial,
    +    Znotgood,
    +    Znotgoodnew,
    +    Znotmax,
    +    Znumfacets,
    +    Znummergemax,
    +    Znummergetot,
    +    Znumneighbors,
    +    Znumridges,
    +    Znumvertices,
    +    Znumvisibility,
    +    Znumvneighbors,
    +    Zonehorizon,
    +    Zpartangle,
    +    Zpartcoplanar,
    +    Zpartflip,
    +    Zparthorizon,
    +    Zpartinside,
    +    Zpartition,
    +    Zpartitionall,
    +    Zpartnear,
    +    Zpbalance,
    +    Wpbalance,
    +    Wpbalance2,
    +    Zpostfacets,
    +    Zpremergetot,
    +    Zprocessed,
    +    Zremvertex,
    +    Zremvertexdel,
    +    Zrenameall,
    +    Zrenamepinch,
    +    Zrenameshare,
    +    Zretry,
    +    Wretrymax,
    +    Zridge,
    +    Wridge,
    +    Wridgemax,
    +    Zridge0,
    +    Wridge0,
    +    Wridge0max,
    +    Zridgemid,
    +    Wridgemid,
    +    Wridgemidmax,
    +    Zridgeok,
    +    Wridgeok,
    +    Wridgeokmax,
    +    Zsearchpoints,
    +    Zsetplane,
    +    Ztestvneighbor,
    +    Ztotcheck,
    +    Ztothorizon,
    +    Ztotmerge,
    +    Ztotpartcoplanar,
    +    Ztotpartition,
    +    Ztotridges,
    +    Ztotvertices,
    +    Ztotvisible,
    +    Ztricoplanar,
    +    Ztricoplanarmax,
    +    Ztricoplanartot,
    +    Ztridegen,
    +    Ztrimirror,
    +    Ztrinull,
    +    Wvertexmax,
    +    Wvertexmin,
    +    Zvertexridge,
    +    Zvertexridgetot,
    +    Zvertexridgemax,
    +    Zvertices,
    +    Zvisfacettot,
    +    Zvisfacetmax,
    +    Zvisit,
    +    Zvisit2max,
    +    Zvisvertextot,
    +    Zvisvertexmax,
    +    Zvvisit,
    +    Zvvisit2max,
    +    Zwidefacet,
    +    Zwidevertices,
    +    ZEND};
    +
    +/*---------------------------------
    +
    +  Zxxx/Wxxx statistics that remain defined if qh_KEEPstatistics=0
    +
    +  notes:
    +    be sure to use zzdef, zzinc, etc. with these statistics (no double checking!)
    +*/
    +#else
    +enum qh_statistics {     /* for zzdef etc. macros */
    +  Zback0,
    +  Zbestdist,
    +  Zcentrumtests,
    +  Zcheckpart,
    +  Zconcaveridges,
    +  Zcoplanarhorizon,
    +  Zcoplanarpart,
    +  Zcoplanarridges,
    +  Zcyclefacettot,
    +  Zcyclehorizon,
    +  Zdelvertextot,
    +  Zdistcheck,
    +  Zdistconvex,
    +  Zdistzero,
    +  Zdoc1,
    +  Zdoc2,
    +  Zdoc3,
    +  Zdoc11,
    +  Zflippedfacets,
    +  Zgauss0,
    +  Zminnorm,
    +  Zmultiridge,
    +  Znearlysingular,
    +  Wnewvertexmax,
    +  Znumvisibility,
    +  Zpartcoplanar,
    +  Zpartition,
    +  Zpartitionall,
    +  Zprocessed,
    +  Zretry,
    +  Zridge,
    +  Wridge,
    +  Wridgemax,
    +  Zridge0,
    +  Wridge0,
    +  Wridge0max,
    +  Zridgemid,
    +  Wridgemid,
    +  Wridgemidmax,
    +  Zridgeok,
    +  Wridgeok,
    +  Wridgeokmax,
    +  Zsetplane,
    +  Ztotcheck,
    +  Ztotmerge,
    +    ZEND};
    +#endif
    +
    +/*---------------------------------
    +
    +  ztype
    +    the type of a statistic sets its initial value.
    +
    +  notes:
    +    The type should be the same as the macro for collecting the statistic
    +*/
    +enum ztypes {zdoc,zinc,zadd,zmax,zmin,ZTYPEreal,wadd,wmax,wmin,ZTYPEend};
    +
    +/*========== macros and constants =============*/
    +
    +/*----------------------------------
    +
    +  MAYdebugx
    +    define as maydebug() to be called frequently for error trapping
    +*/
    +#define MAYdebugx
    +
    +/*----------------------------------
    +
    +  zzdef_, zdef_( type, name, doc, -1)
    +    define a statistic (assumes 'qhstat.next= 0;')
    +
    +  zdef_( type, name, doc, count)
    +    define an averaged statistic
    +    printed as name/count
    +*/
    +#define zzdef_(stype,name,string,cnt) qhstat id[qhstat next++]=name; \
    +   qhstat doc[name]= string; qhstat count[name]= cnt; qhstat type[name]= stype
    +#if qh_KEEPstatistics
    +#define zdef_(stype,name,string,cnt) qhstat id[qhstat next++]=name; \
    +   qhstat doc[name]= string; qhstat count[name]= cnt; qhstat type[name]= stype
    +#else
    +#define zdef_(type,name,doc,count)
    +#endif
    +
    +/*----------------------------------
    +
    +  zzinc_( name ), zinc_( name)
    +    increment an integer statistic
    +*/
    +#define zzinc_(id) {MAYdebugx; qhstat stats[id].i++;}
    +#if qh_KEEPstatistics
    +#define zinc_(id) {MAYdebugx; qhstat stats[id].i++;}
    +#else
    +#define zinc_(id) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zzadd_( name, value ), zadd_( name, value ), wadd_( name, value )
    +    add value to an integer or real statistic
    +*/
    +#define zzadd_(id, val) {MAYdebugx; qhstat stats[id].i += (val);}
    +#define wwadd_(id, val) {MAYdebugx; qhstat stats[id].r += (val);}
    +#if qh_KEEPstatistics
    +#define zadd_(id, val) {MAYdebugx; qhstat stats[id].i += (val);}
    +#define wadd_(id, val) {MAYdebugx; qhstat stats[id].r += (val);}
    +#else
    +#define zadd_(id, val) {}
    +#define wadd_(id, val) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zzval_( name ), zval_( name ), wwval_( name )
    +    set or return value of a statistic
    +*/
    +#define zzval_(id) ((qhstat stats[id]).i)
    +#define wwval_(id) ((qhstat stats[id]).r)
    +#if qh_KEEPstatistics
    +#define zval_(id) ((qhstat stats[id]).i)
    +#define wval_(id) ((qhstat stats[id]).r)
    +#else
    +#define zval_(id) qhstat tempi
    +#define wval_(id) qhstat tempr
    +#endif
    +
    +/*----------------------------------
    +
    +  zmax_( id, val ), wmax_( id, value )
    +    maximize id with val
    +*/
    +#define wwmax_(id, val) {MAYdebugx; maximize_(qhstat stats[id].r,(val));}
    +#if qh_KEEPstatistics
    +#define zmax_(id, val) {MAYdebugx; maximize_(qhstat stats[id].i,(val));}
    +#define wmax_(id, val) {MAYdebugx; maximize_(qhstat stats[id].r,(val));}
    +#else
    +#define zmax_(id, val) {}
    +#define wmax_(id, val) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zmin_( id, val ), wmin_( id, value )
    +    minimize id with val
    +*/
    +#if qh_KEEPstatistics
    +#define zmin_(id, val) {MAYdebugx; minimize_(qhstat stats[id].i,(val));}
    +#define wmin_(id, val) {MAYdebugx; minimize_(qhstat stats[id].r,(val));}
    +#else
    +#define zmin_(id, val) {}
    +#define wmin_(id, val) {}
    +#endif
    +
    +/*================== stat.h types ==============*/
    +
    +
    +/*----------------------------------
    +
    +  intrealT
    +    union of integer and real, used for statistics
    +*/
    +typedef union intrealT intrealT;    /* union of int and realT */
    +union intrealT {
    +    int i;
    +    realT r;
    +};
    +
    +/*----------------------------------
    +
    +  qhstat
    +    global data structure for statistics, similar to qh and qhrbox
    +
    +  notes:
    +   access to qh_qhstat is via the "qhstat" macro.  There are two choices
    +   qh_QHpointer = 1     access globals via a pointer
    +                        enables qh_saveqhull() and qh_restoreqhull()
    +                = 0     qh_qhstat is a static data structure
    +                        only one instance of qhull() can be active at a time
    +                        default value
    +   qh_QHpointer is defined in libqhull.h
    +   qh_QHpointer_dllimport and qh_dllimport define qh_qh as __declspec(dllimport) [libqhull.h]
    +
    +   allocated in stat.c using qh_malloc()
    +*/
    +#ifndef DEFqhstatT
    +#define DEFqhstatT 1
    +typedef struct qhstatT qhstatT;
    +#endif
    +
    +#if qh_QHpointer_dllimport
    +#define qhstat qh_qhstat->
    +__declspec(dllimport) extern qhstatT *qh_qhstat;
    +#elif qh_QHpointer
    +#define qhstat qh_qhstat->
    +extern qhstatT *qh_qhstat;
    +#elif qh_dllimport
    +#define qhstat qh_qhstat.
    +__declspec(dllimport) extern qhstatT qh_qhstat;
    +#else
    +#define qhstat qh_qhstat.
    +extern qhstatT qh_qhstat;
    +#endif
    +struct qhstatT {
    +  intrealT   stats[ZEND];     /* integer and real statistics */
    +  unsigned   char id[ZEND+10]; /* id's in print order */
    +  const char *doc[ZEND];       /* array of documentation strings */
    +  short int  count[ZEND];     /* -1 if none, else index of count to use */
    +  char       type[ZEND];      /* type, see ztypes above */
    +  char       printed[ZEND];   /* true, if statistic has been printed */
    +  intrealT   init[ZTYPEend];  /* initial values by types, set initstatistics */
    +
    +  int        next;            /* next index for zdef_ */
    +  int        precision;       /* index for precision problems */
    +  int        vridges;         /* index for Voronoi ridges */
    +  int        tempi;
    +  realT      tempr;
    +};
    +
    +/*========== function prototypes ===========*/
    +
    +void    qh_allstatA(void);
    +void    qh_allstatB(void);
    +void    qh_allstatC(void);
    +void    qh_allstatD(void);
    +void    qh_allstatE(void);
    +void    qh_allstatE2(void);
    +void    qh_allstatF(void);
    +void    qh_allstatG(void);
    +void    qh_allstatH(void);
    +void    qh_allstatI(void);
    +void    qh_allstatistics(void);
    +void    qh_collectstatistics(void);
    +void    qh_freestatistics(void);
    +void    qh_initstatistics(void);
    +boolT   qh_newstats(int idx, int *nextindex);
    +boolT   qh_nostatistic(int i);
    +void    qh_printallstatistics(FILE *fp, const char *string);
    +void    qh_printstatistics(FILE *fp, const char *string);
    +void    qh_printstatlevel(FILE *fp, int id);
    +void    qh_printstats(FILE *fp, int idx, int *nextindex);
    +realT   qh_stddev(int num, realT tot, realT tot2, realT *ave);
    +
    +#endif   /* qhDEFstat */
    diff --git a/xs/src/qhull/src/libqhull/user.c b/xs/src/qhull/src/libqhull/user.c
    new file mode 100644
    index 0000000000..d4726eaa31
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/user.c
    @@ -0,0 +1,538 @@
    +/*
      ---------------------------------
    +
    +   user.c
    +   user redefinable functions
    +
    +   see user2.c for qh_fprintf, qh_malloc, qh_free
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +
    +   see user_eg.c, user_eg2.c, and unix.c for examples.
    +
    +   see user.h for user-definable constants
    +
    +      use qh_NOmem in mem.h to turn off memory management
    +      use qh_NOmerge in user.h to turn off facet merging
    +      set qh_KEEPstatistics in user.h to 0 to turn off statistics
    +
    +   This is unsupported software.  You're welcome to make changes,
    +   but you're on your own if something goes wrong.  Use 'Tc' to
    +   check frequently.  Usually qhull will report an error if
    +   a data structure becomes inconsistent.  If so, it also reports
    +   the last point added to the hull, e.g., 102.  You can then trace
    +   the execution of qhull with "T4P102".
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +
    +   Qhull-template is a template for calling qhull from within your application
    +
    +   if you recompile and load this module, then user.o will not be loaded
    +   from qhull.a
    +
    +   you can add additional quick allocation sizes in qh_user_memsizes
    +
    +   if the other functions here are redefined to not use qh_print...,
    +   then io.o will not be loaded from qhull.a.  See user_eg.c for an
    +   example.  We recommend keeping io.o for the extra debugging
    +   information it supplies.
    +*/
    +
    +#include "qhull_a.h"
    +
    +#include 
    +
    +/*---------------------------------
    +
    +  Qhull-template
    +    Template for calling qhull from inside your program
    +
    +  returns:
    +    exit code(see qh_ERR... in libqhull.h)
    +    all memory freed
    +
    +  notes:
    +    This can be called any number of times.
    +*/
    +#if 0
    +{
    +  int dim;                  /* dimension of points */
    +  int numpoints;            /* number of points */
    +  coordT *points;           /* array of coordinates for each point */
    +  boolT ismalloc;           /* True if qhull should free points in qh_freeqhull() or reallocation */
    +  char flags[]= "qhull Tv"; /* option flags for qhull, see qh_opt.htm */
    +  FILE *outfile= stdout;    /* output from qh_produce_output()
    +                               use NULL to skip qh_produce_output() */
    +  FILE *errfile= stderr;    /* error messages from qhull code */
    +  int exitcode;             /* 0 if no error from qhull */
    +  facetT *facet;            /* set by FORALLfacets */
    +  int curlong, totlong;     /* memory remaining after qh_memfreeshort */
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +#if qh_QHpointer  /* see user.h */
    +  if (qh_qh){ /* should be NULL */
    +      qh_printf_stderr(6238, "Qhull link error.  The global variable qh_qh was not initialized\n\
    +              to NULL by global.c.  Please compile this program with -Dqh_QHpointer_dllimport\n\
    +              as well as -Dqh_QHpointer, or use libqhullstatic, or use a different tool chain.\n\n");
    +      exit(1);
    +  }
    +#endif
    +
    +  /* initialize dim, numpoints, points[], ismalloc here */
    +  exitcode= qh_new_qhull(dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh facet_list' contains the convex hull */
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +  }
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf(errfile, 7068, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n", totlong, curlong);
    +}
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_new_qhull( dim, numpoints, points, ismalloc, qhull_cmd, outfile, errfile )
    +    build new qhull data structure and return exitcode (0 if no errors)
    +    if numpoints=0 and points=NULL, initializes qhull
    +
    +  notes:
    +    do not modify points until finished with results.
    +      The qhull data structure contains pointers into the points array.
    +    do not call qhull functions before qh_new_qhull().
    +      The qhull data structure is not initialized until qh_new_qhull().
    +
    +    Default errfile is stderr, outfile may be null
    +    qhull_cmd must start with "qhull "
    +    projects points to a new point array for Delaunay triangulations ('d' and 'v')
    +    transforms points into a new point array for halfspace intersection ('H')
    +
    +
    +  To allow multiple, concurrent calls to qhull()
    +    - set qh_QHpointer in user.h
    +    - use qh_save_qhull and qh_restore_qhull to swap the global data structure between calls.
    +    - use qh_freeqhull(qh_ALL) to free intermediate convex hulls
    +
    +  see:
    +      Qhull-template at the beginning of this file.
    +      An example of using qh_new_qhull is user_eg.c
    +*/
    +int qh_new_qhull(int dim, int numpoints, coordT *points, boolT ismalloc,
    +                char *qhull_cmd, FILE *outfile, FILE *errfile) {
    +  /* gcc may issue a "might be clobbered" warning for dim, points, and ismalloc [-Wclobbered].
    +     These parameters are not referenced after a longjmp() and hence not clobbered.
    +     See http://stackoverflow.com/questions/7721854/what-sense-do-these-clobbered-variable-warnings-make */
    +  int exitcode, hulldim;
    +  boolT new_ismalloc;
    +  static boolT firstcall = True;
    +  coordT *new_points;
    +  if(!errfile){
    +      errfile= stderr;
    +  }
    +  if (firstcall) {
    +    qh_meminit(errfile);
    +    firstcall= False;
    +  } else {
    +    qh_memcheck();
    +  }
    +  if (strncmp(qhull_cmd, "qhull ", (size_t)6)) {
    +    qh_fprintf(errfile, 6186, "qhull error (qh_new_qhull): start qhull_cmd argument with \"qhull \"\n");
    +    return qh_ERRinput;
    +  }
    +  qh_initqhull_start(NULL, outfile, errfile);
    +  if(numpoints==0 && points==NULL){
    +      trace1((qh ferr, 1047, "qh_new_qhull: initialize Qhull\n"));
    +      return 0;
    +  }
    +  trace1((qh ferr, 1044, "qh_new_qhull: build new Qhull for %d %d-d points with %s\n", numpoints, dim, qhull_cmd));
    +  exitcode = setjmp(qh errexit);
    +  if (!exitcode)
    +  {
    +    qh NOerrexit = False;
    +    qh_initflags(qhull_cmd);
    +    if (qh DELAUNAY)
    +      qh PROJECTdelaunay= True;
    +    if (qh HALFspace) {
    +      /* points is an array of halfspaces,
    +         the last coordinate of each halfspace is its offset */
    +      hulldim= dim-1;
    +      qh_setfeasible(hulldim);
    +      new_points= qh_sethalfspace_all(dim, numpoints, points, qh feasible_point);
    +      new_ismalloc= True;
    +      if (ismalloc)
    +        qh_free(points);
    +    }else {
    +      hulldim= dim;
    +      new_points= points;
    +      new_ismalloc= ismalloc;
    +    }
    +    qh_init_B(new_points, numpoints, hulldim, new_ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    if (outfile) {
    +      qh_produce_output();
    +    }else {
    +      qh_prepare_output();
    +    }
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +  }
    +  qh NOerrexit = True;
    +  return exitcode;
    +} /* new_qhull */
    +
    +/*---------------------------------
    +
    +  qh_errexit( exitcode, facet, ridge )
    +    report and exit from an error
    +    report facet and ridge if non-NULL
    +    reports useful information such as last point processed
    +    set qh.FORCEoutput to print neighborhood of facet
    +
    +  see:
    +    qh_errexit2() in libqhull.c for printing 2 facets
    +
    +  design:
    +    check for error within error processing
    +    compute qh.hulltime
    +    print facet and ridge (if any)
    +    report commandString, options, qh.furthest_id
    +    print summary and statistics (including precision statistics)
    +    if qh_ERRsingular
    +      print help text for singular data set
    +    exit program via long jump (if defined) or exit()
    +*/
    +void qh_errexit(int exitcode, facetT *facet, ridgeT *ridge) {
    +
    +  if (qh ERREXITcalled) {
    +    qh_fprintf(qh ferr, 8126, "\nqhull error while processing previous error.  Exit program\n");
    +    qh_exit(qh_ERRqhull);
    +  }
    +  qh ERREXITcalled= True;
    +  if (!qh QHULLfinished)
    +    qh hulltime= qh_CPUclock - qh hulltime;
    +  qh_errprint("ERRONEOUS", facet, NULL, ridge, NULL);
    +  qh_fprintf(qh ferr, 8127, "\nWhile executing: %s | %s\n", qh rbox_command, qh qhull_command);
    +  qh_fprintf(qh ferr, 8128, "Options selected for Qhull %s:\n%s\n", qh_version, qh qhull_options);
    +  if (qh furthest_id >= 0) {
    +    qh_fprintf(qh ferr, 8129, "Last point added to hull was p%d.", qh furthest_id);
    +    if (zzval_(Ztotmerge))
    +      qh_fprintf(qh ferr, 8130, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    if (qh QHULLfinished)
    +      qh_fprintf(qh ferr, 8131, "\nQhull has finished constructing the hull.");
    +    else if (qh POSTmerging)
    +      qh_fprintf(qh ferr, 8132, "\nQhull has started post-merging.");
    +    qh_fprintf(qh ferr, 8133, "\n");
    +  }
    +  if (qh FORCEoutput && (qh QHULLfinished || (!facet && !ridge)))
    +    qh_produce_output();
    +  else if (exitcode != qh_ERRinput) {
    +    if (exitcode != qh_ERRsingular && zzval_(Zsetplane) > qh hull_dim+1) {
    +      qh_fprintf(qh ferr, 8134, "\nAt error exit:\n");
    +      qh_printsummary(qh ferr);
    +      if (qh PRINTstatistics) {
    +        qh_collectstatistics();
    +        qh_printstatistics(qh ferr, "at error exit");
    +        qh_memstatistics(qh ferr);
    +      }
    +    }
    +    if (qh PRINTprecision)
    +      qh_printstats(qh ferr, qhstat precision, NULL);
    +  }
    +  if (!exitcode)
    +    exitcode= qh_ERRqhull;
    +  else if (exitcode == qh_ERRsingular)
    +    qh_printhelp_singular(qh ferr);
    +  else if (exitcode == qh_ERRprec && !qh PREmerge)
    +    qh_printhelp_degenerate(qh ferr);
    +  if (qh NOerrexit) {
    +    qh_fprintf(qh ferr, 6187, "qhull error while ending program, or qh->NOerrexit not cleared after setjmp(). Exit program with error.\n");
    +    qh_exit(qh_ERRqhull);
    +  }
    +  qh ERREXITcalled= False;
    +  qh NOerrexit= True;
    +  qh ALLOWrestart= False;  /* longjmp will undo qh_build_withrestart */
    +  longjmp(qh errexit, exitcode);
    +} /* errexit */
    +
    +
    +/*---------------------------------
    +
    +  qh_errprint( fp, string, atfacet, otherfacet, atridge, atvertex )
    +    prints out the information of facets and ridges to fp
    +    also prints neighbors and geomview output
    +
    +  notes:
    +    except for string, any parameter may be NULL
    +*/
    +void qh_errprint(const char *string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex) {
    +  int i;
    +
    +  if (atfacet) {
    +    qh_fprintf(qh ferr, 8135, "%s FACET:\n", string);
    +    qh_printfacet(qh ferr, atfacet);
    +  }
    +  if (otherfacet) {
    +    qh_fprintf(qh ferr, 8136, "%s OTHER FACET:\n", string);
    +    qh_printfacet(qh ferr, otherfacet);
    +  }
    +  if (atridge) {
    +    qh_fprintf(qh ferr, 8137, "%s RIDGE:\n", string);
    +    qh_printridge(qh ferr, atridge);
    +    if (atridge->top && atridge->top != atfacet && atridge->top != otherfacet)
    +      qh_printfacet(qh ferr, atridge->top);
    +    if (atridge->bottom
    +        && atridge->bottom != atfacet && atridge->bottom != otherfacet)
    +      qh_printfacet(qh ferr, atridge->bottom);
    +    if (!atfacet)
    +      atfacet= atridge->top;
    +    if (!otherfacet)
    +      otherfacet= otherfacet_(atridge, atfacet);
    +  }
    +  if (atvertex) {
    +    qh_fprintf(qh ferr, 8138, "%s VERTEX:\n", string);
    +    qh_printvertex(qh ferr, atvertex);
    +  }
    +  if (qh fout && qh FORCEoutput && atfacet && !qh QHULLfinished && !qh IStracing) {
    +    qh_fprintf(qh ferr, 8139, "ERRONEOUS and NEIGHBORING FACETS to output\n");
    +    for (i=0; i < qh_PRINTEND; i++)  /* use fout for geomview output */
    +      qh_printneighborhood(qh fout, qh PRINTout[i], atfacet, otherfacet,
    +                            !qh_ALL);
    +  }
    +} /* errprint */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetlist( fp, facetlist, facets, printall )
    +    print all fields for a facet list and/or set of facets to fp
    +    if !printall,
    +      only prints good facets
    +
    +  notes:
    +    also prints all vertices
    +*/
    +void qh_printfacetlist(facetT *facetlist, setT *facets, boolT printall) {
    +  facetT *facet, **facetp;
    +
    +  qh_printbegin(qh ferr, qh_PRINTfacets, facetlist, facets, printall);
    +  FORALLfacet_(facetlist)
    +    qh_printafacet(qh ferr, qh_PRINTfacets, facet, printall);
    +  FOREACHfacet_(facets)
    +    qh_printafacet(qh ferr, qh_PRINTfacets, facet, printall);
    +  qh_printend(qh ferr, qh_PRINTfacets, facetlist, facets, printall);
    +} /* printfacetlist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhelp_degenerate( fp )
    +    prints descriptive message for precision error
    +
    +  notes:
    +    no message if qh_QUICKhelp
    +*/
    +void qh_printhelp_degenerate(FILE *fp) {
    +
    +  if (qh MERGEexact || qh PREmerge || qh JOGGLEmax < REALmax/2)
    +    qh_fprintf(fp, 9368, "\n\
    +A Qhull error has occurred.  Qhull should have corrected the above\n\
    +precision error.  Please send the input and all of the output to\n\
    +qhull_bug@qhull.org\n");
    +  else if (!qh_QUICKhelp) {
    +    qh_fprintf(fp, 9369, "\n\
    +Precision problems were detected during construction of the convex hull.\n\
    +This occurs because convex hull algorithms assume that calculations are\n\
    +exact, but floating-point arithmetic has roundoff errors.\n\
    +\n\
    +To correct for precision problems, do not use 'Q0'.  By default, Qhull\n\
    +selects 'C-0' or 'Qx' and merges non-convex facets.  With option 'QJ',\n\
    +Qhull joggles the input to prevent precision problems.  See \"Imprecision\n\
    +in Qhull\" (qh-impre.htm).\n\
    +\n\
    +If you use 'Q0', the output may include\n\
    +coplanar ridges, concave ridges, and flipped facets.  In 4-d and higher,\n\
    +Qhull may produce a ridge with four neighbors or two facets with the same \n\
    +vertices.  Qhull reports these events when they occur.  It stops when a\n\
    +concave ridge, flipped facet, or duplicate facet occurs.\n");
    +#if REALfloat
    +    qh_fprintf(fp, 9370, "\
    +\n\
    +Qhull is currently using single precision arithmetic.  The following\n\
    +will probably remove the precision problems:\n\
    +  - recompile qhull for realT precision(#define REALfloat 0 in user.h).\n");
    +#endif
    +    if (qh DELAUNAY && !qh SCALElast && qh MAXabs_coord > 1e4)
    +      qh_fprintf(fp, 9371, "\
    +\n\
    +When computing the Delaunay triangulation of coordinates > 1.0,\n\
    +  - use 'Qbb' to scale the last coordinate to [0,m] (max previous coordinate)\n");
    +    if (qh DELAUNAY && !qh ATinfinity)
    +      qh_fprintf(fp, 9372, "\
    +When computing the Delaunay triangulation:\n\
    +  - use 'Qz' to add a point at-infinity.  This reduces precision problems.\n");
    +
    +    qh_fprintf(fp, 9373, "\
    +\n\
    +If you need triangular output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft'.  It triangulates non-simplicial facets with added points.\n\
    +\n\
    +If you must use 'Q0',\n\
    +try one or more of the following options.  They can not guarantee an output.\n\
    +  - use 'QbB' to scale the input to a cube.\n\
    +  - use 'Po' to produce output and prevent partitioning for flipped facets\n\
    +  - use 'V0' to set min. distance to visible facet as 0 instead of roundoff\n\
    +  - use 'En' to specify a maximum roundoff error less than %2.2g.\n\
    +  - options 'Qf', 'Qbb', and 'QR0' may also help\n",
    +               qh DISTround);
    +    qh_fprintf(fp, 9374, "\
    +\n\
    +To guarantee simplicial output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft' to triangulate the output by adding points\n\
    +  - use exact arithmetic (see \"Imprecision in Qhull\", qh-impre.htm)\n\
    +");
    +  }
    +} /* printhelp_degenerate */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhelp_narrowhull( minangle )
    +    Warn about a narrow hull
    +
    +  notes:
    +    Alternatively, reduce qh_WARNnarrow in user.h
    +
    +*/
    +void qh_printhelp_narrowhull(FILE *fp, realT minangle) {
    +
    +    qh_fprintf(fp, 9375, "qhull precision warning: \n\
    +The initial hull is narrow (cosine of min. angle is %.16f).\n\
    +Is the input lower dimensional (e.g., on a plane in 3-d)?  Qhull may\n\
    +produce a wide facet.  Options 'QbB' (scale to unit box) or 'Qbb' (scale\n\
    +last coordinate) may remove this warning.  Use 'Pp' to skip this warning.\n\
    +See 'Limitations' in qh-impre.htm.\n",
    +          -minangle);   /* convert from angle between normals to angle between facets */
    +} /* printhelp_narrowhull */
    +
    +/*---------------------------------
    +
    +  qh_printhelp_singular( fp )
    +    prints descriptive message for singular input
    +*/
    +void qh_printhelp_singular(FILE *fp) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  realT min, max, *coord, dist;
    +  int i,k;
    +
    +  qh_fprintf(fp, 9376, "\n\
    +The input to qhull appears to be less than %d dimensional, or a\n\
    +computation has overflowed.\n\n\
    +Qhull could not construct a clearly convex simplex from points:\n",
    +           qh hull_dim);
    +  qh_printvertexlist(fp, "", qh facet_list, NULL, qh_ALL);
    +  if (!qh_QUICKhelp)
    +    qh_fprintf(fp, 9377, "\n\
    +The center point is coplanar with a facet, or a vertex is coplanar\n\
    +with a neighboring facet.  The maximum round off error for\n\
    +computing distances is %2.2g.  The center point, facets and distances\n\
    +to the center point are as follows:\n\n", qh DISTround);
    +  qh_printpointid(fp, "center point", qh hull_dim, qh interior_point, qh_IDunknown);
    +  qh_fprintf(fp, 9378, "\n");
    +  FORALLfacets {
    +    qh_fprintf(fp, 9379, "facet");
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(fp, 9380, " p%d", qh_pointid(vertex->point));
    +    zinc_(Zdistio);
    +    qh_distplane(qh interior_point, facet, &dist);
    +    qh_fprintf(fp, 9381, " distance= %4.2g\n", dist);
    +  }
    +  if (!qh_QUICKhelp) {
    +    if (qh HALFspace)
    +      qh_fprintf(fp, 9382, "\n\
    +These points are the dual of the given halfspaces.  They indicate that\n\
    +the intersection is degenerate.\n");
    +    qh_fprintf(fp, 9383,"\n\
    +These points either have a maximum or minimum x-coordinate, or\n\
    +they maximize the determinant for k coordinates.  Trial points\n\
    +are first selected from points that maximize a coordinate.\n");
    +    if (qh hull_dim >= qh_INITIALmax)
    +      qh_fprintf(fp, 9384, "\n\
    +Because of the high dimension, the min x-coordinate and max-coordinate\n\
    +points are used if the determinant is non-zero.  Option 'Qs' will\n\
    +do a better, though much slower, job.  Instead of 'Qs', you can change\n\
    +the points by randomly rotating the input with 'QR0'.\n");
    +  }
    +  qh_fprintf(fp, 9385, "\nThe min and max coordinates for each dimension are:\n");
    +  for (k=0; k < qh hull_dim; k++) {
    +    min= REALmax;
    +    max= -REALmin;
    +    for (i=qh num_points, coord= qh first_point+k; i--; coord += qh hull_dim) {
    +      maximize_(max, *coord);
    +      minimize_(min, *coord);
    +    }
    +    qh_fprintf(fp, 9386, "  %d:  %8.4g  %8.4g  difference= %4.4g\n", k, min, max, max-min);
    +  }
    +  if (!qh_QUICKhelp) {
    +    qh_fprintf(fp, 9387, "\n\
    +If the input should be full dimensional, you have several options that\n\
    +may determine an initial simplex:\n\
    +  - use 'QJ'  to joggle the input and make it full dimensional\n\
    +  - use 'QbB' to scale the points to the unit cube\n\
    +  - use 'QR0' to randomly rotate the input for different maximum points\n\
    +  - use 'Qs'  to search all points for the initial simplex\n\
    +  - use 'En'  to specify a maximum roundoff error less than %2.2g.\n\
    +  - trace execution with 'T3' to see the determinant for each point.\n",
    +                     qh DISTround);
    +#if REALfloat
    +    qh_fprintf(fp, 9388, "\
    +  - recompile qhull for realT precision(#define REALfloat 0 in libqhull.h).\n");
    +#endif
    +    qh_fprintf(fp, 9389, "\n\
    +If the input is lower dimensional:\n\
    +  - use 'QJ' to joggle the input and make it full dimensional\n\
    +  - use 'Qbk:0Bk:0' to delete coordinate k from the input.  You should\n\
    +    pick the coordinate with the least range.  The hull will have the\n\
    +    correct topology.\n\
    +  - determine the flat containing the points, rotate the points\n\
    +    into a coordinate plane, and delete the other coordinates.\n\
    +  - add one or more points to make the input full dimensional.\n\
    +");
    +  }
    +} /* printhelp_singular */
    +
    +/*---------------------------------
    +
    +  qh_user_memsizes()
    +    allocate up to 10 additional, quick allocation sizes
    +
    +  notes:
    +    increase maximum number of allocations in qh_initqhull_mem()
    +*/
    +void qh_user_memsizes(void) {
    +
    +  /* qh_memsize(size); */
    +} /* user_memsizes */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/user.h b/xs/src/qhull/src/libqhull/user.h
    new file mode 100644
    index 0000000000..523aa7b4e8
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/user.h
    @@ -0,0 +1,909 @@
    +/*
      ---------------------------------
    +
    +   user.h
    +   user redefinable constants
    +
    +   for each source file, user.h is included first
    +   see qh-user.htm.  see COPYING for copyright information.
    +
    +   See user.c for sample code.
    +
    +   before reading any code, review libqhull.h for data structure definitions and
    +   the "qh" macro.
    +
    +Sections:
    +   ============= qhull library constants ======================
    +   ============= data types and configuration macros ==========
    +   ============= performance related constants ================
    +   ============= memory constants =============================
    +   ============= joggle constants =============================
    +   ============= conditional compilation ======================
    +   ============= -merge constants- ============================
    +
    +Code flags --
    +  NOerrors -- the code does not call qh_errexit()
    +  WARN64 -- the code may be incompatible with 64-bit pointers
    +
    +*/
    +
    +#include 
    +
    +#ifndef qhDEFuser
    +#define qhDEFuser 1
    +
    +/* Derived from Qt's corelib/global/qglobal.h */
    +#if !defined(SAG_COM) && !defined(__CYGWIN__) && (defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__))
    +#   define QHULL_OS_WIN
    +#elif defined(__MWERKS__) && defined(__INTEL__) /* Metrowerks discontinued before the release of Intel Macs */
    +#   define QHULL_OS_WIN
    +#endif
    +/*============================================================*/
    +/*============= qhull library constants ======================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  FILENAMElen -- max length for TI and TO filenames
    +
    +*/
    +
    +#define qh_FILENAMElen 500
    +
    +/*----------------------------------
    +
    +  msgcode -- Unique message codes for qh_fprintf
    +
    +  If add new messages, assign these values and increment in user.h and user_r.h
    +  See QhullError.h for 10000 errors.
    +
    +  def counters =  [27, 1048, 2059, 3026, 4068, 5003,
    +     6273, 7081, 8147, 9411, 10000, 11029]
    +
    +  See: qh_ERR* [libqhull.h]
    +*/
    +
    +#define MSG_TRACE0 0
    +#define MSG_TRACE1 1000
    +#define MSG_TRACE2 2000
    +#define MSG_TRACE3 3000
    +#define MSG_TRACE4 4000
    +#define MSG_TRACE5 5000
    +#define MSG_ERROR  6000   /* errors written to qh.ferr */
    +#define MSG_WARNING 7000
    +#define MSG_STDERR  8000  /* log messages Written to qh.ferr */
    +#define MSG_OUTPUT  9000
    +#define MSG_QHULL_ERROR 10000 /* errors thrown by QhullError.cpp (QHULLlastError is in QhullError.h) */
    +#define MSG_FIXUP  11000  /* FIXUP QH11... */
    +#define MSG_MAXLEN  3000 /* qh_printhelp_degenerate() in user.c */
    +
    +
    +/*----------------------------------
    +
    +  qh_OPTIONline -- max length of an option line 'FO'
    +*/
    +#define qh_OPTIONline 80
    +
    +/*============================================================*/
    +/*============= data types and configuration macros ==========*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  realT
    +    set the size of floating point numbers
    +
    +  qh_REALdigits
    +    maximimum number of significant digits
    +
    +  qh_REAL_1, qh_REAL_2n, qh_REAL_3n
    +    format strings for printf
    +
    +  qh_REALmax, qh_REALmin
    +    maximum and minimum (near zero) values
    +
    +  qh_REALepsilon
    +    machine roundoff.  Maximum roundoff error for addition and multiplication.
    +
    +  notes:
    +   Select whether to store floating point numbers in single precision (float)
    +   or double precision (double).
    +
    +   Use 'float' to save about 8% in time and 25% in space.  This is particularly
    +   helpful if high-d where convex hulls are space limited.  Using 'float' also
    +   reduces the printed size of Qhull's output since numbers have 8 digits of
    +   precision.
    +
    +   Use 'double' when greater arithmetic precision is needed.  This is needed
    +   for Delaunay triangulations and Voronoi diagrams when you are not merging
    +   facets.
    +
    +   If 'double' gives insufficient precision, your data probably includes
    +   degeneracies.  If so you should use facet merging (done by default)
    +   or exact arithmetic (see imprecision section of manual, qh-impre.htm).
    +   You may also use option 'Po' to force output despite precision errors.
    +
    +   You may use 'long double', but many format statements need to be changed
    +   and you may need a 'long double' square root routine.  S. Grundmann
    +   (sg@eeiwzb.et.tu-dresden.de) has done this.  He reports that the code runs
    +   much slower with little gain in precision.
    +
    +   WARNING: on some machines,    int f(){realT a= REALmax;return (a == REALmax);}
    +      returns False.  Use (a > REALmax/2) instead of (a == REALmax).
    +
    +   REALfloat =   1      all numbers are 'float' type
    +             =   0      all numbers are 'double' type
    +*/
    +#define REALfloat 0
    +
    +#if (REALfloat == 1)
    +#define realT float
    +#define REALmax FLT_MAX
    +#define REALmin FLT_MIN
    +#define REALepsilon FLT_EPSILON
    +#define qh_REALdigits 8   /* maximum number of significant digits */
    +#define qh_REAL_1 "%6.8g "
    +#define qh_REAL_2n "%6.8g %6.8g\n"
    +#define qh_REAL_3n "%6.8g %6.8g %6.8g\n"
    +
    +#elif (REALfloat == 0)
    +#define realT double
    +#define REALmax DBL_MAX
    +#define REALmin DBL_MIN
    +#define REALepsilon DBL_EPSILON
    +#define qh_REALdigits 16    /* maximum number of significant digits */
    +#define qh_REAL_1 "%6.16g "
    +#define qh_REAL_2n "%6.16g %6.16g\n"
    +#define qh_REAL_3n "%6.16g %6.16g %6.16g\n"
    +
    +#else
    +#error unknown float option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_CPUclock
    +    define the clock() function for reporting the total time spent by Qhull
    +    returns CPU ticks as a 'long int'
    +    qh_CPUclock is only used for reporting the total time spent by Qhull
    +
    +  qh_SECticks
    +    the number of clock ticks per second
    +
    +  notes:
    +    looks for CLOCKS_PER_SEC, CLOCKS_PER_SECOND, or assumes microseconds
    +    to define a custom clock, set qh_CLOCKtype to 0
    +
    +    if your system does not use clock() to return CPU ticks, replace
    +    qh_CPUclock with the corresponding function.  It is converted
    +    to 'unsigned long' to prevent wrap-around during long runs.  By default,
    +     defines clock_t as 'long'
    +
    +   Set qh_CLOCKtype to
    +
    +     1          for CLOCKS_PER_SEC, CLOCKS_PER_SECOND, or microsecond
    +                Note:  may fail if more than 1 hour elapsed time
    +
    +     2          use qh_clock() with POSIX times() (see global.c)
    +*/
    +#define qh_CLOCKtype 1  /* change to the desired number */
    +
    +#if (qh_CLOCKtype == 1)
    +
    +#if defined(CLOCKS_PER_SECOND)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLOCKS_PER_SECOND
    +
    +#elif defined(CLOCKS_PER_SEC)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLOCKS_PER_SEC
    +
    +#elif defined(CLK_TCK)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLK_TCK
    +
    +#else
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks 1E6
    +#endif
    +
    +#elif (qh_CLOCKtype == 2)
    +#define qh_CPUclock    qh_clock()  /* return CPU clock */
    +#define qh_SECticks 100
    +
    +#else /* qh_CLOCKtype == ? */
    +#error unknown clock option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_RANDOMtype, qh_RANDOMmax, qh_RANDOMseed
    +    define random number generator
    +
    +    qh_RANDOMint generates a random integer between 0 and qh_RANDOMmax.
    +    qh_RANDOMseed sets the random number seed for qh_RANDOMint
    +
    +  Set qh_RANDOMtype (default 5) to:
    +    1       for random() with 31 bits (UCB)
    +    2       for rand() with RAND_MAX or 15 bits (system 5)
    +    3       for rand() with 31 bits (Sun)
    +    4       for lrand48() with 31 bits (Solaris)
    +    5       for qh_rand() with 31 bits (included with Qhull)
    +
    +  notes:
    +    Random numbers are used by rbox to generate point sets.  Random
    +    numbers are used by Qhull to rotate the input ('QRn' option),
    +    simulate a randomized algorithm ('Qr' option), and to simulate
    +    roundoff errors ('Rn' option).
    +
    +    Random number generators differ between systems.  Most systems provide
    +    rand() but the period varies.  The period of rand() is not critical
    +    since qhull does not normally use random numbers.
    +
    +    The default generator is Park & Miller's minimal standard random
    +    number generator [CACM 31:1195 '88].  It is included with Qhull.
    +
    +    If qh_RANDOMmax is wrong, qhull will report a warning and Geomview
    +    output will likely be invisible.
    +*/
    +#define qh_RANDOMtype 5   /* *** change to the desired number *** */
    +
    +#if (qh_RANDOMtype == 1)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, random()/MAX */
    +#define qh_RANDOMint random()
    +#define qh_RANDOMseed_(seed) srandom(seed);
    +
    +#elif (qh_RANDOMtype == 2)
    +#ifdef RAND_MAX
    +#define qh_RANDOMmax ((realT)RAND_MAX)
    +#else
    +#define qh_RANDOMmax ((realT)32767)   /* 15 bits (System 5) */
    +#endif
    +#define qh_RANDOMint  rand()
    +#define qh_RANDOMseed_(seed) srand((unsigned)seed);
    +
    +#elif (qh_RANDOMtype == 3)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, Sun */
    +#define qh_RANDOMint  rand()
    +#define qh_RANDOMseed_(seed) srand((unsigned)seed);
    +
    +#elif (qh_RANDOMtype == 4)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, lrand38()/MAX */
    +#define qh_RANDOMint lrand48()
    +#define qh_RANDOMseed_(seed) srand48(seed);
    +
    +#elif (qh_RANDOMtype == 5)
    +#define qh_RANDOMmax ((realT)2147483646UL)  /* 31 bits, qh_rand/MAX */
    +#define qh_RANDOMint qh_rand()
    +#define qh_RANDOMseed_(seed) qh_srand(seed);
    +/* unlike rand(), never returns 0 */
    +
    +#else
    +#error: unknown random option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_ORIENTclock
    +    0 for inward pointing normals by Geomview convention
    +*/
    +#define qh_ORIENTclock 0
    +
    +
    +/*============================================================*/
    +/*============= joggle constants =============================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +qh_JOGGLEdefault
    +default qh.JOGGLEmax is qh.DISTround * qh_JOGGLEdefault
    +
    +notes:
    +rbox s r 100 | qhull QJ1e-15 QR0 generates 90% faults at distround 7e-16
    +rbox s r 100 | qhull QJ1e-14 QR0 generates 70% faults
    +rbox s r 100 | qhull QJ1e-13 QR0 generates 35% faults
    +rbox s r 100 | qhull QJ1e-12 QR0 generates 8% faults
    +rbox s r 100 | qhull QJ1e-11 QR0 generates 1% faults
    +rbox s r 100 | qhull QJ1e-10 QR0 generates 0% faults
    +rbox 1000 W0 | qhull QJ1e-12 QR0 generates 86% faults
    +rbox 1000 W0 | qhull QJ1e-11 QR0 generates 20% faults
    +rbox 1000 W0 | qhull QJ1e-10 QR0 generates 2% faults
    +the later have about 20 points per facet, each of which may interfere
    +
    +pick a value large enough to avoid retries on most inputs
    +*/
    +#define qh_JOGGLEdefault 30000.0
    +
    +/*----------------------------------
    +
    +qh_JOGGLEincrease
    +factor to increase qh.JOGGLEmax on qh_JOGGLEretry or qh_JOGGLEagain
    +*/
    +#define qh_JOGGLEincrease 10.0
    +
    +/*----------------------------------
    +
    +qh_JOGGLEretry
    +if ZZretry = qh_JOGGLEretry, increase qh.JOGGLEmax
    +
    +notes:
    +try twice at the original value in case of bad luck the first time
    +*/
    +#define qh_JOGGLEretry 2
    +
    +/*----------------------------------
    +
    +qh_JOGGLEagain
    +every following qh_JOGGLEagain, increase qh.JOGGLEmax
    +
    +notes:
    +1 is OK since it's already failed qh_JOGGLEretry times
    +*/
    +#define qh_JOGGLEagain 1
    +
    +/*----------------------------------
    +
    +qh_JOGGLEmaxincrease
    +maximum qh.JOGGLEmax due to qh_JOGGLEincrease
    +relative to qh.MAXwidth
    +
    +notes:
    +qh.joggleinput will retry at this value until qh_JOGGLEmaxretry
    +*/
    +#define qh_JOGGLEmaxincrease 1e-2
    +
    +/*----------------------------------
    +
    +qh_JOGGLEmaxretry
    +stop after qh_JOGGLEmaxretry attempts
    +*/
    +#define qh_JOGGLEmaxretry 100
    +
    +/*============================================================*/
    +/*============= performance related constants ================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  qh_HASHfactor
    +    total hash slots / used hash slots.  Must be at least 1.1.
    +
    +  notes:
    +    =2 for at worst 50% occupancy for qh.hash_table and normally 25% occupancy
    +*/
    +#define qh_HASHfactor 2
    +
    +/*----------------------------------
    +
    +  qh_VERIFYdirect
    +    with 'Tv' verify all points against all facets if op count is smaller
    +
    +  notes:
    +    if greater, calls qh_check_bestdist() instead
    +*/
    +#define qh_VERIFYdirect 1000000
    +
    +/*----------------------------------
    +
    +  qh_INITIALsearch
    +     if qh_INITIALmax, search points up to this dimension
    +*/
    +#define qh_INITIALsearch 6
    +
    +/*----------------------------------
    +
    +  qh_INITIALmax
    +    if dim >= qh_INITIALmax, use min/max coordinate points for initial simplex
    +
    +  notes:
    +    from points with non-zero determinants
    +    use option 'Qs' to override (much slower)
    +*/
    +#define qh_INITIALmax 8
    +
    +/*============================================================*/
    +/*============= memory constants =============================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  qh_MEMalign
    +    memory alignment for qh_meminitbuffers() in global.c
    +
    +  notes:
    +    to avoid bus errors, memory allocation must consider alignment requirements.
    +    malloc() automatically takes care of alignment.   Since mem.c manages
    +    its own memory, we need to explicitly specify alignment in
    +    qh_meminitbuffers().
    +
    +    A safe choice is sizeof(double).  sizeof(float) may be used if doubles
    +    do not occur in data structures and pointers are the same size.  Be careful
    +    of machines (e.g., DEC Alpha) with large pointers.
    +
    +    If using gcc, best alignment is  [fmax_() is defined in geom_r.h]
    +              #define qh_MEMalign fmax_(__alignof__(realT),__alignof__(void *))
    +*/
    +#define qh_MEMalign ((int)(fmax_(sizeof(realT), sizeof(void *))))
    +
    +/*----------------------------------
    +
    +  qh_MEMbufsize
    +    size of additional memory buffers
    +
    +  notes:
    +    used for qh_meminitbuffers() in global.c
    +*/
    +#define qh_MEMbufsize 0x10000       /* allocate 64K memory buffers */
    +
    +/*----------------------------------
    +
    +  qh_MEMinitbuf
    +    size of initial memory buffer
    +
    +  notes:
    +    use for qh_meminitbuffers() in global.c
    +*/
    +#define qh_MEMinitbuf 0x20000      /* initially allocate 128K buffer */
    +
    +/*----------------------------------
    +
    +  qh_INFINITE
    +    on output, indicates Voronoi center at infinity
    +*/
    +#define qh_INFINITE  -10.101
    +
    +/*----------------------------------
    +
    +  qh_DEFAULTbox
    +    default box size (Geomview expects 0.5)
    +
    +  qh_DEFAULTbox
    +    default box size for integer coorindate (rbox only)
    +*/
    +#define qh_DEFAULTbox 0.5
    +#define qh_DEFAULTzbox 1e6
    +
    +/*============================================================*/
    +/*============= conditional compilation ======================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  __cplusplus
    +    defined by C++ compilers
    +
    +  __MSC_VER
    +    defined by Microsoft Visual C++
    +
    +  __MWERKS__ && __INTEL__
    +    defined by Metrowerks when compiling for Windows (not Intel-based Macintosh)
    +
    +  __MWERKS__ && __POWERPC__
    +    defined by Metrowerks when compiling for PowerPC-based Macintosh
    +  __STDC__
    +    defined for strict ANSI C
    +*/
    +
    +/*----------------------------------
    +
    +  qh_COMPUTEfurthest
    +    compute furthest distance to an outside point instead of storing it with the facet
    +    =1 to compute furthest
    +
    +  notes:
    +    computing furthest saves memory but costs time
    +      about 40% more distance tests for partitioning
    +      removes facet->furthestdist
    +*/
    +#define qh_COMPUTEfurthest 0
    +
    +/*----------------------------------
    +
    +  qh_KEEPstatistics
    +    =0 removes most of statistic gathering and reporting
    +
    +  notes:
    +    if 0, code size is reduced by about 4%.
    +*/
    +#define qh_KEEPstatistics 1
    +
    +/*----------------------------------
    +
    +  qh_MAXoutside
    +    record outer plane for each facet
    +    =1 to record facet->maxoutside
    +
    +  notes:
    +    this takes a realT per facet and slightly slows down qhull
    +    it produces better outer planes for geomview output
    +*/
    +#define qh_MAXoutside 1
    +
    +/*----------------------------------
    +
    +  qh_NOmerge
    +    disables facet merging if defined
    +
    +  notes:
    +    This saves about 10% space.
    +
    +    Unless 'Q0'
    +      qh_NOmerge sets 'QJ' to avoid precision errors
    +
    +    #define qh_NOmerge
    +
    +  see:
    +    qh_NOmem in mem.c
    +
    +    see user.c/user_eg.c for removing io.o
    +*/
    +
    +/*----------------------------------
    +
    +  qh_NOtrace
    +    no tracing if defined
    +
    +  notes:
    +    This saves about 5% space.
    +
    +    #define qh_NOtrace
    +*/
    +
    +/*----------------------------------
    +
    +  qh_QHpointer
    +    access global data with pointer or static structure
    +
    +  qh_QHpointer  = 1     access globals via a pointer to allocated memory
    +                        enables qh_saveqhull() and qh_restoreqhull()
    +                        [2010, gcc] costs about 4% in time and 4% in space
    +                        [2003, msvc] costs about 8% in time and 2% in space
    +
    +                = 0     qh_qh and qh_qhstat are static data structures
    +                        only one instance of qhull() can be active at a time
    +                        default value
    +
    +  qh_QHpointer_dllimport and qh_dllimport define qh_qh as __declspec(dllimport) [libqhull.h]
    +  It is required for msvc-2005.  It is not needed for gcc.
    +
    +  notes:
    +    [jan'16] qh_QHpointer is deprecated for Qhull.  Use libqhull_r instead.
    +    all global variables for qhull are in qh, qhmem, and qhstat
    +    qh is defined in libqhull.h
    +    qhmem is defined in mem.h
    +    qhstat is defined in stat.h
    +
    +*/
    +#ifdef qh_QHpointer
    +#if qh_dllimport
    +#error QH6207 Qhull error: Use qh_QHpointer_dllimport instead of qh_dllimport with qh_QHpointer
    +#endif
    +#else
    +#define qh_QHpointer 0
    +#if qh_QHpointer_dllimport
    +#error QH6234 Qhull error: Use qh_dllimport instead of qh_QHpointer_dllimport when qh_QHpointer is not defined
    +#endif
    +#endif
    +#if 0  /* sample code */
    +    qhT *oldqhA, *oldqhB;
    +
    +    exitcode= qh_new_qhull(dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +    /* use results from first call to qh_new_qhull */
    +    oldqhA= qh_save_qhull();
    +    exitcode= qh_new_qhull(dimB, numpointsB, pointsB, ismalloc,
    +                      flags, outfile, errfile);
    +    /* use results from second call to qh_new_qhull */
    +    oldqhB= qh_save_qhull();
    +    qh_restore_qhull(&oldqhA);
    +    /* use results from first call to qh_new_qhull */
    +    qh_freeqhull(qh_ALL);  /* frees all memory used by first call */
    +    qh_restore_qhull(&oldqhB);
    +    /* use results from second call to qh_new_qhull */
    +    qh_freeqhull(!qh_ALL); /* frees long memory used by second call */
    +    qh_memfreeshort(&curlong, &totlong);  /* frees short memory and memory allocator */
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_QUICKhelp
    +    =1 to use abbreviated help messages, e.g., for degenerate inputs
    +*/
    +#define qh_QUICKhelp    0
    +
    +/*============================================================*/
    +/*============= -merge constants- ============================*/
    +/*============================================================*/
    +/*
    +   These constants effect facet merging.  You probably will not need
    +   to modify them.  They effect the performance of facet merging.
    +*/
    +
    +/*----------------------------------
    +
    +  qh_DIMmergeVertex
    +    max dimension for vertex merging (it is not effective in high-d)
    +*/
    +#define qh_DIMmergeVertex 6
    +
    +/*----------------------------------
    +
    +  qh_DIMreduceBuild
    +     max dimension for vertex reduction during build (slow in high-d)
    +*/
    +#define qh_DIMreduceBuild 5
    +
    +/*----------------------------------
    +
    +  qh_BESTcentrum
    +     if > 2*dim+n vertices, qh_findbestneighbor() tests centrums (faster)
    +     else, qh_findbestneighbor() tests all vertices (much better merges)
    +
    +  qh_BESTcentrum2
    +     if qh_BESTcentrum2 * DIM3 + BESTcentrum < #vertices tests centrums
    +*/
    +#define qh_BESTcentrum 20
    +#define qh_BESTcentrum2 2
    +
    +/*----------------------------------
    +
    +  qh_BESTnonconvex
    +    if > dim+n neighbors, qh_findbestneighbor() tests nonconvex ridges.
    +
    +  notes:
    +    It is needed because qh_findbestneighbor is slow for large facets
    +*/
    +#define qh_BESTnonconvex 15
    +
    +/*----------------------------------
    +
    +  qh_MAXnewmerges
    +    if >n newmerges, qh_merge_nonconvex() calls qh_reducevertices_centrums.
    +
    +  notes:
    +    It is needed because postmerge can merge many facets at once
    +*/
    +#define qh_MAXnewmerges 2
    +
    +/*----------------------------------
    +
    +  qh_MAXnewcentrum
    +    if <= dim+n vertices (n approximates the number of merges),
    +      reset the centrum in qh_updatetested() and qh_mergecycle_facets()
    +
    +  notes:
    +    needed to reduce cost and because centrums may move too much if
    +    many vertices in high-d
    +*/
    +#define qh_MAXnewcentrum 5
    +
    +/*----------------------------------
    +
    +  qh_COPLANARratio
    +    for 3-d+ merging, qh.MINvisible is n*premerge_centrum
    +
    +  notes:
    +    for non-merging, it's DISTround
    +*/
    +#define qh_COPLANARratio 3
    +
    +/*----------------------------------
    +
    +  qh_DISToutside
    +    When is a point clearly outside of a facet?
    +    Stops search in qh_findbestnew or qh_partitionall
    +    qh_findbest uses qh.MINoutside since since it is only called if no merges.
    +
    +  notes:
    +    'Qf' always searches for best facet
    +    if !qh.MERGING, same as qh.MINoutside.
    +    if qh_USEfindbestnew, increase value since neighboring facets may be ill-behaved
    +      [Note: Zdelvertextot occurs normally with interior points]
    +            RBOX 1000 s Z1 G1e-13 t1001188774 | QHULL Tv
    +    When there is a sharp edge, need to move points to a
    +    clearly good facet; otherwise may be lost in another partitioning.
    +    if too big then O(n^2) behavior for partitioning in cone
    +    if very small then important points not processed
    +    Needed in qh_partitionall for
    +      RBOX 1000 s Z1 G1e-13 t1001032651 | QHULL Tv
    +    Needed in qh_findbestnew for many instances of
    +      RBOX 1000 s Z1 G1e-13 t | QHULL Tv
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_DISToutside ((qh_USEfindbestnew ? 2 : 1) * \
    +     fmax_((qh MERGING ? 2 : 1)*qh MINoutside, qh max_outside))
    +
    +/*----------------------------------
    +
    +  qh_RATIOnearinside
    +    ratio of qh.NEARinside to qh.ONEmerge for retaining inside points for
    +    qh_check_maxout().
    +
    +  notes:
    +    This is overkill since do not know the correct value.
    +    It effects whether 'Qc' reports all coplanar points
    +    Not used for 'd' since non-extreme points are coplanar
    +*/
    +#define qh_RATIOnearinside 5
    +
    +/*----------------------------------
    +
    +  qh_SEARCHdist
    +    When is a facet coplanar with the best facet?
    +    qh_findbesthorizon: all coplanar facets of the best facet need to be searched.
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_SEARCHdist ((qh_USEfindbestnew ? 2 : 1) * \
    +      (qh max_outside + 2 * qh DISTround + fmax_( qh MINvisible, qh MAXcoplanar)));
    +
    +/*----------------------------------
    +
    +  qh_USEfindbestnew
    +     Always use qh_findbestnew for qh_partitionpoint, otherwise use
    +     qh_findbestnew if merged new facet or sharpnewfacets.
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_USEfindbestnew (zzval_(Ztotmerge) > 50)
    +
    +/*----------------------------------
    +
    +  qh_WIDEcoplanar
    +    n*MAXcoplanar or n*MINvisible for a WIDEfacet
    +
    +    if vertex is further than qh.WIDEfacet from the hyperplane
    +    then its ridges are not counted in computing the area, and
    +    the facet's centrum is frozen.
    +
    +  notes:
    +   qh.WIDEfacet= max(qh.MAXoutside,qh_WIDEcoplanar*qh.MAXcoplanar,
    +      qh_WIDEcoplanar * qh.MINvisible);
    +*/
    +#define qh_WIDEcoplanar 6
    +
    +/*----------------------------------
    +
    +  qh_WIDEduplicate
    +    Merge ratio for errexit from qh_forcedmerges due to duplicate ridge
    +    Override with option Q12 no-wide-duplicate
    +
    +    Notes:
    +      Merging a duplicate ridge can lead to very wide facets.
    +      A future release of qhull will avoid duplicate ridges by removing duplicate sub-ridges from the horizon
    +*/
    +#define qh_WIDEduplicate 100
    +
    +/*----------------------------------
    +
    +  qh_MAXnarrow
    +    max. cosine in initial hull that sets qh.NARROWhull
    +
    +  notes:
    +    If qh.NARROWhull, the initial partition does not make
    +    coplanar points.  If narrow, a coplanar point can be
    +    coplanar to two facets of opposite orientations and
    +    distant from the exact convex hull.
    +
    +    Conservative estimate.  Don't actually see problems until it is -1.0
    +*/
    +#define qh_MAXnarrow -0.99999999
    +
    +/*----------------------------------
    +
    +  qh_WARNnarrow
    +    max. cosine in initial hull to warn about qh.NARROWhull
    +
    +  notes:
    +    this is a conservative estimate.
    +    Don't actually see problems until it is -1.0.  See qh-impre.htm
    +*/
    +#define qh_WARNnarrow -0.999999999999999
    +
    +/*----------------------------------
    +
    +  qh_ZEROdelaunay
    +    a zero Delaunay facet occurs for input sites coplanar with their convex hull
    +    the last normal coefficient of a zero Delaunay facet is within
    +        qh_ZEROdelaunay * qh.ANGLEround of 0
    +
    +  notes:
    +    qh_ZEROdelaunay does not allow for joggled input ('QJ').
    +
    +    You can avoid zero Delaunay facets by surrounding the input with a box.
    +
    +    Use option 'PDk:-n' to explicitly define zero Delaunay facets
    +      k= dimension of input sites (e.g., 3 for 3-d Delaunay triangulation)
    +      n= the cutoff for zero Delaunay facets (e.g., 'PD3:-1e-12')
    +*/
    +#define qh_ZEROdelaunay 2
    +
    +/*============================================================*/
    +/*============= Microsoft DevStudio ==========================*/
    +/*============================================================*/
    +
    +/*
    +   Finding Memory Leaks Using the CRT Library
    +   https://msdn.microsoft.com/en-us/library/x98tx3cf(v=vs.100).aspx
    +
    +   Reports enabled in qh_lib_check for Debug window and stderr
    +
    +   From 2005=>msvcr80d, 2010=>msvcr100d, 2012=>msvcr110d
    +
    +   Watch: {,,msvcr80d.dll}_crtBreakAlloc  Value from {n} in the leak report
    +   _CrtSetBreakAlloc(689); // qh_lib_check() [global_r.c]
    +
    +   Examples
    +     http://free-cad.sourceforge.net/SrcDocu/d2/d7f/MemDebug_8cpp_source.html
    +     https://github.com/illlust/Game/blob/master/library/MemoryLeak.cpp
    +*/
    +#if 0   /* off (0) by default for QHULL_CRTDBG */
    +#define QHULL_CRTDBG
    +#endif
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)
    +#define _CRTDBG_MAP_ALLOC
    +#include 
    +#include 
    +#endif
    +#endif /* qh_DEFuser */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/usermem.c b/xs/src/qhull/src/libqhull/usermem.c
    new file mode 100644
    index 0000000000..0e99e8f66c
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/usermem.c
    @@ -0,0 +1,94 @@
    +/*
      ---------------------------------
    +
    +   usermem.c
    +   qh_exit(), qh_free(), and qh_malloc()
    +
    +   See README.txt.
    +
    +   If you redefine one of these functions you must redefine all of them.
    +   If you recompile and load this file, then usermem.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull.h for data structures, macros, and user-callable functions.
    +   See user.c for qhull-related, redefinable functions
    +   see user.h for user-definable constants
    +   See userprintf.c for qh_fprintf and userprintf_rbox.c for qh_fprintf_rbox
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull.h"
    +
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +  qh_exit( exitcode )
    +    exit program
    +
    +  notes:
    +    qh_exit() is called when qh_errexit() and longjmp() are not available.
    +
    +    This is the only use of exit() in Qhull
    +    To replace qh_exit with 'throw', see libqhullcpp/usermem_r-cpp.cpp
    +*/
    +void qh_exit(int exitcode) {
    +    exit(exitcode);
    +} /* exit */
    +
    +/*---------------------------------
    +
    +  qh_fprintf_stderr( msgcode, format, list of args )
    +    fprintf to stderr with msgcode (non-zero)
    +
    +  notes:
    +    qh_fprintf_stderr() is called when qh.ferr is not defined, usually due to an initialization error
    +    
    +    It is typically followed by qh_errexit().
    +
    +    Redefine this function to avoid using stderr
    +
    +    Use qh_fprintf [userprintf.c] for normal printing
    +*/
    +void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    va_start(args, fmt);
    +    if(msgcode)
    +      fprintf(stderr, "QH%.4d ", msgcode);
    +    vfprintf(stderr, fmt, args);
    +    va_end(args);
    +} /* fprintf_stderr */
    +
    +/*---------------------------------
    +
    +  qh_free( mem )
    +    free memory
    +
    +  notes:
    +    same as free()
    +    No calls to qh_errexit() 
    +*/
    +void qh_free(void *mem) {
    +    free(mem);
    +} /* free */
    +
    +/*---------------------------------
    +
    +    qh_malloc( mem )
    +      allocate memory
    +
    +    notes:
    +      same as malloc()
    +*/
    +void *qh_malloc(size_t size) {
    +    return malloc(size);
    +} /* malloc */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/userprintf.c b/xs/src/qhull/src/libqhull/userprintf.c
    new file mode 100644
    index 0000000000..190d7cd79b
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/userprintf.c
    @@ -0,0 +1,66 @@
    +/*
      ---------------------------------
    +
    +   userprintf.c
    +   qh_fprintf()
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   If you recompile and load this file, then userprintf.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull.h for data structures, macros, and user-callable functions.
    +   See user.c for qhull-related, redefinable functions
    +   see user.h for user-definable constants
    +   See usermem.c for qh_exit(), qh_free(), and qh_malloc()
    +   see Qhull.cpp and RboxPoints.cpp for examples.
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull.h"
    +#include "mem.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +   qh_fprintf(fp, msgcode, format, list of args )
    +     print arguments to *fp according to format
    +     Use qh_fprintf_rbox() for rboxlib.c
    +
    +   notes:
    +     same as fprintf()
    +     fgets() is not trapped like fprintf()
    +     exit qh_fprintf via qh_errexit()
    +     may be called for errors in qh_initstatistics and qh_meminit
    +*/
    +
    +void qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    if (!fp) {
    +        /* could use qhmem.ferr, but probably better to be cautious */
    +        qh_fprintf_stderr(6232, "Qhull internal error (userprintf.c): fp is 0.  Wrong qh_fprintf called.\n");
    +        qh_errexit(6232, NULL, NULL);
    +    }
    +    va_start(args, fmt);
    +#if qh_QHpointer
    +    if (qh_qh && qh ANNOTATEoutput) {
    +#else
    +    if (qh ANNOTATEoutput) {
    +#endif
    +      fprintf(fp, "[QH%.4d]", msgcode);
    +    }else if (msgcode >= MSG_ERROR && msgcode < MSG_STDERR ) {
    +      fprintf(fp, "QH%.4d ", msgcode);
    +    }
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +
    +    /* Place debugging traps here. Use with option 'Tn' */
    +
    +} /* qh_fprintf */
    +
    diff --git a/xs/src/qhull/src/libqhull/userprintf_rbox.c b/xs/src/qhull/src/libqhull/userprintf_rbox.c
    new file mode 100644
    index 0000000000..8edd2001aa
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/userprintf_rbox.c
    @@ -0,0 +1,53 @@
    +/*
      ---------------------------------
    +
    +   userprintf_rbox.c
    +   qh_fprintf_rbox()
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   If you recompile and load this file, then userprintf_rbox.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull.h for data structures, macros, and user-callable functions.
    +   See user.c for qhull-related, redefinable functions
    +   see user.h for user-definable constants
    +   See usermem.c for qh_exit(), qh_free(), and qh_malloc()
    +   see Qhull.cpp and RboxPoints.cpp for examples.
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +   qh_fprintf_rbox(fp, msgcode, format, list of args )
    +     print arguments to *fp according to format
    +     Use qh_fprintf_rbox() for rboxlib.c
    +
    +   notes:
    +     same as fprintf()
    +     fgets() is not trapped like fprintf()
    +     exit qh_fprintf_rbox via qh_errexit_rbox()
    +*/
    +
    +void qh_fprintf_rbox(FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    if (!fp) {
    +        qh_fprintf_stderr(6231, "Qhull internal error (userprintf_rbox.c): fp is 0.  Wrong qh_fprintf_rbox called.\n");
    +        qh_errexit_rbox(6231);
    +    }
    +    if (msgcode >= MSG_ERROR && msgcode < MSG_STDERR)
    +      fprintf(fp, "QH%.4d ", msgcode);
    +    va_start(args, fmt);
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +} /* qh_fprintf_rbox */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/Makefile b/xs/src/qhull/src/libqhull_r/Makefile
    new file mode 100644
    index 0000000000..5c40969e01
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/Makefile
    @@ -0,0 +1,240 @@
    +# Simple gcc Makefile for reentrant qhull and rbox (default gcc/g++)
    +#
    +#   make help
    +#   See README.txt and ../../Makefile
    +#       
    +# Variables
    +#   BINDIR         directory where to copy executables
    +#   DESTDIR        destination directory for 'make install'
    +#   DOCDIR         directory where to copy html documentation
    +#   INCDIR         directory where to copy headers
    +#   LIBDIR         directory where to copy libraries
    +#   MANDIR         directory where to copy manual pages
    +#   PRINTMAN       command for printing manual pages
    +#   PRINTC         command for printing C files
    +#   CC             ANSI C or C++ compiler
    +#   CC_OPTS1       options used to compile .c files
    +#   CC_OPTS2       options used to link .o files
    +#   CC_OPTS3       options to build shared libraries
    +#
    +#   LIBQHULL_OBJS  .o files for linking
    +#   LIBQHULL_HDRS  .h files for printing
    +#   CFILES         .c files for printing
    +#   DOCFILES       documentation files
    +#   FILES          miscellaneous files for printing
    +#   TFILES         .txt versions of html files
    +#   FILES          all other files
    +#   LIBQHULL_OBJS  specifies the object files of libqhullstatic_r.a
    +#
    +# Results
    +#   rbox           Generates points sets for qhull, qconvex, etc.
    +#   qhull          Computes convex hulls and related structures
    +#   qconvex, qdelaunay, qhalf, qvoronoi
    +#                  Specializations of qhull for each geometric structure
    +#   libqhullstatic_r.a Static library for reentrant qhull
    +#   testqset_r     Standalone test of reentrant qset_r.c with mem_r.c
    +#   user_eg        An example of using qhull (reentrant)
    +#   user_eg2       An example of using qhull (reentrant)
    +#
    +# Make targets
    +#   make           Build results using gcc or another compiler
    +#   make clean     Remove object files
    +#   make cleanall  Remove generated files
    +#   make doc       Print documentation
    +#   make help
    +#   make install   Copy qhull, rbox, qhull.1, rbox.1 to BINDIR, MANDIR
    +#   make new       Rebuild qhull and rbox from source
    +#   make printall  Print all files
    +#   make qtest     Quick test of qset, rbox, and qhull
    +#   make test      Quck test of qhull, qconvex, etc.
    +#
    +# Do not replace tabs with spaces.  Needed for build rules
    +# Unix line endings (\n)
    +# $Id: //main/2015/qhull/src/libqhull_r/Makefile#6 $
    +
    +DESTDIR = /usr/local
    +BINDIR	= $(DESTDIR)/bin
    +INCDIR	= $(DESTDIR)/include
    +LIBDIR	= $(DESTDIR)/lib
    +DOCDIR	= $(DESTDIR)/share/doc/qhull
    +MANDIR	= $(DESTDIR)/share/man/man1
    +
    +# if you do not have enscript, try a2ps or just use lpr.  The files are text.
    +PRINTMAN = enscript -2rl
    +PRINTC = enscript -2r
    +# PRINTMAN = lpr
    +# PRINTC = lpr
    +
    +#for Gnu's gcc compiler, -O3 for optimization, -g for debugging, -pg for profiling
    +# -fpic  needed for gcc x86_64-linux-gnu.  Not needed for mingw
    +CC        = gcc
    +CC_OPTS1  = -O3 -ansi -I../../src -fpic $(CC_WARNINGS)
    +
    +# for Sun's cc compiler, -fast or O2 for optimization, -g for debugging, -Xc for ANSI
    +#CC       = cc
    +#CC_OPTS1 = -Xc -v -fast -I../../src 
    +
    +# for Silicon Graphics cc compiler, -O2 for optimization, -g for debugging
    +#CC       = cc
    +#CC_OPTS1 = -ansi -O2 -I../../src 
    +
    +# for Next cc compiler with fat executable
    +#CC       = cc
    +#CC_OPTS1 = -ansi -O2 -I../../src -arch m68k -arch i386 -arch hppa
    +
    +# For loader, ld, 
    +CC_OPTS2 = $(CC_OPTS1)
    +
    +# Default targets for make
    +
    +all: qhull_links qhull_all qtest
    +
    +help:
    +	head -n 50 Makefile
    +
    +clean:
    +	rm -f *.o 
    +	# Delete linked files from other directories [qhull_links]
    +	rm -f qconvex_r.c unix_r.c qdelaun_r.c qhalf_r.c qvoronoi_r.c rbox_r.c
    +	rm -f user_eg_r.c user_eg2_r.c testqset_r.c
    +	
    +cleanall: clean
    +	rm -f qconvex qdelaunay qhalf qvoronoi qhull *.exe
    +	rm -f core user_eg_r user_eg2_r testqset_r libqhullstatic_r.a
    +
    +doc: 
    +	$(PRINTMAN) $(TXTFILES) $(DOCFILES)
    +
    +install:
    +	mkdir -p $(BINDIR)
    +	mkdir -p $(DOCDIR)
    +	mkdir -p $(INCDIR)/libqhull
    +	mkdir -p $(MANDIR)
    +	cp -p qconvex qdelaunay qhalf qhull qvoronoi rbox $(BINDIR)
    +	cp -p libqhullstatic_r.a $(LIBDIR)
    +	cp -p ../../html/qhull.man $(MANDIR)/qhull.1
    +	cp -p ../../html/rbox.man $(MANDIR)/rbox.1
    +	cp -p ../../html/* $(DOCDIR)
    +	cp *.h $(INCDIR)/libqhull_r
    +
    +new:	cleanall all
    +
    +printall: doc printh printc printf
    +
    +printh:
    +	$(PRINTC) $(LIBQHULL_HDRS)
    +
    +printc:
    +	$(PRINTC) $(CFILES)
    +
    +# LIBQHULL_OBJS_1 ordered by frequency of execution with small files at end.  Better locality.
    +# Same definitions as ../../Makefile
    +
    +LIBQHULLS_OBJS_1= global_r.o stat_r.o geom2_r.o poly2_r.o merge_r.o \
    +        libqhull_r.o geom_r.o poly_r.o qset_r.o mem_r.o random_r.o 
    +
    +LIBQHULLS_OBJS_2= $(LIBQHULLS_OBJS_1) usermem_r.o userprintf_r.o io_r.o user_r.o
    +
    +LIBQHULLS_OBJS= $(LIBQHULLS_OBJS_2)  rboxlib_r.o userprintf_rbox_r.o
    +
    +LIBQHULL_HDRS= user_r.h libqhull_r.h qhull_ra.h geom_r.h \
    +        io_r.h mem_r.h merge_r.h poly_r.h random_r.h \
    +        qset_r.h stat_r.h
    +
    +# CFILES ordered alphabetically after libqhull.c 
    +CFILES= ../qhull/unix_r.c libqhull_r.c geom_r.c geom2_r.c global_r.c io_r.c \
    +	mem_r.c merge_r.c poly_r.c poly2_r.c random_r.c rboxlib_r.c \
    +	qset_r.c stat_r.c user_r.c usermem_r.c userprintf_r.c \
    +	../qconvex/qconvex.c ../qdelaunay/qdelaun.c ../qhalf/qhalf.c ../qvoronoi/qvoronoi.c
    +
    +TXTFILES= ../../Announce.txt ../../REGISTER.txt ../../COPYING.txt ../../README.txt ../Changes.txt
    +DOCFILES= ../../html/rbox.txt ../../html/qhull.txt
    +
    +.c.o:
    +	$(CC) -c $(CC_OPTS1) -o $@ $<
    +
    +# Work around problems with ../ in Red Hat Linux
    +qhull_links:
    +	# On MINSYS, 'ln -s' may create a copy instead of a symbolic link
    +	[ -f qconvex_r.c ]  || ln -s ../qconvex/qconvex_r.c
    +	[ -f qdelaun_r.c ]  || ln -s ../qdelaunay/qdelaun_r.c
    +	[ -f qhalf_r.c ]    || ln -s ../qhalf/qhalf_r.c
    +	[ -f qvoronoi_r.c ] || ln -s ../qvoronoi/qvoronoi_r.c
    +	[ -f rbox_r.c ]     || ln -s ../rbox/rbox_r.c
    +	[ -f testqset_r.c ] || ln -s ../testqset_r/testqset_r.c
    +	[ -f unix_r.c ]     || ln -s ../qhull/unix_r.c
    +	[ -f user_eg_r.c ]  || ln -s ../user_eg/user_eg_r.c
    +	[ -f user_eg2_r.c ] || ln -s ../user_eg2/user_eg2_r.c
    +
    +# compile qhull without using bin/libqhullstatic_r.a
    +qhull_all: qconvex_r.o qdelaun_r.o qhalf_r.o qvoronoi_r.o unix_r.o user_eg_r.o user_eg2_r.o rbox_r.o testqset_r.o $(LIBQHULLS_OBJS)
    +	$(CC) -o qconvex $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) qconvex_r.o
    +	$(CC) -o qdelaunay $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) qdelaun_r.o
    +	$(CC) -o qhalf $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) qhalf_r.o
    +	$(CC) -o qvoronoi $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) qvoronoi_r.o
    +	$(CC) -o qhull $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) unix_r.o 
    +	$(CC) -o rbox $(CC_OPTS2) -lm $(LIBQHULLS_OBJS) rbox_r.o
    +	$(CC) -o user_eg $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) user_eg_r.o 
    +	$(CC) -o user_eg2 $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_1) user_eg2_r.o  usermem_r.o userprintf_r.o io_r.o
    +	$(CC) -o testqset_r $(CC_OPTS2) -lm mem_r.o qset_r.o usermem_r.o testqset_r.o
    +	-ar -rs libqhullstatic_r.a $(LIBQHULLS_OBJS)
    +	#libqhullstatic_r.a is not needed for qhull
    +	#If 'ar -rs' fails try using 'ar -s' with 'ranlib'
    +	#ranlib libqhullstatic_r.a
    +
    +qtest:
    +	@echo ============================================
    +	@echo == make qtest ==============================
    +	@echo ============================================
    +	@echo -n "== "
    +	@date
    +	@echo
    +	@echo Testing qset.c and mem.c with testqset
    +	./testqset_r 10000
    +	@echo Run the qhull smoketest
    +	./rbox D4 | ./qhull
    +	@echo ============================================
    +	@echo == To smoketest qhull programs
    +	@echo '==     make test'
    +	@echo ============================================
    +	@echo
    +	@echo ============================================
    +	@echo == For all make targets
    +	@echo '==     make help'
    +	@echo ============================================
    +	@echo
    +
    +test: qtest
    +	@echo ==============================
    +	@echo ========= qconvex ============
    +	@echo ==============================
    +	-./rbox 10 | ./qconvex Tv 
    +	@echo
    +	@echo ==============================
    +	@echo ========= qdelaunay ==========
    +	@echo ==============================
    +	-./rbox 10 | ./qdelaunay Tv 
    +	@echo
    +	@echo ==============================
    +	@echo ========= qhalf ==============
    +	@echo ==============================
    +	-./rbox 10 | ./qconvex FQ FV n Tv | ./qhalf Tv
    +	@echo
    +	@echo ==============================
    +	@echo ========= qvoronoi ===========
    +	@echo ==============================
    +	-./rbox 10 | ./qvoronoi Tv
    +	@echo
    +	@echo ==============================
    +	@echo ========= user_eg ============
    +	@echo == w/o shared library ========
    +	@echo ==============================
    +	-./user_eg
    +	@echo
    +	@echo ==============================
    +	@echo ========= user_eg2 ===========
    +	@echo ==============================
    +	-./user_eg2
    +	@echo
    +
    +# end of Makefile
    diff --git a/xs/src/qhull/src/libqhull_r/geom2_r.c b/xs/src/qhull/src/libqhull_r/geom2_r.c
    new file mode 100644
    index 0000000000..48addba1cf
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/geom2_r.c
    @@ -0,0 +1,2096 @@
    +/*
      ---------------------------------
    +
    +
    +   geom2_r.c
    +   infrequently used geometric routines of qhull
    +
    +   see qh-geom_r.htm and geom_r.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/geom2_r.c#6 $$Change: 2065 $
    +   $DateTime: 2016/01/18 13:51:04 $$Author: bbarber $
    +
    +   frequently used code goes into geom_r.c
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*================== functions in alphabetic order ============*/
    +
    +/*---------------------------------
    +
    +  qh_copypoints(qh, points, numpoints, dimension)
    +    return qh_malloc'd copy of points
    +  
    +  notes:
    +    qh_free the returned points to avoid a memory leak
    +*/
    +coordT *qh_copypoints(qhT *qh, coordT *points, int numpoints, int dimension) {
    +  int size;
    +  coordT *newpoints;
    +
    +  size= numpoints * dimension * (int)sizeof(coordT);
    +  if (!(newpoints= (coordT*)qh_malloc((size_t)size))) {
    +    qh_fprintf(qh, qh->ferr, 6004, "qhull error: insufficient memory to copy %d points\n",
    +        numpoints);
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  memcpy((char *)newpoints, (char *)points, (size_t)size); /* newpoints!=0 by QH6004 */
    +  return newpoints;
    +} /* copypoints */
    +
    +/*---------------------------------
    +
    +  qh_crossproduct( dim, vecA, vecB, vecC )
    +    crossproduct of 2 dim vectors
    +    C= A x B
    +
    +  notes:
    +    from Glasner, Graphics Gems I, p. 639
    +    only defined for dim==3
    +*/
    +void qh_crossproduct(int dim, realT vecA[3], realT vecB[3], realT vecC[3]){
    +
    +  if (dim == 3) {
    +    vecC[0]=   det2_(vecA[1], vecA[2],
    +                     vecB[1], vecB[2]);
    +    vecC[1]= - det2_(vecA[0], vecA[2],
    +                     vecB[0], vecB[2]);
    +    vecC[2]=   det2_(vecA[0], vecA[1],
    +                     vecB[0], vecB[1]);
    +  }
    +} /* vcross */
    +
    +/*---------------------------------
    +
    +  qh_determinant(qh, rows, dim, nearzero )
    +    compute signed determinant of a square matrix
    +    uses qh.NEARzero to test for degenerate matrices
    +
    +  returns:
    +    determinant
    +    overwrites rows and the matrix
    +    if dim == 2 or 3
    +      nearzero iff determinant < qh->NEARzero[dim-1]
    +      (!quite correct, not critical)
    +    if dim >= 4
    +      nearzero iff diagonal[k] < qh->NEARzero[k]
    +*/
    +realT qh_determinant(qhT *qh, realT **rows, int dim, boolT *nearzero) {
    +  realT det=0;
    +  int i;
    +  boolT sign= False;
    +
    +  *nearzero= False;
    +  if (dim < 2) {
    +    qh_fprintf(qh, qh->ferr, 6005, "qhull internal error (qh_determinate): only implemented for dimension >= 2\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }else if (dim == 2) {
    +    det= det2_(rows[0][0], rows[0][1],
    +                 rows[1][0], rows[1][1]);
    +    if (fabs_(det) < 10*qh->NEARzero[1])  /* not really correct, what should this be? */
    +      *nearzero= True;
    +  }else if (dim == 3) {
    +    det= det3_(rows[0][0], rows[0][1], rows[0][2],
    +                 rows[1][0], rows[1][1], rows[1][2],
    +                 rows[2][0], rows[2][1], rows[2][2]);
    +    if (fabs_(det) < 10*qh->NEARzero[2])  /* what should this be?  det 5.5e-12 was flat for qh_maxsimplex of qdelaunay 0,0 27,27 -36,36 -9,63 */
    +      *nearzero= True;
    +  }else {
    +    qh_gausselim(qh, rows, dim, dim, &sign, nearzero);  /* if nearzero, diagonal still ok*/
    +    det= 1.0;
    +    for (i=dim; i--; )
    +      det *= (rows[i])[i];
    +    if (sign)
    +      det= -det;
    +  }
    +  return det;
    +} /* determinant */
    +
    +/*---------------------------------
    +
    +  qh_detjoggle(qh, points, numpoints, dimension )
    +    determine default max joggle for point array
    +      as qh_distround * qh_JOGGLEdefault
    +
    +  returns:
    +    initial value for JOGGLEmax from points and REALepsilon
    +
    +  notes:
    +    computes DISTround since qh_maxmin not called yet
    +    if qh->SCALElast, last dimension will be scaled later to MAXwidth
    +
    +    loop duplicated from qh_maxmin
    +*/
    +realT qh_detjoggle(qhT *qh, pointT *points, int numpoints, int dimension) {
    +  realT abscoord, distround, joggle, maxcoord, mincoord;
    +  pointT *point, *pointtemp;
    +  realT maxabs= -REALmax;
    +  realT sumabs= 0;
    +  realT maxwidth= 0;
    +  int k;
    +
    +  for (k=0; k < dimension; k++) {
    +    if (qh->SCALElast && k == dimension-1)
    +      abscoord= maxwidth;
    +    else if (qh->DELAUNAY && k == dimension-1) /* will qh_setdelaunay() */
    +      abscoord= 2 * maxabs * maxabs;  /* may be low by qh->hull_dim/2 */
    +    else {
    +      maxcoord= -REALmax;
    +      mincoord= REALmax;
    +      FORALLpoint_(qh, points, numpoints) {
    +        maximize_(maxcoord, point[k]);
    +        minimize_(mincoord, point[k]);
    +      }
    +      maximize_(maxwidth, maxcoord-mincoord);
    +      abscoord= fmax_(maxcoord, -mincoord);
    +    }
    +    sumabs += abscoord;
    +    maximize_(maxabs, abscoord);
    +  } /* for k */
    +  distround= qh_distround(qh, qh->hull_dim, maxabs, sumabs);
    +  joggle= distround * qh_JOGGLEdefault;
    +  maximize_(joggle, REALepsilon * qh_JOGGLEdefault);
    +  trace2((qh, qh->ferr, 2001, "qh_detjoggle: joggle=%2.2g maxwidth=%2.2g\n", joggle, maxwidth));
    +  return joggle;
    +} /* detjoggle */
    +
    +/*---------------------------------
    +
    +  qh_detroundoff(qh)
    +    determine maximum roundoff errors from
    +      REALepsilon, REALmax, REALmin, qh.hull_dim, qh.MAXabs_coord,
    +      qh.MAXsumcoord, qh.MAXwidth, qh.MINdenom_1
    +
    +    accounts for qh.SETroundoff, qh.RANDOMdist, qh->MERGEexact
    +      qh.premerge_cos, qh.postmerge_cos, qh.premerge_centrum,
    +      qh.postmerge_centrum, qh.MINoutside,
    +      qh_RATIOnearinside, qh_COPLANARratio, qh_WIDEcoplanar
    +
    +  returns:
    +    sets qh.DISTround, etc. (see below)
    +    appends precision constants to qh.qhull_options
    +
    +  see:
    +    qh_maxmin() for qh.NEARzero
    +
    +  design:
    +    determine qh.DISTround for distance computations
    +    determine minimum denominators for qh_divzero
    +    determine qh.ANGLEround for angle computations
    +    adjust qh.premerge_cos,... for roundoff error
    +    determine qh.ONEmerge for maximum error due to a single merge
    +    determine qh.NEARinside, qh.MAXcoplanar, qh.MINvisible,
    +      qh.MINoutside, qh.WIDEfacet
    +    initialize qh.max_vertex and qh.minvertex
    +*/
    +void qh_detroundoff(qhT *qh) {
    +
    +  qh_option(qh, "_max-width", NULL, &qh->MAXwidth);
    +  if (!qh->SETroundoff) {
    +    qh->DISTround= qh_distround(qh, qh->hull_dim, qh->MAXabs_coord, qh->MAXsumcoord);
    +    if (qh->RANDOMdist)
    +      qh->DISTround += qh->RANDOMfactor * qh->MAXabs_coord;
    +    qh_option(qh, "Error-roundoff", NULL, &qh->DISTround);
    +  }
    +  qh->MINdenom= qh->MINdenom_1 * qh->MAXabs_coord;
    +  qh->MINdenom_1_2= sqrt(qh->MINdenom_1 * qh->hull_dim) ;  /* if will be normalized */
    +  qh->MINdenom_2= qh->MINdenom_1_2 * qh->MAXabs_coord;
    +                                              /* for inner product */
    +  qh->ANGLEround= 1.01 * qh->hull_dim * REALepsilon;
    +  if (qh->RANDOMdist)
    +    qh->ANGLEround += qh->RANDOMfactor;
    +  if (qh->premerge_cos < REALmax/2) {
    +    qh->premerge_cos -= qh->ANGLEround;
    +    if (qh->RANDOMdist)
    +      qh_option(qh, "Angle-premerge-with-random", NULL, &qh->premerge_cos);
    +  }
    +  if (qh->postmerge_cos < REALmax/2) {
    +    qh->postmerge_cos -= qh->ANGLEround;
    +    if (qh->RANDOMdist)
    +      qh_option(qh, "Angle-postmerge-with-random", NULL, &qh->postmerge_cos);
    +  }
    +  qh->premerge_centrum += 2 * qh->DISTround;    /*2 for centrum and distplane()*/
    +  qh->postmerge_centrum += 2 * qh->DISTround;
    +  if (qh->RANDOMdist && (qh->MERGEexact || qh->PREmerge))
    +    qh_option(qh, "Centrum-premerge-with-random", NULL, &qh->premerge_centrum);
    +  if (qh->RANDOMdist && qh->POSTmerge)
    +    qh_option(qh, "Centrum-postmerge-with-random", NULL, &qh->postmerge_centrum);
    +  { /* compute ONEmerge, max vertex offset for merging simplicial facets */
    +    realT maxangle= 1.0, maxrho;
    +
    +    minimize_(maxangle, qh->premerge_cos);
    +    minimize_(maxangle, qh->postmerge_cos);
    +    /* max diameter * sin theta + DISTround for vertex to its hyperplane */
    +    qh->ONEmerge= sqrt((realT)qh->hull_dim) * qh->MAXwidth *
    +      sqrt(1.0 - maxangle * maxangle) + qh->DISTround;
    +    maxrho= qh->hull_dim * qh->premerge_centrum + qh->DISTround;
    +    maximize_(qh->ONEmerge, maxrho);
    +    maxrho= qh->hull_dim * qh->postmerge_centrum + qh->DISTround;
    +    maximize_(qh->ONEmerge, maxrho);
    +    if (qh->MERGING)
    +      qh_option(qh, "_one-merge", NULL, &qh->ONEmerge);
    +  }
    +  qh->NEARinside= qh->ONEmerge * qh_RATIOnearinside; /* only used if qh->KEEPnearinside */
    +  if (qh->JOGGLEmax < REALmax/2 && (qh->KEEPcoplanar || qh->KEEPinside)) {
    +    realT maxdist;             /* adjust qh.NEARinside for joggle */
    +    qh->KEEPnearinside= True;
    +    maxdist= sqrt((realT)qh->hull_dim) * qh->JOGGLEmax + qh->DISTround;
    +    maxdist= 2*maxdist;        /* vertex and coplanar point can joggle in opposite directions */
    +    maximize_(qh->NEARinside, maxdist);  /* must agree with qh_nearcoplanar() */
    +  }
    +  if (qh->KEEPnearinside)
    +    qh_option(qh, "_near-inside", NULL, &qh->NEARinside);
    +  if (qh->JOGGLEmax < qh->DISTround) {
    +    qh_fprintf(qh, qh->ferr, 6006, "qhull error: the joggle for 'QJn', %.2g, is below roundoff for distance computations, %.2g\n",
    +         qh->JOGGLEmax, qh->DISTround);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh->MINvisible > REALmax/2) {
    +    if (!qh->MERGING)
    +      qh->MINvisible= qh->DISTround;
    +    else if (qh->hull_dim <= 3)
    +      qh->MINvisible= qh->premerge_centrum;
    +    else
    +      qh->MINvisible= qh_COPLANARratio * qh->premerge_centrum;
    +    if (qh->APPROXhull && qh->MINvisible > qh->MINoutside)
    +      qh->MINvisible= qh->MINoutside;
    +    qh_option(qh, "Visible-distance", NULL, &qh->MINvisible);
    +  }
    +  if (qh->MAXcoplanar > REALmax/2) {
    +    qh->MAXcoplanar= qh->MINvisible;
    +    qh_option(qh, "U-coplanar-distance", NULL, &qh->MAXcoplanar);
    +  }
    +  if (!qh->APPROXhull) {             /* user may specify qh->MINoutside */
    +    qh->MINoutside= 2 * qh->MINvisible;
    +    if (qh->premerge_cos < REALmax/2)
    +      maximize_(qh->MINoutside, (1- qh->premerge_cos) * qh->MAXabs_coord);
    +    qh_option(qh, "Width-outside", NULL, &qh->MINoutside);
    +  }
    +  qh->WIDEfacet= qh->MINoutside;
    +  maximize_(qh->WIDEfacet, qh_WIDEcoplanar * qh->MAXcoplanar);
    +  maximize_(qh->WIDEfacet, qh_WIDEcoplanar * qh->MINvisible);
    +  qh_option(qh, "_wide-facet", NULL, &qh->WIDEfacet);
    +  if (qh->MINvisible > qh->MINoutside + 3 * REALepsilon
    +  && !qh->BESToutside && !qh->FORCEoutput)
    +    qh_fprintf(qh, qh->ferr, 7001, "qhull input warning: minimum visibility V%.2g is greater than \nminimum outside W%.2g.  Flipped facets are likely.\n",
    +             qh->MINvisible, qh->MINoutside);
    +  qh->max_vertex= qh->DISTround;
    +  qh->min_vertex= -qh->DISTround;
    +  /* numeric constants reported in printsummary */
    +} /* detroundoff */
    +
    +/*---------------------------------
    +
    +  qh_detsimplex(qh, apex, points, dim, nearzero )
    +    compute determinant of a simplex with point apex and base points
    +
    +  returns:
    +     signed determinant and nearzero from qh_determinant
    +
    +  notes:
    +     uses qh.gm_matrix/qh.gm_row (assumes they're big enough)
    +
    +  design:
    +    construct qm_matrix by subtracting apex from points
    +    compute determinate
    +*/
    +realT qh_detsimplex(qhT *qh, pointT *apex, setT *points, int dim, boolT *nearzero) {
    +  pointT *coorda, *coordp, *gmcoord, *point, **pointp;
    +  coordT **rows;
    +  int k,  i=0;
    +  realT det;
    +
    +  zinc_(Zdetsimplex);
    +  gmcoord= qh->gm_matrix;
    +  rows= qh->gm_row;
    +  FOREACHpoint_(points) {
    +    if (i == dim)
    +      break;
    +    rows[i++]= gmcoord;
    +    coordp= point;
    +    coorda= apex;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= *coordp++ - *coorda++;
    +  }
    +  if (i < dim) {
    +    qh_fprintf(qh, qh->ferr, 6007, "qhull internal error (qh_detsimplex): #points %d < dimension %d\n",
    +               i, dim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  det= qh_determinant(qh, rows, dim, nearzero);
    +  trace2((qh, qh->ferr, 2002, "qh_detsimplex: det=%2.2g for point p%d, dim %d, nearzero? %d\n",
    +          det, qh_pointid(qh, apex), dim, *nearzero));
    +  return det;
    +} /* detsimplex */
    +
    +/*---------------------------------
    +
    +  qh_distnorm( dim, point, normal, offset )
    +    return distance from point to hyperplane at normal/offset
    +
    +  returns:
    +    dist
    +
    +  notes:
    +    dist > 0 if point is outside of hyperplane
    +
    +  see:
    +    qh_distplane in geom_r.c
    +*/
    +realT qh_distnorm(int dim, pointT *point, pointT *normal, realT *offsetp) {
    +  coordT *normalp= normal, *coordp= point;
    +  realT dist;
    +  int k;
    +
    +  dist= *offsetp;
    +  for (k=dim; k--; )
    +    dist += *(coordp++) * *(normalp++);
    +  return dist;
    +} /* distnorm */
    +
    +/*---------------------------------
    +
    +  qh_distround(qh, dimension, maxabs, maxsumabs )
    +    compute maximum round-off error for a distance computation
    +      to a normalized hyperplane
    +    maxabs is the maximum absolute value of a coordinate
    +    maxsumabs is the maximum possible sum of absolute coordinate values
    +
    +  returns:
    +    max dist round for REALepsilon
    +
    +  notes:
    +    calculate roundoff error according to Golub & van Loan, 1983, Lemma 3.2-1, "Rounding Errors"
    +    use sqrt(dim) since one vector is normalized
    +      or use maxsumabs since one vector is < 1
    +*/
    +realT qh_distround(qhT *qh, int dimension, realT maxabs, realT maxsumabs) {
    +  realT maxdistsum, maxround;
    +
    +  maxdistsum= sqrt((realT)dimension) * maxabs;
    +  minimize_( maxdistsum, maxsumabs);
    +  maxround= REALepsilon * (dimension * maxdistsum * 1.01 + maxabs);
    +              /* adds maxabs for offset */
    +  trace4((qh, qh->ferr, 4008, "qh_distround: %2.2g maxabs %2.2g maxsumabs %2.2g maxdistsum %2.2g\n",
    +                 maxround, maxabs, maxsumabs, maxdistsum));
    +  return maxround;
    +} /* distround */
    +
    +/*---------------------------------
    +
    +  qh_divzero( numer, denom, mindenom1, zerodiv )
    +    divide by a number that's nearly zero
    +    mindenom1= minimum denominator for dividing into 1.0
    +
    +  returns:
    +    quotient
    +    sets zerodiv and returns 0.0 if it would overflow
    +
    +  design:
    +    if numer is nearly zero and abs(numer) < abs(denom)
    +      return numer/denom
    +    else if numer is nearly zero
    +      return 0 and zerodiv
    +    else if denom/numer non-zero
    +      return numer/denom
    +    else
    +      return 0 and zerodiv
    +*/
    +realT qh_divzero(realT numer, realT denom, realT mindenom1, boolT *zerodiv) {
    +  realT temp, numerx, denomx;
    +
    +
    +  if (numer < mindenom1 && numer > -mindenom1) {
    +    numerx= fabs_(numer);
    +    denomx= fabs_(denom);
    +    if (numerx < denomx) {
    +      *zerodiv= False;
    +      return numer/denom;
    +    }else {
    +      *zerodiv= True;
    +      return 0.0;
    +    }
    +  }
    +  temp= denom/numer;
    +  if (temp > mindenom1 || temp < -mindenom1) {
    +    *zerodiv= False;
    +    return numer/denom;
    +  }else {
    +    *zerodiv= True;
    +    return 0.0;
    +  }
    +} /* divzero */
    +
    +
    +/*---------------------------------
    +
    +  qh_facetarea(qh, facet )
    +    return area for a facet
    +
    +  notes:
    +    if non-simplicial,
    +      uses centrum to triangulate facet and sums the projected areas.
    +    if (qh->DELAUNAY),
    +      computes projected area instead for last coordinate
    +    assumes facet->normal exists
    +    projecting tricoplanar facets to the hyperplane does not appear to make a difference
    +
    +  design:
    +    if simplicial
    +      compute area
    +    else
    +      for each ridge
    +        compute area from centrum to ridge
    +    negate area if upper Delaunay facet
    +*/
    +realT qh_facetarea(qhT *qh, facetT *facet) {
    +  vertexT *apex;
    +  pointT *centrum;
    +  realT area= 0.0;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (facet->simplicial) {
    +    apex= SETfirstt_(facet->vertices, vertexT);
    +    area= qh_facetarea_simplex(qh, qh->hull_dim, apex->point, facet->vertices,
    +                    apex, facet->toporient, facet->normal, &facet->offset);
    +  }else {
    +    if (qh->CENTERtype == qh_AScentrum)
    +      centrum= facet->center;
    +    else
    +      centrum= qh_getcentrum(qh, facet);
    +    FOREACHridge_(facet->ridges)
    +      area += qh_facetarea_simplex(qh, qh->hull_dim, centrum, ridge->vertices,
    +                 NULL, (boolT)(ridge->top == facet),  facet->normal, &facet->offset);
    +    if (qh->CENTERtype != qh_AScentrum)
    +      qh_memfree(qh, centrum, qh->normal_size);
    +  }
    +  if (facet->upperdelaunay && qh->DELAUNAY)
    +    area= -area;  /* the normal should be [0,...,1] */
    +  trace4((qh, qh->ferr, 4009, "qh_facetarea: f%d area %2.2g\n", facet->id, area));
    +  return area;
    +} /* facetarea */
    +
    +/*---------------------------------
    +
    +  qh_facetarea_simplex(qh, dim, apex, vertices, notvertex, toporient, normal, offset )
    +    return area for a simplex defined by
    +      an apex, a base of vertices, an orientation, and a unit normal
    +    if simplicial or tricoplanar facet,
    +      notvertex is defined and it is skipped in vertices
    +
    +  returns:
    +    computes area of simplex projected to plane [normal,offset]
    +    returns 0 if vertex too far below plane (qh->WIDEfacet)
    +      vertex can't be apex of tricoplanar facet
    +
    +  notes:
    +    if (qh->DELAUNAY),
    +      computes projected area instead for last coordinate
    +    uses qh->gm_matrix/gm_row and qh->hull_dim
    +    helper function for qh_facetarea
    +
    +  design:
    +    if Notvertex
    +      translate simplex to apex
    +    else
    +      project simplex to normal/offset
    +      translate simplex to apex
    +    if Delaunay
    +      set last row/column to 0 with -1 on diagonal
    +    else
    +      set last row to Normal
    +    compute determinate
    +    scale and flip sign for area
    +*/
    +realT qh_facetarea_simplex(qhT *qh, int dim, coordT *apex, setT *vertices,
    +        vertexT *notvertex,  boolT toporient, coordT *normal, realT *offset) {
    +  pointT *coorda, *coordp, *gmcoord;
    +  coordT **rows, *normalp;
    +  int k,  i=0;
    +  realT area, dist;
    +  vertexT *vertex, **vertexp;
    +  boolT nearzero;
    +
    +  gmcoord= qh->gm_matrix;
    +  rows= qh->gm_row;
    +  FOREACHvertex_(vertices) {
    +    if (vertex == notvertex)
    +      continue;
    +    rows[i++]= gmcoord;
    +    coorda= apex;
    +    coordp= vertex->point;
    +    normalp= normal;
    +    if (notvertex) {
    +      for (k=dim; k--; )
    +        *(gmcoord++)= *coordp++ - *coorda++;
    +    }else {
    +      dist= *offset;
    +      for (k=dim; k--; )
    +        dist += *coordp++ * *normalp++;
    +      if (dist < -qh->WIDEfacet) {
    +        zinc_(Znoarea);
    +        return 0.0;
    +      }
    +      coordp= vertex->point;
    +      normalp= normal;
    +      for (k=dim; k--; )
    +        *(gmcoord++)= (*coordp++ - dist * *normalp++) - *coorda++;
    +    }
    +  }
    +  if (i != dim-1) {
    +    qh_fprintf(qh, qh->ferr, 6008, "qhull internal error (qh_facetarea_simplex): #points %d != dim %d -1\n",
    +               i, dim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  rows[i]= gmcoord;
    +  if (qh->DELAUNAY) {
    +    for (i=0; i < dim-1; i++)
    +      rows[i][dim-1]= 0.0;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= 0.0;
    +    rows[dim-1][dim-1]= -1.0;
    +  }else {
    +    normalp= normal;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= *normalp++;
    +  }
    +  zinc_(Zdetsimplex);
    +  area= qh_determinant(qh, rows, dim, &nearzero);
    +  if (toporient)
    +    area= -area;
    +  area *= qh->AREAfactor;
    +  trace4((qh, qh->ferr, 4010, "qh_facetarea_simplex: area=%2.2g for point p%d, toporient %d, nearzero? %d\n",
    +          area, qh_pointid(qh, apex), toporient, nearzero));
    +  return area;
    +} /* facetarea_simplex */
    +
    +/*---------------------------------
    +
    +  qh_facetcenter(qh, vertices )
    +    return Voronoi center (Voronoi vertex) for a facet's vertices
    +
    +  returns:
    +    return temporary point equal to the center
    +
    +  see:
    +    qh_voronoi_center()
    +*/
    +pointT *qh_facetcenter(qhT *qh, setT *vertices) {
    +  setT *points= qh_settemp(qh, qh_setsize(qh, vertices));
    +  vertexT *vertex, **vertexp;
    +  pointT *center;
    +
    +  FOREACHvertex_(vertices)
    +    qh_setappend(qh, &points, vertex->point);
    +  center= qh_voronoi_center(qh, qh->hull_dim-1, points);
    +  qh_settempfree(qh, &points);
    +  return center;
    +} /* facetcenter */
    +
    +/*---------------------------------
    +
    +  qh_findgooddist(qh, point, facetA, dist, facetlist )
    +    find best good facet visible for point from facetA
    +    assumes facetA is visible from point
    +
    +  returns:
    +    best facet, i.e., good facet that is furthest from point
    +      distance to best facet
    +      NULL if none
    +
    +    moves good, visible facets (and some other visible facets)
    +      to end of qh->facet_list
    +
    +  notes:
    +    uses qh->visit_id
    +
    +  design:
    +    initialize bestfacet if facetA is good
    +    move facetA to end of facetlist
    +    for each facet on facetlist
    +      for each unvisited neighbor of facet
    +        move visible neighbors to end of facetlist
    +        update best good neighbor
    +        if no good neighbors, update best facet
    +*/
    +facetT *qh_findgooddist(qhT *qh, pointT *point, facetT *facetA, realT *distp,
    +               facetT **facetlist) {
    +  realT bestdist= -REALmax, dist;
    +  facetT *neighbor, **neighborp, *bestfacet=NULL, *facet;
    +  boolT goodseen= False;
    +
    +  if (facetA->good) {
    +    zzinc_(Zcheckpart);  /* calls from check_bestdist occur after print stats */
    +    qh_distplane(qh, point, facetA, &bestdist);
    +    bestfacet= facetA;
    +    goodseen= True;
    +  }
    +  qh_removefacet(qh, facetA);
    +  qh_appendfacet(qh, facetA);
    +  *facetlist= facetA;
    +  facetA->visitid= ++qh->visit_id;
    +  FORALLfacet_(*facetlist) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == qh->visit_id)
    +        continue;
    +      neighbor->visitid= qh->visit_id;
    +      if (goodseen && !neighbor->good)
    +        continue;
    +      zzinc_(Zcheckpart);
    +      qh_distplane(qh, point, neighbor, &dist);
    +      if (dist > 0) {
    +        qh_removefacet(qh, neighbor);
    +        qh_appendfacet(qh, neighbor);
    +        if (neighbor->good) {
    +          goodseen= True;
    +          if (dist > bestdist) {
    +            bestdist= dist;
    +            bestfacet= neighbor;
    +          }
    +        }
    +      }
    +    }
    +  }
    +  if (bestfacet) {
    +    *distp= bestdist;
    +    trace2((qh, qh->ferr, 2003, "qh_findgooddist: p%d is %2.2g above good facet f%d\n",
    +      qh_pointid(qh, point), bestdist, bestfacet->id));
    +    return bestfacet;
    +  }
    +  trace4((qh, qh->ferr, 4011, "qh_findgooddist: no good facet for p%d above f%d\n",
    +      qh_pointid(qh, point), facetA->id));
    +  return NULL;
    +}  /* findgooddist */
    +
    +/*---------------------------------
    +
    +  qh_getarea(qh, facetlist )
    +    set area of all facets in facetlist
    +    collect statistics
    +    nop if hasAreaVolume
    +
    +  returns:
    +    sets qh->totarea/totvol to total area and volume of convex hull
    +    for Delaunay triangulation, computes projected area of the lower or upper hull
    +      ignores upper hull if qh->ATinfinity
    +
    +  notes:
    +    could compute outer volume by expanding facet area by rays from interior
    +    the following attempt at perpendicular projection underestimated badly:
    +      qh.totoutvol += (-dist + facet->maxoutside + qh->DISTround)
    +                            * area/ qh->hull_dim;
    +  design:
    +    for each facet on facetlist
    +      compute facet->area
    +      update qh.totarea and qh.totvol
    +*/
    +void qh_getarea(qhT *qh, facetT *facetlist) {
    +  realT area;
    +  realT dist;
    +  facetT *facet;
    +
    +  if (qh->hasAreaVolume)
    +    return;
    +  if (qh->REPORTfreq)
    +    qh_fprintf(qh, qh->ferr, 8020, "computing area of each facet and volume of the convex hull\n");
    +  else
    +    trace1((qh, qh->ferr, 1001, "qh_getarea: computing volume and area for each facet\n"));
    +  qh->totarea= qh->totvol= 0.0;
    +  FORALLfacet_(facetlist) {
    +    if (!facet->normal)
    +      continue;
    +    if (facet->upperdelaunay && qh->ATinfinity)
    +      continue;
    +    if (!facet->isarea) {
    +      facet->f.area= qh_facetarea(qh, facet);
    +      facet->isarea= True;
    +    }
    +    area= facet->f.area;
    +    if (qh->DELAUNAY) {
    +      if (facet->upperdelaunay == qh->UPPERdelaunay)
    +        qh->totarea += area;
    +    }else {
    +      qh->totarea += area;
    +      qh_distplane(qh, qh->interior_point, facet, &dist);
    +      qh->totvol += -dist * area/ qh->hull_dim;
    +    }
    +    if (qh->PRINTstatistics) {
    +      wadd_(Wareatot, area);
    +      wmax_(Wareamax, area);
    +      wmin_(Wareamin, area);
    +    }
    +  }
    +  qh->hasAreaVolume= True;
    +} /* getarea */
    +
    +/*---------------------------------
    +
    +  qh_gram_schmidt(qh, dim, row )
    +    implements Gram-Schmidt orthogonalization by rows
    +
    +  returns:
    +    false if zero norm
    +    overwrites rows[dim][dim]
    +
    +  notes:
    +    see Golub & van Loan, 1983, Algorithm 6.2-2, "Modified Gram-Schmidt"
    +    overflow due to small divisors not handled
    +
    +  design:
    +    for each row
    +      compute norm for row
    +      if non-zero, normalize row
    +      for each remaining rowA
    +        compute inner product of row and rowA
    +        reduce rowA by row * inner product
    +*/
    +boolT qh_gram_schmidt(qhT *qh, int dim, realT **row) {
    +  realT *rowi, *rowj, norm;
    +  int i, j, k;
    +
    +  for (i=0; i < dim; i++) {
    +    rowi= row[i];
    +    for (norm= 0.0, k= dim; k--; rowi++)
    +      norm += *rowi * *rowi;
    +    norm= sqrt(norm);
    +    wmin_(Wmindenom, norm);
    +    if (norm == 0.0)  /* either 0 or overflow due to sqrt */
    +      return False;
    +    for (k=dim; k--; )
    +      *(--rowi) /= norm;
    +    for (j=i+1; j < dim; j++) {
    +      rowj= row[j];
    +      for (norm= 0.0, k=dim; k--; )
    +        norm += *rowi++ * *rowj++;
    +      for (k=dim; k--; )
    +        *(--rowj) -= *(--rowi) * norm;
    +    }
    +  }
    +  return True;
    +} /* gram_schmidt */
    +
    +
    +/*---------------------------------
    +
    +  qh_inthresholds(qh, normal, angle )
    +    return True if normal within qh.lower_/upper_threshold
    +
    +  returns:
    +    estimate of angle by summing of threshold diffs
    +      angle may be NULL
    +      smaller "angle" is better
    +
    +  notes:
    +    invalid if qh.SPLITthresholds
    +
    +  see:
    +    qh.lower_threshold in qh_initbuild()
    +    qh_initthresholds()
    +
    +  design:
    +    for each dimension
    +      test threshold
    +*/
    +boolT qh_inthresholds(qhT *qh, coordT *normal, realT *angle) {
    +  boolT within= True;
    +  int k;
    +  realT threshold;
    +
    +  if (angle)
    +    *angle= 0.0;
    +  for (k=0; k < qh->hull_dim; k++) {
    +    threshold= qh->lower_threshold[k];
    +    if (threshold > -REALmax/2) {
    +      if (normal[k] < threshold)
    +        within= False;
    +      if (angle) {
    +        threshold -= normal[k];
    +        *angle += fabs_(threshold);
    +      }
    +    }
    +    if (qh->upper_threshold[k] < REALmax/2) {
    +      threshold= qh->upper_threshold[k];
    +      if (normal[k] > threshold)
    +        within= False;
    +      if (angle) {
    +        threshold -= normal[k];
    +        *angle += fabs_(threshold);
    +      }
    +    }
    +  }
    +  return within;
    +} /* inthresholds */
    +
    +
    +/*---------------------------------
    +
    +  qh_joggleinput(qh)
    +    randomly joggle input to Qhull by qh.JOGGLEmax
    +    initial input is qh.first_point/qh.num_points of qh.hull_dim
    +      repeated calls use qh.input_points/qh.num_points
    +
    +  returns:
    +    joggles points at qh.first_point/qh.num_points
    +    copies data to qh.input_points/qh.input_malloc if first time
    +    determines qh.JOGGLEmax if it was zero
    +    if qh.DELAUNAY
    +      computes the Delaunay projection of the joggled points
    +
    +  notes:
    +    if qh.DELAUNAY, unnecessarily joggles the last coordinate
    +    the initial 'QJn' may be set larger than qh_JOGGLEmaxincrease
    +
    +  design:
    +    if qh.DELAUNAY
    +      set qh.SCALElast for reduced precision errors
    +    if first call
    +      initialize qh.input_points to the original input points
    +      if qh.JOGGLEmax == 0
    +        determine default qh.JOGGLEmax
    +    else
    +      increase qh.JOGGLEmax according to qh.build_cnt
    +    joggle the input by adding a random number in [-qh.JOGGLEmax,qh.JOGGLEmax]
    +    if qh.DELAUNAY
    +      sets the Delaunay projection
    +*/
    +void qh_joggleinput(qhT *qh) {
    +  int i, seed, size;
    +  coordT *coordp, *inputp;
    +  realT randr, randa, randb;
    +
    +  if (!qh->input_points) { /* first call */
    +    qh->input_points= qh->first_point;
    +    qh->input_malloc= qh->POINTSmalloc;
    +    size= qh->num_points * qh->hull_dim * sizeof(coordT);
    +    if (!(qh->first_point=(coordT*)qh_malloc((size_t)size))) {
    +      qh_fprintf(qh, qh->ferr, 6009, "qhull error: insufficient memory to joggle %d points\n",
    +          qh->num_points);
    +      qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +    }
    +    qh->POINTSmalloc= True;
    +    if (qh->JOGGLEmax == 0.0) {
    +      qh->JOGGLEmax= qh_detjoggle(qh, qh->input_points, qh->num_points, qh->hull_dim);
    +      qh_option(qh, "QJoggle", NULL, &qh->JOGGLEmax);
    +    }
    +  }else {                 /* repeated call */
    +    if (!qh->RERUN && qh->build_cnt > qh_JOGGLEretry) {
    +      if (((qh->build_cnt-qh_JOGGLEretry-1) % qh_JOGGLEagain) == 0) {
    +        realT maxjoggle= qh->MAXwidth * qh_JOGGLEmaxincrease;
    +        if (qh->JOGGLEmax < maxjoggle) {
    +          qh->JOGGLEmax *= qh_JOGGLEincrease;
    +          minimize_(qh->JOGGLEmax, maxjoggle);
    +        }
    +      }
    +    }
    +    qh_option(qh, "QJoggle", NULL, &qh->JOGGLEmax);
    +  }
    +  if (qh->build_cnt > 1 && qh->JOGGLEmax > fmax_(qh->MAXwidth/4, 0.1)) {
    +      qh_fprintf(qh, qh->ferr, 6010, "qhull error: the current joggle for 'QJn', %.2g, is too large for the width\nof the input.  If possible, recompile Qhull with higher-precision reals.\n",
    +                qh->JOGGLEmax);
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  /* for some reason, using qh->ROTATErandom and qh_RANDOMseed does not repeat the run. Use 'TRn' instead */
    +  seed= qh_RANDOMint;
    +  qh_option(qh, "_joggle-seed", &seed, NULL);
    +  trace0((qh, qh->ferr, 6, "qh_joggleinput: joggle input by %2.2g with seed %d\n",
    +    qh->JOGGLEmax, seed));
    +  inputp= qh->input_points;
    +  coordp= qh->first_point;
    +  randa= 2.0 * qh->JOGGLEmax/qh_RANDOMmax;
    +  randb= -qh->JOGGLEmax;
    +  size= qh->num_points * qh->hull_dim;
    +  for (i=size; i--; ) {
    +    randr= qh_RANDOMint;
    +    *(coordp++)= *(inputp++) + (randr * randa + randb);
    +  }
    +  if (qh->DELAUNAY) {
    +    qh->last_low= qh->last_high= qh->last_newhigh= REALmax;
    +    qh_setdelaunay(qh, qh->hull_dim, qh->num_points, qh->first_point);
    +  }
    +} /* joggleinput */
    +
    +/*---------------------------------
    +
    +  qh_maxabsval( normal, dim )
    +    return pointer to maximum absolute value of a dim vector
    +    returns NULL if dim=0
    +*/
    +realT *qh_maxabsval(realT *normal, int dim) {
    +  realT maxval= -REALmax;
    +  realT *maxp= NULL, *colp, absval;
    +  int k;
    +
    +  for (k=dim, colp= normal; k--; colp++) {
    +    absval= fabs_(*colp);
    +    if (absval > maxval) {
    +      maxval= absval;
    +      maxp= colp;
    +    }
    +  }
    +  return maxp;
    +} /* maxabsval */
    +
    +
    +/*---------------------------------
    +
    +  qh_maxmin(qh, points, numpoints, dimension )
    +    return max/min points for each dimension
    +    determine max and min coordinates
    +
    +  returns:
    +    returns a temporary set of max and min points
    +      may include duplicate points. Does not include qh.GOODpoint
    +    sets qh.NEARzero, qh.MAXabs_coord, qh.MAXsumcoord, qh.MAXwidth
    +         qh.MAXlastcoord, qh.MINlastcoord
    +    initializes qh.max_outside, qh.min_vertex, qh.WAScoplanar, qh.ZEROall_ok
    +
    +  notes:
    +    loop duplicated in qh_detjoggle()
    +
    +  design:
    +    initialize global precision variables
    +    checks definition of REAL...
    +    for each dimension
    +      for each point
    +        collect maximum and minimum point
    +      collect maximum of maximums and minimum of minimums
    +      determine qh.NEARzero for Gaussian Elimination
    +*/
    +setT *qh_maxmin(qhT *qh, pointT *points, int numpoints, int dimension) {
    +  int k;
    +  realT maxcoord, temp;
    +  pointT *minimum, *maximum, *point, *pointtemp;
    +  setT *set;
    +
    +  qh->max_outside= 0.0;
    +  qh->MAXabs_coord= 0.0;
    +  qh->MAXwidth= -REALmax;
    +  qh->MAXsumcoord= 0.0;
    +  qh->min_vertex= 0.0;
    +  qh->WAScoplanar= False;
    +  if (qh->ZEROcentrum)
    +    qh->ZEROall_ok= True;
    +  if (REALmin < REALepsilon && REALmin < REALmax && REALmin > -REALmax
    +  && REALmax > 0.0 && -REALmax < 0.0)
    +    ; /* all ok */
    +  else {
    +    qh_fprintf(qh, qh->ferr, 6011, "qhull error: floating point constants in user.h are wrong\n\
    +REALepsilon %g REALmin %g REALmax %g -REALmax %g\n",
    +             REALepsilon, REALmin, REALmax, -REALmax);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  set= qh_settemp(qh, 2*dimension);
    +  for (k=0; k < dimension; k++) {
    +    if (points == qh->GOODpointp)
    +      minimum= maximum= points + dimension;
    +    else
    +      minimum= maximum= points;
    +    FORALLpoint_(qh, points, numpoints) {
    +      if (point == qh->GOODpointp)
    +        continue;
    +      if (maximum[k] < point[k])
    +        maximum= point;
    +      else if (minimum[k] > point[k])
    +        minimum= point;
    +    }
    +    if (k == dimension-1) {
    +      qh->MINlastcoord= minimum[k];
    +      qh->MAXlastcoord= maximum[k];
    +    }
    +    if (qh->SCALElast && k == dimension-1)
    +      maxcoord= qh->MAXwidth;
    +    else {
    +      maxcoord= fmax_(maximum[k], -minimum[k]);
    +      if (qh->GOODpointp) {
    +        temp= fmax_(qh->GOODpointp[k], -qh->GOODpointp[k]);
    +        maximize_(maxcoord, temp);
    +      }
    +      temp= maximum[k] - minimum[k];
    +      maximize_(qh->MAXwidth, temp);
    +    }
    +    maximize_(qh->MAXabs_coord, maxcoord);
    +    qh->MAXsumcoord += maxcoord;
    +    qh_setappend(qh, &set, maximum);
    +    qh_setappend(qh, &set, minimum);
    +    /* calculation of qh NEARzero is based on Golub & van Loan, 1983,
    +       Eq. 4.4-13 for "Gaussian elimination with complete pivoting".
    +       Golub & van Loan say that n^3 can be ignored and 10 be used in
    +       place of rho */
    +    qh->NEARzero[k]= 80 * qh->MAXsumcoord * REALepsilon;
    +  }
    +  if (qh->IStracing >=1)
    +    qh_printpoints(qh, qh->ferr, "qh_maxmin: found the max and min points(by dim):", set);
    +  return(set);
    +} /* maxmin */
    +
    +/*---------------------------------
    +
    +  qh_maxouter(qh)
    +    return maximum distance from facet to outer plane
    +    normally this is qh.max_outside+qh.DISTround
    +    does not include qh.JOGGLEmax
    +
    +  see:
    +    qh_outerinner()
    +
    +  notes:
    +    need to add another qh.DISTround if testing actual point with computation
    +
    +  for joggle:
    +    qh_setfacetplane() updated qh.max_outer for Wnewvertexmax (max distance to vertex)
    +    need to use Wnewvertexmax since could have a coplanar point for a high
    +      facet that is replaced by a low facet
    +    need to add qh.JOGGLEmax if testing input points
    +*/
    +realT qh_maxouter(qhT *qh) {
    +  realT dist;
    +
    +  dist= fmax_(qh->max_outside, qh->DISTround);
    +  dist += qh->DISTround;
    +  trace4((qh, qh->ferr, 4012, "qh_maxouter: max distance from facet to outer plane is %2.2g max_outside is %2.2g\n", dist, qh->max_outside));
    +  return dist;
    +} /* maxouter */
    +
    +/*---------------------------------
    +
    +  qh_maxsimplex(qh, dim, maxpoints, points, numpoints, simplex )
    +    determines maximum simplex for a set of points
    +    starts from points already in simplex
    +    skips qh.GOODpointp (assumes that it isn't in maxpoints)
    +
    +  returns:
    +    simplex with dim+1 points
    +
    +  notes:
    +    assumes at least pointsneeded points in points
    +    maximizes determinate for x,y,z,w, etc.
    +    uses maxpoints as long as determinate is clearly non-zero
    +
    +  design:
    +    initialize simplex with at least two points
    +      (find points with max or min x coordinate)
    +    for each remaining dimension
    +      add point that maximizes the determinate
    +        (use points from maxpoints first)
    +*/
    +void qh_maxsimplex(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints, setT **simplex) {
    +  pointT *point, **pointp, *pointtemp, *maxpoint, *minx=NULL, *maxx=NULL;
    +  boolT nearzero, maxnearzero= False;
    +  int k, sizinit;
    +  realT maxdet= -REALmax, det, mincoord= REALmax, maxcoord= -REALmax;
    +
    +  sizinit= qh_setsize(qh, *simplex);
    +  if (sizinit < 2) {
    +    if (qh_setsize(qh, maxpoints) >= 2) {
    +      FOREACHpoint_(maxpoints) {
    +        if (maxcoord < point[0]) {
    +          maxcoord= point[0];
    +          maxx= point;
    +        }
    +        if (mincoord > point[0]) {
    +          mincoord= point[0];
    +          minx= point;
    +        }
    +      }
    +    }else {
    +      FORALLpoint_(qh, points, numpoints) {
    +        if (point == qh->GOODpointp)
    +          continue;
    +        if (maxcoord < point[0]) {
    +          maxcoord= point[0];
    +          maxx= point;
    +        }
    +        if (mincoord > point[0]) {
    +          mincoord= point[0];
    +          minx= point;
    +        }
    +      }
    +    }
    +    qh_setunique(qh, simplex, minx);
    +    if (qh_setsize(qh, *simplex) < 2)
    +      qh_setunique(qh, simplex, maxx);
    +    sizinit= qh_setsize(qh, *simplex);
    +    if (sizinit < 2) {
    +      qh_precision(qh, "input has same x coordinate");
    +      if (zzval_(Zsetplane) > qh->hull_dim+1) {
    +        qh_fprintf(qh, qh->ferr, 6012, "qhull precision error (qh_maxsimplex for voronoi_center):\n%d points with the same x coordinate.\n",
    +                 qh_setsize(qh, maxpoints)+numpoints);
    +        qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +      }else {
    +        qh_fprintf(qh, qh->ferr, 6013, "qhull input error: input is less than %d-dimensional since it has the same x coordinate\n", qh->hull_dim);
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +    }
    +  }
    +  for (k=sizinit; k < dim+1; k++) {
    +    maxpoint= NULL;
    +    maxdet= -REALmax;
    +    FOREACHpoint_(maxpoints) {
    +      if (!qh_setin(*simplex, point)) {
    +        det= qh_detsimplex(qh, point, *simplex, k, &nearzero);
    +        if ((det= fabs_(det)) > maxdet) {
    +          maxdet= det;
    +          maxpoint= point;
    +          maxnearzero= nearzero;
    +        }
    +      }
    +    }
    +    if (!maxpoint || maxnearzero) {
    +      zinc_(Zsearchpoints);
    +      if (!maxpoint) {
    +        trace0((qh, qh->ferr, 7, "qh_maxsimplex: searching all points for %d-th initial vertex.\n", k+1));
    +      }else {
    +        trace0((qh, qh->ferr, 8, "qh_maxsimplex: searching all points for %d-th initial vertex, better than p%d det %2.2g\n",
    +                k+1, qh_pointid(qh, maxpoint), maxdet));
    +      }
    +      FORALLpoint_(qh, points, numpoints) {
    +        if (point == qh->GOODpointp)
    +          continue;
    +        if (!qh_setin(*simplex, point)) {
    +          det= qh_detsimplex(qh, point, *simplex, k, &nearzero);
    +          if ((det= fabs_(det)) > maxdet) {
    +            maxdet= det;
    +            maxpoint= point;
    +            maxnearzero= nearzero;
    +          }
    +        }
    +      }
    +    } /* !maxpoint */
    +    if (!maxpoint) {
    +      qh_fprintf(qh, qh->ferr, 6014, "qhull internal error (qh_maxsimplex): not enough points available\n");
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +    qh_setappend(qh, simplex, maxpoint);
    +    trace1((qh, qh->ferr, 1002, "qh_maxsimplex: selected point p%d for %d`th initial vertex, det=%2.2g\n",
    +            qh_pointid(qh, maxpoint), k+1, maxdet));
    +  } /* k */
    +} /* maxsimplex */
    +
    +/*---------------------------------
    +
    +  qh_minabsval( normal, dim )
    +    return minimum absolute value of a dim vector
    +*/
    +realT qh_minabsval(realT *normal, int dim) {
    +  realT minval= 0;
    +  realT maxval= 0;
    +  realT *colp;
    +  int k;
    +
    +  for (k=dim, colp=normal; k--; colp++) {
    +    maximize_(maxval, *colp);
    +    minimize_(minval, *colp);
    +  }
    +  return fmax_(maxval, -minval);
    +} /* minabsval */
    +
    +
    +/*---------------------------------
    +
    +  qh_mindif(qh, vecA, vecB, dim )
    +    return index of min abs. difference of two vectors
    +*/
    +int qh_mindiff(realT *vecA, realT *vecB, int dim) {
    +  realT mindiff= REALmax, diff;
    +  realT *vecAp= vecA, *vecBp= vecB;
    +  int k, mink= 0;
    +
    +  for (k=0; k < dim; k++) {
    +    diff= *vecAp++ - *vecBp++;
    +    diff= fabs_(diff);
    +    if (diff < mindiff) {
    +      mindiff= diff;
    +      mink= k;
    +    }
    +  }
    +  return mink;
    +} /* mindiff */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_orientoutside(qh, facet  )
    +    make facet outside oriented via qh.interior_point
    +
    +  returns:
    +    True if facet reversed orientation.
    +*/
    +boolT qh_orientoutside(qhT *qh, facetT *facet) {
    +  int k;
    +  realT dist;
    +
    +  qh_distplane(qh, qh->interior_point, facet, &dist);
    +  if (dist > 0) {
    +    for (k=qh->hull_dim; k--; )
    +      facet->normal[k]= -facet->normal[k];
    +    facet->offset= -facet->offset;
    +    return True;
    +  }
    +  return False;
    +} /* orientoutside */
    +
    +/*---------------------------------
    +
    +  qh_outerinner(qh, facet, outerplane, innerplane  )
    +    if facet and qh.maxoutdone (i.e., qh_check_maxout)
    +      returns outer and inner plane for facet
    +    else
    +      returns maximum outer and inner plane
    +    accounts for qh.JOGGLEmax
    +
    +  see:
    +    qh_maxouter(qh), qh_check_bestdist(), qh_check_points()
    +
    +  notes:
    +    outerplaner or innerplane may be NULL
    +    facet is const
    +    Does not error (QhullFacet)
    +
    +    includes qh.DISTround for actual points
    +    adds another qh.DISTround if testing with floating point arithmetic
    +*/
    +void qh_outerinner(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane) {
    +  realT dist, mindist;
    +  vertexT *vertex, **vertexp;
    +
    +  if (outerplane) {
    +    if (!qh_MAXoutside || !facet || !qh->maxoutdone) {
    +      *outerplane= qh_maxouter(qh);       /* includes qh.DISTround */
    +    }else { /* qh_MAXoutside ... */
    +#if qh_MAXoutside
    +      *outerplane= facet->maxoutside + qh->DISTround;
    +#endif
    +
    +    }
    +    if (qh->JOGGLEmax < REALmax/2)
    +      *outerplane += qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
    +  }
    +  if (innerplane) {
    +    if (facet) {
    +      mindist= REALmax;
    +      FOREACHvertex_(facet->vertices) {
    +        zinc_(Zdistio);
    +        qh_distplane(qh, vertex->point, facet, &dist);
    +        minimize_(mindist, dist);
    +      }
    +      *innerplane= mindist - qh->DISTround;
    +    }else
    +      *innerplane= qh->min_vertex - qh->DISTround;
    +    if (qh->JOGGLEmax < REALmax/2)
    +      *innerplane -= qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
    +  }
    +} /* outerinner */
    +
    +/*---------------------------------
    +
    +  qh_pointdist( point1, point2, dim )
    +    return distance between two points
    +
    +  notes:
    +    returns distance squared if 'dim' is negative
    +*/
    +coordT qh_pointdist(pointT *point1, pointT *point2, int dim) {
    +  coordT dist, diff;
    +  int k;
    +
    +  dist= 0.0;
    +  for (k= (dim > 0 ? dim : -dim); k--; ) {
    +    diff= *point1++ - *point2++;
    +    dist += diff * diff;
    +  }
    +  if (dim > 0)
    +    return(sqrt(dist));
    +  return dist;
    +} /* pointdist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printmatrix(qh, fp, string, rows, numrow, numcol )
    +    print matrix to fp given by row vectors
    +    print string as header
    +    qh may be NULL if fp is defined
    +
    +  notes:
    +    print a vector by qh_printmatrix(qh, fp, "", &vect, 1, len)
    +*/
    +void qh_printmatrix(qhT *qh, FILE *fp, const char *string, realT **rows, int numrow, int numcol) {
    +  realT *rowp;
    +  realT r; /*bug fix*/
    +  int i,k;
    +
    +  qh_fprintf(qh, fp, 9001, "%s\n", string);
    +  for (i=0; i < numrow; i++) {
    +    rowp= rows[i];
    +    for (k=0; k < numcol; k++) {
    +      r= *rowp++;
    +      qh_fprintf(qh, fp, 9002, "%6.3g ", r);
    +    }
    +    qh_fprintf(qh, fp, 9003, "\n");
    +  }
    +} /* printmatrix */
    +
    +
    +/*---------------------------------
    +
    +  qh_printpoints(qh, fp, string, points )
    +    print pointids to fp for a set of points
    +    if string, prints string and 'p' point ids
    +*/
    +void qh_printpoints(qhT *qh, FILE *fp, const char *string, setT *points) {
    +  pointT *point, **pointp;
    +
    +  if (string) {
    +    qh_fprintf(qh, fp, 9004, "%s", string);
    +    FOREACHpoint_(points)
    +      qh_fprintf(qh, fp, 9005, " p%d", qh_pointid(qh, point));
    +    qh_fprintf(qh, fp, 9006, "\n");
    +  }else {
    +    FOREACHpoint_(points)
    +      qh_fprintf(qh, fp, 9007, " %d", qh_pointid(qh, point));
    +    qh_fprintf(qh, fp, 9008, "\n");
    +  }
    +} /* printpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectinput(qh)
    +    project input points using qh.lower_bound/upper_bound and qh->DELAUNAY
    +    if qh.lower_bound[k]=qh.upper_bound[k]= 0,
    +      removes dimension k
    +    if halfspace intersection
    +      removes dimension k from qh.feasible_point
    +    input points in qh->first_point, num_points, input_dim
    +
    +  returns:
    +    new point array in qh->first_point of qh->hull_dim coordinates
    +    sets qh->POINTSmalloc
    +    if qh->DELAUNAY
    +      projects points to paraboloid
    +      lowbound/highbound is also projected
    +    if qh->ATinfinity
    +      adds point "at-infinity"
    +    if qh->POINTSmalloc
    +      frees old point array
    +
    +  notes:
    +    checks that qh.hull_dim agrees with qh.input_dim, PROJECTinput, and DELAUNAY
    +
    +
    +  design:
    +    sets project[k] to -1 (delete), 0 (keep), 1 (add for Delaunay)
    +    determines newdim and newnum for qh->hull_dim and qh->num_points
    +    projects points to newpoints
    +    projects qh.lower_bound to itself
    +    projects qh.upper_bound to itself
    +    if qh->DELAUNAY
    +      if qh->ATINFINITY
    +        projects points to paraboloid
    +        computes "infinity" point as vertex average and 10% above all points
    +      else
    +        uses qh_setdelaunay to project points to paraboloid
    +*/
    +void qh_projectinput(qhT *qh) {
    +  int k,i;
    +  int newdim= qh->input_dim, newnum= qh->num_points;
    +  signed char *project;
    +  int projectsize= (qh->input_dim+1)*sizeof(*project);
    +  pointT *newpoints, *coord, *infinity;
    +  realT paraboloid, maxboloid= 0;
    +
    +  project= (signed char*)qh_memalloc(qh, projectsize);
    +  memset((char*)project, 0, (size_t)projectsize);
    +  for (k=0; k < qh->input_dim; k++) {   /* skip Delaunay bound */
    +    if (qh->lower_bound[k] == 0 && qh->upper_bound[k] == 0) {
    +      project[k]= -1;
    +      newdim--;
    +    }
    +  }
    +  if (qh->DELAUNAY) {
    +    project[k]= 1;
    +    newdim++;
    +    if (qh->ATinfinity)
    +      newnum++;
    +  }
    +  if (newdim != qh->hull_dim) {
    +    qh_memfree(qh, project, projectsize);
    +    qh_fprintf(qh, qh->ferr, 6015, "qhull internal error (qh_projectinput): dimension after projection %d != hull_dim %d\n", newdim, qh->hull_dim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  if (!(newpoints= qh->temp_malloc= (coordT*)qh_malloc(newnum*newdim*sizeof(coordT)))){
    +    qh_memfree(qh, project, projectsize);
    +    qh_fprintf(qh, qh->ferr, 6016, "qhull error: insufficient memory to project %d points\n",
    +           qh->num_points);
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  /* qh_projectpoints throws error if mismatched dimensions */
    +  qh_projectpoints(qh, project, qh->input_dim+1, qh->first_point,
    +                    qh->num_points, qh->input_dim, newpoints, newdim);
    +  trace1((qh, qh->ferr, 1003, "qh_projectinput: updating lower and upper_bound\n"));
    +  qh_projectpoints(qh, project, qh->input_dim+1, qh->lower_bound,
    +                    1, qh->input_dim+1, qh->lower_bound, newdim+1);
    +  qh_projectpoints(qh, project, qh->input_dim+1, qh->upper_bound,
    +                    1, qh->input_dim+1, qh->upper_bound, newdim+1);
    +  if (qh->HALFspace) {
    +    if (!qh->feasible_point) {
    +      qh_memfree(qh, project, projectsize);
    +      qh_fprintf(qh, qh->ferr, 6017, "qhull internal error (qh_projectinput): HALFspace defined without qh.feasible_point\n");
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +    qh_projectpoints(qh, project, qh->input_dim, qh->feasible_point,
    +                      1, qh->input_dim, qh->feasible_point, newdim);
    +  }
    +  qh_memfree(qh, project, projectsize);
    +  if (qh->POINTSmalloc)
    +    qh_free(qh->first_point);
    +  qh->first_point= newpoints;
    +  qh->POINTSmalloc= True;
    +  qh->temp_malloc= NULL;
    +  if (qh->DELAUNAY && qh->ATinfinity) {
    +    coord= qh->first_point;
    +    infinity= qh->first_point + qh->hull_dim * qh->num_points;
    +    for (k=qh->hull_dim-1; k--; )
    +      infinity[k]= 0.0;
    +    for (i=qh->num_points; i--; ) {
    +      paraboloid= 0.0;
    +      for (k=0; k < qh->hull_dim-1; k++) {
    +        paraboloid += *coord * *coord;
    +        infinity[k] += *coord;
    +        coord++;
    +      }
    +      *(coord++)= paraboloid;
    +      maximize_(maxboloid, paraboloid);
    +    }
    +    /* coord == infinity */
    +    for (k=qh->hull_dim-1; k--; )
    +      *(coord++) /= qh->num_points;
    +    *(coord++)= maxboloid * 1.1;
    +    qh->num_points++;
    +    trace0((qh, qh->ferr, 9, "qh_projectinput: projected points to paraboloid for Delaunay\n"));
    +  }else if (qh->DELAUNAY)  /* !qh->ATinfinity */
    +    qh_setdelaunay(qh, qh->hull_dim, qh->num_points, qh->first_point);
    +} /* projectinput */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectpoints(qh, project, n, points, numpoints, dim, newpoints, newdim )
    +    project points/numpoints/dim to newpoints/newdim
    +    if project[k] == -1
    +      delete dimension k
    +    if project[k] == 1
    +      add dimension k by duplicating previous column
    +    n is size of project
    +
    +  notes:
    +    newpoints may be points if only adding dimension at end
    +
    +  design:
    +    check that 'project' and 'newdim' agree
    +    for each dimension
    +      if project == -1
    +        skip dimension
    +      else
    +        determine start of column in newpoints
    +        determine start of column in points
    +          if project == +1, duplicate previous column
    +        copy dimension (column) from points to newpoints
    +*/
    +void qh_projectpoints(qhT *qh, signed char *project, int n, realT *points,
    +        int numpoints, int dim, realT *newpoints, int newdim) {
    +  int testdim= dim, oldk=0, newk=0, i,j=0,k;
    +  realT *newp, *oldp;
    +
    +  for (k=0; k < n; k++)
    +    testdim += project[k];
    +  if (testdim != newdim) {
    +    qh_fprintf(qh, qh->ferr, 6018, "qhull internal error (qh_projectpoints): newdim %d should be %d after projection\n",
    +      newdim, testdim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  for (j=0; j= dim)
    +          continue;
    +        oldp= points+oldk;
    +      }else
    +        oldp= points+oldk++;
    +      for (i=numpoints; i--; ) {
    +        *newp= *oldp;
    +        newp += newdim;
    +        oldp += dim;
    +      }
    +    }
    +    if (oldk >= dim)
    +      break;
    +  }
    +  trace1((qh, qh->ferr, 1004, "qh_projectpoints: projected %d points from dim %d to dim %d\n",
    +    numpoints, dim, newdim));
    +} /* projectpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_rotateinput(qh, rows )
    +    rotate input using row matrix
    +    input points given by qh->first_point, num_points, hull_dim
    +    assumes rows[dim] is a scratch buffer
    +    if qh->POINTSmalloc, overwrites input points, else mallocs a new array
    +
    +  returns:
    +    rotated input
    +    sets qh->POINTSmalloc
    +
    +  design:
    +    see qh_rotatepoints
    +*/
    +void qh_rotateinput(qhT *qh, realT **rows) {
    +
    +  if (!qh->POINTSmalloc) {
    +    qh->first_point= qh_copypoints(qh, qh->first_point, qh->num_points, qh->hull_dim);
    +    qh->POINTSmalloc= True;
    +  }
    +  qh_rotatepoints(qh, qh->first_point, qh->num_points, qh->hull_dim, rows);
    +}  /* rotateinput */
    +
    +/*---------------------------------
    +
    +  qh_rotatepoints(qh, points, numpoints, dim, row )
    +    rotate numpoints points by a d-dim row matrix
    +    assumes rows[dim] is a scratch buffer
    +
    +  returns:
    +    rotated points in place
    +
    +  design:
    +    for each point
    +      for each coordinate
    +        use row[dim] to compute partial inner product
    +      for each coordinate
    +        rotate by partial inner product
    +*/
    +void qh_rotatepoints(qhT *qh, realT *points, int numpoints, int dim, realT **row) {
    +  realT *point, *rowi, *coord= NULL, sum, *newval;
    +  int i,j,k;
    +
    +  if (qh->IStracing >= 1)
    +    qh_printmatrix(qh, qh->ferr, "qh_rotatepoints: rotate points by", row, dim, dim);
    +  for (point= points, j= numpoints; j--; point += dim) {
    +    newval= row[dim];
    +    for (i=0; i < dim; i++) {
    +      rowi= row[i];
    +      coord= point;
    +      for (sum= 0.0, k= dim; k--; )
    +        sum += *rowi++ * *coord++;
    +      *(newval++)= sum;
    +    }
    +    for (k=dim; k--; )
    +      *(--coord)= *(--newval);
    +  }
    +} /* rotatepoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_scaleinput(qh)
    +    scale input points using qh->low_bound/high_bound
    +    input points given by qh->first_point, num_points, hull_dim
    +    if qh->POINTSmalloc, overwrites input points, else mallocs a new array
    +
    +  returns:
    +    scales coordinates of points to low_bound[k], high_bound[k]
    +    sets qh->POINTSmalloc
    +
    +  design:
    +    see qh_scalepoints
    +*/
    +void qh_scaleinput(qhT *qh) {
    +
    +  if (!qh->POINTSmalloc) {
    +    qh->first_point= qh_copypoints(qh, qh->first_point, qh->num_points, qh->hull_dim);
    +    qh->POINTSmalloc= True;
    +  }
    +  qh_scalepoints(qh, qh->first_point, qh->num_points, qh->hull_dim,
    +       qh->lower_bound, qh->upper_bound);
    +}  /* scaleinput */
    +
    +/*---------------------------------
    +
    +  qh_scalelast(qh, points, numpoints, dim, low, high, newhigh )
    +    scale last coordinate to [0,m] for Delaunay triangulations
    +    input points given by points, numpoints, dim
    +
    +  returns:
    +    changes scale of last coordinate from [low, high] to [0, newhigh]
    +    overwrites last coordinate of each point
    +    saves low/high/newhigh in qh.last_low, etc. for qh_setdelaunay()
    +
    +  notes:
    +    when called by qh_setdelaunay, low/high may not match actual data
    +
    +  design:
    +    compute scale and shift factors
    +    apply to last coordinate of each point
    +*/
    +void qh_scalelast(qhT *qh, coordT *points, int numpoints, int dim, coordT low,
    +                   coordT high, coordT newhigh) {
    +  realT scale, shift;
    +  coordT *coord;
    +  int i;
    +  boolT nearzero= False;
    +
    +  trace4((qh, qh->ferr, 4013, "qh_scalelast: scale last coordinate from [%2.2g, %2.2g] to [0,%2.2g]\n",
    +    low, high, newhigh));
    +  qh->last_low= low;
    +  qh->last_high= high;
    +  qh->last_newhigh= newhigh;
    +  scale= qh_divzero(newhigh, high - low,
    +                  qh->MINdenom_1, &nearzero);
    +  if (nearzero) {
    +    if (qh->DELAUNAY)
    +      qh_fprintf(qh, qh->ferr, 6019, "qhull input error: can not scale last coordinate.  Input is cocircular\n   or cospherical.   Use option 'Qz' to add a point at infinity.\n");
    +    else
    +      qh_fprintf(qh, qh->ferr, 6020, "qhull input error: can not scale last coordinate.  New bounds [0, %2.2g] are too wide for\nexisting bounds [%2.2g, %2.2g] (width %2.2g)\n",
    +                newhigh, low, high, high-low);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  shift= - low * newhigh / (high-low);
    +  coord= points + dim - 1;
    +  for (i=numpoints; i--; coord += dim)
    +    *coord= *coord * scale + shift;
    +} /* scalelast */
    +
    +/*---------------------------------
    +
    +  qh_scalepoints(qh, points, numpoints, dim, newlows, newhighs )
    +    scale points to new lowbound and highbound
    +    retains old bound when newlow= -REALmax or newhigh= +REALmax
    +
    +  returns:
    +    scaled points
    +    overwrites old points
    +
    +  design:
    +    for each coordinate
    +      compute current low and high bound
    +      compute scale and shift factors
    +      scale all points
    +      enforce new low and high bound for all points
    +*/
    +void qh_scalepoints(qhT *qh, pointT *points, int numpoints, int dim,
    +        realT *newlows, realT *newhighs) {
    +  int i,k;
    +  realT shift, scale, *coord, low, high, newlow, newhigh, mincoord, maxcoord;
    +  boolT nearzero= False;
    +
    +  for (k=0; k < dim; k++) {
    +    newhigh= newhighs[k];
    +    newlow= newlows[k];
    +    if (newhigh > REALmax/2 && newlow < -REALmax/2)
    +      continue;
    +    low= REALmax;
    +    high= -REALmax;
    +    for (i=numpoints, coord=points+k; i--; coord += dim) {
    +      minimize_(low, *coord);
    +      maximize_(high, *coord);
    +    }
    +    if (newhigh > REALmax/2)
    +      newhigh= high;
    +    if (newlow < -REALmax/2)
    +      newlow= low;
    +    if (qh->DELAUNAY && k == dim-1 && newhigh < newlow) {
    +      qh_fprintf(qh, qh->ferr, 6021, "qhull input error: 'Qb%d' or 'QB%d' inverts paraboloid since high bound %.2g < low bound %.2g\n",
    +               k, k, newhigh, newlow);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    scale= qh_divzero(newhigh - newlow, high - low,
    +                  qh->MINdenom_1, &nearzero);
    +    if (nearzero) {
    +      qh_fprintf(qh, qh->ferr, 6022, "qhull input error: %d'th dimension's new bounds [%2.2g, %2.2g] too wide for\nexisting bounds [%2.2g, %2.2g]\n",
    +              k, newlow, newhigh, low, high);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    shift= (newlow * high - low * newhigh)/(high-low);
    +    coord= points+k;
    +    for (i=numpoints; i--; coord += dim)
    +      *coord= *coord * scale + shift;
    +    coord= points+k;
    +    if (newlow < newhigh) {
    +      mincoord= newlow;
    +      maxcoord= newhigh;
    +    }else {
    +      mincoord= newhigh;
    +      maxcoord= newlow;
    +    }
    +    for (i=numpoints; i--; coord += dim) {
    +      minimize_(*coord, maxcoord);  /* because of roundoff error */
    +      maximize_(*coord, mincoord);
    +    }
    +    trace0((qh, qh->ferr, 10, "qh_scalepoints: scaled %d'th coordinate [%2.2g, %2.2g] to [%.2g, %.2g] for %d points by %2.2g and shifted %2.2g\n",
    +      k, low, high, newlow, newhigh, numpoints, scale, shift));
    +  }
    +} /* scalepoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelaunay(qh, dim, count, points )
    +    project count points to dim-d paraboloid for Delaunay triangulation
    +
    +    dim is one more than the dimension of the input set
    +    assumes dim is at least 3 (i.e., at least a 2-d Delaunay triangulation)
    +
    +    points is a dim*count realT array.  The first dim-1 coordinates
    +    are the coordinates of the first input point.  array[dim] is
    +    the first coordinate of the second input point.  array[2*dim] is
    +    the first coordinate of the third input point.
    +
    +    if qh.last_low defined (i.e., 'Qbb' called qh_scalelast)
    +      calls qh_scalelast to scale the last coordinate the same as the other points
    +
    +  returns:
    +    for each point
    +      sets point[dim-1] to sum of squares of coordinates
    +    scale points to 'Qbb' if needed
    +
    +  notes:
    +    to project one point, use
    +      qh_setdelaunay(qh, qh->hull_dim, 1, point)
    +
    +    Do not use options 'Qbk', 'QBk', or 'QbB' since they scale
    +    the coordinates after the original projection.
    +
    +*/
    +void qh_setdelaunay(qhT *qh, int dim, int count, pointT *points) {
    +  int i, k;
    +  coordT *coordp, coord;
    +  realT paraboloid;
    +
    +  trace0((qh, qh->ferr, 11, "qh_setdelaunay: project %d points to paraboloid for Delaunay triangulation\n", count));
    +  coordp= points;
    +  for (i=0; i < count; i++) {
    +    coord= *coordp++;
    +    paraboloid= coord*coord;
    +    for (k=dim-2; k--; ) {
    +      coord= *coordp++;
    +      paraboloid += coord*coord;
    +    }
    +    *coordp++ = paraboloid;
    +  }
    +  if (qh->last_low < REALmax/2)
    +    qh_scalelast(qh, points, count, dim, qh->last_low, qh->last_high, qh->last_newhigh);
    +} /* setdelaunay */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethalfspace(qh, dim, coords, nextp, normal, offset, feasible )
    +    set point to dual of halfspace relative to feasible point
    +    halfspace is normal coefficients and offset.
    +
    +  returns:
    +    false and prints error if feasible point is outside of hull
    +    overwrites coordinates for point at dim coords
    +    nextp= next point (coords)
    +    does not call qh_errexit
    +
    +  design:
    +    compute distance from feasible point to halfspace
    +    divide each normal coefficient by -dist
    +*/
    +boolT qh_sethalfspace(qhT *qh, int dim, coordT *coords, coordT **nextp,
    +         coordT *normal, coordT *offset, coordT *feasible) {
    +  coordT *normp= normal, *feasiblep= feasible, *coordp= coords;
    +  realT dist;
    +  realT r; /*bug fix*/
    +  int k;
    +  boolT zerodiv;
    +
    +  dist= *offset;
    +  for (k=dim; k--; )
    +    dist += *(normp++) * *(feasiblep++);
    +  if (dist > 0)
    +    goto LABELerroroutside;
    +  normp= normal;
    +  if (dist < -qh->MINdenom) {
    +    for (k=dim; k--; )
    +      *(coordp++)= *(normp++) / -dist;
    +  }else {
    +    for (k=dim; k--; ) {
    +      *(coordp++)= qh_divzero(*(normp++), -dist, qh->MINdenom_1, &zerodiv);
    +      if (zerodiv)
    +        goto LABELerroroutside;
    +    }
    +  }
    +  *nextp= coordp;
    +  if (qh->IStracing >= 4) {
    +    qh_fprintf(qh, qh->ferr, 8021, "qh_sethalfspace: halfspace at offset %6.2g to point: ", *offset);
    +    for (k=dim, coordp=coords; k--; ) {
    +      r= *coordp++;
    +      qh_fprintf(qh, qh->ferr, 8022, " %6.2g", r);
    +    }
    +    qh_fprintf(qh, qh->ferr, 8023, "\n");
    +  }
    +  return True;
    +LABELerroroutside:
    +  feasiblep= feasible;
    +  normp= normal;
    +  qh_fprintf(qh, qh->ferr, 6023, "qhull input error: feasible point is not clearly inside halfspace\nfeasible point: ");
    +  for (k=dim; k--; )
    +    qh_fprintf(qh, qh->ferr, 8024, qh_REAL_1, r=*(feasiblep++));
    +  qh_fprintf(qh, qh->ferr, 8025, "\n     halfspace: ");
    +  for (k=dim; k--; )
    +    qh_fprintf(qh, qh->ferr, 8026, qh_REAL_1, r=*(normp++));
    +  qh_fprintf(qh, qh->ferr, 8027, "\n     at offset: ");
    +  qh_fprintf(qh, qh->ferr, 8028, qh_REAL_1, *offset);
    +  qh_fprintf(qh, qh->ferr, 8029, " and distance: ");
    +  qh_fprintf(qh, qh->ferr, 8030, qh_REAL_1, dist);
    +  qh_fprintf(qh, qh->ferr, 8031, "\n");
    +  return False;
    +} /* sethalfspace */
    +
    +/*---------------------------------
    +
    +  qh_sethalfspace_all(qh, dim, count, halfspaces, feasible )
    +    generate dual for halfspace intersection with feasible point
    +    array of count halfspaces
    +      each halfspace is normal coefficients followed by offset
    +      the origin is inside the halfspace if the offset is negative
    +    feasible is a point inside all halfspaces (http://www.qhull.org/html/qhalf.htm#notes)
    +
    +  returns:
    +    malloc'd array of count X dim-1 points
    +
    +  notes:
    +    call before qh_init_B or qh_initqhull_globals
    +    free memory when done
    +    unused/untested code: please email bradb@shore.net if this works ok for you
    +    if using option 'Fp', qh->feasible_point must be set (e.g., to 'feasible')
    +    qh->feasible_point is a malloc'd array that is freed by qh_freebuffers.
    +
    +  design:
    +    see qh_sethalfspace
    +*/
    +coordT *qh_sethalfspace_all(qhT *qh, int dim, int count, coordT *halfspaces, pointT *feasible) {
    +  int i, newdim;
    +  pointT *newpoints;
    +  coordT *coordp, *normalp, *offsetp;
    +
    +  trace0((qh, qh->ferr, 12, "qh_sethalfspace_all: compute dual for halfspace intersection\n"));
    +  newdim= dim - 1;
    +  if (!(newpoints=(coordT*)qh_malloc(count*newdim*sizeof(coordT)))){
    +    qh_fprintf(qh, qh->ferr, 6024, "qhull error: insufficient memory to compute dual of %d halfspaces\n",
    +          count);
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  coordp= newpoints;
    +  normalp= halfspaces;
    +  for (i=0; i < count; i++) {
    +    offsetp= normalp + newdim;
    +    if (!qh_sethalfspace(qh, newdim, coordp, &coordp, normalp, offsetp, feasible)) {
    +      qh_free(newpoints);  /* feasible is not inside halfspace as reported by qh_sethalfspace */
    +      qh_fprintf(qh, qh->ferr, 8032, "The halfspace was at index %d\n", i);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    normalp= offsetp + 1;
    +  }
    +  return newpoints;
    +} /* sethalfspace_all */
    +
    +
    +/*---------------------------------
    +
    +  qh_sharpnewfacets(qh)
    +
    +  returns:
    +    true if could be an acute angle (facets in different quadrants)
    +
    +  notes:
    +    for qh_findbest
    +
    +  design:
    +    for all facets on qh.newfacet_list
    +      if two facets are in different quadrants
    +        set issharp
    +*/
    +boolT qh_sharpnewfacets(qhT *qh) {
    +  facetT *facet;
    +  boolT issharp = False;
    +  int *quadrant, k;
    +
    +  quadrant= (int*)qh_memalloc(qh, qh->hull_dim * sizeof(int));
    +  FORALLfacet_(qh->newfacet_list) {
    +    if (facet == qh->newfacet_list) {
    +      for (k=qh->hull_dim; k--; )
    +        quadrant[ k]= (facet->normal[ k] > 0);
    +    }else {
    +      for (k=qh->hull_dim; k--; ) {
    +        if (quadrant[ k] != (facet->normal[ k] > 0)) {
    +          issharp= True;
    +          break;
    +        }
    +      }
    +    }
    +    if (issharp)
    +      break;
    +  }
    +  qh_memfree(qh, quadrant, qh->hull_dim * sizeof(int));
    +  trace3((qh, qh->ferr, 3001, "qh_sharpnewfacets: %d\n", issharp));
    +  return issharp;
    +} /* sharpnewfacets */
    +
    +/*---------------------------------
    +
    +  qh_voronoi_center(qh, dim, points )
    +    return Voronoi center for a set of points
    +    dim is the orginal dimension of the points
    +    gh.gm_matrix/qh.gm_row are scratch buffers
    +
    +  returns:
    +    center as a temporary point (qh_memalloc)
    +    if non-simplicial,
    +      returns center for max simplex of points
    +
    +  notes:
    +    only called by qh_facetcenter
    +    from Bowyer & Woodwark, A Programmer's Geometry, 1983, p. 65
    +
    +  design:
    +    if non-simplicial
    +      determine max simplex for points
    +    translate point0 of simplex to origin
    +    compute sum of squares of diagonal
    +    compute determinate
    +    compute Voronoi center (see Bowyer & Woodwark)
    +*/
    +pointT *qh_voronoi_center(qhT *qh, int dim, setT *points) {
    +  pointT *point, **pointp, *point0;
    +  pointT *center= (pointT*)qh_memalloc(qh, qh->center_size);
    +  setT *simplex;
    +  int i, j, k, size= qh_setsize(qh, points);
    +  coordT *gmcoord;
    +  realT *diffp, sum2, *sum2row, *sum2p, det, factor;
    +  boolT nearzero, infinite;
    +
    +  if (size == dim+1)
    +    simplex= points;
    +  else if (size < dim+1) {
    +    qh_memfree(qh, center, qh->center_size);
    +    qh_fprintf(qh, qh->ferr, 6025, "qhull internal error (qh_voronoi_center):\n  need at least %d points to construct a Voronoi center\n",
    +             dim+1);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    simplex= points;  /* never executed -- avoids warning */
    +  }else {
    +    simplex= qh_settemp(qh, dim+1);
    +    qh_maxsimplex(qh, dim, points, NULL, 0, &simplex);
    +  }
    +  point0= SETfirstt_(simplex, pointT);
    +  gmcoord= qh->gm_matrix;
    +  for (k=0; k < dim; k++) {
    +    qh->gm_row[k]= gmcoord;
    +    FOREACHpoint_(simplex) {
    +      if (point != point0)
    +        *(gmcoord++)= point[k] - point0[k];
    +    }
    +  }
    +  sum2row= gmcoord;
    +  for (i=0; i < dim; i++) {
    +    sum2= 0.0;
    +    for (k=0; k < dim; k++) {
    +      diffp= qh->gm_row[k] + i;
    +      sum2 += *diffp * *diffp;
    +    }
    +    *(gmcoord++)= sum2;
    +  }
    +  det= qh_determinant(qh, qh->gm_row, dim, &nearzero);
    +  factor= qh_divzero(0.5, det, qh->MINdenom, &infinite);
    +  if (infinite) {
    +    for (k=dim; k--; )
    +      center[k]= qh_INFINITE;
    +    if (qh->IStracing)
    +      qh_printpoints(qh, qh->ferr, "qh_voronoi_center: at infinity for ", simplex);
    +  }else {
    +    for (i=0; i < dim; i++) {
    +      gmcoord= qh->gm_matrix;
    +      sum2p= sum2row;
    +      for (k=0; k < dim; k++) {
    +        qh->gm_row[k]= gmcoord;
    +        if (k == i) {
    +          for (j=dim; j--; )
    +            *(gmcoord++)= *sum2p++;
    +        }else {
    +          FOREACHpoint_(simplex) {
    +            if (point != point0)
    +              *(gmcoord++)= point[k] - point0[k];
    +          }
    +        }
    +      }
    +      center[i]= qh_determinant(qh, qh->gm_row, dim, &nearzero)*factor + point0[i];
    +    }
    +#ifndef qh_NOtrace
    +    if (qh->IStracing >= 3) {
    +      qh_fprintf(qh, qh->ferr, 8033, "qh_voronoi_center: det %2.2g factor %2.2g ", det, factor);
    +      qh_printmatrix(qh, qh->ferr, "center:", ¢er, 1, dim);
    +      if (qh->IStracing >= 5) {
    +        qh_printpoints(qh, qh->ferr, "points", simplex);
    +        FOREACHpoint_(simplex)
    +          qh_fprintf(qh, qh->ferr, 8034, "p%d dist %.2g, ", qh_pointid(qh, point),
    +                   qh_pointdist(point, center, dim));
    +        qh_fprintf(qh, qh->ferr, 8035, "\n");
    +      }
    +    }
    +#endif
    +  }
    +  if (simplex != points)
    +    qh_settempfree(qh, &simplex);
    +  return center;
    +} /* voronoi_center */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/geom_r.c b/xs/src/qhull/src/libqhull_r/geom_r.c
    new file mode 100644
    index 0000000000..8104813cad
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/geom_r.c
    @@ -0,0 +1,1234 @@
    +/*
      ---------------------------------
    +
    +   geom_r.c
    +   geometric routines of qhull
    +
    +   see qh-geom_r.htm and geom_r.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/geom_r.c#2 $$Change: 1995 $
    +   $DateTime: 2015/10/13 21:59:42 $$Author: bbarber $
    +
    +   infrequent code goes into geom2_r.c
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*---------------------------------
    +
    +  qh_distplane(qh, point, facet, dist )
    +    return distance from point to facet
    +
    +  returns:
    +    dist
    +    if qh.RANDOMdist, joggles result
    +
    +  notes:
    +    dist > 0 if point is above facet (i.e., outside)
    +    does not error (for qh_sortfacets, qh_outerinner)
    +
    +  see:
    +    qh_distnorm in geom2_r.c
    +    qh_distplane [geom_r.c], QhullFacet::distance, and QhullHyperplane::distance are copies
    +*/
    +void qh_distplane(qhT *qh, pointT *point, facetT *facet, realT *dist) {
    +  coordT *normal= facet->normal, *coordp, randr;
    +  int k;
    +
    +  switch (qh->hull_dim){
    +  case 2:
    +    *dist= facet->offset + point[0] * normal[0] + point[1] * normal[1];
    +    break;
    +  case 3:
    +    *dist= facet->offset + point[0] * normal[0] + point[1] * normal[1] + point[2] * normal[2];
    +    break;
    +  case 4:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3];
    +    break;
    +  case 5:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4];
    +    break;
    +  case 6:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5];
    +    break;
    +  case 7:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6];
    +    break;
    +  case 8:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6]+point[7]*normal[7];
    +    break;
    +  default:
    +    *dist= facet->offset;
    +    coordp= point;
    +    for (k=qh->hull_dim; k--; )
    +      *dist += *coordp++ * *normal++;
    +    break;
    +  }
    +  zinc_(Zdistplane);
    +  if (!qh->RANDOMdist && qh->IStracing < 4)
    +    return;
    +  if (qh->RANDOMdist) {
    +    randr= qh_RANDOMint;
    +    *dist += (2.0 * randr / qh_RANDOMmax - 1.0) *
    +      qh->RANDOMfactor * qh->MAXabs_coord;
    +  }
    +  if (qh->IStracing >= 4) {
    +    qh_fprintf(qh, qh->ferr, 8001, "qh_distplane: ");
    +    qh_fprintf(qh, qh->ferr, 8002, qh_REAL_1, *dist);
    +    qh_fprintf(qh, qh->ferr, 8003, "from p%d to f%d\n", qh_pointid(qh, point), facet->id);
    +  }
    +  return;
    +} /* distplane */
    +
    +
    +/*---------------------------------
    +
    +  qh_findbest(qh, point, startfacet, bestoutside, qh_ISnewfacets, qh_NOupper, dist, isoutside, numpart )
    +    find facet that is furthest below a point
    +    for upperDelaunay facets
    +      returns facet only if !qh_NOupper and clearly above
    +
    +  input:
    +    starts search at 'startfacet' (can not be flipped)
    +    if !bestoutside(qh_ALL), stops at qh.MINoutside
    +
    +  returns:
    +    best facet (reports error if NULL)
    +    early out if isoutside defined and bestdist > qh.MINoutside
    +    dist is distance to facet
    +    isoutside is true if point is outside of facet
    +    numpart counts the number of distance tests
    +
    +  see also:
    +    qh_findbestnew()
    +
    +  notes:
    +    If merging (testhorizon), searches horizon facets of coplanar best facets because
    +    after qh_distplane, this and qh_partitionpoint are the most expensive in 3-d
    +      avoid calls to distplane, function calls, and real number operations.
    +    caller traces result
    +    Optimized for outside points.   Tried recording a search set for qh_findhorizon.
    +    Made code more complicated.
    +
    +  when called by qh_partitionvisible():
    +    indicated by qh_ISnewfacets
    +    qh.newfacet_list is list of simplicial, new facets
    +    qh_findbestnew set if qh_sharpnewfacets returns True (to use qh_findbestnew)
    +    qh.bestfacet_notsharp set if qh_sharpnewfacets returns False
    +
    +  when called by qh_findfacet(), qh_partitionpoint(), qh_partitioncoplanar(),
    +                 qh_check_bestdist(), qh_addpoint()
    +    indicated by !qh_ISnewfacets
    +    returns best facet in neighborhood of given facet
    +      this is best facet overall if dist > -   qh.MAXcoplanar
    +        or hull has at least a "spherical" curvature
    +
    +  design:
    +    initialize and test for early exit
    +    repeat while there are better facets
    +      for each neighbor of facet
    +        exit if outside facet found
    +        test for better facet
    +    if point is inside and partitioning
    +      test for new facets with a "sharp" intersection
    +      if so, future calls go to qh_findbestnew()
    +    test horizon facets
    +*/
    +facetT *qh_findbest(qhT *qh, pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT isnewfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart) {
    +  realT bestdist= -REALmax/2 /* avoid underflow */;
    +  facetT *facet, *neighbor, **neighborp;
    +  facetT *bestfacet= NULL, *lastfacet= NULL;
    +  int oldtrace= qh->IStracing;
    +  unsigned int visitid= ++qh->visit_id;
    +  int numpartnew=0;
    +  boolT testhorizon = True; /* needed if precise, e.g., rbox c D6 | qhull Q0 Tv */
    +
    +  zinc_(Zfindbest);
    +  if (qh->IStracing >= 3 || (qh->TRACElevel && qh->TRACEpoint >= 0 && qh->TRACEpoint == qh_pointid(qh, point))) {
    +    if (qh->TRACElevel > qh->IStracing)
    +      qh->IStracing= qh->TRACElevel;
    +    qh_fprintf(qh, qh->ferr, 8004, "qh_findbest: point p%d starting at f%d isnewfacets? %d, unless %d exit if > %2.2g\n",
    +             qh_pointid(qh, point), startfacet->id, isnewfacets, bestoutside, qh->MINoutside);
    +    qh_fprintf(qh, qh->ferr, 8005, "  testhorizon? %d noupper? %d", testhorizon, noupper);
    +    qh_fprintf(qh, qh->ferr, 8006, "  Last point added was p%d.", qh->furthest_id);
    +    qh_fprintf(qh, qh->ferr, 8007, "  Last merge was #%d.  max_outside %2.2g\n", zzval_(Ztotmerge), qh->max_outside);
    +  }
    +  if (isoutside)
    +    *isoutside= True;
    +  if (!startfacet->flipped) {  /* test startfacet */
    +    *numpart= 1;
    +    qh_distplane(qh, point, startfacet, dist);  /* this code is duplicated below */
    +    if (!bestoutside && *dist >= qh->MINoutside
    +    && (!startfacet->upperdelaunay || !noupper)) {
    +      bestfacet= startfacet;
    +      goto LABELreturn_best;
    +    }
    +    bestdist= *dist;
    +    if (!startfacet->upperdelaunay) {
    +      bestfacet= startfacet;
    +    }
    +  }else
    +    *numpart= 0;
    +  startfacet->visitid= visitid;
    +  facet= startfacet;
    +  while (facet) {
    +    trace4((qh, qh->ferr, 4001, "qh_findbest: neighbors of f%d, bestdist %2.2g f%d\n",
    +                facet->id, bestdist, getid_(bestfacet)));
    +    lastfacet= facet;
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->newfacet && isnewfacets)
    +        continue;
    +      if (neighbor->visitid == visitid)
    +        continue;
    +      neighbor->visitid= visitid;
    +      if (!neighbor->flipped) {  /* code duplicated above */
    +        (*numpart)++;
    +        qh_distplane(qh, point, neighbor, dist);
    +        if (*dist > bestdist) {
    +          if (!bestoutside && *dist >= qh->MINoutside
    +          && (!neighbor->upperdelaunay || !noupper)) {
    +            bestfacet= neighbor;
    +            goto LABELreturn_best;
    +          }
    +          if (!neighbor->upperdelaunay) {
    +            bestfacet= neighbor;
    +            bestdist= *dist;
    +            break; /* switch to neighbor */
    +          }else if (!bestfacet) {
    +            bestdist= *dist;
    +            break; /* switch to neighbor */
    +          }
    +        } /* end of *dist>bestdist */
    +      } /* end of !flipped */
    +    } /* end of FOREACHneighbor */
    +    facet= neighbor;  /* non-NULL only if *dist>bestdist */
    +  } /* end of while facet (directed search) */
    +  if (isnewfacets) {
    +    if (!bestfacet) {
    +      bestdist= -REALmax/2;
    +      bestfacet= qh_findbestnew(qh, point, startfacet->next, &bestdist, bestoutside, isoutside, &numpartnew);
    +      testhorizon= False; /* qh_findbestnew calls qh_findbesthorizon */
    +    }else if (!qh->findbest_notsharp && bestdist < - qh->DISTround) {
    +      if (qh_sharpnewfacets(qh)) {
    +        /* seldom used, qh_findbestnew will retest all facets */
    +        zinc_(Zfindnewsharp);
    +        bestfacet= qh_findbestnew(qh, point, bestfacet, &bestdist, bestoutside, isoutside, &numpartnew);
    +        testhorizon= False; /* qh_findbestnew calls qh_findbesthorizon */
    +        qh->findbestnew= True;
    +      }else
    +        qh->findbest_notsharp= True;
    +    }
    +  }
    +  if (!bestfacet)
    +    bestfacet= qh_findbestlower(qh, lastfacet, point, &bestdist, numpart);
    +  if (testhorizon)
    +    bestfacet= qh_findbesthorizon(qh, !qh_IScheckmax, point, bestfacet, noupper, &bestdist, &numpartnew);
    +  *dist= bestdist;
    +  if (isoutside && bestdist < qh->MINoutside)
    +    *isoutside= False;
    +LABELreturn_best:
    +  zadd_(Zfindbesttot, *numpart);
    +  zmax_(Zfindbestmax, *numpart);
    +  (*numpart) += numpartnew;
    +  qh->IStracing= oldtrace;
    +  return bestfacet;
    +}  /* findbest */
    +
    +
    +/*---------------------------------
    +
    +  qh_findbesthorizon(qh, qh_IScheckmax, point, startfacet, qh_NOupper, &bestdist, &numpart )
    +    search coplanar and better horizon facets from startfacet/bestdist
    +    ischeckmax turns off statistics and minsearch update
    +    all arguments must be initialized
    +  returns(ischeckmax):
    +    best facet
    +  returns(!ischeckmax):
    +    best facet that is not upperdelaunay
    +    allows upperdelaunay that is clearly outside
    +  returns:
    +    bestdist is distance to bestfacet
    +    numpart -- updates number of distance tests
    +
    +  notes:
    +    no early out -- use qh_findbest() or qh_findbestnew()
    +    Searches coplanar or better horizon facets
    +
    +  when called by qh_check_maxout() (qh_IScheckmax)
    +    startfacet must be closest to the point
    +      Otherwise, if point is beyond and below startfacet, startfacet may be a local minimum
    +      even though other facets are below the point.
    +    updates facet->maxoutside for good, visited facets
    +    may return NULL
    +
    +    searchdist is qh.max_outside + 2 * DISTround
    +      + max( MINvisible('Vn'), MAXcoplanar('Un'));
    +    This setting is a guess.  It must be at least max_outside + 2*DISTround
    +    because a facet may have a geometric neighbor across a vertex
    +
    +  design:
    +    for each horizon facet of coplanar best facets
    +      continue if clearly inside
    +      unless upperdelaunay or clearly outside
    +         update best facet
    +*/
    +facetT *qh_findbesthorizon(qhT *qh, boolT ischeckmax, pointT* point, facetT *startfacet, boolT noupper, realT *bestdist, int *numpart) {
    +  facetT *bestfacet= startfacet;
    +  realT dist;
    +  facetT *neighbor, **neighborp, *facet;
    +  facetT *nextfacet= NULL; /* optimize last facet of coplanarfacetset */
    +  int numpartinit= *numpart, coplanarfacetset_size;
    +  unsigned int visitid= ++qh->visit_id;
    +  boolT newbest= False; /* for tracing */
    +  realT minsearch, searchdist;  /* skip facets that are too far from point */
    +
    +  if (!ischeckmax) {
    +    zinc_(Zfindhorizon);
    +  }else {
    +#if qh_MAXoutside
    +    if ((!qh->ONLYgood || startfacet->good) && *bestdist > startfacet->maxoutside)
    +      startfacet->maxoutside= *bestdist;
    +#endif
    +  }
    +  searchdist= qh_SEARCHdist; /* multiple of qh.max_outside and precision constants */
    +  minsearch= *bestdist - searchdist;
    +  if (ischeckmax) {
    +    /* Always check coplanar facets.  Needed for RBOX 1000 s Z1 G1e-13 t996564279 | QHULL Tv */
    +    minimize_(minsearch, -searchdist);
    +  }
    +  coplanarfacetset_size= 0;
    +  facet= startfacet;
    +  while (True) {
    +    trace4((qh, qh->ferr, 4002, "qh_findbesthorizon: neighbors of f%d bestdist %2.2g f%d ischeckmax? %d noupper? %d minsearch %2.2g searchdist %2.2g\n",
    +                facet->id, *bestdist, getid_(bestfacet), ischeckmax, noupper,
    +                minsearch, searchdist));
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == visitid)
    +        continue;
    +      neighbor->visitid= visitid;
    +      if (!neighbor->flipped) {
    +        qh_distplane(qh, point, neighbor, &dist);
    +        (*numpart)++;
    +        if (dist > *bestdist) {
    +          if (!neighbor->upperdelaunay || ischeckmax || (!noupper && dist >= qh->MINoutside)) {
    +            bestfacet= neighbor;
    +            *bestdist= dist;
    +            newbest= True;
    +            if (!ischeckmax) {
    +              minsearch= dist - searchdist;
    +              if (dist > *bestdist + searchdist) {
    +                zinc_(Zfindjump);  /* everything in qh.coplanarfacetset at least searchdist below */
    +                coplanarfacetset_size= 0;
    +              }
    +            }
    +          }
    +        }else if (dist < minsearch)
    +          continue;  /* if ischeckmax, dist can't be positive */
    +#if qh_MAXoutside
    +        if (ischeckmax && dist > neighbor->maxoutside)
    +          neighbor->maxoutside= dist;
    +#endif
    +      } /* end of !flipped */
    +      if (nextfacet) {
    +        if (!coplanarfacetset_size++) {
    +          SETfirst_(qh->coplanarfacetset)= nextfacet;
    +          SETtruncate_(qh->coplanarfacetset, 1);
    +        }else
    +          qh_setappend(qh, &qh->coplanarfacetset, nextfacet); /* Was needed for RBOX 1000 s W1e-13 P0 t996547055 | QHULL d Qbb Qc Tv
    +                                                 and RBOX 1000 s Z1 G1e-13 t996564279 | qhull Tv  */
    +      }
    +      nextfacet= neighbor;
    +    } /* end of EACHneighbor */
    +    facet= nextfacet;
    +    if (facet)
    +      nextfacet= NULL;
    +    else if (!coplanarfacetset_size)
    +      break;
    +    else if (!--coplanarfacetset_size) {
    +      facet= SETfirstt_(qh->coplanarfacetset, facetT);
    +      SETtruncate_(qh->coplanarfacetset, 0);
    +    }else
    +      facet= (facetT*)qh_setdellast(qh->coplanarfacetset);
    +  } /* while True, for each facet in qh.coplanarfacetset */
    +  if (!ischeckmax) {
    +    zadd_(Zfindhorizontot, *numpart - numpartinit);
    +    zmax_(Zfindhorizonmax, *numpart - numpartinit);
    +    if (newbest)
    +      zinc_(Zparthorizon);
    +  }
    +  trace4((qh, qh->ferr, 4003, "qh_findbesthorizon: newbest? %d bestfacet f%d bestdist %2.2g\n", newbest, getid_(bestfacet), *bestdist));
    +  return bestfacet;
    +}  /* findbesthorizon */
    +
    +/*---------------------------------
    +
    +  qh_findbestnew(qh, point, startfacet, dist, isoutside, numpart )
    +    find best newfacet for point
    +    searches all of qh.newfacet_list starting at startfacet
    +    searches horizon facets of coplanar best newfacets
    +    searches all facets if startfacet == qh.facet_list
    +  returns:
    +    best new or horizon facet that is not upperdelaunay
    +    early out if isoutside and not 'Qf'
    +    dist is distance to facet
    +    isoutside is true if point is outside of facet
    +    numpart is number of distance tests
    +
    +  notes:
    +    Always used for merged new facets (see qh_USEfindbestnew)
    +    Avoids upperdelaunay facet unless (isoutside and outside)
    +
    +    Uses qh.visit_id, qh.coplanarfacetset.
    +    If share visit_id with qh_findbest, coplanarfacetset is incorrect.
    +
    +    If merging (testhorizon), searches horizon facets of coplanar best facets because
    +    a point maybe coplanar to the bestfacet, below its horizon facet,
    +    and above a horizon facet of a coplanar newfacet.  For example,
    +      rbox 1000 s Z1 G1e-13 | qhull
    +      rbox 1000 s W1e-13 P0 t992110337 | QHULL d Qbb Qc
    +
    +    qh_findbestnew() used if
    +       qh_sharpnewfacets -- newfacets contains a sharp angle
    +       if many merges, qh_premerge found a merge, or 'Qf' (qh.findbestnew)
    +
    +  see also:
    +    qh_partitionall() and qh_findbest()
    +
    +  design:
    +    for each new facet starting from startfacet
    +      test distance from point to facet
    +      return facet if clearly outside
    +      unless upperdelaunay and a lowerdelaunay exists
    +         update best facet
    +    test horizon facets
    +*/
    +facetT *qh_findbestnew(qhT *qh, pointT *point, facetT *startfacet,
    +           realT *dist, boolT bestoutside, boolT *isoutside, int *numpart) {
    +  realT bestdist= -REALmax/2;
    +  facetT *bestfacet= NULL, *facet;
    +  int oldtrace= qh->IStracing, i;
    +  unsigned int visitid= ++qh->visit_id;
    +  realT distoutside= 0.0;
    +  boolT isdistoutside; /* True if distoutside is defined */
    +  boolT testhorizon = True; /* needed if precise, e.g., rbox c D6 | qhull Q0 Tv */
    +
    +  if (!startfacet) {
    +    if (qh->MERGING)
    +      qh_fprintf(qh, qh->ferr, 6001, "qhull precision error (qh_findbestnew): merging has formed and deleted a cone of new facets.  Can not continue.\n");
    +    else
    +      qh_fprintf(qh, qh->ferr, 6002, "qhull internal error (qh_findbestnew): no new facets for point p%d\n",
    +              qh->furthest_id);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  zinc_(Zfindnew);
    +  if (qh->BESToutside || bestoutside)
    +    isdistoutside= False;
    +  else {
    +    isdistoutside= True;
    +    distoutside= qh_DISToutside; /* multiple of qh.MINoutside & qh.max_outside, see user.h */
    +  }
    +  if (isoutside)
    +    *isoutside= True;
    +  *numpart= 0;
    +  if (qh->IStracing >= 3 || (qh->TRACElevel && qh->TRACEpoint >= 0 && qh->TRACEpoint == qh_pointid(qh, point))) {
    +    if (qh->TRACElevel > qh->IStracing)
    +      qh->IStracing= qh->TRACElevel;
    +    qh_fprintf(qh, qh->ferr, 8008, "qh_findbestnew: point p%d facet f%d. Stop? %d if dist > %2.2g\n",
    +             qh_pointid(qh, point), startfacet->id, isdistoutside, distoutside);
    +    qh_fprintf(qh, qh->ferr, 8009, "  Last point added p%d visitid %d.",  qh->furthest_id, visitid);
    +    qh_fprintf(qh, qh->ferr, 8010, "  Last merge was #%d.\n", zzval_(Ztotmerge));
    +  }
    +  /* visit all new facets starting with startfacet, maybe qh->facet_list */
    +  for (i=0, facet=startfacet; i < 2; i++, facet= qh->newfacet_list) {
    +    FORALLfacet_(facet) {
    +      if (facet == startfacet && i)
    +        break;
    +      facet->visitid= visitid;
    +      if (!facet->flipped) {
    +        qh_distplane(qh, point, facet, dist);
    +        (*numpart)++;
    +        if (*dist > bestdist) {
    +          if (!facet->upperdelaunay || *dist >= qh->MINoutside) {
    +            bestfacet= facet;
    +            if (isdistoutside && *dist >= distoutside)
    +              goto LABELreturn_bestnew;
    +            bestdist= *dist;
    +          }
    +        }
    +      } /* end of !flipped */
    +    } /* FORALLfacet from startfacet or qh->newfacet_list */
    +  }
    +  if (testhorizon || !bestfacet) /* testhorizon is always True.  Keep the same code as qh_findbest */
    +    bestfacet= qh_findbesthorizon(qh, !qh_IScheckmax, point, bestfacet ? bestfacet : startfacet,
    +                                        !qh_NOupper, &bestdist, numpart);
    +  *dist= bestdist;
    +  if (isoutside && *dist < qh->MINoutside)
    +    *isoutside= False;
    +LABELreturn_bestnew:
    +  zadd_(Zfindnewtot, *numpart);
    +  zmax_(Zfindnewmax, *numpart);
    +  trace4((qh, qh->ferr, 4004, "qh_findbestnew: bestfacet f%d bestdist %2.2g\n", getid_(bestfacet), *dist));
    +  qh->IStracing= oldtrace;
    +  return bestfacet;
    +}  /* findbestnew */
    +
    +/* ============ hyperplane functions -- keep code together [?] ============ */
    +
    +/*---------------------------------
    +
    +  qh_backnormal(qh, rows, numrow, numcol, sign, normal, nearzero )
    +    given an upper-triangular rows array and a sign,
    +    solve for normal equation x using back substitution over rows U
    +
    +  returns:
    +     normal= x
    +
    +     if will not be able to divzero() when normalized(qh.MINdenom_2 and qh.MINdenom_1_2),
    +       if fails on last row
    +         this means that the hyperplane intersects [0,..,1]
    +         sets last coordinate of normal to sign
    +       otherwise
    +         sets tail of normal to [...,sign,0,...], i.e., solves for b= [0...0]
    +         sets nearzero
    +
    +  notes:
    +     assumes numrow == numcol-1
    +
    +     see Golub & van Loan, 1983, Eq. 4.4-9 for "Gaussian elimination with complete pivoting"
    +
    +     solves Ux=b where Ax=b and PA=LU
    +     b= [0,...,0,sign or 0]  (sign is either -1 or +1)
    +     last row of A= [0,...,0,1]
    +
    +     1) Ly=Pb == y=b since P only permutes the 0's of   b
    +
    +  design:
    +    for each row from end
    +      perform back substitution
    +      if near zero
    +        use qh_divzero for division
    +        if zero divide and not last row
    +          set tail of normal to 0
    +*/
    +void qh_backnormal(qhT *qh, realT **rows, int numrow, int numcol, boolT sign,
    +        coordT *normal, boolT *nearzero) {
    +  int i, j;
    +  coordT *normalp, *normal_tail, *ai, *ak;
    +  realT diagonal;
    +  boolT waszero;
    +  int zerocol= -1;
    +
    +  normalp= normal + numcol - 1;
    +  *normalp--= (sign ? -1.0 : 1.0);
    +  for (i=numrow; i--; ) {
    +    *normalp= 0.0;
    +    ai= rows[i] + i + 1;
    +    ak= normalp+1;
    +    for (j=i+1; j < numcol; j++)
    +      *normalp -= *ai++ * *ak++;
    +    diagonal= (rows[i])[i];
    +    if (fabs_(diagonal) > qh->MINdenom_2)
    +      *(normalp--) /= diagonal;
    +    else {
    +      waszero= False;
    +      *normalp= qh_divzero(*normalp, diagonal, qh->MINdenom_1_2, &waszero);
    +      if (waszero) {
    +        zerocol= i;
    +        *(normalp--)= (sign ? -1.0 : 1.0);
    +        for (normal_tail= normalp+2; normal_tail < normal + numcol; normal_tail++)
    +          *normal_tail= 0.0;
    +      }else
    +        normalp--;
    +    }
    +  }
    +  if (zerocol != -1) {
    +    zzinc_(Zback0);
    +    *nearzero= True;
    +    trace4((qh, qh->ferr, 4005, "qh_backnormal: zero diagonal at column %d.\n", i));
    +    qh_precision(qh, "zero diagonal on back substitution");
    +  }
    +} /* backnormal */
    +
    +/*---------------------------------
    +
    +  qh_gausselim(qh, rows, numrow, numcol, sign )
    +    Gaussian elimination with partial pivoting
    +
    +  returns:
    +    rows is upper triangular (includes row exchanges)
    +    flips sign for each row exchange
    +    sets nearzero if pivot[k] < qh.NEARzero[k], else clears it
    +
    +  notes:
    +    if nearzero, the determinant's sign may be incorrect.
    +    assumes numrow <= numcol
    +
    +  design:
    +    for each row
    +      determine pivot and exchange rows if necessary
    +      test for near zero
    +      perform gaussian elimination step
    +*/
    +void qh_gausselim(qhT *qh, realT **rows, int numrow, int numcol, boolT *sign, boolT *nearzero) {
    +  realT *ai, *ak, *rowp, *pivotrow;
    +  realT n, pivot, pivot_abs= 0.0, temp;
    +  int i, j, k, pivoti, flip=0;
    +
    +  *nearzero= False;
    +  for (k=0; k < numrow; k++) {
    +    pivot_abs= fabs_((rows[k])[k]);
    +    pivoti= k;
    +    for (i=k+1; i < numrow; i++) {
    +      if ((temp= fabs_((rows[i])[k])) > pivot_abs) {
    +        pivot_abs= temp;
    +        pivoti= i;
    +      }
    +    }
    +    if (pivoti != k) {
    +      rowp= rows[pivoti];
    +      rows[pivoti]= rows[k];
    +      rows[k]= rowp;
    +      *sign ^= 1;
    +      flip ^= 1;
    +    }
    +    if (pivot_abs <= qh->NEARzero[k]) {
    +      *nearzero= True;
    +      if (pivot_abs == 0.0) {   /* remainder of column == 0 */
    +        if (qh->IStracing >= 4) {
    +          qh_fprintf(qh, qh->ferr, 8011, "qh_gausselim: 0 pivot at column %d. (%2.2g < %2.2g)\n", k, pivot_abs, qh->DISTround);
    +          qh_printmatrix(qh, qh->ferr, "Matrix:", rows, numrow, numcol);
    +        }
    +        zzinc_(Zgauss0);
    +        qh_precision(qh, "zero pivot for Gaussian elimination");
    +        goto LABELnextcol;
    +      }
    +    }
    +    pivotrow= rows[k] + k;
    +    pivot= *pivotrow++;  /* signed value of pivot, and remainder of row */
    +    for (i=k+1; i < numrow; i++) {
    +      ai= rows[i] + k;
    +      ak= pivotrow;
    +      n= (*ai++)/pivot;   /* divzero() not needed since |pivot| >= |*ai| */
    +      for (j= numcol - (k+1); j--; )
    +        *ai++ -= n * *ak++;
    +    }
    +  LABELnextcol:
    +    ;
    +  }
    +  wmin_(Wmindenom, pivot_abs);  /* last pivot element */
    +  if (qh->IStracing >= 5)
    +    qh_printmatrix(qh, qh->ferr, "qh_gausselem: result", rows, numrow, numcol);
    +} /* gausselim */
    +
    +
    +/*---------------------------------
    +
    +  qh_getangle(qh, vect1, vect2 )
    +    returns the dot product of two vectors
    +    if qh.RANDOMdist, joggles result
    +
    +  notes:
    +    the angle may be > 1.0 or < -1.0 because of roundoff errors
    +
    +*/
    +realT qh_getangle(qhT *qh, pointT *vect1, pointT *vect2) {
    +  realT angle= 0, randr;
    +  int k;
    +
    +  for (k=qh->hull_dim; k--; )
    +    angle += *vect1++ * *vect2++;
    +  if (qh->RANDOMdist) {
    +    randr= qh_RANDOMint;
    +    angle += (2.0 * randr / qh_RANDOMmax - 1.0) *
    +      qh->RANDOMfactor;
    +  }
    +  trace4((qh, qh->ferr, 4006, "qh_getangle: %2.2g\n", angle));
    +  return(angle);
    +} /* getangle */
    +
    +
    +/*---------------------------------
    +
    +  qh_getcenter(qh, vertices )
    +    returns arithmetic center of a set of vertices as a new point
    +
    +  notes:
    +    allocates point array for center
    +*/
    +pointT *qh_getcenter(qhT *qh, setT *vertices) {
    +  int k;
    +  pointT *center, *coord;
    +  vertexT *vertex, **vertexp;
    +  int count= qh_setsize(qh, vertices);
    +
    +  if (count < 2) {
    +    qh_fprintf(qh, qh->ferr, 6003, "qhull internal error (qh_getcenter): not defined for %d points\n", count);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  center= (pointT *)qh_memalloc(qh, qh->normal_size);
    +  for (k=0; k < qh->hull_dim; k++) {
    +    coord= center+k;
    +    *coord= 0.0;
    +    FOREACHvertex_(vertices)
    +      *coord += vertex->point[k];
    +    *coord /= count;  /* count>=2 by QH6003 */
    +  }
    +  return(center);
    +} /* getcenter */
    +
    +
    +/*---------------------------------
    +
    +  qh_getcentrum(qh, facet )
    +    returns the centrum for a facet as a new point
    +
    +  notes:
    +    allocates the centrum
    +*/
    +pointT *qh_getcentrum(qhT *qh, facetT *facet) {
    +  realT dist;
    +  pointT *centrum, *point;
    +
    +  point= qh_getcenter(qh, facet->vertices);
    +  zzinc_(Zcentrumtests);
    +  qh_distplane(qh, point, facet, &dist);
    +  centrum= qh_projectpoint(qh, point, facet, dist);
    +  qh_memfree(qh, point, qh->normal_size);
    +  trace4((qh, qh->ferr, 4007, "qh_getcentrum: for f%d, %d vertices dist= %2.2g\n",
    +          facet->id, qh_setsize(qh, facet->vertices), dist));
    +  return centrum;
    +} /* getcentrum */
    +
    +
    +/*---------------------------------
    +
    +  qh_getdistance(qh, facet, neighbor, mindist, maxdist )
    +    returns the maxdist and mindist distance of any vertex from neighbor
    +
    +  returns:
    +    the max absolute value
    +
    +  design:
    +    for each vertex of facet that is not in neighbor
    +      test the distance from vertex to neighbor
    +*/
    +realT qh_getdistance(qhT *qh, facetT *facet, facetT *neighbor, realT *mindist, realT *maxdist) {
    +  vertexT *vertex, **vertexp;
    +  realT dist, maxd, mind;
    +
    +  FOREACHvertex_(facet->vertices)
    +    vertex->seen= False;
    +  FOREACHvertex_(neighbor->vertices)
    +    vertex->seen= True;
    +  mind= 0.0;
    +  maxd= 0.0;
    +  FOREACHvertex_(facet->vertices) {
    +    if (!vertex->seen) {
    +      zzinc_(Zbestdist);
    +      qh_distplane(qh, vertex->point, neighbor, &dist);
    +      if (dist < mind)
    +        mind= dist;
    +      else if (dist > maxd)
    +        maxd= dist;
    +    }
    +  }
    +  *mindist= mind;
    +  *maxdist= maxd;
    +  mind= -mind;
    +  if (maxd > mind)
    +    return maxd;
    +  else
    +    return mind;
    +} /* getdistance */
    +
    +
    +/*---------------------------------
    +
    +  qh_normalize(qh, normal, dim, toporient )
    +    normalize a vector and report if too small
    +    does not use min norm
    +
    +  see:
    +    qh_normalize2
    +*/
    +void qh_normalize(qhT *qh, coordT *normal, int dim, boolT toporient) {
    +  qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
    +} /* normalize */
    +
    +/*---------------------------------
    +
    +  qh_normalize2(qh, normal, dim, toporient, minnorm, ismin )
    +    normalize a vector and report if too small
    +    qh.MINdenom/MINdenom1 are the upper limits for divide overflow
    +
    +  returns:
    +    normalized vector
    +    flips sign if !toporient
    +    if minnorm non-NULL,
    +      sets ismin if normal < minnorm
    +
    +  notes:
    +    if zero norm
    +       sets all elements to sqrt(1.0/dim)
    +    if divide by zero (divzero())
    +       sets largest element to   +/-1
    +       bumps Znearlysingular
    +
    +  design:
    +    computes norm
    +    test for minnorm
    +    if not near zero
    +      normalizes normal
    +    else if zero norm
    +      sets normal to standard value
    +    else
    +      uses qh_divzero to normalize
    +      if nearzero
    +        sets norm to direction of maximum value
    +*/
    +void qh_normalize2(qhT *qh, coordT *normal, int dim, boolT toporient,
    +            realT *minnorm, boolT *ismin) {
    +  int k;
    +  realT *colp, *maxp, norm= 0, temp, *norm1, *norm2, *norm3;
    +  boolT zerodiv;
    +
    +  norm1= normal+1;
    +  norm2= normal+2;
    +  norm3= normal+3;
    +  if (dim == 2)
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1));
    +  else if (dim == 3)
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2));
    +  else if (dim == 4) {
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2)
    +               + (*norm3)*(*norm3));
    +  }else if (dim > 4) {
    +    norm= (*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2)
    +               + (*norm3)*(*norm3);
    +    for (k=dim-4, colp=normal+4; k--; colp++)
    +      norm += (*colp) * (*colp);
    +    norm= sqrt(norm);
    +  }
    +  if (minnorm) {
    +    if (norm < *minnorm)
    +      *ismin= True;
    +    else
    +      *ismin= False;
    +  }
    +  wmin_(Wmindenom, norm);
    +  if (norm > qh->MINdenom) {
    +    if (!toporient)
    +      norm= -norm;
    +    *normal /= norm;
    +    *norm1 /= norm;
    +    if (dim == 2)
    +      ; /* all done */
    +    else if (dim == 3)
    +      *norm2 /= norm;
    +    else if (dim == 4) {
    +      *norm2 /= norm;
    +      *norm3 /= norm;
    +    }else if (dim >4) {
    +      *norm2 /= norm;
    +      *norm3 /= norm;
    +      for (k=dim-4, colp=normal+4; k--; )
    +        *colp++ /= norm;
    +    }
    +  }else if (norm == 0.0) {
    +    temp= sqrt(1.0/dim);
    +    for (k=dim, colp=normal; k--; )
    +      *colp++ = temp;
    +  }else {
    +    if (!toporient)
    +      norm= -norm;
    +    for (k=dim, colp=normal; k--; colp++) { /* k used below */
    +      temp= qh_divzero(*colp, norm, qh->MINdenom_1, &zerodiv);
    +      if (!zerodiv)
    +        *colp= temp;
    +      else {
    +        maxp= qh_maxabsval(normal, dim);
    +        temp= ((*maxp * norm >= 0.0) ? 1.0 : -1.0);
    +        for (k=dim, colp=normal; k--; colp++)
    +          *colp= 0.0;
    +        *maxp= temp;
    +        zzinc_(Znearlysingular);
    +        trace0((qh, qh->ferr, 1, "qh_normalize: norm=%2.2g too small during p%d\n",
    +               norm, qh->furthest_id));
    +        return;
    +      }
    +    }
    +  }
    +} /* normalize */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectpoint(qh, point, facet, dist )
    +    project point onto a facet by dist
    +
    +  returns:
    +    returns a new point
    +
    +  notes:
    +    if dist= distplane(point,facet)
    +      this projects point to hyperplane
    +    assumes qh_memfree_() is valid for normal_size
    +*/
    +pointT *qh_projectpoint(qhT *qh, pointT *point, facetT *facet, realT dist) {
    +  pointT *newpoint, *np, *normal;
    +  int normsize= qh->normal_size;
    +  int k;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_(qh, normsize, freelistp, newpoint, pointT);
    +  np= newpoint;
    +  normal= facet->normal;
    +  for (k=qh->hull_dim; k--; )
    +    *(np++)= *point++ - dist * *normal++;
    +  return(newpoint);
    +} /* projectpoint */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfacetplane(qh, facet )
    +    sets the hyperplane for a facet
    +    if qh.RANDOMdist, joggles hyperplane
    +
    +  notes:
    +    uses global buffers qh.gm_matrix and qh.gm_row
    +    overwrites facet->normal if already defined
    +    updates Wnewvertex if PRINTstatistics
    +    sets facet->upperdelaunay if upper envelope of Delaunay triangulation
    +
    +  design:
    +    copy vertex coordinates to qh.gm_matrix/gm_row
    +    compute determinate
    +    if nearzero
    +      recompute determinate with gaussian elimination
    +      if nearzero
    +        force outside orientation by testing interior point
    +*/
    +void qh_setfacetplane(qhT *qh, facetT *facet) {
    +  pointT *point;
    +  vertexT *vertex, **vertexp;
    +  int normsize= qh->normal_size;
    +  int k,i, oldtrace= 0;
    +  realT dist;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +  coordT *coord, *gmcoord;
    +  pointT *point0= SETfirstt_(facet->vertices, vertexT)->point;
    +  boolT nearzero= False;
    +
    +  zzinc_(Zsetplane);
    +  if (!facet->normal)
    +    qh_memalloc_(qh, normsize, freelistp, facet->normal, coordT);
    +  if (facet == qh->tracefacet) {
    +    oldtrace= qh->IStracing;
    +    qh->IStracing= 5;
    +    qh_fprintf(qh, qh->ferr, 8012, "qh_setfacetplane: facet f%d created.\n", facet->id);
    +    qh_fprintf(qh, qh->ferr, 8013, "  Last point added to hull was p%d.", qh->furthest_id);
    +    if (zzval_(Ztotmerge))
    +      qh_fprintf(qh, qh->ferr, 8014, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    qh_fprintf(qh, qh->ferr, 8015, "\n\nCurrent summary is:\n");
    +      qh_printsummary(qh, qh->ferr);
    +  }
    +  if (qh->hull_dim <= 4) {
    +    i= 0;
    +    if (qh->RANDOMdist) {
    +      gmcoord= qh->gm_matrix;
    +      FOREACHvertex_(facet->vertices) {
    +        qh->gm_row[i++]= gmcoord;
    +        coord= vertex->point;
    +        for (k=qh->hull_dim; k--; )
    +          *(gmcoord++)= *coord++ * qh_randomfactor(qh, qh->RANDOMa, qh->RANDOMb);
    +      }
    +    }else {
    +      FOREACHvertex_(facet->vertices)
    +       qh->gm_row[i++]= vertex->point;
    +    }
    +    qh_sethyperplane_det(qh, qh->hull_dim, qh->gm_row, point0, facet->toporient,
    +                facet->normal, &facet->offset, &nearzero);
    +  }
    +  if (qh->hull_dim > 4 || nearzero) {
    +    i= 0;
    +    gmcoord= qh->gm_matrix;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->point != point0) {
    +        qh->gm_row[i++]= gmcoord;
    +        coord= vertex->point;
    +        point= point0;
    +        for (k=qh->hull_dim; k--; )
    +          *(gmcoord++)= *coord++ - *point++;
    +      }
    +    }
    +    qh->gm_row[i]= gmcoord;  /* for areasimplex */
    +    if (qh->RANDOMdist) {
    +      gmcoord= qh->gm_matrix;
    +      for (i=qh->hull_dim-1; i--; ) {
    +        for (k=qh->hull_dim; k--; )
    +          *(gmcoord++) *= qh_randomfactor(qh, qh->RANDOMa, qh->RANDOMb);
    +      }
    +    }
    +    qh_sethyperplane_gauss(qh, qh->hull_dim, qh->gm_row, point0, facet->toporient,
    +                facet->normal, &facet->offset, &nearzero);
    +    if (nearzero) {
    +      if (qh_orientoutside(qh, facet)) {
    +        trace0((qh, qh->ferr, 2, "qh_setfacetplane: flipped orientation after testing interior_point during p%d\n", qh->furthest_id));
    +      /* this is part of using Gaussian Elimination.  For example in 5-d
    +           1 1 1 1 0
    +           1 1 1 1 1
    +           0 0 0 1 0
    +           0 1 0 0 0
    +           1 0 0 0 0
    +           norm= 0.38 0.38 -0.76 0.38 0
    +         has a determinate of 1, but g.e. after subtracting pt. 0 has
    +         0's in the diagonal, even with full pivoting.  It does work
    +         if you subtract pt. 4 instead. */
    +      }
    +    }
    +  }
    +  facet->upperdelaunay= False;
    +  if (qh->DELAUNAY) {
    +    if (qh->UPPERdelaunay) {     /* matches qh_triangulate_facet and qh.lower_threshold in qh_initbuild */
    +      if (facet->normal[qh->hull_dim -1] >= qh->ANGLEround * qh_ZEROdelaunay)
    +        facet->upperdelaunay= True;
    +    }else {
    +      if (facet->normal[qh->hull_dim -1] > -qh->ANGLEround * qh_ZEROdelaunay)
    +        facet->upperdelaunay= True;
    +    }
    +  }
    +  if (qh->PRINTstatistics || qh->IStracing || qh->TRACElevel || qh->JOGGLEmax < REALmax) {
    +    qh->old_randomdist= qh->RANDOMdist;
    +    qh->RANDOMdist= False;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->point != point0) {
    +        boolT istrace= False;
    +        zinc_(Zdiststat);
    +        qh_distplane(qh, vertex->point, facet, &dist);
    +        dist= fabs_(dist);
    +        zinc_(Znewvertex);
    +        wadd_(Wnewvertex, dist);
    +        if (dist > wwval_(Wnewvertexmax)) {
    +          wwval_(Wnewvertexmax)= dist;
    +          if (dist > qh->max_outside) {
    +            qh->max_outside= dist;  /* used by qh_maxouter(qh) */
    +            if (dist > qh->TRACEdist)
    +              istrace= True;
    +          }
    +        }else if (-dist > qh->TRACEdist)
    +          istrace= True;
    +        if (istrace) {
    +          qh_fprintf(qh, qh->ferr, 8016, "qh_setfacetplane: ====== vertex p%d(v%d) increases max_outside to %2.2g for new facet f%d last p%d\n",
    +                qh_pointid(qh, vertex->point), vertex->id, dist, facet->id, qh->furthest_id);
    +          qh_errprint(qh, "DISTANT", facet, NULL, NULL, NULL);
    +        }
    +      }
    +    }
    +    qh->RANDOMdist= qh->old_randomdist;
    +  }
    +  if (qh->IStracing >= 3) {
    +    qh_fprintf(qh, qh->ferr, 8017, "qh_setfacetplane: f%d offset %2.2g normal: ",
    +             facet->id, facet->offset);
    +    for (k=0; k < qh->hull_dim; k++)
    +      qh_fprintf(qh, qh->ferr, 8018, "%2.2g ", facet->normal[k]);
    +    qh_fprintf(qh, qh->ferr, 8019, "\n");
    +  }
    +  if (facet == qh->tracefacet)
    +    qh->IStracing= oldtrace;
    +} /* setfacetplane */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethyperplane_det(qh, dim, rows, point0, toporient, normal, offset, nearzero )
    +    given dim X dim array indexed by rows[], one row per point,
    +        toporient(flips all signs),
    +        and point0 (any row)
    +    set normalized hyperplane equation from oriented simplex
    +
    +  returns:
    +    normal (normalized)
    +    offset (places point0 on the hyperplane)
    +    sets nearzero if hyperplane not through points
    +
    +  notes:
    +    only defined for dim == 2..4
    +    rows[] is not modified
    +    solves det(P-V_0, V_n-V_0, ..., V_1-V_0)=0, i.e. every point is on hyperplane
    +    see Bower & Woodworth, A programmer's geometry, Butterworths 1983.
    +
    +  derivation of 3-d minnorm
    +    Goal: all vertices V_i within qh.one_merge of hyperplane
    +    Plan: exactly translate the facet so that V_0 is the origin
    +          exactly rotate the facet so that V_1 is on the x-axis and y_2=0.
    +          exactly rotate the effective perturbation to only effect n_0
    +             this introduces a factor of sqrt(3)
    +    n_0 = ((y_2-y_0)*(z_1-z_0) - (z_2-z_0)*(y_1-y_0)) / norm
    +    Let M_d be the max coordinate difference
    +    Let M_a be the greater of M_d and the max abs. coordinate
    +    Let u be machine roundoff and distround be max error for distance computation
    +    The max error for n_0 is sqrt(3) u M_a M_d / norm.  n_1 is approx. 1 and n_2 is approx. 0
    +    The max error for distance of V_1 is sqrt(3) u M_a M_d M_d / norm.  Offset=0 at origin
    +    Then minnorm = 1.8 u M_a M_d M_d / qh.ONEmerge
    +    Note that qh.one_merge is approx. 45.5 u M_a and norm is usually about M_d M_d
    +
    +  derivation of 4-d minnorm
    +    same as above except rotate the facet so that V_1 on x-axis and w_2, y_3, w_3=0
    +     [if two vertices fixed on x-axis, can rotate the other two in yzw.]
    +    n_0 = det3_(...) = y_2 det2_(z_1, w_1, z_3, w_3) = - y_2 w_1 z_3
    +     [all other terms contain at least two factors nearly zero.]
    +    The max error for n_0 is sqrt(4) u M_a M_d M_d / norm
    +    Then minnorm = 2 u M_a M_d M_d M_d / qh.ONEmerge
    +    Note that qh.one_merge is approx. 82 u M_a and norm is usually about M_d M_d M_d
    +*/
    +void qh_sethyperplane_det(qhT *qh, int dim, coordT **rows, coordT *point0,
    +          boolT toporient, coordT *normal, realT *offset, boolT *nearzero) {
    +  realT maxround, dist;
    +  int i;
    +  pointT *point;
    +
    +
    +  if (dim == 2) {
    +    normal[0]= dY(1,0);
    +    normal[1]= dX(0,1);
    +    qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0]+point0[1]*normal[1]);
    +    *nearzero= False;  /* since nearzero norm => incident points */
    +  }else if (dim == 3) {
    +    normal[0]= det2_(dY(2,0), dZ(2,0),
    +                     dY(1,0), dZ(1,0));
    +    normal[1]= det2_(dX(1,0), dZ(1,0),
    +                     dX(2,0), dZ(2,0));
    +    normal[2]= det2_(dX(2,0), dY(2,0),
    +                     dX(1,0), dY(1,0));
    +    qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0] + point0[1]*normal[1]
    +               + point0[2]*normal[2]);
    +    maxround= qh->DISTround;
    +    for (i=dim; i--; ) {
    +      point= rows[i];
    +      if (point != point0) {
    +        dist= *offset + (point[0]*normal[0] + point[1]*normal[1]
    +               + point[2]*normal[2]);
    +        if (dist > maxround || dist < -maxround) {
    +          *nearzero= True;
    +          break;
    +        }
    +      }
    +    }
    +  }else if (dim == 4) {
    +    normal[0]= - det3_(dY(2,0), dZ(2,0), dW(2,0),
    +                        dY(1,0), dZ(1,0), dW(1,0),
    +                        dY(3,0), dZ(3,0), dW(3,0));
    +    normal[1]=   det3_(dX(2,0), dZ(2,0), dW(2,0),
    +                        dX(1,0), dZ(1,0), dW(1,0),
    +                        dX(3,0), dZ(3,0), dW(3,0));
    +    normal[2]= - det3_(dX(2,0), dY(2,0), dW(2,0),
    +                        dX(1,0), dY(1,0), dW(1,0),
    +                        dX(3,0), dY(3,0), dW(3,0));
    +    normal[3]=   det3_(dX(2,0), dY(2,0), dZ(2,0),
    +                        dX(1,0), dY(1,0), dZ(1,0),
    +                        dX(3,0), dY(3,0), dZ(3,0));
    +    qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0] + point0[1]*normal[1]
    +               + point0[2]*normal[2] + point0[3]*normal[3]);
    +    maxround= qh->DISTround;
    +    for (i=dim; i--; ) {
    +      point= rows[i];
    +      if (point != point0) {
    +        dist= *offset + (point[0]*normal[0] + point[1]*normal[1]
    +               + point[2]*normal[2] + point[3]*normal[3]);
    +        if (dist > maxround || dist < -maxround) {
    +          *nearzero= True;
    +          break;
    +        }
    +      }
    +    }
    +  }
    +  if (*nearzero) {
    +    zzinc_(Zminnorm);
    +    trace0((qh, qh->ferr, 3, "qh_sethyperplane_det: degenerate norm during p%d.\n", qh->furthest_id));
    +    zzinc_(Znearlysingular);
    +  }
    +} /* sethyperplane_det */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethyperplane_gauss(qh, dim, rows, point0, toporient, normal, offset, nearzero )
    +    given(dim-1) X dim array of rows[i]= V_{i+1} - V_0 (point0)
    +    set normalized hyperplane equation from oriented simplex
    +
    +  returns:
    +    normal (normalized)
    +    offset (places point0 on the hyperplane)
    +
    +  notes:
    +    if nearzero
    +      orientation may be incorrect because of incorrect sign flips in gausselim
    +    solves [V_n-V_0,...,V_1-V_0, 0 .. 0 1] * N == [0 .. 0 1]
    +        or [V_n-V_0,...,V_1-V_0, 0 .. 0 1] * N == [0]
    +    i.e., N is normal to the hyperplane, and the unnormalized
    +        distance to [0 .. 1] is either 1 or   0
    +
    +  design:
    +    perform gaussian elimination
    +    flip sign for negative values
    +    perform back substitution
    +    normalize result
    +    compute offset
    +*/
    +void qh_sethyperplane_gauss(qhT *qh, int dim, coordT **rows, pointT *point0,
    +                boolT toporient, coordT *normal, coordT *offset, boolT *nearzero) {
    +  coordT *pointcoord, *normalcoef;
    +  int k;
    +  boolT sign= toporient, nearzero2= False;
    +
    +  qh_gausselim(qh, rows, dim-1, dim, &sign, nearzero);
    +  for (k=dim-1; k--; ) {
    +    if ((rows[k])[k] < 0)
    +      sign ^= 1;
    +  }
    +  if (*nearzero) {
    +    zzinc_(Znearlysingular);
    +    trace0((qh, qh->ferr, 4, "qh_sethyperplane_gauss: nearly singular or axis parallel hyperplane during p%d.\n", qh->furthest_id));
    +    qh_backnormal(qh, rows, dim-1, dim, sign, normal, &nearzero2);
    +  }else {
    +    qh_backnormal(qh, rows, dim-1, dim, sign, normal, &nearzero2);
    +    if (nearzero2) {
    +      zzinc_(Znearlysingular);
    +      trace0((qh, qh->ferr, 5, "qh_sethyperplane_gauss: singular or axis parallel hyperplane at normalization during p%d.\n", qh->furthest_id));
    +    }
    +  }
    +  if (nearzero2)
    +    *nearzero= True;
    +  qh_normalize2(qh, normal, dim, True, NULL, NULL);
    +  pointcoord= point0;
    +  normalcoef= normal;
    +  *offset= -(*pointcoord++ * *normalcoef++);
    +  for (k=dim-1; k--; )
    +    *offset -= *pointcoord++ * *normalcoef++;
    +} /* sethyperplane_gauss */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/geom_r.h b/xs/src/qhull/src/libqhull_r/geom_r.h
    new file mode 100644
    index 0000000000..d73e953453
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/geom_r.h
    @@ -0,0 +1,184 @@
    +/*
      ---------------------------------
    +
    +  geom_r.h
    +    header file for geometric routines
    +
    +   see qh-geom_r.htm and geom_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/geom_r.h#3 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFgeom
    +#define qhDEFgeom 1
    +
    +#include "libqhull_r.h"
    +
    +/* ============ -macros- ======================== */
    +
    +/*----------------------------------
    +
    +  fabs_(a)
    +    returns the absolute value of a
    +*/
    +#define fabs_( a ) ((( a ) < 0 ) ? -( a ):( a ))
    +
    +/*----------------------------------
    +
    +  fmax_(a,b)
    +    returns the maximum value of a and b
    +*/
    +#define fmax_( a,b )  ( ( a ) < ( b ) ? ( b ) : ( a ) )
    +
    +/*----------------------------------
    +
    +  fmin_(a,b)
    +    returns the minimum value of a and b
    +*/
    +#define fmin_( a,b )  ( ( a ) > ( b ) ? ( b ) : ( a ) )
    +
    +/*----------------------------------
    +
    +  maximize_(maxval, val)
    +    set maxval to val if val is greater than maxval
    +*/
    +#define maximize_( maxval, val ) { if (( maxval ) < ( val )) ( maxval )= ( val ); }
    +
    +/*----------------------------------
    +
    +  minimize_(minval, val)
    +    set minval to val if val is less than minval
    +*/
    +#define minimize_( minval, val ) { if (( minval ) > ( val )) ( minval )= ( val ); }
    +
    +/*----------------------------------
    +
    +  det2_(a1, a2,
    +        b1, b2)
    +
    +    compute a 2-d determinate
    +*/
    +#define det2_( a1,a2,b1,b2 ) (( a1 )*( b2 ) - ( a2 )*( b1 ))
    +
    +/*----------------------------------
    +
    +  det3_(a1, a2, a3,
    +       b1, b2, b3,
    +       c1, c2, c3)
    +
    +    compute a 3-d determinate
    +*/
    +#define det3_( a1,a2,a3,b1,b2,b3,c1,c2,c3 ) ( ( a1 )*det2_( b2,b3,c2,c3 ) \
    +                - ( b1 )*det2_( a2,a3,c2,c3 ) + ( c1 )*det2_( a2,a3,b2,b3 ) )
    +
    +/*----------------------------------
    +
    +  dX( p1, p2 )
    +  dY( p1, p2 )
    +  dZ( p1, p2 )
    +
    +    given two indices into rows[],
    +
    +    compute the difference between X, Y, or Z coordinates
    +*/
    +#define dX( p1,p2 )  ( *( rows[p1] ) - *( rows[p2] ))
    +#define dY( p1,p2 )  ( *( rows[p1]+1 ) - *( rows[p2]+1 ))
    +#define dZ( p1,p2 )  ( *( rows[p1]+2 ) - *( rows[p2]+2 ))
    +#define dW( p1,p2 )  ( *( rows[p1]+3 ) - *( rows[p2]+3 ))
    +
    +/*============= prototypes in alphabetical order, infrequent at end ======= */
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_backnormal(qhT *qh, realT **rows, int numrow, int numcol, boolT sign, coordT *normal, boolT *nearzero);
    +void    qh_distplane(qhT *qh, pointT *point, facetT *facet, realT *dist);
    +facetT *qh_findbest(qhT *qh, pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT isnewfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart);
    +facetT *qh_findbesthorizon(qhT *qh, boolT ischeckmax, pointT *point,
    +                     facetT *startfacet, boolT noupper, realT *bestdist, int *numpart);
    +facetT *qh_findbestnew(qhT *qh, pointT *point, facetT *startfacet, realT *dist,
    +                     boolT bestoutside, boolT *isoutside, int *numpart);
    +void    qh_gausselim(qhT *qh, realT **rows, int numrow, int numcol, boolT *sign, boolT *nearzero);
    +realT   qh_getangle(qhT *qh, pointT *vect1, pointT *vect2);
    +pointT *qh_getcenter(qhT *qh, setT *vertices);
    +pointT *qh_getcentrum(qhT *qh, facetT *facet);
    +realT   qh_getdistance(qhT *qh, facetT *facet, facetT *neighbor, realT *mindist, realT *maxdist);
    +void    qh_normalize(qhT *qh, coordT *normal, int dim, boolT toporient);
    +void    qh_normalize2(qhT *qh, coordT *normal, int dim, boolT toporient,
    +            realT *minnorm, boolT *ismin);
    +pointT *qh_projectpoint(qhT *qh, pointT *point, facetT *facet, realT dist);
    +
    +void    qh_setfacetplane(qhT *qh, facetT *newfacets);
    +void    qh_sethyperplane_det(qhT *qh, int dim, coordT **rows, coordT *point0,
    +              boolT toporient, coordT *normal, realT *offset, boolT *nearzero);
    +void    qh_sethyperplane_gauss(qhT *qh, int dim, coordT **rows, pointT *point0,
    +             boolT toporient, coordT *normal, coordT *offset, boolT *nearzero);
    +boolT   qh_sharpnewfacets(qhT *qh);
    +
    +/*========= infrequently used code in geom2_r.c =============*/
    +
    +coordT *qh_copypoints(qhT *qh, coordT *points, int numpoints, int dimension);
    +void    qh_crossproduct(int dim, realT vecA[3], realT vecB[3], realT vecC[3]);
    +realT   qh_determinant(qhT *qh, realT **rows, int dim, boolT *nearzero);
    +realT   qh_detjoggle(qhT *qh, pointT *points, int numpoints, int dimension);
    +void    qh_detroundoff(qhT *qh);
    +realT   qh_detsimplex(qhT *qh, pointT *apex, setT *points, int dim, boolT *nearzero);
    +realT   qh_distnorm(int dim, pointT *point, pointT *normal, realT *offsetp);
    +realT   qh_distround(qhT *qh, int dimension, realT maxabs, realT maxsumabs);
    +realT   qh_divzero(realT numer, realT denom, realT mindenom1, boolT *zerodiv);
    +realT   qh_facetarea(qhT *qh, facetT *facet);
    +realT   qh_facetarea_simplex(qhT *qh, int dim, coordT *apex, setT *vertices,
    +          vertexT *notvertex,  boolT toporient, coordT *normal, realT *offset);
    +pointT *qh_facetcenter(qhT *qh, setT *vertices);
    +facetT *qh_findgooddist(qhT *qh, pointT *point, facetT *facetA, realT *distp, facetT **facetlist);
    +void    qh_getarea(qhT *qh, facetT *facetlist);
    +boolT   qh_gram_schmidt(qhT *qh, int dim, realT **rows);
    +boolT   qh_inthresholds(qhT *qh, coordT *normal, realT *angle);
    +void    qh_joggleinput(qhT *qh);
    +realT  *qh_maxabsval(realT *normal, int dim);
    +setT   *qh_maxmin(qhT *qh, pointT *points, int numpoints, int dimension);
    +realT   qh_maxouter(qhT *qh);
    +void    qh_maxsimplex(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints, setT **simplex);
    +realT   qh_minabsval(realT *normal, int dim);
    +int     qh_mindiff(realT *vecA, realT *vecB, int dim);
    +boolT   qh_orientoutside(qhT *qh, facetT *facet);
    +void    qh_outerinner(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane);
    +coordT  qh_pointdist(pointT *point1, pointT *point2, int dim);
    +void    qh_printmatrix(qhT *qh, FILE *fp, const char *string, realT **rows, int numrow, int numcol);
    +void    qh_printpoints(qhT *qh, FILE *fp, const char *string, setT *points);
    +void    qh_projectinput(qhT *qh);
    +void    qh_projectpoints(qhT *qh, signed char *project, int n, realT *points,
    +             int numpoints, int dim, realT *newpoints, int newdim);
    +void    qh_rotateinput(qhT *qh, realT **rows);
    +void    qh_rotatepoints(qhT *qh, realT *points, int numpoints, int dim, realT **rows);
    +void    qh_scaleinput(qhT *qh);
    +void    qh_scalelast(qhT *qh, coordT *points, int numpoints, int dim, coordT low,
    +                   coordT high, coordT newhigh);
    +void    qh_scalepoints(qhT *qh, pointT *points, int numpoints, int dim,
    +                realT *newlows, realT *newhighs);
    +boolT   qh_sethalfspace(qhT *qh, int dim, coordT *coords, coordT **nextp,
    +              coordT *normal, coordT *offset, coordT *feasible);
    +coordT *qh_sethalfspace_all(qhT *qh, int dim, int count, coordT *halfspaces, pointT *feasible);
    +pointT *qh_voronoi_center(qhT *qh, int dim, setT *points);
    +
    +#ifdef __cplusplus
    +} /* extern "C"*/
    +#endif
    +
    +#endif /* qhDEFgeom */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/global_r.c b/xs/src/qhull/src/libqhull_r/global_r.c
    new file mode 100644
    index 0000000000..eef465ca14
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/global_r.c
    @@ -0,0 +1,2100 @@
    +
    +/*
      ---------------------------------
    +
    +   global_r.c
    +   initializes all the globals of the qhull application
    +
    +   see README
    +
    +   see libqhull_r.h for qh.globals and function prototypes
    +
    +   see qhull_ra.h for internal functions
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/global_r.c#16 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    + */
    +
    +#include "qhull_ra.h"
    +
    +/*========= qh->definition -- globals defined in libqhull_r.h =======================*/
    +
    +/*----------------------------------
    +
    +  qh_version
    +    version string by year and date
    +    qh_version2 for Unix users and -V
    +
    +    the revision increases on code changes only
    +
    +  notes:
    +    change date:    Changes.txt, Announce.txt, index.htm, README.txt,
    +                    qhull-news.html, Eudora signatures, CMakeLists.txt
    +    change version: README.txt, qh-get.htm, File_id.diz, Makefile.txt, CMakeLists.txt
    +    check that CmakeLists @version is the same as qh_version2
    +    change year:    Copying.txt
    +    check download size
    +    recompile user_eg_r.c, rbox_r.c, libqhull_r.c, qconvex_r.c, qdelaun_r.c qvoronoi_r.c, qhalf_r.c, testqset_r.c
    +*/
    +
    +const char qh_version[]= "2015.2.r 2016/01/18";
    +const char qh_version2[]= "qhull_r 7.2.0 (2015.2.r 2016/01/18)";
    +
    +/*---------------------------------
    +
    +  qh_appendprint(qh, printFormat )
    +    append printFormat to qh.PRINTout unless already defined
    +*/
    +void qh_appendprint(qhT *qh, qh_PRINT format) {
    +  int i;
    +
    +  for (i=0; i < qh_PRINTEND; i++) {
    +    if (qh->PRINTout[i] == format && format != qh_PRINTqhull)
    +      break;
    +    if (!qh->PRINTout[i]) {
    +      qh->PRINTout[i]= format;
    +      break;
    +    }
    +  }
    +} /* appendprint */
    +
    +/*---------------------------------
    +
    +  qh_checkflags(qh, commandStr, hiddenFlags )
    +    errors if commandStr contains hiddenFlags
    +    hiddenFlags starts and ends with a space and is space delimited (checked)
    +
    +  notes:
    +    ignores first word (e.g., "qconvex i")
    +    use qh_strtol/strtod since strtol/strtod may or may not skip trailing spaces
    +
    +  see:
    +    qh_initflags() initializes Qhull according to commandStr
    +*/
    +void qh_checkflags(qhT *qh, char *command, char *hiddenflags) {
    +  char *s= command, *t, *chkerr; /* qh_skipfilename is non-const */
    +  char key, opt, prevopt;
    +  char chkkey[]= "   ";
    +  char chkopt[]=  "    ";
    +  char chkopt2[]= "     ";
    +  boolT waserr= False;
    +
    +  if (*hiddenflags != ' ' || hiddenflags[strlen(hiddenflags)-1] != ' ') {
    +    qh_fprintf(qh, qh->ferr, 6026, "qhull error (qh_checkflags): hiddenflags must start and end with a space: \"%s\"", hiddenflags);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (strpbrk(hiddenflags, ",\n\r\t")) {
    +    qh_fprintf(qh, qh->ferr, 6027, "qhull error (qh_checkflags): hiddenflags contains commas, newlines, or tabs: \"%s\"", hiddenflags);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  while (*s && !isspace(*s))  /* skip program name */
    +    s++;
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    key = *s++;
    +    chkerr = NULL;
    +    if (key == 'T' && (*s == 'I' || *s == 'O')) {  /* TI or TO 'file name' */
    +      s= qh_skipfilename(qh, ++s);
    +      continue;
    +    }
    +    chkkey[1]= key;
    +    if (strstr(hiddenflags, chkkey)) {
    +      chkerr= chkkey;
    +    }else if (isupper(key)) {
    +      opt= ' ';
    +      prevopt= ' ';
    +      chkopt[1]= key;
    +      chkopt2[1]= key;
    +      while (!chkerr && *s && !isspace(*s)) {
    +        opt= *s++;
    +        if (isalpha(opt)) {
    +          chkopt[2]= opt;
    +          if (strstr(hiddenflags, chkopt))
    +            chkerr= chkopt;
    +          if (prevopt != ' ') {
    +            chkopt2[2]= prevopt;
    +            chkopt2[3]= opt;
    +            if (strstr(hiddenflags, chkopt2))
    +              chkerr= chkopt2;
    +          }
    +        }else if (key == 'Q' && isdigit(opt) && prevopt != 'b'
    +              && (prevopt == ' ' || islower(prevopt))) {
    +            chkopt[2]= opt;
    +            if (strstr(hiddenflags, chkopt))
    +              chkerr= chkopt;
    +        }else {
    +          qh_strtod(s-1, &t);
    +          if (s < t)
    +            s= t;
    +        }
    +        prevopt= opt;
    +      }
    +    }
    +    if (chkerr) {
    +      *chkerr= '\'';
    +      chkerr[strlen(chkerr)-1]=  '\'';
    +      qh_fprintf(qh, qh->ferr, 6029, "qhull error: option %s is not used with this program.\n             It may be used with qhull.\n", chkerr);
    +      waserr= True;
    +    }
    +  }
    +  if (waserr)
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +} /* checkflags */
    +
    +/*---------------------------------
    +
    +  qh_clear_outputflags(qh)
    +    Clear output flags for QhullPoints
    +*/
    +void qh_clear_outputflags(qhT *qh) {
    +  int i,k;
    +
    +  qh->ANNOTATEoutput= False;
    +  qh->DOintersections= False;
    +  qh->DROPdim= -1;
    +  qh->FORCEoutput= False;
    +  qh->GETarea= False;
    +  qh->GOODpoint= 0;
    +  qh->GOODpointp= NULL;
    +  qh->GOODthreshold= False;
    +  qh->GOODvertex= 0;
    +  qh->GOODvertexp= NULL;
    +  qh->IStracing= 0;
    +  qh->KEEParea= False;
    +  qh->KEEPmerge= False;
    +  qh->KEEPminArea= REALmax;
    +  qh->PRINTcentrums= False;
    +  qh->PRINTcoplanar= False;
    +  qh->PRINTdots= False;
    +  qh->PRINTgood= False;
    +  qh->PRINTinner= False;
    +  qh->PRINTneighbors= False;
    +  qh->PRINTnoplanes= False;
    +  qh->PRINToptions1st= False;
    +  qh->PRINTouter= False;
    +  qh->PRINTprecision= True;
    +  qh->PRINTridges= False;
    +  qh->PRINTspheres= False;
    +  qh->PRINTstatistics= False;
    +  qh->PRINTsummary= False;
    +  qh->PRINTtransparent= False;
    +  qh->SPLITthresholds= False;
    +  qh->TRACElevel= 0;
    +  qh->TRInormals= False;
    +  qh->USEstdout= False;
    +  qh->VERIFYoutput= False;
    +  for (k=qh->input_dim+1; k--; ) {  /* duplicated in qh_initqhull_buffers and qh_clear_outputflags */
    +    qh->lower_threshold[k]= -REALmax;
    +    qh->upper_threshold[k]= REALmax;
    +    qh->lower_bound[k]= -REALmax;
    +    qh->upper_bound[k]= REALmax;
    +  }
    +
    +  for (i=0; i < qh_PRINTEND; i++) {
    +    qh->PRINTout[i]= qh_PRINTnone;
    +  }
    +
    +  if (!qh->qhull_commandsiz2)
    +      qh->qhull_commandsiz2= (int)strlen(qh->qhull_command); /* WARN64 */
    +  else {
    +      qh->qhull_command[qh->qhull_commandsiz2]= '\0';
    +  }
    +  if (!qh->qhull_optionsiz2)
    +    qh->qhull_optionsiz2= (int)strlen(qh->qhull_options);  /* WARN64 */
    +  else {
    +    qh->qhull_options[qh->qhull_optionsiz2]= '\0';
    +    qh->qhull_optionlen= qh_OPTIONline;  /* start a new line */
    +  }
    +} /* clear_outputflags */
    +
    +/*---------------------------------
    +
    +  qh_clock()
    +    return user CPU time in 100ths (qh_SECtick)
    +    only defined for qh_CLOCKtype == 2
    +
    +  notes:
    +    use first value to determine time 0
    +    from Stevens '92 8.15
    +*/
    +unsigned long qh_clock(qhT *qh) {
    +
    +#if (qh_CLOCKtype == 2)
    +  struct tms time;
    +  static long clktck;  /* initialized first call and never updated */
    +  double ratio, cpu;
    +  unsigned long ticks;
    +
    +  if (!clktck) {
    +    if ((clktck= sysconf(_SC_CLK_TCK)) < 0) {
    +      qh_fprintf(qh, qh->ferr, 6030, "qhull internal error (qh_clock): sysconf() failed.  Use qh_CLOCKtype 1 in user.h\n");
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +  }
    +  if (times(&time) == -1) {
    +    qh_fprintf(qh, qh->ferr, 6031, "qhull internal error (qh_clock): times() failed.  Use qh_CLOCKtype 1 in user.h\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  ratio= qh_SECticks / (double)clktck;
    +  ticks= time.tms_utime * ratio;
    +  return ticks;
    +#else
    +  qh_fprintf(qh, qh->ferr, 6032, "qhull internal error (qh_clock): use qh_CLOCKtype 2 in user.h\n");
    +  qh_errexit(qh, qh_ERRqhull, NULL, NULL); /* never returns */
    +  return 0;
    +#endif
    +} /* clock */
    +
    +/*---------------------------------
    +
    +  qh_freebuffers()
    +    free up global memory buffers
    +
    +  notes:
    +    must match qh_initbuffers()
    +*/
    +void qh_freebuffers(qhT *qh) {
    +
    +  trace5((qh, qh->ferr, 5001, "qh_freebuffers: freeing up global memory buffers\n"));
    +  /* allocated by qh_initqhull_buffers */
    +  qh_memfree(qh, qh->NEARzero, qh->hull_dim * sizeof(realT));
    +  qh_memfree(qh, qh->lower_threshold, (qh->input_dim+1) * sizeof(realT));
    +  qh_memfree(qh, qh->upper_threshold, (qh->input_dim+1) * sizeof(realT));
    +  qh_memfree(qh, qh->lower_bound, (qh->input_dim+1) * sizeof(realT));
    +  qh_memfree(qh, qh->upper_bound, (qh->input_dim+1) * sizeof(realT));
    +  qh_memfree(qh, qh->gm_matrix, (qh->hull_dim+1) * qh->hull_dim * sizeof(coordT));
    +  qh_memfree(qh, qh->gm_row, (qh->hull_dim+1) * sizeof(coordT *));
    +  qh->NEARzero= qh->lower_threshold= qh->upper_threshold= NULL;
    +  qh->lower_bound= qh->upper_bound= NULL;
    +  qh->gm_matrix= NULL;
    +  qh->gm_row= NULL;
    +  qh_setfree(qh, &qh->other_points);
    +  qh_setfree(qh, &qh->del_vertices);
    +  qh_setfree(qh, &qh->coplanarfacetset);
    +  if (qh->line)                /* allocated by qh_readinput, freed if no error */
    +    qh_free(qh->line);
    +  if (qh->half_space)
    +    qh_free(qh->half_space);
    +  if (qh->temp_malloc)
    +    qh_free(qh->temp_malloc);
    +  if (qh->feasible_point)      /* allocated by qh_readfeasible */
    +    qh_free(qh->feasible_point);
    +  if (qh->feasible_string)     /* allocated by qh_initflags */
    +    qh_free(qh->feasible_string);
    +  qh->line= qh->feasible_string= NULL;
    +  qh->half_space= qh->feasible_point= qh->temp_malloc= NULL;
    +  /* usually allocated by qh_readinput */
    +  if (qh->first_point && qh->POINTSmalloc) {
    +    qh_free(qh->first_point);
    +    qh->first_point= NULL;
    +  }
    +  if (qh->input_points && qh->input_malloc) { /* set by qh_joggleinput */
    +    qh_free(qh->input_points);
    +    qh->input_points= NULL;
    +  }
    +  trace5((qh, qh->ferr, 5002, "qh_freebuffers: finished\n"));
    +} /* freebuffers */
    +
    +
    +/*---------------------------------
    +
    +  qh_freebuild(qh, allmem )
    +    free global memory used by qh_initbuild and qh_buildhull
    +    if !allmem,
    +      does not free short memory (e.g., facetT, freed by qh_memfreeshort)
    +
    +  design:
    +    free centrums
    +    free each vertex
    +    mark unattached ridges
    +    for each facet
    +      free ridges
    +      free outside set, coplanar set, neighbor set, ridge set, vertex set
    +      free facet
    +    free hash table
    +    free interior point
    +    free merge set
    +    free temporary sets
    +*/
    +void qh_freebuild(qhT *qh, boolT allmem) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  ridgeT *ridge, **ridgep;
    +  mergeT *merge, **mergep;
    +
    +  trace1((qh, qh->ferr, 1005, "qh_freebuild: free memory from qh_inithull and qh_buildhull\n"));
    +  if (qh->del_vertices)
    +    qh_settruncate(qh, qh->del_vertices, 0);
    +  if (allmem) {
    +    while ((vertex= qh->vertex_list)) {
    +      if (vertex->next)
    +        qh_delvertex(qh, vertex);
    +      else {
    +        qh_memfree(qh, vertex, (int)sizeof(vertexT));
    +        qh->newvertex_list= qh->vertex_list= NULL;
    +      }
    +    }
    +  }else if (qh->VERTEXneighbors) {
    +    FORALLvertices
    +      qh_setfreelong(qh, &(vertex->neighbors));
    +  }
    +  qh->VERTEXneighbors= False;
    +  qh->GOODclosest= NULL;
    +  if (allmem) {
    +    FORALLfacets {
    +      FOREACHridge_(facet->ridges)
    +        ridge->seen= False;
    +    }
    +    FORALLfacets {
    +      if (facet->visible) {
    +        FOREACHridge_(facet->ridges) {
    +          if (!otherfacet_(ridge, facet)->visible)
    +            ridge->seen= True;  /* an unattached ridge */
    +        }
    +      }
    +    }
    +    while ((facet= qh->facet_list)) {
    +      FOREACHridge_(facet->ridges) {
    +        if (ridge->seen) {
    +          qh_setfree(qh, &(ridge->vertices));
    +          qh_memfree(qh, ridge, (int)sizeof(ridgeT));
    +        }else
    +          ridge->seen= True;
    +      }
    +      qh_setfree(qh, &(facet->outsideset));
    +      qh_setfree(qh, &(facet->coplanarset));
    +      qh_setfree(qh, &(facet->neighbors));
    +      qh_setfree(qh, &(facet->ridges));
    +      qh_setfree(qh, &(facet->vertices));
    +      if (facet->next)
    +        qh_delfacet(qh, facet);
    +      else {
    +        qh_memfree(qh, facet, (int)sizeof(facetT));
    +        qh->visible_list= qh->newfacet_list= qh->facet_list= NULL;
    +      }
    +    }
    +  }else {
    +    FORALLfacets {
    +      qh_setfreelong(qh, &(facet->outsideset));
    +      qh_setfreelong(qh, &(facet->coplanarset));
    +      if (!facet->simplicial) {
    +        qh_setfreelong(qh, &(facet->neighbors));
    +        qh_setfreelong(qh, &(facet->ridges));
    +        qh_setfreelong(qh, &(facet->vertices));
    +      }
    +    }
    +  }
    +  qh_setfree(qh, &(qh->hash_table));
    +  qh_memfree(qh, qh->interior_point, qh->normal_size);
    +  qh->interior_point= NULL;
    +  FOREACHmerge_(qh->facet_mergeset)  /* usually empty */
    +    qh_memfree(qh, merge, (int)sizeof(mergeT));
    +  qh->facet_mergeset= NULL;  /* temp set */
    +  qh->degen_mergeset= NULL;  /* temp set */
    +  qh_settempfree_all(qh);
    +} /* freebuild */
    +
    +/*---------------------------------
    +
    +  qh_freeqhull(qh, allmem )
    +
    +  free global memory and set qhT to 0
    +  if !allmem,
    +    does not free short memory (freed by qh_memfreeshort unless qh_NOmem)
    +
    +notes:
    +  sets qh.NOerrexit in case caller forgets to
    +  Does not throw errors
    +
    +see:
    +  see qh_initqhull_start2()
    +  For libqhull_r, qhstatT is part of qhT
    +
    +design:
    +  free global and temporary memory from qh_initbuild and qh_buildhull
    +  free buffers
    +*/
    +void qh_freeqhull(qhT *qh, boolT allmem) {
    +
    +  qh->NOerrexit= True;  /* no more setjmp since called at exit and ~QhullQh */
    +  trace1((qh, qh->ferr, 1006, "qh_freeqhull: free global memory\n"));
    +  qh_freebuild(qh, allmem);
    +  qh_freebuffers(qh);
    +  /* memset is the same in qh_freeqhull() and qh_initqhull_start2() */
    +  memset((char *)qh, 0, sizeof(qhT)-sizeof(qhmemT)-sizeof(qhstatT));
    +  qh->NOerrexit= True;
    +} /* freeqhull2 */
    +
    +/*---------------------------------
    +
    +  qh_init_A(qh, infile, outfile, errfile, argc, argv )
    +    initialize memory and stdio files
    +    convert input options to option string (qh.qhull_command)
    +
    +  notes:
    +    infile may be NULL if qh_readpoints() is not called
    +
    +    errfile should always be defined.  It is used for reporting
    +    errors.  outfile is used for output and format options.
    +
    +    argc/argv may be 0/NULL
    +
    +    called before error handling initialized
    +    qh_errexit() may not be used
    +*/
    +void qh_init_A(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile, int argc, char *argv[]) {
    +  qh_meminit(qh, errfile);
    +  qh_initqhull_start(qh, infile, outfile, errfile);
    +  qh_init_qhull_command(qh, argc, argv);
    +} /* init_A */
    +
    +/*---------------------------------
    +
    +  qh_init_B(qh, points, numpoints, dim, ismalloc )
    +    initialize globals for points array
    +
    +    points has numpoints dim-dimensional points
    +      points[0] is the first coordinate of the first point
    +      points[1] is the second coordinate of the first point
    +      points[dim] is the first coordinate of the second point
    +
    +    ismalloc=True
    +      Qhull will call qh_free(points) on exit or input transformation
    +    ismalloc=False
    +      Qhull will allocate a new point array if needed for input transformation
    +
    +    qh.qhull_command
    +      is the option string.
    +      It is defined by qh_init_B(), qh_qhull_command(), or qh_initflags
    +
    +  returns:
    +    if qh.PROJECTinput or (qh.DELAUNAY and qh.PROJECTdelaunay)
    +      projects the input to a new point array
    +
    +        if qh.DELAUNAY,
    +          qh.hull_dim is increased by one
    +        if qh.ATinfinity,
    +          qh_projectinput adds point-at-infinity for Delaunay tri.
    +
    +    if qh.SCALEinput
    +      changes the upper and lower bounds of the input, see qh_scaleinput(qh)
    +
    +    if qh.ROTATEinput
    +      rotates the input by a random rotation, see qh_rotateinput()
    +      if qh.DELAUNAY
    +        rotates about the last coordinate
    +
    +  notes:
    +    called after points are defined
    +    qh_errexit() may be used
    +*/
    +void qh_init_B(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc) {
    +  qh_initqhull_globals(qh, points, numpoints, dim, ismalloc);
    +  if (qh->qhmem.LASTsize == 0)
    +    qh_initqhull_mem(qh);
    +  /* mem_r.c and qset_r.c are initialized */
    +  qh_initqhull_buffers(qh);
    +  qh_initthresholds(qh, qh->qhull_command);
    +  if (qh->PROJECTinput || (qh->DELAUNAY && qh->PROJECTdelaunay))
    +    qh_projectinput(qh);
    +  if (qh->SCALEinput)
    +    qh_scaleinput(qh);
    +  if (qh->ROTATErandom >= 0) {
    +    qh_randommatrix(qh, qh->gm_matrix, qh->hull_dim, qh->gm_row);
    +    if (qh->DELAUNAY) {
    +      int k, lastk= qh->hull_dim-1;
    +      for (k=0; k < lastk; k++) {
    +        qh->gm_row[k][lastk]= 0.0;
    +        qh->gm_row[lastk][k]= 0.0;
    +      }
    +      qh->gm_row[lastk][lastk]= 1.0;
    +    }
    +    qh_gram_schmidt(qh, qh->hull_dim, qh->gm_row);
    +    qh_rotateinput(qh, qh->gm_row);
    +  }
    +} /* init_B */
    +
    +/*---------------------------------
    +
    +  qh_init_qhull_command(qh, argc, argv )
    +    build qh.qhull_command from argc/argv
    +    Calls qh_exit if qhull_command is too short
    +
    +  returns:
    +    a space-delimited string of options (just as typed)
    +
    +  notes:
    +    makes option string easy to input and output
    +
    +    argc/argv may be 0/NULL
    +*/
    +void qh_init_qhull_command(qhT *qh, int argc, char *argv[]) {
    +
    +  if (!qh_argv_to_command(argc, argv, qh->qhull_command, (int)sizeof(qh->qhull_command))){
    +    /* Assumes qh.ferr is defined. */
    +    qh_fprintf(qh, qh->ferr, 6033, "qhull input error: more than %d characters in command line.\n",
    +          (int)sizeof(qh->qhull_command));
    +    qh_exit(qh_ERRinput);  /* error reported, can not use qh_errexit */
    +  }
    +} /* init_qhull_command */
    +
    +/*---------------------------------
    +
    +  qh_initflags(qh, commandStr )
    +    set flags and initialized constants from commandStr
    +    calls qh_exit() if qh->NOerrexit
    +
    +  returns:
    +    sets qh.qhull_command to command if needed
    +
    +  notes:
    +    ignores first word (e.g., "qhull d")
    +    use qh_strtol/strtod since strtol/strtod may or may not skip trailing spaces
    +
    +  see:
    +    qh_initthresholds() continues processing of 'Pdn' and 'PDn'
    +    'prompt' in unix_r.c for documentation
    +
    +  design:
    +    for each space-delimited option group
    +      if top-level option
    +        check syntax
    +        append appropriate option to option string
    +        set appropriate global variable or append printFormat to print options
    +      else
    +        for each sub-option
    +          check syntax
    +          append appropriate option to option string
    +          set appropriate global variable or append printFormat to print options
    +*/
    +void qh_initflags(qhT *qh, char *command) {
    +  int k, i, lastproject;
    +  char *s= command, *t, *prev_s, *start, key;
    +  boolT isgeom= False, wasproject;
    +  realT r;
    +
    +  if(qh->NOerrexit){
    +    qh_fprintf(qh, qh->ferr, 6245, "qhull initflags error: qh.NOerrexit was not cleared before calling qh_initflags().  It should be cleared after setjmp().  Exit qhull.");
    +    qh_exit(6245);
    +  }
    +  if (command <= &qh->qhull_command[0] || command > &qh->qhull_command[0] + sizeof(qh->qhull_command)) {
    +    if (command != &qh->qhull_command[0]) {
    +      *qh->qhull_command= '\0';
    +      strncat(qh->qhull_command, command, sizeof(qh->qhull_command)-strlen(qh->qhull_command)-1);
    +    }
    +    while (*s && !isspace(*s))  /* skip program name */
    +      s++;
    +  }
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    prev_s= s;
    +    switch (*s++) {
    +    case 'd':
    +      qh_option(qh, "delaunay", NULL, NULL);
    +      qh->DELAUNAY= True;
    +      break;
    +    case 'f':
    +      qh_option(qh, "facets", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTfacets);
    +      break;
    +    case 'i':
    +      qh_option(qh, "incidence", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTincidences);
    +      break;
    +    case 'm':
    +      qh_option(qh, "mathematica", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTmathematica);
    +      break;
    +    case 'n':
    +      qh_option(qh, "normals", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTnormals);
    +      break;
    +    case 'o':
    +      qh_option(qh, "offFile", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINToff);
    +      break;
    +    case 'p':
    +      qh_option(qh, "points", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTpoints);
    +      break;
    +    case 's':
    +      qh_option(qh, "summary", NULL, NULL);
    +      qh->PRINTsummary= True;
    +      break;
    +    case 'v':
    +      qh_option(qh, "voronoi", NULL, NULL);
    +      qh->VORONOI= True;
    +      qh->DELAUNAY= True;
    +      break;
    +    case 'A':
    +      if (!isdigit(*s) && *s != '.' && *s != '-')
    +        qh_fprintf(qh, qh->ferr, 7002, "qhull warning: no maximum cosine angle given for option 'An'.  Ignored.\n");
    +      else {
    +        if (*s == '-') {
    +          qh->premerge_cos= -qh_strtod(s, &s);
    +          qh_option(qh, "Angle-premerge-", NULL, &qh->premerge_cos);
    +          qh->PREmerge= True;
    +        }else {
    +          qh->postmerge_cos= qh_strtod(s, &s);
    +          qh_option(qh, "Angle-postmerge", NULL, &qh->postmerge_cos);
    +          qh->POSTmerge= True;
    +        }
    +        qh->MERGING= True;
    +      }
    +      break;
    +    case 'C':
    +      if (!isdigit(*s) && *s != '.' && *s != '-')
    +        qh_fprintf(qh, qh->ferr, 7003, "qhull warning: no centrum radius given for option 'Cn'.  Ignored.\n");
    +      else {
    +        if (*s == '-') {
    +          qh->premerge_centrum= -qh_strtod(s, &s);
    +          qh_option(qh, "Centrum-premerge-", NULL, &qh->premerge_centrum);
    +          qh->PREmerge= True;
    +        }else {
    +          qh->postmerge_centrum= qh_strtod(s, &s);
    +          qh_option(qh, "Centrum-postmerge", NULL, &qh->postmerge_centrum);
    +          qh->POSTmerge= True;
    +        }
    +        qh->MERGING= True;
    +      }
    +      break;
    +    case 'E':
    +      if (*s == '-')
    +        qh_fprintf(qh, qh->ferr, 7004, "qhull warning: negative maximum roundoff given for option 'An'.  Ignored.\n");
    +      else if (!isdigit(*s))
    +        qh_fprintf(qh, qh->ferr, 7005, "qhull warning: no maximum roundoff given for option 'En'.  Ignored.\n");
    +      else {
    +        qh->DISTround= qh_strtod(s, &s);
    +        qh_option(qh, "Distance-roundoff", NULL, &qh->DISTround);
    +        qh->SETroundoff= True;
    +      }
    +      break;
    +    case 'H':
    +      start= s;
    +      qh->HALFspace= True;
    +      qh_strtod(s, &t);
    +      while (t > s)  {
    +        if (*t && !isspace(*t)) {
    +          if (*t == ',')
    +            t++;
    +          else
    +            qh_fprintf(qh, qh->ferr, 7006, "qhull warning: origin for Halfspace intersection should be 'Hn,n,n,...'\n");
    +        }
    +        s= t;
    +        qh_strtod(s, &t);
    +      }
    +      if (start < t) {
    +        if (!(qh->feasible_string= (char*)calloc((size_t)(t-start+1), (size_t)1))) {
    +          qh_fprintf(qh, qh->ferr, 6034, "qhull error: insufficient memory for 'Hn,n,n'\n");
    +          qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +        }
    +        strncpy(qh->feasible_string, start, (size_t)(t-start));
    +        qh_option(qh, "Halfspace-about", NULL, NULL);
    +        qh_option(qh, qh->feasible_string, NULL, NULL);
    +      }else
    +        qh_option(qh, "Halfspace", NULL, NULL);
    +      break;
    +    case 'R':
    +      if (!isdigit(*s))
    +        qh_fprintf(qh, qh->ferr, 7007, "qhull warning: missing random perturbation for option 'Rn'.  Ignored\n");
    +      else {
    +        qh->RANDOMfactor= qh_strtod(s, &s);
    +        qh_option(qh, "Random_perturb", NULL, &qh->RANDOMfactor);
    +        qh->RANDOMdist= True;
    +      }
    +      break;
    +    case 'V':
    +      if (!isdigit(*s) && *s != '-')
    +        qh_fprintf(qh, qh->ferr, 7008, "qhull warning: missing visible distance for option 'Vn'.  Ignored\n");
    +      else {
    +        qh->MINvisible= qh_strtod(s, &s);
    +        qh_option(qh, "Visible", NULL, &qh->MINvisible);
    +      }
    +      break;
    +    case 'U':
    +      if (!isdigit(*s) && *s != '-')
    +        qh_fprintf(qh, qh->ferr, 7009, "qhull warning: missing coplanar distance for option 'Un'.  Ignored\n");
    +      else {
    +        qh->MAXcoplanar= qh_strtod(s, &s);
    +        qh_option(qh, "U-coplanar", NULL, &qh->MAXcoplanar);
    +      }
    +      break;
    +    case 'W':
    +      if (*s == '-')
    +        qh_fprintf(qh, qh->ferr, 7010, "qhull warning: negative outside width for option 'Wn'.  Ignored.\n");
    +      else if (!isdigit(*s))
    +        qh_fprintf(qh, qh->ferr, 7011, "qhull warning: missing outside width for option 'Wn'.  Ignored\n");
    +      else {
    +        qh->MINoutside= qh_strtod(s, &s);
    +        qh_option(qh, "W-outside", NULL, &qh->MINoutside);
    +        qh->APPROXhull= True;
    +      }
    +      break;
    +    /************  sub menus ***************/
    +    case 'F':
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'a':
    +          qh_option(qh, "Farea", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTarea);
    +          qh->GETarea= True;
    +          break;
    +        case 'A':
    +          qh_option(qh, "FArea-total", NULL, NULL);
    +          qh->GETarea= True;
    +          break;
    +        case 'c':
    +          qh_option(qh, "Fcoplanars", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTcoplanars);
    +          break;
    +        case 'C':
    +          qh_option(qh, "FCentrums", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTcentrums);
    +          break;
    +        case 'd':
    +          qh_option(qh, "Fd-cdd-in", NULL, NULL);
    +          qh->CDDinput= True;
    +          break;
    +        case 'D':
    +          qh_option(qh, "FD-cdd-out", NULL, NULL);
    +          qh->CDDoutput= True;
    +          break;
    +        case 'F':
    +          qh_option(qh, "FFacets-xridge", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTfacets_xridge);
    +          break;
    +        case 'i':
    +          qh_option(qh, "Finner", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTinner);
    +          break;
    +        case 'I':
    +          qh_option(qh, "FIDs", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTids);
    +          break;
    +        case 'm':
    +          qh_option(qh, "Fmerges", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTmerges);
    +          break;
    +        case 'M':
    +          qh_option(qh, "FMaple", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTmaple);
    +          break;
    +        case 'n':
    +          qh_option(qh, "Fneighbors", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTneighbors);
    +          break;
    +        case 'N':
    +          qh_option(qh, "FNeighbors-vertex", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTvneighbors);
    +          break;
    +        case 'o':
    +          qh_option(qh, "Fouter", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTouter);
    +          break;
    +        case 'O':
    +          if (qh->PRINToptions1st) {
    +            qh_option(qh, "FOptions", NULL, NULL);
    +            qh_appendprint(qh, qh_PRINToptions);
    +          }else
    +            qh->PRINToptions1st= True;
    +          break;
    +        case 'p':
    +          qh_option(qh, "Fpoint-intersect", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTpointintersect);
    +          break;
    +        case 'P':
    +          qh_option(qh, "FPoint-nearest", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTpointnearest);
    +          break;
    +        case 'Q':
    +          qh_option(qh, "FQhull", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTqhull);
    +          break;
    +        case 's':
    +          qh_option(qh, "Fsummary", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTsummary);
    +          break;
    +        case 'S':
    +          qh_option(qh, "FSize", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTsize);
    +          qh->GETarea= True;
    +          break;
    +        case 't':
    +          qh_option(qh, "Ftriangles", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTtriangles);
    +          break;
    +        case 'v':
    +          /* option set in qh_initqhull_globals */
    +          qh_appendprint(qh, qh_PRINTvertices);
    +          break;
    +        case 'V':
    +          qh_option(qh, "FVertex-average", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTaverage);
    +          break;
    +        case 'x':
    +          qh_option(qh, "Fxtremes", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTextremes);
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7012, "qhull warning: unknown 'F' output option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'G':
    +      isgeom= True;
    +      qh_appendprint(qh, qh_PRINTgeom);
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'a':
    +          qh_option(qh, "Gall-points", NULL, NULL);
    +          qh->PRINTdots= True;
    +          break;
    +        case 'c':
    +          qh_option(qh, "Gcentrums", NULL, NULL);
    +          qh->PRINTcentrums= True;
    +          break;
    +        case 'h':
    +          qh_option(qh, "Gintersections", NULL, NULL);
    +          qh->DOintersections= True;
    +          break;
    +        case 'i':
    +          qh_option(qh, "Ginner", NULL, NULL);
    +          qh->PRINTinner= True;
    +          break;
    +        case 'n':
    +          qh_option(qh, "Gno-planes", NULL, NULL);
    +          qh->PRINTnoplanes= True;
    +          break;
    +        case 'o':
    +          qh_option(qh, "Gouter", NULL, NULL);
    +          qh->PRINTouter= True;
    +          break;
    +        case 'p':
    +          qh_option(qh, "Gpoints", NULL, NULL);
    +          qh->PRINTcoplanar= True;
    +          break;
    +        case 'r':
    +          qh_option(qh, "Gridges", NULL, NULL);
    +          qh->PRINTridges= True;
    +          break;
    +        case 't':
    +          qh_option(qh, "Gtransparent", NULL, NULL);
    +          qh->PRINTtransparent= True;
    +          break;
    +        case 'v':
    +          qh_option(qh, "Gvertices", NULL, NULL);
    +          qh->PRINTspheres= True;
    +          break;
    +        case 'D':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6035, "qhull input error: missing dimension for option 'GDn'\n");
    +          else {
    +            if (qh->DROPdim >= 0)
    +              qh_fprintf(qh, qh->ferr, 7013, "qhull warning: can only drop one dimension.  Previous 'GD%d' ignored\n",
    +                   qh->DROPdim);
    +            qh->DROPdim= qh_strtol(s, &s);
    +            qh_option(qh, "GDrop-dim", &qh->DROPdim, NULL);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7014, "qhull warning: unknown 'G' print option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'P':
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'd': case 'D':  /* see qh_initthresholds() */
    +          key= s[-1];
    +          i= qh_strtol(s, &s);
    +          r= 0;
    +          if (*s == ':') {
    +            s++;
    +            r= qh_strtod(s, &s);
    +          }
    +          if (key == 'd')
    +            qh_option(qh, "Pdrop-facets-dim-less", &i, &r);
    +          else
    +            qh_option(qh, "PDrop-facets-dim-more", &i, &r);
    +          break;
    +        case 'g':
    +          qh_option(qh, "Pgood-facets", NULL, NULL);
    +          qh->PRINTgood= True;
    +          break;
    +        case 'G':
    +          qh_option(qh, "PGood-facet-neighbors", NULL, NULL);
    +          qh->PRINTneighbors= True;
    +          break;
    +        case 'o':
    +          qh_option(qh, "Poutput-forced", NULL, NULL);
    +          qh->FORCEoutput= True;
    +          break;
    +        case 'p':
    +          qh_option(qh, "Pprecision-ignore", NULL, NULL);
    +          qh->PRINTprecision= False;
    +          break;
    +        case 'A':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6036, "qhull input error: missing facet count for keep area option 'PAn'\n");
    +          else {
    +            qh->KEEParea= qh_strtol(s, &s);
    +            qh_option(qh, "PArea-keep", &qh->KEEParea, NULL);
    +            qh->GETarea= True;
    +          }
    +          break;
    +        case 'F':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6037, "qhull input error: missing facet area for option 'PFn'\n");
    +          else {
    +            qh->KEEPminArea= qh_strtod(s, &s);
    +            qh_option(qh, "PFacet-area-keep", NULL, &qh->KEEPminArea);
    +            qh->GETarea= True;
    +          }
    +          break;
    +        case 'M':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6038, "qhull input error: missing merge count for option 'PMn'\n");
    +          else {
    +            qh->KEEPmerge= qh_strtol(s, &s);
    +            qh_option(qh, "PMerge-keep", &qh->KEEPmerge, NULL);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7015, "qhull warning: unknown 'P' print option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'Q':
    +      lastproject= -1;
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'b': case 'B':  /* handled by qh_initthresholds */
    +          key= s[-1];
    +          if (key == 'b' && *s == 'B') {
    +            s++;
    +            r= qh_DEFAULTbox;
    +            qh->SCALEinput= True;
    +            qh_option(qh, "QbBound-unit-box", NULL, &r);
    +            break;
    +          }
    +          if (key == 'b' && *s == 'b') {
    +            s++;
    +            qh->SCALElast= True;
    +            qh_option(qh, "Qbbound-last", NULL, NULL);
    +            break;
    +          }
    +          k= qh_strtol(s, &s);
    +          r= 0.0;
    +          wasproject= False;
    +          if (*s == ':') {
    +            s++;
    +            if ((r= qh_strtod(s, &s)) == 0.0) {
    +              t= s;            /* need true dimension for memory allocation */
    +              while (*t && !isspace(*t)) {
    +                if (toupper(*t++) == 'B'
    +                 && k == qh_strtol(t, &t)
    +                 && *t++ == ':'
    +                 && qh_strtod(t, &t) == 0.0) {
    +                  qh->PROJECTinput++;
    +                  trace2((qh, qh->ferr, 2004, "qh_initflags: project dimension %d\n", k));
    +                  qh_option(qh, "Qb-project-dim", &k, NULL);
    +                  wasproject= True;
    +                  lastproject= k;
    +                  break;
    +                }
    +              }
    +            }
    +          }
    +          if (!wasproject) {
    +            if (lastproject == k && r == 0.0)
    +              lastproject= -1;  /* doesn't catch all possible sequences */
    +            else if (key == 'b') {
    +              qh->SCALEinput= True;
    +              if (r == 0.0)
    +                r= -qh_DEFAULTbox;
    +              qh_option(qh, "Qbound-dim-low", &k, &r);
    +            }else {
    +              qh->SCALEinput= True;
    +              if (r == 0.0)
    +                r= qh_DEFAULTbox;
    +              qh_option(qh, "QBound-dim-high", &k, &r);
    +            }
    +          }
    +          break;
    +        case 'c':
    +          qh_option(qh, "Qcoplanar-keep", NULL, NULL);
    +          qh->KEEPcoplanar= True;
    +          break;
    +        case 'f':
    +          qh_option(qh, "Qfurthest-outside", NULL, NULL);
    +          qh->BESToutside= True;
    +          break;
    +        case 'g':
    +          qh_option(qh, "Qgood-facets-only", NULL, NULL);
    +          qh->ONLYgood= True;
    +          break;
    +        case 'i':
    +          qh_option(qh, "Qinterior-keep", NULL, NULL);
    +          qh->KEEPinside= True;
    +          break;
    +        case 'm':
    +          qh_option(qh, "Qmax-outside-only", NULL, NULL);
    +          qh->ONLYmax= True;
    +          break;
    +        case 'r':
    +          qh_option(qh, "Qrandom-outside", NULL, NULL);
    +          qh->RANDOMoutside= True;
    +          break;
    +        case 's':
    +          qh_option(qh, "Qsearch-initial-simplex", NULL, NULL);
    +          qh->ALLpoints= True;
    +          break;
    +        case 't':
    +          qh_option(qh, "Qtriangulate", NULL, NULL);
    +          qh->TRIangulate= True;
    +          break;
    +        case 'T':
    +          qh_option(qh, "QTestPoints", NULL, NULL);
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6039, "qhull input error: missing number of test points for option 'QTn'\n");
    +          else {
    +            qh->TESTpoints= qh_strtol(s, &s);
    +            qh_option(qh, "QTestPoints", &qh->TESTpoints, NULL);
    +          }
    +          break;
    +        case 'u':
    +          qh_option(qh, "QupperDelaunay", NULL, NULL);
    +          qh->UPPERdelaunay= True;
    +          break;
    +        case 'v':
    +          qh_option(qh, "Qvertex-neighbors-convex", NULL, NULL);
    +          qh->TESTvneighbors= True;
    +          break;
    +        case 'x':
    +          qh_option(qh, "Qxact-merge", NULL, NULL);
    +          qh->MERGEexact= True;
    +          break;
    +        case 'z':
    +          qh_option(qh, "Qz-infinity-point", NULL, NULL);
    +          qh->ATinfinity= True;
    +          break;
    +        case '0':
    +          qh_option(qh, "Q0-no-premerge", NULL, NULL);
    +          qh->NOpremerge= True;
    +          break;
    +        case '1':
    +          if (!isdigit(*s)) {
    +            qh_option(qh, "Q1-no-angle-sort", NULL, NULL);
    +            qh->ANGLEmerge= False;
    +            break;
    +          }
    +          switch (*s++) {
    +          case '0':
    +            qh_option(qh, "Q10-no-narrow", NULL, NULL);
    +            qh->NOnarrow= True;
    +            break;
    +          case '1':
    +            qh_option(qh, "Q11-trinormals Qtriangulate", NULL, NULL);
    +            qh->TRInormals= True;
    +            qh->TRIangulate= True;
    +            break;
    +          case '2':
    +              qh_option(qh, "Q12-no-wide-dup", NULL, NULL);
    +              qh->NOwide= True;
    +            break;
    +          default:
    +            s--;
    +            qh_fprintf(qh, qh->ferr, 7016, "qhull warning: unknown 'Q' qhull option 1%c, rest ignored\n", (int)s[0]);
    +            while (*++s && !isspace(*s));
    +            break;
    +          }
    +          break;
    +        case '2':
    +          qh_option(qh, "Q2-no-merge-independent", NULL, NULL);
    +          qh->MERGEindependent= False;
    +          goto LABELcheckdigit;
    +          break; /* no warnings */
    +        case '3':
    +          qh_option(qh, "Q3-no-merge-vertices", NULL, NULL);
    +          qh->MERGEvertices= False;
    +        LABELcheckdigit:
    +          if (isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7017, "qhull warning: can not follow '1', '2', or '3' with a digit.  '%c' skipped.\n",
    +                     *s++);
    +          break;
    +        case '4':
    +          qh_option(qh, "Q4-avoid-old-into-new", NULL, NULL);
    +          qh->AVOIDold= True;
    +          break;
    +        case '5':
    +          qh_option(qh, "Q5-no-check-outer", NULL, NULL);
    +          qh->SKIPcheckmax= True;
    +          break;
    +        case '6':
    +          qh_option(qh, "Q6-no-concave-merge", NULL, NULL);
    +          qh->SKIPconvex= True;
    +          break;
    +        case '7':
    +          qh_option(qh, "Q7-no-breadth-first", NULL, NULL);
    +          qh->VIRTUALmemory= True;
    +          break;
    +        case '8':
    +          qh_option(qh, "Q8-no-near-inside", NULL, NULL);
    +          qh->NOnearinside= True;
    +          break;
    +        case '9':
    +          qh_option(qh, "Q9-pick-furthest", NULL, NULL);
    +          qh->PICKfurthest= True;
    +          break;
    +        case 'G':
    +          i= qh_strtol(s, &t);
    +          if (qh->GOODpoint)
    +            qh_fprintf(qh, qh->ferr, 7018, "qhull warning: good point already defined for option 'QGn'.  Ignored\n");
    +          else if (s == t)
    +            qh_fprintf(qh, qh->ferr, 7019, "qhull warning: missing good point id for option 'QGn'.  Ignored\n");
    +          else if (i < 0 || *s == '-') {
    +            qh->GOODpoint= i-1;
    +            qh_option(qh, "QGood-if-dont-see-point", &i, NULL);
    +          }else {
    +            qh->GOODpoint= i+1;
    +            qh_option(qh, "QGood-if-see-point", &i, NULL);
    +          }
    +          s= t;
    +          break;
    +        case 'J':
    +          if (!isdigit(*s) && *s != '-')
    +            qh->JOGGLEmax= 0.0;
    +          else {
    +            qh->JOGGLEmax= (realT) qh_strtod(s, &s);
    +            qh_option(qh, "QJoggle", NULL, &qh->JOGGLEmax);
    +          }
    +          break;
    +        case 'R':
    +          if (!isdigit(*s) && *s != '-')
    +            qh_fprintf(qh, qh->ferr, 7020, "qhull warning: missing random seed for option 'QRn'.  Ignored\n");
    +          else {
    +            qh->ROTATErandom= i= qh_strtol(s, &s);
    +            if (i > 0)
    +              qh_option(qh, "QRotate-id", &i, NULL );
    +            else if (i < -1)
    +              qh_option(qh, "QRandom-seed", &i, NULL );
    +          }
    +          break;
    +        case 'V':
    +          i= qh_strtol(s, &t);
    +          if (qh->GOODvertex)
    +            qh_fprintf(qh, qh->ferr, 7021, "qhull warning: good vertex already defined for option 'QVn'.  Ignored\n");
    +          else if (s == t)
    +            qh_fprintf(qh, qh->ferr, 7022, "qhull warning: no good point id given for option 'QVn'.  Ignored\n");
    +          else if (i < 0) {
    +            qh->GOODvertex= i - 1;
    +            qh_option(qh, "QV-good-facets-not-point", &i, NULL);
    +          }else {
    +            qh_option(qh, "QV-good-facets-point", &i, NULL);
    +            qh->GOODvertex= i + 1;
    +          }
    +          s= t;
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7023, "qhull warning: unknown 'Q' qhull option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'T':
    +      while (*s && !isspace(*s)) {
    +        if (isdigit(*s) || *s == '-')
    +          qh->IStracing= qh_strtol(s, &s);
    +        else switch (*s++) {
    +        case 'a':
    +          qh_option(qh, "Tannotate-output", NULL, NULL);
    +          qh->ANNOTATEoutput= True;
    +          break;
    +        case 'c':
    +          qh_option(qh, "Tcheck-frequently", NULL, NULL);
    +          qh->CHECKfrequently= True;
    +          break;
    +        case 's':
    +          qh_option(qh, "Tstatistics", NULL, NULL);
    +          qh->PRINTstatistics= True;
    +          break;
    +        case 'v':
    +          qh_option(qh, "Tverify", NULL, NULL);
    +          qh->VERIFYoutput= True;
    +          break;
    +        case 'z':
    +          if (qh->ferr == qh_FILEstderr) {
    +            /* The C++ interface captures the output in qh_fprint_qhull() */
    +            qh_option(qh, "Tz-stdout", NULL, NULL);
    +            qh->USEstdout= True;
    +          }else if (!qh->fout)
    +            qh_fprintf(qh, qh->ferr, 7024, "qhull warning: output file undefined(stdout).  Option 'Tz' ignored.\n");
    +          else {
    +            qh_option(qh, "Tz-stdout", NULL, NULL);
    +            qh->USEstdout= True;
    +            qh->ferr= qh->fout;
    +            qh->qhmem.ferr= qh->fout;
    +          }
    +          break;
    +        case 'C':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7025, "qhull warning: missing point id for cone for trace option 'TCn'.  Ignored\n");
    +          else {
    +            i= qh_strtol(s, &s);
    +            qh_option(qh, "TCone-stop", &i, NULL);
    +            qh->STOPcone= i + 1;
    +          }
    +          break;
    +        case 'F':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7026, "qhull warning: missing frequency count for trace option 'TFn'.  Ignored\n");
    +          else {
    +            qh->REPORTfreq= qh_strtol(s, &s);
    +            qh_option(qh, "TFacet-log", &qh->REPORTfreq, NULL);
    +            qh->REPORTfreq2= qh->REPORTfreq/2;  /* for tracemerging() */
    +          }
    +          break;
    +        case 'I':
    +          if (!isspace(*s))
    +            qh_fprintf(qh, qh->ferr, 7027, "qhull warning: missing space between 'TI' and filename, %s\n", s);
    +          while (isspace(*s))
    +            s++;
    +          t= qh_skipfilename(qh, s);
    +          {
    +            char filename[qh_FILENAMElen];
    +
    +            qh_copyfilename(qh, filename, (int)sizeof(filename), s, (int)(t-s));   /* WARN64 */
    +            s= t;
    +            if (!freopen(filename, "r", stdin)) {
    +              qh_fprintf(qh, qh->ferr, 6041, "qhull error: could not open file \"%s\".", filename);
    +              qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +            }else {
    +              qh_option(qh, "TInput-file", NULL, NULL);
    +              qh_option(qh, filename, NULL, NULL);
    +            }
    +          }
    +          break;
    +        case 'O':
    +            if (!isspace(*s))
    +                qh_fprintf(qh, qh->ferr, 7028, "qhull warning: missing space between 'TO' and filename, %s\n", s);
    +            while (isspace(*s))
    +                s++;
    +            t= qh_skipfilename(qh, s);
    +            {
    +              char filename[qh_FILENAMElen];
    +
    +              qh_copyfilename(qh, filename, (int)sizeof(filename), s, (int)(t-s));  /* WARN64 */
    +              s= t;
    +              if (!qh->fout) {
    +                qh_fprintf(qh, qh->ferr, 6266, "qhull input warning: qh.fout was not set by caller.  Cannot use option 'TO' to redirect output.  Ignoring option 'TO'\n");
    +              }else if (!freopen(filename, "w", qh->fout)) {
    +                qh_fprintf(qh, qh->ferr, 6044, "qhull error: could not open file \"%s\".", filename);
    +                qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +              }else {
    +                qh_option(qh, "TOutput-file", NULL, NULL);
    +              qh_option(qh, filename, NULL, NULL);
    +            }
    +          }
    +          break;
    +        case 'P':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7029, "qhull warning: missing point id for trace option 'TPn'.  Ignored\n");
    +          else {
    +            qh->TRACEpoint= qh_strtol(s, &s);
    +            qh_option(qh, "Trace-point", &qh->TRACEpoint, NULL);
    +          }
    +          break;
    +        case 'M':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7030, "qhull warning: missing merge id for trace option 'TMn'.  Ignored\n");
    +          else {
    +            qh->TRACEmerge= qh_strtol(s, &s);
    +            qh_option(qh, "Trace-merge", &qh->TRACEmerge, NULL);
    +          }
    +          break;
    +        case 'R':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7031, "qhull warning: missing rerun count for trace option 'TRn'.  Ignored\n");
    +          else {
    +            qh->RERUN= qh_strtol(s, &s);
    +            qh_option(qh, "TRerun", &qh->RERUN, NULL);
    +          }
    +          break;
    +        case 'V':
    +          i= qh_strtol(s, &t);
    +          if (s == t)
    +            qh_fprintf(qh, qh->ferr, 7032, "qhull warning: missing furthest point id for trace option 'TVn'.  Ignored\n");
    +          else if (i < 0) {
    +            qh->STOPpoint= i - 1;
    +            qh_option(qh, "TV-stop-before-point", &i, NULL);
    +          }else {
    +            qh->STOPpoint= i + 1;
    +            qh_option(qh, "TV-stop-after-point", &i, NULL);
    +          }
    +          s= t;
    +          break;
    +        case 'W':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7033, "qhull warning: missing max width for trace option 'TWn'.  Ignored\n");
    +          else {
    +            qh->TRACEdist= (realT) qh_strtod(s, &s);
    +            qh_option(qh, "TWide-trace", NULL, &qh->TRACEdist);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7034, "qhull warning: unknown 'T' trace option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    default:
    +      qh_fprintf(qh, qh->ferr, 7035, "qhull warning: unknown flag %c(%x)\n", (int)s[-1],
    +               (int)s[-1]);
    +      break;
    +    }
    +    if (s-1 == prev_s && *s && !isspace(*s)) {
    +      qh_fprintf(qh, qh->ferr, 7036, "qhull warning: missing space after flag %c(%x); reserved for menu. Skipped.\n",
    +               (int)*prev_s, (int)*prev_s);
    +      while (*s && !isspace(*s))
    +        s++;
    +    }
    +  }
    +  if (qh->STOPcone && qh->JOGGLEmax < REALmax/2)
    +    qh_fprintf(qh, qh->ferr, 7078, "qhull warning: 'TCn' (stopCone) ignored when used with 'QJn' (joggle)\n");
    +  if (isgeom && !qh->FORCEoutput && qh->PRINTout[1])
    +    qh_fprintf(qh, qh->ferr, 7037, "qhull warning: additional output formats are not compatible with Geomview\n");
    +  /* set derived values in qh_initqhull_globals */
    +} /* initflags */
    +
    +
    +/*---------------------------------
    +
    +  qh_initqhull_buffers(qh)
    +    initialize global memory buffers
    +
    +  notes:
    +    must match qh_freebuffers()
    +*/
    +void qh_initqhull_buffers(qhT *qh) {
    +  int k;
    +
    +  qh->TEMPsize= (qh->qhmem.LASTsize - sizeof(setT))/SETelemsize;
    +  if (qh->TEMPsize <= 0 || qh->TEMPsize > qh->qhmem.LASTsize)
    +    qh->TEMPsize= 8;  /* e.g., if qh_NOmem */
    +  qh->other_points= qh_setnew(qh, qh->TEMPsize);
    +  qh->del_vertices= qh_setnew(qh, qh->TEMPsize);
    +  qh->coplanarfacetset= qh_setnew(qh, qh->TEMPsize);
    +  qh->NEARzero= (realT *)qh_memalloc(qh, qh->hull_dim * sizeof(realT));
    +  qh->lower_threshold= (realT *)qh_memalloc(qh, (qh->input_dim+1) * sizeof(realT));
    +  qh->upper_threshold= (realT *)qh_memalloc(qh, (qh->input_dim+1) * sizeof(realT));
    +  qh->lower_bound= (realT *)qh_memalloc(qh, (qh->input_dim+1) * sizeof(realT));
    +  qh->upper_bound= (realT *)qh_memalloc(qh, (qh->input_dim+1) * sizeof(realT));
    +  for (k=qh->input_dim+1; k--; ) {  /* duplicated in qh_initqhull_buffers and qh_clear_outputflags */
    +    qh->lower_threshold[k]= -REALmax;
    +    qh->upper_threshold[k]= REALmax;
    +    qh->lower_bound[k]= -REALmax;
    +    qh->upper_bound[k]= REALmax;
    +  }
    +  qh->gm_matrix= (coordT *)qh_memalloc(qh, (qh->hull_dim+1) * qh->hull_dim * sizeof(coordT));
    +  qh->gm_row= (coordT **)qh_memalloc(qh, (qh->hull_dim+1) * sizeof(coordT *));
    +} /* initqhull_buffers */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_globals(qh, points, numpoints, dim, ismalloc )
    +    initialize globals
    +    if ismalloc
    +      points were malloc'd and qhull should free at end
    +
    +  returns:
    +    sets qh.first_point, num_points, input_dim, hull_dim and others
    +    seeds random number generator (seed=1 if tracing)
    +    modifies qh.hull_dim if ((qh.DELAUNAY and qh.PROJECTdelaunay) or qh.PROJECTinput)
    +    adjust user flags as needed
    +    also checks DIM3 dependencies and constants
    +
    +  notes:
    +    do not use qh_point() since an input transformation may move them elsewhere
    +
    +  see:
    +    qh_initqhull_start() sets default values for non-zero globals
    +
    +  design:
    +    initialize points array from input arguments
    +    test for qh.ZEROcentrum
    +      (i.e., use opposite vertex instead of cetrum for convexity testing)
    +    initialize qh.CENTERtype, qh.normal_size,
    +      qh.center_size, qh.TRACEpoint/level,
    +    initialize and test random numbers
    +    qh_initqhull_outputflags() -- adjust and test output flags
    +*/
    +void qh_initqhull_globals(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc) {
    +  int seed, pointsneeded, extra= 0, i, randi, k;
    +  realT randr;
    +  realT factorial;
    +
    +  time_t timedata;
    +
    +  trace0((qh, qh->ferr, 13, "qh_initqhull_globals: for %s | %s\n", qh->rbox_command,
    +      qh->qhull_command));
    +  qh->POINTSmalloc= ismalloc;
    +  qh->first_point= points;
    +  qh->num_points= numpoints;
    +  qh->hull_dim= qh->input_dim= dim;
    +  if (!qh->NOpremerge && !qh->MERGEexact && !qh->PREmerge && qh->JOGGLEmax > REALmax/2) {
    +    qh->MERGING= True;
    +    if (qh->hull_dim <= 4) {
    +      qh->PREmerge= True;
    +      qh_option(qh, "_pre-merge", NULL, NULL);
    +    }else {
    +      qh->MERGEexact= True;
    +      qh_option(qh, "Qxact_merge", NULL, NULL);
    +    }
    +  }else if (qh->MERGEexact)
    +    qh->MERGING= True;
    +  if (!qh->NOpremerge && qh->JOGGLEmax > REALmax/2) {
    +#ifdef qh_NOmerge
    +    qh->JOGGLEmax= 0.0;
    +#endif
    +  }
    +  if (qh->TRIangulate && qh->JOGGLEmax < REALmax/2 && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 7038, "qhull warning: joggle('QJ') always produces simplicial output.  Triangulated output('Qt') does nothing.\n");
    +  if (qh->JOGGLEmax < REALmax/2 && qh->DELAUNAY && !qh->SCALEinput && !qh->SCALElast) {
    +    qh->SCALElast= True;
    +    qh_option(qh, "Qbbound-last-qj", NULL, NULL);
    +  }
    +  if (qh->MERGING && !qh->POSTmerge && qh->premerge_cos > REALmax/2
    +  && qh->premerge_centrum == 0) {
    +    qh->ZEROcentrum= True;
    +    qh->ZEROall_ok= True;
    +    qh_option(qh, "_zero-centrum", NULL, NULL);
    +  }
    +  if (qh->JOGGLEmax < REALmax/2 && REALepsilon > 2e-8 && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 7039, "qhull warning: real epsilon, %2.2g, is probably too large for joggle('QJn')\nRecompile with double precision reals(see user.h).\n",
    +          REALepsilon);
    +#ifdef qh_NOmerge
    +  if (qh->MERGING) {
    +    qh_fprintf(qh, qh->ferr, 6045, "qhull input error: merging not installed(qh_NOmerge + 'Qx', 'Cn' or 'An')\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +#endif
    +  if (qh->DELAUNAY && qh->KEEPcoplanar && !qh->KEEPinside) {
    +    qh->KEEPinside= True;
    +    qh_option(qh, "Qinterior-keep", NULL, NULL);
    +  }
    +  if (qh->DELAUNAY && qh->HALFspace) {
    +    qh_fprintf(qh, qh->ferr, 6046, "qhull input error: can not use Delaunay('d') or Voronoi('v') with halfspace intersection('H')\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (!qh->DELAUNAY && (qh->UPPERdelaunay || qh->ATinfinity)) {
    +    qh_fprintf(qh, qh->ferr, 6047, "qhull input error: use upper-Delaunay('Qu') or infinity-point('Qz') with Delaunay('d') or Voronoi('v')\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh->UPPERdelaunay && qh->ATinfinity) {
    +    qh_fprintf(qh, qh->ferr, 6048, "qhull input error: can not use infinity-point('Qz') with upper-Delaunay('Qu')\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh->SCALElast && !qh->DELAUNAY && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 7040, "qhull input warning: option 'Qbb' (scale-last-coordinate) is normally used with 'd' or 'v'\n");
    +  qh->DOcheckmax= (!qh->SKIPcheckmax && qh->MERGING );
    +  qh->KEEPnearinside= (qh->DOcheckmax && !(qh->KEEPinside && qh->KEEPcoplanar)
    +                          && !qh->NOnearinside);
    +  if (qh->MERGING)
    +    qh->CENTERtype= qh_AScentrum;
    +  else if (qh->VORONOI)
    +    qh->CENTERtype= qh_ASvoronoi;
    +  if (qh->TESTvneighbors && !qh->MERGING) {
    +    qh_fprintf(qh, qh->ferr, 6049, "qhull input error: test vertex neighbors('Qv') needs a merge option\n");
    +    qh_errexit(qh, qh_ERRinput, NULL ,NULL);
    +  }
    +  if (qh->PROJECTinput || (qh->DELAUNAY && qh->PROJECTdelaunay)) {
    +    qh->hull_dim -= qh->PROJECTinput;
    +    if (qh->DELAUNAY) {
    +      qh->hull_dim++;
    +      if (qh->ATinfinity)
    +        extra= 1;
    +    }
    +  }
    +  if (qh->hull_dim <= 1) {
    +    qh_fprintf(qh, qh->ferr, 6050, "qhull error: dimension %d must be > 1\n", qh->hull_dim);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  for (k=2, factorial=1.0; k < qh->hull_dim; k++)
    +    factorial *= k;
    +  qh->AREAfactor= 1.0 / factorial;
    +  trace2((qh, qh->ferr, 2005, "qh_initqhull_globals: initialize globals.  dim %d numpoints %d malloc? %d projected %d to hull_dim %d\n",
    +        dim, numpoints, ismalloc, qh->PROJECTinput, qh->hull_dim));
    +  qh->normal_size= qh->hull_dim * sizeof(coordT);
    +  qh->center_size= qh->normal_size - sizeof(coordT);
    +  pointsneeded= qh->hull_dim+1;
    +  if (qh->hull_dim > qh_DIMmergeVertex) {
    +    qh->MERGEvertices= False;
    +    qh_option(qh, "Q3-no-merge-vertices-dim-high", NULL, NULL);
    +  }
    +  if (qh->GOODpoint)
    +    pointsneeded++;
    +#ifdef qh_NOtrace
    +  if (qh->IStracing) {
    +    qh_fprintf(qh, qh->ferr, 6051, "qhull input error: tracing is not installed(qh_NOtrace in user.h)");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +#endif
    +  if (qh->RERUN > 1) {
    +    qh->TRACElastrun= qh->IStracing; /* qh_build_withrestart duplicates next conditional */
    +    if (qh->IStracing != -1)
    +      qh->IStracing= 0;
    +  }else if (qh->TRACEpoint != qh_IDunknown || qh->TRACEdist < REALmax/2 || qh->TRACEmerge) {
    +    qh->TRACElevel= (qh->IStracing? qh->IStracing : 3);
    +    qh->IStracing= 0;
    +  }
    +  if (qh->ROTATErandom == 0 || qh->ROTATErandom == -1) {
    +    seed= (int)time(&timedata);
    +    if (qh->ROTATErandom  == -1) {
    +      seed= -seed;
    +      qh_option(qh, "QRandom-seed", &seed, NULL );
    +    }else
    +      qh_option(qh, "QRotate-random", &seed, NULL);
    +    qh->ROTATErandom= seed;
    +  }
    +  seed= qh->ROTATErandom;
    +  if (seed == INT_MIN)    /* default value */
    +    seed= 1;
    +  else if (seed < 0)
    +    seed= -seed;
    +  qh_RANDOMseed_(qh, seed);
    +  randr= 0.0;
    +  for (i=1000; i--; ) {
    +    randi= qh_RANDOMint;
    +    randr += randi;
    +    if (randi > qh_RANDOMmax) {
    +      qh_fprintf(qh, qh->ferr, 8036, "\
    +qhull configuration error (qh_RANDOMmax in user.h):\n\
    +   random integer %d > qh_RANDOMmax(qh, %.8g)\n",
    +               randi, qh_RANDOMmax);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +  }
    +  qh_RANDOMseed_(qh, seed);
    +  randr = randr/1000;
    +  if (randr < qh_RANDOMmax * 0.1
    +  || randr > qh_RANDOMmax * 0.9)
    +    qh_fprintf(qh, qh->ferr, 8037, "\
    +qhull configuration warning (qh_RANDOMmax in user.h):\n\
    +   average of 1000 random integers (%.2g) is much different than expected (%.2g).\n\
    +   Is qh_RANDOMmax (%.2g) wrong?\n",
    +             randr, qh_RANDOMmax * 0.5, qh_RANDOMmax);
    +  qh->RANDOMa= 2.0 * qh->RANDOMfactor/qh_RANDOMmax;
    +  qh->RANDOMb= 1.0 - qh->RANDOMfactor;
    +  if (qh_HASHfactor < 1.1) {
    +    qh_fprintf(qh, qh->ferr, 6052, "qhull internal error (qh_initqhull_globals): qh_HASHfactor %d must be at least 1.1.  Qhull uses linear hash probing\n",
    +      qh_HASHfactor);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  if (numpoints+extra < pointsneeded) {
    +    qh_fprintf(qh, qh->ferr, 6214, "qhull input error: not enough points(%d) to construct initial simplex (need %d)\n",
    +            numpoints, pointsneeded);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  qh_initqhull_outputflags(qh);
    +} /* initqhull_globals */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_mem(qh, )
    +    initialize mem_r.c for qhull
    +    qh.hull_dim and qh.normal_size determine some of the allocation sizes
    +    if qh.MERGING,
    +      includes ridgeT
    +    calls qh_user_memsizes(qh) to add up to 10 additional sizes for quick allocation
    +      (see numsizes below)
    +
    +  returns:
    +    mem_r.c already for qh_memalloc/qh_memfree (errors if called beforehand)
    +
    +  notes:
    +    qh_produceoutput() prints memsizes
    +
    +*/
    +void qh_initqhull_mem(qhT *qh) {
    +  int numsizes;
    +  int i;
    +
    +  numsizes= 8+10;
    +  qh_meminitbuffers(qh, qh->IStracing, qh_MEMalign, numsizes,
    +                     qh_MEMbufsize, qh_MEMinitbuf);
    +  qh_memsize(qh, (int)sizeof(vertexT));
    +  if (qh->MERGING) {
    +    qh_memsize(qh, (int)sizeof(ridgeT));
    +    qh_memsize(qh, (int)sizeof(mergeT));
    +  }
    +  qh_memsize(qh, (int)sizeof(facetT));
    +  i= sizeof(setT) + (qh->hull_dim - 1) * SETelemsize;  /* ridge.vertices */
    +  qh_memsize(qh, i);
    +  qh_memsize(qh, qh->normal_size);        /* normal */
    +  i += SETelemsize;                 /* facet.vertices, .ridges, .neighbors */
    +  qh_memsize(qh, i);
    +  qh_user_memsizes(qh);
    +  qh_memsetup(qh);
    +} /* initqhull_mem */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_outputflags
    +    initialize flags concerned with output
    +
    +  returns:
    +    adjust user flags as needed
    +
    +  see:
    +    qh_clear_outputflags() resets the flags
    +
    +  design:
    +    test for qh.PRINTgood (i.e., only print 'good' facets)
    +    check for conflicting print output options
    +*/
    +void qh_initqhull_outputflags(qhT *qh) {
    +  boolT printgeom= False, printmath= False, printcoplanar= False;
    +  int i;
    +
    +  trace3((qh, qh->ferr, 3024, "qh_initqhull_outputflags: %s\n", qh->qhull_command));
    +  if (!(qh->PRINTgood || qh->PRINTneighbors)) {
    +    if (qh->KEEParea || qh->KEEPminArea < REALmax/2 || qh->KEEPmerge || qh->DELAUNAY
    +        || (!qh->ONLYgood && (qh->GOODvertex || qh->GOODpoint))) {
    +      qh->PRINTgood= True;
    +      qh_option(qh, "Pgood", NULL, NULL);
    +    }
    +  }
    +  if (qh->PRINTtransparent) {
    +    if (qh->hull_dim != 4 || !qh->DELAUNAY || qh->VORONOI || qh->DROPdim >= 0) {
    +      qh_fprintf(qh, qh->ferr, 6215, "qhull input error: transparent Delaunay('Gt') needs 3-d Delaunay('d') w/o 'GDn'\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    qh->DROPdim = 3;
    +    qh->PRINTridges = True;
    +  }
    +  for (i=qh_PRINTEND; i--; ) {
    +    if (qh->PRINTout[i] == qh_PRINTgeom)
    +      printgeom= True;
    +    else if (qh->PRINTout[i] == qh_PRINTmathematica || qh->PRINTout[i] == qh_PRINTmaple)
    +      printmath= True;
    +    else if (qh->PRINTout[i] == qh_PRINTcoplanars)
    +      printcoplanar= True;
    +    else if (qh->PRINTout[i] == qh_PRINTpointnearest)
    +      printcoplanar= True;
    +    else if (qh->PRINTout[i] == qh_PRINTpointintersect && !qh->HALFspace) {
    +      qh_fprintf(qh, qh->ferr, 6053, "qhull input error: option 'Fp' is only used for \nhalfspace intersection('Hn,n,n').\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }else if (qh->PRINTout[i] == qh_PRINTtriangles && (qh->HALFspace || qh->VORONOI)) {
    +      qh_fprintf(qh, qh->ferr, 6054, "qhull input error: option 'Ft' is not available for Voronoi vertices or halfspace intersection\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }else if (qh->PRINTout[i] == qh_PRINTcentrums && qh->VORONOI) {
    +      qh_fprintf(qh, qh->ferr, 6055, "qhull input error: option 'FC' is not available for Voronoi vertices('v')\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }else if (qh->PRINTout[i] == qh_PRINTvertices) {
    +      if (qh->VORONOI)
    +        qh_option(qh, "Fvoronoi", NULL, NULL);
    +      else
    +        qh_option(qh, "Fvertices", NULL, NULL);
    +    }
    +  }
    +  if (printcoplanar && qh->DELAUNAY && qh->JOGGLEmax < REALmax/2) {
    +    if (qh->PRINTprecision)
    +      qh_fprintf(qh, qh->ferr, 7041, "qhull input warning: 'QJ' (joggle) will usually prevent coincident input sites for options 'Fc' and 'FP'\n");
    +  }
    +  if (printmath && (qh->hull_dim > 3 || qh->VORONOI)) {
    +    qh_fprintf(qh, qh->ferr, 6056, "qhull input error: Mathematica and Maple output is only available for 2-d and 3-d convex hulls and 2-d Delaunay triangulations\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (printgeom) {
    +    if (qh->hull_dim > 4) {
    +      qh_fprintf(qh, qh->ferr, 6057, "qhull input error: Geomview output is only available for 2-d, 3-d and 4-d\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh->PRINTnoplanes && !(qh->PRINTcoplanar + qh->PRINTcentrums
    +     + qh->PRINTdots + qh->PRINTspheres + qh->DOintersections + qh->PRINTridges)) {
    +      qh_fprintf(qh, qh->ferr, 6058, "qhull input error: no output specified for Geomview\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh->VORONOI && (qh->hull_dim > 3 || qh->DROPdim >= 0)) {
    +      qh_fprintf(qh, qh->ferr, 6059, "qhull input error: Geomview output for Voronoi diagrams only for 2-d\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    /* can not warn about furthest-site Geomview output: no lower_threshold */
    +    if (qh->hull_dim == 4 && qh->DROPdim == -1 &&
    +        (qh->PRINTcoplanar || qh->PRINTspheres || qh->PRINTcentrums)) {
    +      qh_fprintf(qh, qh->ferr, 7042, "qhull input warning: coplanars, vertices, and centrums output not\n\
    +available for 4-d output(ignored).  Could use 'GDn' instead.\n");
    +      qh->PRINTcoplanar= qh->PRINTspheres= qh->PRINTcentrums= False;
    +    }
    +  }
    +  if (!qh->KEEPcoplanar && !qh->KEEPinside && !qh->ONLYgood) {
    +    if ((qh->PRINTcoplanar && qh->PRINTspheres) || printcoplanar) {
    +      if (qh->QHULLfinished) {
    +        qh_fprintf(qh, qh->ferr, 7072, "qhull output warning: ignoring coplanar points, option 'Qc' was not set for the first run of qhull.\n");
    +      }else {
    +        qh->KEEPcoplanar = True;
    +        qh_option(qh, "Qcoplanar", NULL, NULL);
    +      }
    +    }
    +  }
    +  qh->PRINTdim= qh->hull_dim;
    +  if (qh->DROPdim >=0) {    /* after Geomview checks */
    +    if (qh->DROPdim < qh->hull_dim) {
    +      qh->PRINTdim--;
    +      if (!printgeom || qh->hull_dim < 3)
    +        qh_fprintf(qh, qh->ferr, 7043, "qhull input warning: drop dimension 'GD%d' is only available for 3-d/4-d Geomview\n", qh->DROPdim);
    +    }else
    +      qh->DROPdim= -1;
    +  }else if (qh->VORONOI) {
    +    qh->DROPdim= qh->hull_dim-1;
    +    qh->PRINTdim= qh->hull_dim-1;
    +  }
    +} /* qh_initqhull_outputflags */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_start(qh, infile, outfile, errfile )
    +    allocate memory if needed and call qh_initqhull_start2()
    +*/
    +void qh_initqhull_start(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile) {
    +
    +  qh_initstatistics(qh);
    +  qh_initqhull_start2(qh, infile, outfile, errfile);
    +} /* initqhull_start */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_start2(qh, infile, outfile, errfile )
    +    start initialization of qhull
    +    initialize statistics, stdio, default values for global variables
    +    assumes qh is allocated
    +  notes:
    +    report errors elsewhere, error handling and g_qhull_output [Qhull.cpp, QhullQh()] not in initialized
    +  see:
    +    qh_maxmin() determines the precision constants
    +    qh_freeqhull()
    +*/
    +void qh_initqhull_start2(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile) {
    +  time_t timedata;
    +  int seed;
    +
    +  qh_CPUclock; /* start the clock(for qh_clock).  One-shot. */
    +  /* memset is the same in qh_freeqhull() and qh_initqhull_start2() */
    +  memset((char *)qh, 0, sizeof(qhT)-sizeof(qhmemT)-sizeof(qhstatT));   /* every field is 0, FALSE, NULL */
    +  qh->NOerrexit= True;
    +  qh->ANGLEmerge= True;
    +  qh->DROPdim= -1;
    +  qh->ferr= errfile;
    +  qh->fin= infile;
    +  qh->fout= outfile;
    +  qh->furthest_id= qh_IDunknown;
    +  qh->JOGGLEmax= REALmax;
    +  qh->KEEPminArea = REALmax;
    +  qh->last_low= REALmax;
    +  qh->last_high= REALmax;
    +  qh->last_newhigh= REALmax;
    +  qh->last_random= 1;
    +  qh->max_outside= 0.0;
    +  qh->max_vertex= 0.0;
    +  qh->MAXabs_coord= 0.0;
    +  qh->MAXsumcoord= 0.0;
    +  qh->MAXwidth= -REALmax;
    +  qh->MERGEindependent= True;
    +  qh->MINdenom_1= fmax_(1.0/REALmax, REALmin); /* used by qh_scalepoints */
    +  qh->MINoutside= 0.0;
    +  qh->MINvisible= REALmax;
    +  qh->MAXcoplanar= REALmax;
    +  qh->outside_err= REALmax;
    +  qh->premerge_centrum= 0.0;
    +  qh->premerge_cos= REALmax;
    +  qh->PRINTprecision= True;
    +  qh->PRINTradius= 0.0;
    +  qh->postmerge_cos= REALmax;
    +  qh->postmerge_centrum= 0.0;
    +  qh->ROTATErandom= INT_MIN;
    +  qh->MERGEvertices= True;
    +  qh->totarea= 0.0;
    +  qh->totvol= 0.0;
    +  qh->TRACEdist= REALmax;
    +  qh->TRACEpoint= qh_IDunknown; /* recompile or use 'TPn' */
    +  qh->tracefacet_id= UINT_MAX;  /* recompile to trace a facet */
    +  qh->tracevertex_id= UINT_MAX; /* recompile to trace a vertex */
    +  seed= (int)time(&timedata);
    +  qh_RANDOMseed_(qh, seed);
    +  qh->run_id= qh_RANDOMint;
    +  if(!qh->run_id)
    +      qh->run_id++;  /* guarantee non-zero */
    +  qh_option(qh, "run-id", &qh->run_id, NULL);
    +  strcat(qh->qhull, "qhull");
    +} /* initqhull_start2 */
    +
    +/*---------------------------------
    +
    +  qh_initthresholds(qh, commandString )
    +    set thresholds for printing and scaling from commandString
    +
    +  returns:
    +    sets qh.GOODthreshold or qh.SPLITthreshold if 'Pd0D1' used
    +
    +  see:
    +    qh_initflags(), 'Qbk' 'QBk' 'Pdk' and 'PDk'
    +    qh_inthresholds()
    +
    +  design:
    +    for each 'Pdn' or 'PDn' option
    +      check syntax
    +      set qh.lower_threshold or qh.upper_threshold
    +    set qh.GOODthreshold if an unbounded threshold is used
    +    set qh.SPLITthreshold if a bounded threshold is used
    +*/
    +void qh_initthresholds(qhT *qh, char *command) {
    +  realT value;
    +  int idx, maxdim, k;
    +  char *s= command; /* non-const due to strtol */
    +  char key;
    +
    +  maxdim= qh->input_dim;
    +  if (qh->DELAUNAY && (qh->PROJECTdelaunay || qh->PROJECTinput))
    +    maxdim++;
    +  while (*s) {
    +    if (*s == '-')
    +      s++;
    +    if (*s == 'P') {
    +      s++;
    +      while (*s && !isspace(key= *s++)) {
    +        if (key == 'd' || key == 'D') {
    +          if (!isdigit(*s)) {
    +            qh_fprintf(qh, qh->ferr, 7044, "qhull warning: no dimension given for Print option '%c' at: %s.  Ignored\n",
    +                    key, s-1);
    +            continue;
    +          }
    +          idx= qh_strtol(s, &s);
    +          if (idx >= qh->hull_dim) {
    +            qh_fprintf(qh, qh->ferr, 7045, "qhull warning: dimension %d for Print option '%c' is >= %d.  Ignored\n",
    +                idx, key, qh->hull_dim);
    +            continue;
    +          }
    +          if (*s == ':') {
    +            s++;
    +            value= qh_strtod(s, &s);
    +            if (fabs((double)value) > 1.0) {
    +              qh_fprintf(qh, qh->ferr, 7046, "qhull warning: value %2.4g for Print option %c is > +1 or < -1.  Ignored\n",
    +                      value, key);
    +              continue;
    +            }
    +          }else
    +            value= 0.0;
    +          if (key == 'd')
    +            qh->lower_threshold[idx]= value;
    +          else
    +            qh->upper_threshold[idx]= value;
    +        }
    +      }
    +    }else if (*s == 'Q') {
    +      s++;
    +      while (*s && !isspace(key= *s++)) {
    +        if (key == 'b' && *s == 'B') {
    +          s++;
    +          for (k=maxdim; k--; ) {
    +            qh->lower_bound[k]= -qh_DEFAULTbox;
    +            qh->upper_bound[k]= qh_DEFAULTbox;
    +          }
    +        }else if (key == 'b' && *s == 'b')
    +          s++;
    +        else if (key == 'b' || key == 'B') {
    +          if (!isdigit(*s)) {
    +            qh_fprintf(qh, qh->ferr, 7047, "qhull warning: no dimension given for Qhull option %c.  Ignored\n",
    +                    key);
    +            continue;
    +          }
    +          idx= qh_strtol(s, &s);
    +          if (idx >= maxdim) {
    +            qh_fprintf(qh, qh->ferr, 7048, "qhull warning: dimension %d for Qhull option %c is >= %d.  Ignored\n",
    +                idx, key, maxdim);
    +            continue;
    +          }
    +          if (*s == ':') {
    +            s++;
    +            value= qh_strtod(s, &s);
    +          }else if (key == 'b')
    +            value= -qh_DEFAULTbox;
    +          else
    +            value= qh_DEFAULTbox;
    +          if (key == 'b')
    +            qh->lower_bound[idx]= value;
    +          else
    +            qh->upper_bound[idx]= value;
    +        }
    +      }
    +    }else {
    +      while (*s && !isspace(*s))
    +        s++;
    +    }
    +    while (isspace(*s))
    +      s++;
    +  }
    +  for (k=qh->hull_dim; k--; ) {
    +    if (qh->lower_threshold[k] > -REALmax/2) {
    +      qh->GOODthreshold= True;
    +      if (qh->upper_threshold[k] < REALmax/2) {
    +        qh->SPLITthresholds= True;
    +        qh->GOODthreshold= False;
    +        break;
    +      }
    +    }else if (qh->upper_threshold[k] < REALmax/2)
    +      qh->GOODthreshold= True;
    +  }
    +} /* initthresholds */
    +
    +/*---------------------------------
    +
    +  qh_lib_check( qhullLibraryType, qhTsize, vertexTsize, ridgeTsize, facetTsize, setTsize, qhmemTsize )
    +    Report error if library does not agree with caller
    +
    +  notes:
    +    NOerrors -- qh_lib_check can not call qh_errexit()
    +*/
    +void qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeTsize, int facetTsize, int setTsize, int qhmemTsize) {
    +    boolT iserror= False;
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)  /* user_r.h */
    +    // _CrtSetBreakAlloc(744);  /* Break at memalloc {744}, or 'watch' _crtBreakAlloc */
    +    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) );
    +    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
    +    _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR );
    +    _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR );
    +#endif
    +
    +    if (qhullLibraryType==QHULL_NON_REENTRANT) { /* 0 */
    +        qh_fprintf_stderr(6257, "qh_lib_check: Incorrect qhull library called.  Caller uses non-reentrant Qhull with a static qhT.  Library is reentrant.\n");
    +        iserror= True;
    +    }else if (qhullLibraryType==QHULL_QH_POINTER) { /* 1 */
    +        qh_fprintf_stderr(6258, "qh_lib_check: Incorrect qhull library called.  Caller uses non-reentrant Qhull with a dynamic qhT via qh_QHpointer.  Library is reentrant.\n");
    +        iserror= True;
    +    }else if (qhullLibraryType!=QHULL_REENTRANT) { /* 2 */
    +        qh_fprintf_stderr(6262, "qh_lib_check: Expecting qhullLibraryType QHULL_NON_REENTRANT(0), QHULL_QH_POINTER(1), or QHULL_REENTRANT(2).  Got %d\n", qhullLibraryType);
    +        iserror= True;
    +    }
    +    if (qhTsize != sizeof(qhT)) {
    +        qh_fprintf_stderr(6249, "qh_lib_check: Incorrect qhull library called.  Size of qhT for caller is %d, but for library is %d.\n", qhTsize, sizeof(qhT));
    +        iserror= True;
    +    }
    +    if (vertexTsize != sizeof(vertexT)) {
    +        qh_fprintf_stderr(6250, "qh_lib_check: Incorrect qhull library called.  Size of vertexT for caller is %d, but for library is %d.\n", vertexTsize, sizeof(vertexT));
    +        iserror= True;
    +    }
    +    if (ridgeTsize != sizeof(ridgeT)) {
    +        qh_fprintf_stderr(6251, "qh_lib_check: Incorrect qhull library called.  Size of ridgeT for caller is %d, but for library is %d.\n", ridgeTsize, sizeof(ridgeT));
    +        iserror= True;
    +    }
    +    if (facetTsize != sizeof(facetT)) {
    +        qh_fprintf_stderr(6252, "qh_lib_check: Incorrect qhull library called.  Size of facetT for caller is %d, but for library is %d.\n", facetTsize, sizeof(facetT));
    +        iserror= True;
    +    }
    +    if (setTsize && setTsize != sizeof(setT)) {
    +        qh_fprintf_stderr(6253, "qh_lib_check: Incorrect qhull library called.  Size of setT for caller is %d, but for library is %d.\n", setTsize, sizeof(setT));
    +        iserror= True;
    +    }
    +    if (qhmemTsize && qhmemTsize != sizeof(qhmemT)) {
    +        qh_fprintf_stderr(6254, "qh_lib_check: Incorrect qhull library called.  Size of qhmemT for caller is %d, but for library is %d.\n", qhmemTsize, sizeof(qhmemT));
    +        iserror= True;
    +    }
    +    if (iserror) {
    +        qh_fprintf_stderr(6259, "qh_lib_check: Cannot continue.  Library '%s' is reentrant (e.g., qhull_r.so)\n", qh_version2);
    +        qh_exit(qh_ERRqhull);  /* can not use qh_errexit() */
    +    }
    +} /* lib_check */
    +
    +/*---------------------------------
    +
    +  qh_option(qh, option, intVal, realVal )
    +    add an option description to qh.qhull_options
    +
    +  notes:
    +    NOerrors -- qh_option can not call qh_errexit() [qh_initqhull_start2]
    +    will be printed with statistics ('Ts') and errors
    +    strlen(option) < 40
    +*/
    +void qh_option(qhT *qh, const char *option, int *i, realT *r) {
    +  char buf[200];
    +  int len, maxlen;
    +
    +  sprintf(buf, "  %s", option);
    +  if (i)
    +    sprintf(buf+strlen(buf), " %d", *i);
    +  if (r)
    +    sprintf(buf+strlen(buf), " %2.2g", *r);
    +  len= (int)strlen(buf);  /* WARN64 */
    +  qh->qhull_optionlen += len;
    +  maxlen= sizeof(qh->qhull_options) - len -1;
    +  maximize_(maxlen, 0);
    +  if (qh->qhull_optionlen >= qh_OPTIONline && maxlen > 0) {
    +    qh->qhull_optionlen= len;
    +    strncat(qh->qhull_options, "\n", (size_t)(maxlen--));
    +  }
    +  strncat(qh->qhull_options, buf, (size_t)maxlen);
    +} /* option */
    +
    +/*---------------------------------
    +
    +  qh_zero( qh, errfile )
    +    Initialize and zero Qhull's memory for qh_new_qhull()
    +
    +  notes:
    +    Not needed in global.c because static variables are initialized to zero
    +*/
    +void qh_zero(qhT *qh, FILE *errfile) {
    +    memset((char *)qh, 0, sizeof(qhT));   /* every field is 0, FALSE, NULL */
    +    qh->NOerrexit= True;
    +    qh_meminit(qh, errfile);
    +} /* zero */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/index.htm b/xs/src/qhull/src/libqhull_r/index.htm
    new file mode 100644
    index 0000000000..c62030e06b
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/index.htm
    @@ -0,0 +1,266 @@
    +
    +
    +
    +
    +Reentrant Qhull functions, macros, and data structures
    +
    +
    +
    +
    +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code
    +To: Qhull files
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser + +


    + + +

    Reentrant Qhull functions, macros, and data structures

    +
    +

    The following sections provide an overview and index to +reentrant Qhull's functions, macros, and data structures. +Each section starts with an introduction. +See also Calling +Qhull from C programs and Calling Qhull from C++ programs.

    + +

    Qhull uses the following conventions:

    +
    + +
      +
    • in code, global variables start with "qh " +
    • in documentation, global variables start with 'qh.' +
    • constants start with an upper case word +
    • important globals include an '_' +
    • functions, macros, and constants start with "qh_"
    • +
    • data types end in "T"
    • +
    • macros with arguments end in "_"
    • +
    • iterators are macros that use local variables
    • +
    • iterators for sets start with "FOREACH"
    • +
    • iterators for lists start with "FORALL"
    • +
    • qhull options are in single quotes (e.g., 'Pdn')
    • +
    • lists are sorted alphabetically
    • +
    • preprocessor directives on left margin for older compilers
    • +
    +
    +

    +Reentrant Qhull is nearly the same as non-reentrant Qhull. In reentrant +Qhull, the qhT data structure is the first parameter to most functions. Qhull accesses +this data structure with 'qh->...'. +In non-reentrant Qhull, the global data structure is either a struct (qh_QHpointer==0) +or a pointer (qh_QHpointer==1). The non-reentrant code looks different because this data +structure is accessed via the 'qh' macro. This macro expands to 'qh_qh.' or 'qh_qh->' (resp.). +

    +When reading code with an editor, a search for +'"function' +will locate the header of qh_function. A search for '* function' +will locate the tail of qh_function. + +

    A useful starting point is libqhull_r.h. It defines most +of Qhull data structures and top-level functions. Search for 'PFn' to +determine the corresponding constant in Qhull. Search for 'Fp' to +determine the corresponding qh_PRINT... constant. +Search io_r.c to learn how the print function is implemented.

    + +

    If your web browser is configured for .c and .h files, the function, macro, and data type links +go to the corresponding source location. To configure your web browser for .c and .h files. +

      +
    • In the Download Preferences or Options panel, add file extensions 'c' and 'h' to mime type 'text/html'. +
    • Opera 12.10 +
        +
      1. In Tools > Preferences > Advanced > Downloads +
      2. Uncheck 'Hide file types opened with Opera' +
      3. Quick find 'html' +
      4. Select 'text/html' > Edit +
      5. Add File extensions 'c,h,' +
      6. Click 'OK' +
      +
    • Internet Explorer -- Mime types are not available from 'Internet Options'. Is there a registry key for these settings? +
    • Firefox -- Mime types are not available from 'Preferences'. Is there an add-on to change the file extensions for a mime type? +
    • Chrome -- Can Chrome be configured? +
    + +

    +Please report documentation and link errors +to qhull-bug@qhull.org. +

    + +

    Copyright © 1997-2015 C.B. Barber

    + +
    + +

    »Qhull files

    +
    + +

    This sections lists the .c and .h files for Qhull. Please +refer to these files for detailed information.

    +
    + +
    +
    Makefile, CMakeLists.txt
    +
    Makefile is preconfigured for gcc. CMakeLists.txt supports multiple +platforms with CMake. +Qhull includes project files for Visual Studio and Qt. +
    + +
     
    +
    libqhull_r.h
    +
    Include file for the Qhull library (libqhull.so, qhull.dll, libqhullstatic.a). +Data structures are documented under Poly. +Global variables are documented under Global. +Other data structures and variables are documented under +Qhull or Geom.
    + +
     
    +
    Geom, +geom_r.h, +geom_r.c, +geom2_r.c, +random_r.c, +random_r.h
    +
    Geometric routines. These routines implement mathematical +functions such as Gaussian elimination and geometric +routines needed for Qhull. Frequently used routines are +in geom_r.c while infrequent ones are in geom2_r.c. +
    + +
     
    +
    Global, +global_r.c, +libqhull_r.h
    +
    Global routines. Qhull uses a global data structure, qh, +to store globally defined constants, lists, sets, and +variables. +global_r.c initializes and frees these +structures.
    + +
     
    +
    Io, io_r.h, +io_r.c
    +
    Input and output routines. Qhull provides a wide range of +input and output options.
    + +
     
    +
    Mem, +mem_r.h, +mem_r.c
    +
    Memory routines. Qhull provides memory allocation and +deallocation. It uses quick-fit allocation.
    + +
     
    +
    Merge, +merge_r.h, +merge_r.c
    +
    Merge routines. Qhull handles precision problems by +merged facets or joggled input. These routines merge simplicial facets, +merge non-simplicial facets, merge cycles of facets, and +rename redundant vertices.
    + +
     
    +
    Poly, +poly_r.h, +poly_r.c, +poly2_r.c, +libqhull_r.h
    +
    Polyhedral routines. Qhull produces a polyhedron as a +list of facets with vertices, neighbors, ridges, and +geometric information. libqhull_r.h defines the main +data structures. Frequently used routines are in poly_r.c +while infrequent ones are in poly2_r.c.
    + +
     
    +
    Qhull, +libqhull_r.c, +libqhull_r.h, +qhull_ra.h, +unix_r.c , +qconvex_r.c , +qdelaun_r.c , +qhalf_r.c , +qvoronoi_r.c
    +
    Top-level routines. The Quickhull algorithm is +implemented by libqhull_r.c. qhull_ra.h +includes all header files.
    + +
     
    +
    Set, +qset_r.h, +qset_r.c
    +
    Set routines. Qhull implements its data structures as +sets. A set is an array of pointers that is expanded as +needed. This is a separate package that may be used in +other applications.
    + +
     
    +
    Stat, +stat_r.h, +stat_r.c
    +
    Statistical routines. Qhull maintains statistics about +its implementation.
    + +
     
    +
    User, +user_r.h, +user_r.c, +user_eg_r.c, +user_eg2_r.c, +user_eg3_r.cpp, +
    +
    User-defined routines. Qhull allows the user to configure +the code with defined constants and specialized routines. +
    +
    +
    + +
    +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull files
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    + +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/io_r.c b/xs/src/qhull/src/libqhull_r/io_r.c new file mode 100644 index 0000000000..9721a000dd --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/io_r.c @@ -0,0 +1,4062 @@ +/*
      ---------------------------------
    +
    +   io_r.c
    +   Input/Output routines of qhull application
    +
    +   see qh-io_r.htm and io_r.h
    +
    +   see user_r.c for qh_errprint and qh_printfacetlist
    +
    +   unix_r.c calls qh_readpoints and qh_produce_output
    +
    +   unix_r.c and user_r.c are the only callers of io_r.c functions
    +   This allows the user to avoid loading io_r.o from qhull.a
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/io_r.c#4 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*========= -functions in alphabetical order after qh_produce_output(qh)  =====*/
    +
    +/*---------------------------------
    +
    +  qh_produce_output(qh)
    +  qh_produce_output2(qh)
    +    prints out the result of qhull in desired format
    +    qh_produce_output2(qh) does not call qh_prepare_output(qh)
    +    if qh.GETarea
    +      computes and prints area and volume
    +    qh.PRINTout[] is an array of output formats
    +
    +  notes:
    +    prints output in qh.PRINTout order
    +*/
    +void qh_produce_output(qhT *qh) {
    +    int tempsize= qh_setsize(qh, qh->qhmem.tempstack);
    +
    +    qh_prepare_output(qh);
    +    qh_produce_output2(qh);
    +    if (qh_setsize(qh, qh->qhmem.tempstack) != tempsize) {
    +        qh_fprintf(qh, qh->ferr, 6206, "qhull internal error (qh_produce_output): temporary sets not empty(%d)\n",
    +            qh_setsize(qh, qh->qhmem.tempstack));
    +        qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +} /* produce_output */
    +
    +
    +void qh_produce_output2(qhT *qh) {
    +  int i, tempsize= qh_setsize(qh, qh->qhmem.tempstack), d_1;
    +
    +  if (qh->PRINTsummary)
    +    qh_printsummary(qh, qh->ferr);
    +  else if (qh->PRINTout[0] == qh_PRINTnone)
    +    qh_printsummary(qh, qh->fout);
    +  for (i=0; i < qh_PRINTEND; i++)
    +    qh_printfacets(qh, qh->fout, qh->PRINTout[i], qh->facet_list, NULL, !qh_ALL);
    +  qh_allstatistics(qh);
    +  if (qh->PRINTprecision && !qh->MERGING && (qh->JOGGLEmax > REALmax/2 || qh->RERUN))
    +    qh_printstats(qh, qh->ferr, qh->qhstat.precision, NULL);
    +  if (qh->VERIFYoutput && (zzval_(Zridge) > 0 || zzval_(Zridgemid) > 0))
    +    qh_printstats(qh, qh->ferr, qh->qhstat.vridges, NULL);
    +  if (qh->PRINTstatistics) {
    +    qh_printstatistics(qh, qh->ferr, "");
    +    qh_memstatistics(qh, qh->ferr);
    +    d_1= sizeof(setT) + (qh->hull_dim - 1) * SETelemsize;
    +    qh_fprintf(qh, qh->ferr, 8040, "\
    +    size in bytes: merge %d ridge %d vertex %d facet %d\n\
    +         normal %d ridge vertices %d facet vertices or neighbors %d\n",
    +            (int)sizeof(mergeT), (int)sizeof(ridgeT),
    +            (int)sizeof(vertexT), (int)sizeof(facetT),
    +            qh->normal_size, d_1, d_1 + SETelemsize);
    +  }
    +  if (qh_setsize(qh, qh->qhmem.tempstack) != tempsize) {
    +    qh_fprintf(qh, qh->ferr, 6065, "qhull internal error (qh_produce_output2): temporary sets not empty(%d)\n",
    +             qh_setsize(qh, qh->qhmem.tempstack));
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +} /* produce_output2 */
    +
    +/*---------------------------------
    +
    +  qh_dfacet(qh, id )
    +    print facet by id, for debugging
    +
    +*/
    +void qh_dfacet(qhT *qh, unsigned id) {
    +  facetT *facet;
    +
    +  FORALLfacets {
    +    if (facet->id == id) {
    +      qh_printfacet(qh, qh->fout, facet);
    +      break;
    +    }
    +  }
    +} /* dfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_dvertex(qh, id )
    +    print vertex by id, for debugging
    +*/
    +void qh_dvertex(qhT *qh, unsigned id) {
    +  vertexT *vertex;
    +
    +  FORALLvertices {
    +    if (vertex->id == id) {
    +      qh_printvertex(qh, qh->fout, vertex);
    +      break;
    +    }
    +  }
    +} /* dvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_compare_facetarea(p1, p2 )
    +    used by qsort() to order facets by area
    +*/
    +int qh_compare_facetarea(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +
    +  if (!a->isarea)
    +    return -1;
    +  if (!b->isarea)
    +    return 1;
    +  if (a->f.area > b->f.area)
    +    return 1;
    +  else if (a->f.area == b->f.area)
    +    return 0;
    +  return -1;
    +} /* compare_facetarea */
    +
    +/*---------------------------------
    +
    +  qh_compare_facetmerge(p1, p2 )
    +    used by qsort() to order facets by number of merges
    +*/
    +int qh_compare_facetmerge(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +
    +  return(a->nummerge - b->nummerge);
    +} /* compare_facetvisit */
    +
    +/*---------------------------------
    +
    +  qh_compare_facetvisit(p1, p2 )
    +    used by qsort() to order facets by visit id or id
    +*/
    +int qh_compare_facetvisit(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +  int i,j;
    +
    +  if (!(i= a->visitid))
    +    i= 0 - a->id; /* do not convert to int, sign distinguishes id from visitid */
    +  if (!(j= b->visitid))
    +    j= 0 - b->id;
    +  return(i - j);
    +} /* compare_facetvisit */
    +
    +/*---------------------------------
    +
    +  qh_compare_vertexpoint( p1, p2 )
    +    used by qsort() to order vertices by point id
    +
    +  Not usable in qhulllib_r since qh_pointid depends on qh
    +
    +  int qh_compare_vertexpoint(const void *p1, const void *p2) {
    +  const vertexT *a= *((vertexT *const*)p1), *b= *((vertexT *const*)p2);
    +
    +  return((qh_pointid(qh, a->point) > qh_pointid(qh, b->point)?1:-1));
    +}*/
    +
    +/*---------------------------------
    +
    +  qh_copyfilename(qh, dest, size, source, length )
    +    copy filename identified by qh_skipfilename()
    +
    +  notes:
    +    see qh_skipfilename() for syntax
    +*/
    +void qh_copyfilename(qhT *qh, char *filename, int size, const char* source, int length) {
    +  char c= *source;
    +
    +  if (length > size + 1) {
    +      qh_fprintf(qh, qh->ferr, 6040, "qhull error: filename is more than %d characters, %s\n",  size-1, source);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  strncpy(filename, source, length);
    +  filename[length]= '\0';
    +  if (c == '\'' || c == '"') {
    +    char *s= filename + 1;
    +    char *t= filename;
    +    while (*s) {
    +      if (*s == c) {
    +          if (s[-1] == '\\')
    +              t[-1]= c;
    +      }else
    +          *t++= *s;
    +      s++;
    +    }
    +    *t= '\0';
    +  }
    +} /* copyfilename */
    +
    +/*---------------------------------
    +
    +  qh_countfacets(qh, facetlist, facets, printall,
    +          numfacets, numsimplicial, totneighbors, numridges, numcoplanar, numtricoplanars  )
    +    count good facets for printing and set visitid
    +    if allfacets, ignores qh_skipfacet()
    +
    +  notes:
    +    qh_printsummary and qh_countfacets must match counts
    +
    +  returns:
    +    numfacets, numsimplicial, total neighbors, numridges, coplanars
    +    each facet with ->visitid indicating 1-relative position
    +      ->visitid==0 indicates not good
    +
    +  notes
    +    numfacets >= numsimplicial
    +    if qh.NEWfacets,
    +      does not count visible facets (matches qh_printafacet)
    +
    +  design:
    +    for all facets on facetlist and in facets set
    +      unless facet is skipped or visible (i.e., will be deleted)
    +        mark facet->visitid
    +        update counts
    +*/
    +void qh_countfacets(qhT *qh, facetT *facetlist, setT *facets, boolT printall,
    +    int *numfacetsp, int *numsimplicialp, int *totneighborsp, int *numridgesp, int *numcoplanarsp, int *numtricoplanarsp) {
    +  facetT *facet, **facetp;
    +  int numfacets= 0, numsimplicial= 0, numridges= 0, totneighbors= 0, numcoplanars= 0, numtricoplanars= 0;
    +
    +  FORALLfacet_(facetlist) {
    +    if ((facet->visible && qh->NEWfacets)
    +    || (!printall && qh_skipfacet(qh, facet)))
    +      facet->visitid= 0;
    +    else {
    +      facet->visitid= ++numfacets;
    +      totneighbors += qh_setsize(qh, facet->neighbors);
    +      if (facet->simplicial) {
    +        numsimplicial++;
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else
    +        numridges += qh_setsize(qh, facet->ridges);
    +      if (facet->coplanarset)
    +        numcoplanars += qh_setsize(qh, facet->coplanarset);
    +    }
    +  }
    +
    +  FOREACHfacet_(facets) {
    +    if ((facet->visible && qh->NEWfacets)
    +    || (!printall && qh_skipfacet(qh, facet)))
    +      facet->visitid= 0;
    +    else {
    +      facet->visitid= ++numfacets;
    +      totneighbors += qh_setsize(qh, facet->neighbors);
    +      if (facet->simplicial){
    +        numsimplicial++;
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else
    +        numridges += qh_setsize(qh, facet->ridges);
    +      if (facet->coplanarset)
    +        numcoplanars += qh_setsize(qh, facet->coplanarset);
    +    }
    +  }
    +  qh->visit_id += numfacets+1;
    +  *numfacetsp= numfacets;
    +  *numsimplicialp= numsimplicial;
    +  *totneighborsp= totneighbors;
    +  *numridgesp= numridges;
    +  *numcoplanarsp= numcoplanars;
    +  *numtricoplanarsp= numtricoplanars;
    +} /* countfacets */
    +
    +/*---------------------------------
    +
    +  qh_detvnorm(qh, vertex, vertexA, centers, offset )
    +    compute separating plane of the Voronoi diagram for a pair of input sites
    +    centers= set of facets (i.e., Voronoi vertices)
    +      facet->visitid= 0 iff vertex-at-infinity (i.e., unbounded)
    +
    +  assumes:
    +    qh_ASvoronoi and qh_vertexneighbors() already set
    +
    +  returns:
    +    norm
    +      a pointer into qh.gm_matrix to qh.hull_dim-1 reals
    +      copy the data before reusing qh.gm_matrix
    +    offset
    +      if 'QVn'
    +        sign adjusted so that qh.GOODvertexp is inside
    +      else
    +        sign adjusted so that vertex is inside
    +
    +    qh.gm_matrix= simplex of points from centers relative to first center
    +
    +  notes:
    +    in io_r.c so that code for 'v Tv' can be removed by removing io_r.c
    +    returns pointer into qh.gm_matrix to avoid tracking of temporary memory
    +
    +  design:
    +    determine midpoint of input sites
    +    build points as the set of Voronoi vertices
    +    select a simplex from points (if necessary)
    +      include midpoint if the Voronoi region is unbounded
    +    relocate the first vertex of the simplex to the origin
    +    compute the normalized hyperplane through the simplex
    +    orient the hyperplane toward 'QVn' or 'vertex'
    +    if 'Tv' or 'Ts'
    +      if bounded
    +        test that hyperplane is the perpendicular bisector of the input sites
    +      test that Voronoi vertices not in the simplex are still on the hyperplane
    +    free up temporary memory
    +*/
    +pointT *qh_detvnorm(qhT *qh, vertexT *vertex, vertexT *vertexA, setT *centers, realT *offsetp) {
    +  facetT *facet, **facetp;
    +  int  i, k, pointid, pointidA, point_i, point_n;
    +  setT *simplex= NULL;
    +  pointT *point, **pointp, *point0, *midpoint, *normal, *inpoint;
    +  coordT *coord, *gmcoord, *normalp;
    +  setT *points= qh_settemp(qh, qh->TEMPsize);
    +  boolT nearzero= False;
    +  boolT unbounded= False;
    +  int numcenters= 0;
    +  int dim= qh->hull_dim - 1;
    +  realT dist, offset, angle, zero= 0.0;
    +
    +  midpoint= qh->gm_matrix + qh->hull_dim * qh->hull_dim;  /* last row */
    +  for (k=0; k < dim; k++)
    +    midpoint[k]= (vertex->point[k] + vertexA->point[k])/2;
    +  FOREACHfacet_(centers) {
    +    numcenters++;
    +    if (!facet->visitid)
    +      unbounded= True;
    +    else {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(qh, facet->vertices);
    +      qh_setappend(qh, &points, facet->center);
    +    }
    +  }
    +  if (numcenters > dim) {
    +    simplex= qh_settemp(qh, qh->TEMPsize);
    +    qh_setappend(qh, &simplex, vertex->point);
    +    if (unbounded)
    +      qh_setappend(qh, &simplex, midpoint);
    +    qh_maxsimplex(qh, dim, points, NULL, 0, &simplex);
    +    qh_setdelnth(qh, simplex, 0);
    +  }else if (numcenters == dim) {
    +    if (unbounded)
    +      qh_setappend(qh, &points, midpoint);
    +    simplex= points;
    +  }else {
    +    qh_fprintf(qh, qh->ferr, 6216, "qhull internal error (qh_detvnorm): too few points(%d) to compute separating plane\n", numcenters);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  i= 0;
    +  gmcoord= qh->gm_matrix;
    +  point0= SETfirstt_(simplex, pointT);
    +  FOREACHpoint_(simplex) {
    +    if (qh->IStracing >= 4)
    +      qh_printmatrix(qh, qh->ferr, "qh_detvnorm: Voronoi vertex or midpoint",
    +                              &point, 1, dim);
    +    if (point != point0) {
    +      qh->gm_row[i++]= gmcoord;
    +      coord= point0;
    +      for (k=dim; k--; )
    +        *(gmcoord++)= *point++ - *coord++;
    +    }
    +  }
    +  qh->gm_row[i]= gmcoord;  /* does not overlap midpoint, may be used later for qh_areasimplex */
    +  normal= gmcoord;
    +  qh_sethyperplane_gauss(qh, dim, qh->gm_row, point0, True,
    +                normal, &offset, &nearzero);
    +  if (qh->GOODvertexp == vertexA->point)
    +    inpoint= vertexA->point;
    +  else
    +    inpoint= vertex->point;
    +  zinc_(Zdistio);
    +  dist= qh_distnorm(dim, inpoint, normal, &offset);
    +  if (dist > 0) {
    +    offset= -offset;
    +    normalp= normal;
    +    for (k=dim; k--; ) {
    +      *normalp= -(*normalp);
    +      normalp++;
    +    }
    +  }
    +  if (qh->VERIFYoutput || qh->PRINTstatistics) {
    +    pointid= qh_pointid(qh, vertex->point);
    +    pointidA= qh_pointid(qh, vertexA->point);
    +    if (!unbounded) {
    +      zinc_(Zdiststat);
    +      dist= qh_distnorm(dim, midpoint, normal, &offset);
    +      if (dist < 0)
    +        dist= -dist;
    +      zzinc_(Zridgemid);
    +      wwmax_(Wridgemidmax, dist);
    +      wwadd_(Wridgemid, dist);
    +      trace4((qh, qh->ferr, 4014, "qh_detvnorm: points %d %d midpoint dist %2.2g\n",
    +                 pointid, pointidA, dist));
    +      for (k=0; k < dim; k++)
    +        midpoint[k]= vertexA->point[k] - vertex->point[k];  /* overwrites midpoint! */
    +      qh_normalize(qh, midpoint, dim, False);
    +      angle= qh_distnorm(dim, midpoint, normal, &zero); /* qh_detangle uses dim+1 */
    +      if (angle < 0.0)
    +        angle= angle + 1.0;
    +      else
    +        angle= angle - 1.0;
    +      if (angle < 0.0)
    +        angle -= angle;
    +      trace4((qh, qh->ferr, 4015, "qh_detvnorm: points %d %d angle %2.2g nearzero %d\n",
    +                 pointid, pointidA, angle, nearzero));
    +      if (nearzero) {
    +        zzinc_(Zridge0);
    +        wwmax_(Wridge0max, angle);
    +        wwadd_(Wridge0, angle);
    +      }else {
    +        zzinc_(Zridgeok)
    +        wwmax_(Wridgeokmax, angle);
    +        wwadd_(Wridgeok, angle);
    +      }
    +    }
    +    if (simplex != points) {
    +      FOREACHpoint_i_(qh, points) {
    +        if (!qh_setin(simplex, point)) {
    +          facet= SETelemt_(centers, point_i, facetT);
    +          zinc_(Zdiststat);
    +          dist= qh_distnorm(dim, point, normal, &offset);
    +          if (dist < 0)
    +            dist= -dist;
    +          zzinc_(Zridge);
    +          wwmax_(Wridgemax, dist);
    +          wwadd_(Wridge, dist);
    +          trace4((qh, qh->ferr, 4016, "qh_detvnorm: points %d %d Voronoi vertex %d dist %2.2g\n",
    +                             pointid, pointidA, facet->visitid, dist));
    +        }
    +      }
    +    }
    +  }
    +  *offsetp= offset;
    +  if (simplex != points)
    +    qh_settempfree(qh, &simplex);
    +  qh_settempfree(qh, &points);
    +  return normal;
    +} /* detvnorm */
    +
    +/*---------------------------------
    +
    +  qh_detvridge(qh, vertexA )
    +    determine Voronoi ridge from 'seen' neighbors of vertexA
    +    include one vertex-at-infinite if an !neighbor->visitid
    +
    +  returns:
    +    temporary set of centers (facets, i.e., Voronoi vertices)
    +    sorted by center id
    +*/
    +setT *qh_detvridge(qhT *qh, vertexT *vertex) {
    +  setT *centers= qh_settemp(qh, qh->TEMPsize);
    +  setT *tricenters= qh_settemp(qh, qh->TEMPsize);
    +  facetT *neighbor, **neighborp;
    +  boolT firstinf= True;
    +
    +  FOREACHneighbor_(vertex) {
    +    if (neighbor->seen) {
    +      if (neighbor->visitid) {
    +        if (!neighbor->tricoplanar || qh_setunique(qh, &tricenters, neighbor->center))
    +          qh_setappend(qh, ¢ers, neighbor);
    +      }else if (firstinf) {
    +        firstinf= False;
    +        qh_setappend(qh, ¢ers, neighbor);
    +      }
    +    }
    +  }
    +  qsort(SETaddr_(centers, facetT), (size_t)qh_setsize(qh, centers),
    +             sizeof(facetT *), qh_compare_facetvisit);
    +  qh_settempfree(qh, &tricenters);
    +  return centers;
    +} /* detvridge */
    +
    +/*---------------------------------
    +
    +  qh_detvridge3(qh, atvertex, vertex )
    +    determine 3-d Voronoi ridge from 'seen' neighbors of atvertex and vertex
    +    include one vertex-at-infinite for !neighbor->visitid
    +    assumes all facet->seen2= True
    +
    +  returns:
    +    temporary set of centers (facets, i.e., Voronoi vertices)
    +    listed in adjacency order (!oriented)
    +    all facet->seen2= True
    +
    +  design:
    +    mark all neighbors of atvertex
    +    for each adjacent neighbor of both atvertex and vertex
    +      if neighbor selected
    +        add neighbor to set of Voronoi vertices
    +*/
    +setT *qh_detvridge3(qhT *qh, vertexT *atvertex, vertexT *vertex) {
    +  setT *centers= qh_settemp(qh, qh->TEMPsize);
    +  setT *tricenters= qh_settemp(qh, qh->TEMPsize);
    +  facetT *neighbor, **neighborp, *facet= NULL;
    +  boolT firstinf= True;
    +
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen2= False;
    +  FOREACHneighbor_(vertex) {
    +    if (!neighbor->seen2) {
    +      facet= neighbor;
    +      break;
    +    }
    +  }
    +  while (facet) {
    +    facet->seen2= True;
    +    if (neighbor->seen) {
    +      if (facet->visitid) {
    +        if (!facet->tricoplanar || qh_setunique(qh, &tricenters, facet->center))
    +          qh_setappend(qh, ¢ers, facet);
    +      }else if (firstinf) {
    +        firstinf= False;
    +        qh_setappend(qh, ¢ers, facet);
    +      }
    +    }
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->seen2) {
    +        if (qh_setin(vertex->neighbors, neighbor))
    +          break;
    +        else
    +          neighbor->seen2= True;
    +      }
    +    }
    +    facet= neighbor;
    +  }
    +  if (qh->CHECKfrequently) {
    +    FOREACHneighbor_(vertex) {
    +      if (!neighbor->seen2) {
    +          qh_fprintf(qh, qh->ferr, 6217, "qhull internal error (qh_detvridge3): neighbors of vertex p%d are not connected at facet %d\n",
    +                 qh_pointid(qh, vertex->point), neighbor->id);
    +        qh_errexit(qh, qh_ERRqhull, neighbor, NULL);
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen2= True;
    +  qh_settempfree(qh, &tricenters);
    +  return centers;
    +} /* detvridge3 */
    +
    +/*---------------------------------
    +
    +  qh_eachvoronoi(qh, fp, printvridge, vertex, visitall, innerouter, inorder )
    +    if visitall,
    +      visit all Voronoi ridges for vertex (i.e., an input site)
    +    else
    +      visit all unvisited Voronoi ridges for vertex
    +      all vertex->seen= False if unvisited
    +    assumes
    +      all facet->seen= False
    +      all facet->seen2= True (for qh_detvridge3)
    +      all facet->visitid == 0 if vertex_at_infinity
    +                         == index of Voronoi vertex
    +                         >= qh.num_facets if ignored
    +    innerouter:
    +      qh_RIDGEall--  both inner (bounded) and outer(unbounded) ridges
    +      qh_RIDGEinner- only inner
    +      qh_RIDGEouter- only outer
    +
    +    if inorder
    +      orders vertices for 3-d Voronoi diagrams
    +
    +  returns:
    +    number of visited ridges (does not include previously visited ridges)
    +
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers)
    +        fp== any pointer (assumes FILE*)
    +        vertex,vertexA= pair of input sites that define a Voronoi ridge
    +        centers= set of facets (i.e., Voronoi vertices)
    +                 ->visitid == index or 0 if vertex_at_infinity
    +                 ordered for 3-d Voronoi diagram
    +  notes:
    +    uses qh.vertex_visit
    +
    +  see:
    +    qh_eachvoronoi_all()
    +
    +  design:
    +    mark selected neighbors of atvertex
    +    for each selected neighbor (either Voronoi vertex or vertex-at-infinity)
    +      for each unvisited vertex
    +        if atvertex and vertex share more than d-1 neighbors
    +          bump totalcount
    +          if printvridge defined
    +            build the set of shared neighbors (i.e., Voronoi vertices)
    +            call printvridge
    +*/
    +int qh_eachvoronoi(qhT *qh, FILE *fp, printvridgeT printvridge, vertexT *atvertex, boolT visitall, qh_RIDGE innerouter, boolT inorder) {
    +  boolT unbounded;
    +  int count;
    +  facetT *neighbor, **neighborp, *neighborA, **neighborAp;
    +  setT *centers;
    +  setT *tricenters= qh_settemp(qh, qh->TEMPsize);
    +
    +  vertexT *vertex, **vertexp;
    +  boolT firstinf;
    +  unsigned int numfacets= (unsigned int)qh->num_facets;
    +  int totridges= 0;
    +
    +  qh->vertex_visit++;
    +  atvertex->seen= True;
    +  if (visitall) {
    +    FORALLvertices
    +      vertex->seen= False;
    +  }
    +  FOREACHneighbor_(atvertex) {
    +    if (neighbor->visitid < numfacets)
    +      neighbor->seen= True;
    +  }
    +  FOREACHneighbor_(atvertex) {
    +    if (neighbor->seen) {
    +      FOREACHvertex_(neighbor->vertices) {
    +        if (vertex->visitid != qh->vertex_visit && !vertex->seen) {
    +          vertex->visitid= qh->vertex_visit;
    +          count= 0;
    +          firstinf= True;
    +          qh_settruncate(qh, tricenters, 0);
    +          FOREACHneighborA_(vertex) {
    +            if (neighborA->seen) {
    +              if (neighborA->visitid) {
    +                if (!neighborA->tricoplanar || qh_setunique(qh, &tricenters, neighborA->center))
    +                  count++;
    +              }else if (firstinf) {
    +                count++;
    +                firstinf= False;
    +              }
    +            }
    +          }
    +          if (count >= qh->hull_dim - 1) {  /* e.g., 3 for 3-d Voronoi */
    +            if (firstinf) {
    +              if (innerouter == qh_RIDGEouter)
    +                continue;
    +              unbounded= False;
    +            }else {
    +              if (innerouter == qh_RIDGEinner)
    +                continue;
    +              unbounded= True;
    +            }
    +            totridges++;
    +            trace4((qh, qh->ferr, 4017, "qh_eachvoronoi: Voronoi ridge of %d vertices between sites %d and %d\n",
    +                  count, qh_pointid(qh, atvertex->point), qh_pointid(qh, vertex->point)));
    +            if (printvridge && fp) {
    +              if (inorder && qh->hull_dim == 3+1) /* 3-d Voronoi diagram */
    +                centers= qh_detvridge3(qh, atvertex, vertex);
    +              else
    +                centers= qh_detvridge(qh, vertex);
    +              (*printvridge)(qh, fp, atvertex, vertex, centers, unbounded);
    +              qh_settempfree(qh, ¢ers);
    +            }
    +          }
    +        }
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen= False;
    +  qh_settempfree(qh, &tricenters);
    +  return totridges;
    +} /* eachvoronoi */
    +
    +
    +/*---------------------------------
    +
    +  qh_eachvoronoi_all(qh, fp, printvridge, isUpper, innerouter, inorder )
    +    visit all Voronoi ridges
    +
    +    innerouter:
    +      see qh_eachvoronoi()
    +
    +    if inorder
    +      orders vertices for 3-d Voronoi diagrams
    +
    +  returns
    +    total number of ridges
    +
    +    if isUpper == facet->upperdelaunay  (i.e., a Vornoi vertex)
    +      facet->visitid= Voronoi vertex index(same as 'o' format)
    +    else
    +      facet->visitid= 0
    +
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers)
    +      [see qh_eachvoronoi]
    +
    +  notes:
    +    Not used for qhull.exe
    +    same effect as qh_printvdiagram but ridges not sorted by point id
    +*/
    +int qh_eachvoronoi_all(qhT *qh, FILE *fp, printvridgeT printvridge, boolT isUpper, qh_RIDGE innerouter, boolT inorder) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  int numcenters= 1;  /* vertex 0 is vertex-at-infinity */
    +  int totridges= 0;
    +
    +  qh_clearcenters(qh, qh_ASvoronoi);
    +  qh_vertexneighbors(qh);
    +  maximize_(qh->visit_id, (unsigned) qh->num_facets);
    +  FORALLfacets {
    +    facet->visitid= 0;
    +    facet->seen= False;
    +    facet->seen2= True;
    +  }
    +  FORALLfacets {
    +    if (facet->upperdelaunay == isUpper)
    +      facet->visitid= numcenters++;
    +  }
    +  FORALLvertices
    +    vertex->seen= False;
    +  FORALLvertices {
    +    if (qh->GOODvertex > 0 && qh_pointid(qh, vertex->point)+1 != qh->GOODvertex)
    +      continue;
    +    totridges += qh_eachvoronoi(qh, fp, printvridge, vertex,
    +                   !qh_ALL, innerouter, inorder);
    +  }
    +  return totridges;
    +} /* eachvoronoi_all */
    +
    +/*---------------------------------
    +
    +  qh_facet2point(qh, facet, point0, point1, mindist )
    +    return two projected temporary vertices for a 2-d facet
    +    may be non-simplicial
    +
    +  returns:
    +    point0 and point1 oriented and projected to the facet
    +    returns mindist (maximum distance below plane)
    +*/
    +void qh_facet2point(qhT *qh, facetT *facet, pointT **point0, pointT **point1, realT *mindist) {
    +  vertexT *vertex0, *vertex1;
    +  realT dist;
    +
    +  if (facet->toporient ^ qh_ORIENTclock) {
    +    vertex0= SETfirstt_(facet->vertices, vertexT);
    +    vertex1= SETsecondt_(facet->vertices, vertexT);
    +  }else {
    +    vertex1= SETfirstt_(facet->vertices, vertexT);
    +    vertex0= SETsecondt_(facet->vertices, vertexT);
    +  }
    +  zadd_(Zdistio, 2);
    +  qh_distplane(qh, vertex0->point, facet, &dist);
    +  *mindist= dist;
    +  *point0= qh_projectpoint(qh, vertex0->point, facet, dist);
    +  qh_distplane(qh, vertex1->point, facet, &dist);
    +  minimize_(*mindist, dist);
    +  *point1= qh_projectpoint(qh, vertex1->point, facet, dist);
    +} /* facet2point */
    +
    +
    +/*---------------------------------
    +
    +  qh_facetvertices(qh, facetlist, facets, allfacets )
    +    returns temporary set of vertices in a set and/or list of facets
    +    if allfacets, ignores qh_skipfacet()
    +
    +  returns:
    +    vertices with qh.vertex_visit
    +
    +  notes:
    +    optimized for allfacets of facet_list
    +
    +  design:
    +    if allfacets of facet_list
    +      create vertex set from vertex_list
    +    else
    +      for each selected facet in facets or facetlist
    +        append unvisited vertices to vertex set
    +*/
    +setT *qh_facetvertices(qhT *qh, facetT *facetlist, setT *facets, boolT allfacets) {
    +  setT *vertices;
    +  facetT *facet, **facetp;
    +  vertexT *vertex, **vertexp;
    +
    +  qh->vertex_visit++;
    +  if (facetlist == qh->facet_list && allfacets && !facets) {
    +    vertices= qh_settemp(qh, qh->num_vertices);
    +    FORALLvertices {
    +      vertex->visitid= qh->vertex_visit;
    +      qh_setappend(qh, &vertices, vertex);
    +    }
    +  }else {
    +    vertices= qh_settemp(qh, qh->TEMPsize);
    +    FORALLfacet_(facetlist) {
    +      if (!allfacets && qh_skipfacet(qh, facet))
    +        continue;
    +      FOREACHvertex_(facet->vertices) {
    +        if (vertex->visitid != qh->vertex_visit) {
    +          vertex->visitid= qh->vertex_visit;
    +          qh_setappend(qh, &vertices, vertex);
    +        }
    +      }
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (!allfacets && qh_skipfacet(qh, facet))
    +      continue;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh->vertex_visit) {
    +        vertex->visitid= qh->vertex_visit;
    +        qh_setappend(qh, &vertices, vertex);
    +      }
    +    }
    +  }
    +  return vertices;
    +} /* facetvertices */
    +
    +/*---------------------------------
    +
    +  qh_geomplanes(qh, facet, outerplane, innerplane )
    +    return outer and inner planes for Geomview
    +    qh.PRINTradius is size of vertices and points (includes qh.JOGGLEmax)
    +
    +  notes:
    +    assume precise calculations in io.c with roundoff covered by qh_GEOMepsilon
    +*/
    +void qh_geomplanes(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane) {
    +  realT radius;
    +
    +  if (qh->MERGING || qh->JOGGLEmax < REALmax/2) {
    +    qh_outerinner(qh, facet, outerplane, innerplane);
    +    radius= qh->PRINTradius;
    +    if (qh->JOGGLEmax < REALmax/2)
    +      radius -= qh->JOGGLEmax * sqrt((realT)qh->hull_dim);  /* already accounted for in qh_outerinner() */
    +    *outerplane += radius;
    +    *innerplane -= radius;
    +    if (qh->PRINTcoplanar || qh->PRINTspheres) {
    +      *outerplane += qh->MAXabs_coord * qh_GEOMepsilon;
    +      *innerplane -= qh->MAXabs_coord * qh_GEOMepsilon;
    +    }
    +  }else
    +    *innerplane= *outerplane= 0;
    +} /* geomplanes */
    +
    +
    +/*---------------------------------
    +
    +  qh_markkeep(qh, facetlist )
    +    mark good facets that meet qh.KEEParea, qh.KEEPmerge, and qh.KEEPminArea
    +    ignores visible facets (!part of convex hull)
    +
    +  returns:
    +    may clear facet->good
    +    recomputes qh.num_good
    +
    +  design:
    +    get set of good facets
    +    if qh.KEEParea
    +      sort facets by area
    +      clear facet->good for all but n largest facets
    +    if qh.KEEPmerge
    +      sort facets by merge count
    +      clear facet->good for all but n most merged facets
    +    if qh.KEEPminarea
    +      clear facet->good if area too small
    +    update qh.num_good
    +*/
    +void qh_markkeep(qhT *qh, facetT *facetlist) {
    +  facetT *facet, **facetp;
    +  setT *facets= qh_settemp(qh, qh->num_facets);
    +  int size, count;
    +
    +  trace2((qh, qh->ferr, 2006, "qh_markkeep: only keep %d largest and/or %d most merged facets and/or min area %.2g\n",
    +          qh->KEEParea, qh->KEEPmerge, qh->KEEPminArea));
    +  FORALLfacet_(facetlist) {
    +    if (!facet->visible && facet->good)
    +      qh_setappend(qh, &facets, facet);
    +  }
    +  size= qh_setsize(qh, facets);
    +  if (qh->KEEParea) {
    +    qsort(SETaddr_(facets, facetT), (size_t)size,
    +             sizeof(facetT *), qh_compare_facetarea);
    +    if ((count= size - qh->KEEParea) > 0) {
    +      FOREACHfacet_(facets) {
    +        facet->good= False;
    +        if (--count == 0)
    +          break;
    +      }
    +    }
    +  }
    +  if (qh->KEEPmerge) {
    +    qsort(SETaddr_(facets, facetT), (size_t)size,
    +             sizeof(facetT *), qh_compare_facetmerge);
    +    if ((count= size - qh->KEEPmerge) > 0) {
    +      FOREACHfacet_(facets) {
    +        facet->good= False;
    +        if (--count == 0)
    +          break;
    +      }
    +    }
    +  }
    +  if (qh->KEEPminArea < REALmax/2) {
    +    FOREACHfacet_(facets) {
    +      if (!facet->isarea || facet->f.area < qh->KEEPminArea)
    +        facet->good= False;
    +    }
    +  }
    +  qh_settempfree(qh, &facets);
    +  count= 0;
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      count++;
    +  }
    +  qh->num_good= count;
    +} /* markkeep */
    +
    +
    +/*---------------------------------
    +
    +  qh_markvoronoi(qh, facetlist, facets, printall, isLower, numcenters )
    +    mark voronoi vertices for printing by site pairs
    +
    +  returns:
    +    temporary set of vertices indexed by pointid
    +    isLower set if printing lower hull (i.e., at least one facet is lower hull)
    +    numcenters= total number of Voronoi vertices
    +    bumps qh.printoutnum for vertex-at-infinity
    +    clears all facet->seen and sets facet->seen2
    +
    +    if selected
    +      facet->visitid= Voronoi vertex id
    +    else if upper hull (or 'Qu' and lower hull)
    +      facet->visitid= 0
    +    else
    +      facet->visitid >= qh->num_facets
    +
    +  notes:
    +    ignores qh.ATinfinity, if defined
    +*/
    +setT *qh_markvoronoi(qhT *qh, facetT *facetlist, setT *facets, boolT printall, boolT *isLowerp, int *numcentersp) {
    +  int numcenters=0;
    +  facetT *facet, **facetp;
    +  setT *vertices;
    +  boolT isLower= False;
    +
    +  qh->printoutnum++;
    +  qh_clearcenters(qh, qh_ASvoronoi);  /* in case, qh_printvdiagram2 called by user */
    +  qh_vertexneighbors(qh);
    +  vertices= qh_pointvertex(qh);
    +  if (qh->ATinfinity)
    +    SETelem_(vertices, qh->num_points-1)= NULL;
    +  qh->visit_id++;
    +  maximize_(qh->visit_id, (unsigned) qh->num_facets);
    +  FORALLfacet_(facetlist) {
    +    if (printall || !qh_skipfacet(qh, facet)) {
    +      if (!facet->upperdelaunay) {
    +        isLower= True;
    +        break;
    +      }
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (printall || !qh_skipfacet(qh, facet)) {
    +      if (!facet->upperdelaunay) {
    +        isLower= True;
    +        break;
    +      }
    +    }
    +  }
    +  FORALLfacets {
    +    if (facet->normal && (facet->upperdelaunay == isLower))
    +      facet->visitid= 0;  /* facetlist or facets may overwrite */
    +    else
    +      facet->visitid= qh->visit_id;
    +    facet->seen= False;
    +    facet->seen2= True;
    +  }
    +  numcenters++;  /* qh_INFINITE */
    +  FORALLfacet_(facetlist) {
    +    if (printall || !qh_skipfacet(qh, facet))
    +      facet->visitid= numcenters++;
    +  }
    +  FOREACHfacet_(facets) {
    +    if (printall || !qh_skipfacet(qh, facet))
    +      facet->visitid= numcenters++;
    +  }
    +  *isLowerp= isLower;
    +  *numcentersp= numcenters;
    +  trace2((qh, qh->ferr, 2007, "qh_markvoronoi: isLower %d numcenters %d\n", isLower, numcenters));
    +  return vertices;
    +} /* markvoronoi */
    +
    +/*---------------------------------
    +
    +  qh_order_vertexneighbors(qh, vertex )
    +    order facet neighbors of a 2-d or 3-d vertex by adjacency
    +
    +  notes:
    +    does not orient the neighbors
    +
    +  design:
    +    initialize a new neighbor set with the first facet in vertex->neighbors
    +    while vertex->neighbors non-empty
    +      select next neighbor in the previous facet's neighbor set
    +    set vertex->neighbors to the new neighbor set
    +*/
    +void qh_order_vertexneighbors(qhT *qh, vertexT *vertex) {
    +  setT *newset;
    +  facetT *facet, *neighbor, **neighborp;
    +
    +  trace4((qh, qh->ferr, 4018, "qh_order_vertexneighbors: order neighbors of v%d for 3-d\n", vertex->id));
    +  newset= qh_settemp(qh, qh_setsize(qh, vertex->neighbors));
    +  facet= (facetT*)qh_setdellast(vertex->neighbors);
    +  qh_setappend(qh, &newset, facet);
    +  while (qh_setsize(qh, vertex->neighbors)) {
    +    FOREACHneighbor_(vertex) {
    +      if (qh_setin(facet->neighbors, neighbor)) {
    +        qh_setdel(vertex->neighbors, neighbor);
    +        qh_setappend(qh, &newset, neighbor);
    +        facet= neighbor;
    +        break;
    +      }
    +    }
    +    if (!neighbor) {
    +      qh_fprintf(qh, qh->ferr, 6066, "qhull internal error (qh_order_vertexneighbors): no neighbor of v%d for f%d\n",
    +        vertex->id, facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +  }
    +  qh_setfree(qh, &vertex->neighbors);
    +  qh_settemppop(qh);
    +  vertex->neighbors= newset;
    +} /* order_vertexneighbors */
    +
    +/*---------------------------------
    +
    +  qh_prepare_output(qh, )
    +    prepare for qh_produce_output2(qh) according to
    +      qh.KEEPminArea, KEEParea, KEEPmerge, GOODvertex, GOODthreshold, GOODpoint, ONLYgood, SPLITthresholds
    +    does not reset facet->good
    +
    +  notes
    +    except for PRINTstatistics, no-op if previously called with same options
    +*/
    +void qh_prepare_output(qhT *qh) {
    +  if (qh->VORONOI) {
    +    qh_clearcenters(qh, qh_ASvoronoi);  /* must be before qh_triangulate */
    +    qh_vertexneighbors(qh);
    +  }
    +  if (qh->TRIangulate && !qh->hasTriangulation) {
    +    qh_triangulate(qh);
    +    if (qh->VERIFYoutput && !qh->CHECKfrequently)
    +      qh_checkpolygon(qh, qh->facet_list);
    +  }
    +  qh_findgood_all(qh, qh->facet_list);
    +  if (qh->GETarea)
    +    qh_getarea(qh, qh->facet_list);
    +  if (qh->KEEParea || qh->KEEPmerge || qh->KEEPminArea < REALmax/2)
    +    qh_markkeep(qh, qh->facet_list);
    +  if (qh->PRINTstatistics)
    +    qh_collectstatistics(qh);
    +}
    +
    +/*---------------------------------
    +
    +  qh_printafacet(qh, fp, format, facet, printall )
    +    print facet to fp in given output format (see qh.PRINTout)
    +
    +  returns:
    +    nop if !printall and qh_skipfacet()
    +    nop if visible facet and NEWfacets and format != PRINTfacets
    +    must match qh_countfacets
    +
    +  notes
    +    preserves qh.visit_id
    +    facet->normal may be null if PREmerge/MERGEexact and STOPcone before merge
    +
    +  see
    +    qh_printbegin() and qh_printend()
    +
    +  design:
    +    test for printing facet
    +    call appropriate routine for format
    +    or output results directly
    +*/
    +void qh_printafacet(qhT *qh, FILE *fp, qh_PRINT format, facetT *facet, boolT printall) {
    +  realT color[4], offset, dist, outerplane, innerplane;
    +  boolT zerodiv;
    +  coordT *point, *normp, *coordp, **pointp, *feasiblep;
    +  int k;
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +
    +  if (!printall && qh_skipfacet(qh, facet))
    +    return;
    +  if (facet->visible && qh->NEWfacets && format != qh_PRINTfacets)
    +    return;
    +  qh->printoutnum++;
    +  switch (format) {
    +  case qh_PRINTarea:
    +    if (facet->isarea) {
    +      qh_fprintf(qh, fp, 9009, qh_REAL_1, facet->f.area);
    +      qh_fprintf(qh, fp, 9010, "\n");
    +    }else
    +      qh_fprintf(qh, fp, 9011, "0\n");
    +    break;
    +  case qh_PRINTcoplanars:
    +    qh_fprintf(qh, fp, 9012, "%d", qh_setsize(qh, facet->coplanarset));
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_fprintf(qh, fp, 9013, " %d", qh_pointid(qh, point));
    +    qh_fprintf(qh, fp, 9014, "\n");
    +    break;
    +  case qh_PRINTcentrums:
    +    qh_printcenter(qh, fp, format, NULL, facet);
    +    break;
    +  case qh_PRINTfacets:
    +    qh_printfacet(qh, fp, facet);
    +    break;
    +  case qh_PRINTfacets_xridge:
    +    qh_printfacetheader(qh, fp, facet);
    +    break;
    +  case qh_PRINTgeom:  /* either 2 , 3, or 4-d by qh_printbegin */
    +    if (!facet->normal)
    +      break;
    +    for (k=qh->hull_dim; k--; ) {
    +      color[k]= (facet->normal[k]+1.0)/2.0;
    +      maximize_(color[k], -1.0);
    +      minimize_(color[k], +1.0);
    +    }
    +    qh_projectdim3(qh, color, color);
    +    if (qh->PRINTdim != qh->hull_dim)
    +      qh_normalize2(qh, color, 3, True, NULL, NULL);
    +    if (qh->hull_dim <= 2)
    +      qh_printfacet2geom(qh, fp, facet, color);
    +    else if (qh->hull_dim == 3) {
    +      if (facet->simplicial)
    +        qh_printfacet3geom_simplicial(qh, fp, facet, color);
    +      else
    +        qh_printfacet3geom_nonsimplicial(qh, fp, facet, color);
    +    }else {
    +      if (facet->simplicial)
    +        qh_printfacet4geom_simplicial(qh, fp, facet, color);
    +      else
    +        qh_printfacet4geom_nonsimplicial(qh, fp, facet, color);
    +    }
    +    break;
    +  case qh_PRINTids:
    +    qh_fprintf(qh, fp, 9015, "%d\n", facet->id);
    +    break;
    +  case qh_PRINTincidences:
    +  case qh_PRINToff:
    +  case qh_PRINTtriangles:
    +    if (qh->hull_dim == 3 && format != qh_PRINTtriangles)
    +      qh_printfacet3vertex(qh, fp, facet, format);
    +    else if (facet->simplicial || qh->hull_dim == 2 || format == qh_PRINToff)
    +      qh_printfacetNvertex_simplicial(qh, fp, facet, format);
    +    else
    +      qh_printfacetNvertex_nonsimplicial(qh, fp, facet, qh->printoutvar++, format);
    +    break;
    +  case qh_PRINTinner:
    +    qh_outerinner(qh, facet, NULL, &innerplane);
    +    offset= facet->offset - innerplane;
    +    goto LABELprintnorm;
    +    break; /* prevent warning */
    +  case qh_PRINTmerges:
    +    qh_fprintf(qh, fp, 9016, "%d\n", facet->nummerge);
    +    break;
    +  case qh_PRINTnormals:
    +    offset= facet->offset;
    +    goto LABELprintnorm;
    +    break; /* prevent warning */
    +  case qh_PRINTouter:
    +    qh_outerinner(qh, facet, &outerplane, NULL);
    +    offset= facet->offset - outerplane;
    +  LABELprintnorm:
    +    if (!facet->normal) {
    +      qh_fprintf(qh, fp, 9017, "no normal for facet f%d\n", facet->id);
    +      break;
    +    }
    +    if (qh->CDDoutput) {
    +      qh_fprintf(qh, fp, 9018, qh_REAL_1, -offset);
    +      for (k=0; k < qh->hull_dim; k++)
    +        qh_fprintf(qh, fp, 9019, qh_REAL_1, -facet->normal[k]);
    +    }else {
    +      for (k=0; k < qh->hull_dim; k++)
    +        qh_fprintf(qh, fp, 9020, qh_REAL_1, facet->normal[k]);
    +      qh_fprintf(qh, fp, 9021, qh_REAL_1, offset);
    +    }
    +    qh_fprintf(qh, fp, 9022, "\n");
    +    break;
    +  case qh_PRINTmathematica:  /* either 2 or 3-d by qh_printbegin */
    +  case qh_PRINTmaple:
    +    if (qh->hull_dim == 2)
    +      qh_printfacet2math(qh, fp, facet, format, qh->printoutvar++);
    +    else
    +      qh_printfacet3math(qh, fp, facet, format, qh->printoutvar++);
    +    break;
    +  case qh_PRINTneighbors:
    +    qh_fprintf(qh, fp, 9023, "%d", qh_setsize(qh, facet->neighbors));
    +    FOREACHneighbor_(facet)
    +      qh_fprintf(qh, fp, 9024, " %d",
    +               neighbor->visitid ? neighbor->visitid - 1: 0 - neighbor->id);
    +    qh_fprintf(qh, fp, 9025, "\n");
    +    break;
    +  case qh_PRINTpointintersect:
    +    if (!qh->feasible_point) {
    +      qh_fprintf(qh, qh->ferr, 6067, "qhull input error (qh_printafacet): option 'Fp' needs qh->feasible_point\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (facet->offset > 0)
    +      goto LABELprintinfinite;
    +    point= coordp= (coordT*)qh_memalloc(qh, qh->normal_size);
    +    normp= facet->normal;
    +    feasiblep= qh->feasible_point;
    +    if (facet->offset < -qh->MINdenom) {
    +      for (k=qh->hull_dim; k--; )
    +        *(coordp++)= (*(normp++) / - facet->offset) + *(feasiblep++);
    +    }else {
    +      for (k=qh->hull_dim; k--; ) {
    +        *(coordp++)= qh_divzero(*(normp++), facet->offset, qh->MINdenom_1,
    +                                 &zerodiv) + *(feasiblep++);
    +        if (zerodiv) {
    +          qh_memfree(qh, point, qh->normal_size);
    +          goto LABELprintinfinite;
    +        }
    +      }
    +    }
    +    qh_printpoint(qh, fp, NULL, point);
    +    qh_memfree(qh, point, qh->normal_size);
    +    break;
    +  LABELprintinfinite:
    +    for (k=qh->hull_dim; k--; )
    +      qh_fprintf(qh, fp, 9026, qh_REAL_1, qh_INFINITE);
    +    qh_fprintf(qh, fp, 9027, "\n");
    +    break;
    +  case qh_PRINTpointnearest:
    +    FOREACHpoint_(facet->coplanarset) {
    +      int id, id2;
    +      vertex= qh_nearvertex(qh, facet, point, &dist);
    +      id= qh_pointid(qh, vertex->point);
    +      id2= qh_pointid(qh, point);
    +      qh_fprintf(qh, fp, 9028, "%d %d %d " qh_REAL_1 "\n", id, id2, facet->id, dist);
    +    }
    +    break;
    +  case qh_PRINTpoints:  /* VORONOI only by qh_printbegin */
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9029, "1 ");
    +    qh_printcenter(qh, fp, format, NULL, facet);
    +    break;
    +  case qh_PRINTvertices:
    +    qh_fprintf(qh, fp, 9030, "%d", qh_setsize(qh, facet->vertices));
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(qh, fp, 9031, " %d", qh_pointid(qh, vertex->point));
    +    qh_fprintf(qh, fp, 9032, "\n");
    +    break;
    +  default:
    +    break;
    +  }
    +} /* printafacet */
    +
    +/*---------------------------------
    +
    +  qh_printbegin(qh, )
    +    prints header for all output formats
    +
    +  returns:
    +    checks for valid format
    +
    +  notes:
    +    uses qh.visit_id for 3/4off
    +    changes qh.interior_point if printing centrums
    +    qh_countfacets clears facet->visitid for non-good facets
    +
    +  see
    +    qh_printend() and qh_printafacet()
    +
    +  design:
    +    count facets and related statistics
    +    print header for format
    +*/
    +void qh_printbegin(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numcoplanars, numtricoplanars;
    +  int i, num;
    +  facetT *facet, **facetp;
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +  pointT *point, **pointp, *pointtemp;
    +
    +  qh->printoutnum= 0;
    +  qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);
    +  switch (format) {
    +  case qh_PRINTnone:
    +    break;
    +  case qh_PRINTarea:
    +    qh_fprintf(qh, fp, 9033, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTcoplanars:
    +    qh_fprintf(qh, fp, 9034, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTcentrums:
    +    if (qh->CENTERtype == qh_ASnone)
    +      qh_clearcenters(qh, qh_AScentrum);
    +    qh_fprintf(qh, fp, 9035, "%d\n%d\n", qh->hull_dim, numfacets);
    +    break;
    +  case qh_PRINTfacets:
    +  case qh_PRINTfacets_xridge:
    +    if (facetlist)
    +      qh_printvertexlist(qh, fp, "Vertices and facets:\n", facetlist, facets, printall);
    +    break;
    +  case qh_PRINTgeom:
    +    if (qh->hull_dim > 4)  /* qh_initqhull_globals also checks */
    +      goto LABELnoformat;
    +    if (qh->VORONOI && qh->hull_dim > 3)  /* PRINTdim == DROPdim == hull_dim-1 */
    +      goto LABELnoformat;
    +    if (qh->hull_dim == 2 && (qh->PRINTridges || qh->DOintersections))
    +      qh_fprintf(qh, qh->ferr, 7049, "qhull warning: output for ridges and intersections not implemented in 2-d\n");
    +    if (qh->hull_dim == 4 && (qh->PRINTinner || qh->PRINTouter ||
    +                             (qh->PRINTdim == 4 && qh->PRINTcentrums)))
    +      qh_fprintf(qh, qh->ferr, 7050, "qhull warning: output for outer/inner planes and centrums not implemented in 4-d\n");
    +    if (qh->PRINTdim == 4 && (qh->PRINTspheres))
    +      qh_fprintf(qh, qh->ferr, 7051, "qhull warning: output for vertices not implemented in 4-d\n");
    +    if (qh->PRINTdim == 4 && qh->DOintersections && qh->PRINTnoplanes)
    +      qh_fprintf(qh, qh->ferr, 7052, "qhull warning: 'Gnh' generates no output in 4-d\n");
    +    if (qh->PRINTdim == 2) {
    +      qh_fprintf(qh, fp, 9036, "{appearance {linewidth 3} LIST # %s | %s\n",
    +              qh->rbox_command, qh->qhull_command);
    +    }else if (qh->PRINTdim == 3) {
    +      qh_fprintf(qh, fp, 9037, "{appearance {+edge -evert linewidth 2} LIST # %s | %s\n",
    +              qh->rbox_command, qh->qhull_command);
    +    }else if (qh->PRINTdim == 4) {
    +      qh->visit_id++;
    +      num= 0;
    +      FORALLfacet_(facetlist)    /* get number of ridges to be printed */
    +        qh_printend4geom(qh, NULL, facet, &num, printall);
    +      FOREACHfacet_(facets)
    +        qh_printend4geom(qh, NULL, facet, &num, printall);
    +      qh->ridgeoutnum= num;
    +      qh->printoutvar= 0;  /* counts number of ridges in output */
    +      qh_fprintf(qh, fp, 9038, "LIST # %s | %s\n", qh->rbox_command, qh->qhull_command);
    +    }
    +
    +    if (qh->PRINTdots) {
    +      qh->printoutnum++;
    +      num= qh->num_points + qh_setsize(qh, qh->other_points);
    +      if (qh->DELAUNAY && qh->ATinfinity)
    +        num--;
    +      if (qh->PRINTdim == 4)
    +        qh_fprintf(qh, fp, 9039, "4VECT %d %d 1\n", num, num);
    +      else
    +        qh_fprintf(qh, fp, 9040, "VECT %d %d 1\n", num, num);
    +
    +      for (i=num; i--; ) {
    +        if (i % 20 == 0)
    +          qh_fprintf(qh, fp, 9041, "\n");
    +        qh_fprintf(qh, fp, 9042, "1 ");
    +      }
    +      qh_fprintf(qh, fp, 9043, "# 1 point per line\n1 ");
    +      for (i=num-1; i--; ) { /* num at least 3 for D2 */
    +        if (i % 20 == 0)
    +          qh_fprintf(qh, fp, 9044, "\n");
    +        qh_fprintf(qh, fp, 9045, "0 ");
    +      }
    +      qh_fprintf(qh, fp, 9046, "# 1 color for all\n");
    +      FORALLpoints {
    +        if (!qh->DELAUNAY || !qh->ATinfinity || qh_pointid(qh, point) != qh->num_points-1) {
    +          if (qh->PRINTdim == 4)
    +            qh_printpoint(qh, fp, NULL, point);
    +            else
    +              qh_printpoint3(qh, fp, point);
    +        }
    +      }
    +      FOREACHpoint_(qh->other_points) {
    +        if (qh->PRINTdim == 4)
    +          qh_printpoint(qh, fp, NULL, point);
    +        else
    +          qh_printpoint3(qh, fp, point);
    +      }
    +      qh_fprintf(qh, fp, 9047, "0 1 1 1  # color of points\n");
    +    }
    +
    +    if (qh->PRINTdim == 4  && !qh->PRINTnoplanes)
    +      /* 4dview loads up multiple 4OFF objects slowly */
    +      qh_fprintf(qh, fp, 9048, "4OFF %d %d 1\n", 3*qh->ridgeoutnum, qh->ridgeoutnum);
    +    qh->PRINTcradius= 2 * qh->DISTround;  /* include test DISTround */
    +    if (qh->PREmerge) {
    +      maximize_(qh->PRINTcradius, qh->premerge_centrum + qh->DISTround);
    +    }else if (qh->POSTmerge)
    +      maximize_(qh->PRINTcradius, qh->postmerge_centrum + qh->DISTround);
    +    qh->PRINTradius= qh->PRINTcradius;
    +    if (qh->PRINTspheres + qh->PRINTcoplanar)
    +      maximize_(qh->PRINTradius, qh->MAXabs_coord * qh_MINradius);
    +    if (qh->premerge_cos < REALmax/2) {
    +      maximize_(qh->PRINTradius, (1- qh->premerge_cos) * qh->MAXabs_coord);
    +    }else if (!qh->PREmerge && qh->POSTmerge && qh->postmerge_cos < REALmax/2) {
    +      maximize_(qh->PRINTradius, (1- qh->postmerge_cos) * qh->MAXabs_coord);
    +    }
    +    maximize_(qh->PRINTradius, qh->MINvisible);
    +    if (qh->JOGGLEmax < REALmax/2)
    +      qh->PRINTradius += qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
    +    if (qh->PRINTdim != 4 &&
    +        (qh->PRINTcoplanar || qh->PRINTspheres || qh->PRINTcentrums)) {
    +      vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +      if (qh->PRINTspheres && qh->PRINTdim <= 3)
    +        qh_printspheres(qh, fp, vertices, qh->PRINTradius);
    +      if (qh->PRINTcoplanar || qh->PRINTcentrums) {
    +        qh->firstcentrum= True;
    +        if (qh->PRINTcoplanar&& !qh->PRINTspheres) {
    +          FOREACHvertex_(vertices)
    +            qh_printpointvect2(qh, fp, vertex->point, NULL, qh->interior_point, qh->PRINTradius);
    +        }
    +        FORALLfacet_(facetlist) {
    +          if (!printall && qh_skipfacet(qh, facet))
    +            continue;
    +          if (!facet->normal)
    +            continue;
    +          if (qh->PRINTcentrums && qh->PRINTdim <= 3)
    +            qh_printcentrum(qh, fp, facet, qh->PRINTcradius);
    +          if (!qh->PRINTcoplanar)
    +            continue;
    +          FOREACHpoint_(facet->coplanarset)
    +            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
    +          FOREACHpoint_(facet->outsideset)
    +            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
    +        }
    +        FOREACHfacet_(facets) {
    +          if (!printall && qh_skipfacet(qh, facet))
    +            continue;
    +          if (!facet->normal)
    +            continue;
    +          if (qh->PRINTcentrums && qh->PRINTdim <= 3)
    +            qh_printcentrum(qh, fp, facet, qh->PRINTcradius);
    +          if (!qh->PRINTcoplanar)
    +            continue;
    +          FOREACHpoint_(facet->coplanarset)
    +            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
    +          FOREACHpoint_(facet->outsideset)
    +            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
    +        }
    +      }
    +      qh_settempfree(qh, &vertices);
    +    }
    +    qh->visit_id++; /* for printing hyperplane intersections */
    +    break;
    +  case qh_PRINTids:
    +    qh_fprintf(qh, fp, 9049, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTincidences:
    +    if (qh->VORONOI && qh->PRINTprecision)
    +      qh_fprintf(qh, qh->ferr, 7053, "qhull warning: writing Delaunay.  Use 'p' or 'o' for Voronoi centers\n");
    +    qh->printoutvar= qh->vertex_id;  /* centrum id for non-simplicial facets */
    +    if (qh->hull_dim <= 3)
    +      qh_fprintf(qh, fp, 9050, "%d\n", numfacets);
    +    else
    +      qh_fprintf(qh, fp, 9051, "%d\n", numsimplicial+numridges);
    +    break;
    +  case qh_PRINTinner:
    +  case qh_PRINTnormals:
    +  case qh_PRINTouter:
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9052, "%s | %s\nbegin\n    %d %d real\n", qh->rbox_command,
    +            qh->qhull_command, numfacets, qh->hull_dim+1);
    +    else
    +      qh_fprintf(qh, fp, 9053, "%d\n%d\n", qh->hull_dim+1, numfacets);
    +    break;
    +  case qh_PRINTmathematica:
    +  case qh_PRINTmaple:
    +    if (qh->hull_dim > 3)  /* qh_initbuffers also checks */
    +      goto LABELnoformat;
    +    if (qh->VORONOI)
    +      qh_fprintf(qh, qh->ferr, 7054, "qhull warning: output is the Delaunay triangulation\n");
    +    if (format == qh_PRINTmaple) {
    +      if (qh->hull_dim == 2)
    +        qh_fprintf(qh, fp, 9054, "PLOT(CURVES(\n");
    +      else
    +        qh_fprintf(qh, fp, 9055, "PLOT3D(POLYGONS(\n");
    +    }else
    +      qh_fprintf(qh, fp, 9056, "{\n");
    +    qh->printoutvar= 0;   /* counts number of facets for notfirst */
    +    break;
    +  case qh_PRINTmerges:
    +    qh_fprintf(qh, fp, 9057, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTpointintersect:
    +    qh_fprintf(qh, fp, 9058, "%d\n%d\n", qh->hull_dim, numfacets);
    +    break;
    +  case qh_PRINTneighbors:
    +    qh_fprintf(qh, fp, 9059, "%d\n", numfacets);
    +    break;
    +  case qh_PRINToff:
    +  case qh_PRINTtriangles:
    +    if (qh->VORONOI)
    +      goto LABELnoformat;
    +    num = qh->hull_dim;
    +    if (format == qh_PRINToff || qh->hull_dim == 2)
    +      qh_fprintf(qh, fp, 9060, "%d\n%d %d %d\n", num,
    +        qh->num_points+qh_setsize(qh, qh->other_points), numfacets, totneighbors/2);
    +    else { /* qh_PRINTtriangles */
    +      qh->printoutvar= qh->num_points+qh_setsize(qh, qh->other_points); /* first centrum */
    +      if (qh->DELAUNAY)
    +        num--;  /* drop last dimension */
    +      qh_fprintf(qh, fp, 9061, "%d\n%d %d %d\n", num, qh->printoutvar
    +        + numfacets - numsimplicial, numsimplicial + numridges, totneighbors/2);
    +    }
    +    FORALLpoints
    +      qh_printpointid(qh, qh->fout, NULL, num, point, qh_IDunknown);
    +    FOREACHpoint_(qh->other_points)
    +      qh_printpointid(qh, qh->fout, NULL, num, point, qh_IDunknown);
    +    if (format == qh_PRINTtriangles && qh->hull_dim > 2) {
    +      FORALLfacets {
    +        if (!facet->simplicial && facet->visitid)
    +          qh_printcenter(qh, qh->fout, format, NULL, facet);
    +      }
    +    }
    +    break;
    +  case qh_PRINTpointnearest:
    +    qh_fprintf(qh, fp, 9062, "%d\n", numcoplanars);
    +    break;
    +  case qh_PRINTpoints:
    +    if (!qh->VORONOI)
    +      goto LABELnoformat;
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9063, "%s | %s\nbegin\n%d %d real\n", qh->rbox_command,
    +           qh->qhull_command, numfacets, qh->hull_dim);
    +    else
    +      qh_fprintf(qh, fp, 9064, "%d\n%d\n", qh->hull_dim-1, numfacets);
    +    break;
    +  case qh_PRINTvertices:
    +    qh_fprintf(qh, fp, 9065, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTsummary:
    +  default:
    +  LABELnoformat:
    +    qh_fprintf(qh, qh->ferr, 6068, "qhull internal error (qh_printbegin): can not use this format for dimension %d\n",
    +         qh->hull_dim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +} /* printbegin */
    +
    +/*---------------------------------
    +
    +  qh_printcenter(qh, fp, string, facet )
    +    print facet->center as centrum or Voronoi center
    +    string may be NULL.  Don't include '%' codes.
    +    nop if qh->CENTERtype neither CENTERvoronoi nor CENTERcentrum
    +    if upper envelope of Delaunay triangulation and point at-infinity
    +      prints qh_INFINITE instead;
    +
    +  notes:
    +    defines facet->center if needed
    +    if format=PRINTgeom, adds a 0 if would otherwise be 2-d
    +    Same as QhullFacet::printCenter
    +*/
    +void qh_printcenter(qhT *qh, FILE *fp, qh_PRINT format, const char *string, facetT *facet) {
    +  int k, num;
    +
    +  if (qh->CENTERtype != qh_ASvoronoi && qh->CENTERtype != qh_AScentrum)
    +    return;
    +  if (string)
    +    qh_fprintf(qh, fp, 9066, string);
    +  if (qh->CENTERtype == qh_ASvoronoi) {
    +    num= qh->hull_dim-1;
    +    if (!facet->normal || !facet->upperdelaunay || !qh->ATinfinity) {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(qh, facet->vertices);
    +      for (k=0; k < num; k++)
    +        qh_fprintf(qh, fp, 9067, qh_REAL_1, facet->center[k]);
    +    }else {
    +      for (k=0; k < num; k++)
    +        qh_fprintf(qh, fp, 9068, qh_REAL_1, qh_INFINITE);
    +    }
    +  }else /* qh->CENTERtype == qh_AScentrum */ {
    +    num= qh->hull_dim;
    +    if (format == qh_PRINTtriangles && qh->DELAUNAY)
    +      num--;
    +    if (!facet->center)
    +      facet->center= qh_getcentrum(qh, facet);
    +    for (k=0; k < num; k++)
    +      qh_fprintf(qh, fp, 9069, qh_REAL_1, facet->center[k]);
    +  }
    +  if (format == qh_PRINTgeom && num == 2)
    +    qh_fprintf(qh, fp, 9070, " 0\n");
    +  else
    +    qh_fprintf(qh, fp, 9071, "\n");
    +} /* printcenter */
    +
    +/*---------------------------------
    +
    +  qh_printcentrum(qh, fp, facet, radius )
    +    print centrum for a facet in OOGL format
    +    radius defines size of centrum
    +    2-d or 3-d only
    +
    +  returns:
    +    defines facet->center if needed
    +*/
    +void qh_printcentrum(qhT *qh, FILE *fp, facetT *facet, realT radius) {
    +  pointT *centrum, *projpt;
    +  boolT tempcentrum= False;
    +  realT xaxis[4], yaxis[4], normal[4], dist;
    +  realT green[3]={0, 1, 0};
    +  vertexT *apex;
    +  int k;
    +
    +  if (qh->CENTERtype == qh_AScentrum) {
    +    if (!facet->center)
    +      facet->center= qh_getcentrum(qh, facet);
    +    centrum= facet->center;
    +  }else {
    +    centrum= qh_getcentrum(qh, facet);
    +    tempcentrum= True;
    +  }
    +  qh_fprintf(qh, fp, 9072, "{appearance {-normal -edge normscale 0} ");
    +  if (qh->firstcentrum) {
    +    qh->firstcentrum= False;
    +    qh_fprintf(qh, fp, 9073, "{INST geom { define centrum CQUAD  # f%d\n\
    +-0.3 -0.3 0.0001     0 0 1 1\n\
    + 0.3 -0.3 0.0001     0 0 1 1\n\
    + 0.3  0.3 0.0001     0 0 1 1\n\
    +-0.3  0.3 0.0001     0 0 1 1 } transform { \n", facet->id);
    +  }else
    +    qh_fprintf(qh, fp, 9074, "{INST geom { : centrum } transform { # f%d\n", facet->id);
    +  apex= SETfirstt_(facet->vertices, vertexT);
    +  qh_distplane(qh, apex->point, facet, &dist);
    +  projpt= qh_projectpoint(qh, apex->point, facet, dist);
    +  for (k=qh->hull_dim; k--; ) {
    +    xaxis[k]= projpt[k] - centrum[k];
    +    normal[k]= facet->normal[k];
    +  }
    +  if (qh->hull_dim == 2) {
    +    xaxis[2]= 0;
    +    normal[2]= 0;
    +  }else if (qh->hull_dim == 4) {
    +    qh_projectdim3(qh, xaxis, xaxis);
    +    qh_projectdim3(qh, normal, normal);
    +    qh_normalize2(qh, normal, qh->PRINTdim, True, NULL, NULL);
    +  }
    +  qh_crossproduct(3, xaxis, normal, yaxis);
    +  qh_fprintf(qh, fp, 9075, "%8.4g %8.4g %8.4g 0\n", xaxis[0], xaxis[1], xaxis[2]);
    +  qh_fprintf(qh, fp, 9076, "%8.4g %8.4g %8.4g 0\n", yaxis[0], yaxis[1], yaxis[2]);
    +  qh_fprintf(qh, fp, 9077, "%8.4g %8.4g %8.4g 0\n", normal[0], normal[1], normal[2]);
    +  qh_printpoint3(qh, fp, centrum);
    +  qh_fprintf(qh, fp, 9078, "1 }}}\n");
    +  qh_memfree(qh, projpt, qh->normal_size);
    +  qh_printpointvect(qh, fp, centrum, facet->normal, NULL, radius, green);
    +  if (tempcentrum)
    +    qh_memfree(qh, centrum, qh->normal_size);
    +} /* printcentrum */
    +
    +/*---------------------------------
    +
    +  qh_printend(qh, fp, format )
    +    prints trailer for all output formats
    +
    +  see:
    +    qh_printbegin() and qh_printafacet()
    +
    +*/
    +void qh_printend(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int num;
    +  facetT *facet, **facetp;
    +
    +  if (!qh->printoutnum)
    +    qh_fprintf(qh, qh->ferr, 7055, "qhull warning: no facets printed\n");
    +  switch (format) {
    +  case qh_PRINTgeom:
    +    if (qh->hull_dim == 4 && qh->DROPdim < 0  && !qh->PRINTnoplanes) {
    +      qh->visit_id++;
    +      num= 0;
    +      FORALLfacet_(facetlist)
    +        qh_printend4geom(qh, fp, facet,&num, printall);
    +      FOREACHfacet_(facets)
    +        qh_printend4geom(qh, fp, facet, &num, printall);
    +      if (num != qh->ridgeoutnum || qh->printoutvar != qh->ridgeoutnum) {
    +        qh_fprintf(qh, qh->ferr, 6069, "qhull internal error (qh_printend): number of ridges %d != number printed %d and at end %d\n", qh->ridgeoutnum, qh->printoutvar, num);
    +        qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +      }
    +    }else
    +      qh_fprintf(qh, fp, 9079, "}\n");
    +    break;
    +  case qh_PRINTinner:
    +  case qh_PRINTnormals:
    +  case qh_PRINTouter:
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9080, "end\n");
    +    break;
    +  case qh_PRINTmaple:
    +    qh_fprintf(qh, fp, 9081, "));\n");
    +    break;
    +  case qh_PRINTmathematica:
    +    qh_fprintf(qh, fp, 9082, "}\n");
    +    break;
    +  case qh_PRINTpoints:
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9083, "end\n");
    +    break;
    +  default:
    +    break;
    +  }
    +} /* printend */
    +
    +/*---------------------------------
    +
    +  qh_printend4geom(qh, fp, facet, numridges, printall )
    +    helper function for qh_printbegin/printend
    +
    +  returns:
    +    number of printed ridges
    +
    +  notes:
    +    just counts printed ridges if fp=NULL
    +    uses facet->visitid
    +    must agree with qh_printfacet4geom...
    +
    +  design:
    +    computes color for facet from its normal
    +    prints each ridge of facet
    +*/
    +void qh_printend4geom(qhT *qh, FILE *fp, facetT *facet, int *nump, boolT printall) {
    +  realT color[3];
    +  int i, num= *nump;
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (!printall && qh_skipfacet(qh, facet))
    +    return;
    +  if (qh->PRINTnoplanes || (facet->visible && qh->NEWfacets))
    +    return;
    +  if (!facet->normal)
    +    return;
    +  if (fp) {
    +    for (i=0; i < 3; i++) {
    +      color[i]= (facet->normal[i]+1.0)/2.0;
    +      maximize_(color[i], -1.0);
    +      minimize_(color[i], +1.0);
    +    }
    +  }
    +  facet->visitid= qh->visit_id;
    +  if (facet->simplicial) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh->visit_id) {
    +        if (fp)
    +          qh_fprintf(qh, fp, 9084, "3 %d %d %d %8.4g %8.4g %8.4g 1 # f%d f%d\n",
    +                 3*num, 3*num+1, 3*num+2, color[0], color[1], color[2],
    +                 facet->id, neighbor->id);
    +        num++;
    +      }
    +    }
    +  }else {
    +    FOREACHridge_(facet->ridges) {
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->visitid != qh->visit_id) {
    +        if (fp)
    +          qh_fprintf(qh, fp, 9085, "3 %d %d %d %8.4g %8.4g %8.4g 1 #r%d f%d f%d\n",
    +                 3*num, 3*num+1, 3*num+2, color[0], color[1], color[2],
    +                 ridge->id, facet->id, neighbor->id);
    +        num++;
    +      }
    +    }
    +  }
    +  *nump= num;
    +} /* printend4geom */
    +
    +/*---------------------------------
    +
    +  qh_printextremes(qh, fp, facetlist, facets, printall )
    +    print extreme points for convex hulls or halfspace intersections
    +
    +  notes:
    +    #points, followed by ids, one per line
    +
    +    sorted by id
    +    same order as qh_printpoints_out if no coplanar/interior points
    +*/
    +void qh_printextremes(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices, *points;
    +  pointT *point;
    +  vertexT *vertex, **vertexp;
    +  int id;
    +  int numpoints=0, point_i, point_n;
    +  int allpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +
    +  points= qh_settemp(qh, allpoints);
    +  qh_setzero(qh, points, 0, allpoints);
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  FOREACHvertex_(vertices) {
    +    id= qh_pointid(qh, vertex->point);
    +    if (id >= 0) {
    +      SETelem_(points, id)= vertex->point;
    +      numpoints++;
    +    }
    +  }
    +  qh_settempfree(qh, &vertices);
    +  qh_fprintf(qh, fp, 9086, "%d\n", numpoints);
    +  FOREACHpoint_i_(qh, points) {
    +    if (point)
    +      qh_fprintf(qh, fp, 9087, "%d\n", point_i);
    +  }
    +  qh_settempfree(qh, &points);
    +} /* printextremes */
    +
    +/*---------------------------------
    +
    +  qh_printextremes_2d(qh, fp, facetlist, facets, printall )
    +    prints point ids for facets in qh_ORIENTclock order
    +
    +  notes:
    +    #points, followed by ids, one per line
    +    if facetlist/facets are disjoint than the output includes skips
    +    errors if facets form a loop
    +    does not print coplanar points
    +*/
    +void qh_printextremes_2d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numridges, totneighbors, numcoplanars, numsimplicial, numtricoplanars;
    +  setT *vertices;
    +  facetT *facet, *startfacet, *nextfacet;
    +  vertexT *vertexA, *vertexB;
    +
    +  qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars); /* marks qh->visit_id */
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  qh_fprintf(qh, fp, 9088, "%d\n", qh_setsize(qh, vertices));
    +  qh_settempfree(qh, &vertices);
    +  if (!numfacets)
    +    return;
    +  facet= startfacet= facetlist ? facetlist : SETfirstt_(facets, facetT);
    +  qh->vertex_visit++;
    +  qh->visit_id++;
    +  do {
    +    if (facet->toporient ^ qh_ORIENTclock) {
    +      vertexA= SETfirstt_(facet->vertices, vertexT);
    +      vertexB= SETsecondt_(facet->vertices, vertexT);
    +      nextfacet= SETfirstt_(facet->neighbors, facetT);
    +    }else {
    +      vertexA= SETsecondt_(facet->vertices, vertexT);
    +      vertexB= SETfirstt_(facet->vertices, vertexT);
    +      nextfacet= SETsecondt_(facet->neighbors, facetT);
    +    }
    +    if (facet->visitid == qh->visit_id) {
    +      qh_fprintf(qh, qh->ferr, 6218, "Qhull internal error (qh_printextremes_2d): loop in facet list.  facet %d nextfacet %d\n",
    +                 facet->id, nextfacet->id);
    +      qh_errexit2(qh, qh_ERRqhull, facet, nextfacet);
    +    }
    +    if (facet->visitid) {
    +      if (vertexA->visitid != qh->vertex_visit) {
    +        vertexA->visitid= qh->vertex_visit;
    +        qh_fprintf(qh, fp, 9089, "%d\n", qh_pointid(qh, vertexA->point));
    +      }
    +      if (vertexB->visitid != qh->vertex_visit) {
    +        vertexB->visitid= qh->vertex_visit;
    +        qh_fprintf(qh, fp, 9090, "%d\n", qh_pointid(qh, vertexB->point));
    +      }
    +    }
    +    facet->visitid= qh->visit_id;
    +    facet= nextfacet;
    +  }while (facet && facet != startfacet);
    +} /* printextremes_2d */
    +
    +/*---------------------------------
    +
    +  qh_printextremes_d(qh, fp, facetlist, facets, printall )
    +    print extreme points of input sites for Delaunay triangulations
    +
    +  notes:
    +    #points, followed by ids, one per line
    +
    +    unordered
    +*/
    +void qh_printextremes_d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices;
    +  vertexT *vertex, **vertexp;
    +  boolT upperseen, lowerseen;
    +  facetT *neighbor, **neighborp;
    +  int numpoints=0;
    +
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  qh_vertexneighbors(qh);
    +  FOREACHvertex_(vertices) {
    +    upperseen= lowerseen= False;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay)
    +        upperseen= True;
    +      else
    +        lowerseen= True;
    +    }
    +    if (upperseen && lowerseen) {
    +      vertex->seen= True;
    +      numpoints++;
    +    }else
    +      vertex->seen= False;
    +  }
    +  qh_fprintf(qh, fp, 9091, "%d\n", numpoints);
    +  FOREACHvertex_(vertices) {
    +    if (vertex->seen)
    +      qh_fprintf(qh, fp, 9092, "%d\n", qh_pointid(qh, vertex->point));
    +  }
    +  qh_settempfree(qh, &vertices);
    +} /* printextremes_d */
    +
    +/*---------------------------------
    +
    +  qh_printfacet(qh, fp, facet )
    +    prints all fields of a facet to fp
    +
    +  notes:
    +    ridges printed in neighbor order
    +*/
    +void qh_printfacet(qhT *qh, FILE *fp, facetT *facet) {
    +
    +  qh_printfacetheader(qh, fp, facet);
    +  if (facet->ridges)
    +    qh_printfacetridges(qh, fp, facet);
    +} /* printfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet2geom(qh, fp, facet, color )
    +    print facet as part of a 2-d VECT for Geomview
    +
    +    notes:
    +      assume precise calculations in io_r.c with roundoff covered by qh_GEOMepsilon
    +      mindist is calculated within io_r.c.  maxoutside is calculated elsewhere
    +      so a DISTround error may have occurred.
    +*/
    +void qh_printfacet2geom(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  pointT *point0, *point1;
    +  realT mindist, innerplane, outerplane;
    +  int k;
    +
    +  qh_facet2point(qh, facet, &point0, &point1, &mindist);
    +  qh_geomplanes(qh, facet, &outerplane, &innerplane);
    +  if (qh->PRINTouter || (!qh->PRINTnoplanes && !qh->PRINTinner))
    +    qh_printfacet2geom_points(qh, fp, point0, point1, facet, outerplane, color);
    +  if (qh->PRINTinner || (!qh->PRINTnoplanes && !qh->PRINTouter &&
    +                outerplane - innerplane > 2 * qh->MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet2geom_points(qh, fp, point0, point1, facet, innerplane, color);
    +  }
    +  qh_memfree(qh, point1, qh->normal_size);
    +  qh_memfree(qh, point0, qh->normal_size);
    +} /* printfacet2geom */
    +
    +/*---------------------------------
    +
    +  qh_printfacet2geom_points(qh, fp, point1, point2, facet, offset, color )
    +    prints a 2-d facet as a VECT with 2 points at some offset.
    +    The points are on the facet's plane.
    +*/
    +void qh_printfacet2geom_points(qhT *qh, FILE *fp, pointT *point1, pointT *point2,
    +                               facetT *facet, realT offset, realT color[3]) {
    +  pointT *p1= point1, *p2= point2;
    +
    +  qh_fprintf(qh, fp, 9093, "VECT 1 2 1 2 1 # f%d\n", facet->id);
    +  if (offset != 0.0) {
    +    p1= qh_projectpoint(qh, p1, facet, -offset);
    +    p2= qh_projectpoint(qh, p2, facet, -offset);
    +  }
    +  qh_fprintf(qh, fp, 9094, "%8.4g %8.4g %8.4g\n%8.4g %8.4g %8.4g\n",
    +           p1[0], p1[1], 0.0, p2[0], p2[1], 0.0);
    +  if (offset != 0.0) {
    +    qh_memfree(qh, p1, qh->normal_size);
    +    qh_memfree(qh, p2, qh->normal_size);
    +  }
    +  qh_fprintf(qh, fp, 9095, "%8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +} /* printfacet2geom_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet2math(qh, fp, facet, format, notfirst )
    +    print 2-d Maple or Mathematica output for a facet
    +    may be non-simplicial
    +
    +  notes:
    +    use %16.8f since Mathematica 2.2 does not handle exponential format
    +    see qh_printfacet3math
    +*/
    +void qh_printfacet2math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst) {
    +  pointT *point0, *point1;
    +  realT mindist;
    +  const char *pointfmt;
    +
    +  qh_facet2point(qh, facet, &point0, &point1, &mindist);
    +  if (notfirst)
    +    qh_fprintf(qh, fp, 9096, ",");
    +  if (format == qh_PRINTmaple)
    +    pointfmt= "[[%16.8f, %16.8f], [%16.8f, %16.8f]]\n";
    +  else
    +    pointfmt= "Line[{{%16.8f, %16.8f}, {%16.8f, %16.8f}}]\n";
    +  qh_fprintf(qh, fp, 9097, pointfmt, point0[0], point0[1], point1[0], point1[1]);
    +  qh_memfree(qh, point1, qh->normal_size);
    +  qh_memfree(qh, point0, qh->normal_size);
    +} /* printfacet2math */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_nonsimplicial(qh, fp, facet, color )
    +    print Geomview OFF for a 3-d nonsimplicial facet.
    +    if DOintersections, prints ridges to unvisited neighbors(qh->visit_id)
    +
    +  notes
    +    uses facet->visitid for intersections and ridges
    +*/
    +void qh_printfacet3geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  ridgeT *ridge, **ridgep;
    +  setT *projectedpoints, *vertices;
    +  vertexT *vertex, **vertexp, *vertexA, *vertexB;
    +  pointT *projpt, *point, **pointp;
    +  facetT *neighbor;
    +  realT dist, outerplane, innerplane;
    +  int cntvertices, k;
    +  realT black[3]={0, 0, 0}, green[3]={0, 1, 0};
    +
    +  qh_geomplanes(qh, facet, &outerplane, &innerplane);
    +  vertices= qh_facet3vertex(qh, facet); /* oriented */
    +  cntvertices= qh_setsize(qh, vertices);
    +  projectedpoints= qh_settemp(qh, cntvertices);
    +  FOREACHvertex_(vertices) {
    +    zinc_(Zdistio);
    +    qh_distplane(qh, vertex->point, facet, &dist);
    +    projpt= qh_projectpoint(qh, vertex->point, facet, dist);
    +    qh_setappend(qh, &projectedpoints, projpt);
    +  }
    +  if (qh->PRINTouter || (!qh->PRINTnoplanes && !qh->PRINTinner))
    +    qh_printfacet3geom_points(qh, fp, projectedpoints, facet, outerplane, color);
    +  if (qh->PRINTinner || (!qh->PRINTnoplanes && !qh->PRINTouter &&
    +                outerplane - innerplane > 2 * qh->MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet3geom_points(qh, fp, projectedpoints, facet, innerplane, color);
    +  }
    +  FOREACHpoint_(projectedpoints)
    +    qh_memfree(qh, point, qh->normal_size);
    +  qh_settempfree(qh, &projectedpoints);
    +  qh_settempfree(qh, &vertices);
    +  if ((qh->DOintersections || qh->PRINTridges)
    +  && (!facet->visible || !qh->NEWfacets)) {
    +    facet->visitid= qh->visit_id;
    +    FOREACHridge_(facet->ridges) {
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->visitid != qh->visit_id) {
    +        if (qh->DOintersections)
    +          qh_printhyperplaneintersection(qh, fp, facet, neighbor, ridge->vertices, black);
    +        if (qh->PRINTridges) {
    +          vertexA= SETfirstt_(ridge->vertices, vertexT);
    +          vertexB= SETsecondt_(ridge->vertices, vertexT);
    +          qh_printline3geom(qh, fp, vertexA->point, vertexB->point, green);
    +        }
    +      }
    +    }
    +  }
    +} /* printfacet3geom_nonsimplicial */
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_points(qh, fp, points, facet, offset )
    +    prints a 3-d facet as OFF Geomview object.
    +    offset is relative to the facet's hyperplane
    +    Facet is determined as a list of points
    +*/
    +void qh_printfacet3geom_points(qhT *qh, FILE *fp, setT *points, facetT *facet, realT offset, realT color[3]) {
    +  int k, n= qh_setsize(qh, points), i;
    +  pointT *point, **pointp;
    +  setT *printpoints;
    +
    +  qh_fprintf(qh, fp, 9098, "{ OFF %d 1 1 # f%d\n", n, facet->id);
    +  if (offset != 0.0) {
    +    printpoints= qh_settemp(qh, n);
    +    FOREACHpoint_(points)
    +      qh_setappend(qh, &printpoints, qh_projectpoint(qh, point, facet, -offset));
    +  }else
    +    printpoints= points;
    +  FOREACHpoint_(printpoints) {
    +    for (k=0; k < qh->hull_dim; k++) {
    +      if (k == qh->DROPdim)
    +        qh_fprintf(qh, fp, 9099, "0 ");
    +      else
    +        qh_fprintf(qh, fp, 9100, "%8.4g ", point[k]);
    +    }
    +    if (printpoints != points)
    +      qh_memfree(qh, point, qh->normal_size);
    +    qh_fprintf(qh, fp, 9101, "\n");
    +  }
    +  if (printpoints != points)
    +    qh_settempfree(qh, &printpoints);
    +  qh_fprintf(qh, fp, 9102, "%d ", n);
    +  for (i=0; i < n; i++)
    +    qh_fprintf(qh, fp, 9103, "%d ", i);
    +  qh_fprintf(qh, fp, 9104, "%8.4g %8.4g %8.4g 1.0 }\n", color[0], color[1], color[2]);
    +} /* printfacet3geom_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_simplicial(qh, )
    +    print Geomview OFF for a 3-d simplicial facet.
    +
    +  notes:
    +    may flip color
    +    uses facet->visitid for intersections and ridges
    +
    +    assume precise calculations in io_r.c with roundoff covered by qh_GEOMepsilon
    +    innerplane may be off by qh->DISTround.  Maxoutside is calculated elsewhere
    +    so a DISTround error may have occurred.
    +*/
    +void qh_printfacet3geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  setT *points, *vertices;
    +  vertexT *vertex, **vertexp, *vertexA, *vertexB;
    +  facetT *neighbor, **neighborp;
    +  realT outerplane, innerplane;
    +  realT black[3]={0, 0, 0}, green[3]={0, 1, 0};
    +  int k;
    +
    +  qh_geomplanes(qh, facet, &outerplane, &innerplane);
    +  vertices= qh_facet3vertex(qh, facet);
    +  points= qh_settemp(qh, qh->TEMPsize);
    +  FOREACHvertex_(vertices)
    +    qh_setappend(qh, &points, vertex->point);
    +  if (qh->PRINTouter || (!qh->PRINTnoplanes && !qh->PRINTinner))
    +    qh_printfacet3geom_points(qh, fp, points, facet, outerplane, color);
    +  if (qh->PRINTinner || (!qh->PRINTnoplanes && !qh->PRINTouter &&
    +              outerplane - innerplane > 2 * qh->MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet3geom_points(qh, fp, points, facet, innerplane, color);
    +  }
    +  qh_settempfree(qh, &points);
    +  qh_settempfree(qh, &vertices);
    +  if ((qh->DOintersections || qh->PRINTridges)
    +  && (!facet->visible || !qh->NEWfacets)) {
    +    facet->visitid= qh->visit_id;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh->visit_id) {
    +        vertices= qh_setnew_delnthsorted(qh, facet->vertices, qh->hull_dim,
    +                          SETindex_(facet->neighbors, neighbor), 0);
    +        if (qh->DOintersections)
    +           qh_printhyperplaneintersection(qh, fp, facet, neighbor, vertices, black);
    +        if (qh->PRINTridges) {
    +          vertexA= SETfirstt_(vertices, vertexT);
    +          vertexB= SETsecondt_(vertices, vertexT);
    +          qh_printline3geom(qh, fp, vertexA->point, vertexB->point, green);
    +        }
    +        qh_setfree(qh, &vertices);
    +      }
    +    }
    +  }
    +} /* printfacet3geom_simplicial */
    +
    +/*---------------------------------
    +
    +  qh_printfacet3math(qh, fp, facet, notfirst )
    +    print 3-d Maple or Mathematica output for a facet
    +
    +  notes:
    +    may be non-simplicial
    +    use %16.8f since Mathematica 2.2 does not handle exponential format
    +    see qh_printfacet2math
    +*/
    +void qh_printfacet3math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst) {
    +  vertexT *vertex, **vertexp;
    +  setT *points, *vertices;
    +  pointT *point, **pointp;
    +  boolT firstpoint= True;
    +  realT dist;
    +  const char *pointfmt, *endfmt;
    +
    +  if (notfirst)
    +    qh_fprintf(qh, fp, 9105, ",\n");
    +  vertices= qh_facet3vertex(qh, facet);
    +  points= qh_settemp(qh, qh_setsize(qh, vertices));
    +  FOREACHvertex_(vertices) {
    +    zinc_(Zdistio);
    +    qh_distplane(qh, vertex->point, facet, &dist);
    +    point= qh_projectpoint(qh, vertex->point, facet, dist);
    +    qh_setappend(qh, &points, point);
    +  }
    +  if (format == qh_PRINTmaple) {
    +    qh_fprintf(qh, fp, 9106, "[");
    +    pointfmt= "[%16.8f, %16.8f, %16.8f]";
    +    endfmt= "]";
    +  }else {
    +    qh_fprintf(qh, fp, 9107, "Polygon[{");
    +    pointfmt= "{%16.8f, %16.8f, %16.8f}";
    +    endfmt= "}]";
    +  }
    +  FOREACHpoint_(points) {
    +    if (firstpoint)
    +      firstpoint= False;
    +    else
    +      qh_fprintf(qh, fp, 9108, ",\n");
    +    qh_fprintf(qh, fp, 9109, pointfmt, point[0], point[1], point[2]);
    +  }
    +  FOREACHpoint_(points)
    +    qh_memfree(qh, point, qh->normal_size);
    +  qh_settempfree(qh, &points);
    +  qh_settempfree(qh, &vertices);
    +  qh_fprintf(qh, fp, 9110, "%s", endfmt);
    +} /* printfacet3math */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3vertex(qh, fp, facet, format )
    +    print vertices in a 3-d facet as point ids
    +
    +  notes:
    +    prints number of vertices first if format == qh_PRINToff
    +    the facet may be non-simplicial
    +*/
    +void qh_printfacet3vertex(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +
    +  vertices= qh_facet3vertex(qh, facet);
    +  if (format == qh_PRINToff)
    +    qh_fprintf(qh, fp, 9111, "%d ", qh_setsize(qh, vertices));
    +  FOREACHvertex_(vertices)
    +    qh_fprintf(qh, fp, 9112, "%d ", qh_pointid(qh, vertex->point));
    +  qh_fprintf(qh, fp, 9113, "\n");
    +  qh_settempfree(qh, &vertices);
    +} /* printfacet3vertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet4geom_nonsimplicial(qh, )
    +    print Geomview 4OFF file for a 4d nonsimplicial facet
    +    prints all ridges to unvisited neighbors (qh.visit_id)
    +    if qh.DROPdim
    +      prints in OFF format
    +
    +  notes:
    +    must agree with printend4geom()
    +*/
    +void qh_printfacet4geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  facetT *neighbor;
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +  pointT *point;
    +  int k;
    +  realT dist;
    +
    +  facet->visitid= qh->visit_id;
    +  if (qh->PRINTnoplanes || (facet->visible && qh->NEWfacets))
    +    return;
    +  FOREACHridge_(facet->ridges) {
    +    neighbor= otherfacet_(ridge, facet);
    +    if (neighbor->visitid == qh->visit_id)
    +      continue;
    +    if (qh->PRINTtransparent && !neighbor->good)
    +      continue;
    +    if (qh->DOintersections)
    +      qh_printhyperplaneintersection(qh, fp, facet, neighbor, ridge->vertices, color);
    +    else {
    +      if (qh->DROPdim >= 0)
    +        qh_fprintf(qh, fp, 9114, "OFF 3 1 1 # f%d\n", facet->id);
    +      else {
    +        qh->printoutvar++;
    +        qh_fprintf(qh, fp, 9115, "# r%d between f%d f%d\n", ridge->id, facet->id, neighbor->id);
    +      }
    +      FOREACHvertex_(ridge->vertices) {
    +        zinc_(Zdistio);
    +        qh_distplane(qh, vertex->point,facet, &dist);
    +        point=qh_projectpoint(qh, vertex->point,facet, dist);
    +        for (k=0; k < qh->hull_dim; k++) {
    +          if (k != qh->DROPdim)
    +            qh_fprintf(qh, fp, 9116, "%8.4g ", point[k]);
    +        }
    +        qh_fprintf(qh, fp, 9117, "\n");
    +        qh_memfree(qh, point, qh->normal_size);
    +      }
    +      if (qh->DROPdim >= 0)
    +        qh_fprintf(qh, fp, 9118, "3 0 1 2 %8.4g %8.4g %8.4g\n", color[0], color[1], color[2]);
    +    }
    +  }
    +} /* printfacet4geom_nonsimplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet4geom_simplicial(qh, fp, facet, color )
    +    print Geomview 4OFF file for a 4d simplicial facet
    +    prints triangles for unvisited neighbors (qh.visit_id)
    +
    +  notes:
    +    must agree with printend4geom()
    +*/
    +void qh_printfacet4geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  setT *vertices;
    +  facetT *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  int k;
    +
    +  facet->visitid= qh->visit_id;
    +  if (qh->PRINTnoplanes || (facet->visible && qh->NEWfacets))
    +    return;
    +  FOREACHneighbor_(facet) {
    +    if (neighbor->visitid == qh->visit_id)
    +      continue;
    +    if (qh->PRINTtransparent && !neighbor->good)
    +      continue;
    +    vertices= qh_setnew_delnthsorted(qh, facet->vertices, qh->hull_dim,
    +                          SETindex_(facet->neighbors, neighbor), 0);
    +    if (qh->DOintersections)
    +      qh_printhyperplaneintersection(qh, fp, facet, neighbor, vertices, color);
    +    else {
    +      if (qh->DROPdim >= 0)
    +        qh_fprintf(qh, fp, 9119, "OFF 3 1 1 # ridge between f%d f%d\n",
    +                facet->id, neighbor->id);
    +      else {
    +        qh->printoutvar++;
    +        qh_fprintf(qh, fp, 9120, "# ridge between f%d f%d\n", facet->id, neighbor->id);
    +      }
    +      FOREACHvertex_(vertices) {
    +        for (k=0; k < qh->hull_dim; k++) {
    +          if (k != qh->DROPdim)
    +            qh_fprintf(qh, fp, 9121, "%8.4g ", vertex->point[k]);
    +        }
    +        qh_fprintf(qh, fp, 9122, "\n");
    +      }
    +      if (qh->DROPdim >= 0)
    +        qh_fprintf(qh, fp, 9123, "3 0 1 2 %8.4g %8.4g %8.4g\n", color[0], color[1], color[2]);
    +    }
    +    qh_setfree(qh, &vertices);
    +  }
    +} /* printfacet4geom_simplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetNvertex_nonsimplicial(qh, fp, facet, id, format )
    +    print vertices for an N-d non-simplicial facet
    +    triangulates each ridge to the id
    +*/
    +void qh_printfacetNvertex_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, int id, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (facet->visible && qh->NEWfacets)
    +    return;
    +  FOREACHridge_(facet->ridges) {
    +    if (format == qh_PRINTtriangles)
    +      qh_fprintf(qh, fp, 9124, "%d ", qh->hull_dim);
    +    qh_fprintf(qh, fp, 9125, "%d ", id);
    +    if ((ridge->top == facet) ^ qh_ORIENTclock) {
    +      FOREACHvertex_(ridge->vertices)
    +        qh_fprintf(qh, fp, 9126, "%d ", qh_pointid(qh, vertex->point));
    +    }else {
    +      FOREACHvertexreverse12_(ridge->vertices)
    +        qh_fprintf(qh, fp, 9127, "%d ", qh_pointid(qh, vertex->point));
    +    }
    +    qh_fprintf(qh, fp, 9128, "\n");
    +  }
    +} /* printfacetNvertex_nonsimplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetNvertex_simplicial(qh, fp, facet, format )
    +    print vertices for an N-d simplicial facet
    +    prints vertices for non-simplicial facets
    +      2-d facets (orientation preserved by qh_mergefacet2d)
    +      PRINToff ('o') for 4-d and higher
    +*/
    +void qh_printfacetNvertex_simplicial(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +
    +  if (format == qh_PRINToff || format == qh_PRINTtriangles)
    +    qh_fprintf(qh, fp, 9129, "%d ", qh_setsize(qh, facet->vertices));
    +  if ((facet->toporient ^ qh_ORIENTclock)
    +  || (qh->hull_dim > 2 && !facet->simplicial)) {
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(qh, fp, 9130, "%d ", qh_pointid(qh, vertex->point));
    +  }else {
    +    FOREACHvertexreverse12_(facet->vertices)
    +      qh_fprintf(qh, fp, 9131, "%d ", qh_pointid(qh, vertex->point));
    +  }
    +  qh_fprintf(qh, fp, 9132, "\n");
    +} /* printfacetNvertex_simplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetheader(qh, fp, facet )
    +    prints header fields of a facet to fp
    +
    +  notes:
    +    for 'f' output and debugging
    +    Same as QhullFacet::printHeader()
    +*/
    +void qh_printfacetheader(qhT *qh, FILE *fp, facetT *facet) {
    +  pointT *point, **pointp, *furthest;
    +  facetT *neighbor, **neighborp;
    +  realT dist;
    +
    +  if (facet == qh_MERGEridge) {
    +    qh_fprintf(qh, fp, 9133, " MERGEridge\n");
    +    return;
    +  }else if (facet == qh_DUPLICATEridge) {
    +    qh_fprintf(qh, fp, 9134, " DUPLICATEridge\n");
    +    return;
    +  }else if (!facet) {
    +    qh_fprintf(qh, fp, 9135, " NULLfacet\n");
    +    return;
    +  }
    +  qh->old_randomdist= qh->RANDOMdist;
    +  qh->RANDOMdist= False;
    +  qh_fprintf(qh, fp, 9136, "- f%d\n", facet->id);
    +  qh_fprintf(qh, fp, 9137, "    - flags:");
    +  if (facet->toporient)
    +    qh_fprintf(qh, fp, 9138, " top");
    +  else
    +    qh_fprintf(qh, fp, 9139, " bottom");
    +  if (facet->simplicial)
    +    qh_fprintf(qh, fp, 9140, " simplicial");
    +  if (facet->tricoplanar)
    +    qh_fprintf(qh, fp, 9141, " tricoplanar");
    +  if (facet->upperdelaunay)
    +    qh_fprintf(qh, fp, 9142, " upperDelaunay");
    +  if (facet->visible)
    +    qh_fprintf(qh, fp, 9143, " visible");
    +  if (facet->newfacet)
    +    qh_fprintf(qh, fp, 9144, " new");
    +  if (facet->tested)
    +    qh_fprintf(qh, fp, 9145, " tested");
    +  if (!facet->good)
    +    qh_fprintf(qh, fp, 9146, " notG");
    +  if (facet->seen)
    +    qh_fprintf(qh, fp, 9147, " seen");
    +  if (facet->coplanar)
    +    qh_fprintf(qh, fp, 9148, " coplanar");
    +  if (facet->mergehorizon)
    +    qh_fprintf(qh, fp, 9149, " mergehorizon");
    +  if (facet->keepcentrum)
    +    qh_fprintf(qh, fp, 9150, " keepcentrum");
    +  if (facet->dupridge)
    +    qh_fprintf(qh, fp, 9151, " dupridge");
    +  if (facet->mergeridge && !facet->mergeridge2)
    +    qh_fprintf(qh, fp, 9152, " mergeridge1");
    +  if (facet->mergeridge2)
    +    qh_fprintf(qh, fp, 9153, " mergeridge2");
    +  if (facet->newmerge)
    +    qh_fprintf(qh, fp, 9154, " newmerge");
    +  if (facet->flipped)
    +    qh_fprintf(qh, fp, 9155, " flipped");
    +  if (facet->notfurthest)
    +    qh_fprintf(qh, fp, 9156, " notfurthest");
    +  if (facet->degenerate)
    +    qh_fprintf(qh, fp, 9157, " degenerate");
    +  if (facet->redundant)
    +    qh_fprintf(qh, fp, 9158, " redundant");
    +  qh_fprintf(qh, fp, 9159, "\n");
    +  if (facet->isarea)
    +    qh_fprintf(qh, fp, 9160, "    - area: %2.2g\n", facet->f.area);
    +  else if (qh->NEWfacets && facet->visible && facet->f.replace)
    +    qh_fprintf(qh, fp, 9161, "    - replacement: f%d\n", facet->f.replace->id);
    +  else if (facet->newfacet) {
    +    if (facet->f.samecycle && facet->f.samecycle != facet)
    +      qh_fprintf(qh, fp, 9162, "    - shares same visible/horizon as f%d\n", facet->f.samecycle->id);
    +  }else if (facet->tricoplanar /* !isarea */) {
    +    if (facet->f.triowner)
    +      qh_fprintf(qh, fp, 9163, "    - owner of normal & centrum is facet f%d\n", facet->f.triowner->id);
    +  }else if (facet->f.newcycle)
    +    qh_fprintf(qh, fp, 9164, "    - was horizon to f%d\n", facet->f.newcycle->id);
    +  if (facet->nummerge)
    +    qh_fprintf(qh, fp, 9165, "    - merges: %d\n", facet->nummerge);
    +  qh_printpointid(qh, fp, "    - normal: ", qh->hull_dim, facet->normal, qh_IDunknown);
    +  qh_fprintf(qh, fp, 9166, "    - offset: %10.7g\n", facet->offset);
    +  if (qh->CENTERtype == qh_ASvoronoi || facet->center)
    +    qh_printcenter(qh, fp, qh_PRINTfacets, "    - center: ", facet);
    +#if qh_MAXoutside
    +  if (facet->maxoutside > qh->DISTround)
    +    qh_fprintf(qh, fp, 9167, "    - maxoutside: %10.7g\n", facet->maxoutside);
    +#endif
    +  if (!SETempty_(facet->outsideset)) {
    +    furthest= (pointT*)qh_setlast(facet->outsideset);
    +    if (qh_setsize(qh, facet->outsideset) < 6) {
    +      qh_fprintf(qh, fp, 9168, "    - outside set(furthest p%d):\n", qh_pointid(qh, furthest));
    +      FOREACHpoint_(facet->outsideset)
    +        qh_printpoint(qh, fp, "     ", point);
    +    }else if (qh_setsize(qh, facet->outsideset) < 21) {
    +      qh_printpoints(qh, fp, "    - outside set:", facet->outsideset);
    +    }else {
    +      qh_fprintf(qh, fp, 9169, "    - outside set:  %d points.", qh_setsize(qh, facet->outsideset));
    +      qh_printpoint(qh, fp, "  Furthest", furthest);
    +    }
    +#if !qh_COMPUTEfurthest
    +    qh_fprintf(qh, fp, 9170, "    - furthest distance= %2.2g\n", facet->furthestdist);
    +#endif
    +  }
    +  if (!SETempty_(facet->coplanarset)) {
    +    furthest= (pointT*)qh_setlast(facet->coplanarset);
    +    if (qh_setsize(qh, facet->coplanarset) < 6) {
    +      qh_fprintf(qh, fp, 9171, "    - coplanar set(furthest p%d):\n", qh_pointid(qh, furthest));
    +      FOREACHpoint_(facet->coplanarset)
    +        qh_printpoint(qh, fp, "     ", point);
    +    }else if (qh_setsize(qh, facet->coplanarset) < 21) {
    +      qh_printpoints(qh, fp, "    - coplanar set:", facet->coplanarset);
    +    }else {
    +      qh_fprintf(qh, fp, 9172, "    - coplanar set:  %d points.", qh_setsize(qh, facet->coplanarset));
    +      qh_printpoint(qh, fp, "  Furthest", furthest);
    +    }
    +    zinc_(Zdistio);
    +    qh_distplane(qh, furthest, facet, &dist);
    +    qh_fprintf(qh, fp, 9173, "      furthest distance= %2.2g\n", dist);
    +  }
    +  qh_printvertices(qh, fp, "    - vertices:", facet->vertices);
    +  qh_fprintf(qh, fp, 9174, "    - neighboring facets:");
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      qh_fprintf(qh, fp, 9175, " MERGE");
    +    else if (neighbor == qh_DUPLICATEridge)
    +      qh_fprintf(qh, fp, 9176, " DUP");
    +    else
    +      qh_fprintf(qh, fp, 9177, " f%d", neighbor->id);
    +  }
    +  qh_fprintf(qh, fp, 9178, "\n");
    +  qh->RANDOMdist= qh->old_randomdist;
    +} /* printfacetheader */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetridges(qh, fp, facet )
    +    prints ridges of a facet to fp
    +
    +  notes:
    +    ridges printed in neighbor order
    +    assumes the ridges exist
    +    for 'f' output
    +    same as QhullFacet::printRidges
    +*/
    +void qh_printfacetridges(qhT *qh, FILE *fp, facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int numridges= 0;
    +
    +
    +  if (facet->visible && qh->NEWfacets) {
    +    qh_fprintf(qh, fp, 9179, "    - ridges(ids may be garbage):");
    +    FOREACHridge_(facet->ridges)
    +      qh_fprintf(qh, fp, 9180, " r%d", ridge->id);
    +    qh_fprintf(qh, fp, 9181, "\n");
    +  }else {
    +    qh_fprintf(qh, fp, 9182, "    - ridges:\n");
    +    FOREACHridge_(facet->ridges)
    +      ridge->seen= False;
    +    if (qh->hull_dim == 3) {
    +      ridge= SETfirstt_(facet->ridges, ridgeT);
    +      while (ridge && !ridge->seen) {
    +        ridge->seen= True;
    +        qh_printridge(qh, fp, ridge);
    +        numridges++;
    +        ridge= qh_nextridge3d(ridge, facet, NULL);
    +        }
    +    }else {
    +      FOREACHneighbor_(facet) {
    +        FOREACHridge_(facet->ridges) {
    +          if (otherfacet_(ridge,facet) == neighbor) {
    +            ridge->seen= True;
    +            qh_printridge(qh, fp, ridge);
    +            numridges++;
    +          }
    +        }
    +      }
    +    }
    +    if (numridges != qh_setsize(qh, facet->ridges)) {
    +      qh_fprintf(qh, fp, 9183, "     - all ridges:");
    +      FOREACHridge_(facet->ridges)
    +        qh_fprintf(qh, fp, 9184, " r%d", ridge->id);
    +        qh_fprintf(qh, fp, 9185, "\n");
    +    }
    +    FOREACHridge_(facet->ridges) {
    +      if (!ridge->seen)
    +        qh_printridge(qh, fp, ridge);
    +    }
    +  }
    +} /* printfacetridges */
    +
    +/*---------------------------------
    +
    +  qh_printfacets(qh, fp, format, facetlist, facets, printall )
    +    prints facetlist and/or facet set in output format
    +
    +  notes:
    +    also used for specialized formats ('FO' and summary)
    +    turns off 'Rn' option since want actual numbers
    +*/
    +void qh_printfacets(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numcoplanars, numtricoplanars;
    +  facetT *facet, **facetp;
    +  setT *vertices;
    +  coordT *center;
    +  realT outerplane, innerplane;
    +
    +  qh->old_randomdist= qh->RANDOMdist;
    +  qh->RANDOMdist= False;
    +  if (qh->CDDoutput && (format == qh_PRINTcentrums || format == qh_PRINTpointintersect || format == qh_PRINToff))
    +    qh_fprintf(qh, qh->ferr, 7056, "qhull warning: CDD format is not available for centrums, halfspace\nintersections, and OFF file format.\n");
    +  if (format == qh_PRINTnone)
    +    ; /* print nothing */
    +  else if (format == qh_PRINTaverage) {
    +    vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +    center= qh_getcenter(qh, vertices);
    +    qh_fprintf(qh, fp, 9186, "%d 1\n", qh->hull_dim);
    +    qh_printpointid(qh, fp, NULL, qh->hull_dim, center, qh_IDunknown);
    +    qh_memfree(qh, center, qh->normal_size);
    +    qh_settempfree(qh, &vertices);
    +  }else if (format == qh_PRINTextremes) {
    +    if (qh->DELAUNAY)
    +      qh_printextremes_d(qh, fp, facetlist, facets, printall);
    +    else if (qh->hull_dim == 2)
    +      qh_printextremes_2d(qh, fp, facetlist, facets, printall);
    +    else
    +      qh_printextremes(qh, fp, facetlist, facets, printall);
    +  }else if (format == qh_PRINToptions)
    +    qh_fprintf(qh, fp, 9187, "Options selected for Qhull %s:\n%s\n", qh_version, qh->qhull_options);
    +  else if (format == qh_PRINTpoints && !qh->VORONOI)
    +    qh_printpoints_out(qh, fp, facetlist, facets, printall);
    +  else if (format == qh_PRINTqhull)
    +    qh_fprintf(qh, fp, 9188, "%s | %s\n", qh->rbox_command, qh->qhull_command);
    +  else if (format == qh_PRINTsize) {
    +    qh_fprintf(qh, fp, 9189, "0\n2 ");
    +    qh_fprintf(qh, fp, 9190, qh_REAL_1, qh->totarea);
    +    qh_fprintf(qh, fp, 9191, qh_REAL_1, qh->totvol);
    +    qh_fprintf(qh, fp, 9192, "\n");
    +  }else if (format == qh_PRINTsummary) {
    +    qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);
    +    vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +    qh_fprintf(qh, fp, 9193, "10 %d %d %d %d %d %d %d %d %d %d\n2 ", qh->hull_dim,
    +                qh->num_points + qh_setsize(qh, qh->other_points),
    +                qh->num_vertices, qh->num_facets - qh->num_visible,
    +                qh_setsize(qh, vertices), numfacets, numcoplanars,
    +                numfacets - numsimplicial, zzval_(Zdelvertextot),
    +                numtricoplanars);
    +    qh_settempfree(qh, &vertices);
    +    qh_outerinner(qh, NULL, &outerplane, &innerplane);
    +    qh_fprintf(qh, fp, 9194, qh_REAL_2n, outerplane, innerplane);
    +  }else if (format == qh_PRINTvneighbors)
    +    qh_printvneighbors(qh, fp, facetlist, facets, printall);
    +  else if (qh->VORONOI && format == qh_PRINToff)
    +    qh_printvoronoi(qh, fp, format, facetlist, facets, printall);
    +  else if (qh->VORONOI && format == qh_PRINTgeom) {
    +    qh_printbegin(qh, fp, format, facetlist, facets, printall);
    +    qh_printvoronoi(qh, fp, format, facetlist, facets, printall);
    +    qh_printend(qh, fp, format, facetlist, facets, printall);
    +  }else if (qh->VORONOI
    +  && (format == qh_PRINTvertices || format == qh_PRINTinner || format == qh_PRINTouter))
    +    qh_printvdiagram(qh, fp, format, facetlist, facets, printall);
    +  else {
    +    qh_printbegin(qh, fp, format, facetlist, facets, printall);
    +    FORALLfacet_(facetlist)
    +      qh_printafacet(qh, fp, format, facet, printall);
    +    FOREACHfacet_(facets)
    +      qh_printafacet(qh, fp, format, facet, printall);
    +    qh_printend(qh, fp, format, facetlist, facets, printall);
    +  }
    +  qh->RANDOMdist= qh->old_randomdist;
    +} /* printfacets */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhyperplaneintersection(qh, fp, facet1, facet2, vertices, color )
    +    print Geomview OFF or 4OFF for the intersection of two hyperplanes in 3-d or 4-d
    +*/
    +void qh_printhyperplaneintersection(qhT *qh, FILE *fp, facetT *facet1, facetT *facet2,
    +                   setT *vertices, realT color[3]) {
    +  realT costheta, denominator, dist1, dist2, s, t, mindenom, p[4];
    +  vertexT *vertex, **vertexp;
    +  int i, k;
    +  boolT nearzero1, nearzero2;
    +
    +  costheta= qh_getangle(qh, facet1->normal, facet2->normal);
    +  denominator= 1 - costheta * costheta;
    +  i= qh_setsize(qh, vertices);
    +  if (qh->hull_dim == 3)
    +    qh_fprintf(qh, fp, 9195, "VECT 1 %d 1 %d 1 ", i, i);
    +  else if (qh->hull_dim == 4 && qh->DROPdim >= 0)
    +    qh_fprintf(qh, fp, 9196, "OFF 3 1 1 ");
    +  else
    +    qh->printoutvar++;
    +  qh_fprintf(qh, fp, 9197, "# intersect f%d f%d\n", facet1->id, facet2->id);
    +  mindenom= 1 / (10.0 * qh->MAXabs_coord);
    +  FOREACHvertex_(vertices) {
    +    zadd_(Zdistio, 2);
    +    qh_distplane(qh, vertex->point, facet1, &dist1);
    +    qh_distplane(qh, vertex->point, facet2, &dist2);
    +    s= qh_divzero(-dist1 + costheta * dist2, denominator,mindenom,&nearzero1);
    +    t= qh_divzero(-dist2 + costheta * dist1, denominator,mindenom,&nearzero2);
    +    if (nearzero1 || nearzero2)
    +      s= t= 0.0;
    +    for (k=qh->hull_dim; k--; )
    +      p[k]= vertex->point[k] + facet1->normal[k] * s + facet2->normal[k] * t;
    +    if (qh->PRINTdim <= 3) {
    +      qh_projectdim3(qh, p, p);
    +      qh_fprintf(qh, fp, 9198, "%8.4g %8.4g %8.4g # ", p[0], p[1], p[2]);
    +    }else
    +      qh_fprintf(qh, fp, 9199, "%8.4g %8.4g %8.4g %8.4g # ", p[0], p[1], p[2], p[3]);
    +    if (nearzero1+nearzero2)
    +      qh_fprintf(qh, fp, 9200, "p%d(coplanar facets)\n", qh_pointid(qh, vertex->point));
    +    else
    +      qh_fprintf(qh, fp, 9201, "projected p%d\n", qh_pointid(qh, vertex->point));
    +  }
    +  if (qh->hull_dim == 3)
    +    qh_fprintf(qh, fp, 9202, "%8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +  else if (qh->hull_dim == 4 && qh->DROPdim >= 0)
    +    qh_fprintf(qh, fp, 9203, "3 0 1 2 %8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +} /* printhyperplaneintersection */
    +
    +/*---------------------------------
    +
    +  qh_printline3geom(qh, fp, pointA, pointB, color )
    +    prints a line as a VECT
    +    prints 0's for qh.DROPdim
    +
    +  notes:
    +    if pointA == pointB,
    +      it's a 1 point VECT
    +*/
    +void qh_printline3geom(qhT *qh, FILE *fp, pointT *pointA, pointT *pointB, realT color[3]) {
    +  int k;
    +  realT pA[4], pB[4];
    +
    +  qh_projectdim3(qh, pointA, pA);
    +  qh_projectdim3(qh, pointB, pB);
    +  if ((fabs(pA[0] - pB[0]) > 1e-3) ||
    +      (fabs(pA[1] - pB[1]) > 1e-3) ||
    +      (fabs(pA[2] - pB[2]) > 1e-3)) {
    +    qh_fprintf(qh, fp, 9204, "VECT 1 2 1 2 1\n");
    +    for (k=0; k < 3; k++)
    +       qh_fprintf(qh, fp, 9205, "%8.4g ", pB[k]);
    +    qh_fprintf(qh, fp, 9206, " # p%d\n", qh_pointid(qh, pointB));
    +  }else
    +    qh_fprintf(qh, fp, 9207, "VECT 1 1 1 1 1\n");
    +  for (k=0; k < 3; k++)
    +    qh_fprintf(qh, fp, 9208, "%8.4g ", pA[k]);
    +  qh_fprintf(qh, fp, 9209, " # p%d\n", qh_pointid(qh, pointA));
    +  qh_fprintf(qh, fp, 9210, "%8.4g %8.4g %8.4g 1\n", color[0], color[1], color[2]);
    +}
    +
    +/*---------------------------------
    +
    +  qh_printneighborhood(qh, fp, format, facetA, facetB, printall )
    +    print neighborhood of one or two facets
    +
    +  notes:
    +    calls qh_findgood_all()
    +    bumps qh.visit_id
    +*/
    +void qh_printneighborhood(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall) {
    +  facetT *neighbor, **neighborp, *facet;
    +  setT *facets;
    +
    +  if (format == qh_PRINTnone)
    +    return;
    +  qh_findgood_all(qh, qh->facet_list);
    +  if (facetA == facetB)
    +    facetB= NULL;
    +  facets= qh_settemp(qh, 2*(qh_setsize(qh, facetA->neighbors)+1));
    +  qh->visit_id++;
    +  for (facet= facetA; facet; facet= ((facet == facetA) ? facetB : NULL)) {
    +    if (facet->visitid != qh->visit_id) {
    +      facet->visitid= qh->visit_id;
    +      qh_setappend(qh, &facets, facet);
    +    }
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == qh->visit_id)
    +        continue;
    +      neighbor->visitid= qh->visit_id;
    +      if (printall || !qh_skipfacet(qh, neighbor))
    +        qh_setappend(qh, &facets, neighbor);
    +    }
    +  }
    +  qh_printfacets(qh, fp, format, NULL, facets, printall);
    +  qh_settempfree(qh, &facets);
    +} /* printneighborhood */
    +
    +/*---------------------------------
    +
    +  qh_printpoint(qh, fp, string, point )
    +  qh_printpointid(qh, fp, string, dim, point, id )
    +    prints the coordinates of a point
    +
    +  returns:
    +    if string is defined
    +      prints 'string p%d'.  Skips p%d if id=qh_IDunknown(-1) or qh_IDnone(-3)
    +
    +  notes:
    +    nop if point is NULL
    +    Same as QhullPoint's printPoint
    +*/
    +void qh_printpoint(qhT *qh, FILE *fp, const char *string, pointT *point) {
    +  int id= qh_pointid(qh, point);
    +
    +  qh_printpointid(qh, fp, string, qh->hull_dim, point, id);
    +} /* printpoint */
    +
    +void qh_printpointid(qhT *qh, FILE *fp, const char *string, int dim, pointT *point, int id) {
    +  int k;
    +  realT r; /*bug fix*/
    +
    +  if (!point)
    +    return;
    +  if (string) {
    +    qh_fprintf(qh, fp, 9211, "%s", string);
    +    if (id != qh_IDunknown && id != qh_IDnone)
    +      qh_fprintf(qh, fp, 9212, " p%d: ", id);
    +  }
    +  for (k=dim; k--; ) {
    +    r= *point++;
    +    if (string)
    +      qh_fprintf(qh, fp, 9213, " %8.4g", r);
    +    else
    +      qh_fprintf(qh, fp, 9214, qh_REAL_1, r);
    +  }
    +  qh_fprintf(qh, fp, 9215, "\n");
    +} /* printpointid */
    +
    +/*---------------------------------
    +
    +  qh_printpoint3(qh, fp, point )
    +    prints 2-d, 3-d, or 4-d point as Geomview 3-d coordinates
    +*/
    +void qh_printpoint3(qhT *qh, FILE *fp, pointT *point) {
    +  int k;
    +  realT p[4];
    +
    +  qh_projectdim3(qh, point, p);
    +  for (k=0; k < 3; k++)
    +    qh_fprintf(qh, fp, 9216, "%8.4g ", p[k]);
    +  qh_fprintf(qh, fp, 9217, " # p%d\n", qh_pointid(qh, point));
    +} /* printpoint3 */
    +
    +/*----------------------------------------
    +-printpoints- print pointids for a set of points starting at index
    +   see geom_r.c
    +*/
    +
    +/*---------------------------------
    +
    +  qh_printpoints_out(qh, fp, facetlist, facets, printall )
    +    prints vertices, coplanar/inside points, for facets by their point coordinates
    +    allows qh.CDDoutput
    +
    +  notes:
    +    same format as qhull input
    +    if no coplanar/interior points,
    +      same order as qh_printextremes
    +*/
    +void qh_printpoints_out(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  int allpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +  int numpoints=0, point_i, point_n;
    +  setT *vertices, *points;
    +  facetT *facet, **facetp;
    +  pointT *point, **pointp;
    +  vertexT *vertex, **vertexp;
    +  int id;
    +
    +  points= qh_settemp(qh, allpoints);
    +  qh_setzero(qh, points, 0, allpoints);
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  FOREACHvertex_(vertices) {
    +    id= qh_pointid(qh, vertex->point);
    +    if (id >= 0)
    +      SETelem_(points, id)= vertex->point;
    +  }
    +  if (qh->KEEPinside || qh->KEEPcoplanar || qh->KEEPnearinside) {
    +    FORALLfacet_(facetlist) {
    +      if (!printall && qh_skipfacet(qh, facet))
    +        continue;
    +      FOREACHpoint_(facet->coplanarset) {
    +        id= qh_pointid(qh, point);
    +        if (id >= 0)
    +          SETelem_(points, id)= point;
    +      }
    +    }
    +    FOREACHfacet_(facets) {
    +      if (!printall && qh_skipfacet(qh, facet))
    +        continue;
    +      FOREACHpoint_(facet->coplanarset) {
    +        id= qh_pointid(qh, point);
    +        if (id >= 0)
    +          SETelem_(points, id)= point;
    +      }
    +    }
    +  }
    +  qh_settempfree(qh, &vertices);
    +  FOREACHpoint_i_(qh, points) {
    +    if (point)
    +      numpoints++;
    +  }
    +  if (qh->CDDoutput)
    +    qh_fprintf(qh, fp, 9218, "%s | %s\nbegin\n%d %d real\n", qh->rbox_command,
    +             qh->qhull_command, numpoints, qh->hull_dim + 1);
    +  else
    +    qh_fprintf(qh, fp, 9219, "%d\n%d\n", qh->hull_dim, numpoints);
    +  FOREACHpoint_i_(qh, points) {
    +    if (point) {
    +      if (qh->CDDoutput)
    +        qh_fprintf(qh, fp, 9220, "1 ");
    +      qh_printpoint(qh, fp, NULL, point);
    +    }
    +  }
    +  if (qh->CDDoutput)
    +    qh_fprintf(qh, fp, 9221, "end\n");
    +  qh_settempfree(qh, &points);
    +} /* printpoints_out */
    +
    +
    +/*---------------------------------
    +
    +  qh_printpointvect(qh, fp, point, normal, center, radius, color )
    +    prints a 2-d, 3-d, or 4-d point as 3-d VECT's relative to normal or to center point
    +*/
    +void qh_printpointvect(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius, realT color[3]) {
    +  realT diff[4], pointA[4];
    +  int k;
    +
    +  for (k=qh->hull_dim; k--; ) {
    +    if (center)
    +      diff[k]= point[k]-center[k];
    +    else if (normal)
    +      diff[k]= normal[k];
    +    else
    +      diff[k]= 0;
    +  }
    +  if (center)
    +    qh_normalize2(qh, diff, qh->hull_dim, True, NULL, NULL);
    +  for (k=qh->hull_dim; k--; )
    +    pointA[k]= point[k]+diff[k] * radius;
    +  qh_printline3geom(qh, fp, point, pointA, color);
    +} /* printpointvect */
    +
    +/*---------------------------------
    +
    +  qh_printpointvect2(qh, fp, point, normal, center, radius )
    +    prints a 2-d, 3-d, or 4-d point as 2 3-d VECT's for an imprecise point
    +*/
    +void qh_printpointvect2(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius) {
    +  realT red[3]={1, 0, 0}, yellow[3]={1, 1, 0};
    +
    +  qh_printpointvect(qh, fp, point, normal, center, radius, red);
    +  qh_printpointvect(qh, fp, point, normal, center, -radius, yellow);
    +} /* printpointvect2 */
    +
    +/*---------------------------------
    +
    +  qh_printridge(qh, fp, ridge )
    +    prints the information in a ridge
    +
    +  notes:
    +    for qh_printfacetridges()
    +    same as operator<< [QhullRidge.cpp]
    +*/
    +void qh_printridge(qhT *qh, FILE *fp, ridgeT *ridge) {
    +
    +  qh_fprintf(qh, fp, 9222, "     - r%d", ridge->id);
    +  if (ridge->tested)
    +    qh_fprintf(qh, fp, 9223, " tested");
    +  if (ridge->nonconvex)
    +    qh_fprintf(qh, fp, 9224, " nonconvex");
    +  qh_fprintf(qh, fp, 9225, "\n");
    +  qh_printvertices(qh, fp, "           vertices:", ridge->vertices);
    +  if (ridge->top && ridge->bottom)
    +    qh_fprintf(qh, fp, 9226, "           between f%d and f%d\n",
    +            ridge->top->id, ridge->bottom->id);
    +} /* printridge */
    +
    +/*---------------------------------
    +
    +  qh_printspheres(qh, fp, vertices, radius )
    +    prints 3-d vertices as OFF spheres
    +
    +  notes:
    +    inflated octahedron from Stuart Levy earth/mksphere2
    +*/
    +void qh_printspheres(qhT *qh, FILE *fp, setT *vertices, realT radius) {
    +  vertexT *vertex, **vertexp;
    +
    +  qh->printoutnum++;
    +  qh_fprintf(qh, fp, 9227, "{appearance {-edge -normal normscale 0} {\n\
    +INST geom {define vsphere OFF\n\
    +18 32 48\n\
    +\n\
    +0 0 1\n\
    +1 0 0\n\
    +0 1 0\n\
    +-1 0 0\n\
    +0 -1 0\n\
    +0 0 -1\n\
    +0.707107 0 0.707107\n\
    +0 -0.707107 0.707107\n\
    +0.707107 -0.707107 0\n\
    +-0.707107 0 0.707107\n\
    +-0.707107 -0.707107 0\n\
    +0 0.707107 0.707107\n\
    +-0.707107 0.707107 0\n\
    +0.707107 0.707107 0\n\
    +0.707107 0 -0.707107\n\
    +0 0.707107 -0.707107\n\
    +-0.707107 0 -0.707107\n\
    +0 -0.707107 -0.707107\n\
    +\n\
    +3 0 6 11\n\
    +3 0 7 6 \n\
    +3 0 9 7 \n\
    +3 0 11 9\n\
    +3 1 6 8 \n\
    +3 1 8 14\n\
    +3 1 13 6\n\
    +3 1 14 13\n\
    +3 2 11 13\n\
    +3 2 12 11\n\
    +3 2 13 15\n\
    +3 2 15 12\n\
    +3 3 9 12\n\
    +3 3 10 9\n\
    +3 3 12 16\n\
    +3 3 16 10\n\
    +3 4 7 10\n\
    +3 4 8 7\n\
    +3 4 10 17\n\
    +3 4 17 8\n\
    +3 5 14 17\n\
    +3 5 15 14\n\
    +3 5 16 15\n\
    +3 5 17 16\n\
    +3 6 13 11\n\
    +3 7 8 6\n\
    +3 9 10 7\n\
    +3 11 12 9\n\
    +3 14 8 17\n\
    +3 15 13 14\n\
    +3 16 12 15\n\
    +3 17 10 16\n} transforms { TLIST\n");
    +  FOREACHvertex_(vertices) {
    +    qh_fprintf(qh, fp, 9228, "%8.4g 0 0 0 # v%d\n 0 %8.4g 0 0\n0 0 %8.4g 0\n",
    +      radius, vertex->id, radius, radius);
    +    qh_printpoint3(qh, fp, vertex->point);
    +    qh_fprintf(qh, fp, 9229, "1\n");
    +  }
    +  qh_fprintf(qh, fp, 9230, "}}}\n");
    +} /* printspheres */
    +
    +
    +/*----------------------------------------------
    +-printsummary-
    +                see libqhull_r.c
    +*/
    +
    +/*---------------------------------
    +
    +  qh_printvdiagram(qh, fp, format, facetlist, facets, printall )
    +    print voronoi diagram
    +      # of pairs of input sites
    +      #indices site1 site2 vertex1 ...
    +
    +    sites indexed by input point id
    +      point 0 is the first input point
    +    vertices indexed by 'o' and 'p' order
    +      vertex 0 is the 'vertex-at-infinity'
    +      vertex 1 is the first Voronoi vertex
    +
    +  see:
    +    qh_printvoronoi()
    +    qh_eachvoronoi_all()
    +
    +  notes:
    +    if all facets are upperdelaunay,
    +      prints upper hull (furthest-site Voronoi diagram)
    +*/
    +void qh_printvdiagram(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices;
    +  int totcount, numcenters;
    +  boolT isLower;
    +  qh_RIDGE innerouter= qh_RIDGEall;
    +  printvridgeT printvridge= NULL;
    +
    +  if (format == qh_PRINTvertices) {
    +    innerouter= qh_RIDGEall;
    +    printvridge= qh_printvridge;
    +  }else if (format == qh_PRINTinner) {
    +    innerouter= qh_RIDGEinner;
    +    printvridge= qh_printvnorm;
    +  }else if (format == qh_PRINTouter) {
    +    innerouter= qh_RIDGEouter;
    +    printvridge= qh_printvnorm;
    +  }else {
    +    qh_fprintf(qh, qh->ferr, 6219, "Qhull internal error (qh_printvdiagram): unknown print format %d.\n", format);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  vertices= qh_markvoronoi(qh, facetlist, facets, printall, &isLower, &numcenters);
    +  totcount= qh_printvdiagram2(qh, NULL, NULL, vertices, innerouter, False);
    +  qh_fprintf(qh, fp, 9231, "%d\n", totcount);
    +  totcount= qh_printvdiagram2(qh, fp, printvridge, vertices, innerouter, True /* inorder*/);
    +  qh_settempfree(qh, &vertices);
    +#if 0  /* for testing qh_eachvoronoi_all */
    +  qh_fprintf(qh, fp, 9232, "\n");
    +  totcount= qh_eachvoronoi_all(qh, fp, printvridge, qh->UPPERdelaunay, innerouter, True /* inorder*/);
    +  qh_fprintf(qh, fp, 9233, "%d\n", totcount);
    +#endif
    +} /* printvdiagram */
    +
    +/*---------------------------------
    +
    +  qh_printvdiagram2(qh, fp, printvridge, vertices, innerouter, inorder )
    +    visit all pairs of input sites (vertices) for selected Voronoi vertices
    +    vertices may include NULLs
    +
    +  innerouter:
    +    qh_RIDGEall   print inner ridges(bounded) and outer ridges(unbounded)
    +    qh_RIDGEinner print only inner ridges
    +    qh_RIDGEouter print only outer ridges
    +
    +  inorder:
    +    print 3-d Voronoi vertices in order
    +
    +  assumes:
    +    qh_markvoronoi marked facet->visitid for Voronoi vertices
    +    all facet->seen= False
    +    all facet->seen2= True
    +
    +  returns:
    +    total number of Voronoi ridges
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers) for each ridge
    +      [see qh_eachvoronoi()]
    +
    +  see:
    +    qh_eachvoronoi_all()
    +*/
    +int qh_printvdiagram2(qhT *qh, FILE *fp, printvridgeT printvridge, setT *vertices, qh_RIDGE innerouter, boolT inorder) {
    +  int totcount= 0;
    +  int vertex_i, vertex_n;
    +  vertexT *vertex;
    +
    +  FORALLvertices
    +    vertex->seen= False;
    +  FOREACHvertex_i_(qh, vertices) {
    +    if (vertex) {
    +      if (qh->GOODvertex > 0 && qh_pointid(qh, vertex->point)+1 != qh->GOODvertex)
    +        continue;
    +      totcount += qh_eachvoronoi(qh, fp, printvridge, vertex, !qh_ALL, innerouter, inorder);
    +    }
    +  }
    +  return totcount;
    +} /* printvdiagram2 */
    +
    +/*---------------------------------
    +
    +  qh_printvertex(qh, fp, vertex )
    +    prints the information in a vertex
    +    Duplicated as operator<< [QhullVertex.cpp]
    +*/
    +void qh_printvertex(qhT *qh, FILE *fp, vertexT *vertex) {
    +  pointT *point;
    +  int k, count= 0;
    +  facetT *neighbor, **neighborp;
    +  realT r; /*bug fix*/
    +
    +  if (!vertex) {
    +    qh_fprintf(qh, fp, 9234, "  NULLvertex\n");
    +    return;
    +  }
    +  qh_fprintf(qh, fp, 9235, "- p%d(v%d):", qh_pointid(qh, vertex->point), vertex->id);
    +  point= vertex->point;
    +  if (point) {
    +    for (k=qh->hull_dim; k--; ) {
    +      r= *point++;
    +      qh_fprintf(qh, fp, 9236, " %5.2g", r);
    +    }
    +  }
    +  if (vertex->deleted)
    +    qh_fprintf(qh, fp, 9237, " deleted");
    +  if (vertex->delridge)
    +    qh_fprintf(qh, fp, 9238, " ridgedeleted");
    +  qh_fprintf(qh, fp, 9239, "\n");
    +  if (vertex->neighbors) {
    +    qh_fprintf(qh, fp, 9240, "  neighbors:");
    +    FOREACHneighbor_(vertex) {
    +      if (++count % 100 == 0)
    +        qh_fprintf(qh, fp, 9241, "\n     ");
    +      qh_fprintf(qh, fp, 9242, " f%d", neighbor->id);
    +    }
    +    qh_fprintf(qh, fp, 9243, "\n");
    +  }
    +} /* printvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_printvertexlist(qh, fp, string, facetlist, facets, printall )
    +    prints vertices used by a facetlist or facet set
    +    tests qh_skipfacet() if !printall
    +*/
    +void qh_printvertexlist(qhT *qh, FILE *fp, const char* string, facetT *facetlist,
    +                         setT *facets, boolT printall) {
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  qh_fprintf(qh, fp, 9244, "%s", string);
    +  FOREACHvertex_(vertices)
    +    qh_printvertex(qh, fp, vertex);
    +  qh_settempfree(qh, &vertices);
    +} /* printvertexlist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printvertices(qh, fp, string, vertices )
    +    prints vertices in a set
    +    duplicated as printVertexSet [QhullVertex.cpp]
    +*/
    +void qh_printvertices(qhT *qh, FILE *fp, const char* string, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  qh_fprintf(qh, fp, 9245, "%s", string);
    +  FOREACHvertex_(vertices)
    +    qh_fprintf(qh, fp, 9246, " p%d(v%d)", qh_pointid(qh, vertex->point), vertex->id);
    +  qh_fprintf(qh, fp, 9247, "\n");
    +} /* printvertices */
    +
    +/*---------------------------------
    +
    +  qh_printvneighbors(qh, fp, facetlist, facets, printall )
    +    print vertex neighbors of vertices in facetlist and facets ('FN')
    +
    +  notes:
    +    qh_countfacets clears facet->visitid for non-printed facets
    +
    +  design:
    +    collect facet count and related statistics
    +    if necessary, build neighbor sets for each vertex
    +    collect vertices in facetlist and facets
    +    build a point array for point->vertex and point->coplanar facet
    +    for each point
    +      list vertex neighbors or coplanar facet
    +*/
    +void qh_printvneighbors(qhT *qh, FILE *fp, facetT* facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numneighbors, numcoplanars, numtricoplanars;
    +  setT *vertices, *vertex_points, *coplanar_points;
    +  int numpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +  vertexT *vertex, **vertexp;
    +  int vertex_i, vertex_n;
    +  facetT *facet, **facetp, *neighbor, **neighborp;
    +  pointT *point, **pointp;
    +
    +  qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);  /* sets facet->visitid */
    +  qh_fprintf(qh, fp, 9248, "%d\n", numpoints);
    +  qh_vertexneighbors(qh);
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  vertex_points= qh_settemp(qh, numpoints);
    +  coplanar_points= qh_settemp(qh, numpoints);
    +  qh_setzero(qh, vertex_points, 0, numpoints);
    +  qh_setzero(qh, coplanar_points, 0, numpoints);
    +  FOREACHvertex_(vertices)
    +    qh_point_add(qh, vertex_points, vertex->point, vertex);
    +  FORALLfacet_(facetlist) {
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(qh, coplanar_points, point, facet);
    +  }
    +  FOREACHfacet_(facets) {
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(qh, coplanar_points, point, facet);
    +  }
    +  FOREACHvertex_i_(qh, vertex_points) {
    +    if (vertex) {
    +      numneighbors= qh_setsize(qh, vertex->neighbors);
    +      qh_fprintf(qh, fp, 9249, "%d", numneighbors);
    +      if (qh->hull_dim == 3)
    +        qh_order_vertexneighbors(qh, vertex);
    +      else if (qh->hull_dim >= 4)
    +        qsort(SETaddr_(vertex->neighbors, facetT), (size_t)numneighbors,
    +             sizeof(facetT *), qh_compare_facetvisit);
    +      FOREACHneighbor_(vertex)
    +        qh_fprintf(qh, fp, 9250, " %d",
    +                 neighbor->visitid ? neighbor->visitid - 1 : 0 - neighbor->id);
    +      qh_fprintf(qh, fp, 9251, "\n");
    +    }else if ((facet= SETelemt_(coplanar_points, vertex_i, facetT)))
    +      qh_fprintf(qh, fp, 9252, "1 %d\n",
    +                  facet->visitid ? facet->visitid - 1 : 0 - facet->id);
    +    else
    +      qh_fprintf(qh, fp, 9253, "0\n");
    +  }
    +  qh_settempfree(qh, &coplanar_points);
    +  qh_settempfree(qh, &vertex_points);
    +  qh_settempfree(qh, &vertices);
    +} /* printvneighbors */
    +
    +/*---------------------------------
    +
    +  qh_printvoronoi(qh, fp, format, facetlist, facets, printall )
    +    print voronoi diagram in 'o' or 'G' format
    +    for 'o' format
    +      prints voronoi centers for each facet and for infinity
    +      for each vertex, lists ids of printed facets or infinity
    +      assumes facetlist and facets are disjoint
    +    for 'G' format
    +      prints an OFF object
    +      adds a 0 coordinate to center
    +      prints infinity but does not list in vertices
    +
    +  see:
    +    qh_printvdiagram()
    +
    +  notes:
    +    if 'o',
    +      prints a line for each point except "at-infinity"
    +    if all facets are upperdelaunay,
    +      reverses lower and upper hull
    +*/
    +void qh_printvoronoi(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int k, numcenters, numvertices= 0, numneighbors, numinf, vid=1, vertex_i, vertex_n;
    +  facetT *facet, **facetp, *neighbor, **neighborp;
    +  setT *vertices;
    +  vertexT *vertex;
    +  boolT isLower;
    +  unsigned int numfacets= (unsigned int) qh->num_facets;
    +
    +  vertices= qh_markvoronoi(qh, facetlist, facets, printall, &isLower, &numcenters);
    +  FOREACHvertex_i_(qh, vertices) {
    +    if (vertex) {
    +      numvertices++;
    +      numneighbors = numinf = 0;
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visitid == 0)
    +          numinf= 1;
    +        else if (neighbor->visitid < numfacets)
    +          numneighbors++;
    +      }
    +      if (numinf && !numneighbors) {
    +        SETelem_(vertices, vertex_i)= NULL;
    +        numvertices--;
    +      }
    +    }
    +  }
    +  if (format == qh_PRINTgeom)
    +    qh_fprintf(qh, fp, 9254, "{appearance {+edge -face} OFF %d %d 1 # Voronoi centers and cells\n",
    +                numcenters, numvertices);
    +  else
    +    qh_fprintf(qh, fp, 9255, "%d\n%d %d 1\n", qh->hull_dim-1, numcenters, qh_setsize(qh, vertices));
    +  if (format == qh_PRINTgeom) {
    +    for (k=qh->hull_dim-1; k--; )
    +      qh_fprintf(qh, fp, 9256, qh_REAL_1, 0.0);
    +    qh_fprintf(qh, fp, 9257, " 0 # infinity not used\n");
    +  }else {
    +    for (k=qh->hull_dim-1; k--; )
    +      qh_fprintf(qh, fp, 9258, qh_REAL_1, qh_INFINITE);
    +    qh_fprintf(qh, fp, 9259, "\n");
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->visitid && facet->visitid < numfacets) {
    +      if (format == qh_PRINTgeom)
    +        qh_fprintf(qh, fp, 9260, "# %d f%d\n", vid++, facet->id);
    +      qh_printcenter(qh, fp, format, NULL, facet);
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (facet->visitid && facet->visitid < numfacets) {
    +      if (format == qh_PRINTgeom)
    +        qh_fprintf(qh, fp, 9261, "# %d f%d\n", vid++, facet->id);
    +      qh_printcenter(qh, fp, format, NULL, facet);
    +    }
    +  }
    +  FOREACHvertex_i_(qh, vertices) {
    +    numneighbors= 0;
    +    numinf=0;
    +    if (vertex) {
    +      if (qh->hull_dim == 3)
    +        qh_order_vertexneighbors(qh, vertex);
    +      else if (qh->hull_dim >= 4)
    +        qsort(SETaddr_(vertex->neighbors, facetT),
    +             (size_t)qh_setsize(qh, vertex->neighbors),
    +             sizeof(facetT *), qh_compare_facetvisit);
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visitid == 0)
    +          numinf= 1;
    +        else if (neighbor->visitid < numfacets)
    +          numneighbors++;
    +      }
    +    }
    +    if (format == qh_PRINTgeom) {
    +      if (vertex) {
    +        qh_fprintf(qh, fp, 9262, "%d", numneighbors);
    +        FOREACHneighbor_(vertex) {
    +          if (neighbor->visitid && neighbor->visitid < numfacets)
    +            qh_fprintf(qh, fp, 9263, " %d", neighbor->visitid);
    +        }
    +        qh_fprintf(qh, fp, 9264, " # p%d(v%d)\n", vertex_i, vertex->id);
    +      }else
    +        qh_fprintf(qh, fp, 9265, " # p%d is coplanar or isolated\n", vertex_i);
    +    }else {
    +      if (numinf)
    +        numneighbors++;
    +      qh_fprintf(qh, fp, 9266, "%d", numneighbors);
    +      if (vertex) {
    +        FOREACHneighbor_(vertex) {
    +          if (neighbor->visitid == 0) {
    +            if (numinf) {
    +              numinf= 0;
    +              qh_fprintf(qh, fp, 9267, " %d", neighbor->visitid);
    +            }
    +          }else if (neighbor->visitid < numfacets)
    +            qh_fprintf(qh, fp, 9268, " %d", neighbor->visitid);
    +        }
    +      }
    +      qh_fprintf(qh, fp, 9269, "\n");
    +    }
    +  }
    +  if (format == qh_PRINTgeom)
    +    qh_fprintf(qh, fp, 9270, "}\n");
    +  qh_settempfree(qh, &vertices);
    +} /* printvoronoi */
    +
    +/*---------------------------------
    +
    +  qh_printvnorm(qh, fp, vertex, vertexA, centers, unbounded )
    +    print one separating plane of the Voronoi diagram for a pair of input sites
    +    unbounded==True if centers includes vertex-at-infinity
    +
    +  assumes:
    +    qh_ASvoronoi and qh_vertexneighbors() already set
    +
    +  note:
    +    parameter unbounded is UNUSED by this callback
    +
    +  see:
    +    qh_printvdiagram()
    +    qh_eachvoronoi()
    +*/
    +void qh_printvnorm(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded) {
    +  pointT *normal;
    +  realT offset;
    +  int k;
    +  QHULL_UNUSED(unbounded);
    +
    +  normal= qh_detvnorm(qh, vertex, vertexA, centers, &offset);
    +  qh_fprintf(qh, fp, 9271, "%d %d %d ",
    +      2+qh->hull_dim, qh_pointid(qh, vertex->point), qh_pointid(qh, vertexA->point));
    +  for (k=0; k< qh->hull_dim-1; k++)
    +    qh_fprintf(qh, fp, 9272, qh_REAL_1, normal[k]);
    +  qh_fprintf(qh, fp, 9273, qh_REAL_1, offset);
    +  qh_fprintf(qh, fp, 9274, "\n");
    +} /* printvnorm */
    +
    +/*---------------------------------
    +
    +  qh_printvridge(qh, fp, vertex, vertexA, centers, unbounded )
    +    print one ridge of the Voronoi diagram for a pair of input sites
    +    unbounded==True if centers includes vertex-at-infinity
    +
    +  see:
    +    qh_printvdiagram()
    +
    +  notes:
    +    the user may use a different function
    +    parameter unbounded is UNUSED
    +*/
    +void qh_printvridge(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded) {
    +  facetT *facet, **facetp;
    +  QHULL_UNUSED(unbounded);
    +
    +  qh_fprintf(qh, fp, 9275, "%d %d %d", qh_setsize(qh, centers)+2,
    +       qh_pointid(qh, vertex->point), qh_pointid(qh, vertexA->point));
    +  FOREACHfacet_(centers)
    +    qh_fprintf(qh, fp, 9276, " %d", facet->visitid);
    +  qh_fprintf(qh, fp, 9277, "\n");
    +} /* printvridge */
    +
    +/*---------------------------------
    +
    +  qh_projectdim3(qh, source, destination )
    +    project 2-d 3-d or 4-d point to a 3-d point
    +    uses qh.DROPdim and qh.hull_dim
    +    source and destination may be the same
    +
    +  notes:
    +    allocate 4 elements to destination just in case
    +*/
    +void qh_projectdim3(qhT *qh, pointT *source, pointT *destination) {
    +  int i,k;
    +
    +  for (k=0, i=0; k < qh->hull_dim; k++) {
    +    if (qh->hull_dim == 4) {
    +      if (k != qh->DROPdim)
    +        destination[i++]= source[k];
    +    }else if (k == qh->DROPdim)
    +      destination[i++]= 0;
    +    else
    +      destination[i++]= source[k];
    +  }
    +  while (i < 3)
    +    destination[i++]= 0.0;
    +} /* projectdim3 */
    +
    +/*---------------------------------
    +
    +  qh_readfeasible(qh, dim, curline )
    +    read feasible point from current line and qh.fin
    +
    +  returns:
    +    number of lines read from qh.fin
    +    sets qh.feasible_point with malloc'd coordinates
    +
    +  notes:
    +    checks for qh.HALFspace
    +    assumes dim > 1
    +
    +  see:
    +    qh_setfeasible
    +*/
    +int qh_readfeasible(qhT *qh, int dim, const char *curline) {
    +  boolT isfirst= True;
    +  int linecount= 0, tokcount= 0;
    +  const char *s;
    +  char *t, firstline[qh_MAXfirst+1];
    +  coordT *coords, value;
    +
    +  if (!qh->HALFspace) {
    +    qh_fprintf(qh, qh->ferr, 6070, "qhull input error: feasible point(dim 1 coords) is only valid for halfspace intersection\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh->feasible_string)
    +    qh_fprintf(qh, qh->ferr, 7057, "qhull input warning: feasible point(dim 1 coords) overrides 'Hn,n,n' feasible point for halfspace intersection\n");
    +  if (!(qh->feasible_point= (coordT*)qh_malloc(dim* sizeof(coordT)))) {
    +    qh_fprintf(qh, qh->ferr, 6071, "qhull error: insufficient memory for feasible point\n");
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  coords= qh->feasible_point;
    +  while ((s= (isfirst ?  curline : fgets(firstline, qh_MAXfirst, qh->fin)))) {
    +    if (isfirst)
    +      isfirst= False;
    +    else
    +      linecount++;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      value= qh_strtod(s, &t);
    +      if (s == t)
    +        break;
    +      s= t;
    +      *(coords++)= value;
    +      if (++tokcount == dim) {
    +        while (isspace(*s))
    +          s++;
    +        qh_strtod(s, &t);
    +        if (s != t) {
    +          qh_fprintf(qh, qh->ferr, 6072, "qhull input error: coordinates for feasible point do not finish out the line: %s\n",
    +               s);
    +          qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +        }
    +        return linecount;
    +      }
    +    }
    +  }
    +  qh_fprintf(qh, qh->ferr, 6073, "qhull input error: only %d coordinates.  Could not read %d-d feasible point.\n",
    +           tokcount, dim);
    +  qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  return 0;
    +} /* readfeasible */
    +
    +/*---------------------------------
    +
    +  qh_readpoints(qh, numpoints, dimension, ismalloc )
    +    read points from qh.fin into qh.first_point, qh.num_points
    +    qh.fin is lines of coordinates, one per vertex, first line number of points
    +    if 'rbox D4',
    +      gives message
    +    if qh.ATinfinity,
    +      adds point-at-infinity for Delaunay triangulations
    +
    +  returns:
    +    number of points, array of point coordinates, dimension, ismalloc True
    +    if qh.DELAUNAY & !qh.PROJECTinput, projects points to paraboloid
    +        and clears qh.PROJECTdelaunay
    +    if qh.HALFspace, reads optional feasible point, reads halfspaces,
    +        converts to dual.
    +
    +  for feasible point in "cdd format" in 3-d:
    +    3 1
    +    coordinates
    +    comments
    +    begin
    +    n 4 real/integer
    +    ...
    +    end
    +
    +  notes:
    +    dimension will change in qh_initqhull_globals if qh.PROJECTinput
    +    uses malloc() since qh_mem not initialized
    +    FIXUP QH11012: qh_readpoints needs rewriting, too long
    +*/
    +coordT *qh_readpoints(qhT *qh, int *numpoints, int *dimension, boolT *ismalloc) {
    +  coordT *points, *coords, *infinity= NULL;
    +  realT paraboloid, maxboloid= -REALmax, value;
    +  realT *coordp= NULL, *offsetp= NULL, *normalp= NULL;
    +  char *s= 0, *t, firstline[qh_MAXfirst+1];
    +  int diminput=0, numinput=0, dimfeasible= 0, newnum, k, tempi;
    +  int firsttext=0, firstshort=0, firstlong=0, firstpoint=0;
    +  int tokcount= 0, linecount=0, maxcount, coordcount=0;
    +  boolT islong, isfirst= True, wasbegin= False;
    +  boolT isdelaunay= qh->DELAUNAY && !qh->PROJECTinput;
    +
    +  if (qh->CDDinput) {
    +    while ((s= fgets(firstline, qh_MAXfirst, qh->fin))) {
    +      linecount++;
    +      if (qh->HALFspace && linecount == 1 && isdigit(*s)) {
    +        dimfeasible= qh_strtol(s, &s);
    +        while (isspace(*s))
    +          s++;
    +        if (qh_strtol(s, &s) == 1)
    +          linecount += qh_readfeasible(qh, dimfeasible, s);
    +        else
    +          dimfeasible= 0;
    +      }else if (!memcmp(firstline, "begin", (size_t)5) || !memcmp(firstline, "BEGIN", (size_t)5))
    +        break;
    +      else if (!*qh->rbox_command)
    +        strncat(qh->rbox_command, s, sizeof(qh->rbox_command)-1);
    +    }
    +    if (!s) {
    +      qh_fprintf(qh, qh->ferr, 6074, "qhull input error: missing \"begin\" for cdd-formated input\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +  }
    +  while (!numinput && (s= fgets(firstline, qh_MAXfirst, qh->fin))) {
    +    linecount++;
    +    if (!memcmp(s, "begin", (size_t)5) || !memcmp(s, "BEGIN", (size_t)5))
    +      wasbegin= True;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      if (!*s)
    +        break;
    +      if (!isdigit(*s)) {
    +        if (!*qh->rbox_command) {
    +          strncat(qh->rbox_command, s, sizeof(qh->rbox_command)-1);
    +          firsttext= linecount;
    +        }
    +        break;
    +      }
    +      if (!diminput)
    +        diminput= qh_strtol(s, &s);
    +      else {
    +        numinput= qh_strtol(s, &s);
    +        if (numinput == 1 && diminput >= 2 && qh->HALFspace && !qh->CDDinput) {
    +          linecount += qh_readfeasible(qh, diminput, s); /* checks if ok */
    +          dimfeasible= diminput;
    +          diminput= numinput= 0;
    +        }else
    +          break;
    +      }
    +    }
    +  }
    +  if (!s) {
    +    qh_fprintf(qh, qh->ferr, 6075, "qhull input error: short input file.  Did not find dimension and number of points\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (diminput > numinput) {
    +    tempi= diminput;    /* exchange dim and n, e.g., for cdd input format */
    +    diminput= numinput;
    +    numinput= tempi;
    +  }
    +  if (diminput < 2) {
    +    qh_fprintf(qh, qh->ferr, 6220,"qhull input error: dimension %d(first number) should be at least 2\n",
    +            diminput);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (isdelaunay) {
    +    qh->PROJECTdelaunay= False;
    +    if (qh->CDDinput)
    +      *dimension= diminput;
    +    else
    +      *dimension= diminput+1;
    +    *numpoints= numinput;
    +    if (qh->ATinfinity)
    +      (*numpoints)++;
    +  }else if (qh->HALFspace) {
    +    *dimension= diminput - 1;
    +    *numpoints= numinput;
    +    if (diminput < 3) {
    +      qh_fprintf(qh, qh->ferr, 6221,"qhull input error: dimension %d(first number, includes offset) should be at least 3 for halfspaces\n",
    +            diminput);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (dimfeasible) {
    +      if (dimfeasible != *dimension) {
    +        qh_fprintf(qh, qh->ferr, 6222,"qhull input error: dimension %d of feasible point is not one less than dimension %d for halfspaces\n",
    +          dimfeasible, diminput);
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +    }else
    +      qh_setfeasible(qh, *dimension);
    +  }else {
    +    if (qh->CDDinput)
    +      *dimension= diminput-1;
    +    else
    +      *dimension= diminput;
    +    *numpoints= numinput;
    +  }
    +  qh->normal_size= *dimension * sizeof(coordT); /* for tracing with qh_printpoint */
    +  if (qh->HALFspace) {
    +    qh->half_space= coordp= (coordT*)qh_malloc(qh->normal_size + sizeof(coordT));
    +    if (qh->CDDinput) {
    +      offsetp= qh->half_space;
    +      normalp= offsetp + 1;
    +    }else {
    +      normalp= qh->half_space;
    +      offsetp= normalp + *dimension;
    +    }
    +  }
    +  qh->maxline= diminput * (qh_REALdigits + 5);
    +  maximize_(qh->maxline, 500);
    +  qh->line= (char*)qh_malloc((qh->maxline+1) * sizeof(char));
    +  *ismalloc= True;  /* use malloc since memory not setup */
    +  coords= points= qh->temp_malloc=  /* numinput and diminput >=2 by QH6220 */
    +        (coordT*)qh_malloc((*numpoints)*(*dimension)*sizeof(coordT));
    +  if (!coords || !qh->line || (qh->HALFspace && !qh->half_space)) {
    +    qh_fprintf(qh, qh->ferr, 6076, "qhull error: insufficient memory to read %d points\n",
    +            numinput);
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  if (isdelaunay && qh->ATinfinity) {
    +    infinity= points + numinput * (*dimension);
    +    for (k= (*dimension) - 1; k--; )
    +      infinity[k]= 0.0;
    +  }
    +  maxcount= numinput * diminput;
    +  paraboloid= 0.0;
    +  while ((s= (isfirst ?  s : fgets(qh->line, qh->maxline, qh->fin)))) {
    +    if (!isfirst) {
    +      linecount++;
    +      if (*s == 'e' || *s == 'E') {
    +        if (!memcmp(s, "end", (size_t)3) || !memcmp(s, "END", (size_t)3)) {
    +          if (qh->CDDinput )
    +            break;
    +          else if (wasbegin)
    +            qh_fprintf(qh, qh->ferr, 7058, "qhull input warning: the input appears to be in cdd format.  If so, use 'Fd'\n");
    +        }
    +      }
    +    }
    +    islong= False;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      value= qh_strtod(s, &t);
    +      if (s == t) {
    +        if (!*qh->rbox_command)
    +         strncat(qh->rbox_command, s, sizeof(qh->rbox_command)-1);
    +        if (*s && !firsttext)
    +          firsttext= linecount;
    +        if (!islong && !firstshort && coordcount)
    +          firstshort= linecount;
    +        break;
    +      }
    +      if (!firstpoint)
    +        firstpoint= linecount;
    +      s= t;
    +      if (++tokcount > maxcount)
    +        continue;
    +      if (qh->HALFspace) {
    +        if (qh->CDDinput)
    +          *(coordp++)= -value; /* both coefficients and offset */
    +        else
    +          *(coordp++)= value;
    +      }else {
    +        *(coords++)= value;
    +        if (qh->CDDinput && !coordcount) {
    +          if (value != 1.0) {
    +            qh_fprintf(qh, qh->ferr, 6077, "qhull input error: for cdd format, point at line %d does not start with '1'\n",
    +                   linecount);
    +            qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +          }
    +          coords--;
    +        }else if (isdelaunay) {
    +          paraboloid += value * value;
    +          if (qh->ATinfinity) {
    +            if (qh->CDDinput)
    +              infinity[coordcount-1] += value;
    +            else
    +              infinity[coordcount] += value;
    +          }
    +        }
    +      }
    +      if (++coordcount == diminput) {
    +        coordcount= 0;
    +        if (isdelaunay) {
    +          *(coords++)= paraboloid;
    +          maximize_(maxboloid, paraboloid);
    +          paraboloid= 0.0;
    +        }else if (qh->HALFspace) {
    +          if (!qh_sethalfspace(qh, *dimension, coords, &coords, normalp, offsetp, qh->feasible_point)) {
    +            qh_fprintf(qh, qh->ferr, 8048, "The halfspace was on line %d\n", linecount);
    +            if (wasbegin)
    +              qh_fprintf(qh, qh->ferr, 8049, "The input appears to be in cdd format.  If so, you should use option 'Fd'\n");
    +            qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +          }
    +          coordp= qh->half_space;
    +        }
    +        while (isspace(*s))
    +          s++;
    +        if (*s) {
    +          islong= True;
    +          if (!firstlong)
    +            firstlong= linecount;
    +        }
    +      }
    +    }
    +    if (!islong && !firstshort && coordcount)
    +      firstshort= linecount;
    +    if (!isfirst && s - qh->line >= qh->maxline) {
    +      qh_fprintf(qh, qh->ferr, 6078, "qhull input error: line %d contained more than %d characters\n",
    +              linecount, (int) (s - qh->line));   /* WARN64 */
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    isfirst= False;
    +  }
    +  if (tokcount != maxcount) {
    +    newnum= fmin_(numinput, tokcount/diminput);
    +    qh_fprintf(qh, qh->ferr, 7073,"\
    +qhull warning: instead of %d %d-dimensional points, input contains\n\
    +%d points and %d extra coordinates.  Line %d is the first\npoint",
    +       numinput, diminput, tokcount/diminput, tokcount % diminput, firstpoint);
    +    if (firsttext)
    +      qh_fprintf(qh, qh->ferr, 8051, ", line %d is the first comment", firsttext);
    +    if (firstshort)
    +      qh_fprintf(qh, qh->ferr, 8052, ", line %d is the first short\nline", firstshort);
    +    if (firstlong)
    +      qh_fprintf(qh, qh->ferr, 8053, ", line %d is the first long line", firstlong);
    +    qh_fprintf(qh, qh->ferr, 8054, ".  Continue with %d points.\n", newnum);
    +    numinput= newnum;
    +    if (isdelaunay && qh->ATinfinity) {
    +      for (k= tokcount % diminput; k--; )
    +        infinity[k] -= *(--coords);
    +      *numpoints= newnum+1;
    +    }else {
    +      coords -= tokcount % diminput;
    +      *numpoints= newnum;
    +    }
    +  }
    +  if (isdelaunay && qh->ATinfinity) {
    +    for (k= (*dimension) -1; k--; )
    +      infinity[k] /= numinput;
    +    if (coords == infinity)
    +      coords += (*dimension) -1;
    +    else {
    +      for (k=0; k < (*dimension) -1; k++)
    +        *(coords++)= infinity[k];
    +    }
    +    *(coords++)= maxboloid * 1.1;
    +  }
    +  if (qh->rbox_command[0]) {
    +    qh->rbox_command[strlen(qh->rbox_command)-1]= '\0';
    +    if (!strcmp(qh->rbox_command, "./rbox D4"))
    +      qh_fprintf(qh, qh->ferr, 8055, "\n\
    +This is the qhull test case.  If any errors or core dumps occur,\n\
    +recompile qhull with 'make new'.  If errors still occur, there is\n\
    +an incompatibility.  You should try a different compiler.  You can also\n\
    +change the choices in user.h.  If you discover the source of the problem,\n\
    +please send mail to qhull_bug@qhull.org.\n\
    +\n\
    +Type 'qhull' for a short list of options.\n");
    +  }
    +  qh_free(qh->line);
    +  qh->line= NULL;
    +  if (qh->half_space) {
    +    qh_free(qh->half_space);
    +    qh->half_space= NULL;
    +  }
    +  qh->temp_malloc= NULL;
    +  trace1((qh, qh->ferr, 1008,"qh_readpoints: read in %d %d-dimensional points\n",
    +          numinput, diminput));
    +  return(points);
    +} /* readpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfeasible(qh, dim )
    +    set qh.feasible_point from qh.feasible_string in "n,n,n" or "n n n" format
    +
    +  notes:
    +    "n,n,n" already checked by qh_initflags()
    +    see qh_readfeasible()
    +    called only once from qh_new_qhull, otherwise leaks memory
    +*/
    +void qh_setfeasible(qhT *qh, int dim) {
    +  int tokcount= 0;
    +  char *s;
    +  coordT *coords, value;
    +
    +  if (!(s= qh->feasible_string)) {
    +    qh_fprintf(qh, qh->ferr, 6223, "\
    +qhull input error: halfspace intersection needs a feasible point.\n\
    +Either prepend the input with 1 point or use 'Hn,n,n'.  See manual.\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (!(qh->feasible_point= (pointT*)qh_malloc(dim * sizeof(coordT)))) {
    +    qh_fprintf(qh, qh->ferr, 6079, "qhull error: insufficient memory for 'Hn,n,n'\n");
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  coords= qh->feasible_point;
    +  while (*s) {
    +    value= qh_strtod(s, &s);
    +    if (++tokcount > dim) {
    +      qh_fprintf(qh, qh->ferr, 7059, "qhull input warning: more coordinates for 'H%s' than dimension %d\n",
    +          qh->feasible_string, dim);
    +      break;
    +    }
    +    *(coords++)= value;
    +    if (*s)
    +      s++;
    +  }
    +  while (++tokcount <= dim)
    +    *(coords++)= 0.0;
    +} /* setfeasible */
    +
    +/*---------------------------------
    +
    +  qh_skipfacet(qh, facet )
    +    returns 'True' if this facet is not to be printed
    +
    +  notes:
    +    based on the user provided slice thresholds and 'good' specifications
    +*/
    +boolT qh_skipfacet(qhT *qh, facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +
    +  if (qh->PRINTneighbors) {
    +    if (facet->good)
    +      return !qh->PRINTgood;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->good)
    +        return False;
    +    }
    +    return True;
    +  }else if (qh->PRINTgood)
    +    return !facet->good;
    +  else if (!facet->normal)
    +    return True;
    +  return(!qh_inthresholds(qh, facet->normal, NULL));
    +} /* skipfacet */
    +
    +/*---------------------------------
    +
    +  qh_skipfilename(qh, string )
    +    returns pointer to character after filename
    +
    +  notes:
    +    skips leading spaces
    +    ends with spacing or eol
    +    if starts with ' or " ends with the same, skipping \' or \"
    +    For qhull, qh_argv_to_command() only uses double quotes
    +*/
    +char *qh_skipfilename(qhT *qh, char *filename) {
    +  char *s= filename;  /* non-const due to return */
    +  char c;
    +
    +  while (*s && isspace(*s))
    +    s++;
    +  c= *s++;
    +  if (c == '\0') {
    +    qh_fprintf(qh, qh->ferr, 6204, "qhull input error: filename expected, none found.\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (c == '\'' || c == '"') {
    +    while (*s !=c || s[-1] == '\\') {
    +      if (!*s) {
    +        qh_fprintf(qh, qh->ferr, 6203, "qhull input error: missing quote after filename -- %s\n", filename);
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +      s++;
    +    }
    +    s++;
    +  }
    +  else while (*s && !isspace(*s))
    +      s++;
    +  return s;
    +} /* skipfilename */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/io_r.h b/xs/src/qhull/src/libqhull_r/io_r.h
    new file mode 100644
    index 0000000000..12e05ae7ac
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/io_r.h
    @@ -0,0 +1,167 @@
    +/*
      ---------------------------------
    +
    +   io_r.h
    +   declarations of Input/Output functions
    +
    +   see README, libqhull_r.h and io_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/io_r.h#3 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFio
    +#define qhDEFio 1
    +
    +#include "libqhull_r.h"
    +
    +/*============ constants and flags ==================*/
    +
    +/*----------------------------------
    +
    +  qh_MAXfirst
    +    maximum length of first two lines of stdin
    +*/
    +#define qh_MAXfirst  200
    +
    +/*----------------------------------
    +
    +  qh_MINradius
    +    min radius for Gp and Gv, fraction of maxcoord
    +*/
    +#define qh_MINradius 0.02
    +
    +/*----------------------------------
    +
    +  qh_GEOMepsilon
    +    adjust outer planes for 'lines closer' and geomview roundoff.
    +    This prevents bleed through.
    +*/
    +#define qh_GEOMepsilon 2e-3
    +
    +/*----------------------------------
    +
    +  qh_WHITESPACE
    +    possible values of white space
    +*/
    +#define qh_WHITESPACE " \n\t\v\r\f"
    +
    +
    +/*----------------------------------
    +
    +  qh_RIDGE
    +    to select which ridges to print in qh_eachvoronoi
    +*/
    +typedef enum
    +{
    +    qh_RIDGEall = 0, qh_RIDGEinner, qh_RIDGEouter
    +}
    +qh_RIDGE;
    +
    +/*----------------------------------
    +
    +  printvridgeT
    +    prints results of qh_printvdiagram
    +
    +  see:
    +    qh_printvridge for an example
    +*/
    +typedef void (*printvridgeT)(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +
    +/*============== -prototypes in alphabetical order =========*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_dfacet(qhT *qh, unsigned id);
    +void    qh_dvertex(qhT *qh, unsigned id);
    +int     qh_compare_facetarea(const void *p1, const void *p2);
    +int     qh_compare_facetmerge(const void *p1, const void *p2);
    +int     qh_compare_facetvisit(const void *p1, const void *p2);
    +/* int  qh_compare_vertexpoint(const void *p1, const void *p2); Not useable since it depends on qh */
    +void    qh_copyfilename(qhT *qh, char *filename, int size, const char* source, int length);
    +void    qh_countfacets(qhT *qh, facetT *facetlist, setT *facets, boolT printall,
    +              int *numfacetsp, int *numsimplicialp, int *totneighborsp,
    +              int *numridgesp, int *numcoplanarsp, int *numnumtricoplanarsp);
    +pointT *qh_detvnorm(qhT *qh, vertexT *vertex, vertexT *vertexA, setT *centers, realT *offsetp);
    +setT   *qh_detvridge(qhT *qh, vertexT *vertex);
    +setT   *qh_detvridge3(qhT *qh, vertexT *atvertex, vertexT *vertex);
    +int     qh_eachvoronoi(qhT *qh, FILE *fp, printvridgeT printvridge, vertexT *atvertex, boolT visitall, qh_RIDGE innerouter, boolT inorder);
    +int     qh_eachvoronoi_all(qhT *qh, FILE *fp, printvridgeT printvridge, boolT isUpper, qh_RIDGE innerouter, boolT inorder);
    +void    qh_facet2point(qhT *qh, facetT *facet, pointT **point0, pointT **point1, realT *mindist);
    +setT   *qh_facetvertices(qhT *qh, facetT *facetlist, setT *facets, boolT allfacets);
    +void    qh_geomplanes(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane);
    +void    qh_markkeep(qhT *qh, facetT *facetlist);
    +setT   *qh_markvoronoi(qhT *qh, facetT *facetlist, setT *facets, boolT printall, boolT *isLowerp, int *numcentersp);
    +void    qh_order_vertexneighbors(qhT *qh, vertexT *vertex);
    +void    qh_prepare_output(qhT *qh);
    +void    qh_printafacet(qhT *qh, FILE *fp, qh_PRINT format, facetT *facet, boolT printall);
    +void    qh_printbegin(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printcenter(qhT *qh, FILE *fp, qh_PRINT format, const char *string, facetT *facet);
    +void    qh_printcentrum(qhT *qh, FILE *fp, facetT *facet, realT radius);
    +void    qh_printend(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printend4geom(qhT *qh, FILE *fp, facetT *facet, int *num, boolT printall);
    +void    qh_printextremes(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printextremes_2d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printextremes_d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printfacet(qhT *qh, FILE *fp, facetT *facet);
    +void    qh_printfacet2math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst);
    +void    qh_printfacet2geom(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet2geom_points(qhT *qh, FILE *fp, pointT *point1, pointT *point2,
    +                               facetT *facet, realT offset, realT color[3]);
    +void    qh_printfacet3math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst);
    +void    qh_printfacet3geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet3geom_points(qhT *qh, FILE *fp, setT *points, facetT *facet, realT offset, realT color[3]);
    +void    qh_printfacet3geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet3vertex(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format);
    +void    qh_printfacet4geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet4geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacetNvertex_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, int id, qh_PRINT format);
    +void    qh_printfacetNvertex_simplicial(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format);
    +void    qh_printfacetheader(qhT *qh, FILE *fp, facetT *facet);
    +void    qh_printfacetridges(qhT *qh, FILE *fp, facetT *facet);
    +void    qh_printfacets(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printhyperplaneintersection(qhT *qh, FILE *fp, facetT *facet1, facetT *facet2,
    +                   setT *vertices, realT color[3]);
    +void    qh_printneighborhood(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall);
    +void    qh_printline3geom(qhT *qh, FILE *fp, pointT *pointA, pointT *pointB, realT color[3]);
    +void    qh_printpoint(qhT *qh, FILE *fp, const char *string, pointT *point);
    +void    qh_printpointid(qhT *qh, FILE *fp, const char *string, int dim, pointT *point, int id);
    +void    qh_printpoint3(qhT *qh, FILE *fp, pointT *point);
    +void    qh_printpoints_out(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printpointvect(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius, realT color[3]);
    +void    qh_printpointvect2(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius);
    +void    qh_printridge(qhT *qh, FILE *fp, ridgeT *ridge);
    +void    qh_printspheres(qhT *qh, FILE *fp, setT *vertices, realT radius);
    +void    qh_printvdiagram(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +int     qh_printvdiagram2(qhT *qh, FILE *fp, printvridgeT printvridge, setT *vertices, qh_RIDGE innerouter, boolT inorder);
    +void    qh_printvertex(qhT *qh, FILE *fp, vertexT *vertex);
    +void    qh_printvertexlist(qhT *qh, FILE *fp, const char* string, facetT *facetlist,
    +                         setT *facets, boolT printall);
    +void    qh_printvertices(qhT *qh, FILE *fp, const char* string, setT *vertices);
    +void    qh_printvneighbors(qhT *qh, FILE *fp, facetT* facetlist, setT *facets, boolT printall);
    +void    qh_printvoronoi(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printvnorm(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +void    qh_printvridge(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +void    qh_produce_output(qhT *qh);
    +void    qh_produce_output2(qhT *qh);
    +void    qh_projectdim3(qhT *qh, pointT *source, pointT *destination);
    +int     qh_readfeasible(qhT *qh, int dim, const char *curline);
    +coordT *qh_readpoints(qhT *qh, int *numpoints, int *dimension, boolT *ismalloc);
    +void    qh_setfeasible(qhT *qh, int dim);
    +boolT   qh_skipfacet(qhT *qh, facetT *facet);
    +char   *qh_skipfilename(qhT *qh, char *filename);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFio */
    diff --git a/xs/src/qhull/src/libqhull_r/libqhull_r.c b/xs/src/qhull/src/libqhull_r/libqhull_r.c
    new file mode 100644
    index 0000000000..0fe0c980dc
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/libqhull_r.c
    @@ -0,0 +1,1403 @@
    +/*
      ---------------------------------
    +
    +   libqhull_r.c
    +   Quickhull algorithm for convex hulls
    +
    +   qhull() and top-level routines
    +
    +   see qh-qhull_r.htm, libqhull.h, unix_r.c
    +
    +   see qhull_ra.h for internal functions
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/libqhull_r.c#2 $$Change: 2047 $
    +   $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*============= functions in alphabetic order after qhull() =======*/
    +
    +/*---------------------------------
    +
    +  qh_qhull(qh)
    +    compute DIM3 convex hull of qh.num_points starting at qh.first_point
    +    qh->contains all global options and variables
    +
    +  returns:
    +    returns polyhedron
    +      qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices,
    +
    +    returns global variables
    +      qh.hulltime, qh.max_outside, qh.interior_point, qh.max_vertex, qh.min_vertex
    +
    +    returns precision constants
    +      qh.ANGLEround, centrum_radius, cos_max, DISTround, MAXabs_coord, ONEmerge
    +
    +  notes:
    +    unless needed for output
    +      qh.max_vertex and qh.min_vertex are max/min due to merges
    +
    +  see:
    +    to add individual points to either qh.num_points
    +      use qh_addpoint()
    +
    +    if qh.GETarea
    +      qh_produceoutput() returns qh.totarea and qh.totvol via qh_getarea()
    +
    +  design:
    +    record starting time
    +    initialize hull and partition points
    +    build convex hull
    +    unless early termination
    +      update facet->maxoutside for vertices, coplanar, and near-inside points
    +    error if temporary sets exist
    +    record end time
    +*/
    +
    +void qh_qhull(qhT *qh) {
    +  int numoutside;
    +
    +  qh->hulltime= qh_CPUclock;
    +  if (qh->RERUN || qh->JOGGLEmax < REALmax/2)
    +    qh_build_withrestart(qh);
    +  else {
    +    qh_initbuild(qh);
    +    qh_buildhull(qh);
    +  }
    +  if (!qh->STOPpoint && !qh->STOPcone) {
    +    if (qh->ZEROall_ok && !qh->TESTvneighbors && qh->MERGEexact)
    +      qh_checkzero(qh, qh_ALL);
    +    if (qh->ZEROall_ok && !qh->TESTvneighbors && !qh->WAScoplanar) {
    +      trace2((qh, qh->ferr, 2055, "qh_qhull: all facets are clearly convex and no coplanar points.  Post-merging and check of maxout not needed.\n"));
    +      qh->DOcheckmax= False;
    +    }else {
    +      if (qh->MERGEexact || (qh->hull_dim > qh_DIMreduceBuild && qh->PREmerge))
    +        qh_postmerge(qh, "First post-merge", qh->premerge_centrum, qh->premerge_cos,
    +             (qh->POSTmerge ? False : qh->TESTvneighbors));
    +      else if (!qh->POSTmerge && qh->TESTvneighbors)
    +        qh_postmerge(qh, "For testing vertex neighbors", qh->premerge_centrum,
    +             qh->premerge_cos, True);
    +      if (qh->POSTmerge)
    +        qh_postmerge(qh, "For post-merging", qh->postmerge_centrum,
    +             qh->postmerge_cos, qh->TESTvneighbors);
    +      if (qh->visible_list == qh->facet_list) { /* i.e., merging done */
    +        qh->findbestnew= True;
    +        qh_partitionvisible(qh /*qh.visible_list*/, !qh_ALL, &numoutside);
    +        qh->findbestnew= False;
    +        qh_deletevisible(qh /*qh.visible_list*/);
    +        qh_resetlists(qh, False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +      }
    +    }
    +    if (qh->DOcheckmax){
    +      if (qh->REPORTfreq) {
    +        qh_buildtracing(qh, NULL, NULL);
    +        qh_fprintf(qh, qh->ferr, 8115, "\nTesting all coplanar points.\n");
    +      }
    +      qh_check_maxout(qh);
    +    }
    +    if (qh->KEEPnearinside && !qh->maxoutdone)
    +      qh_nearcoplanar(qh);
    +  }
    +  if (qh_setsize(qh, qh->qhmem.tempstack) != 0) {
    +    qh_fprintf(qh, qh->ferr, 6164, "qhull internal error (qh_qhull): temporary sets not empty(%d)\n",
    +             qh_setsize(qh, qh->qhmem.tempstack));
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  qh->hulltime= qh_CPUclock - qh->hulltime;
    +  qh->QHULLfinished= True;
    +  trace1((qh, qh->ferr, 1036, "Qhull: algorithm completed\n"));
    +} /* qhull */
    +
    +/*---------------------------------
    +
    +  qh_addpoint(qh, furthest, facet, checkdist )
    +    add point (usually furthest point) above facet to hull
    +    if checkdist,
    +      check that point is above facet.
    +      if point is not outside of the hull, uses qh_partitioncoplanar()
    +      assumes that facet is defined by qh_findbestfacet()
    +    else if facet specified,
    +      assumes that point is above facet (major damage if below)
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    returns False if user requested an early termination
    +     qh.visible_list, newfacet_list, delvertex_list, NEWfacets may be defined
    +    updates qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices
    +    clear qh.maxoutdone (will need to call qh_check_maxout() for facet->maxoutside)
    +    if unknown point, adds a pointer to qh.other_points
    +      do not deallocate the point's coordinates
    +
    +  notes:
    +    assumes point is near its best facet and not at a local minimum of a lens
    +      distributions.  Use qh_findbestfacet to avoid this case.
    +    uses qh.visible_list, qh.newfacet_list, qh.delvertex_list, qh.NEWfacets
    +
    +  see also:
    +    qh_triangulate() -- triangulate non-simplicial facets
    +
    +  design:
    +    add point to other_points if needed
    +    if checkdist
    +      if point not above facet
    +        partition coplanar point
    +        exit
    +    exit if pre STOPpoint requested
    +    find horizon and visible facets for point
    +    make new facets for point to horizon
    +    make hyperplanes for point
    +    compute balance statistics
    +    match neighboring new facets
    +    update vertex neighbors and delete interior vertices
    +    exit if STOPcone requested
    +    merge non-convex new facets
    +    if merge found, many merges, or 'Qf'
    +       use qh_findbestnew() instead of qh_findbest()
    +    partition outside points from visible facets
    +    delete visible facets
    +    check polyhedron if requested
    +    exit if post STOPpoint requested
    +    reset working lists of facets and vertices
    +*/
    +boolT qh_addpoint(qhT *qh, pointT *furthest, facetT *facet, boolT checkdist) {
    +  int goodvisible, goodhorizon;
    +  vertexT *vertex;
    +  facetT *newfacet;
    +  realT dist, newbalance, pbalance;
    +  boolT isoutside= False;
    +  int numpart, numpoints, numnew, firstnew;
    +
    +  qh->maxoutdone= False;
    +  if (qh_pointid(qh, furthest) == qh_IDunknown)
    +    qh_setappend(qh, &qh->other_points, furthest);
    +  if (!facet) {
    +    qh_fprintf(qh, qh->ferr, 6213, "qhull internal error (qh_addpoint): NULL facet.  Need to call qh_findbestfacet first\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  if (checkdist) {
    +    facet= qh_findbest(qh, furthest, facet, !qh_ALL, !qh_ISnewfacets, !qh_NOupper,
    +                        &dist, &isoutside, &numpart);
    +    zzadd_(Zpartition, numpart);
    +    if (!isoutside) {
    +      zinc_(Znotmax);  /* last point of outsideset is no longer furthest. */
    +      facet->notfurthest= True;
    +      qh_partitioncoplanar(qh, furthest, facet, &dist);
    +      return True;
    +    }
    +  }
    +  qh_buildtracing(qh, furthest, facet);
    +  if (qh->STOPpoint < 0 && qh->furthest_id == -qh->STOPpoint-1) {
    +    facet->notfurthest= True;
    +    return False;
    +  }
    +  qh_findhorizon(qh, furthest, facet, &goodvisible, &goodhorizon);
    +  if (qh->ONLYgood && !(goodvisible+goodhorizon) && !qh->GOODclosest) {
    +    zinc_(Znotgood);
    +    facet->notfurthest= True;
    +    /* last point of outsideset is no longer furthest.  This is ok
    +       since all points of the outside are likely to be bad */
    +    qh_resetlists(qh, False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +    return True;
    +  }
    +  zzinc_(Zprocessed);
    +  firstnew= qh->facet_id;
    +  vertex= qh_makenewfacets(qh, furthest /*visible_list, attaches if !ONLYgood */);
    +  qh_makenewplanes(qh /* newfacet_list */);
    +  numnew= qh->facet_id - firstnew;
    +  newbalance= numnew - (realT) (qh->num_facets-qh->num_visible)
    +                         * qh->hull_dim/qh->num_vertices;
    +  wadd_(Wnewbalance, newbalance);
    +  wadd_(Wnewbalance2, newbalance * newbalance);
    +  if (qh->ONLYgood
    +  && !qh_findgood(qh, qh->newfacet_list, goodhorizon) && !qh->GOODclosest) {
    +    FORALLnew_facets
    +      qh_delfacet(qh, newfacet);
    +    qh_delvertex(qh, vertex);
    +    qh_resetlists(qh, True, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +    zinc_(Znotgoodnew);
    +    facet->notfurthest= True;
    +    return True;
    +  }
    +  if (qh->ONLYgood)
    +    qh_attachnewfacets(qh /*visible_list*/);
    +  qh_matchnewfacets(qh);
    +  qh_updatevertices(qh);
    +  if (qh->STOPcone && qh->furthest_id == qh->STOPcone-1) {
    +    facet->notfurthest= True;
    +    return False;  /* visible_list etc. still defined */
    +  }
    +  qh->findbestnew= False;
    +  if (qh->PREmerge || qh->MERGEexact) {
    +    qh_premerge(qh, vertex, qh->premerge_centrum, qh->premerge_cos);
    +    if (qh_USEfindbestnew)
    +      qh->findbestnew= True;
    +    else {
    +      FORALLnew_facets {
    +        if (!newfacet->simplicial) {
    +          qh->findbestnew= True;  /* use qh_findbestnew instead of qh_findbest*/
    +          break;
    +        }
    +      }
    +    }
    +  }else if (qh->BESToutside)
    +    qh->findbestnew= True;
    +  qh_partitionvisible(qh /*qh.visible_list*/, !qh_ALL, &numpoints);
    +  qh->findbestnew= False;
    +  qh->findbest_notsharp= False;
    +  zinc_(Zpbalance);
    +  pbalance= numpoints - (realT) qh->hull_dim /* assumes all points extreme */
    +                * (qh->num_points - qh->num_vertices)/qh->num_vertices;
    +  wadd_(Wpbalance, pbalance);
    +  wadd_(Wpbalance2, pbalance * pbalance);
    +  qh_deletevisible(qh /*qh.visible_list*/);
    +  zmax_(Zmaxvertex, qh->num_vertices);
    +  qh->NEWfacets= False;
    +  if (qh->IStracing >= 4) {
    +    if (qh->num_facets < 2000)
    +      qh_printlists(qh);
    +    qh_printfacetlist(qh, qh->newfacet_list, NULL, True);
    +    qh_checkpolygon(qh, qh->facet_list);
    +  }else if (qh->CHECKfrequently) {
    +    if (qh->num_facets < 50)
    +      qh_checkpolygon(qh, qh->facet_list);
    +    else
    +      qh_checkpolygon(qh, qh->newfacet_list);
    +  }
    +  if (qh->STOPpoint > 0 && qh->furthest_id == qh->STOPpoint-1)
    +    return False;
    +  qh_resetlists(qh, True, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +  /* qh_triangulate(qh); to test qh.TRInormals */
    +  trace2((qh, qh->ferr, 2056, "qh_addpoint: added p%d new facets %d new balance %2.2g point balance %2.2g\n",
    +    qh_pointid(qh, furthest), numnew, newbalance, pbalance));
    +  return True;
    +} /* addpoint */
    +
    +/*---------------------------------
    +
    +  qh_build_withrestart(qh)
    +    allow restarts due to qh.JOGGLEmax while calling qh_buildhull()
    +       qh_errexit always undoes qh_build_withrestart()
    +    qh.FIRSTpoint/qh.NUMpoints is point array
    +       it may be moved by qh_joggleinput(qh)
    +*/
    +void qh_build_withrestart(qhT *qh) {
    +  int restart;
    +
    +  qh->ALLOWrestart= True;
    +  while (True) {
    +    restart= setjmp(qh->restartexit); /* simple statement for CRAY J916 */
    +    if (restart) {       /* only from qh_precision() */
    +      zzinc_(Zretry);
    +      wmax_(Wretrymax, qh->JOGGLEmax);
    +      /* QH7078 warns about using 'TCn' with 'QJn' */
    +      qh->STOPcone= qh_IDunknown; /* if break from joggle, prevents normal output */
    +    }
    +    if (!qh->RERUN && qh->JOGGLEmax < REALmax/2) {
    +      if (qh->build_cnt > qh_JOGGLEmaxretry) {
    +        qh_fprintf(qh, qh->ferr, 6229, "qhull precision error: %d attempts to construct a convex hull\n\
    +        with joggled input.  Increase joggle above 'QJ%2.2g'\n\
    +        or modify qh_JOGGLE... parameters in user.h\n",
    +           qh->build_cnt, qh->JOGGLEmax);
    +        qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +      }
    +      if (qh->build_cnt && !restart)
    +        break;
    +    }else if (qh->build_cnt && qh->build_cnt >= qh->RERUN)
    +      break;
    +    qh->STOPcone= 0;
    +    qh_freebuild(qh, True);  /* first call is a nop */
    +    qh->build_cnt++;
    +    if (!qh->qhull_optionsiz)
    +      qh->qhull_optionsiz= (int)strlen(qh->qhull_options);   /* WARN64 */
    +    else {
    +      qh->qhull_options [qh->qhull_optionsiz]= '\0';
    +      qh->qhull_optionlen= qh_OPTIONline;  /* starts a new line */
    +    }
    +    qh_option(qh, "_run", &qh->build_cnt, NULL);
    +    if (qh->build_cnt == qh->RERUN) {
    +      qh->IStracing= qh->TRACElastrun;  /* duplicated from qh_initqhull_globals */
    +      if (qh->TRACEpoint != qh_IDunknown || qh->TRACEdist < REALmax/2 || qh->TRACEmerge) {
    +        qh->TRACElevel= (qh->IStracing? qh->IStracing : 3);
    +        qh->IStracing= 0;
    +      }
    +      qh->qhmem.IStracing= qh->IStracing;
    +    }
    +    if (qh->JOGGLEmax < REALmax/2)
    +      qh_joggleinput(qh);
    +    qh_initbuild(qh);
    +    qh_buildhull(qh);
    +    if (qh->JOGGLEmax < REALmax/2 && !qh->MERGING)
    +      qh_checkconvex(qh, qh->facet_list, qh_ALGORITHMfault);
    +  }
    +  qh->ALLOWrestart= False;
    +} /* qh_build_withrestart */
    +
    +/*---------------------------------
    +
    +  qh_buildhull(qh)
    +    construct a convex hull by adding outside points one at a time
    +
    +  returns:
    +
    +  notes:
    +    may be called multiple times
    +    checks facet and vertex lists for incorrect flags
    +    to recover from STOPcone, call qh_deletevisible and qh_resetlists
    +
    +  design:
    +    check visible facet and newfacet flags
    +    check newlist vertex flags and qh.STOPcone/STOPpoint
    +    for each facet with a furthest outside point
    +      add point to facet
    +      exit if qh.STOPcone or qh.STOPpoint requested
    +    if qh.NARROWhull for initial simplex
    +      partition remaining outside points to coplanar sets
    +*/
    +void qh_buildhull(qhT *qh) {
    +  facetT *facet;
    +  pointT *furthest;
    +  vertexT *vertex;
    +  int id;
    +
    +  trace1((qh, qh->ferr, 1037, "qh_buildhull: start build hull\n"));
    +  FORALLfacets {
    +    if (facet->visible || facet->newfacet) {
    +      qh_fprintf(qh, qh->ferr, 6165, "qhull internal error (qh_buildhull): visible or new facet f%d in facet list\n",
    +                   facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +  }
    +  FORALLvertices {
    +    if (vertex->newlist) {
    +      qh_fprintf(qh, qh->ferr, 6166, "qhull internal error (qh_buildhull): new vertex f%d in vertex list\n",
    +                   vertex->id);
    +      qh_errprint(qh, "ERRONEOUS", NULL, NULL, NULL, vertex);
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +    id= qh_pointid(qh, vertex->point);
    +    if ((qh->STOPpoint>0 && id == qh->STOPpoint-1) ||
    +        (qh->STOPpoint<0 && id == -qh->STOPpoint-1) ||
    +        (qh->STOPcone>0 && id == qh->STOPcone-1)) {
    +      trace1((qh, qh->ferr, 1038,"qh_buildhull: stop point or cone P%d in initial hull\n", id));
    +      return;
    +    }
    +  }
    +  qh->facet_next= qh->facet_list;      /* advance facet when processed */
    +  while ((furthest= qh_nextfurthest(qh, &facet))) {
    +    qh->num_outside--;  /* if ONLYmax, furthest may not be outside */
    +    if (!qh_addpoint(qh, furthest, facet, qh->ONLYmax))
    +      break;
    +  }
    +  if (qh->NARROWhull) /* move points from outsideset to coplanarset */
    +    qh_outcoplanar(qh /* facet_list */ );
    +  if (qh->num_outside && !furthest) {
    +    qh_fprintf(qh, qh->ferr, 6167, "qhull internal error (qh_buildhull): %d outside points were never processed.\n", qh->num_outside);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  trace1((qh, qh->ferr, 1039, "qh_buildhull: completed the hull construction\n"));
    +} /* buildhull */
    +
    +
    +/*---------------------------------
    +
    +  qh_buildtracing(qh, furthest, facet )
    +    trace an iteration of qh_buildhull() for furthest point and facet
    +    if !furthest, prints progress message
    +
    +  returns:
    +    tracks progress with qh.lastreport
    +    updates qh.furthest_id (-3 if furthest is NULL)
    +    also resets visit_id, vertext_visit on wrap around
    +
    +  see:
    +    qh_tracemerging()
    +
    +  design:
    +    if !furthest
    +      print progress message
    +      exit
    +    if 'TFn' iteration
    +      print progress message
    +    else if tracing
    +      trace furthest point and facet
    +    reset qh.visit_id and qh.vertex_visit if overflow may occur
    +    set qh.furthest_id for tracing
    +*/
    +void qh_buildtracing(qhT *qh, pointT *furthest, facetT *facet) {
    +  realT dist= 0;
    +  float cpu;
    +  int total, furthestid;
    +  time_t timedata;
    +  struct tm *tp;
    +  vertexT *vertex;
    +
    +  qh->old_randomdist= qh->RANDOMdist;
    +  qh->RANDOMdist= False;
    +  if (!furthest) {
    +    time(&timedata);
    +    tp= localtime(&timedata);
    +    cpu= (float)qh_CPUclock - (float)qh->hulltime;
    +    cpu /= (float)qh_SECticks;
    +    total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +    qh_fprintf(qh, qh->ferr, 8118, "\n\
    +At %02d:%02d:%02d & %2.5g CPU secs, qhull has created %d facets and merged %d.\n\
    + The current hull contains %d facets and %d vertices.  Last point was p%d\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, qh->facet_id -1,
    +      total, qh->num_facets, qh->num_vertices, qh->furthest_id);
    +    return;
    +  }
    +  furthestid= qh_pointid(qh, furthest);
    +  if (qh->TRACEpoint == furthestid) {
    +    qh->IStracing= qh->TRACElevel;
    +    qh->qhmem.IStracing= qh->TRACElevel;
    +  }else if (qh->TRACEpoint != qh_IDunknown && qh->TRACEdist < REALmax/2) {
    +    qh->IStracing= 0;
    +    qh->qhmem.IStracing= 0;
    +  }
    +  if (qh->REPORTfreq && (qh->facet_id-1 > qh->lastreport+qh->REPORTfreq)) {
    +    qh->lastreport= qh->facet_id-1;
    +    time(&timedata);
    +    tp= localtime(&timedata);
    +    cpu= (float)qh_CPUclock - (float)qh->hulltime;
    +    cpu /= (float)qh_SECticks;
    +    total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +    zinc_(Zdistio);
    +    qh_distplane(qh, furthest, facet, &dist);
    +    qh_fprintf(qh, qh->ferr, 8119, "\n\
    +At %02d:%02d:%02d & %2.5g CPU secs, qhull has created %d facets and merged %d.\n\
    + The current hull contains %d facets and %d vertices.  There are %d\n\
    + outside points.  Next is point p%d(v%d), %2.2g above f%d.\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, qh->facet_id -1,
    +      total, qh->num_facets, qh->num_vertices, qh->num_outside+1,
    +      furthestid, qh->vertex_id, dist, getid_(facet));
    +  }else if (qh->IStracing >=1) {
    +    cpu= (float)qh_CPUclock - (float)qh->hulltime;
    +    cpu /= (float)qh_SECticks;
    +    qh_distplane(qh, furthest, facet, &dist);
    +    qh_fprintf(qh, qh->ferr, 8120, "qh_addpoint: add p%d(v%d) to hull of %d facets(%2.2g above f%d) and %d outside at %4.4g CPU secs.  Previous was p%d.\n",
    +      furthestid, qh->vertex_id, qh->num_facets, dist,
    +      getid_(facet), qh->num_outside+1, cpu, qh->furthest_id);
    +  }
    +  zmax_(Zvisit2max, (int)qh->visit_id/2);
    +  if (qh->visit_id > (unsigned) INT_MAX) { /* 31 bits */
    +    zinc_(Zvisit);
    +    qh->visit_id= 0;
    +    FORALLfacets
    +      facet->visitid= 0;
    +  }
    +  zmax_(Zvvisit2max, (int)qh->vertex_visit/2);
    +  if (qh->vertex_visit > (unsigned) INT_MAX) { /* 31 bits */ 
    +    zinc_(Zvvisit);
    +    qh->vertex_visit= 0;
    +    FORALLvertices
    +      vertex->visitid= 0;
    +  }
    +  qh->furthest_id= furthestid;
    +  qh->RANDOMdist= qh->old_randomdist;
    +} /* buildtracing */
    +
    +/*---------------------------------
    +
    +  qh_errexit2(qh, exitcode, facet, otherfacet )
    +    return exitcode to system after an error
    +    report two facets
    +
    +  returns:
    +    assumes exitcode non-zero
    +
    +  see:
    +    normally use qh_errexit() in user.c(reports a facet and a ridge)
    +*/
    +void qh_errexit2(qhT *qh, int exitcode, facetT *facet, facetT *otherfacet) {
    +
    +  qh_errprint(qh, "ERRONEOUS", facet, otherfacet, NULL, NULL);
    +  qh_errexit(qh, exitcode, NULL, NULL);
    +} /* errexit2 */
    +
    +
    +/*---------------------------------
    +
    +  qh_findhorizon(qh, point, facet, goodvisible, goodhorizon )
    +    given a visible facet, find the point's horizon and visible facets
    +    for all facets, !facet-visible
    +
    +  returns:
    +    returns qh.visible_list/num_visible with all visible facets
    +      marks visible facets with ->visible
    +    updates count of good visible and good horizon facets
    +    updates qh.max_outside, qh.max_vertex, facet->maxoutside
    +
    +  see:
    +    similar to qh_delpoint()
    +
    +  design:
    +    move facet to qh.visible_list at end of qh.facet_list
    +    for all visible facets
    +     for each unvisited neighbor of a visible facet
    +       compute distance of point to neighbor
    +       if point above neighbor
    +         move neighbor to end of qh.visible_list
    +       else if point is coplanar with neighbor
    +         update qh.max_outside, qh.max_vertex, neighbor->maxoutside
    +         mark neighbor coplanar (will create a samecycle later)
    +         update horizon statistics
    +*/
    +void qh_findhorizon(qhT *qh, pointT *point, facetT *facet, int *goodvisible, int *goodhorizon) {
    +  facetT *neighbor, **neighborp, *visible;
    +  int numhorizon= 0, coplanar= 0;
    +  realT dist;
    +
    +  trace1((qh, qh->ferr, 1040,"qh_findhorizon: find horizon for point p%d facet f%d\n",qh_pointid(qh, point),facet->id));
    +  *goodvisible= *goodhorizon= 0;
    +  zinc_(Ztotvisible);
    +  qh_removefacet(qh, facet);  /* visible_list at end of qh->facet_list */
    +  qh_appendfacet(qh, facet);
    +  qh->num_visible= 1;
    +  if (facet->good)
    +    (*goodvisible)++;
    +  qh->visible_list= facet;
    +  facet->visible= True;
    +  facet->f.replace= NULL;
    +  if (qh->IStracing >=4)
    +    qh_errprint(qh, "visible", facet, NULL, NULL, NULL);
    +  qh->visit_id++;
    +  FORALLvisible_facets {
    +    if (visible->tricoplanar && !qh->TRInormals) {
    +      qh_fprintf(qh, qh->ferr, 6230, "Qhull internal error (qh_findhorizon): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit(qh, qh_ERRqhull, visible, NULL);
    +    }
    +    visible->visitid= qh->visit_id;
    +    FOREACHneighbor_(visible) {
    +      if (neighbor->visitid == qh->visit_id)
    +        continue;
    +      neighbor->visitid= qh->visit_id;
    +      zzinc_(Znumvisibility);
    +      qh_distplane(qh, point, neighbor, &dist);
    +      if (dist > qh->MINvisible) {
    +        zinc_(Ztotvisible);
    +        qh_removefacet(qh, neighbor);  /* append to end of qh->visible_list */
    +        qh_appendfacet(qh, neighbor);
    +        neighbor->visible= True;
    +        neighbor->f.replace= NULL;
    +        qh->num_visible++;
    +        if (neighbor->good)
    +          (*goodvisible)++;
    +        if (qh->IStracing >=4)
    +          qh_errprint(qh, "visible", neighbor, NULL, NULL, NULL);
    +      }else {
    +        if (dist > - qh->MAXcoplanar) {
    +          neighbor->coplanar= True;
    +          zzinc_(Zcoplanarhorizon);
    +          qh_precision(qh, "coplanar horizon");
    +          coplanar++;
    +          if (qh->MERGING) {
    +            if (dist > 0) {
    +              maximize_(qh->max_outside, dist);
    +              maximize_(qh->max_vertex, dist);
    +#if qh_MAXoutside
    +              maximize_(neighbor->maxoutside, dist);
    +#endif
    +            }else
    +              minimize_(qh->min_vertex, dist);  /* due to merge later */
    +          }
    +          trace2((qh, qh->ferr, 2057, "qh_findhorizon: point p%d is coplanar to horizon f%d, dist=%2.7g < qh->MINvisible(%2.7g)\n",
    +              qh_pointid(qh, point), neighbor->id, dist, qh->MINvisible));
    +        }else
    +          neighbor->coplanar= False;
    +        zinc_(Ztothorizon);
    +        numhorizon++;
    +        if (neighbor->good)
    +          (*goodhorizon)++;
    +        if (qh->IStracing >=4)
    +          qh_errprint(qh, "horizon", neighbor, NULL, NULL, NULL);
    +      }
    +    }
    +  }
    +  if (!numhorizon) {
    +    qh_precision(qh, "empty horizon");
    +    qh_fprintf(qh, qh->ferr, 6168, "qhull precision error (qh_findhorizon): empty horizon\n\
    +QhullPoint p%d was above all facets.\n", qh_pointid(qh, point));
    +    qh_printfacetlist(qh, qh->facet_list, NULL, True);
    +    qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +  }
    +  trace1((qh, qh->ferr, 1041, "qh_findhorizon: %d horizon facets(good %d), %d visible(good %d), %d coplanar\n",
    +       numhorizon, *goodhorizon, qh->num_visible, *goodvisible, coplanar));
    +  if (qh->IStracing >= 4 && qh->num_facets < 50)
    +    qh_printlists(qh);
    +} /* findhorizon */
    +
    +/*---------------------------------
    +
    +  qh_nextfurthest(qh, visible )
    +    returns next furthest point and visible facet for qh_addpoint()
    +    starts search at qh.facet_next
    +
    +  returns:
    +    removes furthest point from outside set
    +    NULL if none available
    +    advances qh.facet_next over facets with empty outside sets
    +
    +  design:
    +    for each facet from qh.facet_next
    +      if empty outside set
    +        advance qh.facet_next
    +      else if qh.NARROWhull
    +        determine furthest outside point
    +        if furthest point is not outside
    +          advance qh.facet_next(point will be coplanar)
    +    remove furthest point from outside set
    +*/
    +pointT *qh_nextfurthest(qhT *qh, facetT **visible) {
    +  facetT *facet;
    +  int size, idx;
    +  realT randr, dist;
    +  pointT *furthest;
    +
    +  while ((facet= qh->facet_next) != qh->facet_tail) {
    +    if (!facet->outsideset) {
    +      qh->facet_next= facet->next;
    +      continue;
    +    }
    +    SETreturnsize_(facet->outsideset, size);
    +    if (!size) {
    +      qh_setfree(qh, &facet->outsideset);
    +      qh->facet_next= facet->next;
    +      continue;
    +    }
    +    if (qh->NARROWhull) {
    +      if (facet->notfurthest)
    +        qh_furthestout(qh, facet);
    +      furthest= (pointT*)qh_setlast(facet->outsideset);
    +#if qh_COMPUTEfurthest
    +      qh_distplane(qh, furthest, facet, &dist);
    +      zinc_(Zcomputefurthest);
    +#else
    +      dist= facet->furthestdist;
    +#endif
    +      if (dist < qh->MINoutside) { /* remainder of outside set is coplanar for qh_outcoplanar */
    +        qh->facet_next= facet->next;
    +        continue;
    +      }
    +    }
    +    if (!qh->RANDOMoutside && !qh->VIRTUALmemory) {
    +      if (qh->PICKfurthest) {
    +        qh_furthestnext(qh /* qh->facet_list */);
    +        facet= qh->facet_next;
    +      }
    +      *visible= facet;
    +      return((pointT*)qh_setdellast(facet->outsideset));
    +    }
    +    if (qh->RANDOMoutside) {
    +      int outcoplanar = 0;
    +      if (qh->NARROWhull) {
    +        FORALLfacets {
    +          if (facet == qh->facet_next)
    +            break;
    +          if (facet->outsideset)
    +            outcoplanar += qh_setsize(qh, facet->outsideset);
    +        }
    +      }
    +      randr= qh_RANDOMint;
    +      randr= randr/(qh_RANDOMmax+1);
    +      idx= (int)floor((qh->num_outside - outcoplanar) * randr);
    +      FORALLfacet_(qh->facet_next) {
    +        if (facet->outsideset) {
    +          SETreturnsize_(facet->outsideset, size);
    +          if (!size)
    +            qh_setfree(qh, &facet->outsideset);
    +          else if (size > idx) {
    +            *visible= facet;
    +            return((pointT*)qh_setdelnth(qh, facet->outsideset, idx));
    +          }else
    +            idx -= size;
    +        }
    +      }
    +      qh_fprintf(qh, qh->ferr, 6169, "qhull internal error (qh_nextfurthest): num_outside %d is too low\nby at least %d, or a random real %g >= 1.0\n",
    +              qh->num_outside, idx+1, randr);
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }else { /* VIRTUALmemory */
    +      facet= qh->facet_tail->previous;
    +      if (!(furthest= (pointT*)qh_setdellast(facet->outsideset))) {
    +        if (facet->outsideset)
    +          qh_setfree(qh, &facet->outsideset);
    +        qh_removefacet(qh, facet);
    +        qh_prependfacet(qh, facet, &qh->facet_list);
    +        continue;
    +      }
    +      *visible= facet;
    +      return furthest;
    +    }
    +  }
    +  return NULL;
    +} /* nextfurthest */
    +
    +/*---------------------------------
    +
    +  qh_partitionall(qh, vertices, points, numpoints )
    +    partitions all points in points/numpoints to the outsidesets of facets
    +    vertices= vertices in qh.facet_list(!partitioned)
    +
    +  returns:
    +    builds facet->outsideset
    +    does not partition qh.GOODpoint
    +    if qh.ONLYgood && !qh.MERGING,
    +      does not partition qh.GOODvertex
    +
    +  notes:
    +    faster if qh.facet_list sorted by anticipated size of outside set
    +
    +  design:
    +    initialize pointset with all points
    +    remove vertices from pointset
    +    remove qh.GOODpointp from pointset (unless it's qh.STOPcone or qh.STOPpoint)
    +    for all facets
    +      for all remaining points in pointset
    +        compute distance from point to facet
    +        if point is outside facet
    +          remove point from pointset (by not reappending)
    +          update bestpoint
    +          append point or old bestpoint to facet's outside set
    +      append bestpoint to facet's outside set (furthest)
    +    for all points remaining in pointset
    +      partition point into facets' outside sets and coplanar sets
    +*/
    +void qh_partitionall(qhT *qh, setT *vertices, pointT *points, int numpoints){
    +  setT *pointset;
    +  vertexT *vertex, **vertexp;
    +  pointT *point, **pointp, *bestpoint;
    +  int size, point_i, point_n, point_end, remaining, i, id;
    +  facetT *facet;
    +  realT bestdist= -REALmax, dist, distoutside;
    +
    +  trace1((qh, qh->ferr, 1042, "qh_partitionall: partition all points into outside sets\n"));
    +  pointset= qh_settemp(qh, numpoints);
    +  qh->num_outside= 0;
    +  pointp= SETaddr_(pointset, pointT);
    +  for (i=numpoints, point= points; i--; point += qh->hull_dim)
    +    *(pointp++)= point;
    +  qh_settruncate(qh, pointset, numpoints);
    +  FOREACHvertex_(vertices) {
    +    if ((id= qh_pointid(qh, vertex->point)) >= 0)
    +      SETelem_(pointset, id)= NULL;
    +  }
    +  id= qh_pointid(qh, qh->GOODpointp);
    +  if (id >=0 && qh->STOPcone-1 != id && -qh->STOPpoint-1 != id)
    +    SETelem_(pointset, id)= NULL;
    +  if (qh->GOODvertexp && qh->ONLYgood && !qh->MERGING) { /* matches qhull()*/
    +    if ((id= qh_pointid(qh, qh->GOODvertexp)) >= 0)
    +      SETelem_(pointset, id)= NULL;
    +  }
    +  if (!qh->BESToutside) {  /* matches conditional for qh_partitionpoint below */
    +    distoutside= qh_DISToutside; /* multiple of qh.MINoutside & qh.max_outside, see user.h */
    +    zval_(Ztotpartition)= qh->num_points - qh->hull_dim - 1; /*misses GOOD... */
    +    remaining= qh->num_facets;
    +    point_end= numpoints;
    +    FORALLfacets {
    +      size= point_end/(remaining--) + 100;
    +      facet->outsideset= qh_setnew(qh, size);
    +      bestpoint= NULL;
    +      point_end= 0;
    +      FOREACHpoint_i_(qh, pointset) {
    +        if (point) {
    +          zzinc_(Zpartitionall);
    +          qh_distplane(qh, point, facet, &dist);
    +          if (dist < distoutside)
    +            SETelem_(pointset, point_end++)= point;
    +          else {
    +            qh->num_outside++;
    +            if (!bestpoint) {
    +              bestpoint= point;
    +              bestdist= dist;
    +            }else if (dist > bestdist) {
    +              qh_setappend(qh, &facet->outsideset, bestpoint);
    +              bestpoint= point;
    +              bestdist= dist;
    +            }else
    +              qh_setappend(qh, &facet->outsideset, point);
    +          }
    +        }
    +      }
    +      if (bestpoint) {
    +        qh_setappend(qh, &facet->outsideset, bestpoint);
    +#if !qh_COMPUTEfurthest
    +        facet->furthestdist= bestdist;
    +#endif
    +      }else
    +        qh_setfree(qh, &facet->outsideset);
    +      qh_settruncate(qh, pointset, point_end);
    +    }
    +  }
    +  /* if !qh->BESToutside, pointset contains points not assigned to outsideset */
    +  if (qh->BESToutside || qh->MERGING || qh->KEEPcoplanar || qh->KEEPinside) {
    +    qh->findbestnew= True;
    +    FOREACHpoint_i_(qh, pointset) {
    +      if (point)
    +        qh_partitionpoint(qh, point, qh->facet_list);
    +    }
    +    qh->findbestnew= False;
    +  }
    +  zzadd_(Zpartitionall, zzval_(Zpartition));
    +  zzval_(Zpartition)= 0;
    +  qh_settempfree(qh, &pointset);
    +  if (qh->IStracing >= 4)
    +    qh_printfacetlist(qh, qh->facet_list, NULL, True);
    +} /* partitionall */
    +
    +
    +/*---------------------------------
    +
    +  qh_partitioncoplanar(qh, point, facet, dist )
    +    partition coplanar point to a facet
    +    dist is distance from point to facet
    +    if dist NULL,
    +      searches for bestfacet and does nothing if inside
    +    if qh.findbestnew set,
    +      searches new facets instead of using qh_findbest()
    +
    +  returns:
    +    qh.max_ouside updated
    +    if qh.KEEPcoplanar or qh.KEEPinside
    +      point assigned to best coplanarset
    +
    +  notes:
    +    facet->maxoutside is updated at end by qh_check_maxout
    +
    +  design:
    +    if dist undefined
    +      find best facet for point
    +      if point sufficiently below facet (depends on qh.NEARinside and qh.KEEPinside)
    +        exit
    +    if keeping coplanar/nearinside/inside points
    +      if point is above furthest coplanar point
    +        append point to coplanar set (it is the new furthest)
    +        update qh.max_outside
    +      else
    +        append point one before end of coplanar set
    +    else if point is clearly outside of qh.max_outside and bestfacet->coplanarset
    +    and bestfacet is more than perpendicular to facet
    +      repartition the point using qh_findbest() -- it may be put on an outsideset
    +    else
    +      update qh.max_outside
    +*/
    +void qh_partitioncoplanar(qhT *qh, pointT *point, facetT *facet, realT *dist) {
    +  facetT *bestfacet;
    +  pointT *oldfurthest;
    +  realT bestdist, dist2= 0, angle;
    +  int numpart= 0, oldfindbest;
    +  boolT isoutside;
    +
    +  qh->WAScoplanar= True;
    +  if (!dist) {
    +    if (qh->findbestnew)
    +      bestfacet= qh_findbestnew(qh, point, facet, &bestdist, qh_ALL, &isoutside, &numpart);
    +    else
    +      bestfacet= qh_findbest(qh, point, facet, qh_ALL, !qh_ISnewfacets, qh->DELAUNAY,
    +                          &bestdist, &isoutside, &numpart);
    +    zinc_(Ztotpartcoplanar);
    +    zzadd_(Zpartcoplanar, numpart);
    +    if (!qh->DELAUNAY && !qh->KEEPinside) { /*  for 'd', bestdist skips upperDelaunay facets */
    +      if (qh->KEEPnearinside) {
    +        if (bestdist < -qh->NEARinside) {
    +          zinc_(Zcoplanarinside);
    +          trace4((qh, qh->ferr, 4062, "qh_partitioncoplanar: point p%d is more than near-inside facet f%d dist %2.2g findbestnew %d\n",
    +                  qh_pointid(qh, point), bestfacet->id, bestdist, qh->findbestnew));
    +          return;
    +        }
    +      }else if (bestdist < -qh->MAXcoplanar) {
    +          trace4((qh, qh->ferr, 4063, "qh_partitioncoplanar: point p%d is inside facet f%d dist %2.2g findbestnew %d\n",
    +                  qh_pointid(qh, point), bestfacet->id, bestdist, qh->findbestnew));
    +        zinc_(Zcoplanarinside);
    +        return;
    +      }
    +    }
    +  }else {
    +    bestfacet= facet;
    +    bestdist= *dist;
    +  }
    +  if (bestdist > qh->max_outside) {
    +    if (!dist && facet != bestfacet) {
    +      zinc_(Zpartangle);
    +      angle= qh_getangle(qh, facet->normal, bestfacet->normal);
    +      if (angle < 0) {
    +        /* typically due to deleted vertex and coplanar facets, e.g.,
    +             RBOX 1000 s Z1 G1e-13 t1001185205 | QHULL Tv */
    +        zinc_(Zpartflip);
    +        trace2((qh, qh->ferr, 2058, "qh_partitioncoplanar: repartition point p%d from f%d.  It is above flipped facet f%d dist %2.2g\n",
    +                qh_pointid(qh, point), facet->id, bestfacet->id, bestdist));
    +        oldfindbest= qh->findbestnew;
    +        qh->findbestnew= False;
    +        qh_partitionpoint(qh, point, bestfacet);
    +        qh->findbestnew= oldfindbest;
    +        return;
    +      }
    +    }
    +    qh->max_outside= bestdist;
    +    if (bestdist > qh->TRACEdist) {
    +      qh_fprintf(qh, qh->ferr, 8122, "qh_partitioncoplanar: ====== p%d from f%d increases max_outside to %2.2g of f%d last p%d\n",
    +                     qh_pointid(qh, point), facet->id, bestdist, bestfacet->id, qh->furthest_id);
    +      qh_errprint(qh, "DISTANT", facet, bestfacet, NULL, NULL);
    +    }
    +  }
    +  if (qh->KEEPcoplanar + qh->KEEPinside + qh->KEEPnearinside) {
    +    oldfurthest= (pointT*)qh_setlast(bestfacet->coplanarset);
    +    if (oldfurthest) {
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(qh, oldfurthest, bestfacet, &dist2);
    +    }
    +    if (!oldfurthest || dist2 < bestdist)
    +      qh_setappend(qh, &bestfacet->coplanarset, point);
    +    else
    +      qh_setappend2ndlast(qh, &bestfacet->coplanarset, point);
    +  }
    +  trace4((qh, qh->ferr, 4064, "qh_partitioncoplanar: point p%d is coplanar with facet f%d(or inside) dist %2.2g\n",
    +          qh_pointid(qh, point), bestfacet->id, bestdist));
    +} /* partitioncoplanar */
    +
    +/*---------------------------------
    +
    +  qh_partitionpoint(qh, point, facet )
    +    assigns point to an outside set, coplanar set, or inside set (i.e., dropt)
    +    if qh.findbestnew
    +      uses qh_findbestnew() to search all new facets
    +    else
    +      uses qh_findbest()
    +
    +  notes:
    +    after qh_distplane(), this and qh_findbest() are most expensive in 3-d
    +
    +  design:
    +    find best facet for point
    +      (either exhaustive search of new facets or directed search from facet)
    +    if qh.NARROWhull
    +      retain coplanar and nearinside points as outside points
    +    if point is outside bestfacet
    +      if point above furthest point for bestfacet
    +        append point to outside set (it becomes the new furthest)
    +        if outside set was empty
    +          move bestfacet to end of qh.facet_list (i.e., after qh.facet_next)
    +        update bestfacet->furthestdist
    +      else
    +        append point one before end of outside set
    +    else if point is coplanar to bestfacet
    +      if keeping coplanar points or need to update qh.max_outside
    +        partition coplanar point into bestfacet
    +    else if near-inside point
    +      partition as coplanar point into bestfacet
    +    else is an inside point
    +      if keeping inside points
    +        partition as coplanar point into bestfacet
    +*/
    +void qh_partitionpoint(qhT *qh, pointT *point, facetT *facet) {
    +  realT bestdist;
    +  boolT isoutside;
    +  facetT *bestfacet;
    +  int numpart;
    +#if qh_COMPUTEfurthest
    +  realT dist;
    +#endif
    +
    +  if (qh->findbestnew)
    +    bestfacet= qh_findbestnew(qh, point, facet, &bestdist, qh->BESToutside, &isoutside, &numpart);
    +  else
    +    bestfacet= qh_findbest(qh, point, facet, qh->BESToutside, qh_ISnewfacets, !qh_NOupper,
    +                          &bestdist, &isoutside, &numpart);
    +  zinc_(Ztotpartition);
    +  zzadd_(Zpartition, numpart);
    +  if (qh->NARROWhull) {
    +    if (qh->DELAUNAY && !isoutside && bestdist >= -qh->MAXcoplanar)
    +      qh_precision(qh, "nearly incident point(narrow hull)");
    +    if (qh->KEEPnearinside) {
    +      if (bestdist >= -qh->NEARinside)
    +        isoutside= True;
    +    }else if (bestdist >= -qh->MAXcoplanar)
    +      isoutside= True;
    +  }
    +
    +  if (isoutside) {
    +    if (!bestfacet->outsideset
    +    || !qh_setlast(bestfacet->outsideset)) {
    +      qh_setappend(qh, &(bestfacet->outsideset), point);
    +      if (!bestfacet->newfacet) {
    +        qh_removefacet(qh, bestfacet);  /* make sure it's after qh->facet_next */
    +        qh_appendfacet(qh, bestfacet);
    +      }
    +#if !qh_COMPUTEfurthest
    +      bestfacet->furthestdist= bestdist;
    +#endif
    +    }else {
    +#if qh_COMPUTEfurthest
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(qh, oldfurthest, bestfacet, &dist);
    +      if (dist < bestdist)
    +        qh_setappend(qh, &(bestfacet->outsideset), point);
    +      else
    +        qh_setappend2ndlast(qh, &(bestfacet->outsideset), point);
    +#else
    +      if (bestfacet->furthestdist < bestdist) {
    +        qh_setappend(qh, &(bestfacet->outsideset), point);
    +        bestfacet->furthestdist= bestdist;
    +      }else
    +        qh_setappend2ndlast(qh, &(bestfacet->outsideset), point);
    +#endif
    +    }
    +    qh->num_outside++;
    +    trace4((qh, qh->ferr, 4065, "qh_partitionpoint: point p%d is outside facet f%d new? %d (or narrowhull)\n",
    +          qh_pointid(qh, point), bestfacet->id, bestfacet->newfacet));
    +  }else if (qh->DELAUNAY || bestdist >= -qh->MAXcoplanar) { /* for 'd', bestdist skips upperDelaunay facets */
    +    zzinc_(Zcoplanarpart);
    +    if (qh->DELAUNAY)
    +      qh_precision(qh, "nearly incident point");
    +    if ((qh->KEEPcoplanar + qh->KEEPnearinside) || bestdist > qh->max_outside)
    +      qh_partitioncoplanar(qh, point, bestfacet, &bestdist);
    +    else {
    +      trace4((qh, qh->ferr, 4066, "qh_partitionpoint: point p%d is coplanar to facet f%d (dropped)\n",
    +          qh_pointid(qh, point), bestfacet->id));
    +    }
    +  }else if (qh->KEEPnearinside && bestdist > -qh->NEARinside) {
    +    zinc_(Zpartnear);
    +    qh_partitioncoplanar(qh, point, bestfacet, &bestdist);
    +  }else {
    +    zinc_(Zpartinside);
    +    trace4((qh, qh->ferr, 4067, "qh_partitionpoint: point p%d is inside all facets, closest to f%d dist %2.2g\n",
    +          qh_pointid(qh, point), bestfacet->id, bestdist));
    +    if (qh->KEEPinside)
    +      qh_partitioncoplanar(qh, point, bestfacet, &bestdist);
    +  }
    +} /* partitionpoint */
    +
    +/*---------------------------------
    +
    +  qh_partitionvisible(qh, allpoints, numoutside )
    +    partitions points in visible facets to qh.newfacet_list
    +    qh.visible_list= visible facets
    +    for visible facets
    +      1st neighbor (if any) points to a horizon facet or a new facet
    +    if allpoints(!used),
    +      repartitions coplanar points
    +
    +  returns:
    +    updates outside sets and coplanar sets of qh.newfacet_list
    +    updates qh.num_outside (count of outside points)
    +
    +  notes:
    +    qh.findbest_notsharp should be clear (extra work if set)
    +
    +  design:
    +    for all visible facets with outside set or coplanar set
    +      select a newfacet for visible facet
    +      if outside set
    +        partition outside set into new facets
    +      if coplanar set and keeping coplanar/near-inside/inside points
    +        if allpoints
    +          partition coplanar set into new facets, may be assigned outside
    +        else
    +          partition coplanar set into coplanar sets of new facets
    +    for each deleted vertex
    +      if allpoints
    +        partition vertex into new facets, may be assigned outside
    +      else
    +        partition vertex into coplanar sets of new facets
    +*/
    +void qh_partitionvisible(qhT *qh /*qh.visible_list*/, boolT allpoints, int *numoutside) {
    +  facetT *visible, *newfacet;
    +  pointT *point, **pointp;
    +  int coplanar=0, size;
    +  unsigned count;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh->ONLYmax)
    +    maximize_(qh->MINoutside, qh->max_vertex);
    +  *numoutside= 0;
    +  FORALLvisible_facets {
    +    if (!visible->outsideset && !visible->coplanarset)
    +      continue;
    +    newfacet= visible->f.replace;
    +    count= 0;
    +    while (newfacet && newfacet->visible) {
    +      newfacet= newfacet->f.replace;
    +      if (count++ > qh->facet_id)
    +        qh_infiniteloop(qh, visible);
    +    }
    +    if (!newfacet)
    +      newfacet= qh->newfacet_list;
    +    if (newfacet == qh->facet_tail) {
    +      qh_fprintf(qh, qh->ferr, 6170, "qhull precision error (qh_partitionvisible): all new facets deleted as\n        degenerate facets. Can not continue.\n");
    +      qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +    }
    +    if (visible->outsideset) {
    +      size= qh_setsize(qh, visible->outsideset);
    +      *numoutside += size;
    +      qh->num_outside -= size;
    +      FOREACHpoint_(visible->outsideset)
    +        qh_partitionpoint(qh, point, newfacet);
    +    }
    +    if (visible->coplanarset && (qh->KEEPcoplanar + qh->KEEPinside + qh->KEEPnearinside)) {
    +      size= qh_setsize(qh, visible->coplanarset);
    +      coplanar += size;
    +      FOREACHpoint_(visible->coplanarset) {
    +        if (allpoints) /* not used */
    +          qh_partitionpoint(qh, point, newfacet);
    +        else
    +          qh_partitioncoplanar(qh, point, newfacet, NULL);
    +      }
    +    }
    +  }
    +  FOREACHvertex_(qh->del_vertices) {
    +    if (vertex->point) {
    +      if (allpoints) /* not used */
    +        qh_partitionpoint(qh, vertex->point, qh->newfacet_list);
    +      else
    +        qh_partitioncoplanar(qh, vertex->point, qh->newfacet_list, NULL);
    +    }
    +  }
    +  trace1((qh, qh->ferr, 1043,"qh_partitionvisible: partitioned %d points from outsidesets and %d points from coplanarsets\n", *numoutside, coplanar));
    +} /* partitionvisible */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_precision(qh, reason )
    +    restart on precision errors if not merging and if 'QJn'
    +*/
    +void qh_precision(qhT *qh, const char *reason) {
    +
    +  if (qh->ALLOWrestart && !qh->PREmerge && !qh->MERGEexact) {
    +    if (qh->JOGGLEmax < REALmax/2) {
    +      trace0((qh, qh->ferr, 26, "qh_precision: qhull restart because of %s\n", reason));
    +      /* May be called repeatedly if qh->ALLOWrestart */
    +      longjmp(qh->restartexit, qh_ERRprec);
    +    }
    +  }
    +} /* qh_precision */
    +
    +/*---------------------------------
    +
    +  qh_printsummary(qh, fp )
    +    prints summary to fp
    +
    +  notes:
    +    not in io_r.c so that user_eg.c can prevent io_r.c from loading
    +    qh_printsummary and qh_countfacets must match counts
    +
    +  design:
    +    determine number of points, vertices, and coplanar points
    +    print summary
    +*/
    +void qh_printsummary(qhT *qh, FILE *fp) {
    +  realT ratio, outerplane, innerplane;
    +  float cpu;
    +  int size, id, nummerged, numvertices, numcoplanars= 0, nonsimplicial=0;
    +  int goodused;
    +  facetT *facet;
    +  const char *s;
    +  int numdel= zzval_(Zdelvertextot);
    +  int numtricoplanars= 0;
    +
    +  size= qh->num_points + qh_setsize(qh, qh->other_points);
    +  numvertices= qh->num_vertices - qh_setsize(qh, qh->del_vertices);
    +  id= qh_pointid(qh, qh->GOODpointp);
    +  FORALLfacets {
    +    if (facet->coplanarset)
    +      numcoplanars += qh_setsize(qh, facet->coplanarset);
    +    if (facet->good) {
    +      if (facet->simplicial) {
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else if (qh_setsize(qh, facet->vertices) != qh->hull_dim)
    +        nonsimplicial++;
    +    }
    +  }
    +  if (id >=0 && qh->STOPcone-1 != id && -qh->STOPpoint-1 != id)
    +    size--;
    +  if (qh->STOPcone || qh->STOPpoint)
    +      qh_fprintf(qh, fp, 9288, "\nAt a premature exit due to 'TVn', 'TCn', 'TRn', or precision error with 'QJn'.");
    +  if (qh->UPPERdelaunay)
    +    goodused= qh->GOODvertex + qh->GOODpoint + qh->SPLITthresholds;
    +  else if (qh->DELAUNAY)
    +    goodused= qh->GOODvertex + qh->GOODpoint + qh->GOODthreshold;
    +  else
    +    goodused= qh->num_good;
    +  nummerged= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +  if (qh->VORONOI) {
    +    if (qh->UPPERdelaunay)
    +      qh_fprintf(qh, fp, 9289, "\n\
    +Furthest-site Voronoi vertices by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    else
    +      qh_fprintf(qh, fp, 9290, "\n\
    +Voronoi diagram by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    qh_fprintf(qh, fp, 9291, "  Number of Voronoi regions%s: %d\n",
    +              qh->ATinfinity ? " and at-infinity" : "", numvertices);
    +    if (numdel)
    +      qh_fprintf(qh, fp, 9292, "  Total number of deleted points due to merging: %d\n", numdel);
    +    if (numcoplanars - numdel > 0)
    +      qh_fprintf(qh, fp, 9293, "  Number of nearly incident points: %d\n", numcoplanars - numdel);
    +    else if (size - numvertices - numdel > 0)
    +      qh_fprintf(qh, fp, 9294, "  Total number of nearly incident points: %d\n", size - numvertices - numdel);
    +    qh_fprintf(qh, fp, 9295, "  Number of%s Voronoi vertices: %d\n",
    +              goodused ? " 'good'" : "", qh->num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(qh, fp, 9296, "  Number of%s non-simplicial Voronoi vertices: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else if (qh->DELAUNAY) {
    +    if (qh->UPPERdelaunay)
    +      qh_fprintf(qh, fp, 9297, "\n\
    +Furthest-site Delaunay triangulation by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    else
    +      qh_fprintf(qh, fp, 9298, "\n\
    +Delaunay triangulation by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    qh_fprintf(qh, fp, 9299, "  Number of input sites%s: %d\n",
    +              qh->ATinfinity ? " and at-infinity" : "", numvertices);
    +    if (numdel)
    +      qh_fprintf(qh, fp, 9300, "  Total number of deleted points due to merging: %d\n", numdel);
    +    if (numcoplanars - numdel > 0)
    +      qh_fprintf(qh, fp, 9301, "  Number of nearly incident points: %d\n", numcoplanars - numdel);
    +    else if (size - numvertices - numdel > 0)
    +      qh_fprintf(qh, fp, 9302, "  Total number of nearly incident points: %d\n", size - numvertices - numdel);
    +    qh_fprintf(qh, fp, 9303, "  Number of%s Delaunay regions: %d\n",
    +              goodused ? " 'good'" : "", qh->num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(qh, fp, 9304, "  Number of%s non-simplicial Delaunay regions: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else if (qh->HALFspace) {
    +    qh_fprintf(qh, fp, 9305, "\n\
    +Halfspace intersection by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    qh_fprintf(qh, fp, 9306, "  Number of halfspaces: %d\n", size);
    +    qh_fprintf(qh, fp, 9307, "  Number of non-redundant halfspaces: %d\n", numvertices);
    +    if (numcoplanars) {
    +      if (qh->KEEPinside && qh->KEEPcoplanar)
    +        s= "similar and redundant";
    +      else if (qh->KEEPinside)
    +        s= "redundant";
    +      else
    +        s= "similar";
    +      qh_fprintf(qh, fp, 9308, "  Number of %s halfspaces: %d\n", s, numcoplanars);
    +    }
    +    qh_fprintf(qh, fp, 9309, "  Number of intersection points: %d\n", qh->num_facets - qh->num_visible);
    +    if (goodused)
    +      qh_fprintf(qh, fp, 9310, "  Number of 'good' intersection points: %d\n", qh->num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(qh, fp, 9311, "  Number of%s non-simplicial intersection points: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else {
    +    qh_fprintf(qh, fp, 9312, "\n\
    +Convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    qh_fprintf(qh, fp, 9313, "  Number of vertices: %d\n", numvertices);
    +    if (numcoplanars) {
    +      if (qh->KEEPinside && qh->KEEPcoplanar)
    +        s= "coplanar and interior";
    +      else if (qh->KEEPinside)
    +        s= "interior";
    +      else
    +        s= "coplanar";
    +      qh_fprintf(qh, fp, 9314, "  Number of %s points: %d\n", s, numcoplanars);
    +    }
    +    qh_fprintf(qh, fp, 9315, "  Number of facets: %d\n", qh->num_facets - qh->num_visible);
    +    if (goodused)
    +      qh_fprintf(qh, fp, 9316, "  Number of 'good' facets: %d\n", qh->num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(qh, fp, 9317, "  Number of%s non-simplicial facets: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }
    +  if (numtricoplanars)
    +      qh_fprintf(qh, fp, 9318, "  Number of triangulated facets: %d\n", numtricoplanars);
    +  qh_fprintf(qh, fp, 9319, "\nStatistics for: %s | %s",
    +                      qh->rbox_command, qh->qhull_command);
    +  if (qh->ROTATErandom != INT_MIN)
    +    qh_fprintf(qh, fp, 9320, " QR%d\n\n", qh->ROTATErandom);
    +  else
    +    qh_fprintf(qh, fp, 9321, "\n\n");
    +  qh_fprintf(qh, fp, 9322, "  Number of points processed: %d\n", zzval_(Zprocessed));
    +  qh_fprintf(qh, fp, 9323, "  Number of hyperplanes created: %d\n", zzval_(Zsetplane));
    +  if (qh->DELAUNAY)
    +    qh_fprintf(qh, fp, 9324, "  Number of facets in hull: %d\n", qh->num_facets - qh->num_visible);
    +  qh_fprintf(qh, fp, 9325, "  Number of distance tests for qhull: %d\n", zzval_(Zpartition)+
    +      zzval_(Zpartitionall)+zzval_(Znumvisibility)+zzval_(Zpartcoplanar));
    +#if 0  /* NOTE: must print before printstatistics() */
    +  {realT stddev, ave;
    +  qh_fprintf(qh, fp, 9326, "  average new facet balance: %2.2g\n",
    +          wval_(Wnewbalance)/zval_(Zprocessed));
    +  stddev= qh_stddev(zval_(Zprocessed), wval_(Wnewbalance),
    +                                 wval_(Wnewbalance2), &ave);
    +  qh_fprintf(qh, fp, 9327, "  new facet standard deviation: %2.2g\n", stddev);
    +  qh_fprintf(qh, fp, 9328, "  average partition balance: %2.2g\n",
    +          wval_(Wpbalance)/zval_(Zpbalance));
    +  stddev= qh_stddev(zval_(Zpbalance), wval_(Wpbalance),
    +                                 wval_(Wpbalance2), &ave);
    +  qh_fprintf(qh, fp, 9329, "  partition standard deviation: %2.2g\n", stddev);
    +  }
    +#endif
    +  if (nummerged) {
    +    qh_fprintf(qh, fp, 9330,"  Number of distance tests for merging: %d\n",zzval_(Zbestdist)+
    +          zzval_(Zcentrumtests)+zzval_(Zdistconvex)+zzval_(Zdistcheck)+
    +          zzval_(Zdistzero));
    +    qh_fprintf(qh, fp, 9331,"  Number of distance tests for checking: %d\n",zzval_(Zcheckpart));
    +    qh_fprintf(qh, fp, 9332,"  Number of merged facets: %d\n", nummerged);
    +  }
    +  if (!qh->RANDOMoutside && qh->QHULLfinished) {
    +    cpu= (float)qh->hulltime;
    +    cpu /= (float)qh_SECticks;
    +    wval_(Wcpu)= cpu;
    +    qh_fprintf(qh, fp, 9333, "  CPU seconds to compute hull (after input): %2.4g\n", cpu);
    +  }
    +  if (qh->RERUN) {
    +    if (!qh->PREmerge && !qh->MERGEexact)
    +      qh_fprintf(qh, fp, 9334, "  Percentage of runs with precision errors: %4.1f\n",
    +           zzval_(Zretry)*100.0/qh->build_cnt);  /* careful of order */
    +  }else if (qh->JOGGLEmax < REALmax/2) {
    +    if (zzval_(Zretry))
    +      qh_fprintf(qh, fp, 9335, "  After %d retries, input joggled by: %2.2g\n",
    +         zzval_(Zretry), qh->JOGGLEmax);
    +    else
    +      qh_fprintf(qh, fp, 9336, "  Input joggled by: %2.2g\n", qh->JOGGLEmax);
    +  }
    +  if (qh->totarea != 0.0)
    +    qh_fprintf(qh, fp, 9337, "  %s facet area:   %2.8g\n",
    +            zzval_(Ztotmerge) ? "Approximate" : "Total", qh->totarea);
    +  if (qh->totvol != 0.0)
    +    qh_fprintf(qh, fp, 9338, "  %s volume:       %2.8g\n",
    +            zzval_(Ztotmerge) ? "Approximate" : "Total", qh->totvol);
    +  if (qh->MERGING) {
    +    qh_outerinner(qh, NULL, &outerplane, &innerplane);
    +    if (outerplane > 2 * qh->DISTround) {
    +      qh_fprintf(qh, fp, 9339, "  Maximum distance of %spoint above facet: %2.2g",
    +            (qh->QHULLfinished ? "" : "merged "), outerplane);
    +      ratio= outerplane/(qh->ONEmerge + qh->DISTround);
    +      /* don't report ratio if MINoutside is large */
    +      if (ratio > 0.05 && 2* qh->ONEmerge > qh->MINoutside && qh->JOGGLEmax > REALmax/2)
    +        qh_fprintf(qh, fp, 9340, " (%.1fx)\n", ratio);
    +      else
    +        qh_fprintf(qh, fp, 9341, "\n");
    +    }
    +    if (innerplane < -2 * qh->DISTround) {
    +      qh_fprintf(qh, fp, 9342, "  Maximum distance of %svertex below facet: %2.2g",
    +            (qh->QHULLfinished ? "" : "merged "), innerplane);
    +      ratio= -innerplane/(qh->ONEmerge+qh->DISTround);
    +      if (ratio > 0.05 && qh->JOGGLEmax > REALmax/2)
    +        qh_fprintf(qh, fp, 9343, " (%.1fx)\n", ratio);
    +      else
    +        qh_fprintf(qh, fp, 9344, "\n");
    +    }
    +  }
    +  qh_fprintf(qh, fp, 9345, "\n");
    +} /* printsummary */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/libqhull_r.h b/xs/src/qhull/src/libqhull_r/libqhull_r.h
    new file mode 100644
    index 0000000000..363e6da6a7
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/libqhull_r.h
    @@ -0,0 +1,1134 @@
    +/*
      ---------------------------------
    +
    +   libqhull_r.h
    +   user-level header file for using qhull.a library
    +
    +   see qh-qhull_r.htm, qhull_ra.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/libqhull_r.h#8 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +
    +   includes function prototypes for libqhull_r.c, geom_r.c, global_r.c, io_r.c, user.c
    +
    +   use mem_r.h for mem_r.c
    +   use qset_r.h for qset_r.c
    +
    +   see unix_r.c for an example of using libqhull_r.h
    +
    +   recompile qhull if you change this file
    +*/
    +
    +#ifndef qhDEFlibqhull
    +#define qhDEFlibqhull 1
    +
    +/*=========================== -included files ==============*/
    +
    +/* user_r.h first for QHULL_CRTDBG */
    +#include "user_r.h"      /* user definable constants (e.g., realT). */
    +
    +#include "mem_r.h"   /* Needed for qhT in libqhull_r.h */
    +#include "qset_r.h"   /* Needed for QHULL_LIB_CHECK */
    +/* include stat_r.h after defining boolT.  statT needed for qhT in libqhull_r.h */
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifndef __STDC__
    +#ifndef __cplusplus
    +#if     !_MSC_VER
    +#error  Neither __STDC__ nor __cplusplus is defined.  Please use strict ANSI C or C++ to compile
    +#error  Qhull.  You may need to turn off compiler extensions in your project configuration.  If
    +#error  your compiler is a standard C compiler, you can delete this warning from libqhull_r.h
    +#endif
    +#endif
    +#endif
    +
    +/*============ constants and basic types ====================*/
    +
    +extern const char qh_version[]; /* defined in global_r.c */
    +extern const char qh_version2[]; /* defined in global_r.c */
    +
    +/*----------------------------------
    +
    +  coordT
    +    coordinates and coefficients are stored as realT (i.e., double)
    +
    +  notes:
    +    Qhull works well if realT is 'float'.  If so joggle (QJ) is not effective.
    +
    +    Could use 'float' for data and 'double' for calculations (realT vs. coordT)
    +      This requires many type casts, and adjusted error bounds.
    +      Also C compilers may do expressions in double anyway.
    +*/
    +#define coordT realT
    +
    +/*----------------------------------
    +
    +  pointT
    +    a point is an array of coordinates, usually qh.hull_dim
    +    qh_pointid returns
    +      qh_IDnone if point==0 or qh is undefined
    +      qh_IDinterior for qh.interior_point
    +      qh_IDunknown if point is neither in qh.first_point... nor qh.other_points
    +
    +  notes:
    +    qh.STOPcone and qh.STOPpoint assume that qh_IDunknown==-1 (other negative numbers indicate points)
    +    qh_IDunknown is also returned by getid_() for unknown facet, ridge, or vertex
    +*/
    +#define pointT coordT
    +typedef enum
    +{
    +    qh_IDnone = -3, qh_IDinterior = -2, qh_IDunknown = -1
    +}
    +qh_pointT;
    +
    +/*----------------------------------
    +
    +  flagT
    +    Boolean flag as a bit
    +*/
    +#define flagT unsigned int
    +
    +/*----------------------------------
    +
    +  boolT
    +    boolean value, either True or False
    +
    +  notes:
    +    needed for portability
    +    Use qh_False/qh_True as synonyms
    +*/
    +#define boolT unsigned int
    +#ifdef False
    +#undef False
    +#endif
    +#ifdef True
    +#undef True
    +#endif
    +#define False 0
    +#define True 1
    +#define qh_False 0
    +#define qh_True 1
    +
    +#include "stat_r.h"  /* needs boolT */
    +
    +/*----------------------------------
    +
    +  qh_CENTER
    +    to distinguish facet->center
    +*/
    +typedef enum
    +{
    +    qh_ASnone = 0,   /* If not MERGING and not VORONOI */
    +    qh_ASvoronoi,    /* Set by qh_clearcenters on qh_prepare_output, or if not MERGING and VORONOI */
    +    qh_AScentrum     /* If MERGING (assumed during merging) */
    +}
    +qh_CENTER;
    +
    +/*----------------------------------
    +
    +  qh_PRINT
    +    output formats for printing (qh.PRINTout).
    +    'Fa' 'FV' 'Fc' 'FC'
    +
    +
    +   notes:
    +   some of these names are similar to qhT names.  The similar names are only
    +   used in switch statements in qh_printbegin() etc.
    +*/
    +typedef enum {qh_PRINTnone= 0,
    +  qh_PRINTarea, qh_PRINTaverage,           /* 'Fa' 'FV' 'Fc' 'FC' */
    +  qh_PRINTcoplanars, qh_PRINTcentrums,
    +  qh_PRINTfacets, qh_PRINTfacets_xridge,   /* 'f' 'FF' 'G' 'FI' 'Fi' 'Fn' */
    +  qh_PRINTgeom, qh_PRINTids, qh_PRINTinner, qh_PRINTneighbors,
    +  qh_PRINTnormals, qh_PRINTouter, qh_PRINTmaple, /* 'n' 'Fo' 'i' 'm' 'Fm' 'FM', 'o' */
    +  qh_PRINTincidences, qh_PRINTmathematica, qh_PRINTmerges, qh_PRINToff,
    +  qh_PRINToptions, qh_PRINTpointintersect, /* 'FO' 'Fp' 'FP' 'p' 'FQ' 'FS' */
    +  qh_PRINTpointnearest, qh_PRINTpoints, qh_PRINTqhull, qh_PRINTsize,
    +  qh_PRINTsummary, qh_PRINTtriangles,      /* 'Fs' 'Ft' 'Fv' 'FN' 'Fx' */
    +  qh_PRINTvertices, qh_PRINTvneighbors, qh_PRINTextremes,
    +  qh_PRINTEND} qh_PRINT;
    +
    +/*----------------------------------
    +
    +  qh_ALL
    +    argument flag for selecting everything
    +*/
    +#define qh_ALL      True
    +#define qh_NOupper  True     /* argument for qh_findbest */
    +#define qh_IScheckmax  True     /* argument for qh_findbesthorizon */
    +#define qh_ISnewfacets  True     /* argument for qh_findbest */
    +#define qh_RESETvisible  True     /* argument for qh_resetlists */
    +
    +/*----------------------------------
    +
    +  qh_ERR
    +    Qhull exit codes, for indicating errors
    +    See: MSG_ERROR and MSG_WARNING [user.h]
    +*/
    +#define qh_ERRnone  0    /* no error occurred during qhull */
    +#define qh_ERRinput 1    /* input inconsistency */
    +#define qh_ERRsingular 2 /* singular input data */
    +#define qh_ERRprec  3    /* precision error */
    +#define qh_ERRmem   4    /* insufficient memory, matches mem_r.h */
    +#define qh_ERRqhull 5    /* internal error detected, matches mem_r.h */
    +
    +/*----------------------------------
    +
    +qh_FILEstderr
    +Fake stderr to distinguish error output from normal output
    +For C++ interface.  Must redefine qh_fprintf_qhull
    +*/
    +#define qh_FILEstderr ((FILE*)1)
    +
    +/* ============ -structures- ====================
    +   each of the following structures is defined by a typedef
    +   all realT and coordT fields occur at the beginning of a structure
    +        (otherwise space may be wasted due to alignment)
    +   define all flags together and pack into 32-bit number
    +
    +   DEFqhT and DEFsetT are likewise defined in
    +   mem_r.h, qset_r.h, and stat_r.h.
    +
    +*/
    +
    +typedef struct vertexT vertexT;
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +
    +#ifndef DEFqhT
    +#define DEFqhT 1
    +typedef struct qhT qhT;          /* defined below */
    +#endif
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;          /* defined in qset_r.h */
    +#endif
    +
    +/*----------------------------------
    +
    +  facetT
    +    defines a facet
    +
    +  notes:
    +   qhull() generates the hull as a list of facets.
    +
    +  topological information:
    +    f.previous,next     doubly-linked list of facets
    +    f.vertices          set of vertices
    +    f.ridges            set of ridges
    +    f.neighbors         set of neighbors
    +    f.toporient         True if facet has top-orientation (else bottom)
    +
    +  geometric information:
    +    f.offset,normal     hyperplane equation
    +    f.maxoutside        offset to outer plane -- all points inside
    +    f.center            centrum for testing convexity or Voronoi center for output
    +    f.simplicial        True if facet is simplicial
    +    f.flipped           True if facet does not include qh.interior_point
    +
    +  for constructing hull:
    +    f.visible           True if facet on list of visible facets (will be deleted)
    +    f.newfacet          True if facet on list of newly created facets
    +    f.coplanarset       set of points coplanar with this facet
    +                        (includes near-inside points for later testing)
    +    f.outsideset        set of points outside of this facet
    +    f.furthestdist      distance to furthest point of outside set
    +    f.visitid           marks visited facets during a loop
    +    f.replace           replacement facet for to-be-deleted, visible facets
    +    f.samecycle,newcycle cycle of facets for merging into horizon facet
    +
    +  see below for other flags and fields
    +*/
    +struct facetT {
    +#if !qh_COMPUTEfurthest
    +  coordT   furthestdist;/* distance to furthest point of outsideset */
    +#endif
    +#if qh_MAXoutside
    +  coordT   maxoutside;  /* max computed distance of point to facet
    +                        Before QHULLfinished this is an approximation
    +                        since maxdist not always set for mergefacet
    +                        Actual outer plane is +DISTround and
    +                        computed outer plane is +2*DISTround */
    +#endif
    +  coordT   offset;      /* exact offset of hyperplane from origin */
    +  coordT  *normal;      /* normal of hyperplane, hull_dim coefficients */
    +                        /*   if ->tricoplanar, shared with a neighbor */
    +  union {               /* in order of testing */
    +   realT   area;        /* area of facet, only in io_r.c if  ->isarea */
    +   facetT *replace;     /*  replacement facet if ->visible and NEWfacets
    +                             is NULL only if qh_mergedegen_redundant or interior */
    +   facetT *samecycle;   /*  cycle of facets from the same visible/horizon intersection,
    +                             if ->newfacet */
    +   facetT *newcycle;    /*  in horizon facet, current samecycle of new facets */
    +   facetT *trivisible;  /* visible facet for ->tricoplanar facets during qh_triangulate() */
    +   facetT *triowner;    /* owner facet for ->tricoplanar, !isarea facets w/ ->keepcentrum */
    +  }f;
    +  coordT  *center;      /* set according to qh.CENTERtype */
    +                        /*   qh_ASnone:    no center (not MERGING) */
    +                        /*   qh_AScentrum: centrum for testing convexity (qh_getcentrum) */
    +                        /*                 assumed qh_AScentrum while merging */
    +                        /*   qh_ASvoronoi: Voronoi center (qh_facetcenter) */
    +                        /* after constructing the hull, it may be changed (qh_clearcenter) */
    +                        /* if tricoplanar and !keepcentrum, shared with a neighbor */
    +  facetT  *previous;    /* previous facet in the facet_list */
    +  facetT  *next;        /* next facet in the facet_list */
    +  setT    *vertices;    /* vertices for this facet, inverse sorted by ID
    +                           if simplicial, 1st vertex was apex/furthest */
    +  setT    *ridges;      /* explicit ridges for nonsimplicial facets.
    +                           for simplicial facets, neighbors define the ridges */
    +  setT    *neighbors;   /* neighbors of the facet.  If simplicial, the kth
    +                           neighbor is opposite the kth vertex, and the first
    +                           neighbor is the horizon facet for the first vertex*/
    +  setT    *outsideset;  /* set of points outside this facet
    +                           if non-empty, last point is furthest
    +                           if NARROWhull, includes coplanars for partitioning*/
    +  setT    *coplanarset; /* set of points coplanar with this facet
    +                           > qh.min_vertex and <= facet->max_outside
    +                           a point is assigned to the furthest facet
    +                           if non-empty, last point is furthest away */
    +  unsigned visitid;     /* visit_id, for visiting all neighbors,
    +                           all uses are independent */
    +  unsigned id;          /* unique identifier from qh.facet_id */
    +  unsigned nummerge:9;  /* number of merges */
    +#define qh_MAXnummerge 511 /*     2^9-1, 32 flags total, see "flags:" in io_r.c */
    +  flagT    tricoplanar:1; /* True if TRIangulate and simplicial and coplanar with a neighbor */
    +                          /*   all tricoplanars share the same apex */
    +                          /*   all tricoplanars share the same ->center, ->normal, ->offset, ->maxoutside */
    +                          /*     ->keepcentrum is true for the owner.  It has the ->coplanareset */
    +                          /*   if ->degenerate, does not span facet (one logical ridge) */
    +                          /*   during qh_triangulate, f.trivisible points to original facet */
    +  flagT    newfacet:1;  /* True if facet on qh.newfacet_list (new or merged) */
    +  flagT    visible:1;   /* True if visible facet (will be deleted) */
    +  flagT    toporient:1; /* True if created with top orientation
    +                           after merging, use ridge orientation */
    +  flagT    simplicial:1;/* True if simplicial facet, ->ridges may be implicit */
    +  flagT    seen:1;      /* used to perform operations only once, like visitid */
    +  flagT    seen2:1;     /* used to perform operations only once, like visitid */
    +  flagT    flipped:1;   /* True if facet is flipped */
    +  flagT    upperdelaunay:1; /* True if facet is upper envelope of Delaunay triangulation */
    +  flagT    notfurthest:1; /* True if last point of outsideset is not furthest*/
    +
    +/*-------- flags primarily for output ---------*/
    +  flagT    good:1;      /* True if a facet marked good for output */
    +  flagT    isarea:1;    /* True if facet->f.area is defined */
    +
    +/*-------- flags for merging ------------------*/
    +  flagT    dupridge:1;  /* True if duplicate ridge in facet */
    +  flagT    mergeridge:1; /* True if facet or neighbor contains a qh_MERGEridge
    +                            ->normal defined (also defined for mergeridge2) */
    +  flagT    mergeridge2:1; /* True if neighbor contains a qh_MERGEridge (qhT *qh, mark_dupridges */
    +  flagT    coplanar:1;  /* True if horizon facet is coplanar at last use */
    +  flagT     mergehorizon:1; /* True if will merge into horizon (->coplanar) */
    +  flagT     cycledone:1;/* True if mergecycle_all already done */
    +  flagT    tested:1;    /* True if facet convexity has been tested (false after merge */
    +  flagT    keepcentrum:1; /* True if keep old centrum after a merge, or marks owner for ->tricoplanar */
    +  flagT    newmerge:1;  /* True if facet is newly merged for reducevertices */
    +  flagT    degenerate:1; /* True if facet is degenerate (degen_mergeset or ->tricoplanar) */
    +  flagT    redundant:1;  /* True if facet is redundant (degen_mergeset) */
    +};
    +
    +
    +/*----------------------------------
    +
    +  ridgeT
    +    defines a ridge
    +
    +  notes:
    +  a ridge is hull_dim-1 simplex between two neighboring facets.  If the
    +  facets are non-simplicial, there may be more than one ridge between
    +  two facets.  E.G. a 4-d hypercube has two triangles between each pair
    +  of neighboring facets.
    +
    +  topological information:
    +    vertices            a set of vertices
    +    top,bottom          neighboring facets with orientation
    +
    +  geometric information:
    +    tested              True if ridge is clearly convex
    +    nonconvex           True if ridge is non-convex
    +*/
    +struct ridgeT {
    +  setT    *vertices;    /* vertices belonging to this ridge, inverse sorted by ID
    +                           NULL if a degen ridge (matchsame) */
    +  facetT  *top;         /* top facet this ridge is part of */
    +  facetT  *bottom;      /* bottom facet this ridge is part of */
    +  unsigned id;          /* unique identifier.  Same size as vertex_id and ridge_id */
    +  flagT    seen:1;      /* used to perform operations only once */
    +  flagT    tested:1;    /* True when ridge is tested for convexity */
    +  flagT    nonconvex:1; /* True if getmergeset detected a non-convex neighbor
    +                           only one ridge between neighbors may have nonconvex */
    +};
    +
    +/*----------------------------------
    +
    +  vertexT
    +     defines a vertex
    +
    +  topological information:
    +    next,previous       doubly-linked list of all vertices
    +    neighbors           set of adjacent facets (only if qh.VERTEXneighbors)
    +
    +  geometric information:
    +    point               array of DIM3 coordinates
    +*/
    +struct vertexT {
    +  vertexT *next;        /* next vertex in vertex_list */
    +  vertexT *previous;    /* previous vertex in vertex_list */
    +  pointT  *point;       /* hull_dim coordinates (coordT) */
    +  setT    *neighbors;   /* neighboring facets of vertex, qh_vertexneighbors()
    +                           inits in io_r.c or after first merge */
    +  unsigned id;          /* unique identifier.  Same size as qh.vertex_id and qh.ridge_id */
    +  unsigned visitid;    /* for use with qh.vertex_visit, size must match */
    +  flagT    seen:1;      /* used to perform operations only once */
    +  flagT    seen2:1;     /* another seen flag */
    +  flagT    delridge:1;  /* vertex was part of a deleted ridge */
    +  flagT    deleted:1;   /* true if vertex on qh.del_vertices */
    +  flagT    newlist:1;   /* true if vertex on qh.newvertex_list */
    +};
    +
    +/*======= -global variables -qh ============================*/
    +
    +/*----------------------------------
    +
    +  qhT
    +   All global variables for qhull are in qhT.  It includes qhmemT, qhstatT, and rbox globals
    +
    +   This version of Qhull is reentrant, but it is not thread-safe.
    +
    +   Do not run separate threads on the same instance of qhT.
    +
    +   QHULL_LIB_CHECK checks that a program and the corresponding
    +   qhull library were built with the same type of header files.
    +*/
    +
    +#define QHULL_NON_REENTRANT 0
    +#define QHULL_QH_POINTER 1
    +#define QHULL_REENTRANT 2
    +
    +#define QHULL_LIB_TYPE QHULL_REENTRANT
    +
    +#define QHULL_LIB_CHECK qh_lib_check(QHULL_LIB_TYPE, sizeof(qhT), sizeof(vertexT), sizeof(ridgeT), sizeof(facetT), sizeof(setT), sizeof(qhmemT));
    +#define QHULL_LIB_CHECK_RBOX qh_lib_check(QHULL_LIB_TYPE, sizeof(qhT), sizeof(vertexT), sizeof(ridgeT), sizeof(facetT), 0, 0);
    +
    +struct qhT {
    +
    +/*----------------------------------
    +
    +  qh constants
    +    configuration flags and constants for Qhull
    +
    +  notes:
    +    The user configures Qhull by defining flags.  They are
    +    copied into qh by qh_setflags().  qh-quick_r.htm#options defines the flags.
    +*/
    +  boolT ALLpoints;        /* true 'Qs' if search all points for initial simplex */
    +  boolT ANGLEmerge;       /* true 'Qa' if sort potential merges by angle */
    +  boolT APPROXhull;       /* true 'Wn' if MINoutside set */
    +  realT   MINoutside;     /*   'Wn' min. distance for an outside point */
    +  boolT ANNOTATEoutput;   /* true 'Ta' if annotate output with message codes */
    +  boolT ATinfinity;       /* true 'Qz' if point num_points-1 is "at-infinity"
    +                             for improving precision in Delaunay triangulations */
    +  boolT AVOIDold;         /* true 'Q4' if avoid old->new merges */
    +  boolT BESToutside;      /* true 'Qf' if partition points into best outsideset */
    +  boolT CDDinput;         /* true 'Pc' if input uses CDD format (1.0/offset first) */
    +  boolT CDDoutput;        /* true 'PC' if print normals in CDD format (offset first) */
    +  boolT CHECKfrequently;  /* true 'Tc' if checking frequently */
    +  realT premerge_cos;     /*   'A-n'   cos_max when pre merging */
    +  realT postmerge_cos;    /*   'An'    cos_max when post merging */
    +  boolT DELAUNAY;         /* true 'd' if computing DELAUNAY triangulation */
    +  boolT DOintersections;  /* true 'Gh' if print hyperplane intersections */
    +  int   DROPdim;          /* drops dim 'GDn' for 4-d -> 3-d output */
    +  boolT FORCEoutput;      /* true 'Po' if forcing output despite degeneracies */
    +  int   GOODpoint;        /* 1+n for 'QGn', good facet if visible/not(-) from point n*/
    +  pointT *GOODpointp;     /*   the actual point */
    +  boolT GOODthreshold;    /* true if qh.lower_threshold/upper_threshold defined
    +                             false if qh.SPLITthreshold */
    +  int   GOODvertex;       /* 1+n, good facet if vertex for point n */
    +  pointT *GOODvertexp;     /*   the actual point */
    +  boolT HALFspace;        /* true 'Hn,n,n' if halfspace intersection */
    +  boolT ISqhullQh;        /* Set by Qhull.cpp on initialization */
    +  int   IStracing;        /* trace execution, 0=none, 1=least, 4=most, -1=events */
    +  int   KEEParea;         /* 'PAn' number of largest facets to keep */
    +  boolT KEEPcoplanar;     /* true 'Qc' if keeping nearest facet for coplanar points */
    +  boolT KEEPinside;       /* true 'Qi' if keeping nearest facet for inside points
    +                              set automatically if 'd Qc' */
    +  int   KEEPmerge;        /* 'PMn' number of facets to keep with most merges */
    +  realT KEEPminArea;      /* 'PFn' minimum facet area to keep */
    +  realT MAXcoplanar;      /* 'Un' max distance below a facet to be coplanar*/
    +  boolT MERGEexact;       /* true 'Qx' if exact merges (coplanar, degen, dupridge, flipped) */
    +  boolT MERGEindependent; /* true 'Q2' if merging independent sets */
    +  boolT MERGING;          /* true if exact-, pre- or post-merging, with angle and centrum tests */
    +  realT   premerge_centrum;  /*   'C-n' centrum_radius when pre merging.  Default is round-off */
    +  realT   postmerge_centrum; /*   'Cn' centrum_radius when post merging.  Default is round-off */
    +  boolT MERGEvertices;    /* true 'Q3' if merging redundant vertices */
    +  realT MINvisible;       /* 'Vn' min. distance for a facet to be visible */
    +  boolT NOnarrow;         /* true 'Q10' if no special processing for narrow distributions */
    +  boolT NOnearinside;     /* true 'Q8' if ignore near-inside points when partitioning */
    +  boolT NOpremerge;       /* true 'Q0' if no defaults for C-0 or Qx */
    +  boolT NOwide;           /* true 'Q12' if no error on wide merge due to duplicate ridge */
    +  boolT ONLYgood;         /* true 'Qg' if process points with good visible or horizon facets */
    +  boolT ONLYmax;          /* true 'Qm' if only process points that increase max_outside */
    +  boolT PICKfurthest;     /* true 'Q9' if process furthest of furthest points*/
    +  boolT POSTmerge;        /* true if merging after buildhull (Cn or An) */
    +  boolT PREmerge;         /* true if merging during buildhull (C-n or A-n) */
    +                        /* NOTE: some of these names are similar to qh_PRINT names */
    +  boolT PRINTcentrums;    /* true 'Gc' if printing centrums */
    +  boolT PRINTcoplanar;    /* true 'Gp' if printing coplanar points */
    +  int   PRINTdim;         /* print dimension for Geomview output */
    +  boolT PRINTdots;        /* true 'Ga' if printing all points as dots */
    +  boolT PRINTgood;        /* true 'Pg' if printing good facets */
    +  boolT PRINTinner;       /* true 'Gi' if printing inner planes */
    +  boolT PRINTneighbors;   /* true 'PG' if printing neighbors of good facets */
    +  boolT PRINTnoplanes;    /* true 'Gn' if printing no planes */
    +  boolT PRINToptions1st;  /* true 'FO' if printing options to stderr */
    +  boolT PRINTouter;       /* true 'Go' if printing outer planes */
    +  boolT PRINTprecision;   /* false 'Pp' if not reporting precision problems */
    +  qh_PRINT PRINTout[qh_PRINTEND]; /* list of output formats to print */
    +  boolT PRINTridges;      /* true 'Gr' if print ridges */
    +  boolT PRINTspheres;     /* true 'Gv' if print vertices as spheres */
    +  boolT PRINTstatistics;  /* true 'Ts' if printing statistics to stderr */
    +  boolT PRINTsummary;     /* true 's' if printing summary to stderr */
    +  boolT PRINTtransparent; /* true 'Gt' if print transparent outer ridges */
    +  boolT PROJECTdelaunay;  /* true if DELAUNAY, no readpoints() and
    +                             need projectinput() for Delaunay in qh_init_B */
    +  int   PROJECTinput;     /* number of projected dimensions 'bn:0Bn:0' */
    +  boolT QUICKhelp;        /* true if quick help message for degen input */
    +  boolT RANDOMdist;       /* true if randomly change distplane and setfacetplane */
    +  realT RANDOMfactor;     /*    maximum random perturbation */
    +  realT RANDOMa;          /*    qh_randomfactor is randr * RANDOMa + RANDOMb */
    +  realT RANDOMb;
    +  boolT RANDOMoutside;    /* true if select a random outside point */
    +  int   REPORTfreq;       /* buildtracing reports every n facets */
    +  int   REPORTfreq2;      /* tracemerging reports every REPORTfreq/2 facets */
    +  int   RERUN;            /* 'TRn' rerun qhull n times (qh.build_cnt) */
    +  int   ROTATErandom;     /* 'QRn' seed, 0 time, >= rotate input */
    +  boolT SCALEinput;       /* true 'Qbk' if scaling input */
    +  boolT SCALElast;        /* true 'Qbb' if scale last coord to max prev coord */
    +  boolT SETroundoff;      /* true 'E' if qh.DISTround is predefined */
    +  boolT SKIPcheckmax;     /* true 'Q5' if skip qh_check_maxout */
    +  boolT SKIPconvex;       /* true 'Q6' if skip convexity testing during pre-merge */
    +  boolT SPLITthresholds;  /* true if upper_/lower_threshold defines a region
    +                               used only for printing (!for qh.ONLYgood) */
    +  int   STOPcone;         /* 'TCn' 1+n for stopping after cone for point n */
    +                          /*       also used by qh_build_withresart for err exit*/
    +  int   STOPpoint;        /* 'TVn' 'TV-n' 1+n for stopping after/before(-)
    +                                        adding point n */
    +  int   TESTpoints;       /* 'QTn' num of test points after qh.num_points.  Test points always coplanar. */
    +  boolT TESTvneighbors;   /*  true 'Qv' if test vertex neighbors at end */
    +  int   TRACElevel;       /* 'Tn' conditional IStracing level */
    +  int   TRACElastrun;     /*  qh.TRACElevel applies to last qh.RERUN */
    +  int   TRACEpoint;       /* 'TPn' start tracing when point n is a vertex */
    +  realT TRACEdist;        /* 'TWn' start tracing when merge distance too big */
    +  int   TRACEmerge;       /* 'TMn' start tracing before this merge */
    +  boolT TRIangulate;      /* true 'Qt' if triangulate non-simplicial facets */
    +  boolT TRInormals;       /* true 'Q11' if triangulate duplicates ->normal and ->center (sets Qt) */
    +  boolT UPPERdelaunay;    /* true 'Qu' if computing furthest-site Delaunay */
    +  boolT USEstdout;        /* true 'Tz' if using stdout instead of stderr */
    +  boolT VERIFYoutput;     /* true 'Tv' if verify output at end of qhull */
    +  boolT VIRTUALmemory;    /* true 'Q7' if depth-first processing in buildhull */
    +  boolT VORONOI;          /* true 'v' if computing Voronoi diagram */
    +
    +  /*--------input constants ---------*/
    +  realT AREAfactor;       /* 1/(hull_dim-1)! for converting det's to area */
    +  boolT DOcheckmax;       /* true if calling qh_check_maxout (qhT *qh, qh_initqhull_globals) */
    +  char  *feasible_string;  /* feasible point 'Hn,n,n' for halfspace intersection */
    +  coordT *feasible_point;  /*    as coordinates, both malloc'd */
    +  boolT GETarea;          /* true 'Fa', 'FA', 'FS', 'PAn', 'PFn' if compute facet area/Voronoi volume in io_r.c */
    +  boolT KEEPnearinside;   /* true if near-inside points in coplanarset */
    +  int   hull_dim;         /* dimension of hull, set by initbuffers */
    +  int   input_dim;        /* dimension of input, set by initbuffers */
    +  int   num_points;       /* number of input points */
    +  pointT *first_point;    /* array of input points, see POINTSmalloc */
    +  boolT POINTSmalloc;     /*   true if qh.first_point/num_points allocated */
    +  pointT *input_points;   /* copy of original qh.first_point for input points for qh_joggleinput */
    +  boolT input_malloc;     /* true if qh.input_points malloc'd */
    +  char  qhull_command[256];/* command line that invoked this program */
    +  int   qhull_commandsiz2; /*    size of qhull_command at qh_clear_outputflags */
    +  char  rbox_command[256]; /* command line that produced the input points */
    +  char  qhull_options[512];/* descriptive list of options */
    +  int   qhull_optionlen;  /*    length of last line */
    +  int   qhull_optionsiz;  /*    size of qhull_options at qh_build_withrestart */
    +  int   qhull_optionsiz2; /*    size of qhull_options at qh_clear_outputflags */
    +  int   run_id;           /* non-zero, random identifier for this instance of qhull */
    +  boolT VERTEXneighbors;  /* true if maintaining vertex neighbors */
    +  boolT ZEROcentrum;      /* true if 'C-0' or 'C-0 Qx'.  sets ZEROall_ok */
    +  realT *upper_threshold; /* don't print if facet->normal[k]>=upper_threshold[k]
    +                             must set either GOODthreshold or SPLITthreshold
    +                             if Delaunay, default is 0.0 for upper envelope */
    +  realT *lower_threshold; /* don't print if facet->normal[k] <=lower_threshold[k] */
    +  realT *upper_bound;     /* scale point[k] to new upper bound */
    +  realT *lower_bound;     /* scale point[k] to new lower bound
    +                             project if both upper_ and lower_bound == 0 */
    +
    +/*----------------------------------
    +
    +  qh precision constants
    +    precision constants for Qhull
    +
    +  notes:
    +    qh_detroundoff(qh) computes the maximum roundoff error for distance
    +    and other computations.  It also sets default values for the
    +    qh constants above.
    +*/
    +  realT ANGLEround;       /* max round off error for angles */
    +  realT centrum_radius;   /* max centrum radius for convexity (roundoff added) */
    +  realT cos_max;          /* max cosine for convexity (roundoff added) */
    +  realT DISTround;        /* max round off error for distances, 'E' overrides qh_distround() */
    +  realT MAXabs_coord;     /* max absolute coordinate */
    +  realT MAXlastcoord;     /* max last coordinate for qh_scalelast */
    +  realT MAXsumcoord;      /* max sum of coordinates */
    +  realT MAXwidth;         /* max rectilinear width of point coordinates */
    +  realT MINdenom_1;       /* min. abs. value for 1/x */
    +  realT MINdenom;         /*    use divzero if denominator < MINdenom */
    +  realT MINdenom_1_2;     /* min. abs. val for 1/x that allows normalization */
    +  realT MINdenom_2;       /*    use divzero if denominator < MINdenom_2 */
    +  realT MINlastcoord;     /* min. last coordinate for qh_scalelast */
    +  boolT NARROWhull;       /* set in qh_initialhull if angle < qh_MAXnarrow */
    +  realT *NEARzero;        /* hull_dim array for near zero in gausselim */
    +  realT NEARinside;       /* keep points for qh_check_maxout if close to facet */
    +  realT ONEmerge;         /* max distance for merging simplicial facets */
    +  realT outside_err;      /* application's epsilon for coplanar points
    +                             qh_check_bestdist() qh_check_points() reports error if point outside */
    +  realT WIDEfacet;        /* size of wide facet for skipping ridge in
    +                             area computation and locking centrum */
    +
    +/*----------------------------------
    +
    +  qh internal constants
    +    internal constants for Qhull
    +*/
    +  char qhull[sizeof("qhull")]; /* "qhull" for checking ownership while debugging */
    +  jmp_buf errexit;        /* exit label for qh_errexit, defined by setjmp() and NOerrexit */
    +  char jmpXtra[40];       /* extra bytes in case jmp_buf is defined wrong by compiler */
    +  jmp_buf restartexit;    /* restart label for qh_errexit, defined by setjmp() and ALLOWrestart */
    +  char jmpXtra2[40];      /* extra bytes in case jmp_buf is defined wrong by compiler*/
    +  FILE *fin;              /* pointer to input file, init by qh_initqhull_start */
    +  FILE *fout;             /* pointer to output file */
    +  FILE *ferr;             /* pointer to error file */
    +  pointT *interior_point; /* center point of the initial simplex*/
    +  int normal_size;     /* size in bytes for facet normals and point coords*/
    +  int center_size;     /* size in bytes for Voronoi centers */
    +  int   TEMPsize;         /* size for small, temporary sets (in quick mem) */
    +
    +/*----------------------------------
    +
    +  qh facet and vertex lists
    +    defines lists of facets, new facets, visible facets, vertices, and
    +    new vertices.  Includes counts, next ids, and trace ids.
    +  see:
    +    qh_resetlists()
    +*/
    +  facetT *facet_list;     /* first facet */
    +  facetT  *facet_tail;     /* end of facet_list (dummy facet) */
    +  facetT *facet_next;     /* next facet for buildhull()
    +                             previous facets do not have outside sets
    +                             NARROWhull: previous facets may have coplanar outside sets for qh_outcoplanar */
    +  facetT *newfacet_list;  /* list of new facets to end of facet_list */
    +  facetT *visible_list;   /* list of visible facets preceding newfacet_list,
    +                             facet->visible set */
    +  int       num_visible;  /* current number of visible facets */
    +  unsigned tracefacet_id;  /* set at init, then can print whenever */
    +  facetT *tracefacet;     /*   set in newfacet/mergefacet, undone in delfacet*/
    +  unsigned tracevertex_id;  /* set at buildtracing, can print whenever */
    +  vertexT *tracevertex;     /*   set in newvertex, undone in delvertex*/
    +  vertexT *vertex_list;     /* list of all vertices, to vertex_tail */
    +  vertexT  *vertex_tail;    /*      end of vertex_list (dummy vertex) */
    +  vertexT *newvertex_list; /* list of vertices in newfacet_list, to vertex_tail
    +                             all vertices have 'newlist' set */
    +  int   num_facets;       /* number of facets in facet_list
    +                             includes visible faces (num_visible) */
    +  int   num_vertices;     /* number of vertices in facet_list */
    +  int   num_outside;      /* number of points in outsidesets (for tracing and RANDOMoutside)
    +                               includes coplanar outsideset points for NARROWhull/qh_outcoplanar() */
    +  int   num_good;         /* number of good facets (after findgood_all) */
    +  unsigned facet_id;      /* ID of next, new facet from newfacet() */
    +  unsigned ridge_id;      /* ID of next, new ridge from newridge() */
    +  unsigned vertex_id;     /* ID of next, new vertex from newvertex() */
    +
    +/*----------------------------------
    +
    +  qh global variables
    +    defines minimum and maximum distances, next visit ids, several flags,
    +    and other global variables.
    +    initialize in qh_initbuild or qh_maxmin if used in qh_buildhull
    +*/
    +  unsigned long hulltime; /* ignore time to set up input and randomize */
    +                          /*   use unsigned to avoid wrap-around errors */
    +  boolT ALLOWrestart;     /* true if qh_precision can use qh.restartexit */
    +  int   build_cnt;        /* number of calls to qh_initbuild */
    +  qh_CENTER CENTERtype;   /* current type of facet->center, qh_CENTER */
    +  int   furthest_id;      /* pointid of furthest point, for tracing */
    +  facetT *GOODclosest;    /* closest facet to GOODthreshold in qh_findgood */
    +  boolT hasAreaVolume;    /* true if totarea, totvol was defined by qh_getarea */
    +  boolT hasTriangulation; /* true if triangulation created by qh_triangulate */
    +  realT JOGGLEmax;        /* set 'QJn' if randomly joggle input */
    +  boolT maxoutdone;       /* set qh_check_maxout(), cleared by qh_addpoint() */
    +  realT max_outside;      /* maximum distance from a point to a facet,
    +                               before roundoff, not simplicial vertices
    +                               actual outer plane is +DISTround and
    +                               computed outer plane is +2*DISTround */
    +  realT max_vertex;       /* maximum distance (>0) from vertex to a facet,
    +                               before roundoff, due to a merge */
    +  realT min_vertex;       /* minimum distance (<0) from vertex to a facet,
    +                               before roundoff, due to a merge
    +                               if qh.JOGGLEmax, qh_makenewplanes sets it
    +                               recomputed if qh.DOcheckmax, default -qh.DISTround */
    +  boolT NEWfacets;        /* true while visible facets invalid due to new or merge
    +                              from makecone/attachnewfacets to deletevisible */
    +  boolT findbestnew;      /* true if partitioning calls qh_findbestnew */
    +  boolT findbest_notsharp; /* true if new facets are at least 90 degrees */
    +  boolT NOerrexit;        /* true if qh.errexit is not available, cleared after setjmp */
    +  realT PRINTcradius;     /* radius for printing centrums */
    +  realT PRINTradius;      /* radius for printing vertex spheres and points */
    +  boolT POSTmerging;      /* true when post merging */
    +  int   printoutvar;      /* temporary variable for qh_printbegin, etc. */
    +  int   printoutnum;      /* number of facets printed */
    +  boolT QHULLfinished;    /* True after qhull() is finished */
    +  realT totarea;          /* 'FA': total facet area computed by qh_getarea, hasAreaVolume */
    +  realT totvol;           /* 'FA': total volume computed by qh_getarea, hasAreaVolume */
    +  unsigned int visit_id;  /* unique ID for searching neighborhoods, */
    +  unsigned int vertex_visit; /* unique ID for searching vertices, reset with qh_buildtracing */
    +  boolT ZEROall_ok;       /* True if qh_checkzero always succeeds */
    +  boolT WAScoplanar;      /* True if qh_partitioncoplanar (qhT *qh, qh_check_maxout) */
    +
    +/*----------------------------------
    +
    +  qh global sets
    +    defines sets for merging, initial simplex, hashing, extra input points,
    +    and deleted vertices
    +*/
    +  setT *facet_mergeset;   /* temporary set of merges to be done */
    +  setT *degen_mergeset;   /* temporary set of degenerate and redundant merges */
    +  setT *hash_table;       /* hash table for matching ridges in qh_matchfacets
    +                             size is setsize() */
    +  setT *other_points;     /* additional points */
    +  setT *del_vertices;     /* vertices to partition and delete with visible
    +                             facets.  Have deleted set for checkfacet */
    +
    +/*----------------------------------
    +
    +  qh global buffers
    +    defines buffers for maxtrix operations, input, and error messages
    +*/
    +  coordT *gm_matrix;      /* (dim+1)Xdim matrix for geom_r.c */
    +  coordT **gm_row;        /* array of gm_matrix rows */
    +  char* line;             /* malloc'd input line of maxline+1 chars */
    +  int maxline;
    +  coordT *half_space;     /* malloc'd input array for halfspace (qh.normal_size+coordT) */
    +  coordT *temp_malloc;    /* malloc'd input array for points */
    +
    +/*----------------------------------
    +
    +  qh static variables
    +    defines static variables for individual functions
    +
    +  notes:
    +    do not use 'static' within a function.  Multiple instances of qhull
    +    may exist.
    +
    +    do not assume zero initialization, 'QPn' may cause a restart
    +*/
    +  boolT ERREXITcalled;    /* true during qh_errexit (qhT *qh, prevents duplicate calls */
    +  boolT firstcentrum;     /* for qh_printcentrum */
    +  boolT old_randomdist;   /* save RANDOMdist flag during io, tracing, or statistics */
    +  setT *coplanarfacetset;  /* set of coplanar facets for searching qh_findbesthorizon() */
    +  realT last_low;         /* qh_scalelast parameters for qh_setdelaunay */
    +  realT last_high;
    +  realT last_newhigh;
    +  unsigned lastreport;    /* for qh_buildtracing */
    +  int mergereport;        /* for qh_tracemerging */
    +  setT *old_tempstack;    /* for saving qh->qhmem.tempstack in save_qhull */
    +  int   ridgeoutnum;      /* number of ridges for 4OFF output (qh_printbegin,etc) */
    +
    +/*----------------------------------
    +
    +  qh memory management, rbox globals, and statistics
    +
    +  Replaces global data structures defined for libqhull
    +*/
    +  int     last_random;    /* Last random number from qh_rand (random_r.c) */
    +  jmp_buf rbox_errexit;   /* errexit from rboxlib_r.c, defined by qh_rboxpoints() only */
    +  char    jmpXtra3[40];   /* extra bytes in case jmp_buf is defined wrong by compiler */
    +  int     rbox_isinteger;
    +  double  rbox_out_offset;
    +  void *  cpp_object;     /* C++ pointer.  Currently used by RboxPoints.qh_fprintf_rbox */
    +
    +  /* Last, otherwise zero'd by qh_initqhull_start2 (global_r.c */
    +  qhmemT  qhmem;          /* Qhull managed memory (mem_r.h) */
    +  /* After qhmem because its size depends on the number of statistics */
    +  qhstatT qhstat;         /* Qhull statistics (stat_r.h) */
    +};
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  otherfacet_(ridge, facet)
    +    return neighboring facet for a ridge in facet
    +*/
    +#define otherfacet_(ridge, facet) \
    +                        (((ridge)->top == (facet)) ? (ridge)->bottom : (ridge)->top)
    +
    +/*----------------------------------
    +
    +  getid_(p)
    +    return int ID for facet, ridge, or vertex
    +    return qh_IDunknown(-1) if NULL
    +*/
    +#define getid_(p)       ((p) ? (int)((p)->id) : qh_IDunknown)
    +
    +/*============== FORALL macros ===================*/
    +
    +/*----------------------------------
    +
    +  FORALLfacets { ... }
    +    assign 'facet' to each facet in qh.facet_list
    +
    +  notes:
    +    uses 'facetT *facet;'
    +    assumes last facet is a sentinel
    +    assumes qh defined
    +
    +  see:
    +    FORALLfacet_( facetlist )
    +*/
    +#define FORALLfacets for (facet=qh->facet_list;facet && facet->next;facet=facet->next)
    +
    +/*----------------------------------
    +
    +  FORALLpoints { ... }
    +    assign 'point' to each point in qh.first_point, qh.num_points
    +
    +  notes:
    +    assumes qh defined
    +
    +  declare:
    +    coordT *point, *pointtemp;
    +*/
    +#define FORALLpoints FORALLpoint_(qh, qh->first_point, qh->num_points)
    +
    +/*----------------------------------
    +
    +  FORALLpoint_( qh, points, num) { ... }
    +    assign 'point' to each point in points array of num points
    +
    +  declare:
    +    coordT *point, *pointtemp;
    +*/
    +#define FORALLpoint_(qh, points, num) for (point= (points), \
    +      pointtemp= (points)+qh->hull_dim*(num); point < pointtemp; point += qh->hull_dim)
    +
    +/*----------------------------------
    +
    +  FORALLvertices { ... }
    +    assign 'vertex' to each vertex in qh.vertex_list
    +
    +  declare:
    +    vertexT *vertex;
    +
    +  notes:
    +    assumes qh.vertex_list terminated with a sentinel
    +    assumes qh defined
    +*/
    +#define FORALLvertices for (vertex=qh->vertex_list;vertex && vertex->next;vertex= vertex->next)
    +
    +/*----------------------------------
    +
    +  FOREACHfacet_( facets ) { ... }
    +    assign 'facet' to each facet in facets
    +
    +  declare:
    +    facetT *facet, **facetp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHfacet_(facets)    FOREACHsetelement_(facetT, facets, facet)
    +
    +/*----------------------------------
    +
    +  FOREACHneighbor_( facet ) { ... }
    +    assign 'neighbor' to each neighbor in facet->neighbors
    +
    +  FOREACHneighbor_( vertex ) { ... }
    +    assign 'neighbor' to each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighbor, **neighborp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHneighbor_(facet)  FOREACHsetelement_(facetT, facet->neighbors, neighbor)
    +
    +/*----------------------------------
    +
    +  FOREACHpoint_( points ) { ... }
    +    assign 'point' to each point in points set
    +
    +  declare:
    +    pointT *point, **pointp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHpoint_(points)    FOREACHsetelement_(pointT, points, point)
    +
    +/*----------------------------------
    +
    +  FOREACHridge_( ridges ) { ... }
    +    assign 'ridge' to each ridge in ridges set
    +
    +  declare:
    +    ridgeT *ridge, **ridgep;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHridge_(ridges)    FOREACHsetelement_(ridgeT, ridges, ridge)
    +
    +/*----------------------------------
    +
    +  FOREACHvertex_( vertices ) { ... }
    +    assign 'vertex' to each vertex in vertices set
    +
    +  declare:
    +    vertexT *vertex, **vertexp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHvertex_(vertices) FOREACHsetelement_(vertexT, vertices,vertex)
    +
    +/*----------------------------------
    +
    +  FOREACHfacet_i_( qh, facets ) { ... }
    +    assign 'facet' and 'facet_i' for each facet in facets set
    +
    +  declare:
    +    facetT *facet;
    +    int     facet_n, facet_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHfacet_i_(qh, facets)    FOREACHsetelement_i_(qh, facetT, facets, facet)
    +
    +/*----------------------------------
    +
    +  FOREACHneighbor_i_( qh, facet ) { ... }
    +    assign 'neighbor' and 'neighbor_i' for each neighbor in facet->neighbors
    +
    +  FOREACHneighbor_i_( qh, vertex ) { ... }
    +    assign 'neighbor' and 'neighbor_i' for each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighbor;
    +    int     neighbor_n, neighbor_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHneighbor_i_(qh, facet)  FOREACHsetelement_i_(qh, facetT, facet->neighbors, neighbor)
    +
    +/*----------------------------------
    +
    +  FOREACHpoint_i_( qh, points ) { ... }
    +    assign 'point' and 'point_i' for each point in points set
    +
    +  declare:
    +    pointT *point;
    +    int     point_n, point_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHpoint_i_(qh, points)    FOREACHsetelement_i_(qh, pointT, points, point)
    +
    +/*----------------------------------
    +
    +  FOREACHridge_i_( qh, ridges ) { ... }
    +    assign 'ridge' and 'ridge_i' for each ridge in ridges set
    +
    +  declare:
    +    ridgeT *ridge;
    +    int     ridge_n, ridge_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHridge_i_(qh, ridges)    FOREACHsetelement_i_(qh, ridgeT, ridges, ridge)
    +
    +/*----------------------------------
    +
    +  FOREACHvertex_i_( qh, vertices ) { ... }
    +    assign 'vertex' and 'vertex_i' for each vertex in vertices set
    +
    +  declare:
    +    vertexT *vertex;
    +    int     vertex_n, vertex_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHvertex_i_(qh, vertices) FOREACHsetelement_i_(qh, vertexT, vertices,vertex)
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +/********* -libqhull_r.c prototypes (duplicated from qhull_ra.h) **********************/
    +
    +void    qh_qhull(qhT *qh);
    +boolT   qh_addpoint(qhT *qh, pointT *furthest, facetT *facet, boolT checkdist);
    +void    qh_printsummary(qhT *qh, FILE *fp);
    +
    +/********* -user.c prototypes (alphabetical) **********************/
    +
    +void    qh_errexit(qhT *qh, int exitcode, facetT *facet, ridgeT *ridge);
    +void    qh_errprint(qhT *qh, const char* string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex);
    +int     qh_new_qhull(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc,
    +                char *qhull_cmd, FILE *outfile, FILE *errfile);
    +void    qh_printfacetlist(qhT *qh, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printhelp_degenerate(qhT *qh, FILE *fp);
    +void    qh_printhelp_narrowhull(qhT *qh, FILE *fp, realT minangle);
    +void    qh_printhelp_singular(qhT *qh, FILE *fp);
    +void    qh_user_memsizes(qhT *qh);
    +
    +/********* -usermem_r.c prototypes (alphabetical) **********************/
    +void    qh_exit(int exitcode);
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +
    +/********* -userprintf_r.c and userprintf_rbox_r.c prototypes **********************/
    +void    qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +
    +/***** -geom_r.c/geom2_r.c/random_r.c prototypes (duplicated from geom_r.h, random_r.h) ****************/
    +
    +facetT *qh_findbest(qhT *qh, pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT newfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart);
    +facetT *qh_findbestnew(qhT *qh, pointT *point, facetT *startfacet,
    +                     realT *dist, boolT bestoutside, boolT *isoutside, int *numpart);
    +boolT   qh_gram_schmidt(qhT *qh, int dim, realT **rows);
    +void    qh_outerinner(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane);
    +void    qh_printsummary(qhT *qh, FILE *fp);
    +void    qh_projectinput(qhT *qh);
    +void    qh_randommatrix(qhT *qh, realT *buffer, int dim, realT **row);
    +void    qh_rotateinput(qhT *qh, realT **rows);
    +void    qh_scaleinput(qhT *qh);
    +void    qh_setdelaunay(qhT *qh, int dim, int count, pointT *points);
    +coordT  *qh_sethalfspace_all(qhT *qh, int dim, int count, coordT *halfspaces, pointT *feasible);
    +
    +/***** -global_r.c prototypes (alphabetical) ***********************/
    +
    +unsigned long qh_clock(qhT *qh);
    +void    qh_checkflags(qhT *qh, char *command, char *hiddenflags);
    +void    qh_clear_outputflags(qhT *qh);
    +void    qh_freebuffers(qhT *qh);
    +void    qh_freeqhull(qhT *qh, boolT allmem);
    +void    qh_init_A(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile, int argc, char *argv[]);
    +void    qh_init_B(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_init_qhull_command(qhT *qh, int argc, char *argv[]);
    +void    qh_initbuffers(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_initflags(qhT *qh, char *command);
    +void    qh_initqhull_buffers(qhT *qh);
    +void    qh_initqhull_globals(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_initqhull_mem(qhT *qh);
    +void    qh_initqhull_outputflags(qhT *qh);
    +void    qh_initqhull_start(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile);
    +void    qh_initqhull_start2(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile);
    +void    qh_initthresholds(qhT *qh, char *command);
    +void    qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeTsize, int facetTsize, int setTsize, int qhmemTsize);
    +void    qh_option(qhT *qh, const char *option, int *i, realT *r);
    +void    qh_zero(qhT *qh, FILE *errfile);
    +
    +/***** -io_r.c prototypes (duplicated from io_r.h) ***********************/
    +
    +void    qh_dfacet(qhT *qh, unsigned id);
    +void    qh_dvertex(qhT *qh, unsigned id);
    +void    qh_printneighborhood(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall);
    +void    qh_produce_output(qhT *qh);
    +coordT *qh_readpoints(qhT *qh, int *numpoints, int *dimension, boolT *ismalloc);
    +
    +
    +/********* -mem_r.c prototypes (duplicated from mem_r.h) **********************/
    +
    +void qh_meminit(qhT *qh, FILE *ferr);
    +void qh_memfreeshort(qhT *qh, int *curlong, int *totlong);
    +
    +/********* -poly_r.c/poly2_r.c prototypes (duplicated from poly_r.h) **********************/
    +
    +void    qh_check_output(qhT *qh);
    +void    qh_check_points(qhT *qh);
    +setT   *qh_facetvertices(qhT *qh, facetT *facetlist, setT *facets, boolT allfacets);
    +facetT *qh_findbestfacet(qhT *qh, pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside);
    +vertexT *qh_nearvertex(qhT *qh, facetT *facet, pointT *point, realT *bestdistp);
    +pointT *qh_point(qhT *qh, int id);
    +setT   *qh_pointfacet(qhT *qh /*qh.facet_list*/);
    +int     qh_pointid(qhT *qh, pointT *point);
    +setT   *qh_pointvertex(qhT *qh /*qh.facet_list*/);
    +void    qh_setvoronoi_all(qhT *qh);
    +void    qh_triangulate(qhT *qh /*qh.facet_list*/);
    +
    +/********* -rboxpoints_r.c prototypes **********************/
    +int     qh_rboxpoints(qhT *qh, char* rbox_command);
    +void    qh_errexit_rbox(qhT *qh, int exitcode);
    +
    +/********* -stat_r.c prototypes (duplicated from stat_r.h) **********************/
    +
    +void    qh_collectstatistics(qhT *qh);
    +void    qh_printallstatistics(qhT *qh, FILE *fp, const char *string);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFlibqhull */
    diff --git a/xs/src/qhull/src/libqhull_r/libqhull_r.pro b/xs/src/qhull/src/libqhull_r/libqhull_r.pro
    new file mode 100644
    index 0000000000..6b8db44b75
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/libqhull_r.pro
    @@ -0,0 +1,67 @@
    +# -------------------------------------------------
    +# libqhull_r.pro -- Qt project for Qhull shared library
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +DESTDIR = ../../lib
    +DLLDESTDIR = ../../bin
    +TEMPLATE = lib
    +CONFIG += shared warn_on
    +CONFIG -= qt
    +
    +build_pass:CONFIG(debug, debug|release):{
    +    TARGET = qhull_rd
    +    OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +    TARGET = qhull_r
    +    OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +win32-msvc* : DEF_FILE += ../../src/libqhull_r/qhull_r-exports.def
    +
    +# libqhull_r/libqhull_r.pro and ../qhull-libqhull-src_r.pri have the same SOURCES and HEADERS
    +
    +SOURCES += ../libqhull_r/global_r.c
    +SOURCES += ../libqhull_r/stat_r.c
    +SOURCES += ../libqhull_r/geom2_r.c
    +SOURCES += ../libqhull_r/poly2_r.c
    +SOURCES += ../libqhull_r/merge_r.c
    +SOURCES += ../libqhull_r/libqhull_r.c
    +SOURCES += ../libqhull_r/geom_r.c
    +SOURCES += ../libqhull_r/poly_r.c
    +SOURCES += ../libqhull_r/qset_r.c
    +SOURCES += ../libqhull_r/mem_r.c
    +SOURCES += ../libqhull_r/random_r.c
    +SOURCES += ../libqhull_r/usermem_r.c
    +SOURCES += ../libqhull_r/userprintf_r.c
    +SOURCES += ../libqhull_r/io_r.c
    +SOURCES += ../libqhull_r/user_r.c
    +SOURCES += ../libqhull_r/rboxlib_r.c
    +SOURCES += ../libqhull_r/userprintf_rbox_r.c
    +
    +HEADERS += ../libqhull_r/geom_r.h
    +HEADERS += ../libqhull_r/io_r.h
    +HEADERS += ../libqhull_r/libqhull_r.h
    +HEADERS += ../libqhull_r/mem_r.h
    +HEADERS += ../libqhull_r/merge_r.h
    +HEADERS += ../libqhull_r/poly_r.h
    +HEADERS += ../libqhull_r/random_r.h
    +HEADERS += ../libqhull_r/qhull_ra.h
    +HEADERS += ../libqhull_r/qset_r.h
    +HEADERS += ../libqhull_r/stat_r.h
    +HEADERS += ../libqhull_r/user_r.h
    +
    +OTHER_FILES += qh-geom_r.htm
    +OTHER_FILES += qh-globa_r.htm
    +OTHER_FILES += qh-io_r.htm
    +OTHER_FILES += qh-mem_r.htm
    +OTHER_FILES += qh-merge_r.htm
    +OTHER_FILES += qh-poly_r.htm
    +OTHER_FILES += qh-qhull_r.htm
    +OTHER_FILES += qh-set_r.htm
    +OTHER_FILES += qh-stat_r.htm
    +OTHER_FILES += qh-user_r.htm
    diff --git a/xs/src/qhull/src/libqhull_r/mem_r.c b/xs/src/qhull/src/libqhull_r/mem_r.c
    new file mode 100644
    index 0000000000..801a8c76a1
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/mem_r.c
    @@ -0,0 +1,562 @@
    +/*
      ---------------------------------
    +
    +  mem_r.c
    +    memory management routines for qhull
    +
    +  See libqhull/mem_r.c for a standalone program.
    +
    +  To initialize memory:
    +
    +    qh_meminit(qh, stderr);
    +    qh_meminitbuffers(qh, qh->IStracing, qh_MEMalign, 7, qh_MEMbufsize,qh_MEMinitbuf);
    +    qh_memsize(qh, (int)sizeof(facetT));
    +    qh_memsize(qh, (int)sizeof(facetT));
    +    ...
    +    qh_memsetup(qh);
    +
    +  To free up all memory buffers:
    +    qh_memfreeshort(qh, &curlong, &totlong);
    +
    +  if qh_NOmem,
    +    malloc/free is used instead of mem.c
    +
    +  notes:
    +    uses Quickfit algorithm (freelists for commonly allocated sizes)
    +    assumes small sizes for freelists (it discards the tail of memory buffers)
    +
    +  see:
    +    qh-mem_r.htm and mem_r.h
    +    global_r.c (qh_initbuffers) for an example of using mem_r.c
    +
    +  Copyright (c) 1993-2015 The Geometry Center.
    +  $Id: //main/2015/qhull/src/libqhull_r/mem_r.c#5 $$Change: 2065 $
    +  $DateTime: 2016/01/18 13:51:04 $$Author: bbarber $
    +*/
    +
    +#include "libqhull_r.h"  /* includes user_r.h and mem_r.h */
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifndef qh_NOmem
    +
    +/*============= internal functions ==============*/
    +
    +static int qh_intcompare(const void *i, const void *j);
    +
    +/*========== functions in alphabetical order ======== */
    +
    +/*---------------------------------
    +
    +  qh_intcompare( i, j )
    +    used by qsort and bsearch to compare two integers
    +*/
    +static int qh_intcompare(const void *i, const void *j) {
    +  return(*((const int *)i) - *((const int *)j));
    +} /* intcompare */
    +
    +
    +/*----------------------------------
    +
    +  qh_memalloc( qh, insize )
    +    returns object of insize bytes
    +    qhmem is the global memory structure
    +
    +  returns:
    +    pointer to allocated memory
    +    errors if insufficient memory
    +
    +  notes:
    +    use explicit type conversion to avoid type warnings on some compilers
    +    actual object may be larger than insize
    +    use qh_memalloc_() for inline code for quick allocations
    +    logs allocations if 'T5'
    +    caller is responsible for freeing the memory.
    +    short memory is freed on shutdown by qh_memfreeshort unless qh_NOmem
    +
    +  design:
    +    if size < qh->qhmem.LASTsize
    +      if qh->qhmem.freelists[size] non-empty
    +        return first object on freelist
    +      else
    +        round up request to size of qh->qhmem.freelists[size]
    +        allocate new allocation buffer if necessary
    +        allocate object from allocation buffer
    +    else
    +      allocate object with qh_malloc() in user.c
    +*/
    +void *qh_memalloc(qhT *qh, int insize) {
    +  void **freelistp, *newbuffer;
    +  int idx, size, n;
    +  int outsize, bufsize;
    +  void *object;
    +
    +  if (insize<0) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6235, "qhull error (qh_memalloc): negative request size (%d).  Did int overflow due to high-D?\n", insize); /* WARN64 */
    +      qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (insize>=0 && insize <= qh->qhmem.LASTsize) {
    +    idx= qh->qhmem.indextable[insize];
    +    outsize= qh->qhmem.sizetable[idx];
    +    qh->qhmem.totshort += outsize;
    +    freelistp= qh->qhmem.freelists+idx;
    +    if ((object= *freelistp)) {
    +      qh->qhmem.cntquick++;
    +      qh->qhmem.totfree -= outsize;
    +      *freelistp= *((void **)*freelistp);  /* replace freelist with next object */
    +#ifdef qh_TRACEshort
    +      n= qh->qhmem.cntshort+qh->qhmem.cntquick+qh->qhmem.freeshort;
    +      if (qh->qhmem.IStracing >= 5)
    +          qh_fprintf(qh, qh->qhmem.ferr, 8141, "qh_mem %p n %8d alloc quick: %d bytes (tot %d cnt %d)\n", object, n, outsize, qh->qhmem.totshort, qh->qhmem.cntshort+qh->qhmem.cntquick-qh->qhmem.freeshort);
    +#endif
    +      return(object);
    +    }else {
    +      qh->qhmem.cntshort++;
    +      if (outsize > qh->qhmem.freesize) {
    +        qh->qhmem.totdropped += qh->qhmem.freesize;
    +        if (!qh->qhmem.curbuffer)
    +          bufsize= qh->qhmem.BUFinit;
    +        else
    +          bufsize= qh->qhmem.BUFsize;
    +        if (!(newbuffer= qh_malloc((size_t)bufsize))) {
    +          qh_fprintf(qh, qh->qhmem.ferr, 6080, "qhull error (qh_memalloc): insufficient memory to allocate short memory buffer (%d bytes)\n", bufsize);
    +          qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +        }
    +        *((void **)newbuffer)= qh->qhmem.curbuffer;  /* prepend newbuffer to curbuffer
    +                                                    list.  newbuffer!=0 by QH6080 */
    +        qh->qhmem.curbuffer= newbuffer;
    +        size= (sizeof(void **) + qh->qhmem.ALIGNmask) & ~qh->qhmem.ALIGNmask;
    +        qh->qhmem.freemem= (void *)((char *)newbuffer+size);
    +        qh->qhmem.freesize= bufsize - size;
    +        qh->qhmem.totbuffer += bufsize - size; /* easier to check */
    +        /* Periodically test totbuffer.  It matches at beginning and exit of every call */
    +        n = qh->qhmem.totshort + qh->qhmem.totfree + qh->qhmem.totdropped + qh->qhmem.freesize - outsize;
    +        if (qh->qhmem.totbuffer != n) {
    +            qh_fprintf(qh, qh->qhmem.ferr, 6212, "qh_memalloc internal error: short totbuffer %d != totshort+totfree... %d\n", qh->qhmem.totbuffer, n);
    +            qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +        }
    +      }
    +      object= qh->qhmem.freemem;
    +      qh->qhmem.freemem= (void *)((char *)qh->qhmem.freemem + outsize);
    +      qh->qhmem.freesize -= outsize;
    +      qh->qhmem.totunused += outsize - insize;
    +#ifdef qh_TRACEshort
    +      n= qh->qhmem.cntshort+qh->qhmem.cntquick+qh->qhmem.freeshort;
    +      if (qh->qhmem.IStracing >= 5)
    +          qh_fprintf(qh, qh->qhmem.ferr, 8140, "qh_mem %p n %8d alloc short: %d bytes (tot %d cnt %d)\n", object, n, outsize, qh->qhmem.totshort, qh->qhmem.cntshort+qh->qhmem.cntquick-qh->qhmem.freeshort);
    +#endif
    +      return object;
    +    }
    +  }else {                     /* long allocation */
    +    if (!qh->qhmem.indextable) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6081, "qhull internal error (qh_memalloc): qhmem has not been initialized.\n");
    +      qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +    }
    +    outsize= insize;
    +    qh->qhmem.cntlong++;
    +    qh->qhmem.totlong += outsize;
    +    if (qh->qhmem.maxlong < qh->qhmem.totlong)
    +      qh->qhmem.maxlong= qh->qhmem.totlong;
    +    if (!(object= qh_malloc((size_t)outsize))) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6082, "qhull error (qh_memalloc): insufficient memory to allocate %d bytes\n", outsize);
    +      qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +    }
    +    if (qh->qhmem.IStracing >= 5)
    +      qh_fprintf(qh, qh->qhmem.ferr, 8057, "qh_mem %p n %8d alloc long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, outsize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
    +  }
    +  return(object);
    +} /* memalloc */
    +
    +
    +/*----------------------------------
    +
    +  qh_memcheck(qh)
    +*/
    +void qh_memcheck(qhT *qh) {
    +  int i, count, totfree= 0;
    +  void *object;
    +
    +  if (!qh) {
    +    qh_fprintf_stderr(6243, "qh_memcheck(qh) error: qh is 0.  It does not point to a qhT");
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (qh->qhmem.ferr == 0 || qh->qhmem.IStracing < 0 || qh->qhmem.IStracing > 10 || (((qh->qhmem.ALIGNmask+1) & qh->qhmem.ALIGNmask) != 0)) {
    +    qh_fprintf_stderr(6244, "qh_memcheck error: either qh->qhmem is overwritten or qh->qhmem is not initialized.  Call qh_mem_new() or qh_new_qhull() before calling qh_mem routines.  ferr 0x%x IsTracing %d ALIGNmask 0x%x", qh->qhmem.ferr, qh->qhmem.IStracing, qh->qhmem.ALIGNmask);
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (qh->qhmem.IStracing != 0)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8143, "qh_memcheck: check size of freelists on qh->qhmem\nqh_memcheck: A segmentation fault indicates an overwrite of qh->qhmem\n");
    +  for (i=0; i < qh->qhmem.TABLEsize; i++) {
    +    count=0;
    +    for (object= qh->qhmem.freelists[i]; object; object= *((void **)object))
    +      count++;
    +    totfree += qh->qhmem.sizetable[i] * count;
    +  }
    +  if (totfree != qh->qhmem.totfree) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6211, "Qhull internal error (qh_memcheck): totfree %d not equal to freelist total %d\n", qh->qhmem.totfree, totfree);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  if (qh->qhmem.IStracing != 0)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8144, "qh_memcheck: total size of freelists totfree is the same as qh->qhmem.totfree\n", totfree);
    +} /* memcheck */
    +
    +/*----------------------------------
    +
    +  qh_memfree(qh, object, insize )
    +    free up an object of size bytes
    +    size is insize from qh_memalloc
    +
    +  notes:
    +    object may be NULL
    +    type checking warns if using (void **)object
    +    use qh_memfree_() for quick free's of small objects
    +
    +  design:
    +    if size <= qh->qhmem.LASTsize
    +      append object to corresponding freelist
    +    else
    +      call qh_free(object)
    +*/
    +void qh_memfree(qhT *qh, void *object, int insize) {
    +  void **freelistp;
    +  int idx, outsize;
    +
    +  if (!object)
    +    return;
    +  if (insize <= qh->qhmem.LASTsize) {
    +    qh->qhmem.freeshort++;
    +    idx= qh->qhmem.indextable[insize];
    +    outsize= qh->qhmem.sizetable[idx];
    +    qh->qhmem.totfree += outsize;
    +    qh->qhmem.totshort -= outsize;
    +    freelistp= qh->qhmem.freelists + idx;
    +    *((void **)object)= *freelistp;
    +    *freelistp= object;
    +#ifdef qh_TRACEshort
    +    idx= qh->qhmem.cntshort+qh->qhmem.cntquick+qh->qhmem.freeshort;
    +    if (qh->qhmem.IStracing >= 5)
    +        qh_fprintf(qh, qh->qhmem.ferr, 8142, "qh_mem %p n %8d free short: %d bytes (tot %d cnt %d)\n", object, idx, outsize, qh->qhmem.totshort, qh->qhmem.cntshort+qh->qhmem.cntquick-qh->qhmem.freeshort);
    +#endif
    +  }else {
    +    qh->qhmem.freelong++;
    +    qh->qhmem.totlong -= insize;
    +    if (qh->qhmem.IStracing >= 5)
    +      qh_fprintf(qh, qh->qhmem.ferr, 8058, "qh_mem %p n %8d free long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, insize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
    +    qh_free(object);
    +  }
    +} /* memfree */
    +
    +
    +/*---------------------------------
    +
    +  qh_memfreeshort(qh, curlong, totlong )
    +    frees up all short and qhmem memory allocations
    +
    +  returns:
    +    number and size of current long allocations
    +  
    +  notes:
    +    if qh_NOmem (qh_malloc() for all allocations), 
    +       short objects (e.g., facetT) are not recovered.
    +       use qh_freeqhull(qh, qh_ALL) instead.
    + 
    +  see:
    +    qh_freeqhull(qh, allMem)
    +    qh_memtotal(qh, curlong, totlong, curshort, totshort, maxlong, totbuffer);
    +*/
    +void qh_memfreeshort(qhT *qh, int *curlong, int *totlong) {
    +  void *buffer, *nextbuffer;
    +  FILE *ferr;
    +
    +  *curlong= qh->qhmem.cntlong - qh->qhmem.freelong;
    +  *totlong= qh->qhmem.totlong;
    +  for (buffer= qh->qhmem.curbuffer; buffer; buffer= nextbuffer) {
    +    nextbuffer= *((void **) buffer);
    +    qh_free(buffer);
    +  }
    +  qh->qhmem.curbuffer= NULL;
    +  if (qh->qhmem.LASTsize) {
    +    qh_free(qh->qhmem.indextable);
    +    qh_free(qh->qhmem.freelists);
    +    qh_free(qh->qhmem.sizetable);
    +  }
    +  ferr= qh->qhmem.ferr;
    +  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
    +  qh->qhmem.ferr= ferr;
    +} /* memfreeshort */
    +
    +
    +/*----------------------------------
    +
    +  qh_meminit(qh, ferr )
    +    initialize qhmem and test sizeof( void*)
    +    Does not throw errors.  qh_exit on failure
    +*/
    +void qh_meminit(qhT *qh, FILE *ferr) {
    +
    +  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
    +  if (ferr)
    +      qh->qhmem.ferr= ferr;
    +  else
    +      qh->qhmem.ferr= stderr;
    +  if (sizeof(void*) < sizeof(int)) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6083, "qhull internal error (qh_meminit): sizeof(void*) %d < sizeof(int) %d.  qset.c will not work\n", (int)sizeof(void*), (int)sizeof(int));
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (sizeof(void*) > sizeof(ptr_intT)) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6084, "qhull internal error (qh_meminit): sizeof(void*) %d > sizeof(ptr_intT) %d. Change ptr_intT in mem.h to 'long long'\n", (int)sizeof(void*), (int)sizeof(ptr_intT));
    +      qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  qh_memcheck(qh);
    +} /* meminit */
    +
    +/*---------------------------------
    +
    +  qh_meminitbuffers(qh, tracelevel, alignment, numsizes, bufsize, bufinit )
    +    initialize qhmem
    +    if tracelevel >= 5, trace memory allocations
    +    alignment= desired address alignment for memory allocations
    +    numsizes= number of freelists
    +    bufsize=  size of additional memory buffers for short allocations
    +    bufinit=  size of initial memory buffer for short allocations
    +*/
    +void qh_meminitbuffers(qhT *qh, int tracelevel, int alignment, int numsizes, int bufsize, int bufinit) {
    +
    +  qh->qhmem.IStracing= tracelevel;
    +  qh->qhmem.NUMsizes= numsizes;
    +  qh->qhmem.BUFsize= bufsize;
    +  qh->qhmem.BUFinit= bufinit;
    +  qh->qhmem.ALIGNmask= alignment-1;
    +  if (qh->qhmem.ALIGNmask & ~qh->qhmem.ALIGNmask) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6085, "qhull internal error (qh_meminit): memory alignment %d is not a power of 2\n", alignment);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh->qhmem.sizetable= (int *) calloc((size_t)numsizes, sizeof(int));
    +  qh->qhmem.freelists= (void **) calloc((size_t)numsizes, sizeof(void *));
    +  if (!qh->qhmem.sizetable || !qh->qhmem.freelists) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6086, "qhull error (qh_meminit): insufficient memory\n");
    +    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (qh->qhmem.IStracing >= 1)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8059, "qh_meminitbuffers: memory initialized with alignment %d\n", alignment);
    +} /* meminitbuffers */
    +
    +/*---------------------------------
    +
    +  qh_memsetup(qh)
    +    set up memory after running memsize()
    +*/
    +void qh_memsetup(qhT *qh) {
    +  int k,i;
    +
    +  qsort(qh->qhmem.sizetable, (size_t)qh->qhmem.TABLEsize, sizeof(int), qh_intcompare);
    +  qh->qhmem.LASTsize= qh->qhmem.sizetable[qh->qhmem.TABLEsize-1];
    +  if(qh->qhmem.LASTsize >= qh->qhmem.BUFsize || qh->qhmem.LASTsize >= qh->qhmem.BUFinit) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6087, "qhull error (qh_memsetup): largest mem size %d is >= buffer size %d or initial buffer size %d\n",
    +            qh->qhmem.LASTsize, qh->qhmem.BUFsize, qh->qhmem.BUFinit);
    +    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (!(qh->qhmem.indextable= (int *)qh_malloc((qh->qhmem.LASTsize+1) * sizeof(int)))) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6088, "qhull error (qh_memsetup): insufficient memory\n");
    +    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  for (k=qh->qhmem.LASTsize+1; k--; )
    +    qh->qhmem.indextable[k]= k;
    +  i= 0;
    +  for (k=0; k <= qh->qhmem.LASTsize; k++) {
    +    if (qh->qhmem.indextable[k] <= qh->qhmem.sizetable[i])
    +      qh->qhmem.indextable[k]= i;
    +    else
    +      qh->qhmem.indextable[k]= ++i;
    +  }
    +} /* memsetup */
    +
    +/*---------------------------------
    +
    +  qh_memsize(qh, size )
    +    define a free list for this size
    +*/
    +void qh_memsize(qhT *qh, int size) {
    +  int k;
    +
    +  if(qh->qhmem.LASTsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6089, "qhull error (qh_memsize): called after qhmem_setup\n");
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  size= (size + qh->qhmem.ALIGNmask) & ~qh->qhmem.ALIGNmask;
    +  for (k=qh->qhmem.TABLEsize; k--; ) {
    +    if (qh->qhmem.sizetable[k] == size)
    +      return;
    +  }
    +  if (qh->qhmem.TABLEsize < qh->qhmem.NUMsizes)
    +    qh->qhmem.sizetable[qh->qhmem.TABLEsize++]= size;
    +  else
    +    qh_fprintf(qh, qh->qhmem.ferr, 7060, "qhull warning (memsize): free list table has room for only %d sizes\n", qh->qhmem.NUMsizes);
    +} /* memsize */
    +
    +
    +/*---------------------------------
    +
    +  qh_memstatistics(qh, fp )
    +    print out memory statistics
    +
    +    Verifies that qh->qhmem.totfree == sum of freelists
    +*/
    +void qh_memstatistics(qhT *qh, FILE *fp) {
    +  int i;
    +  int count;
    +  void *object;
    +
    +  qh_memcheck(qh);
    +  qh_fprintf(qh, fp, 9278, "\nmemory statistics:\n\
    +%7d quick allocations\n\
    +%7d short allocations\n\
    +%7d long allocations\n\
    +%7d short frees\n\
    +%7d long frees\n\
    +%7d bytes of short memory in use\n\
    +%7d bytes of short memory in freelists\n\
    +%7d bytes of dropped short memory\n\
    +%7d bytes of unused short memory (estimated)\n\
    +%7d bytes of long memory allocated (max, except for input)\n\
    +%7d bytes of long memory in use (in %d pieces)\n\
    +%7d bytes of short memory buffers (minus links)\n\
    +%7d bytes per short memory buffer (initially %d bytes)\n",
    +           qh->qhmem.cntquick, qh->qhmem.cntshort, qh->qhmem.cntlong,
    +           qh->qhmem.freeshort, qh->qhmem.freelong,
    +           qh->qhmem.totshort, qh->qhmem.totfree,
    +           qh->qhmem.totdropped + qh->qhmem.freesize, qh->qhmem.totunused,
    +           qh->qhmem.maxlong, qh->qhmem.totlong, qh->qhmem.cntlong - qh->qhmem.freelong,
    +           qh->qhmem.totbuffer, qh->qhmem.BUFsize, qh->qhmem.BUFinit);
    +  if (qh->qhmem.cntlarger) {
    +    qh_fprintf(qh, fp, 9279, "%7d calls to qh_setlarger\n%7.2g     average copy size\n",
    +           qh->qhmem.cntlarger, ((float)qh->qhmem.totlarger)/(float)qh->qhmem.cntlarger);
    +    qh_fprintf(qh, fp, 9280, "  freelists(bytes->count):");
    +  }
    +  for (i=0; i < qh->qhmem.TABLEsize; i++) {
    +    count=0;
    +    for (object= qh->qhmem.freelists[i]; object; object= *((void **)object))
    +      count++;
    +    qh_fprintf(qh, fp, 9281, " %d->%d", qh->qhmem.sizetable[i], count);
    +  }
    +  qh_fprintf(qh, fp, 9282, "\n\n");
    +} /* memstatistics */
    +
    +
    +/*---------------------------------
    +
    +  qh_NOmem
    +    turn off quick-fit memory allocation
    +
    +  notes:
    +    uses qh_malloc() and qh_free() instead
    +*/
    +#else /* qh_NOmem */
    +
    +void *qh_memalloc(qhT *qh, int insize) {
    +  void *object;
    +
    +  if (!(object= qh_malloc((size_t)insize))) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6090, "qhull error (qh_memalloc): insufficient memory\n");
    +    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  qh->qhmem.cntlong++;
    +  qh->qhmem.totlong += insize;
    +  if (qh->qhmem.maxlong < qh->qhmem.totlong)
    +      qh->qhmem.maxlong= qh->qhmem.totlong;
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8060, "qh_mem %p n %8d alloc long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, insize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
    +  return object;
    +}
    +
    +void qh_memfree(qhT *qh, void *object, int insize) {
    +
    +  if (!object)
    +    return;
    +  qh_free(object);
    +  qh->qhmem.freelong++;
    +  qh->qhmem.totlong -= insize;
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8061, "qh_mem %p n %8d free long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, insize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
    +}
    +
    +void qh_memfreeshort(qhT *qh, int *curlong, int *totlong) {
    +  *totlong= qh->qhmem.totlong;
    +  *curlong= qh->qhmem.cntlong - qh->qhmem.freelong;
    +  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
    +}
    +
    +void qh_meminit(qhT *qh, FILE *ferr) {
    +
    +  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
    +  if (ferr)
    +      qh->qhmem.ferr= ferr;
    +  else
    +      qh->qhmem.ferr= stderr;
    +  if (sizeof(void*) < sizeof(int)) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6091, "qhull internal error (qh_meminit): sizeof(void*) %d < sizeof(int) %d.  qset.c will not work\n", (int)sizeof(void*), (int)sizeof(int));
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +}
    +
    +void qh_meminitbuffers(qhT *qh, int tracelevel, int alignment, int numsizes, int bufsize, int bufinit) {
    +
    +  qh->qhmem.IStracing= tracelevel;
    +}
    +
    +void qh_memsetup(qhT *qh) {
    +
    +}
    +
    +void qh_memsize(qhT *qh, int size) {
    +
    +}
    +
    +void qh_memstatistics(qhT *qh, FILE *fp) {
    +
    +  qh_fprintf(qh, fp, 9409, "\nmemory statistics:\n\
    +%7d long allocations\n\
    +%7d long frees\n\
    +%7d bytes of long memory allocated (max, except for input)\n\
    +%7d bytes of long memory in use (in %d pieces)\n",
    +           qh->qhmem.cntlong,
    +           qh->qhmem.freelong,
    +           qh->qhmem.maxlong, qh->qhmem.totlong, qh->qhmem.cntlong - qh->qhmem.freelong);
    +}
    +
    +#endif /* qh_NOmem */
    +
    +/*---------------------------------
    +
    +  qh_memtotal(qh, totlong, curlong, totshort, curshort, maxlong, totbuffer )
    +    Return the total, allocated long and short memory
    +
    +  returns:
    +    Returns the total current bytes of long and short allocations
    +    Returns the current count of long and short allocations
    +    Returns the maximum long memory and total short buffer (minus one link per buffer)
    +    Does not error (for deprecated UsingLibQhull.cpp (libqhullpcpp))
    +*/
    +void qh_memtotal(qhT *qh, int *totlong, int *curlong, int *totshort, int *curshort, int *maxlong, int *totbuffer) {
    +    *totlong= qh->qhmem.totlong;
    +    *curlong= qh->qhmem.cntlong - qh->qhmem.freelong;
    +    *totshort= qh->qhmem.totshort;
    +    *curshort= qh->qhmem.cntshort + qh->qhmem.cntquick - qh->qhmem.freeshort;
    +    *maxlong= qh->qhmem.maxlong;
    +    *totbuffer= qh->qhmem.totbuffer;
    +} /* memtotlong */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/mem_r.h b/xs/src/qhull/src/libqhull_r/mem_r.h
    new file mode 100644
    index 0000000000..25b5513330
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/mem_r.h
    @@ -0,0 +1,234 @@
    +/*
      ---------------------------------
    +
    +   mem_r.h
    +     prototypes for memory management functions
    +
    +   see qh-mem_r.htm, mem_r.c and qset_r.h
    +
    +   for error handling, writes message and calls
    +     qh_errexit(qhT *qh, qhmem_ERRmem, NULL, NULL) if insufficient memory
    +       and
    +     qh_errexit(qhT *qh, qhmem_ERRqhull, NULL, NULL) otherwise
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/mem_r.h#4 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFmem
    +#define qhDEFmem 1
    +
    +#include 
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;          /* defined in qset_r.h */
    +#endif
    +
    +#ifndef DEFqhT
    +#define DEFqhT 1
    +typedef struct qhT qhT;          /* defined in libqhull_r.h */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_NOmem
    +    turn off quick-fit memory allocation
    +
    +  notes:
    +    mem_r.c implements Quickfit memory allocation for about 20% time
    +    savings.  If it fails on your machine, try to locate the
    +    problem, and send the answer to qhull@qhull.org.  If this can
    +    not be done, define qh_NOmem to use malloc/free instead.
    +
    +   #define qh_NOmem
    +*/
    +
    +/*---------------------------------
    +
    +qh_TRACEshort
    +Trace short and quick memory allocations at T5
    +
    +*/
    +#define qh_TRACEshort
    +
    +/*-------------------------------------------
    +    to avoid bus errors, memory allocation must consider alignment requirements.
    +    malloc() automatically takes care of alignment.   Since mem_r.c manages
    +    its own memory, we need to explicitly specify alignment in
    +    qh_meminitbuffers().
    +
    +    A safe choice is sizeof(double).  sizeof(float) may be used if doubles
    +    do not occur in data structures and pointers are the same size.  Be careful
    +    of machines (e.g., DEC Alpha) with large pointers.  If gcc is available,
    +    use __alignof__(double) or fmax_(__alignof__(float), __alignof__(void *)).
    +
    +   see qh_MEMalign in user.h for qhull's alignment
    +*/
    +
    +#define qhmem_ERRmem 4    /* matches qh_ERRmem in libqhull_r.h */
    +#define qhmem_ERRqhull 5  /* matches qh_ERRqhull in libqhull_r.h */
    +
    +/*----------------------------------
    +
    +  ptr_intT
    +    for casting a void * to an integer-type that holds a pointer
    +    Used for integer expressions (e.g., computing qh_gethash() in poly_r.c)
    +
    +  notes:
    +    WARN64 -- these notes indicate 64-bit issues
    +    On 64-bit machines, a pointer may be larger than an 'int'.
    +    qh_meminit()/mem_r.c checks that 'ptr_intT' holds a 'void*'
    +    ptr_intT is typically a signed value, but not necessarily so
    +    size_t is typically unsigned, but should match the parameter type
    +    Qhull uses int instead of size_t except for system calls such as malloc, qsort, qh_malloc, etc.
    +    This matches Qt convention and is easier to work with.
    +*/
    +#if (defined(__MINGW64__)) && defined(_WIN64)
    +typedef long long ptr_intT;
    +#elif (_MSC_VER) && defined(_WIN64)
    +typedef long long ptr_intT;
    +#else
    +typedef long ptr_intT;
    +#endif
    +
    +/*----------------------------------
    +
    +  qhmemT
    +    global memory structure for mem_r.c
    +
    + notes:
    +   users should ignore qhmem except for writing extensions
    +   qhmem is allocated in mem_r.c
    +
    +   qhmem could be swapable like qh and qhstat, but then
    +   multiple qh's and qhmem's would need to keep in synch.
    +   A swapable qhmem would also waste memory buffers.  As long
    +   as memory operations are atomic, there is no problem with
    +   multiple qh structures being active at the same time.
    +   If you need separate address spaces, you can swap the
    +   contents of qh->qhmem.
    +*/
    +typedef struct qhmemT qhmemT;
    +
    +/* Update qhmem in mem_r.c if add or remove fields */
    +struct qhmemT {               /* global memory management variables */
    +  int      BUFsize;           /* size of memory allocation buffer */
    +  int      BUFinit;           /* initial size of memory allocation buffer */
    +  int      TABLEsize;         /* actual number of sizes in free list table */
    +  int      NUMsizes;          /* maximum number of sizes in free list table */
    +  int      LASTsize;          /* last size in free list table */
    +  int      ALIGNmask;         /* worst-case alignment, must be 2^n-1 */
    +  void   **freelists;          /* free list table, linked by offset 0 */
    +  int     *sizetable;         /* size of each freelist */
    +  int     *indextable;        /* size->index table */
    +  void    *curbuffer;         /* current buffer, linked by offset 0 */
    +  void    *freemem;           /*   free memory in curbuffer */
    +  int      freesize;          /*   size of freemem in bytes */
    +  setT    *tempstack;         /* stack of temporary memory, managed by users */
    +  FILE    *ferr;              /* file for reporting errors when 'qh' may be undefined */
    +  int      IStracing;         /* =5 if tracing memory allocations */
    +  int      cntquick;          /* count of quick allocations */
    +                              /* Note: removing statistics doesn't effect speed */
    +  int      cntshort;          /* count of short allocations */
    +  int      cntlong;           /* count of long allocations */
    +  int      freeshort;         /* count of short memfrees */
    +  int      freelong;          /* count of long memfrees */
    +  int      totbuffer;         /* total short memory buffers minus buffer links */
    +  int      totdropped;        /* total dropped memory at end of short memory buffers (e.g., freesize) */
    +  int      totfree;           /* total size of free, short memory on freelists */
    +  int      totlong;           /* total size of long memory in use */
    +  int      maxlong;           /*   maximum totlong */
    +  int      totshort;          /* total size of short memory in use */
    +  int      totunused;         /* total unused short memory (estimated, short size - request size of first allocations) */
    +  int      cntlarger;         /* count of setlarger's */
    +  int      totlarger;         /* total copied by setlarger */
    +};
    +
    +
    +/*==================== -macros ====================*/
    +
    +/*----------------------------------
    +
    +  qh_memalloc_(qh, insize, freelistp, object, type)
    +    returns object of size bytes
    +        assumes size<=qh->qhmem.LASTsize and void **freelistp is a temp
    +*/
    +
    +#if defined qh_NOmem
    +#define qh_memalloc_(qh, insize, freelistp, object, type) {\
    +  object= (type*)qh_memalloc(qh, insize); }
    +#elif defined qh_TRACEshort
    +#define qh_memalloc_(qh, insize, freelistp, object, type) {\
    +    freelistp= NULL; /* Avoid warnings */ \
    +    object= (type*)qh_memalloc(qh, insize); }
    +#else /* !qh_NOmem */
    +
    +#define qh_memalloc_(qh, insize, freelistp, object, type) {\
    +  freelistp= qh->qhmem.freelists + qh->qhmem.indextable[insize];\
    +  if ((object= (type*)*freelistp)) {\
    +    qh->qhmem.totshort += qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
    +    qh->qhmem.totfree -= qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
    +    qh->qhmem.cntquick++;  \
    +    *freelistp= *((void **)*freelistp);\
    +  }else object= (type*)qh_memalloc(qh, insize);}
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_memfree_(qh, object, insize, freelistp)
    +    free up an object
    +
    +  notes:
    +    object may be NULL
    +    assumes size<=qh->qhmem.LASTsize and void **freelistp is a temp
    +*/
    +#if defined qh_NOmem
    +#define qh_memfree_(qh, object, insize, freelistp) {\
    +  qh_memfree(qh, object, insize); }
    +#elif defined qh_TRACEshort
    +#define qh_memfree_(qh, object, insize, freelistp) {\
    +    freelistp= NULL; /* Avoid warnings */ \
    +    qh_memfree(qh, object, insize); }
    +#else /* !qh_NOmem */
    +
    +#define qh_memfree_(qh, object, insize, freelistp) {\
    +  if (object) { \
    +    qh->qhmem.freeshort++;\
    +    freelistp= qh->qhmem.freelists + qh->qhmem.indextable[insize];\
    +    qh->qhmem.totshort -= qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
    +    qh->qhmem.totfree += qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
    +    *((void **)object)= *freelistp;\
    +    *freelistp= object;}}
    +#endif
    +
    +/*=============== prototypes in alphabetical order ============*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void *qh_memalloc(qhT *qh, int insize);
    +void qh_memcheck(qhT *qh);
    +void qh_memfree(qhT *qh, void *object, int insize);
    +void qh_memfreeshort(qhT *qh, int *curlong, int *totlong);
    +void qh_meminit(qhT *qh, FILE *ferr);
    +void qh_meminitbuffers(qhT *qh, int tracelevel, int alignment, int numsizes,
    +                        int bufsize, int bufinit);
    +void qh_memsetup(qhT *qh);
    +void qh_memsize(qhT *qh, int size);
    +void qh_memstatistics(qhT *qh, FILE *fp);
    +void qh_memtotal(qhT *qh, int *totlong, int *curlong, int *totshort, int *curshort, int *maxlong, int *totbuffer);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFmem */
    diff --git a/xs/src/qhull/src/libqhull_r/merge_r.c b/xs/src/qhull/src/libqhull_r/merge_r.c
    new file mode 100644
    index 0000000000..e5823de8d1
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/merge_r.c
    @@ -0,0 +1,3627 @@
    +/*
      ---------------------------------
    +
    +   merge_r.c
    +   merges non-convex facets
    +
    +   see qh-merge_r.htm and merge_r.h
    +
    +   other modules call qh_premerge() and qh_postmerge()
    +
    +   the user may call qh_postmerge() to perform additional merges.
    +
    +   To remove deleted facets and vertices (qhull() in libqhull_r.c):
    +     qh_partitionvisible(qh, !qh_ALL, &numoutside);  // visible_list, newfacet_list
    +     qh_deletevisible();         // qh.visible_list
    +     qh_resetlists(qh, False, qh_RESETvisible);       // qh.visible_list newvertex_list newfacet_list
    +
    +   assumes qh.CENTERtype= centrum
    +
    +   merges occur in qh_mergefacet and in qh_mergecycle
    +   vertex->neighbors not set until the first merge occurs
    +
    +   Copyright (c) 1993-2015 C.B. Barber.
    +   $Id: //main/2015/qhull/src/libqhull_r/merge_r.c#5 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +#ifndef qh_NOmerge
    +
    +/*===== functions(alphabetical after premerge and postmerge) ======*/
    +
    +/*---------------------------------
    +
    +  qh_premerge(qh, apex, maxcentrum )
    +    pre-merge nonconvex facets in qh.newfacet_list for apex
    +    maxcentrum defines coplanar and concave (qh_test_appendmerge)
    +
    +  returns:
    +    deleted facets added to qh.visible_list with facet->visible set
    +
    +  notes:
    +    uses globals, qh.MERGEexact, qh.PREmerge
    +
    +  design:
    +    mark duplicate ridges in qh.newfacet_list
    +    merge facet cycles in qh.newfacet_list
    +    merge duplicate ridges and concave facets in qh.newfacet_list
    +    check merged facet cycles for degenerate and redundant facets
    +    merge degenerate and redundant facets
    +    collect coplanar and concave facets
    +    merge concave, coplanar, degenerate, and redundant facets
    +*/
    +void qh_premerge(qhT *qh, vertexT *apex, realT maxcentrum, realT maxangle) {
    +  boolT othermerge= False;
    +  facetT *newfacet;
    +
    +  if (qh->ZEROcentrum && qh_checkzero(qh, !qh_ALL))
    +    return;
    +  trace2((qh, qh->ferr, 2008, "qh_premerge: premerge centrum %2.2g angle %2.2g for apex v%d facetlist f%d\n",
    +            maxcentrum, maxangle, apex->id, getid_(qh->newfacet_list)));
    +  if (qh->IStracing >= 4 && qh->num_facets < 50)
    +    qh_printlists(qh);
    +  qh->centrum_radius= maxcentrum;
    +  qh->cos_max= maxangle;
    +  qh->degen_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  if (qh->hull_dim >=3) {
    +    qh_mark_dupridges(qh, qh->newfacet_list); /* facet_mergeset */
    +    qh_mergecycle_all(qh, qh->newfacet_list, &othermerge);
    +    qh_forcedmerges(qh, &othermerge /* qh->facet_mergeset */);
    +    FORALLnew_facets {  /* test samecycle merges */
    +      if (!newfacet->simplicial && !newfacet->mergeridge)
    +        qh_degen_redundant_neighbors(qh, newfacet, NULL);
    +    }
    +    if (qh_merge_degenredundant(qh))
    +      othermerge= True;
    +  }else /* qh->hull_dim == 2 */
    +    qh_mergecycle_all(qh, qh->newfacet_list, &othermerge);
    +  qh_flippedmerges(qh, qh->newfacet_list, &othermerge);
    +  if (!qh->MERGEexact || zzval_(Ztotmerge)) {
    +    zinc_(Zpremergetot);
    +    qh->POSTmerging= False;
    +    qh_getmergeset_initial(qh, qh->newfacet_list);
    +    qh_all_merges(qh, othermerge, False);
    +  }
    +  qh_settempfree(qh, &qh->facet_mergeset);
    +  qh_settempfree(qh, &qh->degen_mergeset);
    +} /* premerge */
    +
    +/*---------------------------------
    +
    +  qh_postmerge(qh, reason, maxcentrum, maxangle, vneighbors )
    +    post-merge nonconvex facets as defined by maxcentrum and maxangle
    +    'reason' is for reporting progress
    +    if vneighbors,
    +      calls qh_test_vneighbors at end of qh_all_merge
    +    if firstmerge,
    +      calls qh_reducevertices before qh_getmergeset
    +
    +  returns:
    +    if first call (qh.visible_list != qh.facet_list),
    +      builds qh.facet_newlist, qh.newvertex_list
    +    deleted facets added to qh.visible_list with facet->visible
    +    qh.visible_list == qh.facet_list
    +
    +  notes:
    +
    +
    +  design:
    +    if first call
    +      set qh.visible_list and qh.newfacet_list to qh.facet_list
    +      add all facets to qh.newfacet_list
    +      mark non-simplicial facets, facet->newmerge
    +      set qh.newvertext_list to qh.vertex_list
    +      add all vertices to qh.newvertex_list
    +      if a pre-merge occured
    +        set vertex->delridge {will retest the ridge}
    +        if qh.MERGEexact
    +          call qh_reducevertices()
    +      if no pre-merging
    +        merge flipped facets
    +    determine non-convex facets
    +    merge all non-convex facets
    +*/
    +void qh_postmerge(qhT *qh, const char *reason, realT maxcentrum, realT maxangle,
    +                      boolT vneighbors) {
    +  facetT *newfacet;
    +  boolT othermerges= False;
    +  vertexT *vertex;
    +
    +  if (qh->REPORTfreq || qh->IStracing) {
    +    qh_buildtracing(qh, NULL, NULL);
    +    qh_printsummary(qh, qh->ferr);
    +    if (qh->PRINTstatistics)
    +      qh_printallstatistics(qh, qh->ferr, "reason");
    +    qh_fprintf(qh, qh->ferr, 8062, "\n%s with 'C%.2g' and 'A%.2g'\n",
    +        reason, maxcentrum, maxangle);
    +  }
    +  trace2((qh, qh->ferr, 2009, "qh_postmerge: postmerge.  test vneighbors? %d\n",
    +            vneighbors));
    +  qh->centrum_radius= maxcentrum;
    +  qh->cos_max= maxangle;
    +  qh->POSTmerging= True;
    +  qh->degen_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  if (qh->visible_list != qh->facet_list) {  /* first call */
    +    qh->NEWfacets= True;
    +    qh->visible_list= qh->newfacet_list= qh->facet_list;
    +    FORALLnew_facets {
    +      newfacet->newfacet= True;
    +       if (!newfacet->simplicial)
    +        newfacet->newmerge= True;
    +     zinc_(Zpostfacets);
    +    }
    +    qh->newvertex_list= qh->vertex_list;
    +    FORALLvertices
    +      vertex->newlist= True;
    +    if (qh->VERTEXneighbors) { /* a merge has occurred */
    +      FORALLvertices
    +        vertex->delridge= True; /* test for redundant, needed? */
    +      if (qh->MERGEexact) {
    +        if (qh->hull_dim <= qh_DIMreduceBuild)
    +          qh_reducevertices(qh); /* was skipped during pre-merging */
    +      }
    +    }
    +    if (!qh->PREmerge && !qh->MERGEexact)
    +      qh_flippedmerges(qh, qh->newfacet_list, &othermerges);
    +  }
    +  qh_getmergeset_initial(qh, qh->newfacet_list);
    +  qh_all_merges(qh, False, vneighbors);
    +  qh_settempfree(qh, &qh->facet_mergeset);
    +  qh_settempfree(qh, &qh->degen_mergeset);
    +} /* post_merge */
    +
    +/*---------------------------------
    +
    +  qh_all_merges(qh, othermerge, vneighbors )
    +    merge all non-convex facets
    +
    +    set othermerge if already merged facets (for qh_reducevertices)
    +    if vneighbors
    +      tests vertex neighbors for convexity at end
    +    qh.facet_mergeset lists the non-convex ridges in qh_newfacet_list
    +    qh.degen_mergeset is defined
    +    if qh.MERGEexact && !qh.POSTmerging,
    +      does not merge coplanar facets
    +
    +  returns:
    +    deleted facets added to qh.visible_list with facet->visible
    +    deleted vertices added qh.delvertex_list with vertex->delvertex
    +
    +  notes:
    +    unless !qh.MERGEindependent,
    +      merges facets in independent sets
    +    uses qh.newfacet_list as argument since merges call qh_removefacet()
    +
    +  design:
    +    while merges occur
    +      for each merge in qh.facet_mergeset
    +        unless one of the facets was already merged in this pass
    +          merge the facets
    +        test merged facets for additional merges
    +        add merges to qh.facet_mergeset
    +      if vertices record neighboring facets
    +        rename redundant vertices
    +          update qh.facet_mergeset
    +    if vneighbors ??
    +      tests vertex neighbors for convexity at end
    +*/
    +void qh_all_merges(qhT *qh, boolT othermerge, boolT vneighbors) {
    +  facetT *facet1, *facet2;
    +  mergeT *merge;
    +  boolT wasmerge= True, isreduce;
    +  void **freelistp;  /* used if !qh_NOmem by qh_memfree_() */
    +  vertexT *vertex;
    +  mergeType mergetype;
    +  int numcoplanar=0, numconcave=0, numdegenredun= 0, numnewmerges= 0;
    +
    +  trace2((qh, qh->ferr, 2010, "qh_all_merges: starting to merge facets beginning from f%d\n",
    +            getid_(qh->newfacet_list)));
    +  while (True) {
    +    wasmerge= False;
    +    while (qh_setsize(qh, qh->facet_mergeset)) {
    +      while ((merge= (mergeT*)qh_setdellast(qh->facet_mergeset))) {
    +        facet1= merge->facet1;
    +        facet2= merge->facet2;
    +        mergetype= merge->type;
    +        qh_memfree_(qh, merge, (int)sizeof(mergeT), freelistp);
    +        if (facet1->visible || facet2->visible) /*deleted facet*/
    +          continue;
    +        if ((facet1->newfacet && !facet1->tested)
    +                || (facet2->newfacet && !facet2->tested)) {
    +          if (qh->MERGEindependent && mergetype <= MRGanglecoplanar)
    +            continue;      /* perform independent sets of merges */
    +        }
    +        qh_merge_nonconvex(qh, facet1, facet2, mergetype);
    +        numdegenredun += qh_merge_degenredundant(qh);
    +        numnewmerges++;
    +        wasmerge= True;
    +        if (mergetype == MRGconcave)
    +          numconcave++;
    +        else /* MRGcoplanar or MRGanglecoplanar */
    +          numcoplanar++;
    +      } /* while setdellast */
    +      if (qh->POSTmerging && qh->hull_dim <= qh_DIMreduceBuild
    +      && numnewmerges > qh_MAXnewmerges) {
    +        numnewmerges= 0;
    +        qh_reducevertices(qh);  /* otherwise large post merges too slow */
    +      }
    +      qh_getmergeset(qh, qh->newfacet_list); /* facet_mergeset */
    +    } /* while mergeset */
    +    if (qh->VERTEXneighbors) {
    +      isreduce= False;
    +      if (qh->hull_dim >=4 && qh->POSTmerging) {
    +        FORALLvertices
    +          vertex->delridge= True;
    +        isreduce= True;
    +      }
    +      if ((wasmerge || othermerge) && (!qh->MERGEexact || qh->POSTmerging)
    +          && qh->hull_dim <= qh_DIMreduceBuild) {
    +        othermerge= False;
    +        isreduce= True;
    +      }
    +      if (isreduce) {
    +        if (qh_reducevertices(qh)) {
    +          qh_getmergeset(qh, qh->newfacet_list); /* facet_mergeset */
    +          continue;
    +        }
    +      }
    +    }
    +    if (vneighbors && qh_test_vneighbors(qh /* qh->newfacet_list */))
    +      continue;
    +    break;
    +  } /* while (True) */
    +  if (qh->CHECKfrequently && !qh->MERGEexact) {
    +    qh->old_randomdist= qh->RANDOMdist;
    +    qh->RANDOMdist= False;
    +    qh_checkconvex(qh, qh->newfacet_list, qh_ALGORITHMfault);
    +    /* qh_checkconnect(qh); [this is slow and it changes the facet order] */
    +    qh->RANDOMdist= qh->old_randomdist;
    +  }
    +  trace1((qh, qh->ferr, 1009, "qh_all_merges: merged %d coplanar facets %d concave facets and %d degen or redundant facets.\n",
    +    numcoplanar, numconcave, numdegenredun));
    +  if (qh->IStracing >= 4 && qh->num_facets < 50)
    +    qh_printlists(qh);
    +} /* all_merges */
    +
    +
    +/*---------------------------------
    +
    +  qh_appendmergeset(qh, facet, neighbor, mergetype, angle )
    +    appends an entry to qh.facet_mergeset or qh.degen_mergeset
    +
    +    angle ignored if NULL or !qh.ANGLEmerge
    +
    +  returns:
    +    merge appended to facet_mergeset or degen_mergeset
    +      sets ->degenerate or ->redundant if degen_mergeset
    +
    +  see:
    +    qh_test_appendmerge()
    +
    +  design:
    +    allocate merge entry
    +    if regular merge
    +      append to qh.facet_mergeset
    +    else if degenerate merge and qh.facet_mergeset is all degenerate
    +      append to qh.degen_mergeset
    +    else if degenerate merge
    +      prepend to qh.degen_mergeset
    +    else if redundant merge
    +      append to qh.degen_mergeset
    +*/
    +void qh_appendmergeset(qhT *qh, facetT *facet, facetT *neighbor, mergeType mergetype, realT *angle) {
    +  mergeT *merge, *lastmerge;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  if (facet->redundant)
    +    return;
    +  if (facet->degenerate && mergetype == MRGdegen)
    +    return;
    +  qh_memalloc_(qh, (int)sizeof(mergeT), freelistp, merge, mergeT);
    +  merge->facet1= facet;
    +  merge->facet2= neighbor;
    +  merge->type= mergetype;
    +  if (angle && qh->ANGLEmerge)
    +    merge->angle= *angle;
    +  if (mergetype < MRGdegen)
    +    qh_setappend(qh, &(qh->facet_mergeset), merge);
    +  else if (mergetype == MRGdegen) {
    +    facet->degenerate= True;
    +    if (!(lastmerge= (mergeT*)qh_setlast(qh->degen_mergeset))
    +    || lastmerge->type == MRGdegen)
    +      qh_setappend(qh, &(qh->degen_mergeset), merge);
    +    else
    +      qh_setaddnth(qh, &(qh->degen_mergeset), 0, merge);
    +  }else if (mergetype == MRGredundant) {
    +    facet->redundant= True;
    +    qh_setappend(qh, &(qh->degen_mergeset), merge);
    +  }else /* mergetype == MRGmirror */ {
    +    if (facet->redundant || neighbor->redundant) {
    +      qh_fprintf(qh, qh->ferr, 6092, "qhull error (qh_appendmergeset): facet f%d or f%d is already a mirrored facet\n",
    +           facet->id, neighbor->id);
    +      qh_errexit2(qh, qh_ERRqhull, facet, neighbor);
    +    }
    +    if (!qh_setequal(facet->vertices, neighbor->vertices)) {
    +      qh_fprintf(qh, qh->ferr, 6093, "qhull error (qh_appendmergeset): mirrored facets f%d and f%d do not have the same vertices\n",
    +           facet->id, neighbor->id);
    +      qh_errexit2(qh, qh_ERRqhull, facet, neighbor);
    +    }
    +    facet->redundant= True;
    +    neighbor->redundant= True;
    +    qh_setappend(qh, &(qh->degen_mergeset), merge);
    +  }
    +} /* appendmergeset */
    +
    +
    +/*---------------------------------
    +
    +  qh_basevertices(qh, samecycle )
    +    return temporary set of base vertices for samecycle
    +    samecycle is first facet in the cycle
    +    assumes apex is SETfirst_( samecycle->vertices )
    +
    +  returns:
    +    vertices(settemp)
    +    all ->seen are cleared
    +
    +  notes:
    +    uses qh_vertex_visit;
    +
    +  design:
    +    for each facet in samecycle
    +      for each unseen vertex in facet->vertices
    +        append to result
    +*/
    +setT *qh_basevertices(qhT *qh, facetT *samecycle) {
    +  facetT *same;
    +  vertexT *apex, *vertex, **vertexp;
    +  setT *vertices= qh_settemp(qh, qh->TEMPsize);
    +
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  apex->visitid= ++qh->vertex_visit;
    +  FORALLsame_cycle_(samecycle) {
    +    if (same->mergeridge)
    +      continue;
    +    FOREACHvertex_(same->vertices) {
    +      if (vertex->visitid != qh->vertex_visit) {
    +        qh_setappend(qh, &vertices, vertex);
    +        vertex->visitid= qh->vertex_visit;
    +        vertex->seen= False;
    +      }
    +    }
    +  }
    +  trace4((qh, qh->ferr, 4019, "qh_basevertices: found %d vertices\n",
    +         qh_setsize(qh, vertices)));
    +  return vertices;
    +} /* basevertices */
    +
    +/*---------------------------------
    +
    +  qh_checkconnect(qh)
    +    check that new facets are connected
    +    new facets are on qh.newfacet_list
    +
    +  notes:
    +    this is slow and it changes the order of the facets
    +    uses qh.visit_id
    +
    +  design:
    +    move first new facet to end of qh.facet_list
    +    for all newly appended facets
    +      append unvisited neighbors to end of qh.facet_list
    +    for all new facets
    +      report error if unvisited
    +*/
    +void qh_checkconnect(qhT *qh /* qh->newfacet_list */) {
    +  facetT *facet, *newfacet, *errfacet= NULL, *neighbor, **neighborp;
    +
    +  facet= qh->newfacet_list;
    +  qh_removefacet(qh, facet);
    +  qh_appendfacet(qh, facet);
    +  facet->visitid= ++qh->visit_id;
    +  FORALLfacet_(facet) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh->visit_id) {
    +        qh_removefacet(qh, neighbor);
    +        qh_appendfacet(qh, neighbor);
    +        neighbor->visitid= qh->visit_id;
    +      }
    +    }
    +  }
    +  FORALLnew_facets {
    +    if (newfacet->visitid == qh->visit_id)
    +      break;
    +    qh_fprintf(qh, qh->ferr, 6094, "qhull error: f%d is not attached to the new facets\n",
    +         newfacet->id);
    +    errfacet= newfacet;
    +  }
    +  if (errfacet)
    +    qh_errexit(qh, qh_ERRqhull, errfacet, NULL);
    +} /* checkconnect */
    +
    +/*---------------------------------
    +
    +  qh_checkzero(qh, testall )
    +    check that facets are clearly convex for qh.DISTround with qh.MERGEexact
    +
    +    if testall,
    +      test all facets for qh.MERGEexact post-merging
    +    else
    +      test qh.newfacet_list
    +
    +    if qh.MERGEexact,
    +      allows coplanar ridges
    +      skips convexity test while qh.ZEROall_ok
    +
    +  returns:
    +    True if all facets !flipped, !dupridge, normal
    +         if all horizon facets are simplicial
    +         if all vertices are clearly below neighbor
    +         if all opposite vertices of horizon are below
    +    clears qh.ZEROall_ok if any problems or coplanar facets
    +
    +  notes:
    +    uses qh.vertex_visit
    +    horizon facets may define multiple new facets
    +
    +  design:
    +    for all facets in qh.newfacet_list or qh.facet_list
    +      check for flagged faults (flipped, etc.)
    +    for all facets in qh.newfacet_list or qh.facet_list
    +      for each neighbor of facet
    +        skip horizon facets for qh.newfacet_list
    +        test the opposite vertex
    +      if qh.newfacet_list
    +        test the other vertices in the facet's horizon facet
    +*/
    +boolT qh_checkzero(qhT *qh, boolT testall) {
    +  facetT *facet, *neighbor, **neighborp;
    +  facetT *horizon, *facetlist;
    +  int neighbor_i;
    +  vertexT *vertex, **vertexp;
    +  realT dist;
    +
    +  if (testall)
    +    facetlist= qh->facet_list;
    +  else {
    +    facetlist= qh->newfacet_list;
    +    FORALLfacet_(facetlist) {
    +      horizon= SETfirstt_(facet->neighbors, facetT);
    +      if (!horizon->simplicial)
    +        goto LABELproblem;
    +      if (facet->flipped || facet->dupridge || !facet->normal)
    +        goto LABELproblem;
    +    }
    +    if (qh->MERGEexact && qh->ZEROall_ok) {
    +      trace2((qh, qh->ferr, 2011, "qh_checkzero: skip convexity check until first pre-merge\n"));
    +      return True;
    +    }
    +  }
    +  FORALLfacet_(facetlist) {
    +    qh->vertex_visit++;
    +    neighbor_i= 0;
    +    horizon= NULL;
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor_i && !testall) {
    +        horizon= neighbor;
    +        neighbor_i++;
    +        continue; /* horizon facet tested in qh_findhorizon */
    +      }
    +      vertex= SETelemt_(facet->vertices, neighbor_i++, vertexT);
    +      vertex->visitid= qh->vertex_visit;
    +      zzinc_(Zdistzero);
    +      qh_distplane(qh, vertex->point, neighbor, &dist);
    +      if (dist >= -qh->DISTround) {
    +        qh->ZEROall_ok= False;
    +        if (!qh->MERGEexact || testall || dist > qh->DISTround)
    +          goto LABELnonconvex;
    +      }
    +    }
    +    if (!testall && horizon) {
    +      FOREACHvertex_(horizon->vertices) {
    +        if (vertex->visitid != qh->vertex_visit) {
    +          zzinc_(Zdistzero);
    +          qh_distplane(qh, vertex->point, facet, &dist);
    +          if (dist >= -qh->DISTround) {
    +            qh->ZEROall_ok= False;
    +            if (!qh->MERGEexact || dist > qh->DISTround)
    +              goto LABELnonconvex;
    +          }
    +          break;
    +        }
    +      }
    +    }
    +  }
    +  trace2((qh, qh->ferr, 2012, "qh_checkzero: testall %d, facets are %s\n", testall,
    +        (qh->MERGEexact && !testall) ?
    +           "not concave, flipped, or duplicate ridged" : "clearly convex"));
    +  return True;
    +
    + LABELproblem:
    +  qh->ZEROall_ok= False;
    +  trace2((qh, qh->ferr, 2013, "qh_checkzero: facet f%d needs pre-merging\n",
    +       facet->id));
    +  return False;
    +
    + LABELnonconvex:
    +  trace2((qh, qh->ferr, 2014, "qh_checkzero: facet f%d and f%d are not clearly convex.  v%d dist %.2g\n",
    +         facet->id, neighbor->id, vertex->id, dist));
    +  return False;
    +} /* checkzero */
    +
    +/*---------------------------------
    +
    +  qh_compareangle(angle1, angle2 )
    +    used by qsort() to order merges by angle
    +*/
    +int qh_compareangle(const void *p1, const void *p2) {
    +  const mergeT *a= *((mergeT *const*)p1), *b= *((mergeT *const*)p2);
    +
    +  return((a->angle > b->angle) ? 1 : -1);
    +} /* compareangle */
    +
    +/*---------------------------------
    +
    +  qh_comparemerge(merge1, merge2 )
    +    used by qsort() to order merges
    +*/
    +int qh_comparemerge(const void *p1, const void *p2) {
    +  const mergeT *a= *((mergeT *const*)p1), *b= *((mergeT *const*)p2);
    +
    +  return(a->type - b->type);
    +} /* comparemerge */
    +
    +/*---------------------------------
    +
    +  qh_comparevisit(vertex1, vertex2 )
    +    used by qsort() to order vertices by their visitid
    +*/
    +int qh_comparevisit(const void *p1, const void *p2) {
    +  const vertexT *a= *((vertexT *const*)p1), *b= *((vertexT *const*)p2);
    +
    +  return(a->visitid - b->visitid);
    +} /* comparevisit */
    +
    +/*---------------------------------
    +
    +  qh_copynonconvex(qh, atridge )
    +    set non-convex flag on other ridges (if any) between same neighbors
    +
    +  notes:
    +    may be faster if use smaller ridge set
    +
    +  design:
    +    for each ridge of atridge's top facet
    +      if ridge shares the same neighbor
    +        set nonconvex flag
    +*/
    +void qh_copynonconvex(qhT *qh, ridgeT *atridge) {
    +  facetT *facet, *otherfacet;
    +  ridgeT *ridge, **ridgep;
    +
    +  facet= atridge->top;
    +  otherfacet= atridge->bottom;
    +  FOREACHridge_(facet->ridges) {
    +    if (otherfacet == otherfacet_(ridge, facet) && ridge != atridge) {
    +      ridge->nonconvex= True;
    +      trace4((qh, qh->ferr, 4020, "qh_copynonconvex: moved nonconvex flag from r%d to r%d\n",
    +              atridge->id, ridge->id));
    +      break;
    +    }
    +  }
    +} /* copynonconvex */
    +
    +/*---------------------------------
    +
    +  qh_degen_redundant_facet(qh, facet )
    +    check facet for degen. or redundancy
    +
    +  notes:
    +    bumps vertex_visit
    +    called if a facet was redundant but no longer is (qh_merge_degenredundant)
    +    qh_appendmergeset() only appends first reference to facet (i.e., redundant)
    +
    +  see:
    +    qh_degen_redundant_neighbors()
    +
    +  design:
    +    test for redundant neighbor
    +    test for degenerate facet
    +*/
    +void qh_degen_redundant_facet(qhT *qh, facetT *facet) {
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +
    +  trace4((qh, qh->ferr, 4021, "qh_degen_redundant_facet: test facet f%d for degen/redundant\n",
    +          facet->id));
    +  FOREACHneighbor_(facet) {
    +    qh->vertex_visit++;
    +    FOREACHvertex_(neighbor->vertices)
    +      vertex->visitid= qh->vertex_visit;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh->vertex_visit)
    +        break;
    +    }
    +    if (!vertex) {
    +      qh_appendmergeset(qh, facet, neighbor, MRGredundant, NULL);
    +      trace2((qh, qh->ferr, 2015, "qh_degen_redundant_facet: f%d is contained in f%d.  merge\n", facet->id, neighbor->id));
    +      return;
    +    }
    +  }
    +  if (qh_setsize(qh, facet->neighbors) < qh->hull_dim) {
    +    qh_appendmergeset(qh, facet, facet, MRGdegen, NULL);
    +    trace2((qh, qh->ferr, 2016, "qh_degen_redundant_neighbors: f%d is degenerate.\n", facet->id));
    +  }
    +} /* degen_redundant_facet */
    +
    +
    +/*---------------------------------
    +
    +  qh_degen_redundant_neighbors(qh, facet, delfacet,  )
    +    append degenerate and redundant neighbors to facet_mergeset
    +    if delfacet,
    +      only checks neighbors of both delfacet and facet
    +    also checks current facet for degeneracy
    +
    +  notes:
    +    bumps vertex_visit
    +    called for each qh_mergefacet() and qh_mergecycle()
    +    merge and statistics occur in merge_nonconvex
    +    qh_appendmergeset() only appends first reference to facet (i.e., redundant)
    +      it appends redundant facets after degenerate ones
    +
    +    a degenerate facet has fewer than hull_dim neighbors
    +    a redundant facet's vertices is a subset of its neighbor's vertices
    +    tests for redundant merges first (appendmergeset is nop for others)
    +    in a merge, only needs to test neighbors of merged facet
    +
    +  see:
    +    qh_merge_degenredundant() and qh_degen_redundant_facet()
    +
    +  design:
    +    test for degenerate facet
    +    test for redundant neighbor
    +    test for degenerate neighbor
    +*/
    +void qh_degen_redundant_neighbors(qhT *qh, facetT *facet, facetT *delfacet) {
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +  int size;
    +
    +  trace4((qh, qh->ferr, 4022, "qh_degen_redundant_neighbors: test neighbors of f%d with delfacet f%d\n",
    +          facet->id, getid_(delfacet)));
    +  if ((size= qh_setsize(qh, facet->neighbors)) < qh->hull_dim) {
    +    qh_appendmergeset(qh, facet, facet, MRGdegen, NULL);
    +    trace2((qh, qh->ferr, 2017, "qh_degen_redundant_neighbors: f%d is degenerate with %d neighbors.\n", facet->id, size));
    +  }
    +  if (!delfacet)
    +    delfacet= facet;
    +  qh->vertex_visit++;
    +  FOREACHvertex_(facet->vertices)
    +    vertex->visitid= qh->vertex_visit;
    +  FOREACHneighbor_(delfacet) {
    +    /* uses early out instead of checking vertex count */
    +    if (neighbor == facet)
    +      continue;
    +    FOREACHvertex_(neighbor->vertices) {
    +      if (vertex->visitid != qh->vertex_visit)
    +        break;
    +    }
    +    if (!vertex) {
    +      qh_appendmergeset(qh, neighbor, facet, MRGredundant, NULL);
    +      trace2((qh, qh->ferr, 2018, "qh_degen_redundant_neighbors: f%d is contained in f%d.  merge\n", neighbor->id, facet->id));
    +    }
    +  }
    +  FOREACHneighbor_(delfacet) {   /* redundant merges occur first */
    +    if (neighbor == facet)
    +      continue;
    +    if ((size= qh_setsize(qh, neighbor->neighbors)) < qh->hull_dim) {
    +      qh_appendmergeset(qh, neighbor, neighbor, MRGdegen, NULL);
    +      trace2((qh, qh->ferr, 2019, "qh_degen_redundant_neighbors: f%d is degenerate with %d neighbors.  Neighbor of f%d.\n", neighbor->id, size, facet->id));
    +    }
    +  }
    +} /* degen_redundant_neighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_find_newvertex(qh, oldvertex, vertices, ridges )
    +    locate new vertex for renaming old vertex
    +    vertices is a set of possible new vertices
    +      vertices sorted by number of deleted ridges
    +
    +  returns:
    +    newvertex or NULL
    +      each ridge includes both vertex and oldvertex
    +    vertices sorted by number of deleted ridges
    +
    +  notes:
    +    modifies vertex->visitid
    +    new vertex is in one of the ridges
    +    renaming will not cause a duplicate ridge
    +    renaming will minimize the number of deleted ridges
    +    newvertex may not be adjacent in the dual (though unlikely)
    +
    +  design:
    +    for each vertex in vertices
    +      set vertex->visitid to number of references in ridges
    +    remove unvisited vertices
    +    set qh.vertex_visit above all possible values
    +    sort vertices by number of references in ridges
    +    add each ridge to qh.hash_table
    +    for each vertex in vertices
    +      look for a vertex that would not cause a duplicate ridge after a rename
    +*/
    +vertexT *qh_find_newvertex(qhT *qh, vertexT *oldvertex, setT *vertices, setT *ridges) {
    +  vertexT *vertex, **vertexp;
    +  setT *newridges;
    +  ridgeT *ridge, **ridgep;
    +  int size, hashsize;
    +  int hash;
    +
    +#ifndef qh_NOtrace
    +  if (qh->IStracing >= 4) {
    +    qh_fprintf(qh, qh->ferr, 8063, "qh_find_newvertex: find new vertex for v%d from ",
    +             oldvertex->id);
    +    FOREACHvertex_(vertices)
    +      qh_fprintf(qh, qh->ferr, 8064, "v%d ", vertex->id);
    +    FOREACHridge_(ridges)
    +      qh_fprintf(qh, qh->ferr, 8065, "r%d ", ridge->id);
    +    qh_fprintf(qh, qh->ferr, 8066, "\n");
    +  }
    +#endif
    +  FOREACHvertex_(vertices)
    +    vertex->visitid= 0;
    +  FOREACHridge_(ridges) {
    +    FOREACHvertex_(ridge->vertices)
    +      vertex->visitid++;
    +  }
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->visitid) {
    +      qh_setdelnth(qh, vertices, SETindex_(vertices,vertex));
    +      vertexp--; /* repeat since deleted this vertex */
    +    }
    +  }
    +  qh->vertex_visit += (unsigned int)qh_setsize(qh, ridges);
    +  if (!qh_setsize(qh, vertices)) {
    +    trace4((qh, qh->ferr, 4023, "qh_find_newvertex: vertices not in ridges for v%d\n",
    +            oldvertex->id));
    +    return NULL;
    +  }
    +  qsort(SETaddr_(vertices, vertexT), (size_t)qh_setsize(qh, vertices),
    +                sizeof(vertexT *), qh_comparevisit);
    +  /* can now use qh->vertex_visit */
    +  if (qh->PRINTstatistics) {
    +    size= qh_setsize(qh, vertices);
    +    zinc_(Zintersect);
    +    zadd_(Zintersecttot, size);
    +    zmax_(Zintersectmax, size);
    +  }
    +  hashsize= qh_newhashtable(qh, qh_setsize(qh, ridges));
    +  FOREACHridge_(ridges)
    +    qh_hashridge(qh, qh->hash_table, hashsize, ridge, oldvertex);
    +  FOREACHvertex_(vertices) {
    +    newridges= qh_vertexridges(qh, vertex);
    +    FOREACHridge_(newridges) {
    +      if (qh_hashridge_find(qh, qh->hash_table, hashsize, ridge, vertex, oldvertex, &hash)) {
    +        zinc_(Zdupridge);
    +        break;
    +      }
    +    }
    +    qh_settempfree(qh, &newridges);
    +    if (!ridge)
    +      break;  /* found a rename */
    +  }
    +  if (vertex) {
    +    /* counted in qh_renamevertex */
    +    trace2((qh, qh->ferr, 2020, "qh_find_newvertex: found v%d for old v%d from %d vertices and %d ridges.\n",
    +      vertex->id, oldvertex->id, qh_setsize(qh, vertices), qh_setsize(qh, ridges)));
    +  }else {
    +    zinc_(Zfindfail);
    +    trace0((qh, qh->ferr, 14, "qh_find_newvertex: no vertex for renaming v%d(all duplicated ridges) during p%d\n",
    +      oldvertex->id, qh->furthest_id));
    +  }
    +  qh_setfree(qh, &qh->hash_table);
    +  return vertex;
    +} /* find_newvertex */
    +
    +/*---------------------------------
    +
    +  qh_findbest_test(qh, testcentrum, facet, neighbor, bestfacet, dist, mindist, maxdist )
    +    test neighbor of facet for qh_findbestneighbor()
    +    if testcentrum,
    +      tests centrum (assumes it is defined)
    +    else
    +      tests vertices
    +
    +  returns:
    +    if a better facet (i.e., vertices/centrum of facet closer to neighbor)
    +      updates bestfacet, dist, mindist, and maxdist
    +*/
    +void qh_findbest_test(qhT *qh, boolT testcentrum, facetT *facet, facetT *neighbor,
    +      facetT **bestfacet, realT *distp, realT *mindistp, realT *maxdistp) {
    +  realT dist, mindist, maxdist;
    +
    +  if (testcentrum) {
    +    zzinc_(Zbestdist);
    +    qh_distplane(qh, facet->center, neighbor, &dist);
    +    dist *= qh->hull_dim; /* estimate furthest vertex */
    +    if (dist < 0) {
    +      maxdist= 0;
    +      mindist= dist;
    +      dist= -dist;
    +    }else {
    +      mindist= 0;
    +      maxdist= dist;
    +    }
    +  }else
    +    dist= qh_getdistance(qh, facet, neighbor, &mindist, &maxdist);
    +  if (dist < *distp) {
    +    *bestfacet= neighbor;
    +    *mindistp= mindist;
    +    *maxdistp= maxdist;
    +    *distp= dist;
    +  }
    +} /* findbest_test */
    +
    +/*---------------------------------
    +
    +  qh_findbestneighbor(qh, facet, dist, mindist, maxdist )
    +    finds best neighbor (least dist) of a facet for merging
    +
    +  returns:
    +    returns min and max distances and their max absolute value
    +
    +  notes:
    +    error if qh_ASvoronoi
    +    avoids merging old into new
    +    assumes ridge->nonconvex only set on one ridge between a pair of facets
    +    could use an early out predicate but not worth it
    +
    +  design:
    +    if a large facet
    +      will test centrum
    +    else
    +      will test vertices
    +    if a large facet
    +      test nonconvex neighbors for best merge
    +    else
    +      test all neighbors for the best merge
    +    if testing centrum
    +      get distance information
    +*/
    +facetT *qh_findbestneighbor(qhT *qh, facetT *facet, realT *distp, realT *mindistp, realT *maxdistp) {
    +  facetT *neighbor, **neighborp, *bestfacet= NULL;
    +  ridgeT *ridge, **ridgep;
    +  boolT nonconvex= True, testcentrum= False;
    +  int size= qh_setsize(qh, facet->vertices);
    +
    +  if(qh->CENTERtype==qh_ASvoronoi){
    +    qh_fprintf(qh, qh->ferr, 6272, "qhull error: cannot call qh_findbestneighor for f%d while qh.CENTERtype is qh_ASvoronoi\n", facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  *distp= REALmax;
    +  if (size > qh_BESTcentrum2 * qh->hull_dim + qh_BESTcentrum) {
    +    testcentrum= True;
    +    zinc_(Zbestcentrum);
    +    if (!facet->center)
    +       facet->center= qh_getcentrum(qh, facet);
    +  }
    +  if (size > qh->hull_dim + qh_BESTnonconvex) {
    +    FOREACHridge_(facet->ridges) {
    +      if (ridge->nonconvex) {
    +        neighbor= otherfacet_(ridge, facet);
    +        qh_findbest_test(qh, testcentrum, facet, neighbor,
    +                          &bestfacet, distp, mindistp, maxdistp);
    +      }
    +    }
    +  }
    +  if (!bestfacet) {
    +    nonconvex= False;
    +    FOREACHneighbor_(facet)
    +      qh_findbest_test(qh, testcentrum, facet, neighbor,
    +                        &bestfacet, distp, mindistp, maxdistp);
    +  }
    +  if (!bestfacet) {
    +    qh_fprintf(qh, qh->ferr, 6095, "qhull internal error (qh_findbestneighbor): no neighbors for f%d\n", facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  if (testcentrum)
    +    qh_getdistance(qh, facet, bestfacet, mindistp, maxdistp);
    +  trace3((qh, qh->ferr, 3002, "qh_findbestneighbor: f%d is best neighbor for f%d testcentrum? %d nonconvex? %d dist %2.2g min %2.2g max %2.2g\n",
    +     bestfacet->id, facet->id, testcentrum, nonconvex, *distp, *mindistp, *maxdistp));
    +  return(bestfacet);
    +} /* findbestneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_flippedmerges(qh, facetlist, wasmerge )
    +    merge flipped facets into best neighbor
    +    assumes qh.facet_mergeset at top of temporary stack
    +
    +  returns:
    +    no flipped facets on facetlist
    +    sets wasmerge if merge occurred
    +    degen/redundant merges passed through
    +
    +  notes:
    +    othermerges not needed since qh.facet_mergeset is empty before & after
    +      keep it in case of change
    +
    +  design:
    +    append flipped facets to qh.facetmergeset
    +    for each flipped merge
    +      find best neighbor
    +      merge facet into neighbor
    +      merge degenerate and redundant facets
    +    remove flipped merges from qh.facet_mergeset
    +*/
    +void qh_flippedmerges(qhT *qh, facetT *facetlist, boolT *wasmerge) {
    +  facetT *facet, *neighbor, *facet1;
    +  realT dist, mindist, maxdist;
    +  mergeT *merge, **mergep;
    +  setT *othermerges;
    +  int nummerge=0;
    +
    +  trace4((qh, qh->ferr, 4024, "qh_flippedmerges: begin\n"));
    +  FORALLfacet_(facetlist) {
    +    if (facet->flipped && !facet->visible)
    +      qh_appendmergeset(qh, facet, facet, MRGflip, NULL);
    +  }
    +  othermerges= qh_settemppop(qh); /* was facet_mergeset */
    +  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh_settemppush(qh, othermerges);
    +  FOREACHmerge_(othermerges) {
    +    facet1= merge->facet1;
    +    if (merge->type != MRGflip || facet1->visible)
    +      continue;
    +    if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +      qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +    neighbor= qh_findbestneighbor(qh, facet1, &dist, &mindist, &maxdist);
    +    trace0((qh, qh->ferr, 15, "qh_flippedmerges: merge flipped f%d into f%d dist %2.2g during p%d\n",
    +      facet1->id, neighbor->id, dist, qh->furthest_id));
    +    qh_mergefacet(qh, facet1, neighbor, &mindist, &maxdist, !qh_MERGEapex);
    +    nummerge++;
    +    if (qh->PRINTstatistics) {
    +      zinc_(Zflipped);
    +      wadd_(Wflippedtot, dist);
    +      wmax_(Wflippedmax, dist);
    +    }
    +    qh_merge_degenredundant(qh);
    +  }
    +  FOREACHmerge_(othermerges) {
    +    if (merge->facet1->visible || merge->facet2->visible)
    +      qh_memfree(qh, merge, (int)sizeof(mergeT));
    +    else
    +      qh_setappend(qh, &qh->facet_mergeset, merge);
    +  }
    +  qh_settempfree(qh, &othermerges);
    +  if (nummerge)
    +    *wasmerge= True;
    +  trace1((qh, qh->ferr, 1010, "qh_flippedmerges: merged %d flipped facets into a good neighbor\n", nummerge));
    +} /* flippedmerges */
    +
    +
    +/*---------------------------------
    +
    +  qh_forcedmerges(qh, wasmerge )
    +    merge duplicated ridges
    +
    +  returns:
    +    removes all duplicate ridges on facet_mergeset
    +    wasmerge set if merge
    +    qh.facet_mergeset may include non-forced merges(none for now)
    +    qh.degen_mergeset includes degen/redun merges
    +
    +  notes:
    +    duplicate ridges occur when the horizon is pinched,
    +        i.e. a subridge occurs in more than two horizon ridges.
    +     could rename vertices that pinch the horizon
    +    assumes qh_merge_degenredundant() has not be called
    +    othermerges isn't needed since facet_mergeset is empty afterwards
    +      keep it in case of change
    +
    +  design:
    +    for each duplicate ridge
    +      find current facets by chasing f.replace links
    +      check for wide merge due to duplicate ridge
    +      determine best direction for facet
    +      merge one facet into the other
    +      remove duplicate ridges from qh.facet_mergeset
    +*/
    +void qh_forcedmerges(qhT *qh, boolT *wasmerge) {
    +  facetT *facet1, *facet2;
    +  mergeT *merge, **mergep;
    +  realT dist1, dist2, mindist1, mindist2, maxdist1, maxdist2;
    +  setT *othermerges;
    +  int nummerge=0, numflip=0;
    +
    +  if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +    qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +  trace4((qh, qh->ferr, 4025, "qh_forcedmerges: begin\n"));
    +  othermerges= qh_settemppop(qh); /* was facet_mergeset */
    +  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh_settemppush(qh, othermerges);
    +  FOREACHmerge_(othermerges) {
    +    if (merge->type != MRGridge)
    +        continue;
    +    if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +        qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    while (facet1->visible)      /* must exist, no qh_merge_degenredunant */
    +      facet1= facet1->f.replace; /* previously merged facet */
    +    while (facet2->visible)
    +      facet2= facet2->f.replace; /* previously merged facet */
    +    if (facet1 == facet2)
    +      continue;
    +    if (!qh_setin(facet2->neighbors, facet1)) {
    +      qh_fprintf(qh, qh->ferr, 6096, "qhull internal error (qh_forcedmerges): f%d and f%d had a duplicate ridge but as f%d and f%d they are no longer neighbors\n",
    +               merge->facet1->id, merge->facet2->id, facet1->id, facet2->id);
    +      qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +    }
    +    dist1= qh_getdistance(qh, facet1, facet2, &mindist1, &maxdist1);
    +    dist2= qh_getdistance(qh, facet2, facet1, &mindist2, &maxdist2);
    +    qh_check_dupridge(qh, facet1, dist1, facet2, dist2);
    +    if (dist1 < dist2)
    +      qh_mergefacet(qh, facet1, facet2, &mindist1, &maxdist1, !qh_MERGEapex);
    +    else {
    +      qh_mergefacet(qh, facet2, facet1, &mindist2, &maxdist2, !qh_MERGEapex);
    +      dist1= dist2;
    +      facet1= facet2;
    +    }
    +    if (facet1->flipped) {
    +      zinc_(Zmergeflipdup);
    +      numflip++;
    +    }else
    +      nummerge++;
    +    if (qh->PRINTstatistics) {
    +      zinc_(Zduplicate);
    +      wadd_(Wduplicatetot, dist1);
    +      wmax_(Wduplicatemax, dist1);
    +    }
    +  }
    +  FOREACHmerge_(othermerges) {
    +    if (merge->type == MRGridge)
    +      qh_memfree(qh, merge, (int)sizeof(mergeT));
    +    else
    +      qh_setappend(qh, &qh->facet_mergeset, merge);
    +  }
    +  qh_settempfree(qh, &othermerges);
    +  if (nummerge)
    +    *wasmerge= True;
    +  trace1((qh, qh->ferr, 1011, "qh_forcedmerges: merged %d facets and %d flipped facets across duplicated ridges\n",
    +                nummerge, numflip));
    +} /* forcedmerges */
    +
    +
    +/*---------------------------------
    +
    +  qh_getmergeset(qh, facetlist )
    +    determines nonconvex facets on facetlist
    +    tests !tested ridges and nonconvex ridges of !tested facets
    +
    +  returns:
    +    returns sorted qh.facet_mergeset of facet-neighbor pairs to be merged
    +    all ridges tested
    +
    +  notes:
    +    assumes no nonconvex ridges with both facets tested
    +    uses facet->tested/ridge->tested to prevent duplicate tests
    +    can not limit tests to modified ridges since the centrum changed
    +    uses qh.visit_id
    +
    +  see:
    +    qh_getmergeset_initial()
    +
    +  design:
    +    for each facet on facetlist
    +      for each ridge of facet
    +        if untested ridge
    +          test ridge for convexity
    +          if non-convex
    +            append ridge to qh.facet_mergeset
    +    sort qh.facet_mergeset by angle
    +*/
    +void qh_getmergeset(qhT *qh, facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int nummerges;
    +
    +  nummerges= qh_setsize(qh, qh->facet_mergeset);
    +  trace4((qh, qh->ferr, 4026, "qh_getmergeset: started.\n"));
    +  qh->visit_id++;
    +  FORALLfacet_(facetlist) {
    +    if (facet->tested)
    +      continue;
    +    facet->visitid= qh->visit_id;
    +    facet->tested= True;  /* must be non-simplicial due to merge */
    +    FOREACHneighbor_(facet)
    +      neighbor->seen= False;
    +    FOREACHridge_(facet->ridges) {
    +      if (ridge->tested && !ridge->nonconvex)
    +        continue;
    +      /* if tested & nonconvex, need to append merge */
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->seen) {
    +        ridge->tested= True;
    +        ridge->nonconvex= False;
    +      }else if (neighbor->visitid != qh->visit_id) {
    +        ridge->tested= True;
    +        ridge->nonconvex= False;
    +        neighbor->seen= True;      /* only one ridge is marked nonconvex */
    +        if (qh_test_appendmerge(qh, facet, neighbor))
    +          ridge->nonconvex= True;
    +      }
    +    }
    +  }
    +  nummerges= qh_setsize(qh, qh->facet_mergeset);
    +  if (qh->ANGLEmerge)
    +    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compareangle);
    +  else
    +    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_comparemerge);
    +  if (qh->POSTmerging) {
    +    zadd_(Zmergesettot2, nummerges);
    +  }else {
    +    zadd_(Zmergesettot, nummerges);
    +    zmax_(Zmergesetmax, nummerges);
    +  }
    +  trace2((qh, qh->ferr, 2021, "qh_getmergeset: %d merges found\n", nummerges));
    +} /* getmergeset */
    +
    +
    +/*---------------------------------
    +
    +  qh_getmergeset_initial(qh, facetlist )
    +    determine initial qh.facet_mergeset for facets
    +    tests all facet/neighbor pairs on facetlist
    +
    +  returns:
    +    sorted qh.facet_mergeset with nonconvex ridges
    +    sets facet->tested, ridge->tested, and ridge->nonconvex
    +
    +  notes:
    +    uses visit_id, assumes ridge->nonconvex is False
    +
    +  see:
    +    qh_getmergeset()
    +
    +  design:
    +    for each facet on facetlist
    +      for each untested neighbor of facet
    +        test facet and neighbor for convexity
    +        if non-convex
    +          append merge to qh.facet_mergeset
    +          mark one of the ridges as nonconvex
    +    sort qh.facet_mergeset by angle
    +*/
    +void qh_getmergeset_initial(qhT *qh, facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int nummerges;
    +
    +  qh->visit_id++;
    +  FORALLfacet_(facetlist) {
    +    facet->visitid= qh->visit_id;
    +    facet->tested= True;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh->visit_id) {
    +        if (qh_test_appendmerge(qh, facet, neighbor)) {
    +          FOREACHridge_(neighbor->ridges) {
    +            if (facet == otherfacet_(ridge, neighbor)) {
    +              ridge->nonconvex= True;
    +              break;    /* only one ridge is marked nonconvex */
    +            }
    +          }
    +        }
    +      }
    +    }
    +    FOREACHridge_(facet->ridges)
    +      ridge->tested= True;
    +  }
    +  nummerges= qh_setsize(qh, qh->facet_mergeset);
    +  if (qh->ANGLEmerge)
    +    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compareangle);
    +  else
    +    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_comparemerge);
    +  if (qh->POSTmerging) {
    +    zadd_(Zmergeinittot2, nummerges);
    +  }else {
    +    zadd_(Zmergeinittot, nummerges);
    +    zmax_(Zmergeinitmax, nummerges);
    +  }
    +  trace2((qh, qh->ferr, 2022, "qh_getmergeset_initial: %d merges found\n", nummerges));
    +} /* getmergeset_initial */
    +
    +
    +/*---------------------------------
    +
    +  qh_hashridge(qh, hashtable, hashsize, ridge, oldvertex )
    +    add ridge to hashtable without oldvertex
    +
    +  notes:
    +    assumes hashtable is large enough
    +
    +  design:
    +    determine hash value for ridge without oldvertex
    +    find next empty slot for ridge
    +*/
    +void qh_hashridge(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge, vertexT *oldvertex) {
    +  int hash;
    +  ridgeT *ridgeA;
    +
    +  hash= qh_gethash(qh, hashsize, ridge->vertices, qh->hull_dim-1, 0, oldvertex);
    +  while (True) {
    +    if (!(ridgeA= SETelemt_(hashtable, hash, ridgeT))) {
    +      SETelem_(hashtable, hash)= ridge;
    +      break;
    +    }else if (ridgeA == ridge)
    +      break;
    +    if (++hash == hashsize)
    +      hash= 0;
    +  }
    +} /* hashridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_hashridge_find(qh, hashtable, hashsize, ridge, vertex, oldvertex, hashslot )
    +    returns matching ridge without oldvertex in hashtable
    +      for ridge without vertex
    +    if oldvertex is NULL
    +      matches with any one skip
    +
    +  returns:
    +    matching ridge or NULL
    +    if no match,
    +      if ridge already in   table
    +        hashslot= -1
    +      else
    +        hashslot= next NULL index
    +
    +  notes:
    +    assumes hashtable is large enough
    +    can't match ridge to itself
    +
    +  design:
    +    get hash value for ridge without vertex
    +    for each hashslot
    +      return match if ridge matches ridgeA without oldvertex
    +*/
    +ridgeT *qh_hashridge_find(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge,
    +              vertexT *vertex, vertexT *oldvertex, int *hashslot) {
    +  int hash;
    +  ridgeT *ridgeA;
    +
    +  *hashslot= 0;
    +  zinc_(Zhashridge);
    +  hash= qh_gethash(qh, hashsize, ridge->vertices, qh->hull_dim-1, 0, vertex);
    +  while ((ridgeA= SETelemt_(hashtable, hash, ridgeT))) {
    +    if (ridgeA == ridge)
    +      *hashslot= -1;
    +    else {
    +      zinc_(Zhashridgetest);
    +      if (qh_setequal_except(ridge->vertices, vertex, ridgeA->vertices, oldvertex))
    +        return ridgeA;
    +    }
    +    if (++hash == hashsize)
    +      hash= 0;
    +  }
    +  if (!*hashslot)
    +    *hashslot= hash;
    +  return NULL;
    +} /* hashridge_find */
    +
    +
    +/*---------------------------------
    +
    +  qh_makeridges(qh, facet )
    +    creates explicit ridges between simplicial facets
    +
    +  returns:
    +    facet with ridges and without qh_MERGEridge
    +    ->simplicial is False
    +
    +  notes:
    +    allows qh_MERGEridge flag
    +    uses existing ridges
    +    duplicate neighbors ok if ridges already exist (qh_mergecycle_ridges)
    +
    +  see:
    +    qh_mergecycle_ridges()
    +
    +  design:
    +    look for qh_MERGEridge neighbors
    +    mark neighbors that already have ridges
    +    for each unprocessed neighbor of facet
    +      create a ridge for neighbor and facet
    +    if any qh_MERGEridge neighbors
    +      delete qh_MERGEridge flags (already handled by qh_mark_dupridges)
    +*/
    +void qh_makeridges(qhT *qh, facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int neighbor_i, neighbor_n;
    +  boolT toporient, mergeridge= False;
    +
    +  if (!facet->simplicial)
    +    return;
    +  trace4((qh, qh->ferr, 4027, "qh_makeridges: make ridges for f%d\n", facet->id));
    +  facet->simplicial= False;
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      mergeridge= True;
    +    else
    +      neighbor->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges)
    +    otherfacet_(ridge, facet)->seen= True;
    +  FOREACHneighbor_i_(qh, facet) {
    +    if (neighbor == qh_MERGEridge)
    +      continue;  /* fixed by qh_mark_dupridges */
    +    else if (!neighbor->seen) {  /* no current ridges */
    +      ridge= qh_newridge(qh);
    +      ridge->vertices= qh_setnew_delnthsorted(qh, facet->vertices, qh->hull_dim,
    +                                                          neighbor_i, 0);
    +      toporient= facet->toporient ^ (neighbor_i & 0x1);
    +      if (toporient) {
    +        ridge->top= facet;
    +        ridge->bottom= neighbor;
    +      }else {
    +        ridge->top= neighbor;
    +        ridge->bottom= facet;
    +      }
    +#if 0 /* this also works */
    +      flip= (facet->toporient ^ neighbor->toporient)^(skip1 & 0x1) ^ (skip2 & 0x1);
    +      if (facet->toporient ^ (skip1 & 0x1) ^ flip) {
    +        ridge->top= neighbor;
    +        ridge->bottom= facet;
    +      }else {
    +        ridge->top= facet;
    +        ridge->bottom= neighbor;
    +      }
    +#endif
    +      qh_setappend(qh, &(facet->ridges), ridge);
    +      qh_setappend(qh, &(neighbor->ridges), ridge);
    +    }
    +  }
    +  if (mergeridge) {
    +    while (qh_setdel(facet->neighbors, qh_MERGEridge))
    +      ; /* delete each one */
    +  }
    +} /* makeridges */
    +
    +
    +/*---------------------------------
    +
    +  qh_mark_dupridges(qh, facetlist )
    +    add duplicated ridges to qh.facet_mergeset
    +    facet->dupridge is true
    +
    +  returns:
    +    duplicate ridges on qh.facet_mergeset
    +    ->mergeridge/->mergeridge2 set
    +    duplicate ridges marked by qh_MERGEridge and both sides facet->dupridge
    +    no MERGEridges in neighbor sets
    +
    +  notes:
    +    duplicate ridges occur when the horizon is pinched,
    +        i.e. a subridge occurs in more than two horizon ridges.
    +    could rename vertices that pinch the horizon (thus removing subridge)
    +    uses qh.visit_id
    +
    +  design:
    +    for all facets on facetlist
    +      if facet contains a duplicate ridge
    +        for each neighbor of facet
    +          if neighbor marked qh_MERGEridge (one side of the merge)
    +            set facet->mergeridge
    +          else
    +            if neighbor contains a duplicate ridge
    +            and the back link is qh_MERGEridge
    +              append duplicate ridge to qh.facet_mergeset
    +   for each duplicate ridge
    +     make ridge sets in preparation for merging
    +     remove qh_MERGEridge from neighbor set
    +   for each duplicate ridge
    +     restore the missing neighbor from the neighbor set that was qh_MERGEridge
    +     add the missing ridge for this neighbor
    +*/
    +void qh_mark_dupridges(qhT *qh, facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  int nummerge=0;
    +  mergeT *merge, **mergep;
    +
    +
    +  trace4((qh, qh->ferr, 4028, "qh_mark_dupridges: identify duplicate ridges\n"));
    +  FORALLfacet_(facetlist) {
    +    if (facet->dupridge) {
    +      FOREACHneighbor_(facet) {
    +        if (neighbor == qh_MERGEridge) {
    +          facet->mergeridge= True;
    +          continue;
    +        }
    +        if (neighbor->dupridge
    +        && !qh_setin(neighbor->neighbors, facet)) { /* qh_MERGEridge */
    +          qh_appendmergeset(qh, facet, neighbor, MRGridge, NULL);
    +          facet->mergeridge2= True;
    +          facet->mergeridge= True;
    +          nummerge++;
    +        }
    +      }
    +    }
    +  }
    +  if (!nummerge)
    +    return;
    +  FORALLfacet_(facetlist) {            /* gets rid of qh_MERGEridge */
    +    if (facet->mergeridge && !facet->mergeridge2)
    +      qh_makeridges(qh, facet);
    +  }
    +  FOREACHmerge_(qh->facet_mergeset) {   /* restore the missing neighbors */
    +    if (merge->type == MRGridge) {
    +      qh_setappend(qh, &merge->facet2->neighbors, merge->facet1);
    +      qh_makeridges(qh, merge->facet1);   /* and the missing ridges */
    +    }
    +  }
    +  trace1((qh, qh->ferr, 1012, "qh_mark_dupridges: found %d duplicated ridges\n",
    +                nummerge));
    +} /* mark_dupridges */
    +
    +/*---------------------------------
    +
    +  qh_maydropneighbor(qh, facet )
    +    drop neighbor relationship if no ridge between facet and neighbor
    +
    +  returns:
    +    neighbor sets updated
    +    appends degenerate facets to qh.facet_mergeset
    +
    +  notes:
    +    won't cause redundant facets since vertex inclusion is the same
    +    may drop vertex and neighbor if no ridge
    +    uses qh.visit_id
    +
    +  design:
    +    visit all neighbors with ridges
    +    for each unvisited neighbor of facet
    +      delete neighbor and facet from the neighbor sets
    +      if neighbor becomes degenerate
    +        append neighbor to qh.degen_mergeset
    +    if facet is degenerate
    +      append facet to qh.degen_mergeset
    +*/
    +void qh_maydropneighbor(qhT *qh, facetT *facet) {
    +  ridgeT *ridge, **ridgep;
    +  realT angledegen= qh_ANGLEdegen;
    +  facetT *neighbor, **neighborp;
    +
    +  qh->visit_id++;
    +  trace4((qh, qh->ferr, 4029, "qh_maydropneighbor: test f%d for no ridges to a neighbor\n",
    +          facet->id));
    +  FOREACHridge_(facet->ridges) {
    +    ridge->top->visitid= qh->visit_id;
    +    ridge->bottom->visitid= qh->visit_id;
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (neighbor->visitid != qh->visit_id) {
    +      trace0((qh, qh->ferr, 17, "qh_maydropneighbor: facets f%d and f%d are no longer neighbors during p%d\n",
    +            facet->id, neighbor->id, qh->furthest_id));
    +      zinc_(Zdropneighbor);
    +      qh_setdel(facet->neighbors, neighbor);
    +      neighborp--;  /* repeat, deleted a neighbor */
    +      qh_setdel(neighbor->neighbors, facet);
    +      if (qh_setsize(qh, neighbor->neighbors) < qh->hull_dim) {
    +        zinc_(Zdropdegen);
    +        qh_appendmergeset(qh, neighbor, neighbor, MRGdegen, &angledegen);
    +        trace2((qh, qh->ferr, 2023, "qh_maydropneighbors: f%d is degenerate.\n", neighbor->id));
    +      }
    +    }
    +  }
    +  if (qh_setsize(qh, facet->neighbors) < qh->hull_dim) {
    +    zinc_(Zdropdegen);
    +    qh_appendmergeset(qh, facet, facet, MRGdegen, &angledegen);
    +    trace2((qh, qh->ferr, 2024, "qh_maydropneighbors: f%d is degenerate.\n", facet->id));
    +  }
    +} /* maydropneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_merge_degenredundant(qh)
    +    merge all degenerate and redundant facets
    +    qh.degen_mergeset contains merges from qh_degen_redundant_neighbors()
    +
    +  returns:
    +    number of merges performed
    +    resets facet->degenerate/redundant
    +    if deleted (visible) facet has no neighbors
    +      sets ->f.replace to NULL
    +
    +  notes:
    +    redundant merges happen before degenerate ones
    +    merging and renaming vertices can result in degen/redundant facets
    +
    +  design:
    +    for each merge on qh.degen_mergeset
    +      if redundant merge
    +        if non-redundant facet merged into redundant facet
    +          recheck facet for redundancy
    +        else
    +          merge redundant facet into other facet
    +*/
    +int qh_merge_degenredundant(qhT *qh) {
    +  int size;
    +  mergeT *merge;
    +  facetT *bestneighbor, *facet1, *facet2;
    +  realT dist, mindist, maxdist;
    +  vertexT *vertex, **vertexp;
    +  int nummerges= 0;
    +  mergeType mergetype;
    +
    +  while ((merge= (mergeT*)qh_setdellast(qh->degen_mergeset))) {
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    mergetype= merge->type;
    +    qh_memfree(qh, merge, (int)sizeof(mergeT));
    +    if (facet1->visible)
    +      continue;
    +    facet1->degenerate= False;
    +    facet1->redundant= False;
    +    if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +      qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +    if (mergetype == MRGredundant) {
    +      zinc_(Zneighbor);
    +      while (facet2->visible) {
    +        if (!facet2->f.replace) {
    +          qh_fprintf(qh, qh->ferr, 6097, "qhull internal error (qh_merge_degenredunant): f%d redundant but f%d has no replacement\n",
    +               facet1->id, facet2->id);
    +          qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +        }
    +        facet2= facet2->f.replace;
    +      }
    +      if (facet1 == facet2) {
    +        qh_degen_redundant_facet(qh, facet1); /* in case of others */
    +        continue;
    +      }
    +      trace2((qh, qh->ferr, 2025, "qh_merge_degenredundant: facet f%d is contained in f%d, will merge\n",
    +            facet1->id, facet2->id));
    +      qh_mergefacet(qh, facet1, facet2, NULL, NULL, !qh_MERGEapex);
    +      /* merge distance is already accounted for */
    +      nummerges++;
    +    }else {  /* mergetype == MRGdegen, other merges may have fixed */
    +      if (!(size= qh_setsize(qh, facet1->neighbors))) {
    +        zinc_(Zdelfacetdup);
    +        trace2((qh, qh->ferr, 2026, "qh_merge_degenredundant: facet f%d has no neighbors.  Deleted\n", facet1->id));
    +        qh_willdelete(qh, facet1, NULL);
    +        FOREACHvertex_(facet1->vertices) {
    +          qh_setdel(vertex->neighbors, facet1);
    +          if (!SETfirst_(vertex->neighbors)) {
    +            zinc_(Zdegenvertex);
    +            trace2((qh, qh->ferr, 2027, "qh_merge_degenredundant: deleted v%d because f%d has no neighbors\n",
    +                 vertex->id, facet1->id));
    +            vertex->deleted= True;
    +            qh_setappend(qh, &qh->del_vertices, vertex);
    +          }
    +        }
    +        nummerges++;
    +      }else if (size < qh->hull_dim) {
    +        bestneighbor= qh_findbestneighbor(qh, facet1, &dist, &mindist, &maxdist);
    +        trace2((qh, qh->ferr, 2028, "qh_merge_degenredundant: facet f%d has %d neighbors, merge into f%d dist %2.2g\n",
    +              facet1->id, size, bestneighbor->id, dist));
    +        qh_mergefacet(qh, facet1, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +        nummerges++;
    +        if (qh->PRINTstatistics) {
    +          zinc_(Zdegen);
    +          wadd_(Wdegentot, dist);
    +          wmax_(Wdegenmax, dist);
    +        }
    +      } /* else, another merge fixed the degeneracy and redundancy tested */
    +    }
    +  }
    +  return nummerges;
    +} /* merge_degenredundant */
    +
    +/*---------------------------------
    +
    +  qh_merge_nonconvex(qh, facet1, facet2, mergetype )
    +    remove non-convex ridge between facet1 into facet2
    +    mergetype gives why the facet's are non-convex
    +
    +  returns:
    +    merges one of the facets into the best neighbor
    +
    +  design:
    +    if one of the facets is a new facet
    +      prefer merging new facet into old facet
    +    find best neighbors for both facets
    +    merge the nearest facet into its best neighbor
    +    update the statistics
    +*/
    +void qh_merge_nonconvex(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype) {
    +  facetT *bestfacet, *bestneighbor, *neighbor;
    +  realT dist, dist2, mindist, mindist2, maxdist, maxdist2;
    +
    +  if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +    qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +  trace3((qh, qh->ferr, 3003, "qh_merge_nonconvex: merge #%d for f%d and f%d type %d\n",
    +      zzval_(Ztotmerge) + 1, facet1->id, facet2->id, mergetype));
    +  /* concave or coplanar */
    +  if (!facet1->newfacet) {
    +    bestfacet= facet2;   /* avoid merging old facet if new is ok */
    +    facet2= facet1;
    +    facet1= bestfacet;
    +  }else
    +    bestfacet= facet1;
    +  bestneighbor= qh_findbestneighbor(qh, bestfacet, &dist, &mindist, &maxdist);
    +  neighbor= qh_findbestneighbor(qh, facet2, &dist2, &mindist2, &maxdist2);
    +  if (dist < dist2) {
    +    qh_mergefacet(qh, bestfacet, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +  }else if (qh->AVOIDold && !facet2->newfacet
    +  && ((mindist >= -qh->MAXcoplanar && maxdist <= qh->max_outside)
    +       || dist * 1.5 < dist2)) {
    +    zinc_(Zavoidold);
    +    wadd_(Wavoidoldtot, dist);
    +    wmax_(Wavoidoldmax, dist);
    +    trace2((qh, qh->ferr, 2029, "qh_merge_nonconvex: avoid merging old facet f%d dist %2.2g.  Use f%d dist %2.2g instead\n",
    +           facet2->id, dist2, facet1->id, dist2));
    +    qh_mergefacet(qh, bestfacet, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +  }else {
    +    qh_mergefacet(qh, facet2, neighbor, &mindist2, &maxdist2, !qh_MERGEapex);
    +    dist= dist2;
    +  }
    +  if (qh->PRINTstatistics) {
    +    if (mergetype == MRGanglecoplanar) {
    +      zinc_(Zacoplanar);
    +      wadd_(Wacoplanartot, dist);
    +      wmax_(Wacoplanarmax, dist);
    +    }else if (mergetype == MRGconcave) {
    +      zinc_(Zconcave);
    +      wadd_(Wconcavetot, dist);
    +      wmax_(Wconcavemax, dist);
    +    }else { /* MRGcoplanar */
    +      zinc_(Zcoplanar);
    +      wadd_(Wcoplanartot, dist);
    +      wmax_(Wcoplanarmax, dist);
    +    }
    +  }
    +} /* merge_nonconvex */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle(qh, samecycle, newfacet )
    +    merge a cycle of facets starting at samecycle into a newfacet
    +    newfacet is a horizon facet with ->normal
    +    samecycle facets are simplicial from an apex
    +
    +  returns:
    +    initializes vertex neighbors on first merge
    +    samecycle deleted (placed on qh.visible_list)
    +    newfacet at end of qh.facet_list
    +    deleted vertices on qh.del_vertices
    +
    +  see:
    +    qh_mergefacet()
    +    called by qh_mergecycle_all() for multiple, same cycle facets
    +
    +  design:
    +    make vertex neighbors if necessary
    +    make ridges for newfacet
    +    merge neighbor sets of samecycle into newfacet
    +    merge ridges of samecycle into newfacet
    +    merge vertex neighbors of samecycle into newfacet
    +    make apex of samecycle the apex of newfacet
    +    if newfacet wasn't a new facet
    +      add its vertices to qh.newvertex_list
    +    delete samecycle facets a make newfacet a newfacet
    +*/
    +void qh_mergecycle(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  int traceonce= False, tracerestore= 0;
    +  vertexT *apex;
    +#ifndef qh_NOtrace
    +  facetT *same;
    +#endif
    +
    +  if (newfacet->tricoplanar) {
    +    if (!qh->TRInormals) {
    +      qh_fprintf(qh, qh->ferr, 6224, "Qhull internal error (qh_mergecycle): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit(qh, qh_ERRqhull, newfacet, NULL);
    +    }
    +    newfacet->tricoplanar= False;
    +    newfacet->keepcentrum= False;
    +  }
    +  if (!qh->VERTEXneighbors)
    +    qh_vertexneighbors(qh);
    +  zzinc_(Ztotmerge);
    +  if (qh->REPORTfreq2 && qh->POSTmerging) {
    +    if (zzval_(Ztotmerge) > qh->mergereport + qh->REPORTfreq2)
    +      qh_tracemerging(qh);
    +  }
    +#ifndef qh_NOtrace
    +  if (qh->TRACEmerge == zzval_(Ztotmerge))
    +    qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +  trace2((qh, qh->ferr, 2030, "qh_mergecycle: merge #%d for facets from cycle f%d into coplanar horizon f%d\n",
    +        zzval_(Ztotmerge), samecycle->id, newfacet->id));
    +  if (newfacet == qh->tracefacet) {
    +    tracerestore= qh->IStracing;
    +    qh->IStracing= 4;
    +    qh_fprintf(qh, qh->ferr, 8068, "qh_mergecycle: ========= trace merge %d of samecycle %d into trace f%d, furthest is p%d\n",
    +               zzval_(Ztotmerge), samecycle->id, newfacet->id,  qh->furthest_id);
    +    traceonce= True;
    +  }
    +  if (qh->IStracing >=4) {
    +    qh_fprintf(qh, qh->ferr, 8069, "  same cycle:");
    +    FORALLsame_cycle_(samecycle)
    +      qh_fprintf(qh, qh->ferr, 8070, " f%d", same->id);
    +    qh_fprintf(qh, qh->ferr, 8071, "\n");
    +  }
    +  if (qh->IStracing >=4)
    +    qh_errprint(qh, "MERGING CYCLE", samecycle, newfacet, NULL, NULL);
    +#endif /* !qh_NOtrace */
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  qh_makeridges(qh, newfacet);
    +  qh_mergecycle_neighbors(qh, samecycle, newfacet);
    +  qh_mergecycle_ridges(qh, samecycle, newfacet);
    +  qh_mergecycle_vneighbors(qh, samecycle, newfacet);
    +  if (SETfirstt_(newfacet->vertices, vertexT) != apex)
    +    qh_setaddnth(qh, &newfacet->vertices, 0, apex);  /* apex has last id */
    +  if (!newfacet->newfacet)
    +    qh_newvertices(qh, newfacet->vertices);
    +  qh_mergecycle_facets(qh, samecycle, newfacet);
    +  qh_tracemerge(qh, samecycle, newfacet);
    +  /* check for degen_redundant_neighbors after qh_forcedmerges() */
    +  if (traceonce) {
    +    qh_fprintf(qh, qh->ferr, 8072, "qh_mergecycle: end of trace facet\n");
    +    qh->IStracing= tracerestore;
    +  }
    +} /* mergecycle */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_all(qh, facetlist, wasmerge )
    +    merge all samecycles of coplanar facets into horizon
    +    don't merge facets with ->mergeridge (these already have ->normal)
    +    all facets are simplicial from apex
    +    all facet->cycledone == False
    +
    +  returns:
    +    all newfacets merged into coplanar horizon facets
    +    deleted vertices on  qh.del_vertices
    +    sets wasmerge if any merge
    +
    +  see:
    +    calls qh_mergecycle for multiple, same cycle facets
    +
    +  design:
    +    for each facet on facetlist
    +      skip facets with duplicate ridges and normals
    +      check that facet is in a samecycle (->mergehorizon)
    +      if facet only member of samecycle
    +        sets vertex->delridge for all vertices except apex
    +        merge facet into horizon
    +      else
    +        mark all facets in samecycle
    +        remove facets with duplicate ridges from samecycle
    +        merge samecycle into horizon (deletes facets from facetlist)
    +*/
    +void qh_mergecycle_all(qhT *qh, facetT *facetlist, boolT *wasmerge) {
    +  facetT *facet, *same, *prev, *horizon;
    +  facetT *samecycle= NULL, *nextfacet, *nextsame;
    +  vertexT *apex, *vertex, **vertexp;
    +  int cycles=0, total=0, facets, nummerge;
    +
    +  trace2((qh, qh->ferr, 2031, "qh_mergecycle_all: begin\n"));
    +  for (facet= facetlist; facet && (nextfacet= facet->next); facet= nextfacet) {
    +    if (facet->normal)
    +      continue;
    +    if (!facet->mergehorizon) {
    +      qh_fprintf(qh, qh->ferr, 6225, "Qhull internal error (qh_mergecycle_all): f%d without normal\n", facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +    horizon= SETfirstt_(facet->neighbors, facetT);
    +    if (facet->f.samecycle == facet) {
    +      zinc_(Zonehorizon);
    +      /* merge distance done in qh_findhorizon */
    +      apex= SETfirstt_(facet->vertices, vertexT);
    +      FOREACHvertex_(facet->vertices) {
    +        if (vertex != apex)
    +          vertex->delridge= True;
    +      }
    +      horizon->f.newcycle= NULL;
    +      qh_mergefacet(qh, facet, horizon, NULL, NULL, qh_MERGEapex);
    +    }else {
    +      samecycle= facet;
    +      facets= 0;
    +      prev= facet;
    +      for (same= facet->f.samecycle; same;  /* FORALLsame_cycle_(facet) */
    +           same= (same == facet ? NULL :nextsame)) { /* ends at facet */
    +        nextsame= same->f.samecycle;
    +        if (same->cycledone || same->visible)
    +          qh_infiniteloop(qh, same);
    +        same->cycledone= True;
    +        if (same->normal) {
    +          prev->f.samecycle= same->f.samecycle; /* unlink ->mergeridge */
    +          same->f.samecycle= NULL;
    +        }else {
    +          prev= same;
    +          facets++;
    +        }
    +      }
    +      while (nextfacet && nextfacet->cycledone)  /* will delete samecycle */
    +        nextfacet= nextfacet->next;
    +      horizon->f.newcycle= NULL;
    +      qh_mergecycle(qh, samecycle, horizon);
    +      nummerge= horizon->nummerge + facets;
    +      if (nummerge > qh_MAXnummerge)
    +        horizon->nummerge= qh_MAXnummerge;
    +      else
    +        horizon->nummerge= (short unsigned int)nummerge;
    +      zzinc_(Zcyclehorizon);
    +      total += facets;
    +      zzadd_(Zcyclefacettot, facets);
    +      zmax_(Zcyclefacetmax, facets);
    +    }
    +    cycles++;
    +  }
    +  if (cycles)
    +    *wasmerge= True;
    +  trace1((qh, qh->ferr, 1013, "qh_mergecycle_all: merged %d same cycles or facets into coplanar horizons\n", cycles));
    +} /* mergecycle_all */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_facets(qh, samecycle, newfacet )
    +    finish merge of samecycle into newfacet
    +
    +  returns:
    +    samecycle prepended to visible_list for later deletion and partitioning
    +      each facet->f.replace == newfacet
    +
    +    newfacet moved to end of qh.facet_list
    +      makes newfacet a newfacet (get's facet1->id if it was old)
    +      sets newfacet->newmerge
    +      clears newfacet->center (unless merging into a large facet)
    +      clears newfacet->tested and ridge->tested for facet1
    +
    +    adds neighboring facets to facet_mergeset if redundant or degenerate
    +
    +  design:
    +    make newfacet a new facet and set its flags
    +    move samecycle facets to qh.visible_list for later deletion
    +    unless newfacet is large
    +      remove its centrum
    +*/
    +void qh_mergecycle_facets(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *next;
    +
    +  trace4((qh, qh->ferr, 4030, "qh_mergecycle_facets: make newfacet new and samecycle deleted\n"));
    +  qh_removefacet(qh, newfacet);  /* append as a newfacet to end of qh->facet_list */
    +  qh_appendfacet(qh, newfacet);
    +  newfacet->newfacet= True;
    +  newfacet->simplicial= False;
    +  newfacet->newmerge= True;
    +
    +  for (same= samecycle->f.samecycle; same; same= (same == samecycle ?  NULL : next)) {
    +    next= same->f.samecycle;  /* reused by willdelete */
    +    qh_willdelete(qh, same, newfacet);
    +  }
    +  if (newfacet->center
    +      && qh_setsize(qh, newfacet->vertices) <= qh->hull_dim + qh_MAXnewcentrum) {
    +    qh_memfree(qh, newfacet->center, qh->normal_size);
    +    newfacet->center= NULL;
    +  }
    +  trace3((qh, qh->ferr, 3004, "qh_mergecycle_facets: merged facets from cycle f%d into f%d\n",
    +             samecycle->id, newfacet->id));
    +} /* mergecycle_facets */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_neighbors(qh, samecycle, newfacet )
    +    add neighbors for samecycle facets to newfacet
    +
    +  returns:
    +    newfacet with updated neighbors and vice-versa
    +    newfacet has ridges
    +    all neighbors of newfacet marked with qh.visit_id
    +    samecycle facets marked with qh.visit_id-1
    +    ridges updated for simplicial neighbors of samecycle with a ridge
    +
    +  notes:
    +    assumes newfacet not in samecycle
    +    usually, samecycle facets are new, simplicial facets without internal ridges
    +      not so if horizon facet is coplanar to two different samecycles
    +
    +  see:
    +    qh_mergeneighbors()
    +
    +  design:
    +    check samecycle
    +    delete neighbors from newfacet that are also in samecycle
    +    for each neighbor of a facet in samecycle
    +      if neighbor is simplicial
    +        if first visit
    +          move the neighbor relation to newfacet
    +          update facet links for its ridges
    +        else
    +          make ridges for neighbor
    +          remove samecycle reference
    +      else
    +        update neighbor sets
    +*/
    +void qh_mergecycle_neighbors(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *neighbor, **neighborp;
    +  int delneighbors= 0, newneighbors= 0;
    +  unsigned int samevisitid;
    +  ridgeT *ridge, **ridgep;
    +
    +  samevisitid= ++qh->visit_id;
    +  FORALLsame_cycle_(samecycle) {
    +    if (same->visitid == samevisitid || same->visible)
    +      qh_infiniteloop(qh, samecycle);
    +    same->visitid= samevisitid;
    +  }
    +  newfacet->visitid= ++qh->visit_id;
    +  trace4((qh, qh->ferr, 4031, "qh_mergecycle_neighbors: delete shared neighbors from newfacet\n"));
    +  FOREACHneighbor_(newfacet) {
    +    if (neighbor->visitid == samevisitid) {
    +      SETref_(neighbor)= NULL;  /* samecycle neighbors deleted */
    +      delneighbors++;
    +    }else
    +      neighbor->visitid= qh->visit_id;
    +  }
    +  qh_setcompact(qh, newfacet->neighbors);
    +
    +  trace4((qh, qh->ferr, 4032, "qh_mergecycle_neighbors: update neighbors\n"));
    +  FORALLsame_cycle_(samecycle) {
    +    FOREACHneighbor_(same) {
    +      if (neighbor->visitid == samevisitid)
    +        continue;
    +      if (neighbor->simplicial) {
    +        if (neighbor->visitid != qh->visit_id) {
    +          qh_setappend(qh, &newfacet->neighbors, neighbor);
    +          qh_setreplace(qh, neighbor->neighbors, same, newfacet);
    +          newneighbors++;
    +          neighbor->visitid= qh->visit_id;
    +          FOREACHridge_(neighbor->ridges) { /* update ridge in case of qh_makeridges */
    +            if (ridge->top == same) {
    +              ridge->top= newfacet;
    +              break;
    +            }else if (ridge->bottom == same) {
    +              ridge->bottom= newfacet;
    +              break;
    +            }
    +          }
    +        }else {
    +          qh_makeridges(qh, neighbor);
    +          qh_setdel(neighbor->neighbors, same);
    +          /* same can't be horizon facet for neighbor */
    +        }
    +      }else { /* non-simplicial neighbor */
    +        qh_setdel(neighbor->neighbors, same);
    +        if (neighbor->visitid != qh->visit_id) {
    +          qh_setappend(qh, &neighbor->neighbors, newfacet);
    +          qh_setappend(qh, &newfacet->neighbors, neighbor);
    +          neighbor->visitid= qh->visit_id;
    +          newneighbors++;
    +        }
    +      }
    +    }
    +  }
    +  trace2((qh, qh->ferr, 2032, "qh_mergecycle_neighbors: deleted %d neighbors and added %d\n",
    +             delneighbors, newneighbors));
    +} /* mergecycle_neighbors */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_ridges(qh, samecycle, newfacet )
    +    add ridges/neighbors for facets in samecycle to newfacet
    +    all new/old neighbors of newfacet marked with qh.visit_id
    +    facets in samecycle marked with qh.visit_id-1
    +    newfacet marked with qh.visit_id
    +
    +  returns:
    +    newfacet has merged ridges
    +
    +  notes:
    +    ridge already updated for simplicial neighbors of samecycle with a ridge
    +
    +  see:
    +    qh_mergeridges()
    +    qh_makeridges()
    +
    +  design:
    +    remove ridges between newfacet and samecycle
    +    for each facet in samecycle
    +      for each ridge in facet
    +        update facet pointers in ridge
    +        skip ridges processed in qh_mergecycle_neighors
    +        free ridges between newfacet and samecycle
    +        free ridges between facets of samecycle (on 2nd visit)
    +        append remaining ridges to newfacet
    +      if simpilicial facet
    +        for each neighbor of facet
    +          if simplicial facet
    +          and not samecycle facet or newfacet
    +            make ridge between neighbor and newfacet
    +*/
    +void qh_mergecycle_ridges(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *neighbor= NULL;
    +  int numold=0, numnew=0;
    +  int neighbor_i, neighbor_n;
    +  unsigned int samevisitid;
    +  ridgeT *ridge, **ridgep;
    +  boolT toporient;
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  trace4((qh, qh->ferr, 4033, "qh_mergecycle_ridges: delete shared ridges from newfacet\n"));
    +  samevisitid= qh->visit_id -1;
    +  FOREACHridge_(newfacet->ridges) {
    +    neighbor= otherfacet_(ridge, newfacet);
    +    if (neighbor->visitid == samevisitid)
    +      SETref_(ridge)= NULL; /* ridge free'd below */
    +  }
    +  qh_setcompact(qh, newfacet->ridges);
    +
    +  trace4((qh, qh->ferr, 4034, "qh_mergecycle_ridges: add ridges to newfacet\n"));
    +  FORALLsame_cycle_(samecycle) {
    +    FOREACHridge_(same->ridges) {
    +      if (ridge->top == same) {
    +        ridge->top= newfacet;
    +        neighbor= ridge->bottom;
    +      }else if (ridge->bottom == same) {
    +        ridge->bottom= newfacet;
    +        neighbor= ridge->top;
    +      }else if (ridge->top == newfacet || ridge->bottom == newfacet) {
    +        qh_setappend(qh, &newfacet->ridges, ridge);
    +        numold++;  /* already set by qh_mergecycle_neighbors */
    +        continue;
    +      }else {
    +        qh_fprintf(qh, qh->ferr, 6098, "qhull internal error (qh_mergecycle_ridges): bad ridge r%d\n", ridge->id);
    +        qh_errexit(qh, qh_ERRqhull, NULL, ridge);
    +      }
    +      if (neighbor == newfacet) {
    +        qh_setfree(qh, &(ridge->vertices));
    +        qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
    +        numold++;
    +      }else if (neighbor->visitid == samevisitid) {
    +        qh_setdel(neighbor->ridges, ridge);
    +        qh_setfree(qh, &(ridge->vertices));
    +        qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
    +        numold++;
    +      }else {
    +        qh_setappend(qh, &newfacet->ridges, ridge);
    +        numold++;
    +      }
    +    }
    +    if (same->ridges)
    +      qh_settruncate(qh, same->ridges, 0);
    +    if (!same->simplicial)
    +      continue;
    +    FOREACHneighbor_i_(qh, same) {       /* note: !newfact->simplicial */
    +      if (neighbor->visitid != samevisitid && neighbor->simplicial) {
    +        ridge= qh_newridge(qh);
    +        ridge->vertices= qh_setnew_delnthsorted(qh, same->vertices, qh->hull_dim,
    +                                                          neighbor_i, 0);
    +        toporient= same->toporient ^ (neighbor_i & 0x1);
    +        if (toporient) {
    +          ridge->top= newfacet;
    +          ridge->bottom= neighbor;
    +        }else {
    +          ridge->top= neighbor;
    +          ridge->bottom= newfacet;
    +        }
    +        qh_setappend(qh, &(newfacet->ridges), ridge);
    +        qh_setappend(qh, &(neighbor->ridges), ridge);
    +        numnew++;
    +      }
    +    }
    +  }
    +
    +  trace2((qh, qh->ferr, 2033, "qh_mergecycle_ridges: found %d old ridges and %d new ones\n",
    +             numold, numnew));
    +} /* mergecycle_ridges */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_vneighbors(qh, samecycle, newfacet )
    +    create vertex neighbors for newfacet from vertices of facets in samecycle
    +    samecycle marked with visitid == qh.visit_id - 1
    +
    +  returns:
    +    newfacet vertices with updated neighbors
    +    marks newfacet with qh.visit_id-1
    +    deletes vertices that are merged away
    +    sets delridge on all vertices (faster here than in mergecycle_ridges)
    +
    +  see:
    +    qh_mergevertex_neighbors()
    +
    +  design:
    +    for each vertex of samecycle facet
    +      set vertex->delridge
    +      delete samecycle facets from vertex neighbors
    +      append newfacet to vertex neighbors
    +      if vertex only in newfacet
    +        delete it from newfacet
    +        add it to qh.del_vertices for later deletion
    +*/
    +void qh_mergecycle_vneighbors(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  facetT *neighbor, **neighborp;
    +  unsigned int mergeid;
    +  vertexT *vertex, **vertexp, *apex;
    +  setT *vertices;
    +
    +  trace4((qh, qh->ferr, 4035, "qh_mergecycle_vneighbors: update vertex neighbors for newfacet\n"));
    +  mergeid= qh->visit_id - 1;
    +  newfacet->visitid= mergeid;
    +  vertices= qh_basevertices(qh, samecycle); /* temp */
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  qh_setappend(qh, &vertices, apex);
    +  FOREACHvertex_(vertices) {
    +    vertex->delridge= True;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->visitid == mergeid)
    +        SETref_(neighbor)= NULL;
    +    }
    +    qh_setcompact(qh, vertex->neighbors);
    +    qh_setappend(qh, &vertex->neighbors, newfacet);
    +    if (!SETsecond_(vertex->neighbors)) {
    +      zinc_(Zcyclevertex);
    +      trace2((qh, qh->ferr, 2034, "qh_mergecycle_vneighbors: deleted v%d when merging cycle f%d into f%d\n",
    +        vertex->id, samecycle->id, newfacet->id));
    +      qh_setdelsorted(newfacet->vertices, vertex);
    +      vertex->deleted= True;
    +      qh_setappend(qh, &qh->del_vertices, vertex);
    +    }
    +  }
    +  qh_settempfree(qh, &vertices);
    +  trace3((qh, qh->ferr, 3005, "qh_mergecycle_vneighbors: merged vertices from cycle f%d into f%d\n",
    +             samecycle->id, newfacet->id));
    +} /* mergecycle_vneighbors */
    +
    +/*---------------------------------
    +
    +  qh_mergefacet(qh, facet1, facet2, mindist, maxdist, mergeapex )
    +    merges facet1 into facet2
    +    mergeapex==qh_MERGEapex if merging new facet into coplanar horizon
    +
    +  returns:
    +    qh.max_outside and qh.min_vertex updated
    +    initializes vertex neighbors on first merge
    +
    +  returns:
    +    facet2 contains facet1's vertices, neighbors, and ridges
    +      facet2 moved to end of qh.facet_list
    +      makes facet2 a newfacet
    +      sets facet2->newmerge set
    +      clears facet2->center (unless merging into a large facet)
    +      clears facet2->tested and ridge->tested for facet1
    +
    +    facet1 prepended to visible_list for later deletion and partitioning
    +      facet1->f.replace == facet2
    +
    +    adds neighboring facets to facet_mergeset if redundant or degenerate
    +
    +  notes:
    +    mindist/maxdist may be NULL (only if both NULL)
    +    traces merge if fmax_(maxdist,-mindist) > TRACEdist
    +
    +  see:
    +    qh_mergecycle()
    +
    +  design:
    +    trace merge and check for degenerate simplex
    +    make ridges for both facets
    +    update qh.max_outside, qh.max_vertex, qh.min_vertex
    +    update facet2->maxoutside and keepcentrum
    +    update facet2->nummerge
    +    update tested flags for facet2
    +    if facet1 is simplicial
    +      merge facet1 into facet2
    +    else
    +      merge facet1's neighbors into facet2
    +      merge facet1's ridges into facet2
    +      merge facet1's vertices into facet2
    +      merge facet1's vertex neighbors into facet2
    +      add facet2's vertices to qh.new_vertexlist
    +      unless qh_MERGEapex
    +        test facet2 for degenerate or redundant neighbors
    +      move facet1 to qh.visible_list for later deletion
    +      move facet2 to end of qh.newfacet_list
    +*/
    +void qh_mergefacet(qhT *qh, facetT *facet1, facetT *facet2, realT *mindist, realT *maxdist, boolT mergeapex) {
    +  boolT traceonce= False;
    +  vertexT *vertex, **vertexp;
    +  int tracerestore=0, nummerge;
    +
    +  if (facet1->tricoplanar || facet2->tricoplanar) {
    +    if (!qh->TRInormals) {
    +      qh_fprintf(qh, qh->ferr, 6226, "Qhull internal error (qh_mergefacet): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +    }
    +    if (facet2->tricoplanar) {
    +      facet2->tricoplanar= False;
    +      facet2->keepcentrum= False;
    +    }
    +  }
    +  zzinc_(Ztotmerge);
    +  if (qh->REPORTfreq2 && qh->POSTmerging) {
    +    if (zzval_(Ztotmerge) > qh->mergereport + qh->REPORTfreq2)
    +      qh_tracemerging(qh);
    +  }
    +#ifndef qh_NOtrace
    +  if (qh->build_cnt >= qh->RERUN) {
    +    if (mindist && (-*mindist > qh->TRACEdist || *maxdist > qh->TRACEdist)) {
    +      tracerestore= 0;
    +      qh->IStracing= qh->TRACElevel;
    +      traceonce= True;
    +      qh_fprintf(qh, qh->ferr, 8075, "qh_mergefacet: ========= trace wide merge #%d(%2.2g) for f%d into f%d, last point was p%d\n", zzval_(Ztotmerge),
    +             fmax_(-*mindist, *maxdist), facet1->id, facet2->id, qh->furthest_id);
    +    }else if (facet1 == qh->tracefacet || facet2 == qh->tracefacet) {
    +      tracerestore= qh->IStracing;
    +      qh->IStracing= 4;
    +      traceonce= True;
    +      qh_fprintf(qh, qh->ferr, 8076, "qh_mergefacet: ========= trace merge #%d involving f%d, furthest is p%d\n",
    +                 zzval_(Ztotmerge), qh->tracefacet_id,  qh->furthest_id);
    +    }
    +  }
    +  if (qh->IStracing >= 2) {
    +    realT mergemin= -2;
    +    realT mergemax= -2;
    +
    +    if (mindist) {
    +      mergemin= *mindist;
    +      mergemax= *maxdist;
    +    }
    +    qh_fprintf(qh, qh->ferr, 8077, "qh_mergefacet: #%d merge f%d into f%d, mindist= %2.2g, maxdist= %2.2g\n",
    +    zzval_(Ztotmerge), facet1->id, facet2->id, mergemin, mergemax);
    +  }
    +#endif /* !qh_NOtrace */
    +  if (facet1 == facet2 || facet1->visible || facet2->visible) {
    +    qh_fprintf(qh, qh->ferr, 6099, "qhull internal error (qh_mergefacet): either f%d and f%d are the same or one is a visible facet\n",
    +             facet1->id, facet2->id);
    +    qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +  }
    +  if (qh->num_facets - qh->num_visible <= qh->hull_dim + 1) {
    +    qh_fprintf(qh, qh->ferr, 6227, "\n\
    +qhull precision error: Only %d facets remain.  Can not merge another\n\
    +pair.  The input is too degenerate or the convexity constraints are\n\
    +too strong.\n", qh->hull_dim+1);
    +    if (qh->hull_dim >= 5 && !qh->MERGEexact)
    +      qh_fprintf(qh, qh->ferr, 8079, "Option 'Qx' may avoid this problem.\n");
    +    qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +  }
    +  if (!qh->VERTEXneighbors)
    +    qh_vertexneighbors(qh);
    +  qh_makeridges(qh, facet1);
    +  qh_makeridges(qh, facet2);
    +  if (qh->IStracing >=4)
    +    qh_errprint(qh, "MERGING", facet1, facet2, NULL, NULL);
    +  if (mindist) {
    +    maximize_(qh->max_outside, *maxdist);
    +    maximize_(qh->max_vertex, *maxdist);
    +#if qh_MAXoutside
    +    maximize_(facet2->maxoutside, *maxdist);
    +#endif
    +    minimize_(qh->min_vertex, *mindist);
    +    if (!facet2->keepcentrum
    +    && (*maxdist > qh->WIDEfacet || *mindist < -qh->WIDEfacet)) {
    +      facet2->keepcentrum= True;
    +      zinc_(Zwidefacet);
    +    }
    +  }
    +  nummerge= facet1->nummerge + facet2->nummerge + 1;
    +  if (nummerge >= qh_MAXnummerge)
    +    facet2->nummerge= qh_MAXnummerge;
    +  else
    +    facet2->nummerge= (short unsigned int)nummerge;
    +  facet2->newmerge= True;
    +  facet2->dupridge= False;
    +  qh_updatetested(qh, facet1, facet2);
    +  if (qh->hull_dim > 2 && qh_setsize(qh, facet1->vertices) == qh->hull_dim)
    +    qh_mergesimplex(qh, facet1, facet2, mergeapex);
    +  else {
    +    qh->vertex_visit++;
    +    FOREACHvertex_(facet2->vertices)
    +      vertex->visitid= qh->vertex_visit;
    +    if (qh->hull_dim == 2)
    +      qh_mergefacet2d(qh, facet1, facet2);
    +    else {
    +      qh_mergeneighbors(qh, facet1, facet2);
    +      qh_mergevertices(qh, facet1->vertices, &facet2->vertices);
    +    }
    +    qh_mergeridges(qh, facet1, facet2);
    +    qh_mergevertex_neighbors(qh, facet1, facet2);
    +    if (!facet2->newfacet)
    +      qh_newvertices(qh, facet2->vertices);
    +  }
    +  if (!mergeapex)
    +    qh_degen_redundant_neighbors(qh, facet2, facet1);
    +  if (facet2->coplanar || !facet2->newfacet) {
    +    zinc_(Zmergeintohorizon);
    +  }else if (!facet1->newfacet && facet2->newfacet) {
    +    zinc_(Zmergehorizon);
    +  }else {
    +    zinc_(Zmergenew);
    +  }
    +  qh_willdelete(qh, facet1, facet2);
    +  qh_removefacet(qh, facet2);  /* append as a newfacet to end of qh->facet_list */
    +  qh_appendfacet(qh, facet2);
    +  facet2->newfacet= True;
    +  facet2->tested= False;
    +  qh_tracemerge(qh, facet1, facet2);
    +  if (traceonce) {
    +    qh_fprintf(qh, qh->ferr, 8080, "qh_mergefacet: end of wide tracing\n");
    +    qh->IStracing= tracerestore;
    +  }
    +} /* mergefacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergefacet2d(qh, facet1, facet2 )
    +    in 2d, merges neighbors and vertices of facet1 into facet2
    +
    +  returns:
    +    build ridges for neighbors if necessary
    +    facet2 looks like a simplicial facet except for centrum, ridges
    +      neighbors are opposite the corresponding vertex
    +      maintains orientation of facet2
    +
    +  notes:
    +    qh_mergefacet() retains non-simplicial structures
    +      they are not needed in 2d, but later routines may use them
    +    preserves qh.vertex_visit for qh_mergevertex_neighbors()
    +
    +  design:
    +    get vertices and neighbors
    +    determine new vertices and neighbors
    +    set new vertices and neighbors and adjust orientation
    +    make ridges for new neighbor if needed
    +*/
    +void qh_mergefacet2d(qhT *qh, facetT *facet1, facetT *facet2) {
    +  vertexT *vertex1A, *vertex1B, *vertex2A, *vertex2B, *vertexA, *vertexB;
    +  facetT *neighbor1A, *neighbor1B, *neighbor2A, *neighbor2B, *neighborA, *neighborB;
    +
    +  vertex1A= SETfirstt_(facet1->vertices, vertexT);
    +  vertex1B= SETsecondt_(facet1->vertices, vertexT);
    +  vertex2A= SETfirstt_(facet2->vertices, vertexT);
    +  vertex2B= SETsecondt_(facet2->vertices, vertexT);
    +  neighbor1A= SETfirstt_(facet1->neighbors, facetT);
    +  neighbor1B= SETsecondt_(facet1->neighbors, facetT);
    +  neighbor2A= SETfirstt_(facet2->neighbors, facetT);
    +  neighbor2B= SETsecondt_(facet2->neighbors, facetT);
    +  if (vertex1A == vertex2A) {
    +    vertexA= vertex1B;
    +    vertexB= vertex2B;
    +    neighborA= neighbor2A;
    +    neighborB= neighbor1A;
    +  }else if (vertex1A == vertex2B) {
    +    vertexA= vertex1B;
    +    vertexB= vertex2A;
    +    neighborA= neighbor2B;
    +    neighborB= neighbor1A;
    +  }else if (vertex1B == vertex2A) {
    +    vertexA= vertex1A;
    +    vertexB= vertex2B;
    +    neighborA= neighbor2A;
    +    neighborB= neighbor1B;
    +  }else { /* 1B == 2B */
    +    vertexA= vertex1A;
    +    vertexB= vertex2A;
    +    neighborA= neighbor2B;
    +    neighborB= neighbor1B;
    +  }
    +  /* vertexB always from facet2, neighborB always from facet1 */
    +  if (vertexA->id > vertexB->id) {
    +    SETfirst_(facet2->vertices)= vertexA;
    +    SETsecond_(facet2->vertices)= vertexB;
    +    if (vertexB == vertex2A)
    +      facet2->toporient= !facet2->toporient;
    +    SETfirst_(facet2->neighbors)= neighborA;
    +    SETsecond_(facet2->neighbors)= neighborB;
    +  }else {
    +    SETfirst_(facet2->vertices)= vertexB;
    +    SETsecond_(facet2->vertices)= vertexA;
    +    if (vertexB == vertex2B)
    +      facet2->toporient= !facet2->toporient;
    +    SETfirst_(facet2->neighbors)= neighborB;
    +    SETsecond_(facet2->neighbors)= neighborA;
    +  }
    +  qh_makeridges(qh, neighborB);
    +  qh_setreplace(qh, neighborB->neighbors, facet1, facet2);
    +  trace4((qh, qh->ferr, 4036, "qh_mergefacet2d: merged v%d and neighbor f%d of f%d into f%d\n",
    +       vertexA->id, neighborB->id, facet1->id, facet2->id));
    +} /* mergefacet2d */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergeneighbors(qh, facet1, facet2 )
    +    merges the neighbors of facet1 into facet2
    +
    +  see:
    +    qh_mergecycle_neighbors()
    +
    +  design:
    +    for each neighbor of facet1
    +      if neighbor is also a neighbor of facet2
    +        if neighbor is simpilicial
    +          make ridges for later deletion as a degenerate facet
    +        update its neighbor set
    +      else
    +        move the neighbor relation to facet2
    +    remove the neighbor relation for facet1 and facet2
    +*/
    +void qh_mergeneighbors(qhT *qh, facetT *facet1, facetT *facet2) {
    +  facetT *neighbor, **neighborp;
    +
    +  trace4((qh, qh->ferr, 4037, "qh_mergeneighbors: merge neighbors of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  qh->visit_id++;
    +  FOREACHneighbor_(facet2) {
    +    neighbor->visitid= qh->visit_id;
    +  }
    +  FOREACHneighbor_(facet1) {
    +    if (neighbor->visitid == qh->visit_id) {
    +      if (neighbor->simplicial)    /* is degen, needs ridges */
    +        qh_makeridges(qh, neighbor);
    +      if (SETfirstt_(neighbor->neighbors, facetT) != facet1) /*keep newfacet->horizon*/
    +        qh_setdel(neighbor->neighbors, facet1);
    +      else {
    +        qh_setdel(neighbor->neighbors, facet2);
    +        qh_setreplace(qh, neighbor->neighbors, facet1, facet2);
    +      }
    +    }else if (neighbor != facet2) {
    +      qh_setappend(qh, &(facet2->neighbors), neighbor);
    +      qh_setreplace(qh, neighbor->neighbors, facet1, facet2);
    +    }
    +  }
    +  qh_setdel(facet1->neighbors, facet2);  /* here for makeridges */
    +  qh_setdel(facet2->neighbors, facet1);
    +} /* mergeneighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergeridges(qh, facet1, facet2 )
    +    merges the ridge set of facet1 into facet2
    +
    +  returns:
    +    may delete all ridges for a vertex
    +    sets vertex->delridge on deleted ridges
    +
    +  see:
    +    qh_mergecycle_ridges()
    +
    +  design:
    +    delete ridges between facet1 and facet2
    +      mark (delridge) vertices on these ridges for later testing
    +    for each remaining ridge
    +      rename facet1 to facet2
    +*/
    +void qh_mergeridges(qhT *qh, facetT *facet1, facetT *facet2) {
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +
    +  trace4((qh, qh->ferr, 4038, "qh_mergeridges: merge ridges of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  FOREACHridge_(facet2->ridges) {
    +    if ((ridge->top == facet1) || (ridge->bottom == facet1)) {
    +      FOREACHvertex_(ridge->vertices)
    +        vertex->delridge= True;
    +      qh_delridge(qh, ridge);  /* expensive in high-d, could rebuild */
    +      ridgep--; /*repeat*/
    +    }
    +  }
    +  FOREACHridge_(facet1->ridges) {
    +    if (ridge->top == facet1)
    +      ridge->top= facet2;
    +    else
    +      ridge->bottom= facet2;
    +    qh_setappend(qh, &(facet2->ridges), ridge);
    +  }
    +} /* mergeridges */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergesimplex(qh, facet1, facet2, mergeapex )
    +    merge simplicial facet1 into facet2
    +    mergeapex==qh_MERGEapex if merging samecycle into horizon facet
    +      vertex id is latest (most recently created)
    +    facet1 may be contained in facet2
    +    ridges exist for both facets
    +
    +  returns:
    +    facet2 with updated vertices, ridges, neighbors
    +    updated neighbors for facet1's vertices
    +    facet1 not deleted
    +    sets vertex->delridge on deleted ridges
    +
    +  notes:
    +    special case code since this is the most common merge
    +    called from qh_mergefacet()
    +
    +  design:
    +    if qh_MERGEapex
    +      add vertices of facet2 to qh.new_vertexlist if necessary
    +      add apex to facet2
    +    else
    +      for each ridge between facet1 and facet2
    +        set vertex->delridge
    +      determine the apex for facet1 (i.e., vertex to be merged)
    +      unless apex already in facet2
    +        insert apex into vertices for facet2
    +      add vertices of facet2 to qh.new_vertexlist if necessary
    +      add apex to qh.new_vertexlist if necessary
    +      for each vertex of facet1
    +        if apex
    +          rename facet1 to facet2 in its vertex neighbors
    +        else
    +          delete facet1 from vertex neighors
    +          if only in facet2
    +            add vertex to qh.del_vertices for later deletion
    +      for each ridge of facet1
    +        delete ridges between facet1 and facet2
    +        append other ridges to facet2 after renaming facet to facet2
    +*/
    +void qh_mergesimplex(qhT *qh, facetT *facet1, facetT *facet2, boolT mergeapex) {
    +  vertexT *vertex, **vertexp, *apex;
    +  ridgeT *ridge, **ridgep;
    +  boolT issubset= False;
    +  int vertex_i= -1, vertex_n;
    +  facetT *neighbor, **neighborp, *otherfacet;
    +
    +  if (mergeapex) {
    +    if (!facet2->newfacet)
    +      qh_newvertices(qh, facet2->vertices);  /* apex is new */
    +    apex= SETfirstt_(facet1->vertices, vertexT);
    +    if (SETfirstt_(facet2->vertices, vertexT) != apex)
    +      qh_setaddnth(qh, &facet2->vertices, 0, apex);  /* apex has last id */
    +    else
    +      issubset= True;
    +  }else {
    +    zinc_(Zmergesimplex);
    +    FOREACHvertex_(facet1->vertices)
    +      vertex->seen= False;
    +    FOREACHridge_(facet1->ridges) {
    +      if (otherfacet_(ridge, facet1) == facet2) {
    +        FOREACHvertex_(ridge->vertices) {
    +          vertex->seen= True;
    +          vertex->delridge= True;
    +        }
    +        break;
    +      }
    +    }
    +    FOREACHvertex_(facet1->vertices) {
    +      if (!vertex->seen)
    +        break;  /* must occur */
    +    }
    +    apex= vertex;
    +    trace4((qh, qh->ferr, 4039, "qh_mergesimplex: merge apex v%d of f%d into facet f%d\n",
    +          apex->id, facet1->id, facet2->id));
    +    FOREACHvertex_i_(qh, facet2->vertices) {
    +      if (vertex->id < apex->id) {
    +        break;
    +      }else if (vertex->id == apex->id) {
    +        issubset= True;
    +        break;
    +      }
    +    }
    +    if (!issubset)
    +      qh_setaddnth(qh, &facet2->vertices, vertex_i, apex);
    +    if (!facet2->newfacet)
    +      qh_newvertices(qh, facet2->vertices);
    +    else if (!apex->newlist) {
    +      qh_removevertex(qh, apex);
    +      qh_appendvertex(qh, apex);
    +    }
    +  }
    +  trace4((qh, qh->ferr, 4040, "qh_mergesimplex: update vertex neighbors of f%d\n",
    +          facet1->id));
    +  FOREACHvertex_(facet1->vertices) {
    +    if (vertex == apex && !issubset)
    +      qh_setreplace(qh, vertex->neighbors, facet1, facet2);
    +    else {
    +      qh_setdel(vertex->neighbors, facet1);
    +      if (!SETsecond_(vertex->neighbors))
    +        qh_mergevertex_del(qh, vertex, facet1, facet2);
    +    }
    +  }
    +  trace4((qh, qh->ferr, 4041, "qh_mergesimplex: merge ridges and neighbors of f%d into f%d\n",
    +          facet1->id, facet2->id));
    +  qh->visit_id++;
    +  FOREACHneighbor_(facet2)
    +    neighbor->visitid= qh->visit_id;
    +  FOREACHridge_(facet1->ridges) {
    +    otherfacet= otherfacet_(ridge, facet1);
    +    if (otherfacet == facet2) {
    +      qh_setdel(facet2->ridges, ridge);
    +      qh_setfree(qh, &(ridge->vertices));
    +      qh_memfree(qh, ridge, (int)sizeof(ridgeT));
    +      qh_setdel(facet2->neighbors, facet1);
    +    }else {
    +      qh_setappend(qh, &facet2->ridges, ridge);
    +      if (otherfacet->visitid != qh->visit_id) {
    +        qh_setappend(qh, &facet2->neighbors, otherfacet);
    +        qh_setreplace(qh, otherfacet->neighbors, facet1, facet2);
    +        otherfacet->visitid= qh->visit_id;
    +      }else {
    +        if (otherfacet->simplicial)    /* is degen, needs ridges */
    +          qh_makeridges(qh, otherfacet);
    +        if (SETfirstt_(otherfacet->neighbors, facetT) != facet1)
    +          qh_setdel(otherfacet->neighbors, facet1);
    +        else {   /*keep newfacet->neighbors->horizon*/
    +          qh_setdel(otherfacet->neighbors, facet2);
    +          qh_setreplace(qh, otherfacet->neighbors, facet1, facet2);
    +        }
    +      }
    +      if (ridge->top == facet1) /* wait until after qh_makeridges */
    +        ridge->top= facet2;
    +      else
    +        ridge->bottom= facet2;
    +    }
    +  }
    +  SETfirst_(facet1->ridges)= NULL; /* it will be deleted */
    +  trace3((qh, qh->ferr, 3006, "qh_mergesimplex: merged simplex f%d apex v%d into facet f%d\n",
    +          facet1->id, getid_(apex), facet2->id));
    +} /* mergesimplex */
    +
    +/*---------------------------------
    +
    +  qh_mergevertex_del(qh, vertex, facet1, facet2 )
    +    delete a vertex because of merging facet1 into facet2
    +
    +  returns:
    +    deletes vertex from facet2
    +    adds vertex to qh.del_vertices for later deletion
    +*/
    +void qh_mergevertex_del(qhT *qh, vertexT *vertex, facetT *facet1, facetT *facet2) {
    +
    +  zinc_(Zmergevertex);
    +  trace2((qh, qh->ferr, 2035, "qh_mergevertex_del: deleted v%d when merging f%d into f%d\n",
    +          vertex->id, facet1->id, facet2->id));
    +  qh_setdelsorted(facet2->vertices, vertex);
    +  vertex->deleted= True;
    +  qh_setappend(qh, &qh->del_vertices, vertex);
    +} /* mergevertex_del */
    +
    +/*---------------------------------
    +
    +  qh_mergevertex_neighbors(qh, facet1, facet2 )
    +    merge the vertex neighbors of facet1 to facet2
    +
    +  returns:
    +    if vertex is current qh.vertex_visit
    +      deletes facet1 from vertex->neighbors
    +    else
    +      renames facet1 to facet2 in vertex->neighbors
    +    deletes vertices if only one neighbor
    +
    +  notes:
    +    assumes vertex neighbor sets are good
    +*/
    +void qh_mergevertex_neighbors(qhT *qh, facetT *facet1, facetT *facet2) {
    +  vertexT *vertex, **vertexp;
    +
    +  trace4((qh, qh->ferr, 4042, "qh_mergevertex_neighbors: merge vertex neighbors of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  if (qh->tracevertex) {
    +    qh_fprintf(qh, qh->ferr, 8081, "qh_mergevertex_neighbors: of f%d and f%d at furthest p%d f0= %p\n",
    +             facet1->id, facet2->id, qh->furthest_id, qh->tracevertex->neighbors->e[0].p);
    +    qh_errprint(qh, "TRACE", NULL, NULL, NULL, qh->tracevertex);
    +  }
    +  FOREACHvertex_(facet1->vertices) {
    +    if (vertex->visitid != qh->vertex_visit)
    +      qh_setreplace(qh, vertex->neighbors, facet1, facet2);
    +    else {
    +      qh_setdel(vertex->neighbors, facet1);
    +      if (!SETsecond_(vertex->neighbors))
    +        qh_mergevertex_del(qh, vertex, facet1, facet2);
    +    }
    +  }
    +  if (qh->tracevertex)
    +    qh_errprint(qh, "TRACE", NULL, NULL, NULL, qh->tracevertex);
    +} /* mergevertex_neighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergevertices(qh, vertices1, vertices2 )
    +    merges the vertex set of facet1 into facet2
    +
    +  returns:
    +    replaces vertices2 with merged set
    +    preserves vertex_visit for qh_mergevertex_neighbors
    +    updates qh.newvertex_list
    +
    +  design:
    +    create a merged set of both vertices (in inverse id order)
    +*/
    +void qh_mergevertices(qhT *qh, setT *vertices1, setT **vertices2) {
    +  int newsize= qh_setsize(qh, vertices1)+qh_setsize(qh, *vertices2) - qh->hull_dim + 1;
    +  setT *mergedvertices;
    +  vertexT *vertex, **vertexp, **vertex2= SETaddr_(*vertices2, vertexT);
    +
    +  mergedvertices= qh_settemp(qh, newsize);
    +  FOREACHvertex_(vertices1) {
    +    if (!*vertex2 || vertex->id > (*vertex2)->id)
    +      qh_setappend(qh, &mergedvertices, vertex);
    +    else {
    +      while (*vertex2 && (*vertex2)->id > vertex->id)
    +        qh_setappend(qh, &mergedvertices, *vertex2++);
    +      if (!*vertex2 || (*vertex2)->id < vertex->id)
    +        qh_setappend(qh, &mergedvertices, vertex);
    +      else
    +        qh_setappend(qh, &mergedvertices, *vertex2++);
    +    }
    +  }
    +  while (*vertex2)
    +    qh_setappend(qh, &mergedvertices, *vertex2++);
    +  if (newsize < qh_setsize(qh, mergedvertices)) {
    +    qh_fprintf(qh, qh->ferr, 6100, "qhull internal error (qh_mergevertices): facets did not share a ridge\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  qh_setfree(qh, vertices2);
    +  *vertices2= mergedvertices;
    +  qh_settemppop(qh);
    +} /* mergevertices */
    +
    +
    +/*---------------------------------
    +
    +  qh_neighbor_intersections(qh, vertex )
    +    return intersection of all vertices in vertex->neighbors except for vertex
    +
    +  returns:
    +    returns temporary set of vertices
    +    does not include vertex
    +    NULL if a neighbor is simplicial
    +    NULL if empty set
    +
    +  notes:
    +    used for renaming vertices
    +
    +  design:
    +    initialize the intersection set with vertices of the first two neighbors
    +    delete vertex from the intersection
    +    for each remaining neighbor
    +      intersect its vertex set with the intersection set
    +      return NULL if empty
    +    return the intersection set
    +*/
    +setT *qh_neighbor_intersections(qhT *qh, vertexT *vertex) {
    +  facetT *neighbor, **neighborp, *neighborA, *neighborB;
    +  setT *intersect;
    +  int neighbor_i, neighbor_n;
    +
    +  FOREACHneighbor_(vertex) {
    +    if (neighbor->simplicial)
    +      return NULL;
    +  }
    +  neighborA= SETfirstt_(vertex->neighbors, facetT);
    +  neighborB= SETsecondt_(vertex->neighbors, facetT);
    +  zinc_(Zintersectnum);
    +  if (!neighborA)
    +    return NULL;
    +  if (!neighborB)
    +    intersect= qh_setcopy(qh, neighborA->vertices, 0);
    +  else
    +    intersect= qh_vertexintersect_new(qh, neighborA->vertices, neighborB->vertices);
    +  qh_settemppush(qh, intersect);
    +  qh_setdelsorted(intersect, vertex);
    +  FOREACHneighbor_i_(qh, vertex) {
    +    if (neighbor_i >= 2) {
    +      zinc_(Zintersectnum);
    +      qh_vertexintersect(qh, &intersect, neighbor->vertices);
    +      if (!SETfirst_(intersect)) {
    +        zinc_(Zintersectfail);
    +        qh_settempfree(qh, &intersect);
    +        return NULL;
    +      }
    +    }
    +  }
    +  trace3((qh, qh->ferr, 3007, "qh_neighbor_intersections: %d vertices in neighbor intersection of v%d\n",
    +          qh_setsize(qh, intersect), vertex->id));
    +  return intersect;
    +} /* neighbor_intersections */
    +
    +/*---------------------------------
    +
    +  qh_newvertices(qh, vertices )
    +    add vertices to end of qh.vertex_list (marks as new vertices)
    +
    +  returns:
    +    vertices on qh.newvertex_list
    +    vertex->newlist set
    +*/
    +void qh_newvertices(qhT *qh, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->newlist) {
    +      qh_removevertex(qh, vertex);
    +      qh_appendvertex(qh, vertex);
    +    }
    +  }
    +} /* newvertices */
    +
    +/*---------------------------------
    +
    +  qh_reducevertices(qh)
    +    reduce extra vertices, shared vertices, and redundant vertices
    +    facet->newmerge is set if merged since last call
    +    if !qh.MERGEvertices, only removes extra vertices
    +
    +  returns:
    +    True if also merged degen_redundant facets
    +    vertices are renamed if possible
    +    clears facet->newmerge and vertex->delridge
    +
    +  notes:
    +    ignored if 2-d
    +
    +  design:
    +    merge any degenerate or redundant facets
    +    for each newly merged facet
    +      remove extra vertices
    +    if qh.MERGEvertices
    +      for each newly merged facet
    +        for each vertex
    +          if vertex was on a deleted ridge
    +            rename vertex if it is shared
    +      remove delridge flag from new vertices
    +*/
    +boolT qh_reducevertices(qhT *qh) {
    +  int numshare=0, numrename= 0;
    +  boolT degenredun= False;
    +  facetT *newfacet;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh->hull_dim == 2)
    +    return False;
    +  if (qh_merge_degenredundant(qh))
    +    degenredun= True;
    + LABELrestart:
    +  FORALLnew_facets {
    +    if (newfacet->newmerge) {
    +      if (!qh->MERGEvertices)
    +        newfacet->newmerge= False;
    +      qh_remove_extravertices(qh, newfacet);
    +    }
    +  }
    +  if (!qh->MERGEvertices)
    +    return False;
    +  FORALLnew_facets {
    +    if (newfacet->newmerge) {
    +      newfacet->newmerge= False;
    +      FOREACHvertex_(newfacet->vertices) {
    +        if (vertex->delridge) {
    +          if (qh_rename_sharedvertex(qh, vertex, newfacet)) {
    +            numshare++;
    +            vertexp--; /* repeat since deleted vertex */
    +          }
    +        }
    +      }
    +    }
    +  }
    +  FORALLvertex_(qh->newvertex_list) {
    +    if (vertex->delridge && !vertex->deleted) {
    +      vertex->delridge= False;
    +      if (qh->hull_dim >= 4 && qh_redundant_vertex(qh, vertex)) {
    +        numrename++;
    +        if (qh_merge_degenredundant(qh)) {
    +          degenredun= True;
    +          goto LABELrestart;
    +        }
    +      }
    +    }
    +  }
    +  trace1((qh, qh->ferr, 1014, "qh_reducevertices: renamed %d shared vertices and %d redundant vertices. Degen? %d\n",
    +          numshare, numrename, degenredun));
    +  return degenredun;
    +} /* reducevertices */
    +
    +/*---------------------------------
    +
    +  qh_redundant_vertex(qh, vertex )
    +    detect and rename a redundant vertex
    +    vertices have full vertex->neighbors
    +
    +  returns:
    +    returns true if find a redundant vertex
    +      deletes vertex(vertex->deleted)
    +
    +  notes:
    +    only needed if vertex->delridge and hull_dim >= 4
    +    may add degenerate facets to qh.facet_mergeset
    +    doesn't change vertex->neighbors or create redundant facets
    +
    +  design:
    +    intersect vertices of all facet neighbors of vertex
    +    determine ridges for these vertices
    +    if find a new vertex for vertex amoung these ridges and vertices
    +      rename vertex to the new vertex
    +*/
    +vertexT *qh_redundant_vertex(qhT *qh, vertexT *vertex) {
    +  vertexT *newvertex= NULL;
    +  setT *vertices, *ridges;
    +
    +  trace3((qh, qh->ferr, 3008, "qh_redundant_vertex: check if v%d can be renamed\n", vertex->id));
    +  if ((vertices= qh_neighbor_intersections(qh, vertex))) {
    +    ridges= qh_vertexridges(qh, vertex);
    +    if ((newvertex= qh_find_newvertex(qh, vertex, vertices, ridges)))
    +      qh_renamevertex(qh, vertex, newvertex, ridges, NULL, NULL);
    +    qh_settempfree(qh, &ridges);
    +    qh_settempfree(qh, &vertices);
    +  }
    +  return newvertex;
    +} /* redundant_vertex */
    +
    +/*---------------------------------
    +
    +  qh_remove_extravertices(qh, facet )
    +    remove extra vertices from non-simplicial facets
    +
    +  returns:
    +    returns True if it finds them
    +
    +  design:
    +    for each vertex in facet
    +      if vertex not in a ridge (i.e., no longer used)
    +        delete vertex from facet
    +        delete facet from vertice's neighbors
    +        unless vertex in another facet
    +          add vertex to qh.del_vertices for later deletion
    +*/
    +boolT qh_remove_extravertices(qhT *qh, facetT *facet) {
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +  boolT foundrem= False;
    +
    +  trace4((qh, qh->ferr, 4043, "qh_remove_extravertices: test f%d for extra vertices\n",
    +          facet->id));
    +  FOREACHvertex_(facet->vertices)
    +    vertex->seen= False;
    +  FOREACHridge_(facet->ridges) {
    +    FOREACHvertex_(ridge->vertices)
    +      vertex->seen= True;
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    if (!vertex->seen) {
    +      foundrem= True;
    +      zinc_(Zremvertex);
    +      qh_setdelsorted(facet->vertices, vertex);
    +      qh_setdel(vertex->neighbors, facet);
    +      if (!qh_setsize(qh, vertex->neighbors)) {
    +        vertex->deleted= True;
    +        qh_setappend(qh, &qh->del_vertices, vertex);
    +        zinc_(Zremvertexdel);
    +        trace2((qh, qh->ferr, 2036, "qh_remove_extravertices: v%d deleted because it's lost all ridges\n", vertex->id));
    +      }else
    +        trace3((qh, qh->ferr, 3009, "qh_remove_extravertices: v%d removed from f%d because it's lost all ridges\n", vertex->id, facet->id));
    +      vertexp--; /*repeat*/
    +    }
    +  }
    +  return foundrem;
    +} /* remove_extravertices */
    +
    +/*---------------------------------
    +
    +  qh_rename_sharedvertex(qh, vertex, facet )
    +    detect and rename if shared vertex in facet
    +    vertices have full ->neighbors
    +
    +  returns:
    +    newvertex or NULL
    +    the vertex may still exist in other facets (i.e., a neighbor was pinched)
    +    does not change facet->neighbors
    +    updates vertex->neighbors
    +
    +  notes:
    +    a shared vertex for a facet is only in ridges to one neighbor
    +    this may undo a pinched facet
    +
    +    it does not catch pinches involving multiple facets.  These appear
    +      to be difficult to detect, since an exhaustive search is too expensive.
    +
    +  design:
    +    if vertex only has two neighbors
    +      determine the ridges that contain the vertex
    +      determine the vertices shared by both neighbors
    +      if can find a new vertex in this set
    +        rename the vertex to the new vertex
    +*/
    +vertexT *qh_rename_sharedvertex(qhT *qh, vertexT *vertex, facetT *facet) {
    +  facetT *neighbor, **neighborp, *neighborA= NULL;
    +  setT *vertices, *ridges;
    +  vertexT *newvertex;
    +
    +  if (qh_setsize(qh, vertex->neighbors) == 2) {
    +    neighborA= SETfirstt_(vertex->neighbors, facetT);
    +    if (neighborA == facet)
    +      neighborA= SETsecondt_(vertex->neighbors, facetT);
    +  }else if (qh->hull_dim == 3)
    +    return NULL;
    +  else {
    +    qh->visit_id++;
    +    FOREACHneighbor_(facet)
    +      neighbor->visitid= qh->visit_id;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->visitid == qh->visit_id) {
    +        if (neighborA)
    +          return NULL;
    +        neighborA= neighbor;
    +      }
    +    }
    +    if (!neighborA) {
    +      qh_fprintf(qh, qh->ferr, 6101, "qhull internal error (qh_rename_sharedvertex): v%d's neighbors not in f%d\n",
    +        vertex->id, facet->id);
    +      qh_errprint(qh, "ERRONEOUS", facet, NULL, NULL, vertex);
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +  }
    +  /* the vertex is shared by facet and neighborA */
    +  ridges= qh_settemp(qh, qh->TEMPsize);
    +  neighborA->visitid= ++qh->visit_id;
    +  qh_vertexridges_facet(qh, vertex, facet, &ridges);
    +  trace2((qh, qh->ferr, 2037, "qh_rename_sharedvertex: p%d(v%d) is shared by f%d(%d ridges) and f%d\n",
    +    qh_pointid(qh, vertex->point), vertex->id, facet->id, qh_setsize(qh, ridges), neighborA->id));
    +  zinc_(Zintersectnum);
    +  vertices= qh_vertexintersect_new(qh, facet->vertices, neighborA->vertices);
    +  qh_setdel(vertices, vertex);
    +  qh_settemppush(qh, vertices);
    +  if ((newvertex= qh_find_newvertex(qh, vertex, vertices, ridges)))
    +    qh_renamevertex(qh, vertex, newvertex, ridges, facet, neighborA);
    +  qh_settempfree(qh, &vertices);
    +  qh_settempfree(qh, &ridges);
    +  return newvertex;
    +} /* rename_sharedvertex */
    +
    +/*---------------------------------
    +
    +  qh_renameridgevertex(qh, ridge, oldvertex, newvertex )
    +    renames oldvertex as newvertex in ridge
    +
    +  returns:
    +
    +  design:
    +    delete oldvertex from ridge
    +    if newvertex already in ridge
    +      copy ridge->noconvex to another ridge if possible
    +      delete the ridge
    +    else
    +      insert newvertex into the ridge
    +      adjust the ridge's orientation
    +*/
    +void qh_renameridgevertex(qhT *qh, ridgeT *ridge, vertexT *oldvertex, vertexT *newvertex) {
    +  int nth= 0, oldnth;
    +  facetT *temp;
    +  vertexT *vertex, **vertexp;
    +
    +  oldnth= qh_setindex(ridge->vertices, oldvertex);
    +  qh_setdelnthsorted(qh, ridge->vertices, oldnth);
    +  FOREACHvertex_(ridge->vertices) {
    +    if (vertex == newvertex) {
    +      zinc_(Zdelridge);
    +      if (ridge->nonconvex) /* only one ridge has nonconvex set */
    +        qh_copynonconvex(qh, ridge);
    +      trace2((qh, qh->ferr, 2038, "qh_renameridgevertex: ridge r%d deleted.  It contained both v%d and v%d\n",
    +        ridge->id, oldvertex->id, newvertex->id));
    +      qh_delridge(qh, ridge);
    +      return;
    +    }
    +    if (vertex->id < newvertex->id)
    +      break;
    +    nth++;
    +  }
    +  qh_setaddnth(qh, &ridge->vertices, nth, newvertex);
    +  if (abs(oldnth - nth)%2) {
    +    trace3((qh, qh->ferr, 3010, "qh_renameridgevertex: swapped the top and bottom of ridge r%d\n",
    +            ridge->id));
    +    temp= ridge->top;
    +    ridge->top= ridge->bottom;
    +    ridge->bottom= temp;
    +  }
    +} /* renameridgevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_renamevertex(qh, oldvertex, newvertex, ridges, oldfacet, neighborA )
    +    renames oldvertex as newvertex in ridges
    +    gives oldfacet/neighborA if oldvertex is shared between two facets
    +
    +  returns:
    +    oldvertex may still exist afterwards
    +
    +
    +  notes:
    +    can not change neighbors of newvertex (since it's a subset)
    +
    +  design:
    +    for each ridge in ridges
    +      rename oldvertex to newvertex and delete degenerate ridges
    +    if oldfacet not defined
    +      for each neighbor of oldvertex
    +        delete oldvertex from neighbor's vertices
    +        remove extra vertices from neighbor
    +      add oldvertex to qh.del_vertices
    +    else if oldvertex only between oldfacet and neighborA
    +      delete oldvertex from oldfacet and neighborA
    +      add oldvertex to qh.del_vertices
    +    else oldvertex is in oldfacet and neighborA and other facets (i.e., pinched)
    +      delete oldvertex from oldfacet
    +      delete oldfacet from oldvertice's neighbors
    +      remove extra vertices (e.g., oldvertex) from neighborA
    +*/
    +void qh_renamevertex(qhT *qh, vertexT *oldvertex, vertexT *newvertex, setT *ridges, facetT *oldfacet, facetT *neighborA) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  boolT istrace= False;
    +
    +  if (qh->IStracing >= 2 || oldvertex->id == qh->tracevertex_id ||
    +        newvertex->id == qh->tracevertex_id)
    +    istrace= True;
    +  FOREACHridge_(ridges)
    +    qh_renameridgevertex(qh, ridge, oldvertex, newvertex);
    +  if (!oldfacet) {
    +    zinc_(Zrenameall);
    +    if (istrace)
    +      qh_fprintf(qh, qh->ferr, 8082, "qh_renamevertex: renamed v%d to v%d in several facets\n",
    +               oldvertex->id, newvertex->id);
    +    FOREACHneighbor_(oldvertex) {
    +      qh_maydropneighbor(qh, neighbor);
    +      qh_setdelsorted(neighbor->vertices, oldvertex);
    +      if (qh_remove_extravertices(qh, neighbor))
    +        neighborp--; /* neighbor may be deleted */
    +    }
    +    if (!oldvertex->deleted) {
    +      oldvertex->deleted= True;
    +      qh_setappend(qh, &qh->del_vertices, oldvertex);
    +    }
    +  }else if (qh_setsize(qh, oldvertex->neighbors) == 2) {
    +    zinc_(Zrenameshare);
    +    if (istrace)
    +      qh_fprintf(qh, qh->ferr, 8083, "qh_renamevertex: renamed v%d to v%d in oldfacet f%d\n",
    +               oldvertex->id, newvertex->id, oldfacet->id);
    +    FOREACHneighbor_(oldvertex)
    +      qh_setdelsorted(neighbor->vertices, oldvertex);
    +    oldvertex->deleted= True;
    +    qh_setappend(qh, &qh->del_vertices, oldvertex);
    +  }else {
    +    zinc_(Zrenamepinch);
    +    if (istrace || qh->IStracing)
    +      qh_fprintf(qh, qh->ferr, 8084, "qh_renamevertex: renamed pinched v%d to v%d between f%d and f%d\n",
    +               oldvertex->id, newvertex->id, oldfacet->id, neighborA->id);
    +    qh_setdelsorted(oldfacet->vertices, oldvertex);
    +    qh_setdel(oldvertex->neighbors, oldfacet);
    +    qh_remove_extravertices(qh, neighborA);
    +  }
    +} /* renamevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_test_appendmerge(qh, facet, neighbor )
    +    tests facet/neighbor for convexity
    +    appends to mergeset if non-convex
    +    if pre-merging,
    +      nop if qh.SKIPconvex, or qh.MERGEexact and coplanar
    +
    +  returns:
    +    true if appends facet/neighbor to mergeset
    +    sets facet->center as needed
    +    does not change facet->seen
    +
    +  design:
    +    if qh.cos_max is defined
    +      if the angle between facet normals is too shallow
    +        append an angle-coplanar merge to qh.mergeset
    +        return True
    +    make facet's centrum if needed
    +    if facet's centrum is above the neighbor
    +      set isconcave
    +    else
    +      if facet's centrum is not below the neighbor
    +        set iscoplanar
    +      make neighbor's centrum if needed
    +      if neighbor's centrum is above the facet
    +        set isconcave
    +      else if neighbor's centrum is not below the facet
    +        set iscoplanar
    +   if isconcave or iscoplanar
    +     get angle if needed
    +     append concave or coplanar merge to qh.mergeset
    +*/
    +boolT qh_test_appendmerge(qhT *qh, facetT *facet, facetT *neighbor) {
    +  realT dist, dist2= -REALmax, angle= -REALmax;
    +  boolT isconcave= False, iscoplanar= False, okangle= False;
    +
    +  if (qh->SKIPconvex && !qh->POSTmerging)
    +    return False;
    +  if ((!qh->MERGEexact || qh->POSTmerging) && qh->cos_max < REALmax/2) {
    +    angle= qh_getangle(qh, facet->normal, neighbor->normal);
    +    zinc_(Zangletests);
    +    if (angle > qh->cos_max) {
    +      zinc_(Zcoplanarangle);
    +      qh_appendmergeset(qh, facet, neighbor, MRGanglecoplanar, &angle);
    +      trace2((qh, qh->ferr, 2039, "qh_test_appendmerge: coplanar angle %4.4g between f%d and f%d\n",
    +         angle, facet->id, neighbor->id));
    +      return True;
    +    }else
    +      okangle= True;
    +  }
    +  if (!facet->center)
    +    facet->center= qh_getcentrum(qh, facet);
    +  zzinc_(Zcentrumtests);
    +  qh_distplane(qh, facet->center, neighbor, &dist);
    +  if (dist > qh->centrum_radius)
    +    isconcave= True;
    +  else {
    +    if (dist > -qh->centrum_radius)
    +      iscoplanar= True;
    +    if (!neighbor->center)
    +      neighbor->center= qh_getcentrum(qh, neighbor);
    +    zzinc_(Zcentrumtests);
    +    qh_distplane(qh, neighbor->center, facet, &dist2);
    +    if (dist2 > qh->centrum_radius)
    +      isconcave= True;
    +    else if (!iscoplanar && dist2 > -qh->centrum_radius)
    +      iscoplanar= True;
    +  }
    +  if (!isconcave && (!iscoplanar || (qh->MERGEexact && !qh->POSTmerging)))
    +    return False;
    +  if (!okangle && qh->ANGLEmerge) {
    +    angle= qh_getangle(qh, facet->normal, neighbor->normal);
    +    zinc_(Zangletests);
    +  }
    +  if (isconcave) {
    +    zinc_(Zconcaveridge);
    +    if (qh->ANGLEmerge)
    +      angle += qh_ANGLEconcave + 0.5;
    +    qh_appendmergeset(qh, facet, neighbor, MRGconcave, &angle);
    +    trace0((qh, qh->ferr, 18, "qh_test_appendmerge: concave f%d to f%d dist %4.4g and reverse dist %4.4g angle %4.4g during p%d\n",
    +           facet->id, neighbor->id, dist, dist2, angle, qh->furthest_id));
    +  }else /* iscoplanar */ {
    +    zinc_(Zcoplanarcentrum);
    +    qh_appendmergeset(qh, facet, neighbor, MRGcoplanar, &angle);
    +    trace2((qh, qh->ferr, 2040, "qh_test_appendmerge: coplanar f%d to f%d dist %4.4g, reverse dist %4.4g angle %4.4g\n",
    +              facet->id, neighbor->id, dist, dist2, angle));
    +  }
    +  return True;
    +} /* test_appendmerge */
    +
    +/*---------------------------------
    +
    +  qh_test_vneighbors(qh)
    +    test vertex neighbors for convexity
    +    tests all facets on qh.newfacet_list
    +
    +  returns:
    +    true if non-convex vneighbors appended to qh.facet_mergeset
    +    initializes vertex neighbors if needed
    +
    +  notes:
    +    assumes all facet neighbors have been tested
    +    this can be expensive
    +    this does not guarantee that a centrum is below all facets
    +      but it is unlikely
    +    uses qh.visit_id
    +
    +  design:
    +    build vertex neighbors if necessary
    +    for all new facets
    +      for all vertices
    +        for each unvisited facet neighbor of the vertex
    +          test new facet and neighbor for convexity
    +*/
    +boolT qh_test_vneighbors(qhT *qh /* qh->newfacet_list */) {
    +  facetT *newfacet, *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  int nummerges= 0;
    +
    +  trace1((qh, qh->ferr, 1015, "qh_test_vneighbors: testing vertex neighbors for convexity\n"));
    +  if (!qh->VERTEXneighbors)
    +    qh_vertexneighbors(qh);
    +  FORALLnew_facets
    +    newfacet->seen= False;
    +  FORALLnew_facets {
    +    newfacet->seen= True;
    +    newfacet->visitid= qh->visit_id++;
    +    FOREACHneighbor_(newfacet)
    +      newfacet->visitid= qh->visit_id;
    +    FOREACHvertex_(newfacet->vertices) {
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->seen || neighbor->visitid == qh->visit_id)
    +          continue;
    +        if (qh_test_appendmerge(qh, newfacet, neighbor))
    +          nummerges++;
    +      }
    +    }
    +  }
    +  zadd_(Ztestvneighbor, nummerges);
    +  trace1((qh, qh->ferr, 1016, "qh_test_vneighbors: found %d non-convex, vertex neighbors\n",
    +           nummerges));
    +  return (nummerges > 0);
    +} /* test_vneighbors */
    +
    +/*---------------------------------
    +
    +  qh_tracemerge(qh, facet1, facet2 )
    +    print trace message after merge
    +*/
    +void qh_tracemerge(qhT *qh, facetT *facet1, facetT *facet2) {
    +  boolT waserror= False;
    +
    +#ifndef qh_NOtrace
    +  if (qh->IStracing >= 4)
    +    qh_errprint(qh, "MERGED", facet2, NULL, NULL, NULL);
    +  if (facet2 == qh->tracefacet || (qh->tracevertex && qh->tracevertex->newlist)) {
    +    qh_fprintf(qh, qh->ferr, 8085, "qh_tracemerge: trace facet and vertex after merge of f%d and f%d, furthest p%d\n", facet1->id, facet2->id, qh->furthest_id);
    +    if (facet2 != qh->tracefacet)
    +      qh_errprint(qh, "TRACE", qh->tracefacet,
    +        (qh->tracevertex && qh->tracevertex->neighbors) ?
    +           SETfirstt_(qh->tracevertex->neighbors, facetT) : NULL,
    +        NULL, qh->tracevertex);
    +  }
    +  if (qh->tracevertex) {
    +    if (qh->tracevertex->deleted)
    +      qh_fprintf(qh, qh->ferr, 8086, "qh_tracemerge: trace vertex deleted at furthest p%d\n",
    +            qh->furthest_id);
    +    else
    +      qh_checkvertex(qh, qh->tracevertex);
    +  }
    +  if (qh->tracefacet) {
    +    qh_checkfacet(qh, qh->tracefacet, True, &waserror);
    +    if (waserror)
    +      qh_errexit(qh, qh_ERRqhull, qh->tracefacet, NULL);
    +  }
    +#endif /* !qh_NOtrace */
    +  if (qh->CHECKfrequently || qh->IStracing >= 4) { /* can't check polygon here */
    +    qh_checkfacet(qh, facet2, True, &waserror);
    +    if (waserror)
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +} /* tracemerge */
    +
    +/*---------------------------------
    +
    +  qh_tracemerging(qh)
    +    print trace message during POSTmerging
    +
    +  returns:
    +    updates qh.mergereport
    +
    +  notes:
    +    called from qh_mergecycle() and qh_mergefacet()
    +
    +  see:
    +    qh_buildtracing()
    +*/
    +void qh_tracemerging(qhT *qh) {
    +  realT cpu;
    +  int total;
    +  time_t timedata;
    +  struct tm *tp;
    +
    +  qh->mergereport= zzval_(Ztotmerge);
    +  time(&timedata);
    +  tp= localtime(&timedata);
    +  cpu= qh_CPUclock;
    +  cpu /= qh_SECticks;
    +  total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +  qh_fprintf(qh, qh->ferr, 8087, "\n\
    +At %d:%d:%d & %2.5g CPU secs, qhull has merged %d facets.  The hull\n\
    +  contains %d facets and %d vertices.\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu,
    +      total, qh->num_facets - qh->num_visible,
    +      qh->num_vertices-qh_setsize(qh, qh->del_vertices));
    +} /* tracemerging */
    +
    +/*---------------------------------
    +
    +  qh_updatetested(qh, facet1, facet2 )
    +    clear facet2->tested and facet1->ridge->tested for merge
    +
    +  returns:
    +    deletes facet2->center unless it's already large
    +      if so, clears facet2->ridge->tested
    +
    +  design:
    +    clear facet2->tested
    +    clear ridge->tested for facet1's ridges
    +    if facet2 has a centrum
    +      if facet2 is large
    +        set facet2->keepcentrum
    +      else if facet2 has 3 vertices due to many merges, or not large and post merging
    +        clear facet2->keepcentrum
    +      unless facet2->keepcentrum
    +        clear facet2->center to recompute centrum later
    +        clear ridge->tested for facet2's ridges
    +*/
    +void qh_updatetested(qhT *qh, facetT *facet1, facetT *facet2) {
    +  ridgeT *ridge, **ridgep;
    +  int size;
    +
    +  facet2->tested= False;
    +  FOREACHridge_(facet1->ridges)
    +    ridge->tested= False;
    +  if (!facet2->center)
    +    return;
    +  size= qh_setsize(qh, facet2->vertices);
    +  if (!facet2->keepcentrum) {
    +    if (size > qh->hull_dim + qh_MAXnewcentrum) {
    +      facet2->keepcentrum= True;
    +      zinc_(Zwidevertices);
    +    }
    +  }else if (size <= qh->hull_dim + qh_MAXnewcentrum) {
    +    /* center and keepcentrum was set */
    +    if (size == qh->hull_dim || qh->POSTmerging)
    +      facet2->keepcentrum= False; /* if many merges need to recompute centrum */
    +  }
    +  if (!facet2->keepcentrum) {
    +    qh_memfree(qh, facet2->center, qh->normal_size);
    +    facet2->center= NULL;
    +    FOREACHridge_(facet2->ridges)
    +      ridge->tested= False;
    +  }
    +} /* updatetested */
    +
    +/*---------------------------------
    +
    +  qh_vertexridges(qh, vertex )
    +    return temporary set of ridges adjacent to a vertex
    +    vertex->neighbors defined
    +
    +  ntoes:
    +    uses qh.visit_id
    +    does not include implicit ridges for simplicial facets
    +
    +  design:
    +    for each neighbor of vertex
    +      add ridges that include the vertex to ridges
    +*/
    +setT *qh_vertexridges(qhT *qh, vertexT *vertex) {
    +  facetT *neighbor, **neighborp;
    +  setT *ridges= qh_settemp(qh, qh->TEMPsize);
    +  int size;
    +
    +  qh->visit_id++;
    +  FOREACHneighbor_(vertex)
    +    neighbor->visitid= qh->visit_id;
    +  FOREACHneighbor_(vertex) {
    +    if (*neighborp)   /* no new ridges in last neighbor */
    +      qh_vertexridges_facet(qh, vertex, neighbor, &ridges);
    +  }
    +  if (qh->PRINTstatistics || qh->IStracing) {
    +    size= qh_setsize(qh, ridges);
    +    zinc_(Zvertexridge);
    +    zadd_(Zvertexridgetot, size);
    +    zmax_(Zvertexridgemax, size);
    +    trace3((qh, qh->ferr, 3011, "qh_vertexridges: found %d ridges for v%d\n",
    +             size, vertex->id));
    +  }
    +  return ridges;
    +} /* vertexridges */
    +
    +/*---------------------------------
    +
    +  qh_vertexridges_facet(qh, vertex, facet, ridges )
    +    add adjacent ridges for vertex in facet
    +    neighbor->visitid==qh.visit_id if it hasn't been visited
    +
    +  returns:
    +    ridges updated
    +    sets facet->visitid to qh.visit_id-1
    +
    +  design:
    +    for each ridge of facet
    +      if ridge of visited neighbor (i.e., unprocessed)
    +        if vertex in ridge
    +          append ridge to vertex
    +    mark facet processed
    +*/
    +void qh_vertexridges_facet(qhT *qh, vertexT *vertex, facetT *facet, setT **ridges) {
    +  ridgeT *ridge, **ridgep;
    +  facetT *neighbor;
    +
    +  FOREACHridge_(facet->ridges) {
    +    neighbor= otherfacet_(ridge, facet);
    +    if (neighbor->visitid == qh->visit_id
    +    && qh_setin(ridge->vertices, vertex))
    +      qh_setappend(qh, ridges, ridge);
    +  }
    +  facet->visitid= qh->visit_id-1;
    +} /* vertexridges_facet */
    +
    +/*---------------------------------
    +
    +  qh_willdelete(qh, facet, replace )
    +    moves facet to visible list
    +    sets facet->f.replace to replace (may be NULL)
    +
    +  returns:
    +    bumps qh.num_visible
    +*/
    +void qh_willdelete(qhT *qh, facetT *facet, facetT *replace) {
    +
    +  qh_removefacet(qh, facet);
    +  qh_prependfacet(qh, facet, &qh->visible_list);
    +  qh->num_visible++;
    +  facet->visible= True;
    +  facet->f.replace= replace;
    +} /* willdelete */
    +
    +#else /* qh_NOmerge */
    +void qh_premerge(qhT *qh, vertexT *apex, realT maxcentrum, realT maxangle) {
    +}
    +void qh_postmerge(qhT *qh, const char *reason, realT maxcentrum, realT maxangle,
    +                      boolT vneighbors) {
    +}
    +boolT qh_checkzero(qhT *qh, boolT testall) {
    +   }
    +#endif /* qh_NOmerge */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/merge_r.h b/xs/src/qhull/src/libqhull_r/merge_r.h
    new file mode 100644
    index 0000000000..30a51815da
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/merge_r.h
    @@ -0,0 +1,186 @@
    +/*
      ---------------------------------
    +
    +   merge_r.h
    +   header file for merge_r.c
    +
    +   see qh-merge_r.htm and merge_r.c
    +
    +   Copyright (c) 1993-2015 C.B. Barber.
    +   $Id: //main/2015/qhull/src/libqhull_r/merge_r.h#3 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFmerge
    +#define qhDEFmerge 1
    +
    +#include "libqhull_r.h"
    +
    +
    +/*============ -constants- ==============*/
    +
    +/*----------------------------------
    +
    +  qh_ANGLEredundant
    +    indicates redundant merge in mergeT->angle
    +*/
    +#define qh_ANGLEredundant 6.0
    +
    +/*----------------------------------
    +
    +  qh_ANGLEdegen
    +    indicates degenerate facet in mergeT->angle
    +*/
    +#define qh_ANGLEdegen     5.0
    +
    +/*----------------------------------
    +
    +  qh_ANGLEconcave
    +    offset to indicate concave facets in mergeT->angle
    +
    +  notes:
    +    concave facets are assigned the range of [2,4] in mergeT->angle
    +    roundoff error may make the angle less than 2
    +*/
    +#define qh_ANGLEconcave  1.5
    +
    +/*----------------------------------
    +
    +  MRG... (mergeType)
    +    indicates the type of a merge (mergeT->type)
    +*/
    +typedef enum {  /* in sort order for facet_mergeset */
    +  MRGnone= 0,
    +  MRGcoplanar,          /* centrum coplanar */
    +  MRGanglecoplanar,     /* angle coplanar */
    +                        /* could detect half concave ridges */
    +  MRGconcave,           /* concave ridge */
    +  MRGflip,              /* flipped facet. facet1 == facet2 */
    +  MRGridge,             /* duplicate ridge (qh_MERGEridge) */
    +                        /* degen and redundant go onto degen_mergeset */
    +  MRGdegen,             /* degenerate facet (!enough neighbors) facet1 == facet2 */
    +  MRGredundant,         /* redundant facet (vertex subset) */
    +                        /* merge_degenredundant assumes degen < redundant */
    +  MRGmirror,            /* mirror facet from qh_triangulate */
    +  ENDmrg
    +} mergeType;
    +
    +/*----------------------------------
    +
    +  qh_MERGEapex
    +    flag for qh_mergefacet() to indicate an apex merge
    +*/
    +#define qh_MERGEapex     True
    +
    +/*============ -structures- ====================*/
    +
    +/*----------------------------------
    +
    +  mergeT
    +    structure used to merge facets
    +*/
    +
    +typedef struct mergeT mergeT;
    +struct mergeT {         /* initialize in qh_appendmergeset */
    +  realT   angle;        /* angle between normals of facet1 and facet2 */
    +  facetT *facet1;       /* will merge facet1 into facet2 */
    +  facetT *facet2;
    +  mergeType type;
    +};
    +
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  FOREACHmerge_( merges ) {...}
    +    assign 'merge' to each merge in merges
    +
    +  notes:
    +    uses 'mergeT *merge, **mergep;'
    +    if qh_mergefacet(),
    +      restart since qh.facet_mergeset may change
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHmerge_( merges ) FOREACHsetelement_(mergeT, merges, merge)
    +
    +/*============ prototypes in alphabetical order after pre/postmerge =======*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_premerge(qhT *qh, vertexT *apex, realT maxcentrum, realT maxangle);
    +void    qh_postmerge(qhT *qh, const char *reason, realT maxcentrum, realT maxangle,
    +             boolT vneighbors);
    +void    qh_all_merges(qhT *qh, boolT othermerge, boolT vneighbors);
    +void    qh_appendmergeset(qhT *qh, facetT *facet, facetT *neighbor, mergeType mergetype, realT *angle);
    +setT   *qh_basevertices(qhT *qh, facetT *samecycle);
    +void    qh_checkconnect(qhT *qh /* qh.new_facets */);
    +boolT   qh_checkzero(qhT *qh, boolT testall);
    +int     qh_compareangle(const void *p1, const void *p2);
    +int     qh_comparemerge(const void *p1, const void *p2);
    +int     qh_comparevisit(const void *p1, const void *p2);
    +void    qh_copynonconvex(qhT *qh, ridgeT *atridge);
    +void    qh_degen_redundant_facet(qhT *qh, facetT *facet);
    +void    qh_degen_redundant_neighbors(qhT *qh, facetT *facet, facetT *delfacet);
    +vertexT *qh_find_newvertex(qhT *qh, vertexT *oldvertex, setT *vertices, setT *ridges);
    +void    qh_findbest_test(qhT *qh, boolT testcentrum, facetT *facet, facetT *neighbor,
    +           facetT **bestfacet, realT *distp, realT *mindistp, realT *maxdistp);
    +facetT *qh_findbestneighbor(qhT *qh, facetT *facet, realT *distp, realT *mindistp, realT *maxdistp);
    +void    qh_flippedmerges(qhT *qh, facetT *facetlist, boolT *wasmerge);
    +void    qh_forcedmerges(qhT *qh, boolT *wasmerge);
    +void    qh_getmergeset(qhT *qh, facetT *facetlist);
    +void    qh_getmergeset_initial(qhT *qh, facetT *facetlist);
    +void    qh_hashridge(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge, vertexT *oldvertex);
    +ridgeT *qh_hashridge_find(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge,
    +              vertexT *vertex, vertexT *oldvertex, int *hashslot);
    +void    qh_makeridges(qhT *qh, facetT *facet);
    +void    qh_mark_dupridges(qhT *qh, facetT *facetlist);
    +void    qh_maydropneighbor(qhT *qh, facetT *facet);
    +int     qh_merge_degenredundant(qhT *qh);
    +void    qh_merge_nonconvex(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype);
    +void    qh_mergecycle(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_all(qhT *qh, facetT *facetlist, boolT *wasmerge);
    +void    qh_mergecycle_facets(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_neighbors(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_ridges(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_vneighbors(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergefacet(qhT *qh, facetT *facet1, facetT *facet2, realT *mindist, realT *maxdist, boolT mergeapex);
    +void    qh_mergefacet2d(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_mergeneighbors(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_mergeridges(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_mergesimplex(qhT *qh, facetT *facet1, facetT *facet2, boolT mergeapex);
    +void    qh_mergevertex_del(qhT *qh, vertexT *vertex, facetT *facet1, facetT *facet2);
    +void    qh_mergevertex_neighbors(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_mergevertices(qhT *qh, setT *vertices1, setT **vertices);
    +setT   *qh_neighbor_intersections(qhT *qh, vertexT *vertex);
    +void    qh_newvertices(qhT *qh, setT *vertices);
    +boolT   qh_reducevertices(qhT *qh);
    +vertexT *qh_redundant_vertex(qhT *qh, vertexT *vertex);
    +boolT   qh_remove_extravertices(qhT *qh, facetT *facet);
    +vertexT *qh_rename_sharedvertex(qhT *qh, vertexT *vertex, facetT *facet);
    +void    qh_renameridgevertex(qhT *qh, ridgeT *ridge, vertexT *oldvertex, vertexT *newvertex);
    +void    qh_renamevertex(qhT *qh, vertexT *oldvertex, vertexT *newvertex, setT *ridges,
    +                        facetT *oldfacet, facetT *neighborA);
    +boolT   qh_test_appendmerge(qhT *qh, facetT *facet, facetT *neighbor);
    +boolT   qh_test_vneighbors(qhT *qh /* qh.newfacet_list */);
    +void    qh_tracemerge(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_tracemerging(qhT *qh);
    +void    qh_updatetested(qhT *qh, facetT *facet1, facetT *facet2);
    +setT   *qh_vertexridges(qhT *qh, vertexT *vertex);
    +void    qh_vertexridges_facet(qhT *qh, vertexT *vertex, facetT *facet, setT **ridges);
    +void    qh_willdelete(qhT *qh, facetT *facet, facetT *replace);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFmerge */
    diff --git a/xs/src/qhull/src/libqhull_r/poly2_r.c b/xs/src/qhull/src/libqhull_r/poly2_r.c
    new file mode 100644
    index 0000000000..b8ae9af9f4
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/poly2_r.c
    @@ -0,0 +1,3222 @@
    +/*
      ---------------------------------
    +
    +   poly2_r.c
    +   implements polygons and simplicies
    +
    +   see qh-poly_r.htm, poly_r.h and libqhull_r.h
    +
    +   frequently used code is in poly_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/poly2_r.c#10 $$Change: 2069 $
    +   $DateTime: 2016/01/18 22:05:03 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*======== functions in alphabetical order ==========*/
    +
    +/*---------------------------------
    +
    +  qh_addhash( newelem, hashtable, hashsize, hash )
    +    add newelem to linear hash table at hash if not already there
    +*/
    +void qh_addhash(void *newelem, setT *hashtable, int hashsize, int hash) {
    +  int scan;
    +  void *elem;
    +
    +  for (scan= (int)hash; (elem= SETelem_(hashtable, scan));
    +       scan= (++scan >= hashsize ? 0 : scan)) {
    +    if (elem == newelem)
    +      break;
    +  }
    +  /* loop terminates because qh_HASHfactor >= 1.1 by qh_initbuffers */
    +  if (!elem)
    +    SETelem_(hashtable, scan)= newelem;
    +} /* addhash */
    +
    +/*---------------------------------
    +
    +  qh_check_bestdist(qh)
    +    check that all points are within max_outside of the nearest facet
    +    if qh.ONLYgood,
    +      ignores !good facets
    +
    +  see:
    +    qh_check_maxout(), qh_outerinner()
    +
    +  notes:
    +    only called from qh_check_points()
    +      seldom used since qh.MERGING is almost always set
    +    if notverified>0 at end of routine
    +      some points were well inside the hull.  If the hull contains
    +      a lens-shaped component, these points were not verified.  Use
    +      options 'Qi Tv' to verify all points.  (Exhaustive check also verifies)
    +
    +  design:
    +    determine facet for each point (if any)
    +    for each point
    +      start with the assigned facet or with the first facet
    +      find the best facet for the point and check all coplanar facets
    +      error if point is outside of facet
    +*/
    +void qh_check_bestdist(qhT *qh) {
    +  boolT waserror= False, unassigned;
    +  facetT *facet, *bestfacet, *errfacet1= NULL, *errfacet2= NULL;
    +  facetT *facetlist;
    +  realT dist, maxoutside, maxdist= -REALmax;
    +  pointT *point;
    +  int numpart= 0, facet_i, facet_n, notgood= 0, notverified= 0;
    +  setT *facets;
    +
    +  trace1((qh, qh->ferr, 1020, "qh_check_bestdist: check points below nearest facet.  Facet_list f%d\n",
    +      qh->facet_list->id));
    +  maxoutside= qh_maxouter(qh);
    +  maxoutside += qh->DISTround;
    +  /* one more qh.DISTround for check computation */
    +  trace1((qh, qh->ferr, 1021, "qh_check_bestdist: check that all points are within %2.2g of best facet\n", maxoutside));
    +  facets= qh_pointfacet(qh /*qh.facet_list*/);
    +  if (!qh_QUICKhelp && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 8091, "\n\
    +qhull output completed.  Verifying that %d points are\n\
    +below %2.2g of the nearest %sfacet.\n",
    +             qh_setsize(qh, facets), maxoutside, (qh->ONLYgood ?  "good " : ""));
    +  FOREACHfacet_i_(qh, facets) {  /* for each point with facet assignment */
    +    if (facet)
    +      unassigned= False;
    +    else {
    +      unassigned= True;
    +      facet= qh->facet_list;
    +    }
    +    point= qh_point(qh, facet_i);
    +    if (point == qh->GOODpointp)
    +      continue;
    +    qh_distplane(qh, point, facet, &dist);
    +    numpart++;
    +    bestfacet= qh_findbesthorizon(qh, !qh_IScheckmax, point, facet, qh_NOupper, &dist, &numpart);
    +    /* occurs after statistics reported */
    +    maximize_(maxdist, dist);
    +    if (dist > maxoutside) {
    +      if (qh->ONLYgood && !bestfacet->good
    +          && !((bestfacet= qh_findgooddist(qh, point, bestfacet, &dist, &facetlist))
    +               && dist > maxoutside))
    +        notgood++;
    +      else {
    +        waserror= True;
    +        qh_fprintf(qh, qh->ferr, 6109, "qhull precision error: point p%d is outside facet f%d, distance= %6.8g maxoutside= %6.8g\n",
    +                facet_i, bestfacet->id, dist, maxoutside);
    +        if (errfacet1 != bestfacet) {
    +          errfacet2= errfacet1;
    +          errfacet1= bestfacet;
    +        }
    +      }
    +    }else if (unassigned && dist < -qh->MAXcoplanar)
    +      notverified++;
    +  }
    +  qh_settempfree(qh, &facets);
    +  if (notverified && !qh->DELAUNAY && !qh_QUICKhelp && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 8092, "\n%d points were well inside the hull.  If the hull contains\n\
    +a lens-shaped component, these points were not verified.  Use\n\
    +options 'Qci Tv' to verify all points.\n", notverified);
    +  if (maxdist > qh->outside_err) {
    +    qh_fprintf(qh, qh->ferr, 6110, "qhull precision error (qh_check_bestdist): a coplanar point is %6.2g from convex hull.  The maximum value(qh.outside_err) is %6.2g\n",
    +              maxdist, qh->outside_err);
    +    qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2);
    +  }else if (waserror && qh->outside_err > REALmax/2)
    +    qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2);
    +  /* else if waserror, the error was logged to qh.ferr but does not effect the output */
    +  trace0((qh, qh->ferr, 20, "qh_check_bestdist: max distance outside %2.2g\n", maxdist));
    +} /* check_bestdist */
    +
    +/*---------------------------------
    +
    +  qh_check_dupridge(qh, facet1, dist1, facet2, dist2)
    +    Check duplicate ridge between facet1 and facet2 for wide merge
    +    dist1 is the maximum distance of facet1's vertices to facet2
    +    dist2 is the maximum distance of facet2's vertices to facet1
    +
    +  Returns
    +    Level 1 log of the duplicate ridge with the minimum distance between vertices
    +    Throws error if the merge will increase the maximum facet width by qh_WIDEduplicate (100x)
    +
    +  called from:
    +    qh_forcedmerges()
    +*/
    +#ifndef qh_NOmerge
    +void qh_check_dupridge(qhT *qh, facetT *facet1, realT dist1, facetT *facet2, realT dist2) {
    +  vertexT *vertex, **vertexp, *vertexA, **vertexAp;
    +  realT dist, innerplane, mergedist, outerplane, prevdist, ratio;
    +  realT minvertex= REALmax;
    +
    +  mergedist= fmin_(dist1, dist2);
    +  qh_outerinner(qh, NULL, &outerplane, &innerplane);  /* ratio from qh_printsummary */
    +  prevdist= fmax_(outerplane, innerplane);
    +  maximize_(prevdist, qh->ONEmerge + qh->DISTround);
    +  maximize_(prevdist, qh->MINoutside + qh->DISTround);
    +  ratio= mergedist/prevdist;
    +  FOREACHvertex_(facet1->vertices) {     /* The duplicate ridge is between facet1 and facet2, so either facet can be tested */
    +    FOREACHvertexA_(facet1->vertices) {
    +      if (vertex > vertexA){   /* Test each pair once */
    +        dist= qh_pointdist(vertex->point, vertexA->point, qh->hull_dim);
    +        minimize_(minvertex, dist);
    +      }
    +    }
    +  }
    +  trace0((qh, qh->ferr, 16, "qh_check_dupridge: duplicate ridge between f%d and f%d due to nearly-coincident vertices (%2.2g), dist %2.2g, reverse dist %2.2g, ratio %2.2g while processing p%d\n",
    +        facet1->id, facet2->id, minvertex, dist1, dist2, ratio, qh->furthest_id));
    +  if (ratio > qh_WIDEduplicate) {
    +    qh_fprintf(qh, qh->ferr, 6271, "qhull precision error (qh_check_dupridge): wide merge (%.0f times wider) due to duplicate ridge with nearly coincident points (%2.2g) between f%d and f%d, merge dist %2.2g, while processing p%d\n- Ignore error with option 'Q12'\n- To be fixed in a later version of Qhull\n",
    +          ratio, minvertex, facet1->id, facet2->id, mergedist, qh->furthest_id);
    +    if (qh->DELAUNAY)
    +      qh_fprintf(qh, qh->ferr, 8145, "- A bounding box for the input sites may alleviate this error.\n");
    +    if(minvertex > qh_WIDEduplicate*prevdist)
    +      qh_fprintf(qh, qh->ferr, 8146, "- Vertex distance %2.2g is greater than %d times maximum distance %2.2g\n  Please report to bradb@shore.net with steps to reproduce and all output\n",
    +          minvertex, qh_WIDEduplicate, prevdist);
    +    if (!qh->NOwide)
    +      qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +  }
    +} /* check_dupridge */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_check_maxout(qh)
    +    updates qh.max_outside by checking all points against bestfacet
    +    if qh.ONLYgood, ignores !good facets
    +
    +  returns:
    +    updates facet->maxoutside via qh_findbesthorizon()
    +    sets qh.maxoutdone
    +    if printing qh.min_vertex (qh_outerinner),
    +      it is updated to the current vertices
    +    removes inside/coplanar points from coplanarset as needed
    +
    +  notes:
    +    defines coplanar as min_vertex instead of MAXcoplanar
    +    may not need to check near-inside points because of qh.MAXcoplanar
    +      and qh.KEEPnearinside (before it was -DISTround)
    +
    +  see also:
    +    qh_check_bestdist()
    +
    +  design:
    +    if qh.min_vertex is needed
    +      for all neighbors of all vertices
    +        test distance from vertex to neighbor
    +    determine facet for each point (if any)
    +    for each point with an assigned facet
    +      find the best facet for the point and check all coplanar facets
    +        (updates outer planes)
    +    remove near-inside points from coplanar sets
    +*/
    +#ifndef qh_NOmerge
    +void qh_check_maxout(qhT *qh) {
    +  facetT *facet, *bestfacet, *neighbor, **neighborp, *facetlist;
    +  realT dist, maxoutside, minvertex, old_maxoutside;
    +  pointT *point;
    +  int numpart= 0, facet_i, facet_n, notgood= 0;
    +  setT *facets, *vertices;
    +  vertexT *vertex;
    +
    +  trace1((qh, qh->ferr, 1022, "qh_check_maxout: check and update maxoutside for each facet.\n"));
    +  maxoutside= minvertex= 0;
    +  if (qh->VERTEXneighbors
    +  && (qh->PRINTsummary || qh->KEEPinside || qh->KEEPcoplanar
    +        || qh->TRACElevel || qh->PRINTstatistics
    +        || qh->PRINTout[0] == qh_PRINTsummary || qh->PRINTout[0] == qh_PRINTnone)) {
    +    trace1((qh, qh->ferr, 1023, "qh_check_maxout: determine actual maxoutside and minvertex\n"));
    +    vertices= qh_pointvertex(qh /*qh.facet_list*/);
    +    FORALLvertices {
    +      FOREACHneighbor_(vertex) {
    +        zinc_(Zdistvertex);  /* distance also computed by main loop below */
    +        qh_distplane(qh, vertex->point, neighbor, &dist);
    +        minimize_(minvertex, dist);
    +        if (-dist > qh->TRACEdist || dist > qh->TRACEdist
    +        || neighbor == qh->tracefacet || vertex == qh->tracevertex)
    +          qh_fprintf(qh, qh->ferr, 8093, "qh_check_maxout: p%d(v%d) is %.2g from f%d\n",
    +                    qh_pointid(qh, vertex->point), vertex->id, dist, neighbor->id);
    +      }
    +    }
    +    if (qh->MERGING) {
    +      wmin_(Wminvertex, qh->min_vertex);
    +    }
    +    qh->min_vertex= minvertex;
    +    qh_settempfree(qh, &vertices);
    +  }
    +  facets= qh_pointfacet(qh /*qh.facet_list*/);
    +  do {
    +    old_maxoutside= fmax_(qh->max_outside, maxoutside);
    +    FOREACHfacet_i_(qh, facets) {     /* for each point with facet assignment */
    +      if (facet) {
    +        point= qh_point(qh, facet_i);
    +        if (point == qh->GOODpointp)
    +          continue;
    +        zzinc_(Ztotcheck);
    +        qh_distplane(qh, point, facet, &dist);
    +        numpart++;
    +        bestfacet= qh_findbesthorizon(qh, qh_IScheckmax, point, facet, !qh_NOupper, &dist, &numpart);
    +        if (bestfacet && dist > maxoutside) {
    +          if (qh->ONLYgood && !bestfacet->good
    +          && !((bestfacet= qh_findgooddist(qh, point, bestfacet, &dist, &facetlist))
    +               && dist > maxoutside))
    +            notgood++;
    +          else
    +            maxoutside= dist;
    +        }
    +        if (dist > qh->TRACEdist || (bestfacet && bestfacet == qh->tracefacet))
    +          qh_fprintf(qh, qh->ferr, 8094, "qh_check_maxout: p%d is %.2g above f%d\n",
    +          qh_pointid(qh, point), dist, (bestfacet ? bestfacet->id : UINT_MAX));
    +      }
    +    }
    +  }while
    +    (maxoutside > 2*old_maxoutside);
    +    /* if qh.maxoutside increases substantially, qh_SEARCHdist is not valid
    +          e.g., RBOX 5000 s Z1 G1e-13 t1001200614 | qhull */
    +  zzadd_(Zcheckpart, numpart);
    +  qh_settempfree(qh, &facets);
    +  wval_(Wmaxout)= maxoutside - qh->max_outside;
    +  wmax_(Wmaxoutside, qh->max_outside);
    +  qh->max_outside= maxoutside;
    +  qh_nearcoplanar(qh /*qh.facet_list*/);
    +  qh->maxoutdone= True;
    +  trace1((qh, qh->ferr, 1024, "qh_check_maxout: maxoutside %2.2g, min_vertex %2.2g, outside of not good %d\n",
    +       maxoutside, qh->min_vertex, notgood));
    +} /* check_maxout */
    +#else /* qh_NOmerge */
    +void qh_check_maxout(qhT *qh) {
    +}
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_check_output(qh)
    +    performs the checks at the end of qhull algorithm
    +    Maybe called after voronoi output.  Will recompute otherwise centrums are Voronoi centers instead
    +*/
    +void qh_check_output(qhT *qh) {
    +  int i;
    +
    +  if (qh->STOPcone)
    +    return;
    +  if (qh->VERIFYoutput | qh->IStracing | qh->CHECKfrequently) {
    +    qh_checkpolygon(qh, qh->facet_list);
    +    qh_checkflipped_all(qh, qh->facet_list);
    +    qh_checkconvex(qh, qh->facet_list, qh_ALGORITHMfault);
    +  }else if (!qh->MERGING && qh_newstats(qh, qh->qhstat.precision, &i)) {
    +    qh_checkflipped_all(qh, qh->facet_list);
    +    qh_checkconvex(qh, qh->facet_list, qh_ALGORITHMfault);
    +  }
    +} /* check_output */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_check_point(qh, point, facet, maxoutside, maxdist, errfacet1, errfacet2 )
    +    check that point is less than maxoutside from facet
    +*/
    +void qh_check_point(qhT *qh, pointT *point, facetT *facet, realT *maxoutside, realT *maxdist, facetT **errfacet1, facetT **errfacet2) {
    +  realT dist;
    +
    +  /* occurs after statistics reported */
    +  qh_distplane(qh, point, facet, &dist);
    +  if (dist > *maxoutside) {
    +    if (*errfacet1 != facet) {
    +      *errfacet2= *errfacet1;
    +      *errfacet1= facet;
    +    }
    +    qh_fprintf(qh, qh->ferr, 6111, "qhull precision error: point p%d is outside facet f%d, distance= %6.8g maxoutside= %6.8g\n",
    +              qh_pointid(qh, point), facet->id, dist, *maxoutside);
    +  }
    +  maximize_(*maxdist, dist);
    +} /* qh_check_point */
    +
    +
    +/*---------------------------------
    +
    +  qh_check_points(qh)
    +    checks that all points are inside all facets
    +
    +  notes:
    +    if many points and qh_check_maxout not called (i.e., !qh.MERGING),
    +       calls qh_findbesthorizon (seldom done).
    +    ignores flipped facets
    +    maxoutside includes 2 qh.DISTrounds
    +      one qh.DISTround for the computed distances in qh_check_points
    +    qh_printafacet and qh_printsummary needs only one qh.DISTround
    +    the computation for qh.VERIFYdirect does not account for qh.other_points
    +
    +  design:
    +    if many points
    +      use qh_check_bestdist()
    +    else
    +      for all facets
    +        for all points
    +          check that point is inside facet
    +*/
    +void qh_check_points(qhT *qh) {
    +  facetT *facet, *errfacet1= NULL, *errfacet2= NULL;
    +  realT total, maxoutside, maxdist= -REALmax;
    +  pointT *point, **pointp, *pointtemp;
    +  boolT testouter;
    +
    +  maxoutside= qh_maxouter(qh);
    +  maxoutside += qh->DISTround;
    +  /* one more qh.DISTround for check computation */
    +  trace1((qh, qh->ferr, 1025, "qh_check_points: check all points below %2.2g of all facet planes\n",
    +          maxoutside));
    +  if (qh->num_good)   /* miss counts other_points and !good facets */
    +     total= (float)qh->num_good * (float)qh->num_points;
    +  else
    +     total= (float)qh->num_facets * (float)qh->num_points;
    +  if (total >= qh_VERIFYdirect && !qh->maxoutdone) {
    +    if (!qh_QUICKhelp && qh->SKIPcheckmax && qh->MERGING)
    +      qh_fprintf(qh, qh->ferr, 7075, "qhull input warning: merging without checking outer planes('Q5' or 'Po').\n\
    +Verify may report that a point is outside of a facet.\n");
    +    qh_check_bestdist(qh);
    +  }else {
    +    if (qh_MAXoutside && qh->maxoutdone)
    +      testouter= True;
    +    else
    +      testouter= False;
    +    if (!qh_QUICKhelp) {
    +      if (qh->MERGEexact)
    +        qh_fprintf(qh, qh->ferr, 7076, "qhull input warning: exact merge ('Qx').  Verify may report that a point\n\
    +is outside of a facet.  See qh-optq.htm#Qx\n");
    +      else if (qh->SKIPcheckmax || qh->NOnearinside)
    +        qh_fprintf(qh, qh->ferr, 7077, "qhull input warning: no outer plane check ('Q5') or no processing of\n\
    +near-inside points ('Q8').  Verify may report that a point is outside\n\
    +of a facet.\n");
    +    }
    +    if (qh->PRINTprecision) {
    +      if (testouter)
    +        qh_fprintf(qh, qh->ferr, 8098, "\n\
    +Output completed.  Verifying that all points are below outer planes of\n\
    +all %sfacets.  Will make %2.0f distance computations.\n",
    +              (qh->ONLYgood ?  "good " : ""), total);
    +      else
    +        qh_fprintf(qh, qh->ferr, 8099, "\n\
    +Output completed.  Verifying that all points are below %2.2g of\n\
    +all %sfacets.  Will make %2.0f distance computations.\n",
    +              maxoutside, (qh->ONLYgood ?  "good " : ""), total);
    +    }
    +    FORALLfacets {
    +      if (!facet->good && qh->ONLYgood)
    +        continue;
    +      if (facet->flipped)
    +        continue;
    +      if (!facet->normal) {
    +        qh_fprintf(qh, qh->ferr, 7061, "qhull warning (qh_check_points): missing normal for facet f%d\n", facet->id);
    +        continue;
    +      }
    +      if (testouter) {
    +#if qh_MAXoutside
    +        maxoutside= facet->maxoutside + 2* qh->DISTround;
    +        /* one DISTround to actual point and another to computed point */
    +#endif
    +      }
    +      FORALLpoints {
    +        if (point != qh->GOODpointp)
    +          qh_check_point(qh, point, facet, &maxoutside, &maxdist, &errfacet1, &errfacet2);
    +      }
    +      FOREACHpoint_(qh->other_points) {
    +        if (point != qh->GOODpointp)
    +          qh_check_point(qh, point, facet, &maxoutside, &maxdist, &errfacet1, &errfacet2);
    +      }
    +    }
    +    if (maxdist > qh->outside_err) {
    +      qh_fprintf(qh, qh->ferr, 6112, "qhull precision error (qh_check_points): a coplanar point is %6.2g from convex hull.  The maximum value(qh.outside_err) is %6.2g\n",
    +                maxdist, qh->outside_err );
    +      qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2 );
    +    }else if (errfacet1 && qh->outside_err > REALmax/2)
    +        qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2 );
    +    /* else if errfacet1, the error was logged to qh.ferr but does not effect the output */
    +    trace0((qh, qh->ferr, 21, "qh_check_points: max distance outside %2.2g\n", maxdist));
    +  }
    +} /* check_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkconvex(qh, facetlist, fault )
    +    check that each ridge in facetlist is convex
    +    fault = qh_DATAfault if reporting errors
    +          = qh_ALGORITHMfault otherwise
    +
    +  returns:
    +    counts Zconcaveridges and Zcoplanarridges
    +    errors if concaveridge or if merging an coplanar ridge
    +
    +  note:
    +    if not merging,
    +      tests vertices for neighboring simplicial facets
    +    else if ZEROcentrum,
    +      tests vertices for neighboring simplicial   facets
    +    else
    +      tests centrums of neighboring facets
    +
    +  design:
    +    for all facets
    +      report flipped facets
    +      if ZEROcentrum and simplicial neighbors
    +        test vertices for neighboring simplicial facets
    +      else
    +        test centrum against all neighbors
    +*/
    +void qh_checkconvex(qhT *qh, facetT *facetlist, int fault) {
    +  facetT *facet, *neighbor, **neighborp, *errfacet1=NULL, *errfacet2=NULL;
    +  vertexT *vertex;
    +  realT dist;
    +  pointT *centrum;
    +  boolT waserror= False, centrum_warning= False, tempcentrum= False, allsimplicial;
    +  int neighbor_i;
    +
    +  trace1((qh, qh->ferr, 1026, "qh_checkconvex: check all ridges are convex\n"));
    +  if (!qh->RERUN) {
    +    zzval_(Zconcaveridges)= 0;
    +    zzval_(Zcoplanarridges)= 0;
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->flipped) {
    +      qh_precision(qh, "flipped facet");
    +      qh_fprintf(qh, qh->ferr, 6113, "qhull precision error: f%d is flipped(interior point is outside)\n",
    +               facet->id);
    +      errfacet1= facet;
    +      waserror= True;
    +      continue;
    +    }
    +    if (qh->MERGING && (!qh->ZEROcentrum || !facet->simplicial || facet->tricoplanar))
    +      allsimplicial= False;
    +    else {
    +      allsimplicial= True;
    +      neighbor_i= 0;
    +      FOREACHneighbor_(facet) {
    +        vertex= SETelemt_(facet->vertices, neighbor_i++, vertexT);
    +        if (!neighbor->simplicial || neighbor->tricoplanar) {
    +          allsimplicial= False;
    +          continue;
    +        }
    +        qh_distplane(qh, vertex->point, neighbor, &dist);
    +        if (dist > -qh->DISTround) {
    +          if (fault == qh_DATAfault) {
    +            qh_precision(qh, "coplanar or concave ridge");
    +            qh_fprintf(qh, qh->ferr, 6114, "qhull precision error: initial simplex is not convex. Distance=%.2g\n", dist);
    +            qh_errexit(qh, qh_ERRsingular, NULL, NULL);
    +          }
    +          if (dist > qh->DISTround) {
    +            zzinc_(Zconcaveridges);
    +            qh_precision(qh, "concave ridge");
    +            qh_fprintf(qh, qh->ferr, 6115, "qhull precision error: f%d is concave to f%d, since p%d(v%d) is %6.4g above\n",
    +              facet->id, neighbor->id, qh_pointid(qh, vertex->point), vertex->id, dist);
    +            errfacet1= facet;
    +            errfacet2= neighbor;
    +            waserror= True;
    +          }else if (qh->ZEROcentrum) {
    +            if (dist > 0) {     /* qh_checkzero checks that dist < - qh->DISTround */
    +              zzinc_(Zcoplanarridges);
    +              qh_precision(qh, "coplanar ridge");
    +              qh_fprintf(qh, qh->ferr, 6116, "qhull precision error: f%d is clearly not convex to f%d, since p%d(v%d) is %6.4g above\n",
    +                facet->id, neighbor->id, qh_pointid(qh, vertex->point), vertex->id, dist);
    +              errfacet1= facet;
    +              errfacet2= neighbor;
    +              waserror= True;
    +            }
    +          }else {
    +            zzinc_(Zcoplanarridges);
    +            qh_precision(qh, "coplanar ridge");
    +            trace0((qh, qh->ferr, 22, "qhull precision error: f%d may be coplanar to f%d, since p%d(v%d) is within %6.4g during p%d\n",
    +              facet->id, neighbor->id, qh_pointid(qh, vertex->point), vertex->id, dist, qh->furthest_id));
    +          }
    +        }
    +      }
    +    }
    +    if (!allsimplicial) {
    +      if (qh->CENTERtype == qh_AScentrum) {
    +        if (!facet->center)
    +          facet->center= qh_getcentrum(qh, facet);
    +        centrum= facet->center;
    +      }else {
    +        if (!centrum_warning && (!facet->simplicial || facet->tricoplanar)) {
    +           centrum_warning= True;
    +           qh_fprintf(qh, qh->ferr, 7062, "qhull warning: recomputing centrums for convexity test.  This may lead to false, precision errors.\n");
    +        }
    +        centrum= qh_getcentrum(qh, facet);
    +        tempcentrum= True;
    +      }
    +      FOREACHneighbor_(facet) {
    +        if (qh->ZEROcentrum && facet->simplicial && neighbor->simplicial)
    +          continue;
    +        if (facet->tricoplanar || neighbor->tricoplanar)
    +          continue;
    +        zzinc_(Zdistconvex);
    +        qh_distplane(qh, centrum, neighbor, &dist);
    +        if (dist > qh->DISTround) {
    +          zzinc_(Zconcaveridges);
    +          qh_precision(qh, "concave ridge");
    +          qh_fprintf(qh, qh->ferr, 6117, "qhull precision error: f%d is concave to f%d.  Centrum of f%d is %6.4g above f%d\n",
    +            facet->id, neighbor->id, facet->id, dist, neighbor->id);
    +          errfacet1= facet;
    +          errfacet2= neighbor;
    +          waserror= True;
    +        }else if (dist >= 0.0) {   /* if arithmetic always rounds the same,
    +                                     can test against centrum radius instead */
    +          zzinc_(Zcoplanarridges);
    +          qh_precision(qh, "coplanar ridge");
    +          qh_fprintf(qh, qh->ferr, 6118, "qhull precision error: f%d is coplanar or concave to f%d.  Centrum of f%d is %6.4g above f%d\n",
    +            facet->id, neighbor->id, facet->id, dist, neighbor->id);
    +          errfacet1= facet;
    +          errfacet2= neighbor;
    +          waserror= True;
    +        }
    +      }
    +      if (tempcentrum)
    +        qh_memfree(qh, centrum, qh->normal_size);
    +    }
    +  }
    +  if (waserror && !qh->FORCEoutput)
    +    qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2);
    +} /* checkconvex */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkfacet(qh, facet, newmerge, waserror )
    +    checks for consistency errors in facet
    +    newmerge set if from merge_r.c
    +
    +  returns:
    +    sets waserror if any error occurs
    +
    +  checks:
    +    vertex ids are inverse sorted
    +    unless newmerge, at least hull_dim neighbors and vertices (exactly if simplicial)
    +    if non-simplicial, at least as many ridges as neighbors
    +    neighbors are not duplicated
    +    ridges are not duplicated
    +    in 3-d, ridges=verticies
    +    (qh.hull_dim-1) ridge vertices
    +    neighbors are reciprocated
    +    ridge neighbors are facet neighbors and a ridge for every neighbor
    +    simplicial neighbors match facetintersect
    +    vertex intersection matches vertices of common ridges
    +    vertex neighbors and facet vertices agree
    +    all ridges have distinct vertex sets
    +
    +  notes:
    +    uses neighbor->seen
    +
    +  design:
    +    check sets
    +    check vertices
    +    check sizes of neighbors and vertices
    +    check for qh_MERGEridge and qh_DUPLICATEridge flags
    +    check neighbor set
    +    check ridge set
    +    check ridges, neighbors, and vertices
    +*/
    +void qh_checkfacet(qhT *qh, facetT *facet, boolT newmerge, boolT *waserrorp) {
    +  facetT *neighbor, **neighborp, *errother=NULL;
    +  ridgeT *ridge, **ridgep, *errridge= NULL, *ridge2;
    +  vertexT *vertex, **vertexp;
    +  unsigned previousid= INT_MAX;
    +  int numneighbors, numvertices, numridges=0, numRvertices=0;
    +  boolT waserror= False;
    +  int skipA, skipB, ridge_i, ridge_n, i;
    +  setT *intersection;
    +
    +  if (facet->visible) {
    +    qh_fprintf(qh, qh->ferr, 6119, "qhull internal error (qh_checkfacet): facet f%d is on the visible_list\n",
    +      facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  if (!facet->normal) {
    +    qh_fprintf(qh, qh->ferr, 6120, "qhull internal error (qh_checkfacet): facet f%d does not have  a normal\n",
    +      facet->id);
    +    waserror= True;
    +  }
    +  qh_setcheck(qh, facet->vertices, "vertices for f", facet->id);
    +  qh_setcheck(qh, facet->ridges, "ridges for f", facet->id);
    +  qh_setcheck(qh, facet->outsideset, "outsideset for f", facet->id);
    +  qh_setcheck(qh, facet->coplanarset, "coplanarset for f", facet->id);
    +  qh_setcheck(qh, facet->neighbors, "neighbors for f", facet->id);
    +  FOREACHvertex_(facet->vertices) {
    +    if (vertex->deleted) {
    +      qh_fprintf(qh, qh->ferr, 6121, "qhull internal error (qh_checkfacet): deleted vertex v%d in f%d\n", vertex->id, facet->id);
    +      qh_errprint(qh, "ERRONEOUS", NULL, NULL, NULL, vertex);
    +      waserror= True;
    +    }
    +    if (vertex->id >= previousid) {
    +      qh_fprintf(qh, qh->ferr, 6122, "qhull internal error (qh_checkfacet): vertices of f%d are not in descending id order at v%d\n", facet->id, vertex->id);
    +      waserror= True;
    +      break;
    +    }
    +    previousid= vertex->id;
    +  }
    +  numneighbors= qh_setsize(qh, facet->neighbors);
    +  numvertices= qh_setsize(qh, facet->vertices);
    +  numridges= qh_setsize(qh, facet->ridges);
    +  if (facet->simplicial) {
    +    if (numvertices+numneighbors != 2*qh->hull_dim
    +    && !facet->degenerate && !facet->redundant) {
    +      qh_fprintf(qh, qh->ferr, 6123, "qhull internal error (qh_checkfacet): for simplicial facet f%d, #vertices %d + #neighbors %d != 2*qh->hull_dim\n",
    +                facet->id, numvertices, numneighbors);
    +      qh_setprint(qh, qh->ferr, "", facet->neighbors);
    +      waserror= True;
    +    }
    +  }else { /* non-simplicial */
    +    if (!newmerge
    +    &&(numvertices < qh->hull_dim || numneighbors < qh->hull_dim)
    +    && !facet->degenerate && !facet->redundant) {
    +      qh_fprintf(qh, qh->ferr, 6124, "qhull internal error (qh_checkfacet): for facet f%d, #vertices %d or #neighbors %d < qh->hull_dim\n",
    +         facet->id, numvertices, numneighbors);
    +       waserror= True;
    +    }
    +    /* in 3-d, can get a vertex twice in an edge list, e.g., RBOX 1000 s W1e-13 t995849315 D2 | QHULL d Tc Tv TP624 TW1e-13 T4 */
    +    if (numridges < numneighbors
    +    ||(qh->hull_dim == 3 && numvertices > numridges && !qh->NEWfacets)
    +    ||(qh->hull_dim == 2 && numridges + numvertices + numneighbors != 6)) {
    +      if (!facet->degenerate && !facet->redundant) {
    +        qh_fprintf(qh, qh->ferr, 6125, "qhull internal error (qh_checkfacet): for facet f%d, #ridges %d < #neighbors %d or(3-d) > #vertices %d or(2-d) not all 2\n",
    +            facet->id, numridges, numneighbors, numvertices);
    +        waserror= True;
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge || neighbor == qh_DUPLICATEridge) {
    +      qh_fprintf(qh, qh->ferr, 6126, "qhull internal error (qh_checkfacet): facet f%d still has a MERGE or DUP neighbor\n", facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +    neighbor->seen= True;
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (!qh_setin(neighbor->neighbors, facet)) {
    +      qh_fprintf(qh, qh->ferr, 6127, "qhull internal error (qh_checkfacet): facet f%d has neighbor f%d, but f%d does not have neighbor f%d\n",
    +              facet->id, neighbor->id, neighbor->id, facet->id);
    +      errother= neighbor;
    +      waserror= True;
    +    }
    +    if (!neighbor->seen) {
    +      qh_fprintf(qh, qh->ferr, 6128, "qhull internal error (qh_checkfacet): facet f%d has a duplicate neighbor f%d\n",
    +              facet->id, neighbor->id);
    +      errother= neighbor;
    +      waserror= True;
    +    }
    +    neighbor->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges) {
    +    qh_setcheck(qh, ridge->vertices, "vertices for r", ridge->id);
    +    ridge->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges) {
    +    if (ridge->seen) {
    +      qh_fprintf(qh, qh->ferr, 6129, "qhull internal error (qh_checkfacet): facet f%d has a duplicate ridge r%d\n",
    +              facet->id, ridge->id);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +    ridge->seen= True;
    +    numRvertices= qh_setsize(qh, ridge->vertices);
    +    if (numRvertices != qh->hull_dim - 1) {
    +      qh_fprintf(qh, qh->ferr, 6130, "qhull internal error (qh_checkfacet): ridge between f%d and f%d has %d vertices\n",
    +                ridge->top->id, ridge->bottom->id, numRvertices);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +    neighbor= otherfacet_(ridge, facet);
    +    neighbor->seen= True;
    +    if (!qh_setin(facet->neighbors, neighbor)) {
    +      qh_fprintf(qh, qh->ferr, 6131, "qhull internal error (qh_checkfacet): for facet f%d, neighbor f%d of ridge r%d not in facet\n",
    +           facet->id, neighbor->id, ridge->id);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +  }
    +  if (!facet->simplicial) {
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->seen) {
    +        qh_fprintf(qh, qh->ferr, 6132, "qhull internal error (qh_checkfacet): facet f%d does not have a ridge for neighbor f%d\n",
    +              facet->id, neighbor->id);
    +        errother= neighbor;
    +        waserror= True;
    +      }
    +      intersection= qh_vertexintersect_new(qh, facet->vertices, neighbor->vertices);
    +      qh_settemppush(qh, intersection);
    +      FOREACHvertex_(facet->vertices) {
    +        vertex->seen= False;
    +        vertex->seen2= False;
    +      }
    +      FOREACHvertex_(intersection)
    +        vertex->seen= True;
    +      FOREACHridge_(facet->ridges) {
    +        if (neighbor != otherfacet_(ridge, facet))
    +            continue;
    +        FOREACHvertex_(ridge->vertices) {
    +          if (!vertex->seen) {
    +            qh_fprintf(qh, qh->ferr, 6133, "qhull internal error (qh_checkfacet): vertex v%d in r%d not in f%d intersect f%d\n",
    +                  vertex->id, ridge->id, facet->id, neighbor->id);
    +            qh_errexit(qh, qh_ERRqhull, facet, ridge);
    +          }
    +          vertex->seen2= True;
    +        }
    +      }
    +      if (!newmerge) {
    +        FOREACHvertex_(intersection) {
    +          if (!vertex->seen2) {
    +            if (qh->IStracing >=3 || !qh->MERGING) {
    +              qh_fprintf(qh, qh->ferr, 6134, "qhull precision error (qh_checkfacet): vertex v%d in f%d intersect f%d but\n\
    + not in a ridge.  This is ok under merging.  Last point was p%d\n",
    +                     vertex->id, facet->id, neighbor->id, qh->furthest_id);
    +              if (!qh->FORCEoutput && !qh->MERGING) {
    +                qh_errprint(qh, "ERRONEOUS", facet, neighbor, NULL, vertex);
    +                if (!qh->MERGING)
    +                  qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +              }
    +            }
    +          }
    +        }
    +      }
    +      qh_settempfree(qh, &intersection);
    +    }
    +  }else { /* simplicial */
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->simplicial) {
    +        skipA= SETindex_(facet->neighbors, neighbor);
    +        skipB= qh_setindex(neighbor->neighbors, facet);
    +        if (skipA<0 || skipB<0 || !qh_setequal_skip(facet->vertices, skipA, neighbor->vertices, skipB)) {
    +          qh_fprintf(qh, qh->ferr, 6135, "qhull internal error (qh_checkfacet): facet f%d skip %d and neighbor f%d skip %d do not match \n",
    +                   facet->id, skipA, neighbor->id, skipB);
    +          errother= neighbor;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (qh->hull_dim < 5 && (qh->IStracing > 2 || qh->CHECKfrequently)) {
    +    FOREACHridge_i_(qh, facet->ridges) {           /* expensive */
    +      for (i=ridge_i+1; i < ridge_n; i++) {
    +        ridge2= SETelemt_(facet->ridges, i, ridgeT);
    +        if (qh_setequal(ridge->vertices, ridge2->vertices)) {
    +          qh_fprintf(qh, qh->ferr, 6227, "Qhull internal error (qh_checkfacet): ridges r%d and r%d have the same vertices\n",
    +                  ridge->id, ridge2->id);
    +          errridge= ridge;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_errprint(qh, "ERRONEOUS", facet, errother, errridge, NULL);
    +    *waserrorp= True;
    +  }
    +} /* checkfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkflipped_all(qh, facetlist )
    +    checks orientation of facets in list against interior point
    +*/
    +void qh_checkflipped_all(qhT *qh, facetT *facetlist) {
    +  facetT *facet;
    +  boolT waserror= False;
    +  realT dist;
    +
    +  if (facetlist == qh->facet_list)
    +    zzval_(Zflippedfacets)= 0;
    +  FORALLfacet_(facetlist) {
    +    if (facet->normal && !qh_checkflipped(qh, facet, &dist, !qh_ALL)) {
    +      qh_fprintf(qh, qh->ferr, 6136, "qhull precision error: facet f%d is flipped, distance= %6.12g\n",
    +              facet->id, dist);
    +      if (!qh->FORCEoutput) {
    +        qh_errprint(qh, "ERRONEOUS", facet, NULL, NULL, NULL);
    +        waserror= True;
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_fprintf(qh, qh->ferr, 8101, "\n\
    +A flipped facet occurs when its distance to the interior point is\n\
    +greater than %2.2g, the maximum roundoff error.\n", -qh->DISTround);
    +    qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +  }
    +} /* checkflipped_all */
    +
    +/*---------------------------------
    +
    +  qh_checkpolygon(qh, facetlist )
    +    checks the correctness of the structure
    +
    +  notes:
    +    call with either qh.facet_list or qh.newfacet_list
    +    checks num_facets and num_vertices if qh.facet_list
    +
    +  design:
    +    for each facet
    +      checks facet and outside set
    +    initializes vertexlist
    +    for each facet
    +      checks vertex set
    +    if checking all facets(qh.facetlist)
    +      check facet count
    +      if qh.VERTEXneighbors
    +        check vertex neighbors and count
    +      check vertex count
    +*/
    +void qh_checkpolygon(qhT *qh, facetT *facetlist) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp, *vertexlist;
    +  int numfacets= 0, numvertices= 0, numridges= 0;
    +  int totvneighbors= 0, totvertices= 0;
    +  boolT waserror= False, nextseen= False, visibleseen= False;
    +
    +  trace1((qh, qh->ferr, 1027, "qh_checkpolygon: check all facets from f%d\n", facetlist->id));
    +  if (facetlist != qh->facet_list || qh->ONLYgood)
    +    nextseen= True;
    +  FORALLfacet_(facetlist) {
    +    if (facet == qh->visible_list)
    +      visibleseen= True;
    +    if (!facet->visible) {
    +      if (!nextseen) {
    +        if (facet == qh->facet_next)
    +          nextseen= True;
    +        else if (qh_setsize(qh, facet->outsideset)) {
    +          if (!qh->NARROWhull
    +#if !qh_COMPUTEfurthest
    +               || facet->furthestdist >= qh->MINoutside
    +#endif
    +                        ) {
    +            qh_fprintf(qh, qh->ferr, 6137, "qhull internal error (qh_checkpolygon): f%d has outside points before qh->facet_next\n",
    +                     facet->id);
    +            qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +          }
    +        }
    +      }
    +      numfacets++;
    +      qh_checkfacet(qh, facet, False, &waserror);
    +    }
    +  }
    +  if (qh->visible_list && !visibleseen && facetlist == qh->facet_list) {
    +    qh_fprintf(qh, qh->ferr, 6138, "qhull internal error (qh_checkpolygon): visible list f%d no longer on facet list\n", qh->visible_list->id);
    +    qh_printlists(qh);
    +    qh_errexit(qh, qh_ERRqhull, qh->visible_list, NULL);
    +  }
    +  if (facetlist == qh->facet_list)
    +    vertexlist= qh->vertex_list;
    +  else if (facetlist == qh->newfacet_list)
    +    vertexlist= qh->newvertex_list;
    +  else
    +    vertexlist= NULL;
    +  FORALLvertex_(vertexlist) {
    +    vertex->seen= False;
    +    vertex->visitid= 0;
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->visible)
    +      continue;
    +    if (facet->simplicial)
    +      numridges += qh->hull_dim;
    +    else
    +      numridges += qh_setsize(qh, facet->ridges);
    +    FOREACHvertex_(facet->vertices) {
    +      vertex->visitid++;
    +      if (!vertex->seen) {
    +        vertex->seen= True;
    +        numvertices++;
    +        if (qh_pointid(qh, vertex->point) == qh_IDunknown) {
    +          qh_fprintf(qh, qh->ferr, 6139, "qhull internal error (qh_checkpolygon): unknown point %p for vertex v%d first_point %p\n",
    +                   vertex->point, vertex->id, qh->first_point);
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  qh->vertex_visit += (unsigned int)numfacets;
    +  if (facetlist == qh->facet_list) {
    +    if (numfacets != qh->num_facets - qh->num_visible) {
    +      qh_fprintf(qh, qh->ferr, 6140, "qhull internal error (qh_checkpolygon): actual number of facets is %d, cumulative facet count is %d - %d visible facets\n",
    +              numfacets, qh->num_facets, qh->num_visible);
    +      waserror= True;
    +    }
    +    qh->vertex_visit++;
    +    if (qh->VERTEXneighbors) {
    +      FORALLvertices {
    +        qh_setcheck(qh, vertex->neighbors, "neighbors for v", vertex->id);
    +        if (vertex->deleted)
    +          continue;
    +        totvneighbors += qh_setsize(qh, vertex->neighbors);
    +      }
    +      FORALLfacet_(facetlist)
    +        totvertices += qh_setsize(qh, facet->vertices);
    +      if (totvneighbors != totvertices) {
    +        qh_fprintf(qh, qh->ferr, 6141, "qhull internal error (qh_checkpolygon): vertex neighbors inconsistent.  Totvneighbors %d, totvertices %d\n",
    +                totvneighbors, totvertices);
    +        waserror= True;
    +      }
    +    }
    +    if (numvertices != qh->num_vertices - qh_setsize(qh, qh->del_vertices)) {
    +      qh_fprintf(qh, qh->ferr, 6142, "qhull internal error (qh_checkpolygon): actual number of vertices is %d, cumulative vertex count is %d\n",
    +              numvertices, qh->num_vertices - qh_setsize(qh, qh->del_vertices));
    +      waserror= True;
    +    }
    +    if (qh->hull_dim == 2 && numvertices != numfacets) {
    +      qh_fprintf(qh, qh->ferr, 6143, "qhull internal error (qh_checkpolygon): #vertices %d != #facets %d\n",
    +        numvertices, numfacets);
    +      waserror= True;
    +    }
    +    if (qh->hull_dim == 3 && numvertices + numfacets - numridges/2 != 2) {
    +      qh_fprintf(qh, qh->ferr, 7063, "qhull warning: #vertices %d + #facets %d - #edges %d != 2\n\
    +        A vertex appears twice in a edge list.  May occur during merging.",
    +        numvertices, numfacets, numridges/2);
    +      /* occurs if lots of merging and a vertex ends up twice in an edge list.  e.g., RBOX 1000 s W1e-13 t995849315 D2 | QHULL d Tc Tv */
    +    }
    +  }
    +  if (waserror)
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +} /* checkpolygon */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkvertex(qh, vertex )
    +    check vertex for consistency
    +    checks vertex->neighbors
    +
    +  notes:
    +    neighbors checked efficiently in checkpolygon
    +*/
    +void qh_checkvertex(qhT *qh, vertexT *vertex) {
    +  boolT waserror= False;
    +  facetT *neighbor, **neighborp, *errfacet=NULL;
    +
    +  if (qh_pointid(qh, vertex->point) == qh_IDunknown) {
    +    qh_fprintf(qh, qh->ferr, 6144, "qhull internal error (qh_checkvertex): unknown point id %p\n", vertex->point);
    +    waserror= True;
    +  }
    +  if (vertex->id >= qh->vertex_id) {
    +    qh_fprintf(qh, qh->ferr, 6145, "qhull internal error (qh_checkvertex): unknown vertex id %d\n", vertex->id);
    +    waserror= True;
    +  }
    +  if (!waserror && !vertex->deleted) {
    +    if (qh_setsize(qh, vertex->neighbors)) {
    +      FOREACHneighbor_(vertex) {
    +        if (!qh_setin(neighbor->vertices, vertex)) {
    +          qh_fprintf(qh, qh->ferr, 6146, "qhull internal error (qh_checkvertex): neighbor f%d does not contain v%d\n", neighbor->id, vertex->id);
    +          errfacet= neighbor;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_errprint(qh, "ERRONEOUS", NULL, NULL, NULL, vertex);
    +    qh_errexit(qh, qh_ERRqhull, errfacet, NULL);
    +  }
    +} /* checkvertex */
    +
    +/*---------------------------------
    +
    +  qh_clearcenters(qh, type )
    +    clear old data from facet->center
    +
    +  notes:
    +    sets new centertype
    +    nop if CENTERtype is the same
    +*/
    +void qh_clearcenters(qhT *qh, qh_CENTER type) {
    +  facetT *facet;
    +
    +  if (qh->CENTERtype != type) {
    +    FORALLfacets {
    +      if (facet->tricoplanar && !facet->keepcentrum)
    +          facet->center= NULL;  /* center is owned by the ->keepcentrum facet */
    +      else if (qh->CENTERtype == qh_ASvoronoi){
    +        if (facet->center) {
    +          qh_memfree(qh, facet->center, qh->center_size);
    +          facet->center= NULL;
    +        }
    +      }else /* qh->CENTERtype == qh_AScentrum */ {
    +        if (facet->center) {
    +          qh_memfree(qh, facet->center, qh->normal_size);
    +          facet->center= NULL;
    +        }
    +      }
    +    }
    +    qh->CENTERtype= type;
    +  }
    +  trace2((qh, qh->ferr, 2043, "qh_clearcenters: switched to center type %d\n", type));
    +} /* clearcenters */
    +
    +/*---------------------------------
    +
    +  qh_createsimplex(qh, vertices )
    +    creates a simplex from a set of vertices
    +
    +  returns:
    +    initializes qh.facet_list to the simplex
    +    initializes qh.newfacet_list, .facet_tail
    +    initializes qh.vertex_list, .newvertex_list, .vertex_tail
    +
    +  design:
    +    initializes lists
    +    for each vertex
    +      create a new facet
    +    for each new facet
    +      create its neighbor set
    +*/
    +void qh_createsimplex(qhT *qh, setT *vertices) {
    +  facetT *facet= NULL, *newfacet;
    +  boolT toporient= True;
    +  int vertex_i, vertex_n, nth;
    +  setT *newfacets= qh_settemp(qh, qh->hull_dim+1);
    +  vertexT *vertex;
    +
    +  qh->facet_list= qh->newfacet_list= qh->facet_tail= qh_newfacet(qh);
    +  qh->num_facets= qh->num_vertices= qh->num_visible= 0;
    +  qh->vertex_list= qh->newvertex_list= qh->vertex_tail= qh_newvertex(qh, NULL);
    +  FOREACHvertex_i_(qh, vertices) {
    +    newfacet= qh_newfacet(qh);
    +    newfacet->vertices= qh_setnew_delnthsorted(qh, vertices, vertex_n,
    +                                                vertex_i, 0);
    +    newfacet->toporient= (unsigned char)toporient;
    +    qh_appendfacet(qh, newfacet);
    +    newfacet->newfacet= True;
    +    qh_appendvertex(qh, vertex);
    +    qh_setappend(qh, &newfacets, newfacet);
    +    toporient ^= True;
    +  }
    +  FORALLnew_facets {
    +    nth= 0;
    +    FORALLfacet_(qh->newfacet_list) {
    +      if (facet != newfacet)
    +        SETelem_(newfacet->neighbors, nth++)= facet;
    +    }
    +    qh_settruncate(qh, newfacet->neighbors, qh->hull_dim);
    +  }
    +  qh_settempfree(qh, &newfacets);
    +  trace1((qh, qh->ferr, 1028, "qh_createsimplex: created simplex\n"));
    +} /* createsimplex */
    +
    +/*---------------------------------
    +
    +  qh_delridge(qh, ridge )
    +    deletes ridge from data structures it belongs to
    +    frees up its memory
    +
    +  notes:
    +    in merge_r.c, caller sets vertex->delridge for each vertex
    +    ridges also freed in qh_freeqhull
    +*/
    +void qh_delridge(qhT *qh, ridgeT *ridge) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  qh_setdel(ridge->top->ridges, ridge);
    +  qh_setdel(ridge->bottom->ridges, ridge);
    +  qh_setfree(qh, &(ridge->vertices));
    +  qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
    +} /* delridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_delvertex(qh, vertex )
    +    deletes a vertex and frees its memory
    +
    +  notes:
    +    assumes vertex->adjacencies have been updated if needed
    +    unlinks from vertex_list
    +*/
    +void qh_delvertex(qhT *qh, vertexT *vertex) {
    +
    +  if (vertex == qh->tracevertex)
    +    qh->tracevertex= NULL;
    +  qh_removevertex(qh, vertex);
    +  qh_setfree(qh, &vertex->neighbors);
    +  qh_memfree(qh, vertex, (int)sizeof(vertexT));
    +} /* delvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_facet3vertex(qh, )
    +    return temporary set of 3-d vertices in qh_ORIENTclock order
    +
    +  design:
    +    if simplicial facet
    +      build set from facet->vertices with facet->toporient
    +    else
    +      for each ridge in order
    +        build set from ridge's vertices
    +*/
    +setT *qh_facet3vertex(qhT *qh, facetT *facet) {
    +  ridgeT *ridge, *firstridge;
    +  vertexT *vertex;
    +  int cntvertices, cntprojected=0;
    +  setT *vertices;
    +
    +  cntvertices= qh_setsize(qh, facet->vertices);
    +  vertices= qh_settemp(qh, cntvertices);
    +  if (facet->simplicial) {
    +    if (cntvertices != 3) {
    +      qh_fprintf(qh, qh->ferr, 6147, "qhull internal error (qh_facet3vertex): only %d vertices for simplicial facet f%d\n",
    +                  cntvertices, facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +    qh_setappend(qh, &vertices, SETfirst_(facet->vertices));
    +    if (facet->toporient ^ qh_ORIENTclock)
    +      qh_setappend(qh, &vertices, SETsecond_(facet->vertices));
    +    else
    +      qh_setaddnth(qh, &vertices, 0, SETsecond_(facet->vertices));
    +    qh_setappend(qh, &vertices, SETelem_(facet->vertices, 2));
    +  }else {
    +    ridge= firstridge= SETfirstt_(facet->ridges, ridgeT);   /* no infinite */
    +    while ((ridge= qh_nextridge3d(ridge, facet, &vertex))) {
    +      qh_setappend(qh, &vertices, vertex);
    +      if (++cntprojected > cntvertices || ridge == firstridge)
    +        break;
    +    }
    +    if (!ridge || cntprojected != cntvertices) {
    +      qh_fprintf(qh, qh->ferr, 6148, "qhull internal error (qh_facet3vertex): ridges for facet %d don't match up.  got at least %d\n",
    +                  facet->id, cntprojected);
    +      qh_errexit(qh, qh_ERRqhull, facet, ridge);
    +    }
    +  }
    +  return vertices;
    +} /* facet3vertex */
    +
    +/*---------------------------------
    +
    +  qh_findbestfacet(qh, point, bestoutside, bestdist, isoutside )
    +    find facet that is furthest below a point
    +
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    if bestoutside is set (e.g., qh_ALL)
    +      returns best facet that is not upperdelaunay
    +      if Delaunay and inside, point is outside circumsphere of bestfacet
    +    else
    +      returns first facet below point
    +      if point is inside, returns nearest, !upperdelaunay facet
    +    distance to facet
    +    isoutside set if outside of facet
    +
    +  notes:
    +    For tricoplanar facets, this finds one of the tricoplanar facets closest
    +    to the point.  For Delaunay triangulations, the point may be inside a
    +    different tricoplanar facet. See locate a facet with qh_findbestfacet()
    +
    +    If inside, qh_findbestfacet performs an exhaustive search
    +       this may be too conservative.  Sometimes it is clearly required.
    +
    +    qh_findbestfacet is not used by qhull.
    +    uses qh.visit_id and qh.coplanarset
    +
    +  see:
    +    qh_findbest
    +*/
    +facetT *qh_findbestfacet(qhT *qh, pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside) {
    +  facetT *bestfacet= NULL;
    +  int numpart, totpart= 0;
    +
    +  bestfacet= qh_findbest(qh, point, qh->facet_list,
    +                            bestoutside, !qh_ISnewfacets, bestoutside /* qh_NOupper */,
    +                            bestdist, isoutside, &totpart);
    +  if (*bestdist < -qh->DISTround) {
    +    bestfacet= qh_findfacet_all(qh, point, bestdist, isoutside, &numpart);
    +    totpart += numpart;
    +    if ((isoutside && *isoutside && bestoutside)
    +    || (isoutside && !*isoutside && bestfacet->upperdelaunay)) {
    +      bestfacet= qh_findbest(qh, point, bestfacet,
    +                            bestoutside, False, bestoutside,
    +                            bestdist, isoutside, &totpart);
    +      totpart += numpart;
    +    }
    +  }
    +  trace3((qh, qh->ferr, 3014, "qh_findbestfacet: f%d dist %2.2g isoutside %d totpart %d\n",
    +          bestfacet->id, *bestdist, (isoutside ? *isoutside : UINT_MAX), totpart));
    +  return bestfacet;
    +} /* findbestfacet */
    +
    +/*---------------------------------
    +
    +  qh_findbestlower(qh, facet, point, bestdist, numpart )
    +    returns best non-upper, non-flipped neighbor of facet for point
    +    if needed, searches vertex neighbors
    +
    +  returns:
    +    returns bestdist and updates numpart
    +
    +  notes:
    +    if Delaunay and inside, point is outside of circumsphere of bestfacet
    +    called by qh_findbest() for points above an upperdelaunay facet
    +
    +*/
    +facetT *qh_findbestlower(qhT *qh, facetT *upperfacet, pointT *point, realT *bestdistp, int *numpart) {
    +  facetT *neighbor, **neighborp, *bestfacet= NULL;
    +  realT bestdist= -REALmax/2 /* avoid underflow */;
    +  realT dist;
    +  vertexT *vertex;
    +  boolT isoutside= False;  /* not used */
    +
    +  zinc_(Zbestlower);
    +  FOREACHneighbor_(upperfacet) {
    +    if (neighbor->upperdelaunay || neighbor->flipped)
    +      continue;
    +    (*numpart)++;
    +    qh_distplane(qh, point, neighbor, &dist);
    +    if (dist > bestdist) {
    +      bestfacet= neighbor;
    +      bestdist= dist;
    +    }
    +  }
    +  if (!bestfacet) {
    +    zinc_(Zbestlowerv);
    +    /* rarely called, numpart does not count nearvertex computations */
    +    vertex= qh_nearvertex(qh, upperfacet, point, &dist);
    +    qh_vertexneighbors(qh);
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay || neighbor->flipped)
    +        continue;
    +      (*numpart)++;
    +      qh_distplane(qh, point, neighbor, &dist);
    +      if (dist > bestdist) {
    +        bestfacet= neighbor;
    +        bestdist= dist;
    +      }
    +    }
    +  }
    +  if (!bestfacet) {
    +    zinc_(Zbestlowerall);  /* invoked once per point in outsideset */
    +    zmax_(Zbestloweralln, qh->num_facets);
    +    /* [dec'15] Previously reported as QH6228 */
    +    trace3((qh, qh->ferr, 3025, "qh_findbestlower: all neighbors of facet %d are flipped or upper Delaunay.  Search all facets\n",
    +       upperfacet->id));
    +    /* rarely called */
    +    bestfacet= qh_findfacet_all(qh, point, &bestdist, &isoutside, numpart);
    +  }
    +  *bestdistp= bestdist;
    +  trace3((qh, qh->ferr, 3015, "qh_findbestlower: f%d dist %2.2g for f%d p%d\n",
    +          bestfacet->id, bestdist, upperfacet->id, qh_pointid(qh, point)));
    +  return bestfacet;
    +} /* findbestlower */
    +
    +/*---------------------------------
    +
    +  qh_findfacet_all(qh, point, bestdist, isoutside, numpart )
    +    exhaustive search for facet below a point
    +
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    returns first facet below point
    +    if point is inside,
    +      returns nearest facet
    +    distance to facet
    +    isoutside if point is outside of the hull
    +    number of distance tests
    +
    +  notes:
    +    primarily for library users, rarely used by Qhull
    +*/
    +facetT *qh_findfacet_all(qhT *qh, pointT *point, realT *bestdist, boolT *isoutside,
    +                          int *numpart) {
    +  facetT *bestfacet= NULL, *facet;
    +  realT dist;
    +  int totpart= 0;
    +
    +  *bestdist= -REALmax;
    +  *isoutside= False;
    +  FORALLfacets {
    +    if (facet->flipped || !facet->normal)
    +      continue;
    +    totpart++;
    +    qh_distplane(qh, point, facet, &dist);
    +    if (dist > *bestdist) {
    +      *bestdist= dist;
    +      bestfacet= facet;
    +      if (dist > qh->MINoutside) {
    +        *isoutside= True;
    +        break;
    +      }
    +    }
    +  }
    +  *numpart= totpart;
    +  trace3((qh, qh->ferr, 3016, "qh_findfacet_all: f%d dist %2.2g isoutside %d totpart %d\n",
    +          getid_(bestfacet), *bestdist, *isoutside, totpart));
    +  return bestfacet;
    +} /* findfacet_all */
    +
    +/*---------------------------------
    +
    +  qh_findgood(qh, facetlist, goodhorizon )
    +    identify good facets for qh.PRINTgood
    +    if qh.GOODvertex>0
    +      facet includes point as vertex
    +      if !match, returns goodhorizon
    +      inactive if qh.MERGING
    +    if qh.GOODpoint
    +      facet is visible or coplanar (>0) or not visible (<0)
    +    if qh.GOODthreshold
    +      facet->normal matches threshold
    +    if !goodhorizon and !match,
    +      selects facet with closest angle
    +      sets GOODclosest
    +
    +  returns:
    +    number of new, good facets found
    +    determines facet->good
    +    may update qh.GOODclosest
    +
    +  notes:
    +    qh_findgood_all further reduces the good region
    +
    +  design:
    +    count good facets
    +    mark good facets for qh.GOODpoint
    +    mark good facets for qh.GOODthreshold
    +    if necessary
    +      update qh.GOODclosest
    +*/
    +int qh_findgood(qhT *qh, facetT *facetlist, int goodhorizon) {
    +  facetT *facet, *bestfacet= NULL;
    +  realT angle, bestangle= REALmax, dist;
    +  int  numgood=0;
    +
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      numgood++;
    +  }
    +  if (qh->GOODvertex>0 && !qh->MERGING) {
    +    FORALLfacet_(facetlist) {
    +      if (!qh_isvertex(qh->GOODvertexp, facet->vertices)) {
    +        facet->good= False;
    +        numgood--;
    +      }
    +    }
    +  }
    +  if (qh->GOODpoint && numgood) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && facet->normal) {
    +        zinc_(Zdistgood);
    +        qh_distplane(qh, qh->GOODpointp, facet, &dist);
    +        if ((qh->GOODpoint > 0) ^ (dist > 0.0)) {
    +          facet->good= False;
    +          numgood--;
    +        }
    +      }
    +    }
    +  }
    +  if (qh->GOODthreshold && (numgood || goodhorizon || qh->GOODclosest)) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && facet->normal) {
    +        if (!qh_inthresholds(qh, facet->normal, &angle)) {
    +          facet->good= False;
    +          numgood--;
    +          if (angle < bestangle) {
    +            bestangle= angle;
    +            bestfacet= facet;
    +          }
    +        }
    +      }
    +    }
    +    if (!numgood && (!goodhorizon || qh->GOODclosest)) {
    +      if (qh->GOODclosest) {
    +        if (qh->GOODclosest->visible)
    +          qh->GOODclosest= NULL;
    +        else {
    +          qh_inthresholds(qh, qh->GOODclosest->normal, &angle);
    +          if (angle < bestangle)
    +            bestfacet= qh->GOODclosest;
    +        }
    +      }
    +      if (bestfacet && bestfacet != qh->GOODclosest) {
    +        if (qh->GOODclosest)
    +          qh->GOODclosest->good= False;
    +        qh->GOODclosest= bestfacet;
    +        bestfacet->good= True;
    +        numgood++;
    +        trace2((qh, qh->ferr, 2044, "qh_findgood: f%d is closest(%2.2g) to thresholds\n",
    +           bestfacet->id, bestangle));
    +        return numgood;
    +      }
    +    }else if (qh->GOODclosest) { /* numgood > 0 */
    +      qh->GOODclosest->good= False;
    +      qh->GOODclosest= NULL;
    +    }
    +  }
    +  zadd_(Zgoodfacet, numgood);
    +  trace2((qh, qh->ferr, 2045, "qh_findgood: found %d good facets with %d good horizon\n",
    +               numgood, goodhorizon));
    +  if (!numgood && qh->GOODvertex>0 && !qh->MERGING)
    +    return goodhorizon;
    +  return numgood;
    +} /* findgood */
    +
    +/*---------------------------------
    +
    +  qh_findgood_all(qh, facetlist )
    +    apply other constraints for good facets (used by qh.PRINTgood)
    +    if qh.GOODvertex
    +      facet includes (>0) or doesn't include (<0) point as vertex
    +      if last good facet and ONLYgood, prints warning and continues
    +    if qh.SPLITthresholds
    +      facet->normal matches threshold, or if none, the closest one
    +    calls qh_findgood
    +    nop if good not used
    +
    +  returns:
    +    clears facet->good if not good
    +    sets qh.num_good
    +
    +  notes:
    +    this is like qh_findgood but more restrictive
    +
    +  design:
    +    uses qh_findgood to mark good facets
    +    marks facets for qh.GOODvertex
    +    marks facets for qh.SPLITthreholds
    +*/
    +void qh_findgood_all(qhT *qh, facetT *facetlist) {
    +  facetT *facet, *bestfacet=NULL;
    +  realT angle, bestangle= REALmax;
    +  int  numgood=0, startgood;
    +
    +  if (!qh->GOODvertex && !qh->GOODthreshold && !qh->GOODpoint
    +  && !qh->SPLITthresholds)
    +    return;
    +  if (!qh->ONLYgood)
    +    qh_findgood(qh, qh->facet_list, 0);
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      numgood++;
    +  }
    +  if (qh->GOODvertex <0 || (qh->GOODvertex > 0 && qh->MERGING)) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && ((qh->GOODvertex > 0) ^ !!qh_isvertex(qh->GOODvertexp, facet->vertices))) {
    +        if (!--numgood) {
    +          if (qh->ONLYgood) {
    +            qh_fprintf(qh, qh->ferr, 7064, "qhull warning: good vertex p%d does not match last good facet f%d.  Ignored.\n",
    +               qh_pointid(qh, qh->GOODvertexp), facet->id);
    +            return;
    +          }else if (qh->GOODvertex > 0)
    +            qh_fprintf(qh, qh->ferr, 7065, "qhull warning: point p%d is not a vertex('QV%d').\n",
    +                qh->GOODvertex-1, qh->GOODvertex-1);
    +          else
    +            qh_fprintf(qh, qh->ferr, 7066, "qhull warning: point p%d is a vertex for every facet('QV-%d').\n",
    +                -qh->GOODvertex - 1, -qh->GOODvertex - 1);
    +        }
    +        facet->good= False;
    +      }
    +    }
    +  }
    +  startgood= numgood;
    +  if (qh->SPLITthresholds) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good) {
    +        if (!qh_inthresholds(qh, facet->normal, &angle)) {
    +          facet->good= False;
    +          numgood--;
    +          if (angle < bestangle) {
    +            bestangle= angle;
    +            bestfacet= facet;
    +          }
    +        }
    +      }
    +    }
    +    if (!numgood && bestfacet) {
    +      bestfacet->good= True;
    +      numgood++;
    +      trace0((qh, qh->ferr, 23, "qh_findgood_all: f%d is closest(%2.2g) to thresholds\n",
    +           bestfacet->id, bestangle));
    +      return;
    +    }
    +  }
    +  qh->num_good= numgood;
    +  trace0((qh, qh->ferr, 24, "qh_findgood_all: %d good facets remain out of %d facets\n",
    +        numgood, startgood));
    +} /* findgood_all */
    +
    +/*---------------------------------
    +
    +  qh_furthestnext()
    +    set qh.facet_next to facet with furthest of all furthest points
    +    searches all facets on qh.facet_list
    +
    +  notes:
    +    this may help avoid precision problems
    +*/
    +void qh_furthestnext(qhT *qh /* qh->facet_list */) {
    +  facetT *facet, *bestfacet= NULL;
    +  realT dist, bestdist= -REALmax;
    +
    +  FORALLfacets {
    +    if (facet->outsideset) {
    +#if qh_COMPUTEfurthest
    +      pointT *furthest;
    +      furthest= (pointT*)qh_setlast(facet->outsideset);
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(qh, furthest, facet, &dist);
    +#else
    +      dist= facet->furthestdist;
    +#endif
    +      if (dist > bestdist) {
    +        bestfacet= facet;
    +        bestdist= dist;
    +      }
    +    }
    +  }
    +  if (bestfacet) {
    +    qh_removefacet(qh, bestfacet);
    +    qh_prependfacet(qh, bestfacet, &qh->facet_next);
    +    trace1((qh, qh->ferr, 1029, "qh_furthestnext: made f%d next facet(dist %.2g)\n",
    +            bestfacet->id, bestdist));
    +  }
    +} /* furthestnext */
    +
    +/*---------------------------------
    +
    +  qh_furthestout(qh, facet )
    +    make furthest outside point the last point of outsideset
    +
    +  returns:
    +    updates facet->outsideset
    +    clears facet->notfurthest
    +    sets facet->furthestdist
    +
    +  design:
    +    determine best point of outsideset
    +    make it the last point of outsideset
    +*/
    +void qh_furthestout(qhT *qh, facetT *facet) {
    +  pointT *point, **pointp, *bestpoint= NULL;
    +  realT dist, bestdist= -REALmax;
    +
    +  FOREACHpoint_(facet->outsideset) {
    +    qh_distplane(qh, point, facet, &dist);
    +    zinc_(Zcomputefurthest);
    +    if (dist > bestdist) {
    +      bestpoint= point;
    +      bestdist= dist;
    +    }
    +  }
    +  if (bestpoint) {
    +    qh_setdel(facet->outsideset, point);
    +    qh_setappend(qh, &facet->outsideset, point);
    +#if !qh_COMPUTEfurthest
    +    facet->furthestdist= bestdist;
    +#endif
    +  }
    +  facet->notfurthest= False;
    +  trace3((qh, qh->ferr, 3017, "qh_furthestout: p%d is furthest outside point of f%d\n",
    +          qh_pointid(qh, point), facet->id));
    +} /* furthestout */
    +
    +
    +/*---------------------------------
    +
    +  qh_infiniteloop(qh, facet )
    +    report infinite loop error due to facet
    +*/
    +void qh_infiniteloop(qhT *qh, facetT *facet) {
    +
    +  qh_fprintf(qh, qh->ferr, 6149, "qhull internal error (qh_infiniteloop): potential infinite loop detected\n");
    +  qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +} /* qh_infiniteloop */
    +
    +/*---------------------------------
    +
    +  qh_initbuild()
    +    initialize hull and outside sets with point array
    +    qh.FIRSTpoint/qh.NUMpoints is point array
    +    if qh.GOODpoint
    +      adds qh.GOODpoint to initial hull
    +
    +  returns:
    +    qh_facetlist with initial hull
    +    points partioned into outside sets, coplanar sets, or inside
    +    initializes qh.GOODpointp, qh.GOODvertexp,
    +
    +  design:
    +    initialize global variables used during qh_buildhull
    +    determine precision constants and points with max/min coordinate values
    +      if qh.SCALElast, scale last coordinate(for 'd')
    +    build initial simplex
    +    partition input points into facets of initial simplex
    +    set up lists
    +    if qh.ONLYgood
    +      check consistency
    +      add qh.GOODvertex if defined
    +*/
    +void qh_initbuild(qhT *qh) {
    +  setT *maxpoints, *vertices;
    +  facetT *facet;
    +  int i, numpart;
    +  realT dist;
    +  boolT isoutside;
    +
    +  qh->furthest_id= qh_IDunknown;
    +  qh->lastreport= 0;
    +  qh->facet_id= qh->vertex_id= qh->ridge_id= 0;
    +  qh->visit_id= qh->vertex_visit= 0;
    +  qh->maxoutdone= False;
    +
    +  if (qh->GOODpoint > 0)
    +    qh->GOODpointp= qh_point(qh, qh->GOODpoint-1);
    +  else if (qh->GOODpoint < 0)
    +    qh->GOODpointp= qh_point(qh, -qh->GOODpoint-1);
    +  if (qh->GOODvertex > 0)
    +    qh->GOODvertexp= qh_point(qh, qh->GOODvertex-1);
    +  else if (qh->GOODvertex < 0)
    +    qh->GOODvertexp= qh_point(qh, -qh->GOODvertex-1);
    +  if ((qh->GOODpoint
    +       && (qh->GOODpointp < qh->first_point  /* also catches !GOODpointp */
    +           || qh->GOODpointp > qh_point(qh, qh->num_points-1)))
    +    || (qh->GOODvertex
    +        && (qh->GOODvertexp < qh->first_point  /* also catches !GOODvertexp */
    +            || qh->GOODvertexp > qh_point(qh, qh->num_points-1)))) {
    +    qh_fprintf(qh, qh->ferr, 6150, "qhull input error: either QGn or QVn point is > p%d\n",
    +             qh->num_points-1);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  maxpoints= qh_maxmin(qh, qh->first_point, qh->num_points, qh->hull_dim);
    +  if (qh->SCALElast)
    +    qh_scalelast(qh, qh->first_point, qh->num_points, qh->hull_dim,
    +               qh->MINlastcoord, qh->MAXlastcoord, qh->MAXwidth);
    +  qh_detroundoff(qh);
    +  if (qh->DELAUNAY && qh->upper_threshold[qh->hull_dim-1] > REALmax/2
    +                  && qh->lower_threshold[qh->hull_dim-1] < -REALmax/2) {
    +    for (i=qh_PRINTEND; i--; ) {
    +      if (qh->PRINTout[i] == qh_PRINTgeom && qh->DROPdim < 0
    +          && !qh->GOODthreshold && !qh->SPLITthresholds)
    +        break;  /* in this case, don't set upper_threshold */
    +    }
    +    if (i < 0) {
    +      if (qh->UPPERdelaunay) { /* matches qh.upperdelaunay in qh_setfacetplane */
    +        qh->lower_threshold[qh->hull_dim-1]= qh->ANGLEround * qh_ZEROdelaunay;
    +        qh->GOODthreshold= True;
    +      }else {
    +        qh->upper_threshold[qh->hull_dim-1]= -qh->ANGLEround * qh_ZEROdelaunay;
    +        if (!qh->GOODthreshold)
    +          qh->SPLITthresholds= True; /* build upper-convex hull even if Qg */
    +          /* qh_initqhull_globals errors if Qg without Pdk/etc. */
    +      }
    +    }
    +  }
    +  vertices= qh_initialvertices(qh, qh->hull_dim, maxpoints, qh->first_point, qh->num_points);
    +  qh_initialhull(qh, vertices);  /* initial qh->facet_list */
    +  qh_partitionall(qh, vertices, qh->first_point, qh->num_points);
    +  if (qh->PRINToptions1st || qh->TRACElevel || qh->IStracing) {
    +    if (qh->TRACElevel || qh->IStracing)
    +      qh_fprintf(qh, qh->ferr, 8103, "\nTrace level %d for %s | %s\n",
    +         qh->IStracing ? qh->IStracing : qh->TRACElevel, qh->rbox_command, qh->qhull_command);
    +    qh_fprintf(qh, qh->ferr, 8104, "Options selected for Qhull %s:\n%s\n", qh_version, qh->qhull_options);
    +  }
    +  qh_resetlists(qh, False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +  qh->facet_next= qh->facet_list;
    +  qh_furthestnext(qh /* qh->facet_list */);
    +  if (qh->PREmerge) {
    +    qh->cos_max= qh->premerge_cos;
    +    qh->centrum_radius= qh->premerge_centrum;
    +  }
    +  if (qh->ONLYgood) {
    +    if (qh->GOODvertex > 0 && qh->MERGING) {
    +      qh_fprintf(qh, qh->ferr, 6151, "qhull input error: 'Qg QVn' (only good vertex) does not work with merging.\nUse 'QJ' to joggle the input or 'Q0' to turn off merging.\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (!(qh->GOODthreshold || qh->GOODpoint
    +         || (!qh->MERGEexact && !qh->PREmerge && qh->GOODvertexp))) {
    +      qh_fprintf(qh, qh->ferr, 6152, "qhull input error: 'Qg' (ONLYgood) needs a good threshold('Pd0D0'), a\n\
    +good point(QGn or QG-n), or a good vertex with 'QJ' or 'Q0' (QVn).\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh->GOODvertex > 0  && !qh->MERGING  /* matches qh_partitionall */
    +        && !qh_isvertex(qh->GOODvertexp, vertices)) {
    +      facet= qh_findbestnew(qh, qh->GOODvertexp, qh->facet_list,
    +                          &dist, !qh_ALL, &isoutside, &numpart);
    +      zadd_(Zdistgood, numpart);
    +      if (!isoutside) {
    +        qh_fprintf(qh, qh->ferr, 6153, "qhull input error: point for QV%d is inside initial simplex.  It can not be made a vertex.\n",
    +               qh_pointid(qh, qh->GOODvertexp));
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +      if (!qh_addpoint(qh, qh->GOODvertexp, facet, False)) {
    +        qh_settempfree(qh, &vertices);
    +        qh_settempfree(qh, &maxpoints);
    +        return;
    +      }
    +    }
    +    qh_findgood(qh, qh->facet_list, 0);
    +  }
    +  qh_settempfree(qh, &vertices);
    +  qh_settempfree(qh, &maxpoints);
    +  trace1((qh, qh->ferr, 1030, "qh_initbuild: initial hull created and points partitioned\n"));
    +} /* initbuild */
    +
    +/*---------------------------------
    +
    +  qh_initialhull(qh, vertices )
    +    constructs the initial hull as a DIM3 simplex of vertices
    +
    +  design:
    +    creates a simplex (initializes lists)
    +    determines orientation of simplex
    +    sets hyperplanes for facets
    +    doubles checks orientation (in case of axis-parallel facets with Gaussian elimination)
    +    checks for flipped facets and qh.NARROWhull
    +    checks the result
    +*/
    +void qh_initialhull(qhT *qh, setT *vertices) {
    +  facetT *facet, *firstfacet, *neighbor, **neighborp;
    +  realT dist, angle, minangle= REALmax;
    +#ifndef qh_NOtrace
    +  int k;
    +#endif
    +
    +  qh_createsimplex(qh, vertices);  /* qh->facet_list */
    +  qh_resetlists(qh, False, qh_RESETvisible);
    +  qh->facet_next= qh->facet_list;      /* advance facet when processed */
    +  qh->interior_point= qh_getcenter(qh, vertices);
    +  firstfacet= qh->facet_list;
    +  qh_setfacetplane(qh, firstfacet);
    +  zinc_(Znumvisibility); /* needs to be in printsummary */
    +  qh_distplane(qh, qh->interior_point, firstfacet, &dist);
    +  if (dist > 0) {
    +    FORALLfacets
    +      facet->toporient ^= (unsigned char)True;
    +  }
    +  FORALLfacets
    +    qh_setfacetplane(qh, facet);
    +  FORALLfacets {
    +    if (!qh_checkflipped(qh, facet, NULL, qh_ALL)) {/* due to axis-parallel facet */
    +      trace1((qh, qh->ferr, 1031, "qh_initialhull: initial orientation incorrect.  Correct all facets\n"));
    +      facet->flipped= False;
    +      FORALLfacets {
    +        facet->toporient ^= (unsigned char)True;
    +        qh_orientoutside(qh, facet);
    +      }
    +      break;
    +    }
    +  }
    +  FORALLfacets {
    +    if (!qh_checkflipped(qh, facet, NULL, !qh_ALL)) {  /* can happen with 'R0.1' */
    +      if (qh->DELAUNAY && ! qh->ATinfinity) {
    +        if (qh->UPPERdelaunay)
    +          qh_fprintf(qh, qh->ferr, 6240, "Qhull precision error: Initial simplex is cocircular or cospherical.  Option 'Qs' searches all points.  Can not compute the upper Delaunay triangulation or upper Voronoi diagram of cocircular/cospherical points.\n");
    +        else
    +          qh_fprintf(qh, qh->ferr, 6239, "Qhull precision error: Initial simplex is cocircular or cospherical.  Use option 'Qz' for the Delaunay triangulation or Voronoi diagram of cocircular/cospherical points.  Option 'Qz' adds a point \"at infinity\".  Use option 'Qs' to search all points for the initial simplex.\n");
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +      qh_precision(qh, "initial simplex is flat");
    +      qh_fprintf(qh, qh->ferr, 6154, "Qhull precision error: Initial simplex is flat (facet %d is coplanar with the interior point)\n",
    +                   facet->id);
    +      qh_errexit(qh, qh_ERRsingular, NULL, NULL);  /* calls qh_printhelp_singular */
    +    }
    +    FOREACHneighbor_(facet) {
    +      angle= qh_getangle(qh, facet->normal, neighbor->normal);
    +      minimize_( minangle, angle);
    +    }
    +  }
    +  if (minangle < qh_MAXnarrow && !qh->NOnarrow) {
    +    realT diff= 1.0 + minangle;
    +
    +    qh->NARROWhull= True;
    +    qh_option(qh, "_narrow-hull", NULL, &diff);
    +    if (minangle < qh_WARNnarrow && !qh->RERUN && qh->PRINTprecision)
    +      qh_printhelp_narrowhull(qh, qh->ferr, minangle);
    +  }
    +  zzval_(Zprocessed)= qh->hull_dim+1;
    +  qh_checkpolygon(qh, qh->facet_list);
    +  qh_checkconvex(qh, qh->facet_list,   qh_DATAfault);
    +#ifndef qh_NOtrace
    +  if (qh->IStracing >= 1) {
    +    qh_fprintf(qh, qh->ferr, 8105, "qh_initialhull: simplex constructed, interior point:");
    +    for (k=0; k < qh->hull_dim; k++)
    +      qh_fprintf(qh, qh->ferr, 8106, " %6.4g", qh->interior_point[k]);
    +    qh_fprintf(qh, qh->ferr, 8107, "\n");
    +  }
    +#endif
    +} /* initialhull */
    +
    +/*---------------------------------
    +
    +  qh_initialvertices(qh, dim, maxpoints, points, numpoints )
    +    determines a non-singular set of initial vertices
    +    maxpoints may include duplicate points
    +
    +  returns:
    +    temporary set of dim+1 vertices in descending order by vertex id
    +    if qh.RANDOMoutside && !qh.ALLpoints
    +      picks random points
    +    if dim >= qh_INITIALmax,
    +      uses min/max x and max points with non-zero determinants
    +
    +  notes:
    +    unless qh.ALLpoints,
    +      uses maxpoints as long as determinate is non-zero
    +*/
    +setT *qh_initialvertices(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints) {
    +  pointT *point, **pointp;
    +  setT *vertices, *simplex, *tested;
    +  realT randr;
    +  int idx, point_i, point_n, k;
    +  boolT nearzero= False;
    +
    +  vertices= qh_settemp(qh, dim + 1);
    +  simplex= qh_settemp(qh, dim+1);
    +  if (qh->ALLpoints)
    +    qh_maxsimplex(qh, dim, NULL, points, numpoints, &simplex);
    +  else if (qh->RANDOMoutside) {
    +    while (qh_setsize(qh, simplex) != dim+1) {
    +      randr= qh_RANDOMint;
    +      randr= randr/(qh_RANDOMmax+1);
    +      idx= (int)floor(qh->num_points * randr);
    +      while (qh_setin(simplex, qh_point(qh, idx))) {
    +            idx++; /* in case qh_RANDOMint always returns the same value */
    +        idx= idx < qh->num_points ? idx : 0;
    +      }
    +      qh_setappend(qh, &simplex, qh_point(qh, idx));
    +    }
    +  }else if (qh->hull_dim >= qh_INITIALmax) {
    +    tested= qh_settemp(qh, dim+1);
    +    qh_setappend(qh, &simplex, SETfirst_(maxpoints));   /* max and min X coord */
    +    qh_setappend(qh, &simplex, SETsecond_(maxpoints));
    +    qh_maxsimplex(qh, fmin_(qh_INITIALsearch, dim), maxpoints, points, numpoints, &simplex);
    +    k= qh_setsize(qh, simplex);
    +    FOREACHpoint_i_(qh, maxpoints) {
    +      if (point_i & 0x1) {     /* first pick up max. coord. points */
    +        if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +          qh_detsimplex(qh, point, simplex, k, &nearzero);
    +          if (nearzero)
    +            qh_setappend(qh, &tested, point);
    +          else {
    +            qh_setappend(qh, &simplex, point);
    +            if (++k == dim)  /* use search for last point */
    +              break;
    +          }
    +        }
    +      }
    +    }
    +    while (k != dim && (point= (pointT*)qh_setdellast(maxpoints))) {
    +      if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +        qh_detsimplex(qh, point, simplex, k, &nearzero);
    +        if (nearzero)
    +          qh_setappend(qh, &tested, point);
    +        else {
    +          qh_setappend(qh, &simplex, point);
    +          k++;
    +        }
    +      }
    +    }
    +    idx= 0;
    +    while (k != dim && (point= qh_point(qh, idx++))) {
    +      if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +        qh_detsimplex(qh, point, simplex, k, &nearzero);
    +        if (!nearzero){
    +          qh_setappend(qh, &simplex, point);
    +          k++;
    +        }
    +      }
    +    }
    +    qh_settempfree(qh, &tested);
    +    qh_maxsimplex(qh, dim, maxpoints, points, numpoints, &simplex);
    +  }else
    +    qh_maxsimplex(qh, dim, maxpoints, points, numpoints, &simplex);
    +  FOREACHpoint_(simplex)
    +    qh_setaddnth(qh, &vertices, 0, qh_newvertex(qh, point)); /* descending order */
    +  qh_settempfree(qh, &simplex);
    +  return vertices;
    +} /* initialvertices */
    +
    +
    +/*---------------------------------
    +
    +  qh_isvertex( point, vertices )
    +    returns vertex if point is in vertex set, else returns NULL
    +
    +  notes:
    +    for qh.GOODvertex
    +*/
    +vertexT *qh_isvertex(pointT *point, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (vertex->point == point)
    +      return vertex;
    +  }
    +  return NULL;
    +} /* isvertex */
    +
    +/*---------------------------------
    +
    +  qh_makenewfacets(qh, point )
    +    make new facets from point and qh.visible_list
    +
    +  returns:
    +    qh.newfacet_list= list of new facets with hyperplanes and ->newfacet
    +    qh.newvertex_list= list of vertices in new facets with ->newlist set
    +
    +    if (qh.ONLYgood)
    +      newfacets reference horizon facets, but not vice versa
    +      ridges reference non-simplicial horizon ridges, but not vice versa
    +      does not change existing facets
    +    else
    +      sets qh.NEWfacets
    +      new facets attached to horizon facets and ridges
    +      for visible facets,
    +        visible->r.replace is corresponding new facet
    +
    +  see also:
    +    qh_makenewplanes() -- make hyperplanes for facets
    +    qh_attachnewfacets() -- attachnewfacets if not done here(qh->ONLYgood)
    +    qh_matchnewfacets() -- match up neighbors
    +    qh_updatevertices() -- update vertex neighbors and delvertices
    +    qh_deletevisible() -- delete visible facets
    +    qh_checkpolygon() --check the result
    +    qh_triangulate() -- triangulate a non-simplicial facet
    +
    +  design:
    +    for each visible facet
    +      make new facets to its horizon facets
    +      update its f.replace
    +      clear its neighbor set
    +*/
    +vertexT *qh_makenewfacets(qhT *qh, pointT *point /*visible_list*/) {
    +  facetT *visible, *newfacet= NULL, *newfacet2= NULL, *neighbor, **neighborp;
    +  vertexT *apex;
    +  int numnew=0;
    +
    +  qh->newfacet_list= qh->facet_tail;
    +  qh->newvertex_list= qh->vertex_tail;
    +  apex= qh_newvertex(qh, point);
    +  qh_appendvertex(qh, apex);
    +  qh->visit_id++;
    +  if (!qh->ONLYgood)
    +    qh->NEWfacets= True;
    +  FORALLvisible_facets {
    +    FOREACHneighbor_(visible)
    +      neighbor->seen= False;
    +    if (visible->ridges) {
    +      visible->visitid= qh->visit_id;
    +      newfacet2= qh_makenew_nonsimplicial(qh, visible, apex, &numnew);
    +    }
    +    if (visible->simplicial)
    +      newfacet= qh_makenew_simplicial(qh, visible, apex, &numnew);
    +    if (!qh->ONLYgood) {
    +      if (newfacet2)  /* newfacet is null if all ridges defined */
    +        newfacet= newfacet2;
    +      if (newfacet)
    +        visible->f.replace= newfacet;
    +      else
    +        zinc_(Zinsidevisible);
    +      SETfirst_(visible->neighbors)= NULL;
    +    }
    +  }
    +  trace1((qh, qh->ferr, 1032, "qh_makenewfacets: created %d new facets from point p%d to horizon\n",
    +          numnew, qh_pointid(qh, point)));
    +  if (qh->IStracing >= 4)
    +    qh_printfacetlist(qh, qh->newfacet_list, NULL, qh_ALL);
    +  return apex;
    +} /* makenewfacets */
    +
    +/*---------------------------------
    +
    +  qh_matchduplicates(qh, atfacet, atskip, hashsize, hashcount )
    +    match duplicate ridges in qh.hash_table for atfacet/atskip
    +    duplicates marked with ->dupridge and qh_DUPLICATEridge
    +
    +  returns:
    +    picks match with worst merge (min distance apart)
    +    updates hashcount
    +
    +  see also:
    +    qh_matchneighbor
    +
    +  notes:
    +
    +  design:
    +    compute hash value for atfacet and atskip
    +    repeat twice -- once to make best matches, once to match the rest
    +      for each possible facet in qh.hash_table
    +        if it is a matching facet and pass 2
    +          make match
    +          unless tricoplanar, mark match for merging (qh_MERGEridge)
    +          [e.g., tricoplanar RBOX s 1000 t993602376 | QHULL C-1e-3 d Qbb FA Qt]
    +        if it is a matching facet and pass 1
    +          test if this is a better match
    +      if pass 1,
    +        make best match (it will not be merged)
    +*/
    +#ifndef qh_NOmerge
    +void qh_matchduplicates(qhT *qh, facetT *atfacet, int atskip, int hashsize, int *hashcount) {
    +  boolT same, ismatch;
    +  int hash, scan;
    +  facetT *facet, *newfacet, *maxmatch= NULL, *maxmatch2= NULL, *nextfacet;
    +  int skip, newskip, nextskip= 0, maxskip= 0, maxskip2= 0, makematch;
    +  realT maxdist= -REALmax, mindist, dist2, low, high;
    +
    +  hash= qh_gethash(qh, hashsize, atfacet->vertices, qh->hull_dim, 1,
    +                     SETelem_(atfacet->vertices, atskip));
    +  trace2((qh, qh->ferr, 2046, "qh_matchduplicates: find duplicate matches for f%d skip %d hash %d hashcount %d\n",
    +          atfacet->id, atskip, hash, *hashcount));
    +  for (makematch= 0; makematch < 2; makematch++) {
    +    qh->visit_id++;
    +    for (newfacet= atfacet, newskip= atskip; newfacet; newfacet= nextfacet, newskip= nextskip) {
    +      zinc_(Zhashlookup);
    +      nextfacet= NULL;
    +      newfacet->visitid= qh->visit_id;
    +      for (scan= hash; (facet= SETelemt_(qh->hash_table, scan, facetT));
    +           scan= (++scan >= hashsize ? 0 : scan)) {
    +        if (!facet->dupridge || facet->visitid == qh->visit_id)
    +          continue;
    +        zinc_(Zhashtests);
    +        if (qh_matchvertices(qh, 1, newfacet->vertices, newskip, facet->vertices, &skip, &same)) {
    +          ismatch= (same == (boolT)(newfacet->toporient ^ facet->toporient));
    +          if (SETelemt_(facet->neighbors, skip, facetT) != qh_DUPLICATEridge) {
    +            if (!makematch) {
    +              qh_fprintf(qh, qh->ferr, 6155, "qhull internal error (qh_matchduplicates): missing dupridge at f%d skip %d for new f%d skip %d hash %d\n",
    +                     facet->id, skip, newfacet->id, newskip, hash);
    +              qh_errexit2(qh, qh_ERRqhull, facet, newfacet);
    +            }
    +          }else if (ismatch && makematch) {
    +            if (SETelemt_(newfacet->neighbors, newskip, facetT) == qh_DUPLICATEridge) {
    +              SETelem_(facet->neighbors, skip)= newfacet;
    +              if (newfacet->tricoplanar)
    +                SETelem_(newfacet->neighbors, newskip)= facet;
    +              else
    +                SETelem_(newfacet->neighbors, newskip)= qh_MERGEridge;
    +              *hashcount -= 2; /* removed two unmatched facets */
    +              trace4((qh, qh->ferr, 4059, "qh_matchduplicates: duplicate f%d skip %d matched with new f%d skip %d merge\n",
    +                    facet->id, skip, newfacet->id, newskip));
    +            }
    +          }else if (ismatch) {
    +            mindist= qh_getdistance(qh, facet, newfacet, &low, &high);
    +            dist2= qh_getdistance(qh, newfacet, facet, &low, &high);
    +            minimize_(mindist, dist2);
    +            if (mindist > maxdist) {
    +              maxdist= mindist;
    +              maxmatch= facet;
    +              maxskip= skip;
    +              maxmatch2= newfacet;
    +              maxskip2= newskip;
    +            }
    +            trace3((qh, qh->ferr, 3018, "qh_matchduplicates: duplicate f%d skip %d new f%d skip %d at dist %2.2g, max is now f%d f%d\n",
    +                    facet->id, skip, newfacet->id, newskip, mindist,
    +                    maxmatch->id, maxmatch2->id));
    +          }else { /* !ismatch */
    +            nextfacet= facet;
    +            nextskip= skip;
    +          }
    +        }
    +        if (makematch && !facet
    +        && SETelemt_(facet->neighbors, skip, facetT) == qh_DUPLICATEridge) {
    +          qh_fprintf(qh, qh->ferr, 6156, "qhull internal error (qh_matchduplicates): no MERGEridge match for duplicate f%d skip %d at hash %d\n",
    +                     newfacet->id, newskip, hash);
    +          qh_errexit(qh, qh_ERRqhull, newfacet, NULL);
    +        }
    +      }
    +    } /* end of for each new facet at hash */
    +    if (!makematch) {
    +      if (!maxmatch) {
    +        qh_fprintf(qh, qh->ferr, 6157, "qhull internal error (qh_matchduplicates): no maximum match at duplicate f%d skip %d at hash %d\n",
    +                     atfacet->id, atskip, hash);
    +        qh_errexit(qh, qh_ERRqhull, atfacet, NULL);
    +      }
    +      SETelem_(maxmatch->neighbors, maxskip)= maxmatch2; /* maxmatch!=0 by QH6157 */
    +      SETelem_(maxmatch2->neighbors, maxskip2)= maxmatch;
    +      *hashcount -= 2; /* removed two unmatched facets */
    +      zzinc_(Zmultiridge);
    +      trace0((qh, qh->ferr, 25, "qh_matchduplicates: duplicate f%d skip %d matched with new f%d skip %d keep\n",
    +              maxmatch->id, maxskip, maxmatch2->id, maxskip2));
    +      qh_precision(qh, "ridge with multiple neighbors");
    +      if (qh->IStracing >= 4)
    +        qh_errprint(qh, "DUPLICATED/MATCH", maxmatch, maxmatch2, NULL, NULL);
    +    }
    +  }
    +} /* matchduplicates */
    +
    +/*---------------------------------
    +
    +  qh_nearcoplanar()
    +    for all facets, remove near-inside points from facet->coplanarset
    +    coplanar points defined by innerplane from qh_outerinner()
    +
    +  returns:
    +    if qh->KEEPcoplanar && !qh->KEEPinside
    +      facet->coplanarset only contains coplanar points
    +    if qh.JOGGLEmax
    +      drops inner plane by another qh.JOGGLEmax diagonal since a
    +        vertex could shift out while a coplanar point shifts in
    +
    +  notes:
    +    used for qh.PREmerge and qh.JOGGLEmax
    +    must agree with computation of qh.NEARcoplanar in qh_detroundoff(qh)
    +  design:
    +    if not keeping coplanar or inside points
    +      free all coplanar sets
    +    else if not keeping both coplanar and inside points
    +      remove !coplanar or !inside points from coplanar sets
    +*/
    +void qh_nearcoplanar(qhT *qh /* qh.facet_list */) {
    +  facetT *facet;
    +  pointT *point, **pointp;
    +  int numpart;
    +  realT dist, innerplane;
    +
    +  if (!qh->KEEPcoplanar && !qh->KEEPinside) {
    +    FORALLfacets {
    +      if (facet->coplanarset)
    +        qh_setfree(qh, &facet->coplanarset);
    +    }
    +  }else if (!qh->KEEPcoplanar || !qh->KEEPinside) {
    +    qh_outerinner(qh, NULL, NULL, &innerplane);
    +    if (qh->JOGGLEmax < REALmax/2)
    +      innerplane -= qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
    +    numpart= 0;
    +    FORALLfacets {
    +      if (facet->coplanarset) {
    +        FOREACHpoint_(facet->coplanarset) {
    +          numpart++;
    +          qh_distplane(qh, point, facet, &dist);
    +          if (dist < innerplane) {
    +            if (!qh->KEEPinside)
    +              SETref_(point)= NULL;
    +          }else if (!qh->KEEPcoplanar)
    +            SETref_(point)= NULL;
    +        }
    +        qh_setcompact(qh, facet->coplanarset);
    +      }
    +    }
    +    zzadd_(Zcheckpart, numpart);
    +  }
    +} /* nearcoplanar */
    +
    +/*---------------------------------
    +
    +  qh_nearvertex(qh, facet, point, bestdist )
    +    return nearest vertex in facet to point
    +
    +  returns:
    +    vertex and its distance
    +
    +  notes:
    +    if qh.DELAUNAY
    +      distance is measured in the input set
    +    searches neighboring tricoplanar facets (requires vertexneighbors)
    +      Slow implementation.  Recomputes vertex set for each point.
    +    The vertex set could be stored in the qh.keepcentrum facet.
    +*/
    +vertexT *qh_nearvertex(qhT *qh, facetT *facet, pointT *point, realT *bestdistp) {
    +  realT bestdist= REALmax, dist;
    +  vertexT *bestvertex= NULL, *vertex, **vertexp, *apex;
    +  coordT *center;
    +  facetT *neighbor, **neighborp;
    +  setT *vertices;
    +  int dim= qh->hull_dim;
    +
    +  if (qh->DELAUNAY)
    +    dim--;
    +  if (facet->tricoplanar) {
    +    if (!qh->VERTEXneighbors || !facet->center) {
    +      qh_fprintf(qh, qh->ferr, 6158, "qhull internal error (qh_nearvertex): qh.VERTEXneighbors and facet->center required for tricoplanar facets\n");
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +    vertices= qh_settemp(qh, qh->TEMPsize);
    +    apex= SETfirstt_(facet->vertices, vertexT);
    +    center= facet->center;
    +    FOREACHneighbor_(apex) {
    +      if (neighbor->center == center) {
    +        FOREACHvertex_(neighbor->vertices)
    +          qh_setappend(qh, &vertices, vertex);
    +      }
    +    }
    +  }else
    +    vertices= facet->vertices;
    +  FOREACHvertex_(vertices) {
    +    dist= qh_pointdist(vertex->point, point, -dim);
    +    if (dist < bestdist) {
    +      bestdist= dist;
    +      bestvertex= vertex;
    +    }
    +  }
    +  if (facet->tricoplanar)
    +    qh_settempfree(qh, &vertices);
    +  *bestdistp= sqrt(bestdist);
    +  if (!bestvertex) {
    +      qh_fprintf(qh, qh->ferr, 6261, "qhull internal error (qh_nearvertex): did not find bestvertex for f%d p%d\n", facet->id, qh_pointid(qh, point));
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  trace3((qh, qh->ferr, 3019, "qh_nearvertex: v%d dist %2.2g for f%d p%d\n",
    +        bestvertex->id, *bestdistp, facet->id, qh_pointid(qh, point))); /* bestvertex!=0 by QH2161 */
    +  return bestvertex;
    +} /* nearvertex */
    +
    +/*---------------------------------
    +
    +  qh_newhashtable(qh, newsize )
    +    returns size of qh.hash_table of at least newsize slots
    +
    +  notes:
    +    assumes qh.hash_table is NULL
    +    qh_HASHfactor determines the number of extra slots
    +    size is not divisible by 2, 3, or 5
    +*/
    +int qh_newhashtable(qhT *qh, int newsize) {
    +  int size;
    +
    +  size= ((newsize+1)*qh_HASHfactor) | 0x1;  /* odd number */
    +  while (True) {
    +    if (newsize<0 || size<0) {
    +        qh_fprintf(qh, qh->qhmem.ferr, 6236, "qhull error (qh_newhashtable): negative request (%d) or size (%d).  Did int overflow due to high-D?\n", newsize, size); /* WARN64 */
    +        qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +    }
    +    if ((size%3) && (size%5))
    +      break;
    +    size += 2;
    +    /* loop terminates because there is an infinite number of primes */
    +  }
    +  qh->hash_table= qh_setnew(qh, size);
    +  qh_setzero(qh, qh->hash_table, 0, size);
    +  return size;
    +} /* newhashtable */
    +
    +/*---------------------------------
    +
    +  qh_newvertex(qh, point )
    +    returns a new vertex for point
    +*/
    +vertexT *qh_newvertex(qhT *qh, pointT *point) {
    +  vertexT *vertex;
    +
    +  zinc_(Ztotvertices);
    +  vertex= (vertexT *)qh_memalloc(qh, (int)sizeof(vertexT));
    +  memset((char *) vertex, (size_t)0, sizeof(vertexT));
    +  if (qh->vertex_id == UINT_MAX) {
    +    qh_memfree(qh, vertex, (int)sizeof(vertexT));
    +    qh_fprintf(qh, qh->ferr, 6159, "qhull error: more than 2^32 vertices.  vertexT.id field overflows.  Vertices would not be sorted correctly.\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  if (qh->vertex_id == qh->tracevertex_id)
    +    qh->tracevertex= vertex;
    +  vertex->id= qh->vertex_id++;
    +  vertex->point= point;
    +  trace4((qh, qh->ferr, 4060, "qh_newvertex: vertex p%d(v%d) created\n", qh_pointid(qh, vertex->point),
    +          vertex->id));
    +  return(vertex);
    +} /* newvertex */
    +
    +/*---------------------------------
    +
    +  qh_nextridge3d( atridge, facet, vertex )
    +    return next ridge and vertex for a 3d facet
    +    returns NULL on error
    +    [for QhullFacet::nextRidge3d] Does not call qh_errexit nor access qhT.
    +
    +  notes:
    +    in qh_ORIENTclock order
    +    this is a O(n^2) implementation to trace all ridges
    +    be sure to stop on any 2nd visit
    +    same as QhullRidge::nextRidge3d
    +    does not use qhT or qh_errexit [QhullFacet.cpp]
    +
    +  design:
    +    for each ridge
    +      exit if it is the ridge after atridge
    +*/
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp) {
    +  vertexT *atvertex, *vertex, *othervertex;
    +  ridgeT *ridge, **ridgep;
    +
    +  if ((atridge->top == facet) ^ qh_ORIENTclock)
    +    atvertex= SETsecondt_(atridge->vertices, vertexT);
    +  else
    +    atvertex= SETfirstt_(atridge->vertices, vertexT);
    +  FOREACHridge_(facet->ridges) {
    +    if (ridge == atridge)
    +      continue;
    +    if ((ridge->top == facet) ^ qh_ORIENTclock) {
    +      othervertex= SETsecondt_(ridge->vertices, vertexT);
    +      vertex= SETfirstt_(ridge->vertices, vertexT);
    +    }else {
    +      vertex= SETsecondt_(ridge->vertices, vertexT);
    +      othervertex= SETfirstt_(ridge->vertices, vertexT);
    +    }
    +    if (vertex == atvertex) {
    +      if (vertexp)
    +        *vertexp= othervertex;
    +      return ridge;
    +    }
    +  }
    +  return NULL;
    +} /* nextridge3d */
    +#else /* qh_NOmerge */
    +void qh_matchduplicates(qhT *qh, facetT *atfacet, int atskip, int hashsize, int *hashcount) {
    +}
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp) {
    +
    +  return NULL;
    +}
    +#endif /* qh_NOmerge */
    +
    +/*---------------------------------
    +
    +  qh_outcoplanar()
    +    move points from all facets' outsidesets to their coplanarsets
    +
    +  notes:
    +    for post-processing under qh.NARROWhull
    +
    +  design:
    +    for each facet
    +      for each outside point for facet
    +        partition point into coplanar set
    +*/
    +void qh_outcoplanar(qhT *qh /* facet_list */) {
    +  pointT *point, **pointp;
    +  facetT *facet;
    +  realT dist;
    +
    +  trace1((qh, qh->ferr, 1033, "qh_outcoplanar: move outsideset to coplanarset for qh->NARROWhull\n"));
    +  FORALLfacets {
    +    FOREACHpoint_(facet->outsideset) {
    +      qh->num_outside--;
    +      if (qh->KEEPcoplanar || qh->KEEPnearinside) {
    +        qh_distplane(qh, point, facet, &dist);
    +        zinc_(Zpartition);
    +        qh_partitioncoplanar(qh, point, facet, &dist);
    +      }
    +    }
    +    qh_setfree(qh, &facet->outsideset);
    +  }
    +} /* outcoplanar */
    +
    +/*---------------------------------
    +
    +  qh_point(qh, id )
    +    return point for a point id, or NULL if unknown
    +
    +  alternative code:
    +    return((pointT *)((unsigned   long)qh.first_point
    +           + (unsigned long)((id)*qh.normal_size)));
    +*/
    +pointT *qh_point(qhT *qh, int id) {
    +
    +  if (id < 0)
    +    return NULL;
    +  if (id < qh->num_points)
    +    return qh->first_point + id * qh->hull_dim;
    +  id -= qh->num_points;
    +  if (id < qh_setsize(qh, qh->other_points))
    +    return SETelemt_(qh->other_points, id, pointT);
    +  return NULL;
    +} /* point */
    +
    +/*---------------------------------
    +
    +  qh_point_add(qh, set, point, elem )
    +    stores elem at set[point.id]
    +
    +  returns:
    +    access function for qh_pointfacet and qh_pointvertex
    +
    +  notes:
    +    checks point.id
    +*/
    +void qh_point_add(qhT *qh, setT *set, pointT *point, void *elem) {
    +  int id, size;
    +
    +  SETreturnsize_(set, size);
    +  if ((id= qh_pointid(qh, point)) < 0)
    +    qh_fprintf(qh, qh->ferr, 7067, "qhull internal warning (point_add): unknown point %p id %d\n",
    +      point, id);
    +  else if (id >= size) {
    +    qh_fprintf(qh, qh->ferr, 6160, "qhull internal errror(point_add): point p%d is out of bounds(%d)\n",
    +             id, size);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }else
    +    SETelem_(set, id)= elem;
    +} /* point_add */
    +
    +
    +/*---------------------------------
    +
    +  qh_pointfacet()
    +    return temporary set of facet for each point
    +    the set is indexed by point id
    +
    +  notes:
    +    vertices assigned to one of the facets
    +    coplanarset assigned to the facet
    +    outside set assigned to the facet
    +    NULL if no facet for point (inside)
    +      includes qh.GOODpointp
    +
    +  access:
    +    FOREACHfacet_i_(qh, facets) { ... }
    +    SETelem_(facets, i)
    +
    +  design:
    +    for each facet
    +      add each vertex
    +      add each coplanar point
    +      add each outside point
    +*/
    +setT *qh_pointfacet(qhT *qh /*qh.facet_list*/) {
    +  int numpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +  setT *facets;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  pointT *point, **pointp;
    +
    +  facets= qh_settemp(qh, numpoints);
    +  qh_setzero(qh, facets, 0, numpoints);
    +  qh->vertex_visit++;
    +  FORALLfacets {
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh->vertex_visit) {
    +        vertex->visitid= qh->vertex_visit;
    +        qh_point_add(qh, facets, vertex->point, facet);
    +      }
    +    }
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(qh, facets, point, facet);
    +    FOREACHpoint_(facet->outsideset)
    +      qh_point_add(qh, facets, point, facet);
    +  }
    +  return facets;
    +} /* pointfacet */
    +
    +/*---------------------------------
    +
    +  qh_pointvertex(qh, )
    +    return temporary set of vertices indexed by point id
    +    entry is NULL if no vertex for a point
    +      this will include qh.GOODpointp
    +
    +  access:
    +    FOREACHvertex_i_(qh, vertices) { ... }
    +    SETelem_(vertices, i)
    +*/
    +setT *qh_pointvertex(qhT *qh /*qh.facet_list*/) {
    +  int numpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +  setT *vertices;
    +  vertexT *vertex;
    +
    +  vertices= qh_settemp(qh, numpoints);
    +  qh_setzero(qh, vertices, 0, numpoints);
    +  FORALLvertices
    +    qh_point_add(qh, vertices, vertex->point, vertex);
    +  return vertices;
    +} /* pointvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_prependfacet(qh, facet, facetlist )
    +    prepend facet to the start of a facetlist
    +
    +  returns:
    +    increments qh.numfacets
    +    updates facetlist, qh.facet_list, facet_next
    +
    +  notes:
    +    be careful of prepending since it can lose a pointer.
    +      e.g., can lose _next by deleting and then prepending before _next
    +*/
    +void qh_prependfacet(qhT *qh, facetT *facet, facetT **facetlist) {
    +  facetT *prevfacet, *list;
    +
    +
    +  trace4((qh, qh->ferr, 4061, "qh_prependfacet: prepend f%d before f%d\n",
    +          facet->id, getid_(*facetlist)));
    +  if (!*facetlist)
    +    (*facetlist)= qh->facet_tail;
    +  list= *facetlist;
    +  prevfacet= list->previous;
    +  facet->previous= prevfacet;
    +  if (prevfacet)
    +    prevfacet->next= facet;
    +  list->previous= facet;
    +  facet->next= *facetlist;
    +  if (qh->facet_list == list)  /* this may change *facetlist */
    +    qh->facet_list= facet;
    +  if (qh->facet_next == list)
    +    qh->facet_next= facet;
    +  *facetlist= facet;
    +  qh->num_facets++;
    +} /* prependfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhashtable(qh, fp )
    +    print hash table to fp
    +
    +  notes:
    +    not in I/O to avoid bringing io_r.c in
    +
    +  design:
    +    for each hash entry
    +      if defined
    +        if unmatched or will merge (NULL, qh_MERGEridge, qh_DUPLICATEridge)
    +          print entry and neighbors
    +*/
    +void qh_printhashtable(qhT *qh, FILE *fp) {
    +  facetT *facet, *neighbor;
    +  int id, facet_i, facet_n, neighbor_i= 0, neighbor_n= 0;
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHfacet_i_(qh, qh->hash_table) {
    +    if (facet) {
    +      FOREACHneighbor_i_(qh, facet) {
    +        if (!neighbor || neighbor == qh_MERGEridge || neighbor == qh_DUPLICATEridge)
    +          break;
    +      }
    +      if (neighbor_i == neighbor_n)
    +        continue;
    +      qh_fprintf(qh, fp, 9283, "hash %d f%d ", facet_i, facet->id);
    +      FOREACHvertex_(facet->vertices)
    +        qh_fprintf(qh, fp, 9284, "v%d ", vertex->id);
    +      qh_fprintf(qh, fp, 9285, "\n neighbors:");
    +      FOREACHneighbor_i_(qh, facet) {
    +        if (neighbor == qh_MERGEridge)
    +          id= -3;
    +        else if (neighbor == qh_DUPLICATEridge)
    +          id= -2;
    +        else
    +          id= getid_(neighbor);
    +        qh_fprintf(qh, fp, 9286, " %d", id);
    +      }
    +      qh_fprintf(qh, fp, 9287, "\n");
    +    }
    +  }
    +} /* printhashtable */
    +
    +
    +/*---------------------------------
    +
    +  qh_printlists(qh, fp )
    +    print out facet and vertex list for debugging (without 'f/v' tags)
    +*/
    +void qh_printlists(qhT *qh) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  int count= 0;
    +
    +  qh_fprintf(qh, qh->ferr, 8108, "qh_printlists: facets:");
    +  FORALLfacets {
    +    if (++count % 100 == 0)
    +      qh_fprintf(qh, qh->ferr, 8109, "\n     ");
    +    qh_fprintf(qh, qh->ferr, 8110, " %d", facet->id);
    +  }
    +  qh_fprintf(qh, qh->ferr, 8111, "\n  new facets %d visible facets %d next facet for qh_addpoint %d\n  vertices(new %d):",
    +     getid_(qh->newfacet_list), getid_(qh->visible_list), getid_(qh->facet_next),
    +     getid_(qh->newvertex_list));
    +  count = 0;
    +  FORALLvertices {
    +    if (++count % 100 == 0)
    +      qh_fprintf(qh, qh->ferr, 8112, "\n     ");
    +    qh_fprintf(qh, qh->ferr, 8113, " %d", vertex->id);
    +  }
    +  qh_fprintf(qh, qh->ferr, 8114, "\n");
    +} /* printlists */
    +
    +/*---------------------------------
    +
    +  qh_resetlists(qh, stats, qh_RESETvisible )
    +    reset newvertex_list, newfacet_list, visible_list
    +    if stats,
    +      maintains statistics
    +
    +  returns:
    +    visible_list is empty if qh_deletevisible was called
    +*/
    +void qh_resetlists(qhT *qh, boolT stats, boolT resetVisible /*qh.newvertex_list newfacet_list visible_list*/) {
    +  vertexT *vertex;
    +  facetT *newfacet, *visible;
    +  int totnew=0, totver=0;
    +
    +  if (stats) {
    +    FORALLvertex_(qh->newvertex_list)
    +      totver++;
    +    FORALLnew_facets
    +      totnew++;
    +    zadd_(Zvisvertextot, totver);
    +    zmax_(Zvisvertexmax, totver);
    +    zadd_(Znewfacettot, totnew);
    +    zmax_(Znewfacetmax, totnew);
    +  }
    +  FORALLvertex_(qh->newvertex_list)
    +    vertex->newlist= False;
    +  qh->newvertex_list= NULL;
    +  FORALLnew_facets
    +    newfacet->newfacet= False;
    +  qh->newfacet_list= NULL;
    +  if (resetVisible) {
    +    FORALLvisible_facets {
    +      visible->f.replace= NULL;
    +      visible->visible= False;
    +    }
    +    qh->num_visible= 0;
    +  }
    +  qh->visible_list= NULL; /* may still have visible facets via qh_triangulate */
    +  qh->NEWfacets= False;
    +} /* resetlists */
    +
    +/*---------------------------------
    +
    +  qh_setvoronoi_all(qh)
    +    compute Voronoi centers for all facets
    +    includes upperDelaunay facets if qh.UPPERdelaunay ('Qu')
    +
    +  returns:
    +    facet->center is the Voronoi center
    +
    +  notes:
    +    this is unused/untested code
    +      please email bradb@shore.net if this works ok for you
    +
    +  use:
    +    FORALLvertices {...} to locate the vertex for a point.
    +    FOREACHneighbor_(vertex) {...} to visit the Voronoi centers for a Voronoi cell.
    +*/
    +void qh_setvoronoi_all(qhT *qh) {
    +  facetT *facet;
    +
    +  qh_clearcenters(qh, qh_ASvoronoi);
    +  qh_vertexneighbors(qh);
    +
    +  FORALLfacets {
    +    if (!facet->normal || !facet->upperdelaunay || qh->UPPERdelaunay) {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(qh, facet->vertices);
    +    }
    +  }
    +} /* setvoronoi_all */
    +
    +#ifndef qh_NOmerge
    +
    +/*---------------------------------
    +
    +  qh_triangulate()
    +    triangulate non-simplicial facets on qh.facet_list,
    +    if qh->VORONOI, sets Voronoi centers of non-simplicial facets
    +    nop if hasTriangulation
    +
    +  returns:
    +    all facets simplicial
    +    each tricoplanar facet has ->f.triowner == owner of ->center,normal,etc.
    +
    +  notes:
    +    call after qh_check_output since may switch to Voronoi centers
    +    Output may overwrite ->f.triowner with ->f.area
    +*/
    +void qh_triangulate(qhT *qh /*qh.facet_list*/) {
    +  facetT *facet, *nextfacet, *owner;
    +  int onlygood= qh->ONLYgood;
    +  facetT *neighbor, *visible= NULL, *facet1, *facet2, *new_facet_list= NULL;
    +  facetT *orig_neighbor= NULL, *otherfacet;
    +  vertexT *new_vertex_list= NULL;
    +  mergeT *merge;
    +  mergeType mergetype;
    +  int neighbor_i, neighbor_n;
    +
    +  if (qh->hasTriangulation)
    +      return;
    +  trace1((qh, qh->ferr, 1034, "qh_triangulate: triangulate non-simplicial facets\n"));
    +  if (qh->hull_dim == 2)
    +    return;
    +  if (qh->VORONOI) {  /* otherwise lose Voronoi centers [could rebuild vertex set from tricoplanar] */
    +    qh_clearcenters(qh, qh_ASvoronoi);
    +    qh_vertexneighbors(qh);
    +  }
    +  qh->ONLYgood= False; /* for makenew_nonsimplicial */
    +  qh->visit_id++;
    +  qh->NEWfacets= True;
    +  qh->degen_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh->newvertex_list= qh->vertex_tail;
    +  for (facet= qh->facet_list; facet && facet->next; facet= nextfacet) { /* non-simplicial facets moved to end */
    +    nextfacet= facet->next;
    +    if (facet->visible || facet->simplicial)
    +      continue;
    +    /* triangulate all non-simplicial facets, otherwise merging does not work, e.g., RBOX c P-0.1 P+0.1 P+0.1 D3 | QHULL d Qt Tv */
    +    if (!new_facet_list)
    +      new_facet_list= facet;  /* will be moved to end */
    +    qh_triangulate_facet(qh, facet, &new_vertex_list);
    +  }
    +  trace2((qh, qh->ferr, 2047, "qh_triangulate: delete null facets from f%d -- apex same as second vertex\n", getid_(new_facet_list)));
    +  for (facet= new_facet_list; facet && facet->next; facet= nextfacet) { /* null facets moved to end */
    +    nextfacet= facet->next;
    +    if (facet->visible)
    +      continue;
    +    if (facet->ridges) {
    +      if (qh_setsize(qh, facet->ridges) > 0) {
    +        qh_fprintf(qh, qh->ferr, 6161, "qhull error (qh_triangulate): ridges still defined for f%d\n", facet->id);
    +        qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +      }
    +      qh_setfree(qh, &facet->ridges);
    +    }
    +    if (SETfirst_(facet->vertices) == SETsecond_(facet->vertices)) {
    +      zinc_(Ztrinull);
    +      qh_triangulate_null(qh, facet);
    +    }
    +  }
    +  trace2((qh, qh->ferr, 2048, "qh_triangulate: delete %d or more mirror facets -- same vertices and neighbors\n", qh_setsize(qh, qh->degen_mergeset)));
    +  qh->visible_list= qh->facet_tail;
    +  while ((merge= (mergeT*)qh_setdellast(qh->degen_mergeset))) {
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    mergetype= merge->type;
    +    qh_memfree(qh, merge, (int)sizeof(mergeT));
    +    if (mergetype == MRGmirror) {
    +      zinc_(Ztrimirror);
    +      qh_triangulate_mirror(qh, facet1, facet2);
    +    }
    +  }
    +  qh_settempfree(qh, &qh->degen_mergeset);
    +  trace2((qh, qh->ferr, 2049, "qh_triangulate: update neighbor lists for vertices from v%d\n", getid_(new_vertex_list)));
    +  qh->newvertex_list= new_vertex_list;  /* all vertices of new facets */
    +  qh->visible_list= NULL;
    +  qh_updatevertices(qh /*qh.newvertex_list, empty newfacet_list and visible_list*/);
    +  qh_resetlists(qh, False, !qh_RESETvisible /*qh.newvertex_list, empty newfacet_list and visible_list*/);
    +
    +  trace2((qh, qh->ferr, 2050, "qh_triangulate: identify degenerate tricoplanar facets from f%d\n", getid_(new_facet_list)));
    +  trace2((qh, qh->ferr, 2051, "qh_triangulate: and replace facet->f.triowner with tricoplanar facets that own center, normal, etc.\n"));
    +  FORALLfacet_(new_facet_list) {
    +    if (facet->tricoplanar && !facet->visible) {
    +      FOREACHneighbor_i_(qh, facet) {
    +        if (neighbor_i == 0) {  /* first iteration */
    +          if (neighbor->tricoplanar)
    +            orig_neighbor= neighbor->f.triowner;
    +          else
    +            orig_neighbor= neighbor;
    +        }else {
    +          if (neighbor->tricoplanar)
    +            otherfacet= neighbor->f.triowner;
    +          else
    +            otherfacet= neighbor;
    +          if (orig_neighbor == otherfacet) {
    +            zinc_(Ztridegen);
    +            facet->degenerate= True;
    +            break;
    +          }
    +        }
    +      }
    +    }
    +  }
    +
    +  trace2((qh, qh->ferr, 2052, "qh_triangulate: delete visible facets -- non-simplicial, null, and mirrored facets\n"));
    +  owner= NULL;
    +  visible= NULL;
    +  for (facet= new_facet_list; facet && facet->next; facet= nextfacet) { /* may delete facet */
    +    nextfacet= facet->next;
    +    if (facet->visible) {
    +      if (facet->tricoplanar) { /* a null or mirrored facet */
    +        qh_delfacet(qh, facet);
    +        qh->num_visible--;
    +      }else {  /* a non-simplicial facet followed by its tricoplanars */
    +        if (visible && !owner) {
    +          /*  RBOX 200 s D5 t1001471447 | QHULL Qt C-0.01 Qx Qc Tv Qt -- f4483 had 6 vertices/neighbors and 8 ridges */
    +          trace2((qh, qh->ferr, 2053, "qh_triangulate: all tricoplanar facets degenerate for non-simplicial facet f%d\n",
    +                       visible->id));
    +          qh_delfacet(qh, visible);
    +          qh->num_visible--;
    +        }
    +        visible= facet;
    +        owner= NULL;
    +      }
    +    }else if (facet->tricoplanar) {
    +      if (facet->f.triowner != visible || visible==NULL) {
    +        qh_fprintf(qh, qh->ferr, 6162, "qhull error (qh_triangulate): tricoplanar facet f%d not owned by its visible, non-simplicial facet f%d\n", facet->id, getid_(visible));
    +        qh_errexit2(qh, qh_ERRqhull, facet, visible);
    +      }
    +      if (owner)
    +        facet->f.triowner= owner;
    +      else if (!facet->degenerate) {
    +        owner= facet;
    +        nextfacet= visible->next; /* rescan tricoplanar facets with owner, visible!=0 by QH6162 */
    +        facet->keepcentrum= True;  /* one facet owns ->normal, etc. */
    +        facet->coplanarset= visible->coplanarset;
    +        facet->outsideset= visible->outsideset;
    +        visible->coplanarset= NULL;
    +        visible->outsideset= NULL;
    +        if (!qh->TRInormals) { /* center and normal copied to tricoplanar facets */
    +          visible->center= NULL;
    +          visible->normal= NULL;
    +        }
    +        qh_delfacet(qh, visible);
    +        qh->num_visible--;
    +      }
    +    }
    +  }
    +  if (visible && !owner) {
    +    trace2((qh, qh->ferr, 2054, "qh_triangulate: all tricoplanar facets degenerate for last non-simplicial facet f%d\n",
    +                 visible->id));
    +    qh_delfacet(qh, visible);
    +    qh->num_visible--;
    +  }
    +  qh->NEWfacets= False;
    +  qh->ONLYgood= onlygood; /* restore value */
    +  if (qh->CHECKfrequently)
    +    qh_checkpolygon(qh, qh->facet_list);
    +  qh->hasTriangulation= True;
    +} /* triangulate */
    +
    +
    +/*---------------------------------
    +
    +  qh_triangulate_facet(qh, facetA, &firstVertex )
    +    triangulate a non-simplicial facet
    +      if qh.CENTERtype=qh_ASvoronoi, sets its Voronoi center
    +  returns:
    +    qh.newfacet_list == simplicial facets
    +      facet->tricoplanar set and ->keepcentrum false
    +      facet->degenerate set if duplicated apex
    +      facet->f.trivisible set to facetA
    +      facet->center copied from facetA (created if qh_ASvoronoi)
    +        qh_eachvoronoi, qh_detvridge, qh_detvridge3 assume centers copied
    +      facet->normal,offset,maxoutside copied from facetA
    +
    +  notes:
    +      only called by qh_triangulate
    +      qh_makenew_nonsimplicial uses neighbor->seen for the same
    +      if qh.TRInormals, newfacet->normal will need qh_free
    +        if qh.TRInormals and qh_AScentrum, newfacet->center will need qh_free
    +        keepcentrum is also set on Zwidefacet in qh_mergefacet
    +        freed by qh_clearcenters
    +
    +  see also:
    +      qh_addpoint() -- add a point
    +      qh_makenewfacets() -- construct a cone of facets for a new vertex
    +
    +  design:
    +      if qh_ASvoronoi,
    +         compute Voronoi center (facet->center)
    +      select first vertex (highest ID to preserve ID ordering of ->vertices)
    +      triangulate from vertex to ridges
    +      copy facet->center, normal, offset
    +      update vertex neighbors
    +*/
    +void qh_triangulate_facet(qhT *qh, facetT *facetA, vertexT **first_vertex) {
    +  facetT *newfacet;
    +  facetT *neighbor, **neighborp;
    +  vertexT *apex;
    +  int numnew=0;
    +
    +  trace3((qh, qh->ferr, 3020, "qh_triangulate_facet: triangulate facet f%d\n", facetA->id));
    +
    +  if (qh->IStracing >= 4)
    +    qh_printfacet(qh, qh->ferr, facetA);
    +  FOREACHneighbor_(facetA) {
    +    neighbor->seen= False;
    +    neighbor->coplanar= False;
    +  }
    +  if (qh->CENTERtype == qh_ASvoronoi && !facetA->center  /* matches upperdelaunay in qh_setfacetplane() */
    +        && fabs_(facetA->normal[qh->hull_dim -1]) >= qh->ANGLEround * qh_ZEROdelaunay) {
    +    facetA->center= qh_facetcenter(qh, facetA->vertices);
    +  }
    +  qh_willdelete(qh, facetA, NULL);
    +  qh->newfacet_list= qh->facet_tail;
    +  facetA->visitid= qh->visit_id;
    +  apex= SETfirstt_(facetA->vertices, vertexT);
    +  qh_makenew_nonsimplicial(qh, facetA, apex, &numnew);
    +  SETfirst_(facetA->neighbors)= NULL;
    +  FORALLnew_facets {
    +    newfacet->tricoplanar= True;
    +    newfacet->f.trivisible= facetA;
    +    newfacet->degenerate= False;
    +    newfacet->upperdelaunay= facetA->upperdelaunay;
    +    newfacet->good= facetA->good;
    +    if (qh->TRInormals) { /* 'Q11' triangulate duplicates ->normal and ->center */
    +      newfacet->keepcentrum= True;
    +      if(facetA->normal){
    +        newfacet->normal= qh_memalloc(qh, qh->normal_size);
    +        memcpy((char *)newfacet->normal, facetA->normal, qh->normal_size);
    +      }
    +      if (qh->CENTERtype == qh_AScentrum)
    +        newfacet->center= qh_getcentrum(qh, newfacet);
    +      else if (qh->CENTERtype == qh_ASvoronoi && facetA->center){
    +        newfacet->center= qh_memalloc(qh, qh->center_size);
    +        memcpy((char *)newfacet->center, facetA->center, qh->center_size);
    +      }
    +    }else {
    +      newfacet->keepcentrum= False;
    +      /* one facet will have keepcentrum=True at end of qh_triangulate */
    +      newfacet->normal= facetA->normal;
    +      newfacet->center= facetA->center;
    +    }
    +    newfacet->offset= facetA->offset;
    +#if qh_MAXoutside
    +    newfacet->maxoutside= facetA->maxoutside;
    +#endif
    +  }
    +  qh_matchnewfacets(qh /*qh.newfacet_list*/);
    +  zinc_(Ztricoplanar);
    +  zadd_(Ztricoplanartot, numnew);
    +  zmax_(Ztricoplanarmax, numnew);
    +  qh->visible_list= NULL;
    +  if (!(*first_vertex))
    +    (*first_vertex)= qh->newvertex_list;
    +  qh->newvertex_list= NULL;
    +  qh_updatevertices(qh /*qh.newfacet_list, qh.empty visible_list and qh.newvertex_list*/);
    +  qh_resetlists(qh, False, !qh_RESETvisible /*qh.newfacet_list, qh.empty visible_list and qh.newvertex_list*/);
    +} /* triangulate_facet */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_link(qh, oldfacetA, facetA, oldfacetB, facetB)
    +    relink facetA to facetB via oldfacets
    +  returns:
    +    adds mirror facets to qh->degen_mergeset (4-d and up only)
    +  design:
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_link(qhT *qh, facetT *oldfacetA, facetT *facetA, facetT *oldfacetB, facetT *facetB) {
    +  int errmirror= False;
    +
    +  trace3((qh, qh->ferr, 3021, "qh_triangulate_link: relink old facets f%d and f%d between neighbors f%d and f%d\n",
    +         oldfacetA->id, oldfacetB->id, facetA->id, facetB->id));
    +  if (qh_setin(facetA->neighbors, facetB)) {
    +    if (!qh_setin(facetB->neighbors, facetA))
    +      errmirror= True;
    +    else
    +      qh_appendmergeset(qh, facetA, facetB, MRGmirror, NULL);
    +  }else if (qh_setin(facetB->neighbors, facetA))
    +    errmirror= True;
    +  if (errmirror) {
    +    qh_fprintf(qh, qh->ferr, 6163, "qhull error (qh_triangulate_link): mirror facets f%d and f%d do not match for old facets f%d and f%d\n",
    +       facetA->id, facetB->id, oldfacetA->id, oldfacetB->id);
    +    qh_errexit2(qh, qh_ERRqhull, facetA, facetB);
    +  }
    +  qh_setreplace(qh, facetB->neighbors, oldfacetB, facetA);
    +  qh_setreplace(qh, facetA->neighbors, oldfacetA, facetB);
    +} /* triangulate_link */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_mirror(qh, facetA, facetB)
    +    delete mirrored facets from qh_triangulate_null() and qh_triangulate_mirror
    +      a mirrored facet shares the same vertices of a logical ridge
    +  design:
    +    since a null facet duplicates the first two vertices, the opposing neighbors absorb the null facet
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_mirror(qhT *qh, facetT *facetA, facetT *facetB) {
    +  facetT *neighbor, *neighborB;
    +  int neighbor_i, neighbor_n;
    +
    +  trace3((qh, qh->ferr, 3022, "qh_triangulate_mirror: delete mirrored facets f%d and f%d\n",
    +         facetA->id, facetB->id));
    +  FOREACHneighbor_i_(qh, facetA) {
    +    neighborB= SETelemt_(facetB->neighbors, neighbor_i, facetT);
    +    if (neighbor == neighborB)
    +      continue; /* occurs twice */
    +    qh_triangulate_link(qh, facetA, neighbor, facetB, neighborB);
    +  }
    +  qh_willdelete(qh, facetA, NULL);
    +  qh_willdelete(qh, facetB, NULL);
    +} /* triangulate_mirror */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_null(qh, facetA)
    +    remove null facetA from qh_triangulate_facet()
    +      a null facet has vertex #1 (apex) == vertex #2
    +  returns:
    +    adds facetA to ->visible for deletion after qh_updatevertices
    +    qh->degen_mergeset contains mirror facets (4-d and up only)
    +  design:
    +    since a null facet duplicates the first two vertices, the opposing neighbors absorb the null facet
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_null(qhT *qh, facetT *facetA) {
    +  facetT *neighbor, *otherfacet;
    +
    +  trace3((qh, qh->ferr, 3023, "qh_triangulate_null: delete null facet f%d\n", facetA->id));
    +  neighbor= SETfirstt_(facetA->neighbors, facetT);
    +  otherfacet= SETsecondt_(facetA->neighbors, facetT);
    +  qh_triangulate_link(qh, facetA, neighbor, facetA, otherfacet);
    +  qh_willdelete(qh, facetA, NULL);
    +} /* triangulate_null */
    +
    +#else /* qh_NOmerge */
    +void qh_triangulate(qhT *qh) {
    +}
    +#endif /* qh_NOmerge */
    +
    +   /*---------------------------------
    +
    +  qh_vertexintersect(qh, vertexsetA, vertexsetB )
    +    intersects two vertex sets (inverse id ordered)
    +    vertexsetA is a temporary set at the top of qh->qhmem.tempstack
    +
    +  returns:
    +    replaces vertexsetA with the intersection
    +
    +  notes:
    +    could overwrite vertexsetA if currently too slow
    +*/
    +void qh_vertexintersect(qhT *qh, setT **vertexsetA,setT *vertexsetB) {
    +  setT *intersection;
    +
    +  intersection= qh_vertexintersect_new(qh, *vertexsetA, vertexsetB);
    +  qh_settempfree(qh, vertexsetA);
    +  *vertexsetA= intersection;
    +  qh_settemppush(qh, intersection);
    +} /* vertexintersect */
    +
    +/*---------------------------------
    +
    +  qh_vertexintersect_new(qh, )
    +    intersects two vertex sets (inverse id ordered)
    +
    +  returns:
    +    a new set
    +*/
    +setT *qh_vertexintersect_new(qhT *qh, setT *vertexsetA,setT *vertexsetB) {
    +  setT *intersection= qh_setnew(qh, qh->hull_dim - 1);
    +  vertexT **vertexA= SETaddr_(vertexsetA, vertexT);
    +  vertexT **vertexB= SETaddr_(vertexsetB, vertexT);
    +
    +  while (*vertexA && *vertexB) {
    +    if (*vertexA  == *vertexB) {
    +      qh_setappend(qh, &intersection, *vertexA);
    +      vertexA++; vertexB++;
    +    }else {
    +      if ((*vertexA)->id > (*vertexB)->id)
    +        vertexA++;
    +      else
    +        vertexB++;
    +    }
    +  }
    +  return intersection;
    +} /* vertexintersect_new */
    +
    +/*---------------------------------
    +
    +  qh_vertexneighbors(qh)
    +    for each vertex in qh.facet_list,
    +      determine its neighboring facets
    +
    +  returns:
    +    sets qh.VERTEXneighbors
    +      nop if qh.VERTEXneighbors already set
    +      qh_addpoint() will maintain them
    +
    +  notes:
    +    assumes all vertex->neighbors are NULL
    +
    +  design:
    +    for each facet
    +      for each vertex
    +        append facet to vertex->neighbors
    +*/
    +void qh_vertexneighbors(qhT *qh /*qh.facet_list*/) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh->VERTEXneighbors)
    +    return;
    +  trace1((qh, qh->ferr, 1035, "qh_vertexneighbors: determining neighboring facets for each vertex\n"));
    +  qh->vertex_visit++;
    +  FORALLfacets {
    +    if (facet->visible)
    +      continue;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh->vertex_visit) {
    +        vertex->visitid= qh->vertex_visit;
    +        vertex->neighbors= qh_setnew(qh, qh->hull_dim);
    +      }
    +      qh_setappend(qh, &vertex->neighbors, facet);
    +    }
    +  }
    +  qh->VERTEXneighbors= True;
    +} /* vertexneighbors */
    +
    +/*---------------------------------
    +
    +  qh_vertexsubset( vertexsetA, vertexsetB )
    +    returns True if vertexsetA is a subset of vertexsetB
    +    assumes vertexsets are sorted
    +
    +  note:
    +    empty set is a subset of any other set
    +*/
    +boolT qh_vertexsubset(setT *vertexsetA, setT *vertexsetB) {
    +  vertexT **vertexA= (vertexT **) SETaddr_(vertexsetA, vertexT);
    +  vertexT **vertexB= (vertexT **) SETaddr_(vertexsetB, vertexT);
    +
    +  while (True) {
    +    if (!*vertexA)
    +      return True;
    +    if (!*vertexB)
    +      return False;
    +    if ((*vertexA)->id > (*vertexB)->id)
    +      return False;
    +    if (*vertexA  == *vertexB)
    +      vertexA++;
    +    vertexB++;
    +  }
    +  return False; /* avoid warnings */
    +} /* vertexsubset */
    diff --git a/xs/src/qhull/src/libqhull_r/poly_r.c b/xs/src/qhull/src/libqhull_r/poly_r.c
    new file mode 100644
    index 0000000000..e5b4797437
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/poly_r.c
    @@ -0,0 +1,1205 @@
    +/*
      ---------------------------------
    +
    +   poly_r.c
    +   implements polygons and simplices
    +
    +   see qh-poly_r.htm, poly_r.h and libqhull_r.h
    +
    +   infrequent code is in poly2_r.c
    +   (all but top 50 and their callers 12/3/95)
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/poly_r.c#3 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*======== functions in alphabetical order ==========*/
    +
    +/*---------------------------------
    +
    +  qh_appendfacet(qh, facet )
    +    appends facet to end of qh.facet_list,
    +
    +  returns:
    +    updates qh.newfacet_list, facet_next, facet_list
    +    increments qh.numfacets
    +
    +  notes:
    +    assumes qh.facet_list/facet_tail is defined (createsimplex)
    +
    +  see:
    +    qh_removefacet()
    +
    +*/
    +void qh_appendfacet(qhT *qh, facetT *facet) {
    +  facetT *tail= qh->facet_tail;
    +
    +  if (tail == qh->newfacet_list)
    +    qh->newfacet_list= facet;
    +  if (tail == qh->facet_next)
    +    qh->facet_next= facet;
    +  facet->previous= tail->previous;
    +  facet->next= tail;
    +  if (tail->previous)
    +    tail->previous->next= facet;
    +  else
    +    qh->facet_list= facet;
    +  tail->previous= facet;
    +  qh->num_facets++;
    +  trace4((qh, qh->ferr, 4044, "qh_appendfacet: append f%d to facet_list\n", facet->id));
    +} /* appendfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_appendvertex(qh, vertex )
    +    appends vertex to end of qh.vertex_list,
    +
    +  returns:
    +    sets vertex->newlist
    +    updates qh.vertex_list, newvertex_list
    +    increments qh.num_vertices
    +
    +  notes:
    +    assumes qh.vertex_list/vertex_tail is defined (createsimplex)
    +
    +*/
    +void qh_appendvertex(qhT *qh, vertexT *vertex) {
    +  vertexT *tail= qh->vertex_tail;
    +
    +  if (tail == qh->newvertex_list)
    +    qh->newvertex_list= vertex;
    +  vertex->newlist= True;
    +  vertex->previous= tail->previous;
    +  vertex->next= tail;
    +  if (tail->previous)
    +    tail->previous->next= vertex;
    +  else
    +    qh->vertex_list= vertex;
    +  tail->previous= vertex;
    +  qh->num_vertices++;
    +  trace4((qh, qh->ferr, 4045, "qh_appendvertex: append v%d to vertex_list\n", vertex->id));
    +} /* appendvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_attachnewfacets(qh, )
    +    attach horizon facets to new facets in qh.newfacet_list
    +    newfacets have neighbor and ridge links to horizon but not vice versa
    +    only needed for qh.ONLYgood
    +
    +  returns:
    +    set qh.NEWfacets
    +    horizon facets linked to new facets
    +      ridges changed from visible facets to new facets
    +      simplicial ridges deleted
    +    qh.visible_list, no ridges valid
    +    facet->f.replace is a newfacet (if any)
    +
    +  design:
    +    delete interior ridges and neighbor sets by
    +      for each visible, non-simplicial facet
    +        for each ridge
    +          if last visit or if neighbor is simplicial
    +            if horizon neighbor
    +              delete ridge for horizon's ridge set
    +            delete ridge
    +        erase neighbor set
    +    attach horizon facets and new facets by
    +      for all new facets
    +        if corresponding horizon facet is simplicial
    +          locate corresponding visible facet {may be more than one}
    +          link visible facet to new facet
    +          replace visible facet with new facet in horizon
    +        else it's non-simplicial
    +          for all visible neighbors of the horizon facet
    +            link visible neighbor to new facet
    +            delete visible neighbor from horizon facet
    +          append new facet to horizon's neighbors
    +          the first ridge of the new facet is the horizon ridge
    +          link the new facet into the horizon ridge
    +*/
    +void qh_attachnewfacets(qhT *qh /* qh.visible_list, newfacet_list */) {
    +  facetT *newfacet= NULL, *neighbor, **neighborp, *horizon, *visible;
    +  ridgeT *ridge, **ridgep;
    +
    +  qh->NEWfacets= True;
    +  trace3((qh, qh->ferr, 3012, "qh_attachnewfacets: delete interior ridges\n"));
    +  qh->visit_id++;
    +  FORALLvisible_facets {
    +    visible->visitid= qh->visit_id;
    +    if (visible->ridges) {
    +      FOREACHridge_(visible->ridges) {
    +        neighbor= otherfacet_(ridge, visible);
    +        if (neighbor->visitid == qh->visit_id
    +            || (!neighbor->visible && neighbor->simplicial)) {
    +          if (!neighbor->visible)  /* delete ridge for simplicial horizon */
    +            qh_setdel(neighbor->ridges, ridge);
    +          qh_setfree(qh, &(ridge->vertices)); /* delete on 2nd visit */
    +          qh_memfree(qh, ridge, (int)sizeof(ridgeT));
    +        }
    +      }
    +      SETfirst_(visible->ridges)= NULL;
    +    }
    +    SETfirst_(visible->neighbors)= NULL;
    +  }
    +  trace1((qh, qh->ferr, 1017, "qh_attachnewfacets: attach horizon facets to new facets\n"));
    +  FORALLnew_facets {
    +    horizon= SETfirstt_(newfacet->neighbors, facetT);
    +    if (horizon->simplicial) {
    +      visible= NULL;
    +      FOREACHneighbor_(horizon) {   /* may have more than one horizon ridge */
    +        if (neighbor->visible) {
    +          if (visible) {
    +            if (qh_setequal_skip(newfacet->vertices, 0, horizon->vertices,
    +                                  SETindex_(horizon->neighbors, neighbor))) {
    +              visible= neighbor;
    +              break;
    +            }
    +          }else
    +            visible= neighbor;
    +        }
    +      }
    +      if (visible) {
    +        visible->f.replace= newfacet;
    +        qh_setreplace(qh, horizon->neighbors, visible, newfacet);
    +      }else {
    +        qh_fprintf(qh, qh->ferr, 6102, "qhull internal error (qh_attachnewfacets): couldn't find visible facet for horizon f%d of newfacet f%d\n",
    +                 horizon->id, newfacet->id);
    +        qh_errexit2(qh, qh_ERRqhull, horizon, newfacet);
    +      }
    +    }else { /* non-simplicial, with a ridge for newfacet */
    +      FOREACHneighbor_(horizon) {    /* may hold for many new facets */
    +        if (neighbor->visible) {
    +          neighbor->f.replace= newfacet;
    +          qh_setdelnth(qh, horizon->neighbors,
    +                        SETindex_(horizon->neighbors, neighbor));
    +          neighborp--; /* repeat */
    +        }
    +      }
    +      qh_setappend(qh, &horizon->neighbors, newfacet);
    +      ridge= SETfirstt_(newfacet->ridges, ridgeT);
    +      if (ridge->top == horizon)
    +        ridge->bottom= newfacet;
    +      else
    +        ridge->top= newfacet;
    +      }
    +  } /* newfacets */
    +  if (qh->PRINTstatistics) {
    +    FORALLvisible_facets {
    +      if (!visible->f.replace)
    +        zinc_(Zinsidevisible);
    +    }
    +  }
    +} /* attachnewfacets */
    +
    +/*---------------------------------
    +
    +  qh_checkflipped(qh, facet, dist, allerror )
    +    checks facet orientation to interior point
    +
    +    if allerror set,
    +      tests against qh.DISTround
    +    else
    +      tests against 0 since tested against DISTround before
    +
    +  returns:
    +    False if it flipped orientation (sets facet->flipped)
    +    distance if non-NULL
    +*/
    +boolT qh_checkflipped(qhT *qh, facetT *facet, realT *distp, boolT allerror) {
    +  realT dist;
    +
    +  if (facet->flipped && !distp)
    +    return False;
    +  zzinc_(Zdistcheck);
    +  qh_distplane(qh, qh->interior_point, facet, &dist);
    +  if (distp)
    +    *distp= dist;
    +  if ((allerror && dist > -qh->DISTround)|| (!allerror && dist >= 0.0)) {
    +    facet->flipped= True;
    +    zzinc_(Zflippedfacets);
    +    trace0((qh, qh->ferr, 19, "qh_checkflipped: facet f%d is flipped, distance= %6.12g during p%d\n",
    +              facet->id, dist, qh->furthest_id));
    +    qh_precision(qh, "flipped facet");
    +    return False;
    +  }
    +  return True;
    +} /* checkflipped */
    +
    +/*---------------------------------
    +
    +  qh_delfacet(qh, facet )
    +    removes facet from facet_list and frees up its memory
    +
    +  notes:
    +    assumes vertices and ridges already freed
    +*/
    +void qh_delfacet(qhT *qh, facetT *facet) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  trace4((qh, qh->ferr, 4046, "qh_delfacet: delete f%d\n", facet->id));
    +  if (facet == qh->tracefacet)
    +    qh->tracefacet= NULL;
    +  if (facet == qh->GOODclosest)
    +    qh->GOODclosest= NULL;
    +  qh_removefacet(qh, facet);
    +  if (!facet->tricoplanar || facet->keepcentrum) {
    +    qh_memfree_(qh, facet->normal, qh->normal_size, freelistp);
    +    if (qh->CENTERtype == qh_ASvoronoi) {   /* braces for macro calls */
    +      qh_memfree_(qh, facet->center, qh->center_size, freelistp);
    +    }else /* AScentrum */ {
    +      qh_memfree_(qh, facet->center, qh->normal_size, freelistp);
    +    }
    +  }
    +  qh_setfree(qh, &(facet->neighbors));
    +  if (facet->ridges)
    +    qh_setfree(qh, &(facet->ridges));
    +  qh_setfree(qh, &(facet->vertices));
    +  if (facet->outsideset)
    +    qh_setfree(qh, &(facet->outsideset));
    +  if (facet->coplanarset)
    +    qh_setfree(qh, &(facet->coplanarset));
    +  qh_memfree_(qh, facet, (int)sizeof(facetT), freelistp);
    +} /* delfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_deletevisible()
    +    delete visible facets and vertices
    +
    +  returns:
    +    deletes each facet and removes from facetlist
    +    at exit, qh.visible_list empty (== qh.newfacet_list)
    +
    +  notes:
    +    ridges already deleted
    +    horizon facets do not reference facets on qh.visible_list
    +    new facets in qh.newfacet_list
    +    uses   qh.visit_id;
    +*/
    +void qh_deletevisible(qhT *qh /*qh.visible_list*/) {
    +  facetT *visible, *nextfacet;
    +  vertexT *vertex, **vertexp;
    +  int numvisible= 0, numdel= qh_setsize(qh, qh->del_vertices);
    +
    +  trace1((qh, qh->ferr, 1018, "qh_deletevisible: delete %d visible facets and %d vertices\n",
    +         qh->num_visible, numdel));
    +  for (visible= qh->visible_list; visible && visible->visible;
    +                visible= nextfacet) { /* deleting current */
    +    nextfacet= visible->next;
    +    numvisible++;
    +    qh_delfacet(qh, visible);
    +  }
    +  if (numvisible != qh->num_visible) {
    +    qh_fprintf(qh, qh->ferr, 6103, "qhull internal error (qh_deletevisible): qh->num_visible %d is not number of visible facets %d\n",
    +             qh->num_visible, numvisible);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  qh->num_visible= 0;
    +  zadd_(Zvisfacettot, numvisible);
    +  zmax_(Zvisfacetmax, numvisible);
    +  zzadd_(Zdelvertextot, numdel);
    +  zmax_(Zdelvertexmax, numdel);
    +  FOREACHvertex_(qh->del_vertices)
    +    qh_delvertex(qh, vertex);
    +  qh_settruncate(qh, qh->del_vertices, 0);
    +} /* deletevisible */
    +
    +/*---------------------------------
    +
    +  qh_facetintersect(qh, facetA, facetB, skipa, skipB, prepend )
    +    return vertices for intersection of two simplicial facets
    +    may include 1 prepended entry (if more, need to settemppush)
    +
    +  returns:
    +    returns set of qh.hull_dim-1 + prepend vertices
    +    returns skipped index for each test and checks for exactly one
    +
    +  notes:
    +    does not need settemp since set in quick memory
    +
    +  see also:
    +    qh_vertexintersect and qh_vertexintersect_new
    +    use qh_setnew_delnthsorted to get nth ridge (no skip information)
    +
    +  design:
    +    locate skipped vertex by scanning facet A's neighbors
    +    locate skipped vertex by scanning facet B's neighbors
    +    intersect the vertex sets
    +*/
    +setT *qh_facetintersect(qhT *qh, facetT *facetA, facetT *facetB,
    +                         int *skipA,int *skipB, int prepend) {
    +  setT *intersect;
    +  int dim= qh->hull_dim, i, j;
    +  facetT **neighborsA, **neighborsB;
    +
    +  neighborsA= SETaddr_(facetA->neighbors, facetT);
    +  neighborsB= SETaddr_(facetB->neighbors, facetT);
    +  i= j= 0;
    +  if (facetB == *neighborsA++)
    +    *skipA= 0;
    +  else if (facetB == *neighborsA++)
    +    *skipA= 1;
    +  else if (facetB == *neighborsA++)
    +    *skipA= 2;
    +  else {
    +    for (i=3; i < dim; i++) {
    +      if (facetB == *neighborsA++) {
    +        *skipA= i;
    +        break;
    +      }
    +    }
    +  }
    +  if (facetA == *neighborsB++)
    +    *skipB= 0;
    +  else if (facetA == *neighborsB++)
    +    *skipB= 1;
    +  else if (facetA == *neighborsB++)
    +    *skipB= 2;
    +  else {
    +    for (j=3; j < dim; j++) {
    +      if (facetA == *neighborsB++) {
    +        *skipB= j;
    +        break;
    +      }
    +    }
    +  }
    +  if (i >= dim || j >= dim) {
    +    qh_fprintf(qh, qh->ferr, 6104, "qhull internal error (qh_facetintersect): f%d or f%d not in others neighbors\n",
    +            facetA->id, facetB->id);
    +    qh_errexit2(qh, qh_ERRqhull, facetA, facetB);
    +  }
    +  intersect= qh_setnew_delnthsorted(qh, facetA->vertices, qh->hull_dim, *skipA, prepend);
    +  trace4((qh, qh->ferr, 4047, "qh_facetintersect: f%d skip %d matches f%d skip %d\n",
    +          facetA->id, *skipA, facetB->id, *skipB));
    +  return(intersect);
    +} /* facetintersect */
    +
    +/*---------------------------------
    +
    +  qh_gethash(qh, hashsize, set, size, firstindex, skipelem )
    +    return hashvalue for a set with firstindex and skipelem
    +
    +  notes:
    +    returned hash is in [0,hashsize)
    +    assumes at least firstindex+1 elements
    +    assumes skipelem is NULL, in set, or part of hash
    +
    +    hashes memory addresses which may change over different runs of the same data
    +    using sum for hash does badly in high d
    +*/
    +int qh_gethash(qhT *qh, int hashsize, setT *set, int size, int firstindex, void *skipelem) {
    +  void **elemp= SETelemaddr_(set, firstindex, void);
    +  ptr_intT hash = 0, elem;
    +  unsigned result;
    +  int i;
    +#ifdef _MSC_VER                   /* Microsoft Visual C++ -- warn about 64-bit issues */
    +#pragma warning( push)            /* WARN64 -- ptr_intT holds a 64-bit pointer */
    +#pragma warning( disable : 4311)  /* 'type cast': pointer truncation from 'void*' to 'ptr_intT' */
    +#endif
    +
    +  switch (size-firstindex) {
    +  case 1:
    +    hash= (ptr_intT)(*elemp) - (ptr_intT) skipelem;
    +    break;
    +  case 2:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] - (ptr_intT) skipelem;
    +    break;
    +  case 3:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      - (ptr_intT) skipelem;
    +    break;
    +  case 4:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] - (ptr_intT) skipelem;
    +    break;
    +  case 5:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] + (ptr_intT)elemp[4] - (ptr_intT) skipelem;
    +    break;
    +  case 6:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] + (ptr_intT)elemp[4]+ (ptr_intT)elemp[5]
    +      - (ptr_intT) skipelem;
    +    break;
    +  default:
    +    hash= 0;
    +    i= 3;
    +    do {     /* this is about 10% in 10-d */
    +      if ((elem= (ptr_intT)*elemp++) != (ptr_intT)skipelem) {
    +        hash ^= (elem << i) + (elem >> (32-i));
    +        i += 3;
    +        if (i >= 32)
    +          i -= 32;
    +      }
    +    }while (*elemp);
    +    break;
    +  }
    +  if (hashsize<0) {
    +    qh_fprintf(qh, qh->ferr, 6202, "qhull internal error: negative hashsize %d passed to qh_gethash [poly.c]\n", hashsize);
    +    qh_errexit2(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  result= (unsigned)hash;
    +  result %= (unsigned)hashsize;
    +  /* result= 0; for debugging */
    +  return result;
    +#ifdef _MSC_VER
    +#pragma warning( pop)
    +#endif
    +} /* gethash */
    +
    +/*---------------------------------
    +
    +  qh_makenewfacet(qh, vertices, toporient, horizon )
    +    creates a toporient? facet from vertices
    +
    +  returns:
    +    returns newfacet
    +      adds newfacet to qh.facet_list
    +      newfacet->vertices= vertices
    +      if horizon
    +        newfacet->neighbor= horizon, but not vice versa
    +    newvertex_list updated with vertices
    +*/
    +facetT *qh_makenewfacet(qhT *qh, setT *vertices, boolT toporient,facetT *horizon) {
    +  facetT *newfacet;
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->newlist) {
    +      qh_removevertex(qh, vertex);
    +      qh_appendvertex(qh, vertex);
    +    }
    +  }
    +  newfacet= qh_newfacet(qh);
    +  newfacet->vertices= vertices;
    +  newfacet->toporient= (unsigned char)toporient;
    +  if (horizon)
    +    qh_setappend(qh, &(newfacet->neighbors), horizon);
    +  qh_appendfacet(qh, newfacet);
    +  return(newfacet);
    +} /* makenewfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_makenewplanes()
    +    make new hyperplanes for facets on qh.newfacet_list
    +
    +  returns:
    +    all facets have hyperplanes or are marked for   merging
    +    doesn't create hyperplane if horizon is coplanar (will merge)
    +    updates qh.min_vertex if qh.JOGGLEmax
    +
    +  notes:
    +    facet->f.samecycle is defined for facet->mergehorizon facets
    +*/
    +void qh_makenewplanes(qhT *qh /* qh.newfacet_list */) {
    +  facetT *newfacet;
    +
    +  FORALLnew_facets {
    +    if (!newfacet->mergehorizon)
    +      qh_setfacetplane(qh, newfacet);
    +  }
    +  if (qh->JOGGLEmax < REALmax/2)
    +    minimize_(qh->min_vertex, -wwval_(Wnewvertexmax));
    +} /* makenewplanes */
    +
    +/*---------------------------------
    +
    +  qh_makenew_nonsimplicial(qh, visible, apex, numnew )
    +    make new facets for ridges of a visible facet
    +
    +  returns:
    +    first newfacet, bumps numnew as needed
    +    attaches new facets if !qh.ONLYgood
    +    marks ridge neighbors for simplicial visible
    +    if (qh.ONLYgood)
    +      ridges on newfacet, horizon, and visible
    +    else
    +      ridge and neighbors between newfacet and   horizon
    +      visible facet's ridges are deleted
    +
    +  notes:
    +    qh.visit_id if visible has already been processed
    +    sets neighbor->seen for building f.samecycle
    +      assumes all 'seen' flags initially false
    +
    +  design:
    +    for each ridge of visible facet
    +      get neighbor of visible facet
    +      if neighbor was already processed
    +        delete the ridge (will delete all visible facets later)
    +      if neighbor is a horizon facet
    +        create a new facet
    +        if neighbor coplanar
    +          adds newfacet to f.samecycle for later merging
    +        else
    +          updates neighbor's neighbor set
    +          (checks for non-simplicial facet with multiple ridges to visible facet)
    +        updates neighbor's ridge set
    +        (checks for simplicial neighbor to non-simplicial visible facet)
    +        (deletes ridge if neighbor is simplicial)
    +
    +*/
    +#ifndef qh_NOmerge
    +facetT *qh_makenew_nonsimplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +  ridgeT *ridge, **ridgep;
    +  facetT *neighbor, *newfacet= NULL, *samecycle;
    +  setT *vertices;
    +  boolT toporient;
    +  int ridgeid;
    +
    +  FOREACHridge_(visible->ridges) {
    +    ridgeid= ridge->id;
    +    neighbor= otherfacet_(ridge, visible);
    +    if (neighbor->visible) {
    +      if (!qh->ONLYgood) {
    +        if (neighbor->visitid == qh->visit_id) {
    +          qh_setfree(qh, &(ridge->vertices));  /* delete on 2nd visit */
    +          qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
    +        }
    +      }
    +    }else {  /* neighbor is an horizon facet */
    +      toporient= (ridge->top == visible);
    +      vertices= qh_setnew(qh, qh->hull_dim); /* makes sure this is quick */
    +      qh_setappend(qh, &vertices, apex);
    +      qh_setappend_set(qh, &vertices, ridge->vertices);
    +      newfacet= qh_makenewfacet(qh, vertices, toporient, neighbor);
    +      (*numnew)++;
    +      if (neighbor->coplanar) {
    +        newfacet->mergehorizon= True;
    +        if (!neighbor->seen) {
    +          newfacet->f.samecycle= newfacet;
    +          neighbor->f.newcycle= newfacet;
    +        }else {
    +          samecycle= neighbor->f.newcycle;
    +          newfacet->f.samecycle= samecycle->f.samecycle;
    +          samecycle->f.samecycle= newfacet;
    +        }
    +      }
    +      if (qh->ONLYgood) {
    +        if (!neighbor->simplicial)
    +          qh_setappend(qh, &(newfacet->ridges), ridge);
    +      }else {  /* qh_attachnewfacets */
    +        if (neighbor->seen) {
    +          if (neighbor->simplicial) {
    +            qh_fprintf(qh, qh->ferr, 6105, "qhull internal error (qh_makenew_nonsimplicial): simplicial f%d sharing two ridges with f%d\n",
    +                   neighbor->id, visible->id);
    +            qh_errexit2(qh, qh_ERRqhull, neighbor, visible);
    +          }
    +          qh_setappend(qh, &(neighbor->neighbors), newfacet);
    +        }else
    +          qh_setreplace(qh, neighbor->neighbors, visible, newfacet);
    +        if (neighbor->simplicial) {
    +          qh_setdel(neighbor->ridges, ridge);
    +          qh_setfree(qh, &(ridge->vertices));
    +          qh_memfree(qh, ridge, (int)sizeof(ridgeT));
    +        }else {
    +          qh_setappend(qh, &(newfacet->ridges), ridge);
    +          if (toporient)
    +            ridge->top= newfacet;
    +          else
    +            ridge->bottom= newfacet;
    +        }
    +      trace4((qh, qh->ferr, 4048, "qh_makenew_nonsimplicial: created facet f%d from v%d and r%d of horizon f%d\n",
    +            newfacet->id, apex->id, ridgeid, neighbor->id));
    +      }
    +    }
    +    neighbor->seen= True;
    +  } /* for each ridge */
    +  if (!qh->ONLYgood)
    +    SETfirst_(visible->ridges)= NULL;
    +  return newfacet;
    +} /* makenew_nonsimplicial */
    +#else /* qh_NOmerge */
    +facetT *qh_makenew_nonsimplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew) {
    +  return NULL;
    +}
    +#endif /* qh_NOmerge */
    +
    +/*---------------------------------
    +
    +  qh_makenew_simplicial(qh, visible, apex, numnew )
    +    make new facets for simplicial visible facet and apex
    +
    +  returns:
    +    attaches new facets if (!qh.ONLYgood)
    +      neighbors between newfacet and horizon
    +
    +  notes:
    +    nop if neighbor->seen or neighbor->visible(see qh_makenew_nonsimplicial)
    +
    +  design:
    +    locate neighboring horizon facet for visible facet
    +    determine vertices and orientation
    +    create new facet
    +    if coplanar,
    +      add new facet to f.samecycle
    +    update horizon facet's neighbor list
    +*/
    +facetT *qh_makenew_simplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew) {
    +  facetT *neighbor, **neighborp, *newfacet= NULL;
    +  setT *vertices;
    +  boolT flip, toporient;
    +  int horizonskip= 0, visibleskip= 0;
    +
    +  FOREACHneighbor_(visible) {
    +    if (!neighbor->seen && !neighbor->visible) {
    +      vertices= qh_facetintersect(qh, neighbor,visible, &horizonskip, &visibleskip, 1);
    +      SETfirst_(vertices)= apex;
    +      flip= ((horizonskip & 0x1) ^ (visibleskip & 0x1));
    +      if (neighbor->toporient)
    +        toporient= horizonskip & 0x1;
    +      else
    +        toporient= (horizonskip & 0x1) ^ 0x1;
    +      newfacet= qh_makenewfacet(qh, vertices, toporient, neighbor);
    +      (*numnew)++;
    +      if (neighbor->coplanar && (qh->PREmerge || qh->MERGEexact)) {
    +#ifndef qh_NOmerge
    +        newfacet->f.samecycle= newfacet;
    +        newfacet->mergehorizon= True;
    +#endif
    +      }
    +      if (!qh->ONLYgood)
    +        SETelem_(neighbor->neighbors, horizonskip)= newfacet;
    +      trace4((qh, qh->ferr, 4049, "qh_makenew_simplicial: create facet f%d top %d from v%d and horizon f%d skip %d top %d and visible f%d skip %d, flip? %d\n",
    +            newfacet->id, toporient, apex->id, neighbor->id, horizonskip,
    +              neighbor->toporient, visible->id, visibleskip, flip));
    +    }
    +  }
    +  return newfacet;
    +} /* makenew_simplicial */
    +
    +/*---------------------------------
    +
    +  qh_matchneighbor(qh, newfacet, newskip, hashsize, hashcount )
    +    either match subridge of newfacet with neighbor or add to hash_table
    +
    +  returns:
    +    duplicate ridges are unmatched and marked by qh_DUPLICATEridge
    +
    +  notes:
    +    ridge is newfacet->vertices w/o newskip vertex
    +    do not allocate memory (need to free hash_table cleanly)
    +    uses linear hash chains
    +
    +  see also:
    +    qh_matchduplicates
    +
    +  design:
    +    for each possible matching facet in qh.hash_table
    +      if vertices match
    +        set ismatch, if facets have opposite orientation
    +        if ismatch and matching facet doesn't have a match
    +          match the facets by updating their neighbor sets
    +        else
    +          indicate a duplicate ridge
    +          set facet hyperplane for later testing
    +          add facet to hashtable
    +          unless the other facet was already a duplicate ridge
    +            mark both facets with a duplicate ridge
    +            add other facet (if defined) to hash table
    +*/
    +void qh_matchneighbor(qhT *qh, facetT *newfacet, int newskip, int hashsize, int *hashcount) {
    +  boolT newfound= False;   /* True, if new facet is already in hash chain */
    +  boolT same, ismatch;
    +  int hash, scan;
    +  facetT *facet, *matchfacet;
    +  int skip, matchskip;
    +
    +  hash= qh_gethash(qh, hashsize, newfacet->vertices, qh->hull_dim, 1,
    +                     SETelem_(newfacet->vertices, newskip));
    +  trace4((qh, qh->ferr, 4050, "qh_matchneighbor: newfacet f%d skip %d hash %d hashcount %d\n",
    +          newfacet->id, newskip, hash, *hashcount));
    +  zinc_(Zhashlookup);
    +  for (scan= hash; (facet= SETelemt_(qh->hash_table, scan, facetT));
    +       scan= (++scan >= hashsize ? 0 : scan)) {
    +    if (facet == newfacet) {
    +      newfound= True;
    +      continue;
    +    }
    +    zinc_(Zhashtests);
    +    if (qh_matchvertices(qh, 1, newfacet->vertices, newskip, facet->vertices, &skip, &same)) {
    +      if (SETelem_(newfacet->vertices, newskip) ==
    +          SETelem_(facet->vertices, skip)) {
    +        qh_precision(qh, "two facets with the same vertices");
    +        qh_fprintf(qh, qh->ferr, 6106, "qhull precision error: Vertex sets are the same for f%d and f%d.  Can not force output.\n",
    +          facet->id, newfacet->id);
    +        qh_errexit2(qh, qh_ERRprec, facet, newfacet);
    +      }
    +      ismatch= (same == (boolT)((newfacet->toporient ^ facet->toporient)));
    +      matchfacet= SETelemt_(facet->neighbors, skip, facetT);
    +      if (ismatch && !matchfacet) {
    +        SETelem_(facet->neighbors, skip)= newfacet;
    +        SETelem_(newfacet->neighbors, newskip)= facet;
    +        (*hashcount)--;
    +        trace4((qh, qh->ferr, 4051, "qh_matchneighbor: f%d skip %d matched with new f%d skip %d\n",
    +           facet->id, skip, newfacet->id, newskip));
    +        return;
    +      }
    +      if (!qh->PREmerge && !qh->MERGEexact) {
    +        qh_precision(qh, "a ridge with more than two neighbors");
    +        qh_fprintf(qh, qh->ferr, 6107, "qhull precision error: facets f%d, f%d and f%d meet at a ridge with more than 2 neighbors.  Can not continue.\n",
    +                 facet->id, newfacet->id, getid_(matchfacet));
    +        qh_errexit2(qh, qh_ERRprec, facet, newfacet);
    +      }
    +      SETelem_(newfacet->neighbors, newskip)= qh_DUPLICATEridge;
    +      newfacet->dupridge= True;
    +      if (!newfacet->normal)
    +        qh_setfacetplane(qh, newfacet);
    +      qh_addhash(newfacet, qh->hash_table, hashsize, hash);
    +      (*hashcount)++;
    +      if (!facet->normal)
    +        qh_setfacetplane(qh, facet);
    +      if (matchfacet != qh_DUPLICATEridge) {
    +        SETelem_(facet->neighbors, skip)= qh_DUPLICATEridge;
    +        facet->dupridge= True;
    +        if (!facet->normal)
    +          qh_setfacetplane(qh, facet);
    +        if (matchfacet) {
    +          matchskip= qh_setindex(matchfacet->neighbors, facet);
    +          if (matchskip<0) {
    +              qh_fprintf(qh, qh->ferr, 6260, "qhull internal error (qh_matchneighbor): matchfacet f%d is in f%d neighbors but not vice versa.  Can not continue.\n",
    +                  matchfacet->id, facet->id);
    +              qh_errexit2(qh, qh_ERRqhull, matchfacet, facet);
    +          }
    +          SETelem_(matchfacet->neighbors, matchskip)= qh_DUPLICATEridge; /* matchskip>=0 by QH6260 */
    +          matchfacet->dupridge= True;
    +          if (!matchfacet->normal)
    +            qh_setfacetplane(qh, matchfacet);
    +          qh_addhash(matchfacet, qh->hash_table, hashsize, hash);
    +          *hashcount += 2;
    +        }
    +      }
    +      trace4((qh, qh->ferr, 4052, "qh_matchneighbor: new f%d skip %d duplicates ridge for f%d skip %d matching f%d ismatch %d at hash %d\n",
    +           newfacet->id, newskip, facet->id, skip,
    +           (matchfacet == qh_DUPLICATEridge ? -2 : getid_(matchfacet)),
    +           ismatch, hash));
    +      return; /* end of duplicate ridge */
    +    }
    +  }
    +  if (!newfound)
    +    SETelem_(qh->hash_table, scan)= newfacet;  /* same as qh_addhash */
    +  (*hashcount)++;
    +  trace4((qh, qh->ferr, 4053, "qh_matchneighbor: no match for f%d skip %d at hash %d\n",
    +           newfacet->id, newskip, hash));
    +} /* matchneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_matchnewfacets()
    +    match newfacets in qh.newfacet_list to their newfacet neighbors
    +
    +  returns:
    +    qh.newfacet_list with full neighbor sets
    +      get vertices with nth neighbor by deleting nth vertex
    +    if qh.PREmerge/MERGEexact or qh.FORCEoutput
    +      sets facet->flippped if flipped normal (also prevents point partitioning)
    +    if duplicate ridges and qh.PREmerge/MERGEexact
    +      sets facet->dupridge
    +      missing neighbor links identifies extra ridges to be merging (qh_MERGEridge)
    +
    +  notes:
    +    newfacets already have neighbor[0] (horizon facet)
    +    assumes qh.hash_table is NULL
    +    vertex->neighbors has not been updated yet
    +    do not allocate memory after qh.hash_table (need to free it cleanly)
    +
    +  design:
    +    delete neighbor sets for all new facets
    +    initialize a hash table
    +    for all new facets
    +      match facet with neighbors
    +    if unmatched facets (due to duplicate ridges)
    +      for each new facet with a duplicate ridge
    +        match it with a facet
    +    check for flipped facets
    +*/
    +void qh_matchnewfacets(qhT *qh /* qh.newfacet_list */) {
    +  int numnew=0, hashcount=0, newskip;
    +  facetT *newfacet, *neighbor;
    +  int dim= qh->hull_dim, hashsize, neighbor_i, neighbor_n;
    +  setT *neighbors;
    +#ifndef qh_NOtrace
    +  int facet_i, facet_n, numfree= 0;
    +  facetT *facet;
    +#endif
    +
    +  trace1((qh, qh->ferr, 1019, "qh_matchnewfacets: match neighbors for new facets.\n"));
    +  FORALLnew_facets {
    +    numnew++;
    +    {  /* inline qh_setzero(qh, newfacet->neighbors, 1, qh->hull_dim); */
    +      neighbors= newfacet->neighbors;
    +      neighbors->e[neighbors->maxsize].i= dim+1; /*may be overwritten*/
    +      memset((char *)SETelemaddr_(neighbors, 1, void), 0, dim * SETelemsize);
    +    }
    +  }
    +
    +  qh_newhashtable(qh, numnew*(qh->hull_dim-1)); /* twice what is normally needed,
    +                                     but every ridge could be DUPLICATEridge */
    +  hashsize= qh_setsize(qh, qh->hash_table);
    +  FORALLnew_facets {
    +    for (newskip=1; newskiphull_dim; newskip++) /* furthest/horizon already matched */
    +      /* hashsize>0 because hull_dim>1 and numnew>0 */
    +      qh_matchneighbor(qh, newfacet, newskip, hashsize, &hashcount);
    +#if 0   /* use the following to trap hashcount errors */
    +    {
    +      int count= 0, k;
    +      facetT *facet, *neighbor;
    +
    +      count= 0;
    +      FORALLfacet_(qh->newfacet_list) {  /* newfacet already in use */
    +        for (k=1; k < qh->hull_dim; k++) {
    +          neighbor= SETelemt_(facet->neighbors, k, facetT);
    +          if (!neighbor || neighbor == qh_DUPLICATEridge)
    +            count++;
    +        }
    +        if (facet == newfacet)
    +          break;
    +      }
    +      if (count != hashcount) {
    +        qh_fprintf(qh, qh->ferr, 8088, "qh_matchnewfacets: after adding facet %d, hashcount %d != count %d\n",
    +                 newfacet->id, hashcount, count);
    +        qh_errexit(qh, qh_ERRqhull, newfacet, NULL);
    +      }
    +    }
    +#endif  /* end of trap code */
    +  }
    +  if (hashcount) {
    +    FORALLnew_facets {
    +      if (newfacet->dupridge) {
    +        FOREACHneighbor_i_(qh, newfacet) {
    +          if (neighbor == qh_DUPLICATEridge) {
    +            qh_matchduplicates(qh, newfacet, neighbor_i, hashsize, &hashcount);
    +                    /* this may report MERGEfacet */
    +          }
    +        }
    +      }
    +    }
    +  }
    +  if (hashcount) {
    +    qh_fprintf(qh, qh->ferr, 6108, "qhull internal error (qh_matchnewfacets): %d neighbors did not match up\n",
    +        hashcount);
    +    qh_printhashtable(qh, qh->ferr);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +#ifndef qh_NOtrace
    +  if (qh->IStracing >= 2) {
    +    FOREACHfacet_i_(qh, qh->hash_table) {
    +      if (!facet)
    +        numfree++;
    +    }
    +    qh_fprintf(qh, qh->ferr, 8089, "qh_matchnewfacets: %d new facets, %d unused hash entries .  hashsize %d\n",
    +             numnew, numfree, qh_setsize(qh, qh->hash_table));
    +  }
    +#endif /* !qh_NOtrace */
    +  qh_setfree(qh, &qh->hash_table);
    +  if (qh->PREmerge || qh->MERGEexact) {
    +    if (qh->IStracing >= 4)
    +      qh_printfacetlist(qh, qh->newfacet_list, NULL, qh_ALL);
    +    FORALLnew_facets {
    +      if (newfacet->normal)
    +        qh_checkflipped(qh, newfacet, NULL, qh_ALL);
    +    }
    +  }else if (qh->FORCEoutput)
    +    qh_checkflipped_all(qh, qh->newfacet_list);  /* prints warnings for flipped */
    +} /* matchnewfacets */
    +
    +
    +/*---------------------------------
    +
    +  qh_matchvertices(qh, firstindex, verticesA, skipA, verticesB, skipB, same )
    +    tests whether vertices match with a single skip
    +    starts match at firstindex since all new facets have a common vertex
    +
    +  returns:
    +    true if matched vertices
    +    skip index for each set
    +    sets same iff vertices have the same orientation
    +
    +  notes:
    +    assumes skipA is in A and both sets are the same size
    +
    +  design:
    +    set up pointers
    +    scan both sets checking for a match
    +    test orientation
    +*/
    +boolT qh_matchvertices(qhT *qh, int firstindex, setT *verticesA, int skipA,
    +       setT *verticesB, int *skipB, boolT *same) {
    +  vertexT **elemAp, **elemBp, **skipBp=NULL, **skipAp;
    +
    +  elemAp= SETelemaddr_(verticesA, firstindex, vertexT);
    +  elemBp= SETelemaddr_(verticesB, firstindex, vertexT);
    +  skipAp= SETelemaddr_(verticesA, skipA, vertexT);
    +  do if (elemAp != skipAp) {
    +    while (*elemAp != *elemBp++) {
    +      if (skipBp)
    +        return False;
    +      skipBp= elemBp;  /* one extra like FOREACH */
    +    }
    +  }while (*(++elemAp));
    +  if (!skipBp)
    +    skipBp= ++elemBp;
    +  *skipB= SETindex_(verticesB, skipB); /* i.e., skipBp - verticesB */
    +  *same= !((skipA & 0x1) ^ (*skipB & 0x1)); /* result is 0 or 1 */
    +  trace4((qh, qh->ferr, 4054, "qh_matchvertices: matched by skip %d(v%d) and skip %d(v%d) same? %d\n",
    +          skipA, (*skipAp)->id, *skipB, (*(skipBp-1))->id, *same));
    +  return(True);
    +} /* matchvertices */
    +
    +/*---------------------------------
    +
    +  qh_newfacet(qh)
    +    return a new facet
    +
    +  returns:
    +    all fields initialized or cleared   (NULL)
    +    preallocates neighbors set
    +*/
    +facetT *qh_newfacet(qhT *qh) {
    +  facetT *facet;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_(qh, (int)sizeof(facetT), freelistp, facet, facetT);
    +  memset((char *)facet, (size_t)0, sizeof(facetT));
    +  if (qh->facet_id == qh->tracefacet_id)
    +    qh->tracefacet= facet;
    +  facet->id= qh->facet_id++;
    +  facet->neighbors= qh_setnew(qh, qh->hull_dim);
    +#if !qh_COMPUTEfurthest
    +  facet->furthestdist= 0.0;
    +#endif
    +#if qh_MAXoutside
    +  if (qh->FORCEoutput && qh->APPROXhull)
    +    facet->maxoutside= qh->MINoutside;
    +  else
    +    facet->maxoutside= qh->DISTround;
    +#endif
    +  facet->simplicial= True;
    +  facet->good= True;
    +  facet->newfacet= True;
    +  trace4((qh, qh->ferr, 4055, "qh_newfacet: created facet f%d\n", facet->id));
    +  return(facet);
    +} /* newfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_newridge()
    +    return a new ridge
    +*/
    +ridgeT *qh_newridge(qhT *qh) {
    +  ridgeT *ridge;
    +  void **freelistp;   /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_(qh, (int)sizeof(ridgeT), freelistp, ridge, ridgeT);
    +  memset((char *)ridge, (size_t)0, sizeof(ridgeT));
    +  zinc_(Ztotridges);
    +  if (qh->ridge_id == UINT_MAX) {
    +    qh_fprintf(qh, qh->ferr, 7074, "\
    +qhull warning: more than 2^32 ridges.  Qhull results are OK.  Since the ridge ID wraps around to 0, two ridges may have the same identifier.\n");
    +  }
    +  ridge->id= qh->ridge_id++;
    +  trace4((qh, qh->ferr, 4056, "qh_newridge: created ridge r%d\n", ridge->id));
    +  return(ridge);
    +} /* newridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_pointid(qh, point )
    +    return id for a point,
    +    returns qh_IDnone(-3) if null, qh_IDinterior(-2) if interior, or qh_IDunknown(-1) if not known
    +
    +  alternative code if point is in qh.first_point...
    +    unsigned long id;
    +    id= ((unsigned long)point - (unsigned long)qh.first_point)/qh.normal_size;
    +
    +  notes:
    +    Valid points are non-negative
    +    WARN64 -- id truncated to 32-bits, at most 2G points
    +    NOerrors returned (QhullPoint::id)
    +    if point not in point array
    +      the code does a comparison of unrelated pointers.
    +*/
    +int qh_pointid(qhT *qh, pointT *point) {
    +  ptr_intT offset, id;
    +
    +  if (!point || !qh)
    +    return qh_IDnone;
    +  else if (point == qh->interior_point)
    +    return qh_IDinterior;
    +  else if (point >= qh->first_point
    +  && point < qh->first_point + qh->num_points * qh->hull_dim) {
    +    offset= (ptr_intT)(point - qh->first_point);
    +    id= offset / qh->hull_dim;
    +  }else if ((id= qh_setindex(qh->other_points, point)) != -1)
    +    id += qh->num_points;
    +  else
    +    return qh_IDunknown;
    +  return (int)id;
    +} /* pointid */
    +
    +/*---------------------------------
    +
    +  qh_removefacet(qh, facet )
    +    unlinks facet from qh.facet_list,
    +
    +  returns:
    +    updates qh.facet_list .newfacet_list .facet_next visible_list
    +    decrements qh.num_facets
    +
    +  see:
    +    qh_appendfacet
    +*/
    +void qh_removefacet(qhT *qh, facetT *facet) {
    +  facetT *next= facet->next, *previous= facet->previous;
    +
    +  if (facet == qh->newfacet_list)
    +    qh->newfacet_list= next;
    +  if (facet == qh->facet_next)
    +    qh->facet_next= next;
    +  if (facet == qh->visible_list)
    +    qh->visible_list= next;
    +  if (previous) {
    +    previous->next= next;
    +    next->previous= previous;
    +  }else {  /* 1st facet in qh->facet_list */
    +    qh->facet_list= next;
    +    qh->facet_list->previous= NULL;
    +  }
    +  qh->num_facets--;
    +  trace4((qh, qh->ferr, 4057, "qh_removefacet: remove f%d from facet_list\n", facet->id));
    +} /* removefacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_removevertex(qh, vertex )
    +    unlinks vertex from qh.vertex_list,
    +
    +  returns:
    +    updates qh.vertex_list .newvertex_list
    +    decrements qh.num_vertices
    +*/
    +void qh_removevertex(qhT *qh, vertexT *vertex) {
    +  vertexT *next= vertex->next, *previous= vertex->previous;
    +
    +  if (vertex == qh->newvertex_list)
    +    qh->newvertex_list= next;
    +  if (previous) {
    +    previous->next= next;
    +    next->previous= previous;
    +  }else {  /* 1st vertex in qh->vertex_list */
    +    qh->vertex_list= vertex->next;
    +    qh->vertex_list->previous= NULL;
    +  }
    +  qh->num_vertices--;
    +  trace4((qh, qh->ferr, 4058, "qh_removevertex: remove v%d from vertex_list\n", vertex->id));
    +} /* removevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_updatevertices()
    +    update vertex neighbors and delete interior vertices
    +
    +  returns:
    +    if qh.VERTEXneighbors, updates neighbors for each vertex
    +      if qh.newvertex_list,
    +         removes visible neighbors  from vertex neighbors
    +      if qh.newfacet_list
    +         adds new facets to vertex neighbors
    +    if qh.visible_list
    +       interior vertices added to qh.del_vertices for later partitioning
    +
    +  design:
    +    if qh.VERTEXneighbors
    +      deletes references to visible facets from vertex neighbors
    +      appends new facets to the neighbor list for each vertex
    +      checks all vertices of visible facets
    +        removes visible facets from neighbor lists
    +        marks unused vertices for deletion
    +*/
    +void qh_updatevertices(qhT *qh /*qh.newvertex_list, newfacet_list, visible_list*/) {
    +  facetT *newfacet= NULL, *neighbor, **neighborp, *visible;
    +  vertexT *vertex, **vertexp;
    +
    +  trace3((qh, qh->ferr, 3013, "qh_updatevertices: delete interior vertices and update vertex->neighbors\n"));
    +  if (qh->VERTEXneighbors) {
    +    FORALLvertex_(qh->newvertex_list) {
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visible)
    +          SETref_(neighbor)= NULL;
    +      }
    +      qh_setcompact(qh, vertex->neighbors);
    +    }
    +    FORALLnew_facets {
    +      FOREACHvertex_(newfacet->vertices)
    +        qh_setappend(qh, &vertex->neighbors, newfacet);
    +    }
    +    FORALLvisible_facets {
    +      FOREACHvertex_(visible->vertices) {
    +        if (!vertex->newlist && !vertex->deleted) {
    +          FOREACHneighbor_(vertex) { /* this can happen under merging */
    +            if (!neighbor->visible)
    +              break;
    +          }
    +          if (neighbor)
    +            qh_setdel(vertex->neighbors, visible);
    +          else {
    +            vertex->deleted= True;
    +            qh_setappend(qh, &qh->del_vertices, vertex);
    +            trace2((qh, qh->ferr, 2041, "qh_updatevertices: delete vertex p%d(v%d) in f%d\n",
    +                  qh_pointid(qh, vertex->point), vertex->id, visible->id));
    +          }
    +        }
    +      }
    +    }
    +  }else {  /* !VERTEXneighbors */
    +    FORALLvisible_facets {
    +      FOREACHvertex_(visible->vertices) {
    +        if (!vertex->newlist && !vertex->deleted) {
    +          vertex->deleted= True;
    +          qh_setappend(qh, &qh->del_vertices, vertex);
    +          trace2((qh, qh->ferr, 2042, "qh_updatevertices: delete vertex p%d(v%d) in f%d\n",
    +                  qh_pointid(qh, vertex->point), vertex->id, visible->id));
    +        }
    +      }
    +    }
    +  }
    +} /* updatevertices */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/poly_r.h b/xs/src/qhull/src/libqhull_r/poly_r.h
    new file mode 100644
    index 0000000000..c71511bd69
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/poly_r.h
    @@ -0,0 +1,303 @@
    +/*
      ---------------------------------
    +
    +   poly_r.h
    +   header file for poly_r.c and poly2_r.c
    +
    +   see qh-poly_r.htm, libqhull_r.h and poly_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/poly_r.h#5 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFpoly
    +#define qhDEFpoly 1
    +
    +#include "libqhull_r.h"
    +
    +/*===============   constants ========================== */
    +
    +/*----------------------------------
    +
    +  ALGORITHMfault
    +    use as argument to checkconvex() to report errors during buildhull
    +*/
    +#define qh_ALGORITHMfault 0
    +
    +/*----------------------------------
    +
    +  DATAfault
    +    use as argument to checkconvex() to report errors during initialhull
    +*/
    +#define qh_DATAfault 1
    +
    +/*----------------------------------
    +
    +  DUPLICATEridge
    +    special value for facet->neighbor to indicate a duplicate ridge
    +
    +  notes:
    +    set by matchneighbor, used by matchmatch and mark_dupridge
    +*/
    +#define qh_DUPLICATEridge (facetT *)1L
    +
    +/*----------------------------------
    +
    +  MERGEridge       flag in facet
    +    special value for facet->neighbor to indicate a merged ridge
    +
    +  notes:
    +    set by matchneighbor, used by matchmatch and mark_dupridge
    +*/
    +#define qh_MERGEridge (facetT *)2L
    +
    +
    +/*============ -structures- ====================*/
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  FORALLfacet_( facetlist ) { ... }
    +    assign 'facet' to each facet in facetlist
    +
    +  notes:
    +    uses 'facetT *facet;'
    +    assumes last facet is a sentinel
    +
    +  see:
    +    FORALLfacets
    +*/
    +#define FORALLfacet_( facetlist ) if (facetlist ) for ( facet=( facetlist ); facet && facet->next; facet= facet->next )
    +
    +/*----------------------------------
    +
    +  FORALLnew_facets { ... }
    +    assign 'newfacet' to each facet in qh.newfacet_list
    +
    +  notes:
    +    uses 'facetT *newfacet;'
    +    at exit, newfacet==NULL
    +*/
    +#define FORALLnew_facets for ( newfacet=qh->newfacet_list;newfacet && newfacet->next;newfacet=newfacet->next )
    +
    +/*----------------------------------
    +
    +  FORALLvertex_( vertexlist ) { ... }
    +    assign 'vertex' to each vertex in vertexlist
    +
    +  notes:
    +    uses 'vertexT *vertex;'
    +    at exit, vertex==NULL
    +*/
    +#define FORALLvertex_( vertexlist ) for (vertex=( vertexlist );vertex && vertex->next;vertex= vertex->next )
    +
    +/*----------------------------------
    +
    +  FORALLvisible_facets { ... }
    +    assign 'visible' to each visible facet in qh.visible_list
    +
    +  notes:
    +    uses 'vacetT *visible;'
    +    at exit, visible==NULL
    +*/
    +#define FORALLvisible_facets for (visible=qh->visible_list; visible && visible->visible; visible= visible->next)
    +
    +/*----------------------------------
    +
    +  FORALLsame_( newfacet ) { ... }
    +    assign 'same' to each facet in newfacet->f.samecycle
    +
    +  notes:
    +    uses 'facetT *same;'
    +    stops when it returns to newfacet
    +*/
    +#define FORALLsame_(newfacet) for (same= newfacet->f.samecycle; same != newfacet; same= same->f.samecycle)
    +
    +/*----------------------------------
    +
    +  FORALLsame_cycle_( newfacet ) { ... }
    +    assign 'same' to each facet in newfacet->f.samecycle
    +
    +  notes:
    +    uses 'facetT *same;'
    +    at exit, same == NULL
    +*/
    +#define FORALLsame_cycle_(newfacet) \
    +     for (same= newfacet->f.samecycle; \
    +         same; same= (same == newfacet ?  NULL : same->f.samecycle))
    +
    +/*----------------------------------
    +
    +  FOREACHneighborA_( facet ) { ... }
    +    assign 'neighborA' to each neighbor in facet->neighbors
    +
    +  FOREACHneighborA_( vertex ) { ... }
    +    assign 'neighborA' to each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighborA, **neighborAp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHneighborA_(facet)  FOREACHsetelement_(facetT, facet->neighbors, neighborA)
    +
    +/*----------------------------------
    +
    +  FOREACHvisible_( facets ) { ... }
    +    assign 'visible' to each facet in facets
    +
    +  notes:
    +    uses 'facetT *facet, *facetp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvisible_(facets) FOREACHsetelement_(facetT, facets, visible)
    +
    +/*----------------------------------
    +
    +  FOREACHnewfacet_( facets ) { ... }
    +    assign 'newfacet' to each facet in facets
    +
    +  notes:
    +    uses 'facetT *newfacet, *newfacetp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHnewfacet_(facets) FOREACHsetelement_(facetT, facets, newfacet)
    +
    +/*----------------------------------
    +
    +  FOREACHvertexA_( vertices ) { ... }
    +    assign 'vertexA' to each vertex in vertices
    +
    +  notes:
    +    uses 'vertexT *vertexA, *vertexAp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvertexA_(vertices) FOREACHsetelement_(vertexT, vertices, vertexA)
    +
    +/*----------------------------------
    +
    +  FOREACHvertexreverse12_( vertices ) { ... }
    +    assign 'vertex' to each vertex in vertices
    +    reverse order of first two vertices
    +
    +  notes:
    +    uses 'vertexT *vertex, *vertexp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvertexreverse12_(vertices) FOREACHsetelementreverse12_(vertexT, vertices, vertex)
    +
    +
    +/*=============== prototypes poly_r.c in alphabetical order ================*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_appendfacet(qhT *qh, facetT *facet);
    +void    qh_appendvertex(qhT *qh, vertexT *vertex);
    +void    qh_attachnewfacets(qhT *qh /* qh.visible_list, qh.newfacet_list */);
    +boolT   qh_checkflipped(qhT *qh, facetT *facet, realT *dist, boolT allerror);
    +void    qh_delfacet(qhT *qh, facetT *facet);
    +void    qh_deletevisible(qhT *qh /* qh.visible_list, qh.horizon_list */);
    +setT   *qh_facetintersect(qhT *qh, facetT *facetA, facetT *facetB, int *skipAp,int *skipBp, int extra);
    +int     qh_gethash(qhT *qh, int hashsize, setT *set, int size, int firstindex, void *skipelem);
    +facetT *qh_makenewfacet(qhT *qh, setT *vertices, boolT toporient, facetT *facet);
    +void    qh_makenewplanes(qhT *qh /* qh.newfacet_list */);
    +facetT *qh_makenew_nonsimplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew);
    +facetT *qh_makenew_simplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew);
    +void    qh_matchneighbor(qhT *qh, facetT *newfacet, int newskip, int hashsize,
    +                          int *hashcount);
    +void    qh_matchnewfacets(qhT *qh);
    +boolT   qh_matchvertices(qhT *qh, int firstindex, setT *verticesA, int skipA,
    +                          setT *verticesB, int *skipB, boolT *same);
    +facetT *qh_newfacet(qhT *qh);
    +ridgeT *qh_newridge(qhT *qh);
    +int     qh_pointid(qhT *qh, pointT *point);
    +void    qh_removefacet(qhT *qh, facetT *facet);
    +void    qh_removevertex(qhT *qh, vertexT *vertex);
    +void    qh_updatevertices(qhT *qh);
    +
    +
    +/*========== -prototypes poly2_r.c in alphabetical order ===========*/
    +
    +void    qh_addhash(void *newelem, setT *hashtable, int hashsize, int hash);
    +void    qh_check_bestdist(qhT *qh);
    +void    qh_check_dupridge(qhT *qh, facetT *facet1, realT dist1, facetT *facet2, realT dist2);
    +void    qh_check_maxout(qhT *qh);
    +void    qh_check_output(qhT *qh);
    +void    qh_check_point(qhT *qh, pointT *point, facetT *facet, realT *maxoutside, realT *maxdist, facetT **errfacet1, facetT **errfacet2);
    +void    qh_check_points(qhT *qh);
    +void    qh_checkconvex(qhT *qh, facetT *facetlist, int fault);
    +void    qh_checkfacet(qhT *qh, facetT *facet, boolT newmerge, boolT *waserrorp);
    +void    qh_checkflipped_all(qhT *qh, facetT *facetlist);
    +void    qh_checkpolygon(qhT *qh, facetT *facetlist);
    +void    qh_checkvertex(qhT *qh, vertexT *vertex);
    +void    qh_clearcenters(qhT *qh, qh_CENTER type);
    +void    qh_createsimplex(qhT *qh, setT *vertices);
    +void    qh_delridge(qhT *qh, ridgeT *ridge);
    +void    qh_delvertex(qhT *qh, vertexT *vertex);
    +setT   *qh_facet3vertex(qhT *qh, facetT *facet);
    +facetT *qh_findbestfacet(qhT *qh, pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside);
    +facetT *qh_findbestlower(qhT *qh, facetT *upperfacet, pointT *point, realT *bestdistp, int *numpart);
    +facetT *qh_findfacet_all(qhT *qh, pointT *point, realT *bestdist, boolT *isoutside,
    +                          int *numpart);
    +int     qh_findgood(qhT *qh, facetT *facetlist, int goodhorizon);
    +void    qh_findgood_all(qhT *qh, facetT *facetlist);
    +void    qh_furthestnext(qhT *qh /* qh.facet_list */);
    +void    qh_furthestout(qhT *qh, facetT *facet);
    +void    qh_infiniteloop(qhT *qh, facetT *facet);
    +void    qh_initbuild(qhT *qh);
    +void    qh_initialhull(qhT *qh, setT *vertices);
    +setT   *qh_initialvertices(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints);
    +vertexT *qh_isvertex(pointT *point, setT *vertices);
    +vertexT *qh_makenewfacets(qhT *qh, pointT *point /*horizon_list, visible_list*/);
    +void    qh_matchduplicates(qhT *qh, facetT *atfacet, int atskip, int hashsize, int *hashcount);
    +void    qh_nearcoplanar(qhT *qh /* qh.facet_list */);
    +vertexT *qh_nearvertex(qhT *qh, facetT *facet, pointT *point, realT *bestdistp);
    +int     qh_newhashtable(qhT *qh, int newsize);
    +vertexT *qh_newvertex(qhT *qh, pointT *point);
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp);
    +void    qh_outcoplanar(qhT *qh /* qh.facet_list */);
    +pointT *qh_point(qhT *qh, int id);
    +void    qh_point_add(qhT *qh, setT *set, pointT *point, void *elem);
    +setT   *qh_pointfacet(qhT *qh /*qh.facet_list*/);
    +setT   *qh_pointvertex(qhT *qh /*qh.facet_list*/);
    +void    qh_prependfacet(qhT *qh, facetT *facet, facetT **facetlist);
    +void    qh_printhashtable(qhT *qh, FILE *fp);
    +void    qh_printlists(qhT *qh);
    +void    qh_resetlists(qhT *qh, boolT stats, boolT resetVisible /*qh.newvertex_list qh.newfacet_list qh.visible_list*/);
    +void    qh_setvoronoi_all(qhT *qh);
    +void    qh_triangulate(qhT *qh /*qh.facet_list*/);
    +void    qh_triangulate_facet(qhT *qh, facetT *facetA, vertexT **first_vertex);
    +void    qh_triangulate_link(qhT *qh, facetT *oldfacetA, facetT *facetA, facetT *oldfacetB, facetT *facetB);
    +void    qh_triangulate_mirror(qhT *qh, facetT *facetA, facetT *facetB);
    +void    qh_triangulate_null(qhT *qh, facetT *facetA);
    +void    qh_vertexintersect(qhT *qh, setT **vertexsetA,setT *vertexsetB);
    +setT   *qh_vertexintersect_new(qhT *qh, setT *vertexsetA,setT *vertexsetB);
    +void    qh_vertexneighbors(qhT *qh /*qh.facet_list*/);
    +boolT   qh_vertexsubset(setT *vertexsetA, setT *vertexsetB);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFpoly */
    diff --git a/xs/src/qhull/src/libqhull_r/qh-geom_r.htm b/xs/src/qhull/src/libqhull_r/qh-geom_r.htm
    new file mode 100644
    index 0000000000..eeefc0c758
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/qh-geom_r.htm
    @@ -0,0 +1,295 @@
    +
    +
    +
    +
    +geom_r.c, geom2_r.c -- geometric and floating point routines
    +
    +
    +
    +
    +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + + +

    geom_r.c, geom2_r.c, random_r.c -- geometric and floating point routines

    +
    +

    Geometrically, a vertex is a point with d coordinates +and a facet is a halfspace. A halfspace is defined by an +oriented hyperplane through the facet's vertices. A hyperplane +is defined by d normalized coefficients and an offset. A +point is above a facet if its distance to the facet is +positive.

    + +

    Qhull uses floating point coordinates for input points, +vertices, halfspace equations, centrums, and an interior point.

    + +

    Qhull may be configured for single precision or double +precision floating point arithmetic (see realT +).

    + +

    Each floating point operation may incur round-off error (see +Merge). The maximum error for distance +computations is determined at initialization. The roundoff error +in halfspace computation is accounted for by computing the +distance from vertices to the halfspace.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly • +QhullSet • +StatUser

    + +

    Index to geom_r.c, +geom2_r.c, geom_r.h, +random_r.c, random_r.h +

    + + + +

    »geometric data types +and constants

    + +
      +
    • coordT coordinates and +coefficients are stored as realT
    • +
    • pointT a point is an array +of DIM3 coordinates
    • +
    + +

    »mathematical macros

    + +
      +
    • fabs_ returns the absolute +value of a
    • +
    • fmax_ returns the maximum +value of a and b
    • +
    • fmin_ returns the minimum +value of a and b
    • +
    • maximize_ maximize a value +
    • +
    • minimize_ minimize a value +
    • +
    • det2_ compute a 2-d +determinate
    • +
    • det3_ compute a 3-d +determinate
    • +
    • dX, dY, dZ compute the difference +between two coordinates
    • +
    + +

    »mathematical functions

    + + + +

    »computational geometry functions

    + + + +

    »point array functions

    + + +

    »geometric facet functions

    + + +

    »geometric roundoff functions

    +
      +
    • qh_detjoggle determine +default joggle for points and distance roundoff error
    • +
    • qh_detroundoff +determine maximum roundoff error and other precision constants
    • +
    • qh_distround compute +maximum roundoff error due to a distance computation to a +normalized hyperplane
    • +
    • qh_divzero divide by a +number that is nearly zero
    • +
    • qh_maxouter return maximum outer +plane
    • +
    • qh_outerinner return actual +outer and inner planes +
    + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    + + +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-globa_r.htm b/xs/src/qhull/src/libqhull_r/qh-globa_r.htm new file mode 100644 index 0000000000..45437a0597 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-globa_r.htm @@ -0,0 +1,163 @@ + + + + +global_r.c -- global variables and their functions + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + + +

    global_r.c -- global variables and their functions

    +
    +

    Qhull uses a data structure, qhT, to store +globally defined constants, lists, sets, and variables. It is passed as the +first argument to most functions. +

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly • +QhullSet • +StatUser

    + +

    Index to global_r.c and +libqhull_r.h

    + + + +

    »Qhull's global +variables

    + + + +

    »Global variable and +initialization routines

    + + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-io_r.htm b/xs/src/qhull/src/libqhull_r/qh-io_r.htm new file mode 100644 index 0000000000..8a8a96300f --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-io_r.htm @@ -0,0 +1,305 @@ + + + + +io_r.c -- input and output operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    io_r.c -- input and output operations

    +
    + +

    Qhull provides a wide range of input +and output options. To organize the code, most output formats use +the same driver:

    + +
    +    qh_printbegin( fp, format, facetlist, facets, printall );
    +
    +    FORALLfacet_( facetlist )
    +      qh_printafacet( fp, format, facet, printall );
    +
    +    FOREACHfacet_( facets )
    +      qh_printafacet( fp, format, facet, printall );
    +
    +    qh_printend( fp, format );
    +
    + +

    Note the 'printall' flag. It selects whether or not +qh_skipfacet() is tested.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom +GlobalIo • +MemMerge • +PolyQhull • +SetStat • +User

    + +

    Index to io_r.c and io_r.h

    + + + +

    »io_r.h constants and types

    + +
      +
    • qh_MAXfirst maximum length +of first two lines of stdin
    • +
    • qh_WHITESPACE possible +values of white space
    • +
    • printvridgeT function to +print results of qh_printvdiagram or qh_eachvoronoi
    • +
    + +

    »User level functions

    + + + +

    »Print functions for all +output formats

    + + + +

    »Text output functions

    + + +

    »Text utility functions

    + + +

    »Geomview output functions

    + +

    »Geomview utility functions

    + +

    +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-mem_r.htm b/xs/src/qhull/src/libqhull_r/qh-mem_r.htm new file mode 100644 index 0000000000..db59119cb9 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-mem_r.htm @@ -0,0 +1,145 @@ + + + + +mem_r.c -- memory operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    mem_r.c -- memory operations

    +
    +

    Qhull uses quick-fit memory allocation. It maintains a +set of free lists for a variety of small allocations. A +small request returns a block from the best fitting free +list. If the free list is empty, Qhull allocates a block +from a reserved buffer.

    +

    Use 'T5' to trace memory allocations.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to mem_r.c and +mem_r.h

    + +

    »mem_r.h data types and constants

    +
      +
    • ptr_intT for casting +a void* to an integer-type
    • +
    • qhmemT global memory +structure for mem_r.c
    • +
    • qh_NOmem disable memory allocation
    • +
    +

    »mem_r.h macros

    + +

    »User level +functions

    + + +

    »Initialization and +termination functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-merge_r.htm b/xs/src/qhull/src/libqhull_r/qh-merge_r.htm new file mode 100644 index 0000000000..63e5135be1 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-merge_r.htm @@ -0,0 +1,366 @@ + + + + +merge_r.c -- facet merge operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    merge_r.c -- facet merge operations

    +
    +

    Qhull handles precision problems by merged facets or joggled input. +Except for redundant vertices, it corrects a problem by +merging two facets. When done, all facets are clearly +convex. See Imprecision in Qhull +for further information.

    +

    Users may joggle the input ('QJn') +instead of merging facets.

    +

    Qhull detects and corrects the following problems:

    +
      +
    • More than two facets meeting at a ridge. When +Qhull creates facets, it creates an even number +of facets for each ridge. A convex hull always +has two facets for each ridge. More than two +facets may be created if non-adjacent facets +share a vertex. This is called a duplicate +ridge. In 2-d, a duplicate ridge would +create a loop of facets.
    • +
    +
      +
    • A facet contained in another facet. Facet +merging may leave all vertices of one facet as a +subset of the vertices of another facet. This is +called a redundant facet.
    • +
    +
      +
    • A facet with fewer than three neighbors. Facet +merging may leave a facet with one or two +neighbors. This is called a degenerate facet. +
    • +
    +
      +
    • A facet with flipped orientation. A +facet's hyperplane may define a halfspace that +does not include the interior point.This is +called a flipped facet.
    • +
    +
      +
    • A coplanar horizon facet. A +newly processed point may be coplanar with an +horizon facet. Qhull creates a new facet without +a hyperplane. It links new facets for the same +horizon facet together. This is called a samecycle. +The new facet or samecycle is merged into the +horizon facet.
    • +
    +
      +
    • Concave facets. A facet's centrum may be +above a neighboring facet. If so, the facets meet +at a concave angle.
    • +
    +
      +
    • Coplanar facets. A facet's centrum may be +coplanar with a neighboring facet (i.e., it is +neither clearly below nor clearly above the +facet's hyperplane). Qhull removes coplanar +facets in independent sets sorted by angle.
    • +
    +
      +
    • Redundant vertex. A vertex may have fewer +than three neighboring facets. If so, it is +redundant and may be renamed to an adjacent +vertex without changing the topological +structure.This is called a redundant vertex. +
    • +
    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to merge_r.c and +merge_r.h

    + + +

    »merge_r.h data +types, macros, and global sets

    +
      +
    • mergeT structure to +identify a merge of two facets
    • +
    • FOREACHmerge_ +assign 'merge' to each merge in merges
    • +
    • qh global sets +qh.facet_mergeset contains non-convex merges +while qh.degen_mergeset contains degenerate and +redundant facets
    • +
    +

    »merge_r.h +constants

    + +

    »top-level merge +functions

    + + +

    »functions for +identifying merges

    + + +

    »functions for +determining the best merge

    + + +

    »functions for +merging facets

    + + +

    »functions for +merging a cycle of facets

    +

    If a point is coplanar with an horizon facet, the +corresponding new facets are linked together (a samecycle) +for merging.

    + +

    »functions +for renaming a vertex

    + + +

    »functions +for identifying vertices for renaming

    + + +

    »functions for check and +trace

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-poly_r.htm b/xs/src/qhull/src/libqhull_r/qh-poly_r.htm new file mode 100644 index 0000000000..c5b6f2f836 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-poly_r.htm @@ -0,0 +1,485 @@ + + + + +poly_r.c, poly2_r.c -- polyhedron operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    poly_r.c, poly2_r.c -- polyhedron operations

    +
    + +

    Qhull uses dimension-free terminology. Qhull builds a +polyhedron in dimension d. A polyhedron is a +simplicial complex of faces with geometric information for the +top and bottom-level faces. A (d-1)-face is a facet, +a (d-2)-face is a ridge, and a 0-face +is a vertex. For example in 3-d, a facet is a polygon +and a ridge is an edge. A facet is built from a ridge (the base) +and a vertex (the apex). See +Qhull's data structures.

    + +

    Qhull's primary data structure is a polyhedron. A +polyhedron is a list of facets. Each facet has a set of +neighboring facets and a set of vertices. Each facet has a +hyperplane. For example, a tetrahedron has four facets. +If its vertices are a, b, c, d, and its facets +are 1, 2, 3, 4, the tetrahedron is

    +
    +
      +
    • facet 1
        +
      • vertices: b c d
      • +
      • neighbors: 2 3 4
      • +
      +
    • +
    • facet 2
        +
      • vertices: a c d
      • +
      • neighbors: 1 3 4
      • +
      +
    • +
    • facet 3
        +
      • vertices: a b d
      • +
      • neighbors: 1 2 4
      • +
      +
    • +
    • facet 4
        +
      • vertices: a b c
      • +
      • neighbors: 1 2 3
      • +
      +
    • +
    +
    +

    A facet may be simplicial or non-simplicial. In 3-d, a +simplicial facet has three vertices and three +neighbors. A nonsimplicial facet has more than +three vertices and more than three neighbors. A +nonsimplicial facet has a set of ridges and a centrum.

    +

    +A simplicial facet has an orientation. An orientation +is either top or bottom. +The flag, facet->toporient, +defines the orientation of the facet's vertices. For example in 3-d, +'top' is left-handed orientation (i.e., the vertex order follows the direction +of the left-hand fingers when the thumb is pointing away from the center). +Except for axis-parallel facets in 5-d and higher, topological orientation +determines the geometric orientation of the facet's hyperplane. + +

    A nonsimplicial facet is due to merging two or more +facets. The facet's ridge set determine a simplicial +decomposition of the facet. Each ridge is a 1-face (i.e., +it has two vertices and two neighboring facets). The +orientation of a ridge is determined by the order of the +neighboring facets. The flag, facet->toporient,is +ignored.

    +

    A nonsimplicial facet has a centrum for testing +convexity. A centrum is a point on the facet's +hyperplane that is near the center of the facet. Except +for large facets, it is the arithmetic average of the +facet's vertices.

    +

    A nonsimplicial facet is an approximation that is +defined by offsets from the facet's hyperplane. When +Qhull finishes, the outer plane is above all +points while the inner plane is below the facet's +vertices. This guarantees that any exact convex hull +passes between the inner and outer planes. The outer +plane is defined by facet->maxoutside while +the inner plane is computed from the facet's vertices.

    + +

    Qhull 3.1 includes triangulation of non-simplicial facets +('Qt'). +These facets, +called tricoplanar, share the same normal. centrum, and Voronoi center. +One facet (keepcentrum) owns these data structures. +While tricoplanar facets are more accurate than the simplicial facets from +joggled input, they +may have zero area or flipped orientation. + +

    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to poly_r.c, +poly2_r.c, poly_r.h, +and libqhull_r.h

    + +

    »Data +types and global lists for polyhedrons

    + +

    »poly_r.h constants

    +
      +
    • ALGORITHMfault +flag to not report errors in qh_checkconvex()
    • +
    • DATAfault flag to +report errors in qh_checkconvex()
    • +
    • DUPLICATEridge +special value for facet->neighbor to indicate +a duplicate ridge
    • +
    • MERGEridge +special value for facet->neighbor to indicate +a merged ridge
    • +
    +

    »Global FORALL +macros

    + +

    »FORALL macros

    + +

    »FOREACH macros

    + +

    »Indexed +FOREACH macros

    +
      +
    • FOREACHfacet_i_ +assign 'facet' and 'facet_i' to each facet in +facet set
    • +
    • FOREACHneighbor_i_ +assign 'neighbor' and 'neighbor_i' to each facet +in facet->neighbors or vertex->neighbors
    • +
    • FOREACHpoint_i_ +assign 'point' and 'point_i' to each point in +points set
    • +
    • FOREACHridge_i_ +assign 'ridge' and 'ridge_i' to each ridge in +ridges set
    • +
    • FOREACHvertex_i_ +assign 'vertex' and 'vertex_i' to each vertex in +vertices set
    • +
    • FOREACHvertexreverse12_ +assign 'vertex' to each vertex in vertex set; +reverse the order of first two vertices
    • +
    +

    »Other macros for polyhedrons

    +
      +
    • getid_ return ID for +a facet, ridge, or vertex
    • +
    • otherfacet_ +return neighboring facet for a ridge in a facet
    • +
    +

    »Facetlist +functions

    + +

    »Facet +functions

    + +

    »Vertex, +ridge, and point functions

    + +

    »Hashtable functions

    + +

    »Allocation and +deallocation functions

    + +

    »Check +functions

    + + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-qhull_r.htm b/xs/src/qhull/src/libqhull_r/qh-qhull_r.htm new file mode 100644 index 0000000000..25d5e49722 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-qhull_r.htm @@ -0,0 +1,279 @@ + + + + +libqhull_r.c -- top-level functions and basic data types + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    libqhull_r.c -- top-level functions and basic data types

    +
    +

    Qhull implements the Quickhull algorithm for computing +the convex hull. The Quickhull algorithm combines two +well-known algorithms: the 2-d quickhull algorithm and +the n-d beneath-beyond algorithm. See +Description of Qhull.

    +

    This section provides an index to the top-level +functions and base data types. The top-level header file, libqhull_r.h, +contains prototypes for these functions.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to libqhull_r.c, +libqhull_r.h, and +unix_r.c

    + + +

    »libqhull_r.h and unix_r.c +data types and constants

    +
      +
    • flagT Boolean flag as +a bit
    • +
    • boolT boolean value, +either True or False
    • +
    • CENTERtype to +distinguish facet->center
    • +
    • qh_PRINT output +formats for printing (qh.PRINTout)
    • +
    • qh_ALL argument flag +for selecting everything
    • +
    • qh_ERR Qhull exit +codes for indicating errors
    • +
    • qh_FILEstderr Fake stderr +to distinguish error output from normal output [C++ only]
    • +
    • qh_prompt version and long prompt for Qhull
    • +
    • qh_prompt2 synopsis for Qhull
    • +
    • qh_prompt3 concise prompt for Qhull
    • +
    • qh_version version stamp
    • +
    + +

    »libqhull_r.h other +macros

    +
      +
    • traceN print trace +message if qh.IStracing >= N.
    • +
    • QHULL_UNUSED declare an + unused variable to avoid warnings.
    • +
    + +

    »Quickhull +routines in call order

    + + +

    »Top-level routines for initializing and terminating Qhull (in other modules)

    + + +

    »Top-level routines for reading and modifying the input (in other modules)

    + + +

    »Top-level routines for calling Qhull (in other modules)

    + + +

    »Top-level routines for returning results (in other modules)

    + + +

    »Top-level routines for testing and debugging (in other modules)

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-set_r.htm b/xs/src/qhull/src/libqhull_r/qh-set_r.htm new file mode 100644 index 0000000000..cf8ab63af9 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-set_r.htm @@ -0,0 +1,308 @@ + + + + +qset_r.c -- set data type and operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    qset_r.c -- set data type and operations

    +
    +

    Qhull's data structures are constructed from sets. The +functions and macros in qset_r.c construct, iterate, and +modify these sets. They are the most frequently called +functions in Qhull. For this reason, efficiency is the +primary concern.

    +

    In Qhull, a set is represented by an unordered +array of pointers with a maximum size and a NULL +terminator (setT). +Most sets correspond to mathematical sets +(i.e., the pointers are unique). Some sets are sorted to +enforce uniqueness. Some sets are ordered. For example, +the order of vertices in a ridge determine the ridge's +orientation. If you reverse the order of adjacent +vertices, the orientation reverses. Some sets are not +mathematical sets. They may be indexed as an array and +they may include NULL pointers.

    +

    The most common operation on a set is to iterate its +members. This is done with a 'FOREACH...' macro. Each set +has a custom macro. For example, 'FOREACHvertex_' +iterates over a set of vertices. Each vertex is assigned +to the variable 'vertex' from the pointer 'vertexp'.

    +

    Most sets are constructed by appending elements to the +set. The last element of a set is either NULL or the +index of the terminating NULL for a partially full set. +If a set is full, appending an element copies the set to +a larger array.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly +• QhullSet +• StatUser +

    +

    Index to qset_r.c and +qset_r.h

    + +

    »Data types and +constants

    +
      +
    • SETelemsize size +of a set element in bytes
    • +
    • setT a set with a +maximum size and a current size
    • +
    • qh global sets +global sets for temporary sets, etc.
    • +
    +

    »FOREACH macros

    + +

    »Access and +size macros

    + +

    »Internal macros

    +
      +
    • SETsizeaddr_ +return pointer to end element of a set (indicates +current size)
    • +
    + +

    »address macros

    +
      +
    • SETaddr_ return +address of a set's elements
    • +
    • SETelemaddr_ +return address of the n'th element of a set
    • +
    • SETref_ l_r.h.s. for +modifying the current element in a FOREACH +iteration
    • +
    + +

    »Allocation and +deallocation functions

    + + +

    »Access and +predicate functions

    + + +

    »Add functions

    + + +

    »Check and print functions

    + + +

    »Copy, compact, and zero functions

    + + +

    »Delete functions

    + + +

    »Temporary set functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-stat_r.htm b/xs/src/qhull/src/libqhull_r/qh-stat_r.htm new file mode 100644 index 0000000000..ea9d7fc565 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-stat_r.htm @@ -0,0 +1,161 @@ + + + + +stat_r.c -- statistical operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    stat_r.c -- statistical operations

    +
    +

    Qhull records many statistics. These functions and +macros make it inexpensive to add a statistic. +

    As with Qhull's global variables, the statistics data structure is +accessed by a macro, 'qhstat'. If qh_QHpointer is defined, the macro +is 'qh_qhstat->', otherwise the macro is 'qh_qhstat.'. +Statistics +may be turned off in user_r.h. If so, all but the 'zz' +statistics are ignored.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to stat_r.c and +stat_r.h

    + + +

    »stat_r.h types

    +
      +
    • intrealT union of +integer and real
    • +
    • qhstat global data +structure for statistics
    • +
    +

    »stat_r.h +constants

    +
      +
    • qh_KEEPstatistics 0 turns off most statistics
    • +
    • Z..., W... integer (Z) and real (W) statistics +
    • +
    • ZZstat Z.../W... statistics that +remain defined if qh_KEEPstatistics=0 +
    • +
    • ztype zdoc, zinc, etc. +for definining statistics
    • +
    +

    »stat_r.h macros

    +
      +
    • MAYdebugx called +frequently for error trapping
    • +
    • zadd_/wadd_ add value +to an integer or real statistic
    • +
    • zdef_ define a +statistic
    • +
    • zinc_ increment an +integer statistic
    • +
    • zmax_/wmax_ update +integer or real maximum statistic
    • +
    • zmin_/wmin_ update +integer or real minimum statistic
    • +
    • zval_/wval_ set or +return value of a statistic
    • +
    + +

    »stat_r.c +functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-user_r.htm b/xs/src/qhull/src/libqhull_r/qh-user_r.htm new file mode 100644 index 0000000000..909fec6564 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-user_r.htm @@ -0,0 +1,271 @@ + + + + +user_r.c -- user-definable operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    +

    user_r.c -- user-definable operations

    +
    +

    This section contains functions and constants that the +user may want to change.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to user_r.c, usermem_r.c, userprintf_r.c, userprintf_rbox_r.c and +user_r.h

    + + +

    »Qhull library constants

    + + + +

    »user_r.h data +types and configuration macros

    + + +

    »definition constants

    +
      +
    • qh_DEFAULTbox +define default box size for rbox, 'Qbb', and 'QbB' (Geomview expects 0.5)
    • +
    • qh_INFINITE on +output, indicates Voronoi center at infinity
    • +
    • qh_ORIENTclock +define convention for orienting facets
    • +
    • qh_ZEROdelaunay +define facets that are ignored in Delaunay triangulations
    • +
    + +

    »joggle constants

    + + +

    »performance +related constants

    + + +

    »memory constants

    + + +

    »conditional compilation

    +
      +
    • compiler defined symbols, +e.g., _STDC_ and _cplusplus + +
    • qh_COMPUTEfurthest + compute furthest distance to an outside point instead of storing it with the facet +
    • qh_KEEPstatistics + enable statistic gathering and reporting with option 'Ts' +
    • qh_MAXoutside +record outer plane for each facet +
    • qh_NOmerge +disable facet merging +
    • qh_NOtrace +disable tracing with option 'T4' +
    • qh_QHpointer +access global data with pointer or static structure +
    • qh_QUICKhelp +use abbreviated help messages, e.g., for degenerate inputs +
    + +

    »merge +constants

    + + +

    »user_r.c +functions

    + + +

    »usermem_r.c +functions

    +
      +
    • qh_exit exit program, same as exit(). May be redefined as throw "QH10003.." by libqhullcpp/usermem_r-cpp.cpp
    • +
    • qh_fprintf_stderr print to stderr when qh->ferr is not defined.
    • +
    • qh_free free memory, same as free().
    • +
    • qh_malloc allocate memory, same as malloc()
    • +
    + +

    »userprintf_r.c + and userprintf_rbox,c functions

    +
      +
    • qh_fprintf print +information from Qhull, sames as fprintf().
    • +
    • qh_fprintf_rbox print +information from Rbox, sames as fprintf().
    • +
    + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qhull_r-exports.def b/xs/src/qhull/src/libqhull_r/qhull_r-exports.def new file mode 100644 index 0000000000..325d57c3b8 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qhull_r-exports.def @@ -0,0 +1,404 @@ +; qhull_r-exports.def -- msvc module-definition file +; +; Generated from depends.exe by cut-and-paste of exported symbols by mingw gcc +; [jan'14] 391 symbols +; Same as ../libqhullp/qhull-exports.def without DATA items (reentrant) +; +; $Id: //main/2015/qhull/src/libqhull_r/qhull_r-exports.def#3 $$Change: 2047 $ +; $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $ +; +; Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, and qhull-warn.pri +VERSION 7.0 +EXPORTS +qh_addhash +qh_addpoint +qh_all_merges +qh_allstatA +qh_allstatB +qh_allstatC +qh_allstatD +qh_allstatE +qh_allstatE2 +qh_allstatF +qh_allstatG +qh_allstatH +qh_allstatI +qh_allstatistics +qh_appendfacet +qh_appendmergeset +qh_appendprint +qh_appendvertex +qh_argv_to_command +qh_argv_to_command_size +qh_attachnewfacets +qh_backnormal +qh_basevertices +qh_build_withrestart +qh_buildhull +qh_buildtracing +qh_check_bestdist +qh_check_dupridge +qh_check_maxout +qh_check_output +qh_check_point +qh_check_points +qh_checkconnect +qh_checkconvex +qh_checkfacet +qh_checkflags +qh_checkflipped +qh_checkflipped_all +qh_checkpolygon +qh_checkvertex +qh_checkzero +qh_clear_outputflags +qh_clearcenters +qh_clock +qh_collectstatistics +qh_compare_facetarea +qh_compare_facetmerge +qh_compare_facetvisit +qh_compareangle +qh_comparemerge +qh_comparevisit +qh_copyfilename +qh_copynonconvex +qh_copypoints +qh_countfacets +qh_createsimplex +qh_crossproduct +qh_degen_redundant_facet +qh_degen_redundant_neighbors +qh_deletevisible +qh_delfacet +qh_delridge +qh_delvertex +qh_determinant +qh_detjoggle +qh_detroundoff +qh_detsimplex +qh_detvnorm +qh_detvridge +qh_detvridge3 +qh_dfacet +qh_distnorm +qh_distplane +qh_distround +qh_divzero +qh_dvertex +qh_eachvoronoi +qh_eachvoronoi_all +qh_errexit +qh_errexit2 +qh_errexit_rbox +qh_errprint +qh_exit +qh_facet2point +qh_facet3vertex +qh_facetarea +qh_facetarea_simplex +qh_facetcenter +qh_facetintersect +qh_facetvertices +qh_find_newvertex +qh_findbest +qh_findbest_test +qh_findbestfacet +qh_findbesthorizon +qh_findbestlower +qh_findbestneighbor +qh_findbestnew +qh_findfacet_all +qh_findgood +qh_findgood_all +qh_findgooddist +qh_findhorizon +qh_flippedmerges +qh_forcedmerges +qh_fprintf +qh_fprintf_rbox +qh_fprintf_stderr +qh_free +qh_freebuffers +qh_freebuild +qh_freeqhull +qh_furthestnext +qh_furthestout +qh_gausselim +qh_geomplanes +qh_getangle +qh_getarea +qh_getcenter +qh_getcentrum +qh_getdistance +qh_gethash +qh_getmergeset +qh_getmergeset_initial +qh_gram_schmidt +qh_hashridge +qh_hashridge_find +qh_infiniteloop +qh_init_A +qh_init_B +qh_init_qhull_command +qh_initbuild +qh_initflags +qh_initialhull +qh_initialvertices +qh_initqhull_buffers +qh_initqhull_globals +qh_initqhull_mem +qh_initqhull_outputflags +qh_initqhull_start +qh_initqhull_start2 +qh_initstatistics +qh_initthresholds +qh_inthresholds +qh_isvertex +qh_joggleinput +qh_lib_check +qh_makenew_nonsimplicial +qh_makenew_simplicial +qh_makenewfacet +qh_makenewfacets +qh_makenewplanes +qh_makeridges +qh_malloc +qh_mark_dupridges +qh_markkeep +qh_markvoronoi +qh_matchduplicates +qh_matchneighbor +qh_matchnewfacets +qh_matchvertices +qh_maxabsval +qh_maxmin +qh_maxouter +qh_maxsimplex +qh_maydropneighbor +qh_memalloc +qh_memfree +qh_memfreeshort +qh_meminit +qh_meminitbuffers +qh_memsetup +qh_memsize +qh_memstatistics +qh_memtotal +qh_merge_degenredundant +qh_merge_nonconvex +qh_mergecycle +qh_mergecycle_all +qh_mergecycle_facets +qh_mergecycle_neighbors +qh_mergecycle_ridges +qh_mergecycle_vneighbors +qh_mergefacet +qh_mergefacet2d +qh_mergeneighbors +qh_mergeridges +qh_mergesimplex +qh_mergevertex_del +qh_mergevertex_neighbors +qh_mergevertices +qh_minabsval +qh_mindiff +qh_nearcoplanar +qh_nearvertex +qh_neighbor_intersections +qh_new_qhull +qh_newfacet +qh_newhashtable +qh_newridge +qh_newstats +qh_newvertex +qh_newvertices +qh_nextfurthest +qh_nextridge3d +qh_normalize +qh_normalize2 +qh_nostatistic +qh_option +qh_order_vertexneighbors +qh_orientoutside +qh_out1 +qh_out2n +qh_out3n +qh_outcoplanar +qh_outerinner +qh_partitionall +qh_partitioncoplanar +qh_partitionpoint +qh_partitionvisible +qh_point +qh_point_add +qh_pointdist +qh_pointfacet +qh_pointid +qh_pointvertex +qh_postmerge +qh_precision +qh_premerge +qh_prepare_output +qh_prependfacet +qh_printafacet +qh_printallstatistics +qh_printbegin +qh_printcenter +qh_printcentrum +qh_printend +qh_printend4geom +qh_printextremes +qh_printextremes_2d +qh_printextremes_d +qh_printfacet +qh_printfacet2geom +qh_printfacet2geom_points +qh_printfacet2math +qh_printfacet3geom_nonsimplicial +qh_printfacet3geom_points +qh_printfacet3geom_simplicial +qh_printfacet3math +qh_printfacet3vertex +qh_printfacet4geom_nonsimplicial +qh_printfacet4geom_simplicial +qh_printfacetNvertex_nonsimplicial +qh_printfacetNvertex_simplicial +qh_printfacetheader +qh_printfacetlist +qh_printfacetridges +qh_printfacets +qh_printhashtable +qh_printhelp_degenerate +qh_printhelp_narrowhull +qh_printhelp_singular +qh_printhyperplaneintersection +qh_printline3geom +qh_printlists +qh_printmatrix +qh_printneighborhood +qh_printpoint +qh_printpoint3 +qh_printpointid +qh_printpoints +qh_printpoints_out +qh_printpointvect +qh_printpointvect2 +qh_printridge +qh_printspheres +qh_printstatistics +qh_printstatlevel +qh_printstats +qh_printsummary +qh_printvdiagram +qh_printvdiagram2 +qh_printvertex +qh_printvertexlist +qh_printvertices +qh_printvneighbors +qh_printvnorm +qh_printvoronoi +qh_printvridge +qh_produce_output +qh_produce_output2 +qh_projectdim3 +qh_projectinput +qh_projectpoint +qh_projectpoints +qh_qhull +qh_rand +qh_randomfactor +qh_randommatrix +qh_rboxpoints +qh_readfeasible +qh_readpoints +qh_reducevertices +qh_redundant_vertex +qh_remove_extravertices +qh_removefacet +qh_removevertex +qh_rename_sharedvertex +qh_renameridgevertex +qh_renamevertex +qh_resetlists +qh_rotateinput +qh_rotatepoints +qh_roundi +qh_scaleinput +qh_scalelast +qh_scalepoints +qh_setaddnth +qh_setaddsorted +qh_setappend +qh_setappend2ndlast +qh_setappend_set +qh_setcheck +qh_setcompact +qh_setcopy +qh_setdel +qh_setdelaunay +qh_setdellast +qh_setdelnth +qh_setdelnthsorted +qh_setdelsorted +qh_setduplicate +qh_setequal +qh_setequal_except +qh_setequal_skip +qh_setfacetplane +qh_setfeasible +qh_setfree +qh_setfree2 +qh_setfreelong +qh_sethalfspace +qh_sethalfspace_all +qh_sethyperplane_det +qh_sethyperplane_gauss +qh_setin +qh_setindex +qh_setlarger +qh_setlast +qh_setnew +qh_setnew_delnthsorted +qh_setprint +qh_setreplace +qh_setsize +qh_settemp +qh_settempfree +qh_settempfree_all +qh_settemppop +qh_settemppush +qh_settruncate +qh_setunique +qh_setvoronoi_all +qh_setzero +qh_sharpnewfacets +qh_skipfacet +qh_skipfilename +qh_srand +qh_stddev +qh_strtod +qh_strtol +qh_test_appendmerge +qh_test_vneighbors +qh_tracemerge +qh_tracemerging +qh_triangulate +qh_triangulate_facet +qh_triangulate_link +qh_triangulate_mirror +qh_triangulate_null +qh_updatetested +qh_updatevertices +qh_user_memsizes +qh_version +qh_version2 +qh_vertexintersect +qh_vertexintersect_new +qh_vertexneighbors +qh_vertexridges +qh_vertexridges_facet +qh_vertexsubset +qh_voronoi_center +qh_willdelete +qh_zero diff --git a/xs/src/qhull/src/libqhull_r/qhull_ra.h b/xs/src/qhull/src/libqhull_r/qhull_ra.h new file mode 100644 index 0000000000..5c5bd8779c --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qhull_ra.h @@ -0,0 +1,158 @@ +/*
      ---------------------------------
    +
    +   qhull_ra.h
    +   all header files for compiling qhull with reentrant code
    +   included before C++ headers for user_r.h:QHULL_CRTDBG
    +
    +   see qh-qhull.htm
    +
    +   see libqhull_r.h for user-level definitions
    +
    +   see user_r.h for user-definable constants
    +
    +   defines internal functions for libqhull_r.c global_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/qhull_ra.h#6 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +
    +   Notes:  grep for ((" and (" to catch fprintf("lkasdjf");
    +           full parens around (x?y:z)
    +           use '#include "libqhull_r/qhull_ra.h"' to avoid name clashes
    +*/
    +
    +#ifndef qhDEFqhulla
    +#define qhDEFqhulla 1
    +
    +#include "libqhull_r.h"  /* Includes user_r.h and data types */
    +
    +#include "stat_r.h"
    +#include "random_r.h"
    +#include "mem_r.h"
    +#include "qset_r.h"
    +#include "geom_r.h"
    +#include "merge_r.h"
    +#include "poly_r.h"
    +#include "io_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include     /* some compilers will not need float.h */
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +/*** uncomment here and qset_r.c
    +     if string.h does not define memcpy()
    +#include 
    +*/
    +
    +#if qh_CLOCKtype == 2  /* defined in user_r.h from libqhull_r.h */
    +#include 
    +#include 
    +#include 
    +#endif
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4100)  /* unreferenced formal parameter */
    +#pragma warning( disable : 4127)  /* conditional expression is constant */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#pragma warning( disable : 4996)  /* function was declared deprecated(strcpy, localtime, etc.) */
    +#endif
    +
    +/* ======= -macros- =========== */
    +
    +/*----------------------------------
    +
    +  traceN((qh, qh->ferr, 0Nnnn, "format\n", vars));
    +    calls qh_fprintf if qh.IStracing >= N
    +
    +    Add debugging traps to the end of qh_fprintf
    +
    +  notes:
    +    removing tracing reduces code size but doesn't change execution speed
    +*/
    +#ifndef qh_NOtrace
    +#define trace0(args) {if (qh->IStracing) qh_fprintf args;}
    +#define trace1(args) {if (qh->IStracing >= 1) qh_fprintf args;}
    +#define trace2(args) {if (qh->IStracing >= 2) qh_fprintf args;}
    +#define trace3(args) {if (qh->IStracing >= 3) qh_fprintf args;}
    +#define trace4(args) {if (qh->IStracing >= 4) qh_fprintf args;}
    +#define trace5(args) {if (qh->IStracing >= 5) qh_fprintf args;}
    +#else /* qh_NOtrace */
    +#define trace0(args) {}
    +#define trace1(args) {}
    +#define trace2(args) {}
    +#define trace3(args) {}
    +#define trace4(args) {}
    +#define trace5(args) {}
    +#endif /* qh_NOtrace */
    +
    +/*----------------------------------
    +
    +  Define an unused variable to avoid compiler warnings
    +
    +  Derived from Qt's corelib/global/qglobal.h
    +
    +*/
    +
    +#if defined(__cplusplus) && defined(__INTEL_COMPILER) && !defined(QHULL_OS_WIN)
    +template 
    +inline void qhullUnused(T &x) { (void)x; }
    +#  define QHULL_UNUSED(x) qhullUnused(x);
    +#else
    +#  define QHULL_UNUSED(x) (void)x;
    +#endif
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +/***** -libqhull_r.c prototypes (alphabetical after qhull) ********************/
    +
    +void    qh_qhull(qhT *qh);
    +boolT   qh_addpoint(qhT *qh, pointT *furthest, facetT *facet, boolT checkdist);
    +void    qh_buildhull(qhT *qh);
    +void    qh_buildtracing(qhT *qh, pointT *furthest, facetT *facet);
    +void    qh_build_withrestart(qhT *qh);
    +void    qh_errexit2(qhT *qh, int exitcode, facetT *facet, facetT *otherfacet);
    +void    qh_findhorizon(qhT *qh, pointT *point, facetT *facet, int *goodvisible,int *goodhorizon);
    +pointT *qh_nextfurthest(qhT *qh, facetT **visible);
    +void    qh_partitionall(qhT *qh, setT *vertices, pointT *points,int npoints);
    +void    qh_partitioncoplanar(qhT *qh, pointT *point, facetT *facet, realT *dist);
    +void    qh_partitionpoint(qhT *qh, pointT *point, facetT *facet);
    +void    qh_partitionvisible(qhT *qh, boolT allpoints, int *numpoints);
    +void    qh_precision(qhT *qh, const char *reason);
    +void    qh_printsummary(qhT *qh, FILE *fp);
    +
    +/***** -global_r.c internal prototypes (alphabetical) ***********************/
    +
    +void    qh_appendprint(qhT *qh, qh_PRINT format);
    +void    qh_freebuild(qhT *qh, boolT allmem);
    +void    qh_freebuffers(qhT *qh);
    +void    qh_initbuffers(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +
    +/***** -stat_r.c internal prototypes (alphabetical) ***********************/
    +
    +void    qh_allstatA(qhT *qh);
    +void    qh_allstatB(qhT *qh);
    +void    qh_allstatC(qhT *qh);
    +void    qh_allstatD(qhT *qh);
    +void    qh_allstatE(qhT *qh);
    +void    qh_allstatE2(qhT *qh);
    +void    qh_allstatF(qhT *qh);
    +void    qh_allstatG(qhT *qh);
    +void    qh_allstatH(qhT *qh);
    +void    qh_freebuffers(qhT *qh);
    +void    qh_initbuffers(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFqhulla */
    diff --git a/xs/src/qhull/src/libqhull_r/qset_r.c b/xs/src/qhull/src/libqhull_r/qset_r.c
    new file mode 100644
    index 0000000000..15cd3c0e29
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/qset_r.c
    @@ -0,0 +1,1340 @@
    +/*
      ---------------------------------
    +
    +   qset_r.c
    +   implements set manipulations needed for quickhull
    +
    +   see qh-set_r.htm and qset_r.h
    +
    +   Be careful of strict aliasing (two pointers of different types
    +   that reference the same location).  The last slot of a set is
    +   either the actual size of the set plus 1, or the NULL terminator
    +   of the set (i.e., setelemT).
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/qset_r.c#3 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "libqhull_r.h" /* for qhT and QHULL_CRTDBG */
    +#include "qset_r.h"
    +#include "mem_r.h"
    +#include 
    +#include 
    +/*** uncomment here and qhull_ra.h
    +     if string.h does not define memcpy()
    +#include 
    +*/
    +
    +#ifndef qhDEFlibqhull
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +void    qh_errexit(qhT *qh, int exitcode, facetT *, ridgeT *);
    +void    qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +#  ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#  pragma warning( disable : 4127)  /* conditional expression is constant */
    +#  pragma warning( disable : 4706)  /* assignment within conditional function */
    +#  endif
    +#endif
    +
    +/*=============== internal macros ===========================*/
    +
    +/*============ functions in alphabetical order ===================*/
    +
    +/*----------------------------------
    +
    +  qh_setaddnth(qh, setp, nth, newelem)
    +    adds newelem as n'th element of sorted or unsorted *setp
    +
    +  notes:
    +    *setp and newelem must be defined
    +    *setp may be a temp set
    +    nth=0 is first element
    +    errors if nth is out of bounds
    +
    +  design:
    +    expand *setp if empty or full
    +    move tail of *setp up one
    +    insert newelem
    +*/
    +void qh_setaddnth(qhT *qh, setT **setp, int nth, void *newelem) {
    +  int oldsize, i;
    +  setelemT *sizep;          /* avoid strict aliasing */
    +  setelemT *oldp, *newp;
    +
    +  if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +    qh_setlarger(qh, setp);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  oldsize= sizep->i - 1;
    +  if (nth < 0 || nth > oldsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6171, "qhull internal error (qh_setaddnth): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qh, qh->qhmem.ferr, "", *setp);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  sizep->i++;
    +  oldp= (setelemT *)SETelemaddr_(*setp, oldsize, void);   /* NULL */
    +  newp= oldp+1;
    +  for (i=oldsize-nth+1; i--; )  /* move at least NULL  */
    +    (newp--)->p= (oldp--)->p;       /* may overwrite *sizep */
    +  newp->p= newelem;
    +} /* setaddnth */
    +
    +
    +/*----------------------------------
    +
    +  setaddsorted( setp, newelem )
    +    adds an newelem into sorted *setp
    +
    +  notes:
    +    *setp and newelem must be defined
    +    *setp may be a temp set
    +    nop if newelem already in set
    +
    +  design:
    +    find newelem's position in *setp
    +    insert newelem
    +*/
    +void qh_setaddsorted(qhT *qh, setT **setp, void *newelem) {
    +  int newindex=0;
    +  void *elem, **elemp;
    +
    +  FOREACHelem_(*setp) {          /* could use binary search instead */
    +    if (elem < newelem)
    +      newindex++;
    +    else if (elem == newelem)
    +      return;
    +    else
    +      break;
    +  }
    +  qh_setaddnth(qh, setp, newindex, newelem);
    +} /* setaddsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setappend(qh, setp, newelem)
    +    append newelem to *setp
    +
    +  notes:
    +    *setp may be a temp set
    +    *setp and newelem may be NULL
    +
    +  design:
    +    expand *setp if empty or full
    +    append newelem to *setp
    +
    +*/
    +void qh_setappend(qhT *qh, setT **setp, void *newelem) {
    +  setelemT *sizep;  /* Avoid strict aliasing.  Writing to *endp may overwrite *sizep */
    +  setelemT *endp;
    +  int count;
    +
    +  if (!newelem)
    +    return;
    +  if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +    qh_setlarger(qh, setp);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  count= (sizep->i)++ - 1;
    +  endp= (setelemT *)SETelemaddr_(*setp, count, void);
    +  (endp++)->p= newelem;
    +  endp->p= NULL;
    +} /* setappend */
    +
    +/*---------------------------------
    +
    +  qh_setappend_set(qh, setp, setA)
    +    appends setA to *setp
    +
    +  notes:
    +    *setp can not be a temp set
    +    *setp and setA may be NULL
    +
    +  design:
    +    setup for copy
    +    expand *setp if it is too small
    +    append all elements of setA to *setp
    +*/
    +void qh_setappend_set(qhT *qh, setT **setp, setT *setA) {
    +  int sizeA, size;
    +  setT *oldset;
    +  setelemT *sizep;
    +
    +  if (!setA)
    +    return;
    +  SETreturnsize_(setA, sizeA);
    +  if (!*setp)
    +    *setp= qh_setnew(qh, sizeA);
    +  sizep= SETsizeaddr_(*setp);
    +  if (!(size= sizep->i))
    +    size= (*setp)->maxsize;
    +  else
    +    size--;
    +  if (size + sizeA > (*setp)->maxsize) {
    +    oldset= *setp;
    +    *setp= qh_setcopy(qh, oldset, sizeA);
    +    qh_setfree(qh, &oldset);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  if (sizeA > 0) {
    +    sizep->i= size+sizeA+1;   /* memcpy may overwrite */
    +    memcpy((char *)&((*setp)->e[size].p), (char *)&(setA->e[0].p), (size_t)(sizeA+1) * SETelemsize);
    +  }
    +} /* setappend_set */
    +
    +
    +/*---------------------------------
    +
    +  qh_setappend2ndlast(qh, setp, newelem )
    +    makes newelem the next to the last element in *setp
    +
    +  notes:
    +    *setp must have at least one element
    +    newelem must be defined
    +    *setp may be a temp set
    +
    +  design:
    +    expand *setp if empty or full
    +    move last element of *setp up one
    +    insert newelem
    +*/
    +void qh_setappend2ndlast(qhT *qh, setT **setp, void *newelem) {
    +    setelemT *sizep;  /* Avoid strict aliasing.  Writing to *endp may overwrite *sizep */
    +    setelemT *endp, *lastp;
    +    int count;
    +
    +    if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +        qh_setlarger(qh, setp);
    +        sizep= SETsizeaddr_(*setp);
    +    }
    +    count= (sizep->i)++ - 1;
    +    endp= (setelemT *)SETelemaddr_(*setp, count, void); /* NULL */
    +    lastp= endp-1;
    +    *(endp++)= *lastp;
    +    endp->p= NULL;    /* may overwrite *sizep */
    +    lastp->p= newelem;
    +} /* setappend2ndlast */
    +
    +/*---------------------------------
    +
    +  qh_setcheck(qh, set, typename, id )
    +    check set for validity
    +    report errors with typename and id
    +
    +  design:
    +    checks that maxsize, actual size, and NULL terminator agree
    +*/
    +void qh_setcheck(qhT *qh, setT *set, const char *tname, unsigned id) {
    +  int maxsize, size;
    +  int waserr= 0;
    +
    +  if (!set)
    +    return;
    +  SETreturnsize_(set, size);
    +  maxsize= set->maxsize;
    +  if (size > maxsize || !maxsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6172, "qhull internal error (qh_setcheck): actual size %d of %s%d is greater than max size %d\n",
    +             size, tname, id, maxsize);
    +    waserr= 1;
    +  }else if (set->e[size].p) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6173, "qhull internal error (qh_setcheck): %s%d(size %d max %d) is not null terminated.\n",
    +             tname, id, size-1, maxsize);
    +    waserr= 1;
    +  }
    +  if (waserr) {
    +    qh_setprint(qh, qh->qhmem.ferr, "ERRONEOUS", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +} /* setcheck */
    +
    +
    +/*---------------------------------
    +
    +  qh_setcompact(qh, set )
    +    remove internal NULLs from an unsorted set
    +
    +  returns:
    +    updated set
    +
    +  notes:
    +    set may be NULL
    +    it would be faster to swap tail of set into holes, like qh_setdel
    +
    +  design:
    +    setup pointers into set
    +    skip NULLs while copying elements to start of set
    +    update the actual size
    +*/
    +void qh_setcompact(qhT *qh, setT *set) {
    +  int size;
    +  void **destp, **elemp, **endp, **firstp;
    +
    +  if (!set)
    +    return;
    +  SETreturnsize_(set, size);
    +  destp= elemp= firstp= SETaddr_(set, void);
    +  endp= destp + size;
    +  while (1) {
    +    if (!(*destp++ = *elemp++)) {
    +      destp--;
    +      if (elemp > endp)
    +        break;
    +    }
    +  }
    +  qh_settruncate(qh, set, (int)(destp-firstp));   /* WARN64 */
    +} /* setcompact */
    +
    +
    +/*---------------------------------
    +
    +  qh_setcopy(qh, set, extra )
    +    make a copy of a sorted or unsorted set with extra slots
    +
    +  returns:
    +    new set
    +
    +  design:
    +    create a newset with extra slots
    +    copy the elements to the newset
    +
    +*/
    +setT *qh_setcopy(qhT *qh, setT *set, int extra) {
    +  setT *newset;
    +  int size;
    +
    +  if (extra < 0)
    +    extra= 0;
    +  SETreturnsize_(set, size);
    +  newset= qh_setnew(qh, size+extra);
    +  SETsizeaddr_(newset)->i= size+1;    /* memcpy may overwrite */
    +  memcpy((char *)&(newset->e[0].p), (char *)&(set->e[0].p), (size_t)(size+1) * SETelemsize);
    +  return(newset);
    +} /* setcopy */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdel(set, oldelem )
    +    delete oldelem from an unsorted set
    +
    +  returns:
    +    returns oldelem if found
    +    returns NULL otherwise
    +
    +  notes:
    +    set may be NULL
    +    oldelem must not be NULL;
    +    only deletes one copy of oldelem in set
    +
    +  design:
    +    locate oldelem
    +    update actual size if it was full
    +    move the last element to the oldelem's location
    +*/
    +void *qh_setdel(setT *set, void *oldelem) {
    +  setelemT *sizep;
    +  setelemT *elemp;
    +  setelemT *lastp;
    +
    +  if (!set)
    +    return NULL;
    +  elemp= (setelemT *)SETaddr_(set, void);
    +  while (elemp->p != oldelem && elemp->p)
    +    elemp++;
    +  if (elemp->p) {
    +    sizep= SETsizeaddr_(set);
    +    if (!(sizep->i)--)         /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (maxsize-1)+ 1 */
    +    lastp= (setelemT *)SETelemaddr_(set, sizep->i-1, void);
    +    elemp->p= lastp->p;      /* may overwrite itself */
    +    lastp->p= NULL;
    +    return oldelem;
    +  }
    +  return NULL;
    +} /* setdel */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdellast(set)
    +    return last element of set or NULL
    +
    +  notes:
    +    deletes element from set
    +    set may be NULL
    +
    +  design:
    +    return NULL if empty
    +    if full set
    +      delete last element and set actual size
    +    else
    +      delete last element and update actual size
    +*/
    +void *qh_setdellast(setT *set) {
    +  int setsize;  /* actually, actual_size + 1 */
    +  int maxsize;
    +  setelemT *sizep;
    +  void *returnvalue;
    +
    +  if (!set || !(set->e[0].p))
    +    return NULL;
    +  sizep= SETsizeaddr_(set);
    +  if ((setsize= sizep->i)) {
    +    returnvalue= set->e[setsize - 2].p;
    +    set->e[setsize - 2].p= NULL;
    +    sizep->i--;
    +  }else {
    +    maxsize= set->maxsize;
    +    returnvalue= set->e[maxsize - 1].p;
    +    set->e[maxsize - 1].p= NULL;
    +    sizep->i= maxsize;
    +  }
    +  return returnvalue;
    +} /* setdellast */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelnth(qh, set, nth )
    +    deletes nth element from unsorted set
    +    0 is first element
    +
    +  returns:
    +    returns the element (needs type conversion)
    +
    +  notes:
    +    errors if nth invalid
    +
    +  design:
    +    setup points and check nth
    +    delete nth element and overwrite with last element
    +*/
    +void *qh_setdelnth(qhT *qh, setT *set, int nth) {
    +  void *elem;
    +  setelemT *sizep;
    +  setelemT *elemp, *lastp;
    +
    +  sizep= SETsizeaddr_(set);
    +  if ((sizep->i--)==0)         /*  if was a full set */
    +    sizep->i= set->maxsize;  /*     *sizep= (maxsize-1)+ 1 */
    +  if (nth < 0 || nth >= sizep->i) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6174, "qhull internal error (qh_setdelnth): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  elemp= (setelemT *)SETelemaddr_(set, nth, void); /* nth valid by QH6174 */
    +  lastp= (setelemT *)SETelemaddr_(set, sizep->i-1, void);
    +  elem= elemp->p;
    +  elemp->p= lastp->p;      /* may overwrite itself */
    +  lastp->p= NULL;
    +  return elem;
    +} /* setdelnth */
    +
    +/*---------------------------------
    +
    +  qh_setdelnthsorted(qh, set, nth )
    +    deletes nth element from sorted set
    +
    +  returns:
    +    returns the element (use type conversion)
    +
    +  notes:
    +    errors if nth invalid
    +
    +  see also:
    +    setnew_delnthsorted
    +
    +  design:
    +    setup points and check nth
    +    copy remaining elements down one
    +    update actual size
    +*/
    +void *qh_setdelnthsorted(qhT *qh, setT *set, int nth) {
    +  void *elem;
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  sizep= SETsizeaddr_(set);
    +  if (nth < 0 || (sizep->i && nth >= sizep->i-1) || nth >= set->maxsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6175, "qhull internal error (qh_setdelnthsorted): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  newp= (setelemT *)SETelemaddr_(set, nth, void);
    +  elem= newp->p;
    +  oldp= newp+1;
    +  while (((newp++)->p= (oldp++)->p))
    +    ; /* copy remaining elements and NULL */
    +  if ((sizep->i--)==0)         /*  if was a full set */
    +    sizep->i= set->maxsize;  /*     *sizep= (max size-1)+ 1 */
    +  return elem;
    +} /* setdelnthsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelsorted(set, oldelem )
    +    deletes oldelem from sorted set
    +
    +  returns:
    +    returns oldelem if it was deleted
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    locate oldelem in set
    +    copy remaining elements down one
    +    update actual size
    +*/
    +void *qh_setdelsorted(setT *set, void *oldelem) {
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  if (!set)
    +    return NULL;
    +  newp= (setelemT *)SETaddr_(set, void);
    +  while(newp->p != oldelem && newp->p)
    +    newp++;
    +  if (newp->p) {
    +    oldp= newp+1;
    +    while (((newp++)->p= (oldp++)->p))
    +      ; /* copy remaining elements */
    +    sizep= SETsizeaddr_(set);
    +    if ((sizep->i--)==0)    /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (max size-1)+ 1 */
    +    return oldelem;
    +  }
    +  return NULL;
    +} /* setdelsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setduplicate(qh, set, elemsize )
    +    duplicate a set of elemsize elements
    +
    +  notes:
    +    use setcopy if retaining old elements
    +
    +  design:
    +    create a new set
    +    for each elem of the old set
    +      create a newelem
    +      append newelem to newset
    +*/
    +setT *qh_setduplicate(qhT *qh, setT *set, int elemsize) {
    +  void          *elem, **elemp, *newElem;
    +  setT          *newSet;
    +  int           size;
    +
    +  if (!(size= qh_setsize(qh, set)))
    +    return NULL;
    +  newSet= qh_setnew(qh, size);
    +  FOREACHelem_(set) {
    +    newElem= qh_memalloc(qh, elemsize);
    +    memcpy(newElem, elem, (size_t)elemsize);
    +    qh_setappend(qh, &newSet, newElem);
    +  }
    +  return newSet;
    +} /* setduplicate */
    +
    +
    +/*---------------------------------
    +
    +  qh_setendpointer( set )
    +    Returns pointer to NULL terminator of a set's elements
    +    set can not be NULL
    +
    +*/
    +void **qh_setendpointer(setT *set) {
    +
    +  setelemT *sizep= SETsizeaddr_(set);
    +  int n= sizep->i;
    +  return (n ? &set->e[n-1].p : &sizep->p);
    +} /* qh_setendpointer */
    +
    +/*---------------------------------
    +
    +  qh_setequal( setA, setB )
    +    returns 1 if two sorted sets are equal, otherwise returns 0
    +
    +  notes:
    +    either set may be NULL
    +
    +  design:
    +    check size of each set
    +    setup pointers
    +    compare elements of each set
    +*/
    +int qh_setequal(setT *setA, setT *setB) {
    +  void **elemAp, **elemBp;
    +  int sizeA= 0, sizeB= 0;
    +
    +  if (setA) {
    +    SETreturnsize_(setA, sizeA);
    +  }
    +  if (setB) {
    +    SETreturnsize_(setB, sizeB);
    +  }
    +  if (sizeA != sizeB)
    +    return 0;
    +  if (!sizeA)
    +    return 1;
    +  elemAp= SETaddr_(setA, void);
    +  elemBp= SETaddr_(setB, void);
    +  if (!memcmp((char *)elemAp, (char *)elemBp, sizeA*SETelemsize))
    +    return 1;
    +  return 0;
    +} /* setequal */
    +
    +
    +/*---------------------------------
    +
    +  qh_setequal_except( setA, skipelemA, setB, skipelemB )
    +    returns 1 if sorted setA and setB are equal except for skipelemA & B
    +
    +  returns:
    +    false if either skipelemA or skipelemB are missing
    +
    +  notes:
    +    neither set may be NULL
    +
    +    if skipelemB is NULL,
    +      can skip any one element of setB
    +
    +  design:
    +    setup pointers
    +    search for skipelemA, skipelemB, and mismatches
    +    check results
    +*/
    +int qh_setequal_except(setT *setA, void *skipelemA, setT *setB, void *skipelemB) {
    +  void **elemA, **elemB;
    +  int skip=0;
    +
    +  elemA= SETaddr_(setA, void);
    +  elemB= SETaddr_(setB, void);
    +  while (1) {
    +    if (*elemA == skipelemA) {
    +      skip++;
    +      elemA++;
    +    }
    +    if (skipelemB) {
    +      if (*elemB == skipelemB) {
    +        skip++;
    +        elemB++;
    +      }
    +    }else if (*elemA != *elemB) {
    +      skip++;
    +      if (!(skipelemB= *elemB++))
    +        return 0;
    +    }
    +    if (!*elemA)
    +      break;
    +    if (*elemA++ != *elemB++)
    +      return 0;
    +  }
    +  if (skip != 2 || *elemB)
    +    return 0;
    +  return 1;
    +} /* setequal_except */
    +
    +
    +/*---------------------------------
    +
    +  qh_setequal_skip( setA, skipA, setB, skipB )
    +    returns 1 if sorted setA and setB are equal except for elements skipA & B
    +
    +  returns:
    +    false if different size
    +
    +  notes:
    +    neither set may be NULL
    +
    +  design:
    +    setup pointers
    +    search for mismatches while skipping skipA and skipB
    +*/
    +int qh_setequal_skip(setT *setA, int skipA, setT *setB, int skipB) {
    +  void **elemA, **elemB, **skipAp, **skipBp;
    +
    +  elemA= SETaddr_(setA, void);
    +  elemB= SETaddr_(setB, void);
    +  skipAp= SETelemaddr_(setA, skipA, void);
    +  skipBp= SETelemaddr_(setB, skipB, void);
    +  while (1) {
    +    if (elemA == skipAp)
    +      elemA++;
    +    if (elemB == skipBp)
    +      elemB++;
    +    if (!*elemA)
    +      break;
    +    if (*elemA++ != *elemB++)
    +      return 0;
    +  }
    +  if (*elemB)
    +    return 0;
    +  return 1;
    +} /* setequal_skip */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfree(qh, setp )
    +    frees the space occupied by a sorted or unsorted set
    +
    +  returns:
    +    sets setp to NULL
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    free array
    +    free set
    +*/
    +void qh_setfree(qhT *qh, setT **setp) {
    +  int size;
    +  void **freelistp;  /* used if !qh_NOmem by qh_memfree_() */
    +
    +  if (*setp) {
    +    size= sizeof(setT) + ((*setp)->maxsize)*SETelemsize;
    +    if (size <= qh->qhmem.LASTsize) {
    +      qh_memfree_(qh, *setp, size, freelistp);
    +    }else
    +      qh_memfree(qh, *setp, size);
    +    *setp= NULL;
    +  }
    +} /* setfree */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfree2(qh, setp, elemsize )
    +    frees the space occupied by a set and its elements
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    free each element
    +    free set
    +*/
    +void qh_setfree2(qhT *qh, setT **setp, int elemsize) {
    +  void          *elem, **elemp;
    +
    +  FOREACHelem_(*setp)
    +    qh_memfree(qh, elem, elemsize);
    +  qh_setfree(qh, setp);
    +} /* setfree2 */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_setfreelong(qh, setp )
    +    frees a set only if it's in long memory
    +
    +  returns:
    +    sets setp to NULL if it is freed
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    if set is large
    +      free it
    +*/
    +void qh_setfreelong(qhT *qh, setT **setp) {
    +  int size;
    +
    +  if (*setp) {
    +    size= sizeof(setT) + ((*setp)->maxsize)*SETelemsize;
    +    if (size > qh->qhmem.LASTsize) {
    +      qh_memfree(qh, *setp, size);
    +      *setp= NULL;
    +    }
    +  }
    +} /* setfreelong */
    +
    +
    +/*---------------------------------
    +
    +  qh_setin(set, setelem )
    +    returns 1 if setelem is in a set, 0 otherwise
    +
    +  notes:
    +    set may be NULL or unsorted
    +
    +  design:
    +    scans set for setelem
    +*/
    +int qh_setin(setT *set, void *setelem) {
    +  void *elem, **elemp;
    +
    +  FOREACHelem_(set) {
    +    if (elem == setelem)
    +      return 1;
    +  }
    +  return 0;
    +} /* setin */
    +
    +
    +/*---------------------------------
    +
    +  qh_setindex(set, atelem )
    +    returns the index of atelem in set.
    +    returns -1, if not in set or maxsize wrong
    +
    +  notes:
    +    set may be NULL and may contain nulls.
    +    NOerrors returned (qh_pointid, QhullPoint::id)
    +
    +  design:
    +    checks maxsize
    +    scans set for atelem
    +*/
    +int qh_setindex(setT *set, void *atelem) {
    +  void **elem;
    +  int size, i;
    +
    +  if (!set)
    +    return -1;
    +  SETreturnsize_(set, size);
    +  if (size > set->maxsize)
    +    return -1;
    +  elem= SETaddr_(set, void);
    +  for (i=0; i < size; i++) {
    +    if (*elem++ == atelem)
    +      return i;
    +  }
    +  return -1;
    +} /* setindex */
    +
    +
    +/*---------------------------------
    +
    +  qh_setlarger(qh, oldsetp )
    +    returns a larger set that contains all elements of *oldsetp
    +
    +  notes:
    +    the set is at least twice as large
    +    if temp set, updates qh->qhmem.tempstack
    +
    +  design:
    +    creates a new set
    +    copies the old set to the new set
    +    updates pointers in tempstack
    +    deletes the old set
    +*/
    +void qh_setlarger(qhT *qh, setT **oldsetp) {
    +  int size= 1;
    +  setT *newset, *set, **setp, *oldset;
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  if (*oldsetp) {
    +    oldset= *oldsetp;
    +    SETreturnsize_(oldset, size);
    +    qh->qhmem.cntlarger++;
    +    qh->qhmem.totlarger += size+1;
    +    newset= qh_setnew(qh, 2 * size);
    +    oldp= (setelemT *)SETaddr_(oldset, void);
    +    newp= (setelemT *)SETaddr_(newset, void);
    +    memcpy((char *)newp, (char *)oldp, (size_t)(size+1) * SETelemsize);
    +    sizep= SETsizeaddr_(newset);
    +    sizep->i= size+1;
    +    FOREACHset_((setT *)qh->qhmem.tempstack) {
    +      if (set == oldset)
    +        *(setp-1)= newset;
    +    }
    +    qh_setfree(qh, oldsetp);
    +  }else
    +    newset= qh_setnew(qh, 3);
    +  *oldsetp= newset;
    +} /* setlarger */
    +
    +
    +/*---------------------------------
    +
    +  qh_setlast( set )
    +    return last element of set or NULL (use type conversion)
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    return last element
    +*/
    +void *qh_setlast(setT *set) {
    +  int size;
    +
    +  if (set) {
    +    size= SETsizeaddr_(set)->i;
    +    if (!size)
    +      return SETelem_(set, set->maxsize - 1);
    +    else if (size > 1)
    +      return SETelem_(set, size - 2);
    +  }
    +  return NULL;
    +} /* setlast */
    +
    +
    +/*---------------------------------
    +
    +  qh_setnew(qh, setsize )
    +    creates and allocates space for a set
    +
    +  notes:
    +    setsize means the number of elements (!including the NULL terminator)
    +    use qh_settemp/qh_setfreetemp if set is temporary
    +
    +  design:
    +    allocate memory for set
    +    roundup memory if small set
    +    initialize as empty set
    +*/
    +setT *qh_setnew(qhT *qh, int setsize) {
    +  setT *set;
    +  int sizereceived; /* used if !qh_NOmem */
    +  int size;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  if (!setsize)
    +    setsize++;
    +  size= sizeof(setT) + setsize * SETelemsize;
    +  if (size>0 && size <= qh->qhmem.LASTsize) {
    +    qh_memalloc_(qh, size, freelistp, set, setT);
    +#ifndef qh_NOmem
    +    sizereceived= qh->qhmem.sizetable[ qh->qhmem.indextable[size]];
    +    if (sizereceived > size)
    +      setsize += (sizereceived - size)/SETelemsize;
    +#endif
    +  }else
    +    set= (setT*)qh_memalloc(qh, size);
    +  set->maxsize= setsize;
    +  set->e[setsize].i= 1;
    +  set->e[0].p= NULL;
    +  return(set);
    +} /* setnew */
    +
    +
    +/*---------------------------------
    +
    +  qh_setnew_delnthsorted(qh, set, size, nth, prepend )
    +    creates a sorted set not containing nth element
    +    if prepend, the first prepend elements are undefined
    +
    +  notes:
    +    set must be defined
    +    checks nth
    +    see also: setdelnthsorted
    +
    +  design:
    +    create new set
    +    setup pointers and allocate room for prepend'ed entries
    +    append head of old set to new set
    +    append tail of old set to new set
    +*/
    +setT *qh_setnew_delnthsorted(qhT *qh, setT *set, int size, int nth, int prepend) {
    +  setT *newset;
    +  void **oldp, **newp;
    +  int tailsize= size - nth -1, newsize;
    +
    +  if (tailsize < 0) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6176, "qhull internal error (qh_setnew_delnthsorted): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  newsize= size-1 + prepend;
    +  newset= qh_setnew(qh, newsize);
    +  newset->e[newset->maxsize].i= newsize+1;  /* may be overwritten */
    +  oldp= SETaddr_(set, void);
    +  newp= SETaddr_(newset, void) + prepend;
    +  switch (nth) {
    +  case 0:
    +    break;
    +  case 1:
    +    *(newp++)= *oldp++;
    +    break;
    +  case 2:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 3:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 4:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  default:
    +    memcpy((char *)newp, (char *)oldp, (size_t)nth * SETelemsize);
    +    newp += nth;
    +    oldp += nth;
    +    break;
    +  }
    +  oldp++;
    +  switch (tailsize) {
    +  case 0:
    +    break;
    +  case 1:
    +    *(newp++)= *oldp++;
    +    break;
    +  case 2:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 3:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 4:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  default:
    +    memcpy((char *)newp, (char *)oldp, (size_t)tailsize * SETelemsize);
    +    newp += tailsize;
    +  }
    +  *newp= NULL;
    +  return(newset);
    +} /* setnew_delnthsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setprint(qh, fp, string, set )
    +    print set elements to fp with identifying string
    +
    +  notes:
    +    never errors
    +*/
    +void qh_setprint(qhT *qh, FILE *fp, const char* string, setT *set) {
    +  int size, k;
    +
    +  if (!set)
    +    qh_fprintf(qh, fp, 9346, "%s set is null\n", string);
    +  else {
    +    SETreturnsize_(set, size);
    +    qh_fprintf(qh, fp, 9347, "%s set=%p maxsize=%d size=%d elems=",
    +             string, set, set->maxsize, size);
    +    if (size > set->maxsize)
    +      size= set->maxsize+1;
    +    for (k=0; k < size; k++)
    +      qh_fprintf(qh, fp, 9348, " %p", set->e[k].p);
    +    qh_fprintf(qh, fp, 9349, "\n");
    +  }
    +} /* setprint */
    +
    +/*---------------------------------
    +
    +  qh_setreplace(qh, set, oldelem, newelem )
    +    replaces oldelem in set with newelem
    +
    +  notes:
    +    errors if oldelem not in the set
    +    newelem may be NULL, but it turns the set into an indexed set (no FOREACH)
    +
    +  design:
    +    find oldelem
    +    replace with newelem
    +*/
    +void qh_setreplace(qhT *qh, setT *set, void *oldelem, void *newelem) {
    +  void **elemp;
    +
    +  elemp= SETaddr_(set, void);
    +  while (*elemp != oldelem && *elemp)
    +    elemp++;
    +  if (*elemp)
    +    *elemp= newelem;
    +  else {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6177, "qhull internal error (qh_setreplace): elem %p not found in set\n",
    +       oldelem);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +} /* setreplace */
    +
    +
    +/*---------------------------------
    +
    +  qh_setsize(qh, set )
    +    returns the size of a set
    +
    +  notes:
    +    errors if set's maxsize is incorrect
    +    same as SETreturnsize_(set)
    +    same code for qh_setsize [qset_r.c] and QhullSetBase::count
    +
    +  design:
    +    determine actual size of set from maxsize
    +*/
    +int qh_setsize(qhT *qh, setT *set) {
    +  int size;
    +  setelemT *sizep;
    +
    +  if (!set)
    +    return(0);
    +  sizep= SETsizeaddr_(set);
    +  if ((size= sizep->i)) {
    +    size--;
    +    if (size > set->maxsize) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6178, "qhull internal error (qh_setsize): current set size %d is greater than maximum size %d\n",
    +               size, set->maxsize);
    +      qh_setprint(qh, qh->qhmem.ferr, "set: ", set);
    +      qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +    }
    +  }else
    +    size= set->maxsize;
    +  return size;
    +} /* setsize */
    +
    +/*---------------------------------
    +
    +  qh_settemp(qh, setsize )
    +    return a stacked, temporary set of upto setsize elements
    +
    +  notes:
    +    use settempfree or settempfree_all to release from qh->qhmem.tempstack
    +    see also qh_setnew
    +
    +  design:
    +    allocate set
    +    append to qh->qhmem.tempstack
    +
    +*/
    +setT *qh_settemp(qhT *qh, int setsize) {
    +  setT *newset;
    +
    +  newset= qh_setnew(qh, setsize);
    +  qh_setappend(qh, &qh->qhmem.tempstack, newset);
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8123, "qh_settemp: temp set %p of %d elements, depth %d\n",
    +       newset, newset->maxsize, qh_setsize(qh, qh->qhmem.tempstack));
    +  return newset;
    +} /* settemp */
    +
    +/*---------------------------------
    +
    +  qh_settempfree(qh, set )
    +    free temporary set at top of qh->qhmem.tempstack
    +
    +  notes:
    +    nop if set is NULL
    +    errors if set not from previous   qh_settemp
    +
    +  to locate errors:
    +    use 'T2' to find source and then find mis-matching qh_settemp
    +
    +  design:
    +    check top of qh->qhmem.tempstack
    +    free it
    +*/
    +void qh_settempfree(qhT *qh, setT **set) {
    +  setT *stackedset;
    +
    +  if (!*set)
    +    return;
    +  stackedset= qh_settemppop(qh);
    +  if (stackedset != *set) {
    +    qh_settemppush(qh, stackedset);
    +    qh_fprintf(qh, qh->qhmem.ferr, 6179, "qhull internal error (qh_settempfree): set %p(size %d) was not last temporary allocated(depth %d, set %p, size %d)\n",
    +             *set, qh_setsize(qh, *set), qh_setsize(qh, qh->qhmem.tempstack)+1,
    +             stackedset, qh_setsize(qh, stackedset));
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh_setfree(qh, set);
    +} /* settempfree */
    +
    +/*---------------------------------
    +
    +  qh_settempfree_all(qh)
    +    free all temporary sets in qh->qhmem.tempstack
    +
    +  design:
    +    for each set in tempstack
    +      free set
    +    free qh->qhmem.tempstack
    +*/
    +void qh_settempfree_all(qhT *qh) {
    +  setT *set, **setp;
    +
    +  FOREACHset_(qh->qhmem.tempstack)
    +    qh_setfree(qh, &set);
    +  qh_setfree(qh, &qh->qhmem.tempstack);
    +} /* settempfree_all */
    +
    +/*---------------------------------
    +
    +  qh_settemppop(qh)
    +    pop and return temporary set from qh->qhmem.tempstack
    +
    +  notes:
    +    the returned set is permanent
    +
    +  design:
    +    pop and check top of qh->qhmem.tempstack
    +*/
    +setT *qh_settemppop(qhT *qh) {
    +  setT *stackedset;
    +
    +  stackedset= (setT*)qh_setdellast(qh->qhmem.tempstack);
    +  if (!stackedset) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6180, "qhull internal error (qh_settemppop): pop from empty temporary stack\n");
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8124, "qh_settemppop: depth %d temp set %p of %d elements\n",
    +       qh_setsize(qh, qh->qhmem.tempstack)+1, stackedset, qh_setsize(qh, stackedset));
    +  return stackedset;
    +} /* settemppop */
    +
    +/*---------------------------------
    +
    +  qh_settemppush(qh, set )
    +    push temporary set unto qh->qhmem.tempstack (makes it temporary)
    +
    +  notes:
    +    duplicates settemp() for tracing
    +
    +  design:
    +    append set to tempstack
    +*/
    +void qh_settemppush(qhT *qh, setT *set) {
    +  if (!set) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6267, "qhull error (qh_settemppush): can not push a NULL temp\n");
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh_setappend(qh, &qh->qhmem.tempstack, set);
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8125, "qh_settemppush: depth %d temp set %p of %d elements\n",
    +      qh_setsize(qh, qh->qhmem.tempstack), set, qh_setsize(qh, set));
    +} /* settemppush */
    +
    +
    +/*---------------------------------
    +
    +  qh_settruncate(qh, set, size )
    +    truncate set to size elements
    +
    +  notes:
    +    set must be defined
    +
    +  see:
    +    SETtruncate_
    +
    +  design:
    +    check size
    +    update actual size of set
    +*/
    +void qh_settruncate(qhT *qh, setT *set, int size) {
    +
    +  if (size < 0 || size > set->maxsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6181, "qhull internal error (qh_settruncate): size %d out of bounds for set:\n", size);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  set->e[set->maxsize].i= size+1;   /* maybe overwritten */
    +  set->e[size].p= NULL;
    +} /* settruncate */
    +
    +/*---------------------------------
    +
    +  qh_setunique(qh, set, elem )
    +    add elem to unsorted set unless it is already in set
    +
    +  notes:
    +    returns 1 if it is appended
    +
    +  design:
    +    if elem not in set
    +      append elem to set
    +*/
    +int qh_setunique(qhT *qh, setT **set, void *elem) {
    +
    +  if (!qh_setin(*set, elem)) {
    +    qh_setappend(qh, set, elem);
    +    return 1;
    +  }
    +  return 0;
    +} /* setunique */
    +
    +/*---------------------------------
    +
    +  qh_setzero(qh, set, index, size )
    +    zero elements from index on
    +    set actual size of set to size
    +
    +  notes:
    +    set must be defined
    +    the set becomes an indexed set (can not use FOREACH...)
    +
    +  see also:
    +    qh_settruncate
    +
    +  design:
    +    check index and size
    +    update actual size
    +    zero elements starting at e[index]
    +*/
    +void qh_setzero(qhT *qh, setT *set, int idx, int size) {
    +  int count;
    +
    +  if (idx < 0 || idx >= size || size > set->maxsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6182, "qhull internal error (qh_setzero): index %d or size %d out of bounds for set:\n", idx, size);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  set->e[set->maxsize].i=  size+1;  /* may be overwritten */
    +  count= size - idx + 1;   /* +1 for NULL terminator */
    +  memset((char *)SETelemaddr_(set, idx, void), 0, (size_t)count * SETelemsize);
    +} /* setzero */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/qset_r.h b/xs/src/qhull/src/libqhull_r/qset_r.h
    new file mode 100644
    index 0000000000..7ba199d6f4
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/qset_r.h
    @@ -0,0 +1,502 @@
    +/*
      ---------------------------------
    +
    +   qset_r.h
    +     header file for qset_r.c that implements set
    +
    +   see qh-set_r.htm and qset_r.c
    +
    +   only uses mem_r.c, malloc/free
    +
    +   for error handling, writes message and calls
    +      qh_errexit(qhT *qh, qhmem_ERRqhull, NULL, NULL);
    +
    +   set operations satisfy the following properties:
    +    - sets have a max size, the actual size (if different) is stored at the end
    +    - every set is NULL terminated
    +    - sets may be sorted or unsorted, the caller must distinguish this
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/qset_r.h#4 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFset
    +#define qhDEFset 1
    +
    +#include 
    +
    +/*================= -structures- ===============*/
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;   /* a set is a sorted or unsorted array of pointers */
    +#endif
    +
    +#ifndef DEFqhT
    +#define DEFqhT 1
    +typedef struct qhT qhT;          /* defined in libqhull_r.h */
    +#endif
    +
    +/* [jan'15] Decided not to use countT.  Most sets are small.  The code uses signed tests */
    +
    +/*------------------------------------------
    +
    +setT
    +  a set or list of pointers with maximum size and actual size.
    +
    +variations:
    +  unsorted, unique   -- a list of unique pointers with NULL terminator
    +                           user guarantees uniqueness
    +  sorted             -- a sorted list of unique pointers with NULL terminator
    +                           qset_r.c guarantees uniqueness
    +  unsorted           -- a list of pointers terminated with NULL
    +  indexed            -- an array of pointers with NULL elements
    +
    +structure for set of n elements:
    +
    +        --------------
    +        |  maxsize
    +        --------------
    +        |  e[0] - a pointer, may be NULL for indexed sets
    +        --------------
    +        |  e[1]
    +
    +        --------------
    +        |  ...
    +        --------------
    +        |  e[n-1]
    +        --------------
    +        |  e[n] = NULL
    +        --------------
    +        |  ...
    +        --------------
    +        |  e[maxsize] - n+1 or NULL (determines actual size of set)
    +        --------------
    +
    +*/
    +
    +/*-- setelemT -- internal type to allow both pointers and indices
    +*/
    +typedef union setelemT setelemT;
    +union setelemT {
    +  void    *p;
    +  int   i;         /* integer used for e[maxSize] */
    +};
    +
    +struct setT {
    +  int maxsize;          /* maximum number of elements (except NULL) */
    +  setelemT e[1];        /* array of pointers, tail is NULL */
    +                        /* last slot (unless NULL) is actual size+1
    +                           e[maxsize]==NULL or e[e[maxsize]-1]==NULL */
    +                        /* this may generate a warning since e[] contains
    +                           maxsize elements */
    +};
    +
    +/*=========== -constants- =========================*/
    +
    +/*-------------------------------------
    +
    +  SETelemsize
    +    size of a set element in bytes
    +*/
    +#define SETelemsize ((int)sizeof(setelemT))
    +
    +
    +/*=========== -macros- =========================*/
    +
    +/*-------------------------------------
    +
    +   FOREACHsetelement_(type, set, variable)
    +     define FOREACH iterator
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +     no space in "variable)" [DEC Alpha cc compiler]
    +
    +   each iteration:
    +     variable is set element
    +     variablep is one beyond variable.
    +
    +   to repeat an element:
    +     variablep--; / *repeat* /
    +
    +   at exit:
    +     variable is NULL at end of loop
    +
    +   example:
    +     #define FOREACHfacet_( facets ) FOREACHsetelement_( facetT, facets, facet )
    +
    +   notes:
    +     use FOREACHsetelement_i_() if need index or include NULLs
    +
    +   WARNING:
    +     nested loops can't use the same variable (define another FOREACH)
    +
    +     needs braces if nested inside another FOREACH
    +     this includes intervening blocks, e.g. FOREACH...{ if () FOREACH...} )
    +*/
    +#define FOREACHsetelement_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##p= (type **)&((set)->e[0].p); \
    +          (variable= *variable##p++);)
    +
    +/*------------------------------------------
    +
    +   FOREACHsetelement_i_(qh, type, set, variable)
    +     define indexed FOREACH iterator
    +
    +   declare:
    +     type *variable, variable_n, variable_i;
    +
    +   each iteration:
    +     variable is set element, may be NULL
    +     variable_i is index, variable_n is qh_setsize()
    +
    +   to repeat an element:
    +     variable_i--; variable_n-- repeats for deleted element
    +
    +   at exit:
    +     variable==NULL and variable_i==variable_n
    +
    +   example:
    +     #define FOREACHfacet_i_( qh, facets ) FOREACHsetelement_i_( qh, facetT, facets, facet )
    +
    +   WARNING:
    +     nested loops can't use the same variable (define another FOREACH)
    +
    +     needs braces if nested inside another FOREACH
    +     this includes intervening blocks, e.g. FOREACH...{ if () FOREACH...} )
    +*/
    +#define FOREACHsetelement_i_(qh, type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##_i= 0, variable= (type *)((set)->e[0].p), \
    +                   variable##_n= qh_setsize(qh, set);\
    +          variable##_i < variable##_n;\
    +          variable= (type *)((set)->e[++variable##_i].p) )
    +
    +/*----------------------------------------
    +
    +   FOREACHsetelementreverse_(qh, type, set, variable)-
    +     define FOREACH iterator in reverse order
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +     also declare 'int variabletemp'
    +
    +   each iteration:
    +     variable is set element
    +
    +   to repeat an element:
    +     variabletemp++; / *repeat* /
    +
    +   at exit:
    +     variable is NULL
    +
    +   example:
    +     #define FOREACHvertexreverse_( vertices ) FOREACHsetelementreverse_( vertexT, vertices, vertex )
    +
    +   notes:
    +     use FOREACHsetelementreverse12_() to reverse first two elements
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHsetelementreverse_(qh, type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +           variable##temp= qh_setsize(qh, set)-1, variable= qh_setlast(qh, set);\
    +           variable; variable= \
    +           ((--variable##temp >= 0) ? SETelemt_(set, variable##temp, type) : NULL))
    +
    +/*-------------------------------------
    +
    +   FOREACHsetelementreverse12_(type, set, variable)-
    +     define FOREACH iterator with e[1] and e[0] reversed
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +
    +   each iteration:
    +     variable is set element
    +     variablep is one after variable.
    +
    +   to repeat an element:
    +     variablep--; / *repeat* /
    +
    +   at exit:
    +     variable is NULL at end of loop
    +
    +   example
    +     #define FOREACHvertexreverse12_( vertices ) FOREACHsetelementreverse12_( vertexT, vertices, vertex )
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHsetelementreverse12_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##p= (type **)&((set)->e[1].p); \
    +          (variable= *variable##p); \
    +          variable##p == ((type **)&((set)->e[0].p))?variable##p += 2: \
    +              (variable##p == ((type **)&((set)->e[1].p))?variable##p--:variable##p++))
    +
    +/*-------------------------------------
    +
    +   FOREACHelem_( set )-
    +     iterate elements in a set
    +
    +   declare:
    +     void *elem, *elemp;
    +
    +   each iteration:
    +     elem is set element
    +     elemp is one beyond
    +
    +   to repeat an element:
    +     elemp--; / *repeat* /
    +
    +   at exit:
    +     elem == NULL at end of loop
    +
    +   example:
    +     FOREACHelem_(set) {
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHelem_(set) FOREACHsetelement_(void, set, elem)
    +
    +/*-------------------------------------
    +
    +   FOREACHset_( set )-
    +     iterate a set of sets
    +
    +   declare:
    +     setT *set, **setp;
    +
    +   each iteration:
    +     set is set element
    +     setp is one beyond
    +
    +   to repeat an element:
    +     setp--; / *repeat* /
    +
    +   at exit:
    +     set == NULL at end of loop
    +
    +   example
    +     FOREACHset_(sets) {
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHset_(sets) FOREACHsetelement_(setT, sets, set)
    +
    +/*-------------------------------------------
    +
    +   SETindex_( set, elem )
    +     return index of elem in set
    +
    +   notes:
    +     for use with FOREACH iteration
    +     WARN64 -- Maximum set size is 2G
    +
    +   example:
    +     i= SETindex_(ridges, ridge)
    +*/
    +#define SETindex_(set, elem) ((int)((void **)elem##p - (void **)&(set)->e[1].p))
    +
    +/*-----------------------------------------
    +
    +   SETref_( elem )
    +     l.h.s. for modifying the current element in a FOREACH iteration
    +
    +   example:
    +     SETref_(ridge)= anotherridge;
    +*/
    +#define SETref_(elem) (elem##p[-1])
    +
    +/*-----------------------------------------
    +
    +   SETelem_(set, n)
    +     return the n'th element of set
    +
    +   notes:
    +      assumes that n is valid [0..size] and that set is defined
    +      use SETelemt_() for type cast
    +*/
    +#define SETelem_(set, n)           ((set)->e[n].p)
    +
    +/*-----------------------------------------
    +
    +   SETelemt_(set, n, type)
    +     return the n'th element of set as a type
    +
    +   notes:
    +      assumes that n is valid [0..size] and that set is defined
    +*/
    +#define SETelemt_(set, n, type)    ((type*)((set)->e[n].p))
    +
    +/*-----------------------------------------
    +
    +   SETelemaddr_(set, n, type)
    +     return address of the n'th element of a set
    +
    +   notes:
    +      assumes that n is valid [0..size] and set is defined
    +*/
    +#define SETelemaddr_(set, n, type) ((type **)(&((set)->e[n].p)))
    +
    +/*-----------------------------------------
    +
    +   SETfirst_(set)
    +     return first element of set
    +
    +*/
    +#define SETfirst_(set)             ((set)->e[0].p)
    +
    +/*-----------------------------------------
    +
    +   SETfirstt_(set, type)
    +     return first element of set as a type
    +
    +*/
    +#define SETfirstt_(set, type)      ((type*)((set)->e[0].p))
    +
    +/*-----------------------------------------
    +
    +   SETsecond_(set)
    +     return second element of set
    +
    +*/
    +#define SETsecond_(set)            ((set)->e[1].p)
    +
    +/*-----------------------------------------
    +
    +   SETsecondt_(set, type)
    +     return second element of set as a type
    +*/
    +#define SETsecondt_(set, type)     ((type*)((set)->e[1].p))
    +
    +/*-----------------------------------------
    +
    +   SETaddr_(set, type)
    +       return address of set's elements
    +*/
    +#define SETaddr_(set,type)         ((type **)(&((set)->e[0].p)))
    +
    +/*-----------------------------------------
    +
    +   SETreturnsize_(set, size)
    +     return size of a set
    +
    +   notes:
    +      set must be defined
    +      use qh_setsize(qhT *qh, set) unless speed is critical
    +*/
    +#define SETreturnsize_(set, size) (((size)= ((set)->e[(set)->maxsize].i))?(--(size)):((size)= (set)->maxsize))
    +
    +/*-----------------------------------------
    +
    +   SETempty_(set)
    +     return true(1) if set is empty
    +
    +   notes:
    +      set may be NULL
    +*/
    +#define SETempty_(set)            (!set || (SETfirst_(set) ? 0 : 1))
    +
    +/*---------------------------------
    +
    +  SETsizeaddr_(set)
    +    return pointer to 'actual size+1' of set (set CANNOT be NULL!!)
    +    Its type is setelemT* for strict aliasing
    +    All SETelemaddr_ must be cast to setelemT
    +
    +
    +  notes:
    +    *SETsizeaddr==NULL or e[*SETsizeaddr-1].p==NULL
    +*/
    +#define SETsizeaddr_(set) (&((set)->e[(set)->maxsize]))
    +
    +/*-----------------------------------------
    +
    +   SETtruncate_(set, size)
    +     truncate set to size
    +
    +   see:
    +     qh_settruncate()
    +
    +*/
    +#define SETtruncate_(set, size) {set->e[set->maxsize].i= size+1; /* maybe overwritten */ \
    +      set->e[size].p= NULL;}
    +
    +/*======= prototypes in alphabetical order ============*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void  qh_setaddsorted(qhT *qh, setT **setp, void *elem);
    +void  qh_setaddnth(qhT *qh, setT **setp, int nth, void *newelem);
    +void  qh_setappend(qhT *qh, setT **setp, void *elem);
    +void  qh_setappend_set(qhT *qh, setT **setp, setT *setA);
    +void  qh_setappend2ndlast(qhT *qh, setT **setp, void *elem);
    +void  qh_setcheck(qhT *qh, setT *set, const char *tname, unsigned id);
    +void  qh_setcompact(qhT *qh, setT *set);
    +setT *qh_setcopy(qhT *qh, setT *set, int extra);
    +void *qh_setdel(setT *set, void *elem);
    +void *qh_setdellast(setT *set);
    +void *qh_setdelnth(qhT *qh, setT *set, int nth);
    +void *qh_setdelnthsorted(qhT *qh, setT *set, int nth);
    +void *qh_setdelsorted(setT *set, void *newelem);
    +setT *qh_setduplicate(qhT *qh, setT *set, int elemsize);
    +void **qh_setendpointer(setT *set);
    +int   qh_setequal(setT *setA, setT *setB);
    +int   qh_setequal_except(setT *setA, void *skipelemA, setT *setB, void *skipelemB);
    +int   qh_setequal_skip(setT *setA, int skipA, setT *setB, int skipB);
    +void  qh_setfree(qhT *qh, setT **set);
    +void  qh_setfree2(qhT *qh, setT **setp, int elemsize);
    +void  qh_setfreelong(qhT *qh, setT **set);
    +int   qh_setin(setT *set, void *setelem);
    +int qh_setindex(setT *set, void *setelem);
    +void  qh_setlarger(qhT *qh, setT **setp);
    +void *qh_setlast(setT *set);
    +setT *qh_setnew(qhT *qh, int size);
    +setT *qh_setnew_delnthsorted(qhT *qh, setT *set, int size, int nth, int prepend);
    +void  qh_setprint(qhT *qh, FILE *fp, const char* string, setT *set);
    +void  qh_setreplace(qhT *qh, setT *set, void *oldelem, void *newelem);
    +int qh_setsize(qhT *qh, setT *set);
    +setT *qh_settemp(qhT *qh, int setsize);
    +void  qh_settempfree(qhT *qh, setT **set);
    +void  qh_settempfree_all(qhT *qh);
    +setT *qh_settemppop(qhT *qh);
    +void  qh_settemppush(qhT *qh, setT *set);
    +void  qh_settruncate(qhT *qh, setT *set, int size);
    +int   qh_setunique(qhT *qh, setT **set, void *elem);
    +void  qh_setzero(qhT *qh, setT *set, int idx, int size);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFset */
    diff --git a/xs/src/qhull/src/libqhull_r/random_r.c b/xs/src/qhull/src/libqhull_r/random_r.c
    new file mode 100644
    index 0000000000..1fefb51c36
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/random_r.c
    @@ -0,0 +1,247 @@
    +/*
      ---------------------------------
    +
    +   random_r.c and utilities
    +     Park & Miller's minimimal standard random number generator
    +     argc/argv conversion
    +
    +     Used by rbox.  Do not use 'qh' 
    +*/
    +
    +#include "libqhull_r.h"
    +#include "random_r.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#pragma warning( disable : 4996)  /* function was declared deprecated(strcpy, localtime, etc.) */
    +#endif
    +
    +/*---------------------------------
    +
    + qh_argv_to_command(argc, argv, command, max_size )
    +
    +    build command from argc/argv
    +    max_size is at least
    +
    + returns:
    +    a space-delimited string of options (just as typed)
    +    returns false if max_size is too short
    +
    + notes:
    +    silently removes
    +    makes option string easy to input and output
    +    matches qh_argv_to_command_size()
    +
    +    argc may be 0
    +*/
    +int qh_argv_to_command(int argc, char *argv[], char* command, int max_size) {
    +  int i, remaining;
    +  char *s;
    +  *command= '\0';  /* max_size > 0 */
    +
    +  if (argc) {
    +    if ((s= strrchr( argv[0], '\\')) /* get filename w/o .exe extension */
    +    || (s= strrchr( argv[0], '/')))
    +        s++;
    +    else
    +        s= argv[0];
    +    if ((int)strlen(s) < max_size)   /* WARN64 */
    +        strcpy(command, s);
    +    else
    +        goto error_argv;
    +    if ((s= strstr(command, ".EXE"))
    +    ||  (s= strstr(command, ".exe")))
    +        *s= '\0';
    +  }
    +  for (i=1; i < argc; i++) {
    +    s= argv[i];
    +    remaining= max_size - (int)strlen(command) - (int)strlen(s) - 2;   /* WARN64 */
    +    if (!*s || strchr(s, ' ')) {
    +      char *t= command + strlen(command);
    +      remaining -= 2;
    +      if (remaining < 0) {
    +        goto error_argv;
    +      }
    +      *t++= ' ';
    +      *t++= '"';
    +      while (*s) {
    +        if (*s == '"') {
    +          if (--remaining < 0)
    +            goto error_argv;
    +          *t++= '\\';
    +        }
    +        *t++= *s++;
    +      }
    +      *t++= '"';
    +      *t= '\0';
    +    }else if (remaining < 0) {
    +      goto error_argv;
    +    }else
    +      strcat(command, " ");
    +      strcat(command, s);
    +  }
    +  return 1;
    +
    +error_argv:
    +  return 0;
    +} /* argv_to_command */
    +
    +/*---------------------------------
    +
    +qh_argv_to_command_size(argc, argv )
    +
    +    return size to allocate for qh_argv_to_command()
    +
    +notes:
    +    argc may be 0
    +    actual size is usually shorter
    +*/
    +int qh_argv_to_command_size(int argc, char *argv[]) {
    +    unsigned int count= 1; /* null-terminator if argc==0 */
    +    int i;
    +    char *s;
    +
    +    for (i=0; i0 && strchr(argv[i], ' ')) {
    +        count += 2;  /* quote delimiters */
    +        for (s=argv[i]; *s; s++) {
    +          if (*s == '"') {
    +            count++;
    +          }
    +        }
    +      }
    +    }
    +    return count;
    +} /* argv_to_command_size */
    +
    +/*---------------------------------
    +
    +  qh_rand()
    +  qh_srand(qh, seed )
    +    generate pseudo-random number between 1 and 2^31 -2
    +
    +  notes:
    +    For qhull and rbox, called from qh_RANDOMint(),etc. [user.h]
    +
    +    From Park & Miller's minimal standard random number generator
    +      Communications of the ACM, 31:1192-1201, 1988.
    +    Does not use 0 or 2^31 -1
    +      this is silently enforced by qh_srand()
    +    Can make 'Rn' much faster by moving qh_rand to qh_distplane
    +*/
    +
    +/* Global variables and constants */
    +
    +#define qh_rand_a 16807
    +#define qh_rand_m 2147483647
    +#define qh_rand_q 127773  /* m div a */
    +#define qh_rand_r 2836    /* m mod a */
    +
    +int qh_rand(qhT *qh) {
    +    int lo, hi, test;
    +    int seed = qh->last_random;
    +
    +    hi = seed / qh_rand_q;  /* seed div q */
    +    lo = seed % qh_rand_q;  /* seed mod q */
    +    test = qh_rand_a * lo - qh_rand_r * hi;
    +    if (test > 0)
    +        seed= test;
    +    else
    +        seed= test + qh_rand_m;
    +    qh->last_random= seed;
    +    /* seed = seed < qh_RANDOMmax/2 ? 0 : qh_RANDOMmax;  for testing */
    +    /* seed = qh_RANDOMmax;  for testing */
    +    return seed;
    +} /* rand */
    +
    +void qh_srand(qhT *qh, int seed) {
    +    if (seed < 1)
    +        qh->last_random= 1;
    +    else if (seed >= qh_rand_m)
    +        qh->last_random= qh_rand_m - 1;
    +    else
    +        qh->last_random= seed;
    +} /* qh_srand */
    +
    +/*---------------------------------
    +
    +qh_randomfactor(qh, scale, offset )
    +  return a random factor r * scale + offset
    +
    +notes:
    +  qh.RANDOMa/b are defined in global_r.c
    +  qh_RANDOMint requires 'qh'
    +*/
    +realT qh_randomfactor(qhT *qh, realT scale, realT offset) {
    +    realT randr;
    +
    +    randr= qh_RANDOMint;
    +    return randr * scale + offset;
    +} /* randomfactor */
    +
    +/*---------------------------------
    +
    +qh_randommatrix(qh, buffer, dim, rows )
    +  generate a random dim X dim matrix in range [-1,1]
    +  assumes buffer is [dim+1, dim]
    +
    +  returns:
    +    sets buffer to random numbers
    +    sets rows to rows of buffer
    +    sets row[dim] as scratch row
    +
    +  notes:
    +    qh_RANDOMint requires 'qh'
    +*/
    +void qh_randommatrix(qhT *qh, realT *buffer, int dim, realT **rows) {
    +    int i, k;
    +    realT **rowi, *coord, realr;
    +
    +    coord= buffer;
    +    rowi= rows;
    +    for (i=0; i < dim; i++) {
    +        *(rowi++)= coord;
    +        for (k=0; k < dim; k++) {
    +            realr= qh_RANDOMint;
    +            *(coord++)= 2.0 * realr/(qh_RANDOMmax+1) - 1.0;
    +        }
    +    }
    +    *rowi= coord;
    +} /* randommatrix */
    +
    +/*---------------------------------
    +
    +  qh_strtol( s, endp) qh_strtod( s, endp)
    +    internal versions of strtol() and strtod()
    +    does not skip trailing spaces
    +  notes:
    +    some implementations of strtol()/strtod() skip trailing spaces
    +*/
    +double qh_strtod(const char *s, char **endp) {
    +  double result;
    +
    +  result= strtod(s, endp);
    +  if (s < (*endp) && (*endp)[-1] == ' ')
    +    (*endp)--;
    +  return result;
    +} /* strtod */
    +
    +int qh_strtol(const char *s, char **endp) {
    +  int result;
    +
    +  result= (int) strtol(s, endp, 10);     /* WARN64 */
    +  if (s< (*endp) && (*endp)[-1] == ' ')
    +    (*endp)--;
    +  return result;
    +} /* strtol */
    diff --git a/xs/src/qhull/src/libqhull_r/random_r.h b/xs/src/qhull/src/libqhull_r/random_r.h
    new file mode 100644
    index 0000000000..dc884b33cb
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/random_r.h
    @@ -0,0 +1,41 @@
    +/*
      ---------------------------------
    +
    +  random.h
    +    header file for random and utility routines
    +
    +   see qh-geom_r.htm and random_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/random_r.h#4 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFrandom
    +#define qhDEFrandom 1
    +
    +#include "libqhull_r.h"
    +
    +/*============= prototypes in alphabetical order ======= */
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +int     qh_argv_to_command(int argc, char *argv[], char* command, int max_size);
    +int     qh_argv_to_command_size(int argc, char *argv[]);
    +int     qh_rand(qhT *qh);
    +void    qh_srand(qhT *qh, int seed);
    +realT   qh_randomfactor(qhT *qh, realT scale, realT offset);
    +void    qh_randommatrix(qhT *qh, realT *buffer, int dim, realT **row);
    +int     qh_strtol(const char *s, char **endp);
    +double  qh_strtod(const char *s, char **endp);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFrandom */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/rboxlib_r.c b/xs/src/qhull/src/libqhull_r/rboxlib_r.c
    new file mode 100644
    index 0000000000..6c0c7e35ef
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/rboxlib_r.c
    @@ -0,0 +1,842 @@
    +/*
      ---------------------------------
    +
    +   rboxlib_r.c
    +     Generate input points
    +
    +   notes:
    +     For documentation, see prompt[] of rbox_r.c
    +     50 points generated for 'rbox D4'
    +
    +   WARNING:
    +     incorrect range if qh_RANDOMmax is defined wrong (user_r.h)
    +*/
    +
    +#include "libqhull_r.h"  /* First for user_r.h */
    +#include "random_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ */
    +#pragma warning( disable : 4706)  /* assignment within conditional expression. */
    +#pragma warning( disable : 4996)  /* this function (strncat,sprintf,strcpy) or variable may be unsafe. */
    +#endif
    +
    +#define MAXdim 200
    +#define PI 3.1415926535897932384
    +
    +/* ------------------------------ prototypes ----------------*/
    +int qh_roundi(qhT *qh, double a);
    +void qh_out1(qhT *qh, double a);
    +void qh_out2n(qhT *qh, double a, double b);
    +void qh_out3n(qhT *qh, double a, double b, double c);
    +void qh_outcoord(qhT *qh, int iscdd, double *coord, int dim);
    +void qh_outcoincident(qhT *qh, int coincidentpoints, double radius, int iscdd, double *coord, int dim);
    +
    +void    qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +int     qh_rand(qhT *qh);
    +void    qh_srand(qhT *qh, int seed);
    +
    +/*---------------------------------
    +
    +  qh_rboxpoints(qh, rbox_command )
    +    Generate points to qh->fout according to rbox options
    +    Report errors on qh->ferr
    +
    +  returns:
    +    0 (qh_ERRnone) on success
    +    1 (qh_ERRinput) on input error
    +    4 (qh_ERRmem) on memory error
    +    5 (qh_ERRqhull) on internal error
    +
    +  notes:
    +    To avoid using stdio, redefine qh_malloc, qh_free, and qh_fprintf_rbox (user_r.c)
    +
    +  design:
    +    Straight line code (consider defining a struct and functions):
    +
    +    Parse arguments into variables
    +    Determine the number of points
    +    Generate the points
    +*/
    +int qh_rboxpoints(qhT *qh, char* rbox_command) {
    +  int i,j,k;
    +  int gendim;
    +  int coincidentcount=0, coincidenttotal=0, coincidentpoints=0;
    +  int cubesize, diamondsize, seed=0, count, apex;
    +  int dim=3 , numpoints= 0, totpoints, addpoints=0;
    +  int issphere=0, isaxis=0,  iscdd= 0, islens= 0, isregular=0, iswidth=0, addcube=0;
    +  int isgap=0, isspiral=0, NOcommand= 0, adddiamond=0;
    +  int israndom=0, istime=0;
    +  int isbox=0, issimplex=0, issimplex2=0, ismesh=0;
    +  double width=0.0, gap=0.0, radius=0.0, coincidentradius=0.0;
    +  double coord[MAXdim], offset, meshm=3.0, meshn=4.0, meshr=5.0;
    +  double *coordp, *simplex= NULL, *simplexp;
    +  int nthroot, mult[MAXdim];
    +  double norm, factor, randr, rangap, lensangle= 0, lensbase= 1;
    +  double anglediff, angle, x, y, cube= 0.0, diamond= 0.0;
    +  double box= qh_DEFAULTbox; /* scale all numbers before output */
    +  double randmax= qh_RANDOMmax;
    +  char command[200], seedbuf[200];
    +  char *s= command, *t, *first_point= NULL;
    +  time_t timedata;
    +  int exitcode;
    +
    +  exitcode= setjmp(qh->rbox_errexit);
    +  if (exitcode) {
    +    /* same code for error exit and normal return, qh->NOerrexit is set */
    +    if (simplex)
    +        qh_free(simplex);
    +    return exitcode;
    +  }
    +
    +  *command= '\0';
    +  strncat(command, rbox_command, sizeof(command)-strlen(command)-1);
    +
    +  while (*s && !isspace(*s))  /* skip program name */
    +    s++;
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    if (isdigit(*s)) {
    +      numpoints= qh_strtol(s, &s);
    +      continue;
    +    }
    +    /* ============= read flags =============== */
    +    switch (*s++) {
    +    case 'c':
    +      addcube= 1;
    +      t= s;
    +      while (isspace(*t))
    +        t++;
    +      if (*t == 'G')
    +        cube= qh_strtod(++t, &s);
    +      break;
    +    case 'd':
    +      adddiamond= 1;
    +      t= s;
    +      while (isspace(*t))
    +        t++;
    +      if (*t == 'G')
    +        diamond= qh_strtod(++t, &s);
    +      break;
    +    case 'h':
    +      iscdd= 1;
    +      break;
    +    case 'l':
    +      isspiral= 1;
    +      break;
    +    case 'n':
    +      NOcommand= 1;
    +      break;
    +    case 'r':
    +      isregular= 1;
    +      break;
    +    case 's':
    +      issphere= 1;
    +      break;
    +    case 't':
    +      istime= 1;
    +      if (isdigit(*s)) {
    +        seed= qh_strtol(s, &s);
    +        israndom= 0;
    +      }else
    +        israndom= 1;
    +      break;
    +    case 'x':
    +      issimplex= 1;
    +      break;
    +    case 'y':
    +      issimplex2= 1;
    +      break;
    +    case 'z':
    +      qh->rbox_isinteger= 1;
    +      break;
    +    case 'B':
    +      box= qh_strtod(s, &s);
    +      isbox= 1;
    +      break;
    +    case 'C':
    +      if (*s)
    +        coincidentpoints=  qh_strtol(s, &s);
    +      if (*s == ',') {
    +        ++s;
    +        coincidentradius=  qh_strtod(s, &s);
    +      }
    +      if (*s == ',') {
    +        ++s;
    +        coincidenttotal=  qh_strtol(s, &s);
    +      }
    +      if (*s && !isspace(*s)) {
    +        qh_fprintf_rbox(qh, qh->ferr, 7080, "rbox error: arguments for 'Cn,r,m' are not 'int', 'float', and 'int'.  Remaining string is '%s'\n", s);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      if (coincidentpoints==0){
    +        qh_fprintf_rbox(qh, qh->ferr, 6268, "rbox error: missing arguments for 'Cn,r,m' where n is the number of coincident points, r is the radius (default 0.0), and m is the number of points\n");
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      if (coincidentpoints<0 || coincidenttotal<0 || coincidentradius<0.0){
    +        qh_fprintf_rbox(qh, qh->ferr, 6269, "rbox error: negative arguments for 'Cn,m,r' where n (%d) is the number of coincident points, m (%d) is the number of points, and r (%.2g) is the radius (default 0.0)\n", coincidentpoints, coincidenttotal, coincidentradius);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      break;
    +    case 'D':
    +      dim= qh_strtol(s, &s);
    +      if (dim < 1
    +      || dim > MAXdim) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6189, "rbox error: dimension, D%d, out of bounds (>=%d or <=0)", dim, MAXdim);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      break;
    +    case 'G':
    +      if (isdigit(*s))
    +        gap= qh_strtod(s, &s);
    +      else
    +        gap= 0.5;
    +      isgap= 1;
    +      break;
    +    case 'L':
    +      if (isdigit(*s))
    +        radius= qh_strtod(s, &s);
    +      else
    +        radius= 10;
    +      islens= 1;
    +      break;
    +    case 'M':
    +      ismesh= 1;
    +      if (*s)
    +        meshn= qh_strtod(s, &s);
    +      if (*s == ',') {
    +        ++s;
    +        meshm= qh_strtod(s, &s);
    +      }else
    +        meshm= 0.0;
    +      if (*s == ',') {
    +        ++s;
    +        meshr= qh_strtod(s, &s);
    +      }else
    +        meshr= sqrt(meshn*meshn + meshm*meshm);
    +      if (*s && !isspace(*s)) {
    +        qh_fprintf_rbox(qh, qh->ferr, 7069, "rbox warning: assuming 'M3,4,5' since mesh args are not integers or reals\n");
    +        meshn= 3.0, meshm=4.0, meshr=5.0;
    +      }
    +      break;
    +    case 'O':
    +      qh->rbox_out_offset= qh_strtod(s, &s);
    +      break;
    +    case 'P':
    +      if (!first_point)
    +        first_point= s-1;
    +      addpoints++;
    +      while (*s && !isspace(*s))   /* read points later */
    +        s++;
    +      break;
    +    case 'W':
    +      width= qh_strtod(s, &s);
    +      iswidth= 1;
    +      break;
    +    case 'Z':
    +      if (isdigit(*s))
    +        radius= qh_strtod(s, &s);
    +      else
    +        radius= 1.0;
    +      isaxis= 1;
    +      break;
    +    default:
    +      qh_fprintf_rbox(qh, qh->ferr, 7070, "rbox error: unknown flag at %s.\nExecute 'rbox' without arguments for documentation.\n", s);
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    if (*s && !isspace(*s)) {
    +      qh_fprintf_rbox(qh, qh->ferr, 7071, "rbox error: missing space between flags at %s.\n", s);
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +  }
    +
    +  /* ============= defaults, constants, and sizes =============== */
    +  if (qh->rbox_isinteger && !isbox)
    +    box= qh_DEFAULTzbox;
    +  if (addcube) {
    +    cubesize= (int)floor(ldexp(1.0,dim)+0.5);
    +    if (cube == 0.0)
    +      cube= box;
    +  }else
    +    cubesize= 0;
    +  if (adddiamond) {
    +    diamondsize= 2*dim;
    +    if (diamond == 0.0)
    +      diamond= box;
    +  }else
    +    diamondsize= 0;
    +  if (islens) {
    +    if (isaxis) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6190, "rbox error: can not combine 'Ln' with 'Zn'\n");
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    if (radius <= 1.0) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6191, "rbox error: lens radius %.2g should be greater than 1.0\n",
    +               radius);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    lensangle= asin(1.0/radius);
    +    lensbase= radius * cos(lensangle);
    +  }
    +
    +  if (!numpoints) {
    +    if (issimplex2)
    +        ; /* ok */
    +    else if (isregular + issimplex + islens + issphere + isaxis + isspiral + iswidth + ismesh) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6192, "rbox error: missing count\n");
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +    }else if (adddiamond + addcube + addpoints)
    +        ; /* ok */
    +    else {
    +        numpoints= 50;  /* ./rbox D4 is the test case */
    +        issphere= 1;
    +    }
    +  }
    +  if ((issimplex + islens + isspiral + ismesh > 1)
    +  || (issimplex + issphere + isspiral + ismesh > 1)) {
    +    qh_fprintf_rbox(qh, qh->ferr, 6193, "rbox error: can only specify one of 'l', 's', 'x', 'Ln', or 'Mn,m,r' ('Ln s' is ok).\n");
    +    qh_errexit_rbox(qh, qh_ERRinput);
    +  }
    +  if (coincidentpoints>0 && (numpoints == 0 || coincidenttotal > numpoints)) {
    +    qh_fprintf_rbox(qh, qh->ferr, 6270, "rbox error: 'Cn,r,m' requested n coincident points for each of m points.  Either there is no points or m (%d) is greater than the number of points (%d).\n", coincidenttotal, numpoints);
    +    qh_errexit_rbox(qh, qh_ERRinput);
    +  }
    +  if (coincidenttotal == 0)
    +    coincidenttotal= numpoints;
    +
    +  /* ============= print header with total points =============== */
    +  if (issimplex || ismesh)
    +    totpoints= numpoints;
    +  else if (issimplex2)
    +    totpoints= numpoints+dim+1;
    +  else if (isregular) {
    +    totpoints= numpoints;
    +    if (dim == 2) {
    +        if (islens)
    +          totpoints += numpoints - 2;
    +    }else if (dim == 3) {
    +        if (islens)
    +          totpoints += 2 * numpoints;
    +      else if (isgap)
    +        totpoints += 1 + numpoints;
    +      else
    +        totpoints += 2;
    +    }
    +  }else
    +    totpoints= numpoints + isaxis;
    +  totpoints += cubesize + diamondsize + addpoints;
    +  totpoints += coincidentpoints*coincidenttotal;
    +
    +  /* ============= seed randoms =============== */
    +  if (istime == 0) {
    +    for (s=command; *s; s++) {
    +      if (issimplex2 && *s == 'y') /* make 'y' same seed as 'x' */
    +        i= 'x';
    +      else
    +        i= *s;
    +      seed= 11*seed + i;
    +    }
    +  }else if (israndom) {
    +    seed= (int)time(&timedata);
    +    sprintf(seedbuf, " t%d", seed);  /* appends an extra t, not worth removing */
    +    strncat(command, seedbuf, sizeof(command)-strlen(command)-1);
    +    t= strstr(command, " t ");
    +    if (t)
    +      strcpy(t+1, t+3); /* remove " t " */
    +  } /* else, seed explicitly set to n */
    +  qh_RANDOMseed_(qh, seed);
    +
    +  /* ============= print header =============== */
    +
    +  if (iscdd)
    +      qh_fprintf_rbox(qh, qh->fout, 9391, "%s\nbegin\n        %d %d %s\n",
    +      NOcommand ? "" : command,
    +      totpoints, dim+1,
    +      qh->rbox_isinteger ? "integer" : "real");
    +  else if (NOcommand)
    +      qh_fprintf_rbox(qh, qh->fout, 9392, "%d\n%d\n", dim, totpoints);
    +  else
    +      /* qh_fprintf_rbox special cases 9393 to append 'command' to the RboxPoints.comment() */
    +      qh_fprintf_rbox(qh, qh->fout, 9393, "%d %s\n%d\n", dim, command, totpoints);
    +
    +  /* ============= explicit points =============== */
    +  if ((s= first_point)) {
    +    while (s && *s) { /* 'P' */
    +      count= 0;
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      while (*++s) {
    +        qh_out1(qh, qh_strtod(s, &s));
    +        count++;
    +        if (isspace(*s) || !*s)
    +          break;
    +        if (*s != ',') {
    +          qh_fprintf_rbox(qh, qh->ferr, 6194, "rbox error: missing comma after coordinate in %s\n\n", s);
    +          qh_errexit_rbox(qh, qh_ERRinput);
    +        }
    +      }
    +      if (count < dim) {
    +        for (k=dim-count; k--; )
    +          qh_out1(qh, 0.0);
    +      }else if (count > dim) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6195, "rbox error: %d coordinates instead of %d coordinates in %s\n\n",
    +                  count, dim, s);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      qh_fprintf_rbox(qh, qh->fout, 9394, "\n");
    +      while ((s= strchr(s, 'P'))) {
    +        if (isspace(s[-1]))
    +          break;
    +      }
    +    }
    +  }
    +
    +  /* ============= simplex distribution =============== */
    +  if (issimplex+issimplex2) {
    +    if (!(simplex= (double*)qh_malloc( dim * (dim+1) * sizeof(double)))) {
    +      qh_fprintf_rbox(qh, qh->ferr, 6196, "rbox error: insufficient memory for simplex\n");
    +      qh_errexit_rbox(qh, qh_ERRmem); /* qh_ERRmem */
    +    }
    +    simplexp= simplex;
    +    if (isregular) {
    +      for (i=0; ifout, 9395, "\n");
    +      }
    +    }
    +    for (j=0; jferr, 6197, "rbox error: regular points can be used only in 2-d and 3-d\n\n");
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    if (!isaxis || radius == 0.0) {
    +      isaxis= 1;
    +      radius= 1.0;
    +    }
    +    if (dim == 3) {
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out3n(qh, 0.0, 0.0, -box);
    +      if (!isgap) {
    +        if (iscdd)
    +          qh_out1(qh, 1.0);
    +        qh_out3n(qh, 0.0, 0.0, box);
    +      }
    +    }
    +    angle= 0.0;
    +    anglediff= 2.0 * PI/numpoints;
    +    for (i=0; i < numpoints; i++) {
    +      angle += anglediff;
    +      x= radius * cos(angle);
    +      y= radius * sin(angle);
    +      if (dim == 2) {
    +        if (iscdd)
    +          qh_out1(qh, 1.0);
    +        qh_out2n(qh, x*box, y*box);
    +      }else {
    +        norm= sqrt(1.0 + x*x + y*y);
    +        if (iscdd)
    +          qh_out1(qh, 1.0);
    +        qh_out3n(qh, box*x/norm, box*y/norm, box/norm);
    +        if (isgap) {
    +          x *= 1-gap;
    +          y *= 1-gap;
    +          norm= sqrt(1.0 + x*x + y*y);
    +          if (iscdd)
    +            qh_out1(qh, 1.0);
    +          qh_out3n(qh, box*x/norm, box*y/norm, box/norm);
    +        }
    +      }
    +    }
    +  }
    +  /* ============= regular points for 'r Ln D2' =============== */
    +  else if (isregular && islens && dim == 2) {
    +    double cos_0;
    +
    +    angle= lensangle;
    +    anglediff= 2 * lensangle/(numpoints - 1);
    +    cos_0= cos(lensangle);
    +    for (i=0; i < numpoints; i++, angle -= anglediff) {
    +      x= radius * sin(angle);
    +      y= radius * (cos(angle) - cos_0);
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out2n(qh, x*box, y*box);
    +      if (i != 0 && i != numpoints - 1) {
    +        if (iscdd)
    +          qh_out1(qh, 1.0);
    +        qh_out2n(qh, x*box, -y*box);
    +      }
    +    }
    +  }
    +  /* ============= regular points for 'r Ln D3' =============== */
    +  else if (isregular && islens && dim != 2) {
    +    if (dim != 3) {
    +      qh_free(simplex);
    +      qh_fprintf_rbox(qh, qh->ferr, 6198, "rbox error: regular points can be used only in 2-d and 3-d\n\n");
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    angle= 0.0;
    +    anglediff= 2* PI/numpoints;
    +    if (!isgap) {
    +      isgap= 1;
    +      gap= 0.5;
    +    }
    +    offset= sqrt(radius * radius - (1-gap)*(1-gap)) - lensbase;
    +    for (i=0; i < numpoints; i++, angle += anglediff) {
    +      x= cos(angle);
    +      y= sin(angle);
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out3n(qh, box*x, box*y, 0.0);
    +      x *= 1-gap;
    +      y *= 1-gap;
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out3n(qh, box*x, box*y, box * offset);
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out3n(qh, box*x, box*y, -box * offset);
    +    }
    +  }
    +  /* ============= apex of 'Zn' distribution + gendim =============== */
    +  else {
    +    if (isaxis) {
    +      gendim= dim-1;
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      for (j=0; j < gendim; j++)
    +        qh_out1(qh, 0.0);
    +      qh_out1(qh, -box);
    +      qh_fprintf_rbox(qh, qh->fout, 9398, "\n");
    +    }else if (islens)
    +      gendim= dim-1;
    +    else
    +      gendim= dim;
    +    /* ============= generate random point in unit cube =============== */
    +    for (i=0; i < numpoints; i++) {
    +      norm= 0.0;
    +      for (j=0; j < gendim; j++) {
    +        randr= qh_RANDOMint;
    +        coord[j]= 2.0 * randr/randmax - 1.0;
    +        norm += coord[j] * coord[j];
    +      }
    +      norm= sqrt(norm);
    +      /* ============= dim-1 point of 'Zn' distribution ========== */
    +      if (isaxis) {
    +        if (!isgap) {
    +          isgap= 1;
    +          gap= 1.0;
    +        }
    +        randr= qh_RANDOMint;
    +        rangap= 1.0 - gap * randr/randmax;
    +        factor= radius * rangap / norm;
    +        for (j=0; jferr, 6199, "rbox error: spiral distribution is available only in 3d\n\n");
    +          qh_errexit_rbox(qh, qh_ERRinput);
    +        }
    +        coord[0]= cos(2*PI*i/(numpoints - 1));
    +        coord[1]= sin(2*PI*i/(numpoints - 1));
    +        coord[2]= 2.0*(double)i/(double)(numpoints-1) - 1.0;
    +      /* ============= point of 's' distribution =============== */
    +      }else if (issphere) {
    +        factor= 1.0/norm;
    +        if (iswidth) {
    +          randr= qh_RANDOMint;
    +          factor *= 1.0 - width * randr/randmax;
    +        }
    +        for (j=0; j randmax/2)
    +          coord[dim-1]= -coord[dim-1];
    +      /* ============= project 'Wn' point toward boundary =============== */
    +      }else if (iswidth && !issphere) {
    +        j= qh_RANDOMint % gendim;
    +        if (coord[j] < 0)
    +          coord[j]= -1.0 - coord[j] * width;
    +        else
    +          coord[j]= 1.0 - coord[j] * width;
    +      }
    +      /* ============= scale point to box =============== */
    +      for (k=0; k=0; k--) {
    +        if (j & ( 1 << k))
    +          qh_out1(qh, cube);
    +        else
    +          qh_out1(qh, -cube);
    +      }
    +      qh_fprintf_rbox(qh, qh->fout, 9400, "\n");
    +    }
    +  }
    +
    +  /* ============= write diamond vertices =============== */
    +  if (adddiamond) {
    +    for (j=0; j=0; k--) {
    +        if (j/2 != k)
    +          qh_out1(qh, 0.0);
    +        else if (j & 0x1)
    +          qh_out1(qh, diamond);
    +        else
    +          qh_out1(qh, -diamond);
    +      }
    +      qh_fprintf_rbox(qh, qh->fout, 9401, "\n");
    +    }
    +  }
    +
    +  if (iscdd)
    +    qh_fprintf_rbox(qh, qh->fout, 9402, "end\nhull\n");
    +
    +  /* same code for error exit and normal return */
    +  qh_free(simplex);
    +  return qh_ERRnone;
    +} /* rboxpoints */
    +
    +/*------------------------------------------------
    +outxxx - output functions for qh_rboxpoints
    +*/
    +int qh_roundi(qhT *qh, double a) {
    +  if (a < 0.0) {
    +    if (a - 0.5 < INT_MIN) {
    +      qh_fprintf_rbox(qh, qh->ferr, 6200, "rbox input error: negative coordinate %2.2g is too large.  Reduce 'Bn'\n", a);
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    return (int)(a - 0.5);
    +  }else {
    +    if (a + 0.5 > INT_MAX) {
    +      qh_fprintf_rbox(qh, qh->ferr, 6201, "rbox input error: coordinate %2.2g is too large.  Reduce 'Bn'\n", a);
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    return (int)(a + 0.5);
    +  }
    +} /* qh_roundi */
    +
    +void qh_out1(qhT *qh, double a) {
    +
    +  if (qh->rbox_isinteger)
    +    qh_fprintf_rbox(qh, qh->fout, 9403, "%d ", qh_roundi(qh, a+qh->rbox_out_offset));
    +  else
    +    qh_fprintf_rbox(qh, qh->fout, 9404, qh_REAL_1, a+qh->rbox_out_offset);
    +} /* qh_out1 */
    +
    +void qh_out2n(qhT *qh, double a, double b) {
    +
    +  if (qh->rbox_isinteger)
    +    qh_fprintf_rbox(qh, qh->fout, 9405, "%d %d\n", qh_roundi(qh, a+qh->rbox_out_offset), qh_roundi(qh, b+qh->rbox_out_offset));
    +  else
    +    qh_fprintf_rbox(qh, qh->fout, 9406, qh_REAL_2n, a+qh->rbox_out_offset, b+qh->rbox_out_offset);
    +} /* qh_out2n */
    +
    +void qh_out3n(qhT *qh, double a, double b, double c) {
    +
    +  if (qh->rbox_isinteger)
    +    qh_fprintf_rbox(qh, qh->fout, 9407, "%d %d %d\n", qh_roundi(qh, a+qh->rbox_out_offset), qh_roundi(qh, b+qh->rbox_out_offset), qh_roundi(qh, c+qh->rbox_out_offset));
    +  else
    +    qh_fprintf_rbox(qh, qh->fout, 9408, qh_REAL_3n, a+qh->rbox_out_offset, b+qh->rbox_out_offset, c+qh->rbox_out_offset);
    +} /* qh_out3n */
    +
    +void qh_outcoord(qhT *qh, int iscdd, double *coord, int dim) {
    +    double *p= coord;
    +    int k;
    +
    +    if (iscdd)
    +      qh_out1(qh, 1.0);
    +    for (k=0; k < dim; k++)
    +      qh_out1(qh, *(p++));
    +    qh_fprintf_rbox(qh, qh->fout, 9396, "\n");
    +} /* qh_outcoord */
    +
    +void qh_outcoincident(qhT *qh, int coincidentpoints, double radius, int iscdd, double *coord, int dim) {
    +  double *p;
    +  double randr, delta;
    +  int i,k;
    +  double randmax= qh_RANDOMmax;
    +
    +  for (i= 0; ifout, 9410, "\n");
    +  }
    +} /* qh_outcoincident */
    +
    +/*------------------------------------------------
    +   Only called from qh_rboxpoints or qh_fprintf_rbox
    +   qh_fprintf_rbox is only called from qh_rboxpoints
    +*/
    +void qh_errexit_rbox(qhT *qh, int exitcode)
    +{
    +    longjmp(qh->rbox_errexit, exitcode);
    +} /* qh_errexit_rbox */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/stat_r.c b/xs/src/qhull/src/libqhull_r/stat_r.c
    new file mode 100644
    index 0000000000..0f3ff0d3d4
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/stat_r.c
    @@ -0,0 +1,682 @@
    +/*
      ---------------------------------
    +
    +   stat_r.c
    +   contains all statistics that are collected for qhull
    +
    +   see qh-stat_r.htm and stat_r.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/stat_r.c#5 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*========== functions in alphabetic order ================*/
    +
    +/*---------------------------------
    +
    +  qh_allstatA()
    +    define statistics in groups of 20
    +
    +  notes:
    +    (otherwise, 'gcc -O2' uses too much memory)
    +    uses qhstat.next
    +*/
    +void qh_allstatA(qhT *qh) {
    +
    +   /* zdef_(type,name,doc,average) */
    +  zzdef_(zdoc, Zdoc2, "precision statistics", -1);
    +  zdef_(zinc, Znewvertex, NULL, -1);
    +  zdef_(wadd, Wnewvertex, "ave. distance of a new vertex to a facet(!0s)", Znewvertex);
    +  zzdef_(wmax, Wnewvertexmax, "max. distance of a new vertex to a facet", -1);
    +  zdef_(wmax, Wvertexmax, "max. distance of an output vertex to a facet", -1);
    +  zdef_(wmin, Wvertexmin, "min. distance of an output vertex to a facet", -1);
    +  zdef_(wmin, Wmindenom, "min. denominator in hyperplane computation", -1);
    +
    +  qh->qhstat.precision= qh->qhstat.next;  /* call qh_precision for each of these */
    +  zzdef_(zdoc, Zdoc3, "precision problems (corrected unless 'Q0' or an error)", -1);
    +  zzdef_(zinc, Zcoplanarridges, "coplanar half ridges in output", -1);
    +  zzdef_(zinc, Zconcaveridges, "concave half ridges in output", -1);
    +  zzdef_(zinc, Zflippedfacets, "flipped facets", -1);
    +  zzdef_(zinc, Zcoplanarhorizon, "coplanar horizon facets for new vertices", -1);
    +  zzdef_(zinc, Zcoplanarpart, "coplanar points during partitioning", -1);
    +  zzdef_(zinc, Zminnorm, "degenerate hyperplanes recomputed with gaussian elimination", -1);
    +  zzdef_(zinc, Znearlysingular, "nearly singular or axis-parallel hyperplanes", -1);
    +  zzdef_(zinc, Zback0, "zero divisors during back substitute", -1);
    +  zzdef_(zinc, Zgauss0, "zero divisors during gaussian elimination", -1);
    +  zzdef_(zinc, Zmultiridge, "ridges with multiple neighbors", -1);
    +}
    +void qh_allstatB(qhT *qh) {
    +  zzdef_(zdoc, Zdoc1, "summary information", -1);
    +  zdef_(zinc, Zvertices, "number of vertices in output", -1);
    +  zdef_(zinc, Znumfacets, "number of facets in output", -1);
    +  zdef_(zinc, Znonsimplicial, "number of non-simplicial facets in output", -1);
    +  zdef_(zinc, Znowsimplicial, "number of simplicial facets that were merged", -1);
    +  zdef_(zinc, Znumridges, "number of ridges in output", -1);
    +  zdef_(zadd, Znumridges, "average number of ridges per facet", Znumfacets);
    +  zdef_(zmax, Zmaxridges, "maximum number of ridges", -1);
    +  zdef_(zadd, Znumneighbors, "average number of neighbors per facet", Znumfacets);
    +  zdef_(zmax, Zmaxneighbors, "maximum number of neighbors", -1);
    +  zdef_(zadd, Znumvertices, "average number of vertices per facet", Znumfacets);
    +  zdef_(zmax, Zmaxvertices, "maximum number of vertices", -1);
    +  zdef_(zadd, Znumvneighbors, "average number of neighbors per vertex", Zvertices);
    +  zdef_(zmax, Zmaxvneighbors, "maximum number of neighbors", -1);
    +  zdef_(wadd, Wcpu, "cpu seconds for qhull after input", -1);
    +  zdef_(zinc, Ztotvertices, "vertices created altogether", -1);
    +  zzdef_(zinc, Zsetplane, "facets created altogether", -1);
    +  zdef_(zinc, Ztotridges, "ridges created altogether", -1);
    +  zdef_(zinc, Zpostfacets, "facets before post merge", -1);
    +  zdef_(zadd, Znummergetot, "average merges per facet(at most 511)", Znumfacets);
    +  zdef_(zmax, Znummergemax, "  maximum merges for a facet(at most 511)", -1);
    +  zdef_(zinc, Zangle, NULL, -1);
    +  zdef_(wadd, Wangle, "average angle(cosine) of facet normals for all ridges", Zangle);
    +  zdef_(wmax, Wanglemax, "  maximum angle(cosine) of facet normals across a ridge", -1);
    +  zdef_(wmin, Wanglemin, "  minimum angle(cosine) of facet normals across a ridge", -1);
    +  zdef_(wadd, Wareatot, "total area of facets", -1);
    +  zdef_(wmax, Wareamax, "  maximum facet area", -1);
    +  zdef_(wmin, Wareamin, "  minimum facet area", -1);
    +}
    +void qh_allstatC(qhT *qh) {
    +  zdef_(zdoc, Zdoc9, "build hull statistics", -1);
    +  zzdef_(zinc, Zprocessed, "points processed", -1);
    +  zzdef_(zinc, Zretry, "retries due to precision problems", -1);
    +  zdef_(wmax, Wretrymax, "  max. random joggle", -1);
    +  zdef_(zmax, Zmaxvertex, "max. vertices at any one time", -1);
    +  zdef_(zinc, Ztotvisible, "ave. visible facets per iteration", Zprocessed);
    +  zdef_(zinc, Zinsidevisible, "  ave. visible facets without an horizon neighbor", Zprocessed);
    +  zdef_(zadd, Zvisfacettot,  "  ave. facets deleted per iteration", Zprocessed);
    +  zdef_(zmax, Zvisfacetmax,  "    maximum", -1);
    +  zdef_(zadd, Zvisvertextot, "ave. visible vertices per iteration", Zprocessed);
    +  zdef_(zmax, Zvisvertexmax, "    maximum", -1);
    +  zdef_(zinc, Ztothorizon, "ave. horizon facets per iteration", Zprocessed);
    +  zdef_(zadd, Znewfacettot,  "ave. new or merged facets per iteration", Zprocessed);
    +  zdef_(zmax, Znewfacetmax,  "    maximum(includes initial simplex)", -1);
    +  zdef_(wadd, Wnewbalance, "average new facet balance", Zprocessed);
    +  zdef_(wadd, Wnewbalance2, "  standard deviation", -1);
    +  zdef_(wadd, Wpbalance, "average partition balance", Zpbalance);
    +  zdef_(wadd, Wpbalance2, "  standard deviation", -1);
    +  zdef_(zinc, Zpbalance, "  number of trials", -1);
    +  zdef_(zinc, Zsearchpoints, "searches of all points for initial simplex", -1);
    +  zdef_(zinc, Zdetsimplex, "determinants computed(area & initial hull)", -1);
    +  zdef_(zinc, Znoarea, "determinants not computed because vertex too low", -1);
    +  zdef_(zinc, Znotmax, "points ignored(!above max_outside)", -1);
    +  zdef_(zinc, Znotgood, "points ignored(!above a good facet)", -1);
    +  zdef_(zinc, Znotgoodnew, "points ignored(didn't create a good new facet)", -1);
    +  zdef_(zinc, Zgoodfacet, "good facets found", -1);
    +  zzdef_(zinc, Znumvisibility, "distance tests for facet visibility", -1);
    +  zdef_(zinc, Zdistvertex, "distance tests to report minimum vertex", -1);
    +  zzdef_(zinc, Ztotcheck, "points checked for facets' outer planes", -1);
    +  zzdef_(zinc, Zcheckpart, "  ave. distance tests per check", Ztotcheck);
    +}
    +void qh_allstatD(qhT *qh) {
    +  zdef_(zinc, Zvisit, "resets of visit_id", -1);
    +  zdef_(zinc, Zvvisit, "  resets of vertex_visit", -1);
    +  zdef_(zmax, Zvisit2max, "  max visit_id/2", -1);
    +  zdef_(zmax, Zvvisit2max, "  max vertex_visit/2", -1);
    +
    +  zdef_(zdoc, Zdoc4, "partitioning statistics(see previous for outer planes)", -1);
    +  zzdef_(zadd, Zdelvertextot, "total vertices deleted", -1);
    +  zdef_(zmax, Zdelvertexmax, "    maximum vertices deleted per iteration", -1);
    +  zdef_(zinc, Zfindbest, "calls to findbest", -1);
    +  zdef_(zadd, Zfindbesttot, " ave. facets tested", Zfindbest);
    +  zdef_(zmax, Zfindbestmax, " max. facets tested", -1);
    +  zdef_(zadd, Zfindcoplanar, " ave. coplanar search", Zfindbest);
    +  zdef_(zinc, Zfindnew, "calls to findbestnew", -1);
    +  zdef_(zadd, Zfindnewtot, " ave. facets tested", Zfindnew);
    +  zdef_(zmax, Zfindnewmax, " max. facets tested", -1);
    +  zdef_(zinc, Zfindnewjump, " ave. clearly better", Zfindnew);
    +  zdef_(zinc, Zfindnewsharp, " calls due to qh_sharpnewfacets", -1);
    +  zdef_(zinc, Zfindhorizon, "calls to findhorizon", -1);
    +  zdef_(zadd, Zfindhorizontot, " ave. facets tested", Zfindhorizon);
    +  zdef_(zmax, Zfindhorizonmax, " max. facets tested", -1);
    +  zdef_(zinc, Zfindjump,       " ave. clearly better", Zfindhorizon);
    +  zdef_(zinc, Zparthorizon, " horizon facets better than bestfacet", -1);
    +  zdef_(zinc, Zpartangle, "angle tests for repartitioned coplanar points", -1);
    +  zdef_(zinc, Zpartflip, "  repartitioned coplanar points for flipped orientation", -1);
    +}
    +void qh_allstatE(qhT *qh) {
    +  zdef_(zinc, Zpartinside, "inside points", -1);
    +  zdef_(zinc, Zpartnear, "  inside points kept with a facet", -1);
    +  zdef_(zinc, Zcoplanarinside, "  inside points that were coplanar with a facet", -1);
    +  zdef_(zinc, Zbestlower, "calls to findbestlower", -1);
    +  zdef_(zinc, Zbestlowerv, "  with search of vertex neighbors", -1);
    +  zdef_(zinc, Zbestlowerall, "  with rare search of all facets", -1);
    +  zdef_(zmax, Zbestloweralln, "  facets per search of all facets", -1);
    +  zdef_(wadd, Wmaxout, "difference in max_outside at final check", -1);
    +  zzdef_(zinc, Zpartitionall, "distance tests for initial partition", -1);
    +  zdef_(zinc, Ztotpartition, "partitions of a point", -1);
    +  zzdef_(zinc, Zpartition, "distance tests for partitioning", -1);
    +  zzdef_(zinc, Zdistcheck, "distance tests for checking flipped facets", -1);
    +  zzdef_(zinc, Zdistconvex, "distance tests for checking convexity", -1);
    +  zdef_(zinc, Zdistgood, "distance tests for checking good point", -1);
    +  zdef_(zinc, Zdistio, "distance tests for output", -1);
    +  zdef_(zinc, Zdiststat, "distance tests for statistics", -1);
    +  zdef_(zinc, Zdistplane, "total number of distance tests", -1);
    +  zdef_(zinc, Ztotpartcoplanar, "partitions of coplanar points or deleted vertices", -1);
    +  zzdef_(zinc, Zpartcoplanar, "   distance tests for these partitions", -1);
    +  zdef_(zinc, Zcomputefurthest, "distance tests for computing furthest", -1);
    +}
    +void qh_allstatE2(qhT *qh) {
    +  zdef_(zdoc, Zdoc5, "statistics for matching ridges", -1);
    +  zdef_(zinc, Zhashlookup, "total lookups for matching ridges of new facets", -1);
    +  zdef_(zinc, Zhashtests, "average number of tests to match a ridge", Zhashlookup);
    +  zdef_(zinc, Zhashridge, "total lookups of subridges(duplicates and boundary)", -1);
    +  zdef_(zinc, Zhashridgetest, "average number of tests per subridge", Zhashridge);
    +  zdef_(zinc, Zdupsame, "duplicated ridges in same merge cycle", -1);
    +  zdef_(zinc, Zdupflip, "duplicated ridges with flipped facets", -1);
    +
    +  zdef_(zdoc, Zdoc6, "statistics for determining merges", -1);
    +  zdef_(zinc, Zangletests, "angles computed for ridge convexity", -1);
    +  zdef_(zinc, Zbestcentrum, "best merges used centrum instead of vertices",-1);
    +  zzdef_(zinc, Zbestdist, "distance tests for best merge", -1);
    +  zzdef_(zinc, Zcentrumtests, "distance tests for centrum convexity", -1);
    +  zzdef_(zinc, Zdistzero, "distance tests for checking simplicial convexity", -1);
    +  zdef_(zinc, Zcoplanarangle, "coplanar angles in getmergeset", -1);
    +  zdef_(zinc, Zcoplanarcentrum, "coplanar centrums in getmergeset", -1);
    +  zdef_(zinc, Zconcaveridge, "concave ridges in getmergeset", -1);
    +}
    +void qh_allstatF(qhT *qh) {
    +  zdef_(zdoc, Zdoc7, "statistics for merging", -1);
    +  zdef_(zinc, Zpremergetot, "merge iterations", -1);
    +  zdef_(zadd, Zmergeinittot, "ave. initial non-convex ridges per iteration", Zpremergetot);
    +  zdef_(zadd, Zmergeinitmax, "  maximum", -1);
    +  zdef_(zadd, Zmergesettot, "  ave. additional non-convex ridges per iteration", Zpremergetot);
    +  zdef_(zadd, Zmergesetmax, "  maximum additional in one pass", -1);
    +  zdef_(zadd, Zmergeinittot2, "initial non-convex ridges for post merging", -1);
    +  zdef_(zadd, Zmergesettot2, "  additional non-convex ridges", -1);
    +  zdef_(wmax, Wmaxoutside, "max distance of vertex or coplanar point above facet(w/roundoff)", -1);
    +  zdef_(wmin, Wminvertex, "max distance of merged vertex below facet(or roundoff)", -1);
    +  zdef_(zinc, Zwidefacet, "centrums frozen due to a wide merge", -1);
    +  zdef_(zinc, Zwidevertices, "centrums frozen due to extra vertices", -1);
    +  zzdef_(zinc, Ztotmerge, "total number of facets or cycles of facets merged", -1);
    +  zdef_(zinc, Zmergesimplex, "merged a simplex", -1);
    +  zdef_(zinc, Zonehorizon, "simplices merged into coplanar horizon", -1);
    +  zzdef_(zinc, Zcyclehorizon, "cycles of facets merged into coplanar horizon", -1);
    +  zzdef_(zadd, Zcyclefacettot, "  ave. facets per cycle", Zcyclehorizon);
    +  zdef_(zmax, Zcyclefacetmax, "  max. facets", -1);
    +  zdef_(zinc, Zmergeintohorizon, "new facets merged into horizon", -1);
    +  zdef_(zinc, Zmergenew, "new facets merged", -1);
    +  zdef_(zinc, Zmergehorizon, "horizon facets merged into new facets", -1);
    +  zdef_(zinc, Zmergevertex, "vertices deleted by merging", -1);
    +  zdef_(zinc, Zcyclevertex, "vertices deleted by merging into coplanar horizon", -1);
    +  zdef_(zinc, Zdegenvertex, "vertices deleted by degenerate facet", -1);
    +  zdef_(zinc, Zmergeflipdup, "merges due to flipped facets in duplicated ridge", -1);
    +  zdef_(zinc, Zneighbor, "merges due to redundant neighbors", -1);
    +  zdef_(zadd, Ztestvneighbor, "non-convex vertex neighbors", -1);
    +}
    +void qh_allstatG(qhT *qh) {
    +  zdef_(zinc, Zacoplanar, "merges due to angle coplanar facets", -1);
    +  zdef_(wadd, Wacoplanartot, "  average merge distance", Zacoplanar);
    +  zdef_(wmax, Wacoplanarmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zcoplanar, "merges due to coplanar facets", -1);
    +  zdef_(wadd, Wcoplanartot, "  average merge distance", Zcoplanar);
    +  zdef_(wmax, Wcoplanarmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zconcave, "merges due to concave facets", -1);
    +  zdef_(wadd, Wconcavetot, "  average merge distance", Zconcave);
    +  zdef_(wmax, Wconcavemax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zavoidold, "coplanar/concave merges due to avoiding old merge", -1);
    +  zdef_(wadd, Wavoidoldtot, "  average merge distance", Zavoidold);
    +  zdef_(wmax, Wavoidoldmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zdegen, "merges due to degenerate facets", -1);
    +  zdef_(wadd, Wdegentot, "  average merge distance", Zdegen);
    +  zdef_(wmax, Wdegenmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zflipped, "merges due to removing flipped facets", -1);
    +  zdef_(wadd, Wflippedtot, "  average merge distance", Zflipped);
    +  zdef_(wmax, Wflippedmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zduplicate, "merges due to duplicated ridges", -1);
    +  zdef_(wadd, Wduplicatetot, "  average merge distance", Zduplicate);
    +  zdef_(wmax, Wduplicatemax, "  maximum merge distance", -1);
    +}
    +void qh_allstatH(qhT *qh) {
    +  zdef_(zdoc, Zdoc8, "renamed vertex statistics", -1);
    +  zdef_(zinc, Zrenameshare, "renamed vertices shared by two facets", -1);
    +  zdef_(zinc, Zrenamepinch, "renamed vertices in a pinched facet", -1);
    +  zdef_(zinc, Zrenameall, "renamed vertices shared by multiple facets", -1);
    +  zdef_(zinc, Zfindfail, "rename failures due to duplicated ridges", -1);
    +  zdef_(zinc, Zdupridge, "  duplicate ridges detected", -1);
    +  zdef_(zinc, Zdelridge, "deleted ridges due to renamed vertices", -1);
    +  zdef_(zinc, Zdropneighbor, "dropped neighbors due to renamed vertices", -1);
    +  zdef_(zinc, Zdropdegen, "degenerate facets due to dropped neighbors", -1);
    +  zdef_(zinc, Zdelfacetdup, "  facets deleted because of no neighbors", -1);
    +  zdef_(zinc, Zremvertex, "vertices removed from facets due to no ridges", -1);
    +  zdef_(zinc, Zremvertexdel, "  deleted", -1);
    +  zdef_(zinc, Zintersectnum, "vertex intersections for locating redundant vertices", -1);
    +  zdef_(zinc, Zintersectfail, "intersections failed to find a redundant vertex", -1);
    +  zdef_(zinc, Zintersect, "intersections found redundant vertices", -1);
    +  zdef_(zadd, Zintersecttot, "   ave. number found per vertex", Zintersect);
    +  zdef_(zmax, Zintersectmax, "   max. found for a vertex", -1);
    +  zdef_(zinc, Zvertexridge, NULL, -1);
    +  zdef_(zadd, Zvertexridgetot, "  ave. number of ridges per tested vertex", Zvertexridge);
    +  zdef_(zmax, Zvertexridgemax, "  max. number of ridges per tested vertex", -1);
    +
    +  zdef_(zdoc, Zdoc10, "memory usage statistics(in bytes)", -1);
    +  zdef_(zadd, Zmemfacets, "for facets and their normals, neighbor and vertex sets", -1);
    +  zdef_(zadd, Zmemvertices, "for vertices and their neighbor sets", -1);
    +  zdef_(zadd, Zmempoints, "for input points, outside and coplanar sets, and qhT",-1);
    +  zdef_(zadd, Zmemridges, "for ridges and their vertex sets", -1);
    +} /* allstat */
    +
    +void qh_allstatI(qhT *qh) {
    +  qh->qhstat.vridges= qh->qhstat.next;
    +  zzdef_(zdoc, Zdoc11, "Voronoi ridge statistics", -1);
    +  zzdef_(zinc, Zridge, "non-simplicial Voronoi vertices for all ridges", -1);
    +  zzdef_(wadd, Wridge, "  ave. distance to ridge", Zridge);
    +  zzdef_(wmax, Wridgemax, "  max. distance to ridge", -1);
    +  zzdef_(zinc, Zridgemid, "bounded ridges", -1);
    +  zzdef_(wadd, Wridgemid, "  ave. distance of midpoint to ridge", Zridgemid);
    +  zzdef_(wmax, Wridgemidmax, "  max. distance of midpoint to ridge", -1);
    +  zzdef_(zinc, Zridgeok, "bounded ridges with ok normal", -1);
    +  zzdef_(wadd, Wridgeok, "  ave. angle to ridge", Zridgeok);
    +  zzdef_(wmax, Wridgeokmax, "  max. angle to ridge", -1);
    +  zzdef_(zinc, Zridge0, "bounded ridges with near-zero normal", -1);
    +  zzdef_(wadd, Wridge0, "  ave. angle to ridge", Zridge0);
    +  zzdef_(wmax, Wridge0max, "  max. angle to ridge", -1);
    +
    +  zdef_(zdoc, Zdoc12, "Triangulation statistics(Qt)", -1);
    +  zdef_(zinc, Ztricoplanar, "non-simplicial facets triangulated", -1);
    +  zdef_(zadd, Ztricoplanartot, "  ave. new facets created(may be deleted)", Ztricoplanar);
    +  zdef_(zmax, Ztricoplanarmax, "  max. new facets created", -1);
    +  zdef_(zinc, Ztrinull, "null new facets deleted(duplicated vertex)", -1);
    +  zdef_(zinc, Ztrimirror, "mirrored pairs of new facets deleted(same vertices)", -1);
    +  zdef_(zinc, Ztridegen, "degenerate new facets in output(same ridge)", -1);
    +} /* allstat */
    +
    +/*---------------------------------
    +
    +  qh_allstatistics()
    +    reset printed flag for all statistics
    +*/
    +void qh_allstatistics(qhT *qh) {
    +  int i;
    +
    +  for(i=ZEND; i--; )
    +    qh->qhstat.printed[i]= False;
    +} /* allstatistics */
    +
    +#if qh_KEEPstatistics
    +/*---------------------------------
    +
    +  qh_collectstatistics()
    +    collect statistics for qh.facet_list
    +
    +*/
    +void qh_collectstatistics(qhT *qh) {
    +  facetT *facet, *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  realT dotproduct, dist;
    +  int sizneighbors, sizridges, sizvertices, i;
    +
    +  qh->old_randomdist= qh->RANDOMdist;
    +  qh->RANDOMdist= False;
    +  zval_(Zmempoints)= qh->num_points * qh->normal_size + sizeof(qhT);
    +  zval_(Zmemfacets)= 0;
    +  zval_(Zmemridges)= 0;
    +  zval_(Zmemvertices)= 0;
    +  zval_(Zangle)= 0;
    +  wval_(Wangle)= 0.0;
    +  zval_(Znumridges)= 0;
    +  zval_(Znumfacets)= 0;
    +  zval_(Znumneighbors)= 0;
    +  zval_(Znumvertices)= 0;
    +  zval_(Znumvneighbors)= 0;
    +  zval_(Znummergetot)= 0;
    +  zval_(Znummergemax)= 0;
    +  zval_(Zvertices)= qh->num_vertices - qh_setsize(qh, qh->del_vertices);
    +  if (qh->MERGING || qh->APPROXhull || qh->JOGGLEmax < REALmax/2)
    +    wmax_(Wmaxoutside, qh->max_outside);
    +  if (qh->MERGING)
    +    wmin_(Wminvertex, qh->min_vertex);
    +  FORALLfacets
    +    facet->seen= False;
    +  if (qh->DELAUNAY) {
    +    FORALLfacets {
    +      if (facet->upperdelaunay != qh->UPPERdelaunay)
    +        facet->seen= True; /* remove from angle statistics */
    +    }
    +  }
    +  FORALLfacets {
    +    if (facet->visible && qh->NEWfacets)
    +      continue;
    +    sizvertices= qh_setsize(qh, facet->vertices);
    +    sizneighbors= qh_setsize(qh, facet->neighbors);
    +    sizridges= qh_setsize(qh, facet->ridges);
    +    zinc_(Znumfacets);
    +    zadd_(Znumvertices, sizvertices);
    +    zmax_(Zmaxvertices, sizvertices);
    +    zadd_(Znumneighbors, sizneighbors);
    +    zmax_(Zmaxneighbors, sizneighbors);
    +    zadd_(Znummergetot, facet->nummerge);
    +    i= facet->nummerge; /* avoid warnings */
    +    zmax_(Znummergemax, i);
    +    if (!facet->simplicial) {
    +      if (sizvertices == qh->hull_dim) {
    +        zinc_(Znowsimplicial);
    +      }else {
    +        zinc_(Znonsimplicial);
    +      }
    +    }
    +    if (sizridges) {
    +      zadd_(Znumridges, sizridges);
    +      zmax_(Zmaxridges, sizridges);
    +    }
    +    zadd_(Zmemfacets, sizeof(facetT) + qh->normal_size + 2*sizeof(setT)
    +       + SETelemsize * (sizneighbors + sizvertices));
    +    if (facet->ridges) {
    +      zadd_(Zmemridges,
    +         sizeof(setT) + SETelemsize * sizridges + sizridges *
    +         (sizeof(ridgeT) + sizeof(setT) + SETelemsize * (qh->hull_dim-1))/2);
    +    }
    +    if (facet->outsideset)
    +      zadd_(Zmempoints, sizeof(setT) + SETelemsize * qh_setsize(qh, facet->outsideset));
    +    if (facet->coplanarset)
    +      zadd_(Zmempoints, sizeof(setT) + SETelemsize * qh_setsize(qh, facet->coplanarset));
    +    if (facet->seen) /* Delaunay upper envelope */
    +      continue;
    +    facet->seen= True;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor == qh_DUPLICATEridge || neighbor == qh_MERGEridge
    +          || neighbor->seen || !facet->normal || !neighbor->normal)
    +        continue;
    +      dotproduct= qh_getangle(qh, facet->normal, neighbor->normal);
    +      zinc_(Zangle);
    +      wadd_(Wangle, dotproduct);
    +      wmax_(Wanglemax, dotproduct)
    +      wmin_(Wanglemin, dotproduct)
    +    }
    +    if (facet->normal) {
    +      FOREACHvertex_(facet->vertices) {
    +        zinc_(Zdiststat);
    +        qh_distplane(qh, vertex->point, facet, &dist);
    +        wmax_(Wvertexmax, dist);
    +        wmin_(Wvertexmin, dist);
    +      }
    +    }
    +  }
    +  FORALLvertices {
    +    if (vertex->deleted)
    +      continue;
    +    zadd_(Zmemvertices, sizeof(vertexT));
    +    if (vertex->neighbors) {
    +      sizneighbors= qh_setsize(qh, vertex->neighbors);
    +      zadd_(Znumvneighbors, sizneighbors);
    +      zmax_(Zmaxvneighbors, sizneighbors);
    +      zadd_(Zmemvertices, sizeof(vertexT) + SETelemsize * sizneighbors);
    +    }
    +  }
    +  qh->RANDOMdist= qh->old_randomdist;
    +} /* collectstatistics */
    +#endif /* qh_KEEPstatistics */
    +
    +/*---------------------------------
    +
    +  qh_initstatistics(qh)
    +    initialize statistics
    +
    +  notes:
    +  NOerrors -- qh_initstatistics can not use qh_errexit(), qh_fprintf, or qh.ferr
    +  On first call, only qhmem.ferr is defined.  qh_memalloc is not setup.
    +  Also invoked by QhullQh().
    +*/
    +void qh_initstatistics(qhT *qh) {
    +  int i;
    +  realT realx;
    +  int intx;
    +
    +  qh->qhstat.next= 0;
    +  qh_allstatA(qh);
    +  qh_allstatB(qh);
    +  qh_allstatC(qh);
    +  qh_allstatD(qh);
    +  qh_allstatE(qh);
    +  qh_allstatE2(qh);
    +  qh_allstatF(qh);
    +  qh_allstatG(qh);
    +  qh_allstatH(qh);
    +  qh_allstatI(qh);
    +  if (qh->qhstat.next > (int)sizeof(qh->qhstat.id)) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6184, "qhull error (qh_initstatistics): increase size of qhstat.id[].\n\
    +      qhstat.next %d should be <= sizeof(qh->qhstat.id) %d\n", qh->qhstat.next, (int)sizeof(qh->qhstat.id));
    +#if 0 /* for locating error, Znumridges should be duplicated */
    +    for(i=0; i < ZEND; i++) {
    +      int j;
    +      for(j=i+1; j < ZEND; j++) {
    +        if (qh->qhstat.id[i] == qh->qhstat.id[j]) {
    +          qh_fprintf(qh, qh->qhmem.ferr, 6185, "qhull error (qh_initstatistics): duplicated statistic %d at indices %d and %d\n",
    +              qh->qhstat.id[i], i, j);
    +        }
    +      }
    +    }
    +#endif
    +    qh_exit(qh_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  qh->qhstat.init[zinc].i= 0;
    +  qh->qhstat.init[zadd].i= 0;
    +  qh->qhstat.init[zmin].i= INT_MAX;
    +  qh->qhstat.init[zmax].i= INT_MIN;
    +  qh->qhstat.init[wadd].r= 0;
    +  qh->qhstat.init[wmin].r= REALmax;
    +  qh->qhstat.init[wmax].r= -REALmax;
    +  for(i=0; i < ZEND; i++) {
    +    if (qh->qhstat.type[i] > ZTYPEreal) {
    +      realx= qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].r;
    +      qh->qhstat.stats[i].r= realx;
    +    }else if (qh->qhstat.type[i] != zdoc) {
    +      intx= qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].i;
    +      qh->qhstat.stats[i].i= intx;
    +    }
    +  }
    +} /* initstatistics */
    +
    +/*---------------------------------
    +
    +  qh_newstats(qh, )
    +    returns True if statistics for zdoc
    +
    +  returns:
    +    next zdoc
    +*/
    +boolT qh_newstats(qhT *qh, int idx, int *nextindex) {
    +  boolT isnew= False;
    +  int start, i;
    +
    +  if (qh->qhstat.type[qh->qhstat.id[idx]] == zdoc)
    +    start= idx+1;
    +  else
    +    start= idx;
    +  for(i= start; i < qh->qhstat.next && qh->qhstat.type[qh->qhstat.id[i]] != zdoc; i++) {
    +    if (!qh_nostatistic(qh, qh->qhstat.id[i]) && !qh->qhstat.printed[qh->qhstat.id[i]])
    +        isnew= True;
    +  }
    +  *nextindex= i;
    +  return isnew;
    +} /* newstats */
    +
    +/*---------------------------------
    +
    +  qh_nostatistic(qh, index )
    +    true if no statistic to print
    +*/
    +boolT qh_nostatistic(qhT *qh, int i) {
    +
    +  if ((qh->qhstat.type[i] > ZTYPEreal
    +       &&qh->qhstat.stats[i].r == qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].r)
    +      || (qh->qhstat.type[i] < ZTYPEreal
    +          &&qh->qhstat.stats[i].i == qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].i))
    +    return True;
    +  return False;
    +} /* nostatistic */
    +
    +#if qh_KEEPstatistics
    +/*---------------------------------
    +
    +  qh_printallstatistics(qh, fp, string )
    +    print all statistics with header 'string'
    +*/
    +void qh_printallstatistics(qhT *qh, FILE *fp, const char *string) {
    +
    +  qh_allstatistics(qh);
    +  qh_collectstatistics(qh);
    +  qh_printstatistics(qh, fp, string);
    +  qh_memstatistics(qh, fp);
    +}
    +
    +
    +/*---------------------------------
    +
    +  qh_printstatistics(qh, fp, string )
    +    print statistics to a file with header 'string'
    +    skips statistics with qhstat.printed[] (reset with qh_allstatistics)
    +
    +  see:
    +    qh_printallstatistics()
    +*/
    +void qh_printstatistics(qhT *qh, FILE *fp, const char *string) {
    +  int i, k;
    +  realT ave;
    +
    +  if (qh->num_points != qh->num_vertices) {
    +    wval_(Wpbalance)= 0;
    +    wval_(Wpbalance2)= 0;
    +  }else
    +    wval_(Wpbalance2)= qh_stddev(zval_(Zpbalance), wval_(Wpbalance),
    +                                 wval_(Wpbalance2), &ave);
    +  wval_(Wnewbalance2)= qh_stddev(zval_(Zprocessed), wval_(Wnewbalance),
    +                                 wval_(Wnewbalance2), &ave);
    +  qh_fprintf(qh, fp, 9350, "\n\
    +%s\n\
    + qhull invoked by: %s | %s\n%s with options:\n%s\n", string, qh->rbox_command,
    +     qh->qhull_command, qh_version, qh->qhull_options);
    +  qh_fprintf(qh, fp, 9351, "\nprecision constants:\n\
    + %6.2g max. abs. coordinate in the (transformed) input('Qbd:n')\n\
    + %6.2g max. roundoff error for distance computation('En')\n\
    + %6.2g max. roundoff error for angle computations\n\
    + %6.2g min. distance for outside points ('Wn')\n\
    + %6.2g min. distance for visible facets ('Vn')\n\
    + %6.2g max. distance for coplanar facets ('Un')\n\
    + %6.2g max. facet width for recomputing centrum and area\n\
    +",
    +  qh->MAXabs_coord, qh->DISTround, qh->ANGLEround, qh->MINoutside,
    +        qh->MINvisible, qh->MAXcoplanar, qh->WIDEfacet);
    +  if (qh->KEEPnearinside)
    +    qh_fprintf(qh, fp, 9352, "\
    + %6.2g max. distance for near-inside points\n", qh->NEARinside);
    +  if (qh->premerge_cos < REALmax/2) qh_fprintf(qh, fp, 9353, "\
    + %6.2g max. cosine for pre-merge angle\n", qh->premerge_cos);
    +  if (qh->PREmerge) qh_fprintf(qh, fp, 9354, "\
    + %6.2g radius of pre-merge centrum\n", qh->premerge_centrum);
    +  if (qh->postmerge_cos < REALmax/2) qh_fprintf(qh, fp, 9355, "\
    + %6.2g max. cosine for post-merge angle\n", qh->postmerge_cos);
    +  if (qh->POSTmerge) qh_fprintf(qh, fp, 9356, "\
    + %6.2g radius of post-merge centrum\n", qh->postmerge_centrum);
    +  qh_fprintf(qh, fp, 9357, "\
    + %6.2g max. distance for merging two simplicial facets\n\
    + %6.2g max. roundoff error for arithmetic operations\n\
    + %6.2g min. denominator for divisions\n\
    +  zero diagonal for Gauss: ", qh->ONEmerge, REALepsilon, qh->MINdenom);
    +  for(k=0; k < qh->hull_dim; k++)
    +    qh_fprintf(qh, fp, 9358, "%6.2e ", qh->NEARzero[k]);
    +  qh_fprintf(qh, fp, 9359, "\n\n");
    +  for(i=0 ; i < qh->qhstat.next; )
    +    qh_printstats(qh, fp, i, &i);
    +} /* printstatistics */
    +#endif /* qh_KEEPstatistics */
    +
    +/*---------------------------------
    +
    +  qh_printstatlevel(qh, fp, id )
    +    print level information for a statistic
    +
    +  notes:
    +    nop if id >= ZEND, printed, or same as initial value
    +*/
    +void qh_printstatlevel(qhT *qh, FILE *fp, int id) {
    +#define NULLfield "       "
    +
    +  if (id >= ZEND || qh->qhstat.printed[id])
    +    return;
    +  if (qh->qhstat.type[id] == zdoc) {
    +    qh_fprintf(qh, fp, 9360, "%s\n", qh->qhstat.doc[id]);
    +    return;
    +  }
    +  if (qh_nostatistic(qh, id) || !qh->qhstat.doc[id])
    +    return;
    +  qh->qhstat.printed[id]= True;
    +  if (qh->qhstat.count[id] != -1
    +      && qh->qhstat.stats[(unsigned char)(qh->qhstat.count[id])].i == 0)
    +    qh_fprintf(qh, fp, 9361, " *0 cnt*");
    +  else if (qh->qhstat.type[id] >= ZTYPEreal && qh->qhstat.count[id] == -1)
    +    qh_fprintf(qh, fp, 9362, "%7.2g", qh->qhstat.stats[id].r);
    +  else if (qh->qhstat.type[id] >= ZTYPEreal && qh->qhstat.count[id] != -1)
    +    qh_fprintf(qh, fp, 9363, "%7.2g", qh->qhstat.stats[id].r/ qh->qhstat.stats[(unsigned char)(qh->qhstat.count[id])].i);
    +  else if (qh->qhstat.type[id] < ZTYPEreal && qh->qhstat.count[id] == -1)
    +    qh_fprintf(qh, fp, 9364, "%7d", qh->qhstat.stats[id].i);
    +  else if (qh->qhstat.type[id] < ZTYPEreal && qh->qhstat.count[id] != -1)
    +    qh_fprintf(qh, fp, 9365, "%7.3g", (realT) qh->qhstat.stats[id].i / qh->qhstat.stats[(unsigned char)(qh->qhstat.count[id])].i);
    +  qh_fprintf(qh, fp, 9366, " %s\n", qh->qhstat.doc[id]);
    +} /* printstatlevel */
    +
    +
    +/*---------------------------------
    +
    +  qh_printstats(qh, fp, index, nextindex )
    +    print statistics for a zdoc group
    +
    +  returns:
    +    next zdoc if non-null
    +*/
    +void qh_printstats(qhT *qh, FILE *fp, int idx, int *nextindex) {
    +  int j, nexti;
    +
    +  if (qh_newstats(qh, idx, &nexti)) {
    +    qh_fprintf(qh, fp, 9367, "\n");
    +    for (j=idx; jqhstat.id[j]);
    +  }
    +  if (nextindex)
    +    *nextindex= nexti;
    +} /* printstats */
    +
    +#if qh_KEEPstatistics
    +
    +/*---------------------------------
    +
    +  qh_stddev(num, tot, tot2, ave )
    +    compute the standard deviation and average from statistics
    +
    +    tot2 is the sum of the squares
    +  notes:
    +    computes r.m.s.:
    +      (x-ave)^2
    +      == x^2 - 2x tot/num +   (tot/num)^2
    +      == tot2 - 2 tot tot/num + tot tot/num
    +      == tot2 - tot ave
    +*/
    +realT qh_stddev(int num, realT tot, realT tot2, realT *ave) {
    +  realT stddev;
    +
    +  *ave= tot/num;
    +  stddev= sqrt(tot2/num - *ave * *ave);
    +  return stddev;
    +} /* stddev */
    +
    +#endif /* qh_KEEPstatistics */
    +
    +#if !qh_KEEPstatistics
    +void    qh_collectstatistics(qhT *qh) {}
    +void    qh_printallstatistics(qhT *qh, FILE *fp, char *string) {};
    +void    qh_printstatistics(qhT *qh, FILE *fp, char *string) {}
    +#endif
    +
    diff --git a/xs/src/qhull/src/libqhull_r/stat_r.h b/xs/src/qhull/src/libqhull_r/stat_r.h
    new file mode 100644
    index 0000000000..75e7d10578
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/stat_r.h
    @@ -0,0 +1,533 @@
    +/*
      ---------------------------------
    +
    +   stat_r.h
    +     contains all statistics that are collected for qhull
    +
    +   see qh-stat_r.htm and stat_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/stat_r.h#5 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +
    +   recompile qhull if you change this file
    +
    +   Integer statistics are Z* while real statistics are W*.
    +
    +   define MAYdebugx to call a routine at every statistic event
    +
    +*/
    +
    +#ifndef qhDEFstat
    +#define qhDEFstat 1
    +
    +/* Depends on realT.  Do not include libqhull_r to avoid circular dependency */
    +
    +#ifndef DEFqhT
    +#define DEFqhT 1
    +typedef struct qhT qhT;         /* Defined by libqhull_r.h */
    +#endif
    +
    +#ifndef DEFqhstatT
    +#define DEFqhstatT 1
    +typedef struct qhstatT qhstatT; /* Defined here */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_KEEPstatistics
    +    0 turns off statistic gathering (except zzdef/zzinc/zzadd/zzval/wwval)
    +*/
    +#ifndef qh_KEEPstatistics
    +#define qh_KEEPstatistics 1
    +#endif
    +
    +/*---------------------------------
    +
    +  Zxxx for integers, Wxxx for reals
    +
    +  notes:
    +    be sure that all statistics are defined in stat_r.c
    +      otherwise initialization may core dump
    +    can pick up all statistics by:
    +      grep '[zw].*_[(][ZW]' *.c >z.x
    +    remove trailers with query">-
    +    remove leaders with  query-replace-regexp [ ^I]+  (
    +*/
    +#if qh_KEEPstatistics
    +enum qh_statistics {     /* alphabetical after Z/W */
    +    Zacoplanar,
    +    Wacoplanarmax,
    +    Wacoplanartot,
    +    Zangle,
    +    Wangle,
    +    Wanglemax,
    +    Wanglemin,
    +    Zangletests,
    +    Wareatot,
    +    Wareamax,
    +    Wareamin,
    +    Zavoidold,
    +    Wavoidoldmax,
    +    Wavoidoldtot,
    +    Zback0,
    +    Zbestcentrum,
    +    Zbestdist,
    +    Zbestlower,
    +    Zbestlowerall,
    +    Zbestloweralln,
    +    Zbestlowerv,
    +    Zcentrumtests,
    +    Zcheckpart,
    +    Zcomputefurthest,
    +    Zconcave,
    +    Wconcavemax,
    +    Wconcavetot,
    +    Zconcaveridges,
    +    Zconcaveridge,
    +    Zcoplanar,
    +    Wcoplanarmax,
    +    Wcoplanartot,
    +    Zcoplanarangle,
    +    Zcoplanarcentrum,
    +    Zcoplanarhorizon,
    +    Zcoplanarinside,
    +    Zcoplanarpart,
    +    Zcoplanarridges,
    +    Wcpu,
    +    Zcyclefacetmax,
    +    Zcyclefacettot,
    +    Zcyclehorizon,
    +    Zcyclevertex,
    +    Zdegen,
    +    Wdegenmax,
    +    Wdegentot,
    +    Zdegenvertex,
    +    Zdelfacetdup,
    +    Zdelridge,
    +    Zdelvertextot,
    +    Zdelvertexmax,
    +    Zdetsimplex,
    +    Zdistcheck,
    +    Zdistconvex,
    +    Zdistgood,
    +    Zdistio,
    +    Zdistplane,
    +    Zdiststat,
    +    Zdistvertex,
    +    Zdistzero,
    +    Zdoc1,
    +    Zdoc2,
    +    Zdoc3,
    +    Zdoc4,
    +    Zdoc5,
    +    Zdoc6,
    +    Zdoc7,
    +    Zdoc8,
    +    Zdoc9,
    +    Zdoc10,
    +    Zdoc11,
    +    Zdoc12,
    +    Zdropdegen,
    +    Zdropneighbor,
    +    Zdupflip,
    +    Zduplicate,
    +    Wduplicatemax,
    +    Wduplicatetot,
    +    Zdupridge,
    +    Zdupsame,
    +    Zflipped,
    +    Wflippedmax,
    +    Wflippedtot,
    +    Zflippedfacets,
    +    Zfindbest,
    +    Zfindbestmax,
    +    Zfindbesttot,
    +    Zfindcoplanar,
    +    Zfindfail,
    +    Zfindhorizon,
    +    Zfindhorizonmax,
    +    Zfindhorizontot,
    +    Zfindjump,
    +    Zfindnew,
    +    Zfindnewmax,
    +    Zfindnewtot,
    +    Zfindnewjump,
    +    Zfindnewsharp,
    +    Zgauss0,
    +    Zgoodfacet,
    +    Zhashlookup,
    +    Zhashridge,
    +    Zhashridgetest,
    +    Zhashtests,
    +    Zinsidevisible,
    +    Zintersect,
    +    Zintersectfail,
    +    Zintersectmax,
    +    Zintersectnum,
    +    Zintersecttot,
    +    Zmaxneighbors,
    +    Wmaxout,
    +    Wmaxoutside,
    +    Zmaxridges,
    +    Zmaxvertex,
    +    Zmaxvertices,
    +    Zmaxvneighbors,
    +    Zmemfacets,
    +    Zmempoints,
    +    Zmemridges,
    +    Zmemvertices,
    +    Zmergeflipdup,
    +    Zmergehorizon,
    +    Zmergeinittot,
    +    Zmergeinitmax,
    +    Zmergeinittot2,
    +    Zmergeintohorizon,
    +    Zmergenew,
    +    Zmergesettot,
    +    Zmergesetmax,
    +    Zmergesettot2,
    +    Zmergesimplex,
    +    Zmergevertex,
    +    Wmindenom,
    +    Wminvertex,
    +    Zminnorm,
    +    Zmultiridge,
    +    Znearlysingular,
    +    Zneighbor,
    +    Wnewbalance,
    +    Wnewbalance2,
    +    Znewfacettot,
    +    Znewfacetmax,
    +    Znewvertex,
    +    Wnewvertex,
    +    Wnewvertexmax,
    +    Znoarea,
    +    Znonsimplicial,
    +    Znowsimplicial,
    +    Znotgood,
    +    Znotgoodnew,
    +    Znotmax,
    +    Znumfacets,
    +    Znummergemax,
    +    Znummergetot,
    +    Znumneighbors,
    +    Znumridges,
    +    Znumvertices,
    +    Znumvisibility,
    +    Znumvneighbors,
    +    Zonehorizon,
    +    Zpartangle,
    +    Zpartcoplanar,
    +    Zpartflip,
    +    Zparthorizon,
    +    Zpartinside,
    +    Zpartition,
    +    Zpartitionall,
    +    Zpartnear,
    +    Zpbalance,
    +    Wpbalance,
    +    Wpbalance2,
    +    Zpostfacets,
    +    Zpremergetot,
    +    Zprocessed,
    +    Zremvertex,
    +    Zremvertexdel,
    +    Zrenameall,
    +    Zrenamepinch,
    +    Zrenameshare,
    +    Zretry,
    +    Wretrymax,
    +    Zridge,
    +    Wridge,
    +    Wridgemax,
    +    Zridge0,
    +    Wridge0,
    +    Wridge0max,
    +    Zridgemid,
    +    Wridgemid,
    +    Wridgemidmax,
    +    Zridgeok,
    +    Wridgeok,
    +    Wridgeokmax,
    +    Zsearchpoints,
    +    Zsetplane,
    +    Ztestvneighbor,
    +    Ztotcheck,
    +    Ztothorizon,
    +    Ztotmerge,
    +    Ztotpartcoplanar,
    +    Ztotpartition,
    +    Ztotridges,
    +    Ztotvertices,
    +    Ztotvisible,
    +    Ztricoplanar,
    +    Ztricoplanarmax,
    +    Ztricoplanartot,
    +    Ztridegen,
    +    Ztrimirror,
    +    Ztrinull,
    +    Wvertexmax,
    +    Wvertexmin,
    +    Zvertexridge,
    +    Zvertexridgetot,
    +    Zvertexridgemax,
    +    Zvertices,
    +    Zvisfacettot,
    +    Zvisfacetmax,
    +    Zvisit,
    +    Zvisit2max,
    +    Zvisvertextot,
    +    Zvisvertexmax,
    +    Zvvisit,
    +    Zvvisit2max,
    +    Zwidefacet,
    +    Zwidevertices,
    +    ZEND};
    +
    +/*---------------------------------
    +
    +  Zxxx/Wxxx statistics that remain defined if qh_KEEPstatistics=0
    +
    +  notes:
    +    be sure to use zzdef, zzinc, etc. with these statistics (no double checking!)
    +*/
    +#else
    +enum qh_statistics {     /* for zzdef etc. macros */
    +  Zback0,
    +  Zbestdist,
    +  Zcentrumtests,
    +  Zcheckpart,
    +  Zconcaveridges,
    +  Zcoplanarhorizon,
    +  Zcoplanarpart,
    +  Zcoplanarridges,
    +  Zcyclefacettot,
    +  Zcyclehorizon,
    +  Zdelvertextot,
    +  Zdistcheck,
    +  Zdistconvex,
    +  Zdistzero,
    +  Zdoc1,
    +  Zdoc2,
    +  Zdoc3,
    +  Zdoc11,
    +  Zflippedfacets,
    +  Zgauss0,
    +  Zminnorm,
    +  Zmultiridge,
    +  Znearlysingular,
    +  Wnewvertexmax,
    +  Znumvisibility,
    +  Zpartcoplanar,
    +  Zpartition,
    +  Zpartitionall,
    +  Zprocessed,
    +  Zretry,
    +  Zridge,
    +  Wridge,
    +  Wridgemax,
    +  Zridge0,
    +  Wridge0,
    +  Wridge0max,
    +  Zridgemid,
    +  Wridgemid,
    +  Wridgemidmax,
    +  Zridgeok,
    +  Wridgeok,
    +  Wridgeokmax,
    +  Zsetplane,
    +  Ztotcheck,
    +  Ztotmerge,
    +    ZEND};
    +#endif
    +
    +/*---------------------------------
    +
    +  ztype
    +    the type of a statistic sets its initial value.
    +
    +  notes:
    +    The type should be the same as the macro for collecting the statistic
    +*/
    +enum ztypes {zdoc,zinc,zadd,zmax,zmin,ZTYPEreal,wadd,wmax,wmin,ZTYPEend};
    +
    +/*========== macros and constants =============*/
    +
    +/*----------------------------------
    +
    +  MAYdebugx
    +    define as maydebug() to be called frequently for error trapping
    +*/
    +#define MAYdebugx
    +
    +/*----------------------------------
    +
    +  zzdef_, zdef_( type, name, doc, -1)
    +    define a statistic (assumes 'qhstat.next= 0;')
    +
    +  zdef_( type, name, doc, count)
    +    define an averaged statistic
    +    printed as name/count
    +*/
    +#define zzdef_(stype,name,string,cnt) qh->qhstat.id[qh->qhstat.next++]=name; \
    +   qh->qhstat.doc[name]= string; qh->qhstat.count[name]= cnt; qh->qhstat.type[name]= stype
    +#if qh_KEEPstatistics
    +#define zdef_(stype,name,string,cnt) qh->qhstat.id[qh->qhstat.next++]=name; \
    +   qh->qhstat.doc[name]= string; qh->qhstat.count[name]= cnt; qh->qhstat.type[name]= stype
    +#else
    +#define zdef_(type,name,doc,count)
    +#endif
    +
    +/*----------------------------------
    +
    +  zzinc_( name ), zinc_( name)
    +    increment an integer statistic
    +*/
    +#define zzinc_(id) {MAYdebugx; qh->qhstat.stats[id].i++;}
    +#if qh_KEEPstatistics
    +#define zinc_(id) {MAYdebugx; qh->qhstat.stats[id].i++;}
    +#else
    +#define zinc_(id) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zzadd_( name, value ), zadd_( name, value ), wadd_( name, value )
    +    add value to an integer or real statistic
    +*/
    +#define zzadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].i += (val);}
    +#define wwadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].r += (val);}
    +#if qh_KEEPstatistics
    +#define zadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].i += (val);}
    +#define wadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].r += (val);}
    +#else
    +#define zadd_(id, val) {}
    +#define wadd_(id, val) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zzval_( name ), zval_( name ), wwval_( name )
    +    set or return value of a statistic
    +*/
    +#define zzval_(id) ((qh->qhstat.stats[id]).i)
    +#define wwval_(id) ((qh->qhstat.stats[id]).r)
    +#if qh_KEEPstatistics
    +#define zval_(id) ((qh->qhstat.stats[id]).i)
    +#define wval_(id) ((qh->qhstat.stats[id]).r)
    +#else
    +#define zval_(id) qh->qhstat.tempi
    +#define wval_(id) qh->qhstat.tempr
    +#endif
    +
    +/*----------------------------------
    +
    +  zmax_( id, val ), wmax_( id, value )
    +    maximize id with val
    +*/
    +#define wwmax_(id, val) {MAYdebugx; maximize_(qh->qhstat.stats[id].r,(val));}
    +#if qh_KEEPstatistics
    +#define zmax_(id, val) {MAYdebugx; maximize_(qh->qhstat.stats[id].i,(val));}
    +#define wmax_(id, val) {MAYdebugx; maximize_(qh->qhstat.stats[id].r,(val));}
    +#else
    +#define zmax_(id, val) {}
    +#define wmax_(id, val) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zmin_( id, val ), wmin_( id, value )
    +    minimize id with val
    +*/
    +#if qh_KEEPstatistics
    +#define zmin_(id, val) {MAYdebugx; minimize_(qh->qhstat.stats[id].i,(val));}
    +#define wmin_(id, val) {MAYdebugx; minimize_(qh->qhstat.stats[id].r,(val));}
    +#else
    +#define zmin_(id, val) {}
    +#define wmin_(id, val) {}
    +#endif
    +
    +/*================== stat_r.h types ==============*/
    +
    +
    +/*----------------------------------
    +
    +  intrealT
    +    union of integer and real, used for statistics
    +*/
    +typedef union intrealT intrealT;    /* union of int and realT */
    +union intrealT {
    +    int i;
    +    realT r;
    +};
    +
    +/*----------------------------------
    +
    +  qhstat
    +    Data structure for statistics, similar to qh and qhrbox
    +
    +    Allocated as part of qhT (libqhull_r.h)
    +*/
    +
    +struct qhstatT {
    +  intrealT   stats[ZEND];     /* integer and real statistics */
    +  unsigned   char id[ZEND+10]; /* id's in print order */
    +  const char *doc[ZEND];       /* array of documentation strings */
    +  short int  count[ZEND];     /* -1 if none, else index of count to use */
    +  char       type[ZEND];      /* type, see ztypes above */
    +  char       printed[ZEND];   /* true, if statistic has been printed */
    +  intrealT   init[ZTYPEend];  /* initial values by types, set initstatistics */
    +
    +  int        next;            /* next index for zdef_ */
    +  int        precision;       /* index for precision problems */
    +  int        vridges;         /* index for Voronoi ridges */
    +  int        tempi;
    +  realT      tempr;
    +};
    +
    +/*========== function prototypes ===========*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_allstatA(qhT *qh);
    +void    qh_allstatB(qhT *qh);
    +void    qh_allstatC(qhT *qh);
    +void    qh_allstatD(qhT *qh);
    +void    qh_allstatE(qhT *qh);
    +void    qh_allstatE2(qhT *qh);
    +void    qh_allstatF(qhT *qh);
    +void    qh_allstatG(qhT *qh);
    +void    qh_allstatH(qhT *qh);
    +void    qh_allstatI(qhT *qh);
    +void    qh_allstatistics(qhT *qh);
    +void    qh_collectstatistics(qhT *qh);
    +void    qh_initstatistics(qhT *qh);
    +boolT   qh_newstats(qhT *qh, int idx, int *nextindex);
    +boolT   qh_nostatistic(qhT *qh, int i);
    +void    qh_printallstatistics(qhT *qh, FILE *fp, const char *string);
    +void    qh_printstatistics(qhT *qh, FILE *fp, const char *string);
    +void    qh_printstatlevel(qhT *qh, FILE *fp, int id);
    +void    qh_printstats(qhT *qh, FILE *fp, int idx, int *nextindex);
    +realT   qh_stddev(int num, realT tot, realT tot2, realT *ave);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif   /* qhDEFstat */
    diff --git a/xs/src/qhull/src/libqhull_r/user_r.c b/xs/src/qhull/src/libqhull_r/user_r.c
    new file mode 100644
    index 0000000000..bf7ed1d8d6
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/user_r.c
    @@ -0,0 +1,527 @@
    +/*
      ---------------------------------
    +
    +   user.c
    +   user redefinable functions
    +
    +   see user2_r.c for qh_fprintf, qh_malloc, qh_free
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   see libqhull_r.h for data structures, macros, and user-callable functions.
    +
    +   see user_eg.c, user_eg2.c, and unix.c for examples.
    +
    +   see user.h for user-definable constants
    +
    +      use qh_NOmem in mem_r.h to turn off memory management
    +      use qh_NOmerge in user.h to turn off facet merging
    +      set qh_KEEPstatistics in user.h to 0 to turn off statistics
    +
    +   This is unsupported software.  You're welcome to make changes,
    +   but you're on your own if something goes wrong.  Use 'Tc' to
    +   check frequently.  Usually qhull will report an error if
    +   a data structure becomes inconsistent.  If so, it also reports
    +   the last point added to the hull, e.g., 102.  You can then trace
    +   the execution of qhull with "T4P102".
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +
    +   Qhull-template is a template for calling qhull from within your application
    +
    +   if you recompile and load this module, then user.o will not be loaded
    +   from qhull.a
    +
    +   you can add additional quick allocation sizes in qh_user_memsizes
    +
    +   if the other functions here are redefined to not use qh_print...,
    +   then io.o will not be loaded from qhull.a.  See user_eg_r.c for an
    +   example.  We recommend keeping io.o for the extra debugging
    +   information it supplies.
    +*/
    +
    +#include "qhull_ra.h"
    +
    +#include 
    +
    +/*---------------------------------
    +
    +  Qhull-template
    +    Template for calling qhull from inside your program
    +
    +  returns:
    +    exit code(see qh_ERR... in libqhull_r.h)
    +    all memory freed
    +
    +  notes:
    +    This can be called any number of times.
    +
    +*/
    +#if 0
    +{
    +  int dim;                  /* dimension of points */
    +  int numpoints;            /* number of points */
    +  coordT *points;           /* array of coordinates for each point */
    +  boolT ismalloc;           /* True if qhull should free points in qh_freeqhull() or reallocation */
    +  char flags[]= "qhull Tv"; /* option flags for qhull, see qh_opt.htm */
    +  FILE *outfile= stdout;    /* output from qh_produce_output(qh)
    +                               use NULL to skip qh_produce_output(qh) */
    +  FILE *errfile= stderr;    /* error messages from qhull code */
    +  int exitcode;             /* 0 if no error from qhull */
    +  facetT *facet;            /* set by FORALLfacets */
    +  int curlong, totlong;     /* memory remaining after qh_memfreeshort */
    +
    +  qhT qh_qh;                /* Qhull's data structure.  First argument of most calls */
    +  qhT *qh= &qh_qh;          /* Alternatively -- qhT *qh= (qhT*)malloc(sizeof(qhT)) */
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  qh_zero(qh, errfile);
    +
    +  /* initialize dim, numpoints, points[], ismalloc here */
    +  exitcode= qh_new_qhull(qh, dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh->facet_list' contains the convex hull */
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +  }
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf(qh, errfile, 7068, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n", totlong, curlong);
    +}
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_new_qhull(qh, dim, numpoints, points, ismalloc, qhull_cmd, outfile, errfile )
    +    Run qhull and return results in qh.
    +    Returns exitcode (0 if no errors).
    +    Before first call, either call qh_zero(qh, errfile), or set qh to all zero.
    +
    +  notes:
    +    do not modify points until finished with results.
    +      The qhull data structure contains pointers into the points array.
    +    do not call qhull functions before qh_new_qhull().
    +      The qhull data structure is not initialized until qh_new_qhull().
    +    do not call qh_init_A (global_r.c)
    +
    +    Default errfile is stderr, outfile may be null
    +    qhull_cmd must start with "qhull "
    +    projects points to a new point array for Delaunay triangulations ('d' and 'v')
    +    transforms points into a new point array for halfspace intersection ('H')
    +
    +  see:
    +    Qhull-template at the beginning of this file.
    +    An example of using qh_new_qhull is user_eg_r.c
    +*/
    +int qh_new_qhull(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc,
    +                char *qhull_cmd, FILE *outfile, FILE *errfile) {
    +  /* gcc may issue a "might be clobbered" warning for dim, points, and ismalloc [-Wclobbered].
    +     These parameters are not referenced after a longjmp() and hence not clobbered.
    +     See http://stackoverflow.com/questions/7721854/what-sense-do-these-clobbered-variable-warnings-make */
    +  int exitcode, hulldim;
    +  boolT new_ismalloc;
    +  coordT *new_points;
    +
    +  if(!errfile){
    +    errfile= stderr;
    +  }
    +  if (!qh->qhmem.ferr) {
    +    qh_meminit(qh, errfile);
    +  } else {
    +    qh_memcheck(qh);
    +  }
    +  if (strncmp(qhull_cmd, "qhull ", (size_t)6)) {
    +    qh_fprintf(qh, errfile, 6186, "qhull error (qh_new_qhull): start qhull_cmd argument with \"qhull \"\n");
    +    return qh_ERRinput;
    +  }
    +  qh_initqhull_start(qh, NULL, outfile, errfile);
    +  trace1((qh, qh->ferr, 1044, "qh_new_qhull: build new Qhull for %d %d-d points with %s\n", numpoints, dim, qhull_cmd));
    +  exitcode = setjmp(qh->errexit);
    +  if (!exitcode)
    +  {
    +    qh->NOerrexit = False;
    +    qh_initflags(qh, qhull_cmd);
    +    if (qh->DELAUNAY)
    +      qh->PROJECTdelaunay= True;
    +    if (qh->HALFspace) {
    +      /* points is an array of halfspaces,
    +         the last coordinate of each halfspace is its offset */
    +      hulldim= dim-1;
    +      qh_setfeasible(qh, hulldim);
    +      new_points= qh_sethalfspace_all(qh, dim, numpoints, points, qh->feasible_point);
    +      new_ismalloc= True;
    +      if (ismalloc)
    +        qh_free(points);
    +    }else {
    +      hulldim= dim;
    +      new_points= points;
    +      new_ismalloc= ismalloc;
    +    }
    +    qh_init_B(qh, new_points, numpoints, hulldim, new_ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    if (outfile) {
    +      qh_produce_output(qh);
    +    }else {
    +      qh_prepare_output(qh);
    +    }
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +  }
    +  qh->NOerrexit = True;
    +  return exitcode;
    +} /* new_qhull */
    +
    +/*---------------------------------
    +
    +  qh_errexit(qh, exitcode, facet, ridge )
    +    report and exit from an error
    +    report facet and ridge if non-NULL
    +    reports useful information such as last point processed
    +    set qh.FORCEoutput to print neighborhood of facet
    +
    +  see:
    +    qh_errexit2() in libqhull_r.c for printing 2 facets
    +
    +  design:
    +    check for error within error processing
    +    compute qh.hulltime
    +    print facet and ridge (if any)
    +    report commandString, options, qh.furthest_id
    +    print summary and statistics (including precision statistics)
    +    if qh_ERRsingular
    +      print help text for singular data set
    +    exit program via long jump (if defined) or exit()
    +*/
    +void qh_errexit(qhT *qh, int exitcode, facetT *facet, ridgeT *ridge) {
    +
    +  if (qh->ERREXITcalled) {
    +    qh_fprintf(qh, qh->ferr, 8126, "\nqhull error while processing previous error.  Exit program\n");
    +    qh_exit(qh_ERRqhull);
    +  }
    +  qh->ERREXITcalled= True;
    +  if (!qh->QHULLfinished)
    +    qh->hulltime= qh_CPUclock - qh->hulltime;
    +  qh_errprint(qh, "ERRONEOUS", facet, NULL, ridge, NULL);
    +  qh_fprintf(qh, qh->ferr, 8127, "\nWhile executing: %s | %s\n", qh->rbox_command, qh->qhull_command);
    +  qh_fprintf(qh, qh->ferr, 8128, "Options selected for Qhull %s:\n%s\n", qh_version, qh->qhull_options);
    +  if (qh->furthest_id >= 0) {
    +    qh_fprintf(qh, qh->ferr, 8129, "Last point added to hull was p%d.", qh->furthest_id);
    +    if (zzval_(Ztotmerge))
    +      qh_fprintf(qh, qh->ferr, 8130, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    if (qh->QHULLfinished)
    +      qh_fprintf(qh, qh->ferr, 8131, "\nQhull has finished constructing the hull.");
    +    else if (qh->POSTmerging)
    +      qh_fprintf(qh, qh->ferr, 8132, "\nQhull has started post-merging.");
    +    qh_fprintf(qh, qh->ferr, 8133, "\n");
    +  }
    +  if (qh->FORCEoutput && (qh->QHULLfinished || (!facet && !ridge)))
    +    qh_produce_output(qh);
    +  else if (exitcode != qh_ERRinput) {
    +    if (exitcode != qh_ERRsingular && zzval_(Zsetplane) > qh->hull_dim+1) {
    +      qh_fprintf(qh, qh->ferr, 8134, "\nAt error exit:\n");
    +      qh_printsummary(qh, qh->ferr);
    +      if (qh->PRINTstatistics) {
    +        qh_collectstatistics(qh);
    +        qh_printstatistics(qh, qh->ferr, "at error exit");
    +        qh_memstatistics(qh, qh->ferr);
    +      }
    +    }
    +    if (qh->PRINTprecision)
    +      qh_printstats(qh, qh->ferr, qh->qhstat.precision, NULL);
    +  }
    +  if (!exitcode)
    +    exitcode= qh_ERRqhull;
    +  else if (exitcode == qh_ERRsingular)
    +    qh_printhelp_singular(qh, qh->ferr);
    +  else if (exitcode == qh_ERRprec && !qh->PREmerge)
    +    qh_printhelp_degenerate(qh, qh->ferr);
    +  if (qh->NOerrexit) {
    +    qh_fprintf(qh, qh->ferr, 6187, "qhull error while ending program, or qh->NOerrexit not cleared after setjmp(). Exit program with error.\n");
    +    qh_exit(qh_ERRqhull);
    +  }
    +  qh->ERREXITcalled= False;
    +  qh->NOerrexit= True;
    +  qh->ALLOWrestart= False;  /* longjmp will undo qh_build_withrestart */
    +  longjmp(qh->errexit, exitcode);
    +} /* errexit */
    +
    +
    +/*---------------------------------
    +
    +  qh_errprint(qh, fp, string, atfacet, otherfacet, atridge, atvertex )
    +    prints out the information of facets and ridges to fp
    +    also prints neighbors and geomview output
    +
    +  notes:
    +    except for string, any parameter may be NULL
    +*/
    +void qh_errprint(qhT *qh, const char *string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex) {
    +  int i;
    +
    +  if (atfacet) {
    +    qh_fprintf(qh, qh->ferr, 8135, "%s FACET:\n", string);
    +    qh_printfacet(qh, qh->ferr, atfacet);
    +  }
    +  if (otherfacet) {
    +    qh_fprintf(qh, qh->ferr, 8136, "%s OTHER FACET:\n", string);
    +    qh_printfacet(qh, qh->ferr, otherfacet);
    +  }
    +  if (atridge) {
    +    qh_fprintf(qh, qh->ferr, 8137, "%s RIDGE:\n", string);
    +    qh_printridge(qh, qh->ferr, atridge);
    +    if (atridge->top && atridge->top != atfacet && atridge->top != otherfacet)
    +      qh_printfacet(qh, qh->ferr, atridge->top);
    +    if (atridge->bottom
    +        && atridge->bottom != atfacet && atridge->bottom != otherfacet)
    +      qh_printfacet(qh, qh->ferr, atridge->bottom);
    +    if (!atfacet)
    +      atfacet= atridge->top;
    +    if (!otherfacet)
    +      otherfacet= otherfacet_(atridge, atfacet);
    +  }
    +  if (atvertex) {
    +    qh_fprintf(qh, qh->ferr, 8138, "%s VERTEX:\n", string);
    +    qh_printvertex(qh, qh->ferr, atvertex);
    +  }
    +  if (qh->fout && qh->FORCEoutput && atfacet && !qh->QHULLfinished && !qh->IStracing) {
    +    qh_fprintf(qh, qh->ferr, 8139, "ERRONEOUS and NEIGHBORING FACETS to output\n");
    +    for (i=0; i < qh_PRINTEND; i++)  /* use fout for geomview output */
    +      qh_printneighborhood(qh, qh->fout, qh->PRINTout[i], atfacet, otherfacet,
    +                            !qh_ALL);
    +  }
    +} /* errprint */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetlist(qh, fp, facetlist, facets, printall )
    +    print all fields for a facet list and/or set of facets to fp
    +    if !printall,
    +      only prints good facets
    +
    +  notes:
    +    also prints all vertices
    +*/
    +void qh_printfacetlist(qhT *qh, facetT *facetlist, setT *facets, boolT printall) {
    +  facetT *facet, **facetp;
    +
    +  qh_printbegin(qh, qh->ferr, qh_PRINTfacets, facetlist, facets, printall);
    +  FORALLfacet_(facetlist)
    +    qh_printafacet(qh, qh->ferr, qh_PRINTfacets, facet, printall);
    +  FOREACHfacet_(facets)
    +    qh_printafacet(qh, qh->ferr, qh_PRINTfacets, facet, printall);
    +  qh_printend(qh, qh->ferr, qh_PRINTfacets, facetlist, facets, printall);
    +} /* printfacetlist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhelp_degenerate(qh, fp )
    +    prints descriptive message for precision error
    +
    +  notes:
    +    no message if qh_QUICKhelp
    +*/
    +void qh_printhelp_degenerate(qhT *qh, FILE *fp) {
    +
    +  if (qh->MERGEexact || qh->PREmerge || qh->JOGGLEmax < REALmax/2)
    +    qh_fprintf(qh, fp, 9368, "\n\
    +A Qhull error has occurred.  Qhull should have corrected the above\n\
    +precision error.  Please send the input and all of the output to\n\
    +qhull_bug@qhull.org\n");
    +  else if (!qh_QUICKhelp) {
    +    qh_fprintf(qh, fp, 9369, "\n\
    +Precision problems were detected during construction of the convex hull.\n\
    +This occurs because convex hull algorithms assume that calculations are\n\
    +exact, but floating-point arithmetic has roundoff errors.\n\
    +\n\
    +To correct for precision problems, do not use 'Q0'.  By default, Qhull\n\
    +selects 'C-0' or 'Qx' and merges non-convex facets.  With option 'QJ',\n\
    +Qhull joggles the input to prevent precision problems.  See \"Imprecision\n\
    +in Qhull\" (qh-impre.htm).\n\
    +\n\
    +If you use 'Q0', the output may include\n\
    +coplanar ridges, concave ridges, and flipped facets.  In 4-d and higher,\n\
    +Qhull may produce a ridge with four neighbors or two facets with the same \n\
    +vertices.  Qhull reports these events when they occur.  It stops when a\n\
    +concave ridge, flipped facet, or duplicate facet occurs.\n");
    +#if REALfloat
    +    qh_fprintf(qh, fp, 9370, "\
    +\n\
    +Qhull is currently using single precision arithmetic.  The following\n\
    +will probably remove the precision problems:\n\
    +  - recompile qhull for realT precision(#define REALfloat 0 in user.h).\n");
    +#endif
    +    if (qh->DELAUNAY && !qh->SCALElast && qh->MAXabs_coord > 1e4)
    +      qh_fprintf(qh, fp, 9371, "\
    +\n\
    +When computing the Delaunay triangulation of coordinates > 1.0,\n\
    +  - use 'Qbb' to scale the last coordinate to [0,m] (max previous coordinate)\n");
    +    if (qh->DELAUNAY && !qh->ATinfinity)
    +      qh_fprintf(qh, fp, 9372, "\
    +When computing the Delaunay triangulation:\n\
    +  - use 'Qz' to add a point at-infinity.  This reduces precision problems.\n");
    +
    +    qh_fprintf(qh, fp, 9373, "\
    +\n\
    +If you need triangular output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft'.  It triangulates non-simplicial facets with added points.\n\
    +\n\
    +If you must use 'Q0',\n\
    +try one or more of the following options.  They can not guarantee an output.\n\
    +  - use 'QbB' to scale the input to a cube.\n\
    +  - use 'Po' to produce output and prevent partitioning for flipped facets\n\
    +  - use 'V0' to set min. distance to visible facet as 0 instead of roundoff\n\
    +  - use 'En' to specify a maximum roundoff error less than %2.2g.\n\
    +  - options 'Qf', 'Qbb', and 'QR0' may also help\n",
    +               qh->DISTround);
    +    qh_fprintf(qh, fp, 9374, "\
    +\n\
    +To guarantee simplicial output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft' to triangulate the output by adding points\n\
    +  - use exact arithmetic (see \"Imprecision in Qhull\", qh-impre.htm)\n\
    +");
    +  }
    +} /* printhelp_degenerate */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhelp_narrowhull(qh, minangle )
    +    Warn about a narrow hull
    +
    +  notes:
    +    Alternatively, reduce qh_WARNnarrow in user.h
    +
    +*/
    +void qh_printhelp_narrowhull(qhT *qh, FILE *fp, realT minangle) {
    +
    +    qh_fprintf(qh, fp, 9375, "qhull precision warning: \n\
    +The initial hull is narrow (cosine of min. angle is %.16f).\n\
    +Is the input lower dimensional (e.g., on a plane in 3-d)?  Qhull may\n\
    +produce a wide facet.  Options 'QbB' (scale to unit box) or 'Qbb' (scale\n\
    +last coordinate) may remove this warning.  Use 'Pp' to skip this warning.\n\
    +See 'Limitations' in qh-impre.htm.\n",
    +          -minangle);   /* convert from angle between normals to angle between facets */
    +} /* printhelp_narrowhull */
    +
    +/*---------------------------------
    +
    +  qh_printhelp_singular(qh, fp )
    +    prints descriptive message for singular input
    +*/
    +void qh_printhelp_singular(qhT *qh, FILE *fp) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  realT min, max, *coord, dist;
    +  int i,k;
    +
    +  qh_fprintf(qh, fp, 9376, "\n\
    +The input to qhull appears to be less than %d dimensional, or a\n\
    +computation has overflowed.\n\n\
    +Qhull could not construct a clearly convex simplex from points:\n",
    +           qh->hull_dim);
    +  qh_printvertexlist(qh, fp, "", qh->facet_list, NULL, qh_ALL);
    +  if (!qh_QUICKhelp)
    +    qh_fprintf(qh, fp, 9377, "\n\
    +The center point is coplanar with a facet, or a vertex is coplanar\n\
    +with a neighboring facet.  The maximum round off error for\n\
    +computing distances is %2.2g.  The center point, facets and distances\n\
    +to the center point are as follows:\n\n", qh->DISTround);
    +  qh_printpointid(qh, fp, "center point", qh->hull_dim, qh->interior_point, qh_IDunknown);
    +  qh_fprintf(qh, fp, 9378, "\n");
    +  FORALLfacets {
    +    qh_fprintf(qh, fp, 9379, "facet");
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(qh, fp, 9380, " p%d", qh_pointid(qh, vertex->point));
    +    zinc_(Zdistio);
    +    qh_distplane(qh, qh->interior_point, facet, &dist);
    +    qh_fprintf(qh, fp, 9381, " distance= %4.2g\n", dist);
    +  }
    +  if (!qh_QUICKhelp) {
    +    if (qh->HALFspace)
    +      qh_fprintf(qh, fp, 9382, "\n\
    +These points are the dual of the given halfspaces.  They indicate that\n\
    +the intersection is degenerate.\n");
    +    qh_fprintf(qh, fp, 9383,"\n\
    +These points either have a maximum or minimum x-coordinate, or\n\
    +they maximize the determinant for k coordinates.  Trial points\n\
    +are first selected from points that maximize a coordinate.\n");
    +    if (qh->hull_dim >= qh_INITIALmax)
    +      qh_fprintf(qh, fp, 9384, "\n\
    +Because of the high dimension, the min x-coordinate and max-coordinate\n\
    +points are used if the determinant is non-zero.  Option 'Qs' will\n\
    +do a better, though much slower, job.  Instead of 'Qs', you can change\n\
    +the points by randomly rotating the input with 'QR0'.\n");
    +  }
    +  qh_fprintf(qh, fp, 9385, "\nThe min and max coordinates for each dimension are:\n");
    +  for (k=0; k < qh->hull_dim; k++) {
    +    min= REALmax;
    +    max= -REALmin;
    +    for (i=qh->num_points, coord= qh->first_point+k; i--; coord += qh->hull_dim) {
    +      maximize_(max, *coord);
    +      minimize_(min, *coord);
    +    }
    +    qh_fprintf(qh, fp, 9386, "  %d:  %8.4g  %8.4g  difference= %4.4g\n", k, min, max, max-min);
    +  }
    +  if (!qh_QUICKhelp) {
    +    qh_fprintf(qh, fp, 9387, "\n\
    +If the input should be full dimensional, you have several options that\n\
    +may determine an initial simplex:\n\
    +  - use 'QJ'  to joggle the input and make it full dimensional\n\
    +  - use 'QbB' to scale the points to the unit cube\n\
    +  - use 'QR0' to randomly rotate the input for different maximum points\n\
    +  - use 'Qs'  to search all points for the initial simplex\n\
    +  - use 'En'  to specify a maximum roundoff error less than %2.2g.\n\
    +  - trace execution with 'T3' to see the determinant for each point.\n",
    +                     qh->DISTround);
    +#if REALfloat
    +    qh_fprintf(qh, fp, 9388, "\
    +  - recompile qhull for realT precision(#define REALfloat 0 in libqhull_r.h).\n");
    +#endif
    +    qh_fprintf(qh, fp, 9389, "\n\
    +If the input is lower dimensional:\n\
    +  - use 'QJ' to joggle the input and make it full dimensional\n\
    +  - use 'Qbk:0Bk:0' to delete coordinate k from the input.  You should\n\
    +    pick the coordinate with the least range.  The hull will have the\n\
    +    correct topology.\n\
    +  - determine the flat containing the points, rotate the points\n\
    +    into a coordinate plane, and delete the other coordinates.\n\
    +  - add one or more points to make the input full dimensional.\n\
    +");
    +  }
    +} /* printhelp_singular */
    +
    +/*---------------------------------
    +
    +  qh_user_memsizes(qh)
    +    allocate up to 10 additional, quick allocation sizes
    +
    +  notes:
    +    increase maximum number of allocations in qh_initqhull_mem()
    +*/
    +void qh_user_memsizes(qhT *qh) {
    +
    +  QHULL_UNUSED(qh)
    +  /* qh_memsize(qh, size); */
    +} /* user_memsizes */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/user_r.h b/xs/src/qhull/src/libqhull_r/user_r.h
    new file mode 100644
    index 0000000000..7cca65a804
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/user_r.h
    @@ -0,0 +1,882 @@
    +/*
      ---------------------------------
    +
    +   user.h
    +   user redefinable constants
    +
    +   for each source file, user_r.h is included first
    +
    +   see qh-user_r.htm.  see COPYING for copyright information.
    +
    +   See user_r.c for sample code.
    +
    +   before reading any code, review libqhull_r.h for data structure definitions
    +
    +Sections:
    +   ============= qhull library constants ======================
    +   ============= data types and configuration macros ==========
    +   ============= performance related constants ================
    +   ============= memory constants =============================
    +   ============= joggle constants =============================
    +   ============= conditional compilation ======================
    +   ============= -merge constants- ============================
    +
    +Code flags --
    +  NOerrors -- the code does not call qh_errexit()
    +  WARN64 -- the code may be incompatible with 64-bit pointers
    +
    +*/
    +
    +#include 
    +
    +#ifndef qhDEFuser
    +#define qhDEFuser 1
    +
    +/* Derived from Qt's corelib/global/qglobal.h */
    +#if !defined(SAG_COM) && !defined(__CYGWIN__) && (defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__))
    +#   define QHULL_OS_WIN
    +#elif defined(__MWERKS__) && defined(__INTEL__) /* Metrowerks discontinued before the release of Intel Macs */
    +#   define QHULL_OS_WIN
    +#endif
    +
    +/*============================================================*/
    +/*============= qhull library constants ======================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  FILENAMElen -- max length for TI and TO filenames
    +
    +*/
    +
    +#define qh_FILENAMElen 500
    +
    +/*----------------------------------
    +
    +  msgcode -- Unique message codes for qh_fprintf
    +
    +  If add new messages, assign these values and increment in user.h and user_r.h
    +  See QhullError.h for 10000 errors.
    +
    +  def counters =  [27, 1048, 2059, 3026, 4068, 5003,
    +     6273, 7081, 8147, 9411, 10000, 11029]
    +
    +  See: qh_ERR* [libqhull_r.h]
    +*/
    +
    +#define MSG_TRACE0 0
    +#define MSG_TRACE1 1000
    +#define MSG_TRACE2 2000
    +#define MSG_TRACE3 3000
    +#define MSG_TRACE4 4000
    +#define MSG_TRACE5 5000
    +#define MSG_ERROR  6000   /* errors written to qh.ferr */
    +#define MSG_WARNING 7000
    +#define MSG_STDERR  8000  /* log messages Written to qh.ferr */
    +#define MSG_OUTPUT  9000
    +#define MSG_QHULL_ERROR 10000 /* errors thrown by QhullError.cpp (QHULLlastError is in QhullError.h) */
    +#define MSG_FIXUP  11000  /* FIXUP QH11... */
    +#define MSG_MAXLEN  3000 /* qh_printhelp_degenerate() in user.c */
    +
    +
    +/*----------------------------------
    +
    +  qh_OPTIONline -- max length of an option line 'FO'
    +*/
    +#define qh_OPTIONline 80
    +
    +/*============================================================*/
    +/*============= data types and configuration macros ==========*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  realT
    +    set the size of floating point numbers
    +
    +  qh_REALdigits
    +    maximimum number of significant digits
    +
    +  qh_REAL_1, qh_REAL_2n, qh_REAL_3n
    +    format strings for printf
    +
    +  qh_REALmax, qh_REALmin
    +    maximum and minimum (near zero) values
    +
    +  qh_REALepsilon
    +    machine roundoff.  Maximum roundoff error for addition and multiplication.
    +
    +  notes:
    +   Select whether to store floating point numbers in single precision (float)
    +   or double precision (double).
    +
    +   Use 'float' to save about 8% in time and 25% in space.  This is particularly
    +   helpful if high-d where convex hulls are space limited.  Using 'float' also
    +   reduces the printed size of Qhull's output since numbers have 8 digits of
    +   precision.
    +
    +   Use 'double' when greater arithmetic precision is needed.  This is needed
    +   for Delaunay triangulations and Voronoi diagrams when you are not merging
    +   facets.
    +
    +   If 'double' gives insufficient precision, your data probably includes
    +   degeneracies.  If so you should use facet merging (done by default)
    +   or exact arithmetic (see imprecision section of manual, qh-impre.htm).
    +   You may also use option 'Po' to force output despite precision errors.
    +
    +   You may use 'long double', but many format statements need to be changed
    +   and you may need a 'long double' square root routine.  S. Grundmann
    +   (sg@eeiwzb.et.tu-dresden.de) has done this.  He reports that the code runs
    +   much slower with little gain in precision.
    +
    +   WARNING: on some machines,    int f(){realT a= REALmax;return (a == REALmax);}
    +      returns False.  Use (a > REALmax/2) instead of (a == REALmax).
    +
    +   REALfloat =   1      all numbers are 'float' type
    +             =   0      all numbers are 'double' type
    +*/
    +#define REALfloat 1
    +
    +#if (REALfloat == 1)
    +#define realT float
    +#define REALmax FLT_MAX
    +#define REALmin FLT_MIN
    +#define REALepsilon FLT_EPSILON
    +#define qh_REALdigits 8   /* maximum number of significant digits */
    +#define qh_REAL_1 "%6.8g "
    +#define qh_REAL_2n "%6.8g %6.8g\n"
    +#define qh_REAL_3n "%6.8g %6.8g %6.8g\n"
    +
    +#elif (REALfloat == 0)
    +#define realT double
    +#define REALmax DBL_MAX
    +#define REALmin DBL_MIN
    +#define REALepsilon DBL_EPSILON
    +#define qh_REALdigits 16    /* maximum number of significant digits */
    +#define qh_REAL_1 "%6.16g "
    +#define qh_REAL_2n "%6.16g %6.16g\n"
    +#define qh_REAL_3n "%6.16g %6.16g %6.16g\n"
    +
    +#else
    +#error unknown float option
    +#endif
    +
    +/*----------------------------------
    +
    +  countT
    +    The type for counts and identifiers (e.g., the number of points, vertex identifiers)
    +    Currently used by C++ code-only.  Decided against using it for setT because most sets are small.
    +
    +    Defined as 'int' for C-code compatibility and QH11026
    +
    +    FIXUP QH11026 countT may be defined as a unsigned value, but several code issues need to be solved first.  See countT in Changes.txt
    +*/
    +
    +#ifndef DEFcountT
    +#define DEFcountT 1
    +typedef int countT;
    +#endif
    +#define COUNTmax 0x7fffffff
    +
    +
    +/*----------------------------------
    +
    +  qh_CPUclock
    +    define the clock() function for reporting the total time spent by Qhull
    +    returns CPU ticks as a 'long int'
    +    qh_CPUclock is only used for reporting the total time spent by Qhull
    +
    +  qh_SECticks
    +    the number of clock ticks per second
    +
    +  notes:
    +    looks for CLOCKS_PER_SEC, CLOCKS_PER_SECOND, or assumes microseconds
    +    to define a custom clock, set qh_CLOCKtype to 0
    +
    +    if your system does not use clock() to return CPU ticks, replace
    +    qh_CPUclock with the corresponding function.  It is converted
    +    to 'unsigned long' to prevent wrap-around during long runs.  By default,
    +     defines clock_t as 'long'
    +
    +   Set qh_CLOCKtype to
    +
    +     1          for CLOCKS_PER_SEC, CLOCKS_PER_SECOND, or microsecond
    +                Note:  may fail if more than 1 hour elapsed time
    +
    +     2          use qh_clock() with POSIX times() (see global_r.c)
    +*/
    +#define qh_CLOCKtype 1  /* change to the desired number */
    +
    +#if (qh_CLOCKtype == 1)
    +
    +#if defined(CLOCKS_PER_SECOND)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLOCKS_PER_SECOND
    +
    +#elif defined(CLOCKS_PER_SEC)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLOCKS_PER_SEC
    +
    +#elif defined(CLK_TCK)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLK_TCK
    +
    +#else
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks 1E6
    +#endif
    +
    +#elif (qh_CLOCKtype == 2)
    +#define qh_CPUclock    qh_clock()  /* return CPU clock */
    +#define qh_SECticks 100
    +
    +#else /* qh_CLOCKtype == ? */
    +#error unknown clock option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_RANDOMtype, qh_RANDOMmax, qh_RANDOMseed
    +    define random number generator
    +
    +    qh_RANDOMint generates a random integer between 0 and qh_RANDOMmax.
    +    qh_RANDOMseed sets the random number seed for qh_RANDOMint
    +
    +  Set qh_RANDOMtype (default 5) to:
    +    1       for random() with 31 bits (UCB)
    +    2       for rand() with RAND_MAX or 15 bits (system 5)
    +    3       for rand() with 31 bits (Sun)
    +    4       for lrand48() with 31 bits (Solaris)
    +    5       for qh_rand(qh) with 31 bits (included with Qhull, requires 'qh')
    +
    +  notes:
    +    Random numbers are used by rbox to generate point sets.  Random
    +    numbers are used by Qhull to rotate the input ('QRn' option),
    +    simulate a randomized algorithm ('Qr' option), and to simulate
    +    roundoff errors ('Rn' option).
    +
    +    Random number generators differ between systems.  Most systems provide
    +    rand() but the period varies.  The period of rand() is not critical
    +    since qhull does not normally use random numbers.
    +
    +    The default generator is Park & Miller's minimal standard random
    +    number generator [CACM 31:1195 '88].  It is included with Qhull.
    +
    +    If qh_RANDOMmax is wrong, qhull will report a warning and Geomview
    +    output will likely be invisible.
    +*/
    +#define qh_RANDOMtype 5   /* *** change to the desired number *** */
    +
    +#if (qh_RANDOMtype == 1)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, random()/MAX */
    +#define qh_RANDOMint random()
    +#define qh_RANDOMseed_(qh, seed) srandom(seed);
    +
    +#elif (qh_RANDOMtype == 2)
    +#ifdef RAND_MAX
    +#define qh_RANDOMmax ((realT)RAND_MAX)
    +#else
    +#define qh_RANDOMmax ((realT)32767)   /* 15 bits (System 5) */
    +#endif
    +#define qh_RANDOMint  rand()
    +#define qh_RANDOMseed_(qh, seed) srand((unsigned)seed);
    +
    +#elif (qh_RANDOMtype == 3)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, Sun */
    +#define qh_RANDOMint  rand()
    +#define qh_RANDOMseed_(qh, seed) srand((unsigned)seed);
    +
    +#elif (qh_RANDOMtype == 4)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, lrand38()/MAX */
    +#define qh_RANDOMint lrand48()
    +#define qh_RANDOMseed_(qh, seed) srand48(seed);
    +
    +#elif (qh_RANDOMtype == 5)  /* 'qh' is an implicit parameter */
    +#define qh_RANDOMmax ((realT)2147483646UL)  /* 31 bits, qh_rand/MAX */
    +#define qh_RANDOMint qh_rand(qh)
    +#define qh_RANDOMseed_(qh, seed) qh_srand(qh, seed);
    +/* unlike rand(), never returns 0 */
    +
    +#else
    +#error: unknown random option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_ORIENTclock
    +    0 for inward pointing normals by Geomview convention
    +*/
    +#define qh_ORIENTclock 0
    +
    +
    +/*============================================================*/
    +/*============= joggle constants =============================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +qh_JOGGLEdefault
    +default qh.JOGGLEmax is qh.DISTround * qh_JOGGLEdefault
    +
    +notes:
    +rbox s r 100 | qhull QJ1e-15 QR0 generates 90% faults at distround 7e-16
    +rbox s r 100 | qhull QJ1e-14 QR0 generates 70% faults
    +rbox s r 100 | qhull QJ1e-13 QR0 generates 35% faults
    +rbox s r 100 | qhull QJ1e-12 QR0 generates 8% faults
    +rbox s r 100 | qhull QJ1e-11 QR0 generates 1% faults
    +rbox s r 100 | qhull QJ1e-10 QR0 generates 0% faults
    +rbox 1000 W0 | qhull QJ1e-12 QR0 generates 86% faults
    +rbox 1000 W0 | qhull QJ1e-11 QR0 generates 20% faults
    +rbox 1000 W0 | qhull QJ1e-10 QR0 generates 2% faults
    +the later have about 20 points per facet, each of which may interfere
    +
    +pick a value large enough to avoid retries on most inputs
    +*/
    +#define qh_JOGGLEdefault 30000.0
    +
    +/*----------------------------------
    +
    +qh_JOGGLEincrease
    +factor to increase qh.JOGGLEmax on qh_JOGGLEretry or qh_JOGGLEagain
    +*/
    +#define qh_JOGGLEincrease 10.0
    +
    +/*----------------------------------
    +
    +qh_JOGGLEretry
    +if ZZretry = qh_JOGGLEretry, increase qh.JOGGLEmax
    +
    +notes:
    +try twice at the original value in case of bad luck the first time
    +*/
    +#define qh_JOGGLEretry 2
    +
    +/*----------------------------------
    +
    +qh_JOGGLEagain
    +every following qh_JOGGLEagain, increase qh.JOGGLEmax
    +
    +notes:
    +1 is OK since it's already failed qh_JOGGLEretry times
    +*/
    +#define qh_JOGGLEagain 1
    +
    +/*----------------------------------
    +
    +qh_JOGGLEmaxincrease
    +maximum qh.JOGGLEmax due to qh_JOGGLEincrease
    +relative to qh.MAXwidth
    +
    +notes:
    +qh.joggleinput will retry at this value until qh_JOGGLEmaxretry
    +*/
    +#define qh_JOGGLEmaxincrease 1e-2
    +
    +/*----------------------------------
    +
    +qh_JOGGLEmaxretry
    +stop after qh_JOGGLEmaxretry attempts
    +*/
    +#define qh_JOGGLEmaxretry 100
    +
    +/*============================================================*/
    +/*============= performance related constants ================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  qh_HASHfactor
    +    total hash slots / used hash slots.  Must be at least 1.1.
    +
    +  notes:
    +    =2 for at worst 50% occupancy for qh.hash_table and normally 25% occupancy
    +*/
    +#define qh_HASHfactor 2
    +
    +/*----------------------------------
    +
    +  qh_VERIFYdirect
    +    with 'Tv' verify all points against all facets if op count is smaller
    +
    +  notes:
    +    if greater, calls qh_check_bestdist() instead
    +*/
    +#define qh_VERIFYdirect 1000000
    +
    +/*----------------------------------
    +
    +  qh_INITIALsearch
    +     if qh_INITIALmax, search points up to this dimension
    +*/
    +#define qh_INITIALsearch 6
    +
    +/*----------------------------------
    +
    +  qh_INITIALmax
    +    if dim >= qh_INITIALmax, use min/max coordinate points for initial simplex
    +
    +  notes:
    +    from points with non-zero determinants
    +    use option 'Qs' to override (much slower)
    +*/
    +#define qh_INITIALmax 8
    +
    +/*============================================================*/
    +/*============= memory constants =============================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  qh_MEMalign
    +    memory alignment for qh_meminitbuffers() in global_r.c
    +
    +  notes:
    +    to avoid bus errors, memory allocation must consider alignment requirements.
    +    malloc() automatically takes care of alignment.   Since mem_r.c manages
    +    its own memory, we need to explicitly specify alignment in
    +    qh_meminitbuffers().
    +
    +    A safe choice is sizeof(double).  sizeof(float) may be used if doubles
    +    do not occur in data structures and pointers are the same size.  Be careful
    +    of machines (e.g., DEC Alpha) with large pointers.
    +
    +    If using gcc, best alignment is [fmax_() is defined in geom_r.h]
    +              #define qh_MEMalign fmax_(__alignof__(realT),__alignof__(void *))
    +*/
    +#define qh_MEMalign ((int)(fmax_(sizeof(realT), sizeof(void *))))
    +
    +/*----------------------------------
    +
    +  qh_MEMbufsize
    +    size of additional memory buffers
    +
    +  notes:
    +    used for qh_meminitbuffers() in global_r.c
    +*/
    +#define qh_MEMbufsize 0x10000       /* allocate 64K memory buffers */
    +
    +/*----------------------------------
    +
    +  qh_MEMinitbuf
    +    size of initial memory buffer
    +
    +  notes:
    +    use for qh_meminitbuffers() in global_r.c
    +*/
    +#define qh_MEMinitbuf 0x20000      /* initially allocate 128K buffer */
    +
    +/*----------------------------------
    +
    +  qh_INFINITE
    +    on output, indicates Voronoi center at infinity
    +*/
    +#define qh_INFINITE  -10.101
    +
    +/*----------------------------------
    +
    +  qh_DEFAULTbox
    +    default box size (Geomview expects 0.5)
    +
    +  qh_DEFAULTbox
    +    default box size for integer coorindate (rbox only)
    +*/
    +#define qh_DEFAULTbox 0.5
    +#define qh_DEFAULTzbox 1e6
    +
    +/*============================================================*/
    +/*============= conditional compilation ======================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  __cplusplus
    +    defined by C++ compilers
    +
    +  __MSC_VER
    +    defined by Microsoft Visual C++
    +
    +  __MWERKS__ && __INTEL__
    +    defined by Metrowerks when compiling for Windows (not Intel-based Macintosh)
    +
    +  __MWERKS__ && __POWERPC__
    +    defined by Metrowerks when compiling for PowerPC-based Macintosh
    +
    +  __STDC__
    +    defined for strict ANSI C
    +*/
    +
    +/*----------------------------------
    +
    +  qh_COMPUTEfurthest
    +    compute furthest distance to an outside point instead of storing it with the facet
    +    =1 to compute furthest
    +
    +  notes:
    +    computing furthest saves memory but costs time
    +      about 40% more distance tests for partitioning
    +      removes facet->furthestdist
    +*/
    +#define qh_COMPUTEfurthest 0
    +
    +/*----------------------------------
    +
    +  qh_KEEPstatistics
    +    =0 removes most of statistic gathering and reporting
    +
    +  notes:
    +    if 0, code size is reduced by about 4%.
    +*/
    +#define qh_KEEPstatistics 1
    +
    +/*----------------------------------
    +
    +  qh_MAXoutside
    +    record outer plane for each facet
    +    =1 to record facet->maxoutside
    +
    +  notes:
    +    this takes a realT per facet and slightly slows down qhull
    +    it produces better outer planes for geomview output
    +*/
    +#define qh_MAXoutside 1
    +
    +/*----------------------------------
    +
    +  qh_NOmerge
    +    disables facet merging if defined
    +
    +  notes:
    +    This saves about 10% space.
    +
    +    Unless 'Q0'
    +      qh_NOmerge sets 'QJ' to avoid precision errors
    +
    +    #define qh_NOmerge
    +
    +  see:
    +    qh_NOmem in mem_r.c
    +
    +    see user_r.c/user_eg.c for removing io_r.o
    +*/
    +
    +/*----------------------------------
    +
    +  qh_NOtrace
    +    no tracing if defined
    +
    +  notes:
    +    This saves about 5% space.
    +
    +    #define qh_NOtrace
    +*/
    +
    +#if 0  /* sample code */
    +    exitcode= qh_new_qhull(qhT *qh, dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +    qh_freeqhull(qhT *qh, !qh_ALL); /* frees long memory used by second call */
    +    qh_memfreeshort(qhT *qh, &curlong, &totlong);  /* frees short memory and memory allocator */
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_QUICKhelp
    +    =1 to use abbreviated help messages, e.g., for degenerate inputs
    +*/
    +#define qh_QUICKhelp    0
    +
    +/*============================================================*/
    +/*============= -merge constants- ============================*/
    +/*============================================================*/
    +/*
    +   These constants effect facet merging.  You probably will not need
    +   to modify them.  They effect the performance of facet merging.
    +*/
    +
    +/*----------------------------------
    +
    +  qh_DIMmergeVertex
    +    max dimension for vertex merging (it is not effective in high-d)
    +*/
    +#define qh_DIMmergeVertex 6
    +
    +/*----------------------------------
    +
    +  qh_DIMreduceBuild
    +     max dimension for vertex reduction during build (slow in high-d)
    +*/
    +#define qh_DIMreduceBuild 5
    +
    +/*----------------------------------
    +
    +  qh_BESTcentrum
    +     if > 2*dim+n vertices, qh_findbestneighbor() tests centrums (faster)
    +     else, qh_findbestneighbor() tests all vertices (much better merges)
    +
    +  qh_BESTcentrum2
    +     if qh_BESTcentrum2 * DIM3 + BESTcentrum < #vertices tests centrums
    +*/
    +#define qh_BESTcentrum 20
    +#define qh_BESTcentrum2 2
    +
    +/*----------------------------------
    +
    +  qh_BESTnonconvex
    +    if > dim+n neighbors, qh_findbestneighbor() tests nonconvex ridges.
    +
    +  notes:
    +    It is needed because qh_findbestneighbor is slow for large facets
    +*/
    +#define qh_BESTnonconvex 15
    +
    +/*----------------------------------
    +
    +  qh_MAXnewmerges
    +    if >n newmerges, qh_merge_nonconvex() calls qh_reducevertices_centrums.
    +
    +  notes:
    +    It is needed because postmerge can merge many facets at once
    +*/
    +#define qh_MAXnewmerges 2
    +
    +/*----------------------------------
    +
    +  qh_MAXnewcentrum
    +    if <= dim+n vertices (n approximates the number of merges),
    +      reset the centrum in qh_updatetested() and qh_mergecycle_facets()
    +
    +  notes:
    +    needed to reduce cost and because centrums may move too much if
    +    many vertices in high-d
    +*/
    +#define qh_MAXnewcentrum 5
    +
    +/*----------------------------------
    +
    +  qh_COPLANARratio
    +    for 3-d+ merging, qh.MINvisible is n*premerge_centrum
    +
    +  notes:
    +    for non-merging, it's DISTround
    +*/
    +#define qh_COPLANARratio 3
    +
    +/*----------------------------------
    +
    +  qh_DISToutside
    +    When is a point clearly outside of a facet?
    +    Stops search in qh_findbestnew or qh_partitionall
    +    qh_findbest uses qh.MINoutside since since it is only called if no merges.
    +
    +  notes:
    +    'Qf' always searches for best facet
    +    if !qh.MERGING, same as qh.MINoutside.
    +    if qh_USEfindbestnew, increase value since neighboring facets may be ill-behaved
    +      [Note: Zdelvertextot occurs normally with interior points]
    +            RBOX 1000 s Z1 G1e-13 t1001188774 | QHULL Tv
    +    When there is a sharp edge, need to move points to a
    +    clearly good facet; otherwise may be lost in another partitioning.
    +    if too big then O(n^2) behavior for partitioning in cone
    +    if very small then important points not processed
    +    Needed in qh_partitionall for
    +      RBOX 1000 s Z1 G1e-13 t1001032651 | QHULL Tv
    +    Needed in qh_findbestnew for many instances of
    +      RBOX 1000 s Z1 G1e-13 t | QHULL Tv
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_DISToutside ((qh_USEfindbestnew ? 2 : 1) * \
    +     fmax_((qh->MERGING ? 2 : 1)*qh->MINoutside, qh->max_outside))
    +
    +/*----------------------------------
    +
    +  qh_RATIOnearinside
    +    ratio of qh.NEARinside to qh.ONEmerge for retaining inside points for
    +    qh_check_maxout().
    +
    +  notes:
    +    This is overkill since do not know the correct value.
    +    It effects whether 'Qc' reports all coplanar points
    +    Not used for 'd' since non-extreme points are coplanar
    +*/
    +#define qh_RATIOnearinside 5
    +
    +/*----------------------------------
    +
    +  qh_SEARCHdist
    +    When is a facet coplanar with the best facet?
    +    qh_findbesthorizon: all coplanar facets of the best facet need to be searched.
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_SEARCHdist ((qh_USEfindbestnew ? 2 : 1) * \
    +      (qh->max_outside + 2 * qh->DISTround + fmax_( qh->MINvisible, qh->MAXcoplanar)));
    +
    +/*----------------------------------
    +
    +  qh_USEfindbestnew
    +     Always use qh_findbestnew for qh_partitionpoint, otherwise use
    +     qh_findbestnew if merged new facet or sharpnewfacets.
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_USEfindbestnew (zzval_(Ztotmerge) > 50)
    +
    +/*----------------------------------
    +
    +  qh_WIDEcoplanar
    +    n*MAXcoplanar or n*MINvisible for a WIDEfacet
    +
    +    if vertex is further than qh.WIDEfacet from the hyperplane
    +    then its ridges are not counted in computing the area, and
    +    the facet's centrum is frozen.
    +
    +  notes:
    +   qh.WIDEfacet= max(qh.MAXoutside,qh_WIDEcoplanar*qh.MAXcoplanar,
    +      qh_WIDEcoplanar * qh.MINvisible);
    +*/
    +#define qh_WIDEcoplanar 6
    +
    +/*----------------------------------
    +
    +  qh_WIDEduplicate
    +    Merge ratio for errexit from qh_forcedmerges due to duplicate ridge
    +    Override with option Q12 no-wide-duplicate
    +
    +    Notes:
    +      Merging a duplicate ridge can lead to very wide facets.
    +      A future release of qhull will avoid duplicate ridges by removing duplicate sub-ridges from the horizon
    +*/
    +#define qh_WIDEduplicate 100
    +
    +/*----------------------------------
    +
    +  qh_MAXnarrow
    +    max. cosine in initial hull that sets qh.NARROWhull
    +
    +  notes:
    +    If qh.NARROWhull, the initial partition does not make
    +    coplanar points.  If narrow, a coplanar point can be
    +    coplanar to two facets of opposite orientations and
    +    distant from the exact convex hull.
    +
    +    Conservative estimate.  Don't actually see problems until it is -1.0
    +*/
    +#define qh_MAXnarrow -0.99999999
    +
    +/*----------------------------------
    +
    +  qh_WARNnarrow
    +    max. cosine in initial hull to warn about qh.NARROWhull
    +
    +  notes:
    +    this is a conservative estimate.
    +    Don't actually see problems until it is -1.0.  See qh-impre.htm
    +*/
    +#define qh_WARNnarrow -0.999999999999999
    +
    +/*----------------------------------
    +
    +  qh_ZEROdelaunay
    +    a zero Delaunay facet occurs for input sites coplanar with their convex hull
    +    the last normal coefficient of a zero Delaunay facet is within
    +        qh_ZEROdelaunay * qh.ANGLEround of 0
    +
    +  notes:
    +    qh_ZEROdelaunay does not allow for joggled input ('QJ').
    +
    +    You can avoid zero Delaunay facets by surrounding the input with a box.
    +
    +    Use option 'PDk:-n' to explicitly define zero Delaunay facets
    +      k= dimension of input sites (e.g., 3 for 3-d Delaunay triangulation)
    +      n= the cutoff for zero Delaunay facets (e.g., 'PD3:-1e-12')
    +*/
    +#define qh_ZEROdelaunay 2
    +
    +/*============================================================*/
    +/*============= Microsoft DevStudio ==========================*/
    +/*============================================================*/
    +
    +/*
    +   Finding Memory Leaks Using the CRT Library
    +   https://msdn.microsoft.com/en-us/library/x98tx3cf(v=vs.100).aspx
    +
    +   Reports enabled in qh_lib_check for Debug window and stderr
    +
    +   From 2005=>msvcr80d, 2010=>msvcr100d, 2012=>msvcr110d
    +
    +   Watch: {,,msvcr80d.dll}_crtBreakAlloc  Value from {n} in the leak report
    +   _CrtSetBreakAlloc(689); // qh_lib_check() [global_r.c]
    +
    +   Examples
    +     http://free-cad.sourceforge.net/SrcDocu/d2/d7f/MemDebug_8cpp_source.html
    +     https://github.com/illlust/Game/blob/master/library/MemoryLeak.cpp
    +*/
    +#if 0   /* off (0) by default for QHULL_CRTDBG */
    +#define QHULL_CRTDBG
    +#endif
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)
    +#define _CRTDBG_MAP_ALLOC
    +#include 
    +#include 
    +#endif
    +
    +#endif /* qh_DEFuser */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/usermem_r.c b/xs/src/qhull/src/libqhull_r/usermem_r.c
    new file mode 100644
    index 0000000000..3297b03185
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/usermem_r.c
    @@ -0,0 +1,94 @@
    +/*
      ---------------------------------
    +
    +   usermem_r.c
    +   qh_exit(), qh_free(), and qh_malloc()
    +
    +   See README.txt.
    +
    +   If you redefine one of these functions you must redefine all of them.
    +   If you recompile and load this file, then usermem.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull_r.h for data structures, macros, and user-callable functions.
    +   See user_r.c for qhull-related, redefinable functions
    +   see user_r.h for user-definable constants
    +   See userprintf_r.c for qh_fprintf and userprintf_rbox_r.c for qh_fprintf_rbox
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull_r.h"
    +
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +  qh_exit( exitcode )
    +    exit program
    +
    +  notes:
    +    qh_exit() is called when qh_errexit() and longjmp() are not available.
    +
    +    This is the only use of exit() in Qhull
    +    To replace qh_exit with 'throw', see libqhullcpp/usermem_r-cpp.cpp
    +*/
    +void qh_exit(int exitcode) {
    +    exit(exitcode);
    +} /* exit */
    +
    +/*---------------------------------
    +
    +  qh_fprintf_stderr( msgcode, format, list of args )
    +    fprintf to stderr with msgcode (non-zero)
    +
    +  notes:
    +    qh_fprintf_stderr() is called when qh->ferr is not defined, usually due to an initialization error
    +    
    +    It is typically followed by qh_errexit().
    +
    +    Redefine this function to avoid using stderr
    +
    +    Use qh_fprintf [userprintf_r.c] for normal printing
    +*/
    +void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    va_start(args, fmt);
    +    if(msgcode)
    +      fprintf(stderr, "QH%.4d ", msgcode);
    +    vfprintf(stderr, fmt, args);
    +    va_end(args);
    +} /* fprintf_stderr */
    +
    +/*---------------------------------
    +
    +  qh_free(qhT *qh, mem )
    +    free memory
    +
    +  notes:
    +    same as free()
    +    No calls to qh_errexit() 
    +*/
    +void qh_free(void *mem) {
    +    free(mem);
    +} /* free */
    +
    +/*---------------------------------
    +
    +    qh_malloc( mem )
    +      allocate memory
    +
    +    notes:
    +      same as malloc()
    +*/
    +void *qh_malloc(size_t size) {
    +    return malloc(size);
    +} /* malloc */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/userprintf_r.c b/xs/src/qhull/src/libqhull_r/userprintf_r.c
    new file mode 100644
    index 0000000000..6004491a1c
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/userprintf_r.c
    @@ -0,0 +1,65 @@
    +/*
      ---------------------------------
    +
    +   userprintf_r.c
    +   qh_fprintf()
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   If you recompile and load this file, then userprintf_r.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull_r.h for data structures, macros, and user-callable functions.
    +   See user_r.c for qhull-related, redefinable functions
    +   see user_r.h for user-definable constants
    +   See usermem_r.c for qh_exit(), qh_free(), and qh_malloc()
    +   see Qhull.cpp and RboxPoints.cpp for examples.
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +   qh_fprintf(qh, fp, msgcode, format, list of args )
    +     print arguments to *fp according to format
    +     Use qh_fprintf_rbox() for rboxlib_r.c
    +
    +   notes:
    +     same as fprintf()
    +     fgets() is not trapped like fprintf()
    +     exit qh_fprintf via qh_errexit()
    +     may be called for errors in qh_initstatistics and qh_meminit
    +*/
    +
    +void qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    if (!fp) {
    +        if(!qh){
    +            qh_fprintf_stderr(6241, "userprintf_r.c: fp and qh not defined for qh_fprintf '%s'", fmt);
    +            qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +        }
    +        /* could use qh->qhmem.ferr, but probably better to be cautious */
    +        qh_fprintf_stderr(6232, "Qhull internal error (userprintf_r.c): fp is 0.  Wrong qh_fprintf called.\n");
    +        qh_errexit(qh, 6232, NULL, NULL);
    +    }
    +    va_start(args, fmt);
    +    if (qh && qh->ANNOTATEoutput) {
    +      fprintf(fp, "[QH%.4d]", msgcode);
    +    }else if (msgcode >= MSG_ERROR && msgcode < MSG_STDERR ) {
    +      fprintf(fp, "QH%.4d ", msgcode);
    +    }
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +
    +    /* Place debugging traps here. Use with option 'Tn' */
    +
    +} /* qh_fprintf */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/userprintf_rbox_r.c b/xs/src/qhull/src/libqhull_r/userprintf_rbox_r.c
    new file mode 100644
    index 0000000000..1e721a22ae
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/userprintf_rbox_r.c
    @@ -0,0 +1,53 @@
    +/*
      ---------------------------------
    +
    +   userprintf_rbox_r.c
    +   qh_fprintf_rbox()
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   If you recompile and load this file, then userprintf_rbox_r.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull_r.h for data structures, macros, and user-callable functions.
    +   See user_r.c for qhull-related, redefinable functions
    +   see user_r.h for user-definable constants
    +   See usermem_r.c for qh_exit(), qh_free(), and qh_malloc()
    +   see Qhull.cpp and RboxPoints.cpp for examples.
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +   qh_fprintf_rbox(qh, fp, msgcode, format, list of args )
    +     print arguments to *fp according to format
    +     Use qh_fprintf_rbox() for rboxlib_r.c
    +
    +   notes:
    +     same as fprintf()
    +     fgets() is not trapped like fprintf()
    +     exit qh_fprintf_rbox via qh_errexit_rbox()
    +*/
    +
    +void qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    if (!fp) {
    +        qh_fprintf_stderr(6231, "Qhull internal error (userprintf_rbox_r.c): fp is 0.  Wrong qh_fprintf_rbox called.\n");
    +        qh_errexit_rbox(qh, 6231);
    +    }
    +    if (msgcode >= MSG_ERROR && msgcode < MSG_STDERR)
    +      fprintf(fp, "QH%.4d ", msgcode);
    +    va_start(args, fmt);
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +} /* qh_fprintf_rbox */
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/Coordinates.cpp b/xs/src/qhull/src/libqhullcpp/Coordinates.cpp
    new file mode 100644
    index 0000000000..806b438aba
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/Coordinates.cpp
    @@ -0,0 +1,198 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/Coordinates.cpp#4 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/Coordinates.h"
    +
    +#include "libqhullcpp/functionObjects.h"
    +#include "libqhullcpp/QhullError.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//! Coordinates -- vector of coordT (normally double)
    +
    +#//!\name Constructor
    +
    +#//!\name Element access
    +
    +// Inefficient without result-value-optimization or implicitly shared object
    +Coordinates Coordinates::
    +mid(countT idx, countT length) const
    +{
    +    countT newLength= length;
    +    if(length<0 || idx+length > count()){
    +        newLength= count()-idx;
    +    }
    +    Coordinates result;
    +    if(newLength>0){
    +        std::copy(begin()+idx, begin()+(idx+newLength), std::back_inserter(result));
    +    }
    +    return result;
    +}//mid
    +
    +coordT Coordinates::
    +value(countT idx, const coordT &defaultValue) const
    +{
    +    return ((idx < 0 || idx >= count()) ? defaultValue : (*this)[idx]);
    +}//value
    +
    +#//!\name GetSet
    +
    +Coordinates Coordinates::
    +operator+(const Coordinates &other) const
    +{
    +    Coordinates result(*this);
    +    std::copy(other.begin(), other.end(), std::back_inserter(result));
    +    return result;
    +}//operator+
    +
    +Coordinates & Coordinates::
    +operator+=(const Coordinates &other)
    +{
    +    if(&other==this){
    +        Coordinates clone(other);
    +        std::copy(clone.begin(), clone.end(), std::back_inserter(*this));
    +    }else{
    +        std::copy(other.begin(), other.end(), std::back_inserter(*this));
    +    }
    +    return *this;
    +}//operator+=
    +
    +#//!\name Read-write
    +
    +void Coordinates::
    +append(int pointDimension, coordT *c)
    +{
    +    if(c){
    +        coordT *p= c;
    +        for(int i= 0; i(i-begin())); // WARN64 coordinate index
    +            }
    +            ++i;
    +        }
    +    }
    +    return -1;
    +}//indexOf
    +
    +countT Coordinates::
    +lastIndexOf(const coordT &t, countT from) const
    +{
    +    if(from<0){
    +        from += count();
    +    }else if(from>=count()){
    +        from= count()-1;
    +    }
    +    if(from>=0){
    +        const_iterator i= begin()+from+1;
    +        while(i-- != constBegin()){
    +            if(*i==t){
    +                return (static_cast(i-begin())); // WARN64 coordinate index
    +            }
    +        }
    +    }
    +    return -1;
    +}//lastIndexOf
    +
    +void Coordinates::
    +removeAll(const coordT &t)
    +{
    +    MutableCoordinatesIterator i(*this);
    +    while(i.findNext(t)){
    +        i.remove();
    +    }
    +}//removeAll
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::istream;
    +using std::ostream;
    +using std::string;
    +using std::ws;
    +using orgQhull::Coordinates;
    +
    +ostream &
    +operator<<(ostream &os, const Coordinates &cs)
    +{
    +    Coordinates::const_iterator c= cs.begin();
    +    for(countT i=cs.count(); i--; ){
    +        os << *c++ << " ";
    +    }
    +    return os;
    +}//operator<<
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/Coordinates.h b/xs/src/qhull/src/libqhullcpp/Coordinates.h
    new file mode 100644
    index 0000000000..df8bd11386
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/Coordinates.h
    @@ -0,0 +1,303 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/Coordinates.h#6 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHCOORDINATES_H
    +#define QHCOORDINATES_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullIterator.h"
    +
    +#include  // ptrdiff_t, size_t
    +#include 
    +// Requires STL vector class.  Can use with another vector class such as QList.
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! An std::vector of point coordinates independent of dimension
    +    //! Used by PointCoordinates for RboxPoints and by Qhull for feasiblePoint
    +    //! A QhullPoint refers to previously allocated coordinates
    +    class Coordinates;
    +    class MutableCoordinatesIterator;
    +
    +class Coordinates {
    +
    +private:
    +#//!\name Fields
    +    std::vector coordinate_array;
    +
    +public:
    +#//!\name Subtypes
    +
    +    class const_iterator;
    +    class iterator;
    +    typedef iterator Iterator;
    +    typedef const_iterator ConstIterator;
    +
    +    typedef coordT              value_type;
    +    typedef const value_type   *const_pointer;
    +    typedef const value_type &  const_reference;
    +    typedef value_type *        pointer;
    +    typedef value_type &        reference;
    +    typedef ptrdiff_t           difference_type;
    +    typedef countT              size_type;
    +
    +#//!\name Construct
    +                        Coordinates() {};
    +    explicit            Coordinates(const std::vector &other) : coordinate_array(other) {}
    +                        Coordinates(const Coordinates &other) : coordinate_array(other.coordinate_array) {}
    +    Coordinates &       operator=(const Coordinates &other) { coordinate_array= other.coordinate_array; return *this; }
    +    Coordinates &       operator=(const std::vector &other) { coordinate_array= other; return *this; }
    +                        ~Coordinates() {}
    +
    +#//!\name Conversion
    +
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const { return coordinate_array; }
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList       toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +    countT              count() const { return static_cast(size()); }
    +    coordT *            data() { return isEmpty() ? 0 : &at(0); }
    +    const coordT *      data() const { return const_cast(isEmpty() ? 0 : &at(0)); }
    +    bool                isEmpty() const { return coordinate_array.empty(); }
    +    bool                operator==(const Coordinates &other) const  { return coordinate_array==other.coordinate_array; }
    +    bool                operator!=(const Coordinates &other) const  { return coordinate_array!=other.coordinate_array; }
    +    size_t              size() const { return coordinate_array.size(); }
    +
    +#//!\name Element access
    +    coordT &            at(countT idx) { return coordinate_array.at(idx); }
    +    const coordT &      at(countT idx) const { return coordinate_array.at(idx); }
    +    coordT &            back() { return coordinate_array.back(); }
    +    const coordT &      back() const { return coordinate_array.back(); }
    +    coordT &            first() { return front(); }
    +    const coordT &      first() const { return front(); }
    +    coordT &            front() { return coordinate_array.front(); }
    +    const coordT &      front() const { return coordinate_array.front(); }
    +    coordT &            last() { return back(); }
    +    const coordT &      last() const { return back(); }
    +    Coordinates         mid(countT idx, countT length= -1) const; //!<\todo countT -1 indicates
    +    coordT &            operator[](countT idx) { return coordinate_array.operator[](idx); }
    +    const coordT &      operator[](countT idx) const { return coordinate_array.operator[](idx); }
    +    coordT              value(countT idx, const coordT &defaultValue) const;
    +
    +#//!\name Iterator
    +    iterator            begin() { return iterator(coordinate_array.begin()); }
    +    const_iterator      begin() const { return const_iterator(coordinate_array.begin()); }
    +    const_iterator      constBegin() const { return begin(); }
    +    const_iterator      constEnd() const { return end(); }
    +    iterator            end() { return iterator(coordinate_array.end()); }
    +    const_iterator      end() const { return const_iterator(coordinate_array.end()); }
    +
    +#//!\name GetSet
    +    Coordinates         operator+(const Coordinates &other) const;
    +
    +#//!\name Modify
    +    void                append(int pointDimension, coordT *c);
    +    void                append(const coordT &c) { push_back(c); }
    +    void                clear() { coordinate_array.clear(); }
    +    iterator            erase(iterator idx) { return iterator(coordinate_array.erase(idx.base())); }
    +    iterator            erase(iterator beginIterator, iterator endIterator) { return iterator(coordinate_array.erase(beginIterator.base(), endIterator.base())); }
    +    void                insert(countT before, const coordT &c) { insert(begin()+before, c); }
    +    iterator            insert(iterator before, const coordT &c) { return iterator(coordinate_array.insert(before.base(), c)); }
    +    void                move(countT from, countT to) { insert(to, takeAt(from)); }
    +    Coordinates &       operator+=(const Coordinates &other);
    +    Coordinates &       operator+=(const coordT &c) { append(c); return *this; }
    +    Coordinates &       operator<<(const Coordinates &other) { return *this += other; }
    +    Coordinates &       operator<<(const coordT &c) { return *this += c; }
    +    void                pop_back() { coordinate_array.pop_back(); }
    +    void                pop_front() { removeFirst(); }
    +    void                prepend(const coordT &c) { insert(begin(), c); }
    +    void                push_back(const coordT &c) { coordinate_array.push_back(c); }
    +    void                push_front(const coordT &c) { insert(begin(), c); }
    +                        //removeAll below
    +    void                removeAt(countT idx) { erase(begin()+idx); }
    +    void                removeFirst() { erase(begin()); }
    +    void                removeLast() { erase(--end()); }
    +    void                replace(countT idx, const coordT &c) { (*this)[idx]= c; }
    +    void                reserve(countT i) { coordinate_array.reserve(i); }
    +    void                swap(countT idx, countT other);
    +    coordT              takeAt(countT idx);
    +    coordT              takeFirst() { return takeAt(0); }
    +    coordT              takeLast();
    +
    +#//!\name Search
    +    bool                contains(const coordT &t) const;
    +    countT              count(const coordT &t) const;
    +    countT              indexOf(const coordT &t, countT from = 0) const;
    +    countT              lastIndexOf(const coordT &t, countT from = -1) const;
    +    void                removeAll(const coordT &t);
    +
    +#//!\name Coordinates::iterator -- from QhullPoints, forwarding to coordinate_array
    +    // before const_iterator for conversion with comparison operators
    +    // Reviewed corelib/tools/qlist.h and corelib/tools/qvector.h w/o QT_STRICT_ITERATORS
    +    class iterator {
    +
    +    private:
    +        std::vector::iterator i;
    +        friend class const_iterator;
    +
    +    public:
    +        typedef std::random_access_iterator_tag  iterator_category;
    +        typedef coordT      value_type;
    +        typedef value_type *pointer;
    +        typedef value_type &reference;
    +        typedef ptrdiff_t   difference_type;
    +
    +                        iterator() {}
    +                        iterator(const iterator &other) { i= other.i; }
    +        explicit        iterator(const std::vector::iterator &vi) { i= vi; }
    +        iterator &      operator=(const iterator &other) { i= other.i; return *this; }
    +        std::vector::iterator &base() { return i; }
    +        coordT &        operator*() const { return *i; }
    +        // No operator->() when the base type is double
    +        coordT &        operator[](countT idx) const { return i[idx]; }
    +
    +        bool            operator==(const iterator &other) const { return i==other.i; }
    +        bool            operator!=(const iterator &other) const { return i!=other.i; }
    +        bool            operator<(const iterator &other) const { return i(const iterator &other) const { return i>other.i; }
    +        bool            operator>=(const iterator &other) const { return i>=other.i; }
    +              // reinterpret_cast to break circular dependency
    +        bool            operator==(const Coordinates::const_iterator &other) const { return *this==reinterpret_cast(other); }
    +        bool            operator!=(const Coordinates::const_iterator &other) const { return *this!=reinterpret_cast(other); }
    +        bool            operator<(const Coordinates::const_iterator &other) const { return *this(other); }
    +        bool            operator<=(const Coordinates::const_iterator &other) const { return *this<=reinterpret_cast(other); }
    +        bool            operator>(const Coordinates::const_iterator &other) const { return *this>reinterpret_cast(other); }
    +        bool            operator>=(const Coordinates::const_iterator &other) const { return *this>=reinterpret_cast(other); }
    +
    +        iterator &      operator++() { ++i; return *this; }
    +        iterator        operator++(int) { return iterator(i++); }
    +        iterator &      operator--() { --i; return *this; }
    +        iterator        operator--(int) { return iterator(i--); }
    +        iterator &      operator+=(countT idx) { i += idx; return *this; }
    +        iterator &      operator-=(countT idx) { i -= idx; return *this; }
    +        iterator        operator+(countT idx) const { return iterator(i+idx); }
    +        iterator        operator-(countT idx) const { return iterator(i-idx); }
    +        difference_type operator-(iterator other) const { return i-other.i; }
    +    };//Coordinates::iterator
    +
    +#//!\name Coordinates::const_iterator
    +    class const_iterator {
    +
    +    private:
    +        std::vector::const_iterator i;
    +
    +    public:
    +        typedef std::random_access_iterator_tag  iterator_category;
    +        typedef coordT            value_type;
    +        typedef const value_type *pointer;
    +        typedef const value_type &reference;
    +        typedef ptrdiff_t         difference_type;
    +
    +                        const_iterator() {}
    +                        const_iterator(const const_iterator &other) { i= other.i; }
    +                        const_iterator(const iterator &o) : i(o.i) {}
    +        explicit        const_iterator(const std::vector::const_iterator &vi) { i= vi; }
    +        const_iterator &operator=(const const_iterator &other) { i= other.i; return *this; }
    +        const coordT &  operator*() const { return *i; }
    +        // No operator->() when the base type is double
    +        const coordT &  operator[](countT idx) const { return i[idx]; }
    +
    +        bool            operator==(const const_iterator &other) const { return i==other.i; }
    +        bool            operator!=(const const_iterator &other) const { return i!=other.i; }
    +        bool            operator<(const const_iterator &other) const { return i(const const_iterator &other) const { return i>other.i; }
    +        bool            operator>=(const const_iterator &other) const { return i>=other.i; }
    +
    +        const_iterator & operator++() { ++i; return *this; } 
    +        const_iterator  operator++(int) { return const_iterator(i++); }
    +        const_iterator & operator--() { --i; return *this; }
    +        const_iterator  operator--(int) { return const_iterator(i--); }
    +        const_iterator & operator+=(countT idx) { i += idx; return *this; }
    +        const_iterator & operator-=(countT idx) { i -= idx; return *this; }
    +        const_iterator  operator+(countT idx) const { return const_iterator(i+idx); }
    +        const_iterator  operator-(countT idx) const { return const_iterator(i-idx); }
    +        difference_type operator-(const_iterator other) const { return i-other.i; }
    +    };//Coordinates::const_iterator
    +
    +};//Coordinates
    +
    +//class CoordinatesIterator
    +//QHULL_DECLARE_SEQUENTIAL_ITERATOR(Coordinates, coordT)
    +
    +class CoordinatesIterator
    +{
    +    typedef Coordinates::const_iterator const_iterator;
    +
    +private:
    +    const Coordinates * c;
    +    const_iterator      i;
    +
    +public:
    +                        CoordinatesIterator(const Coordinates &container): c(&container), i(c->constBegin()) {}
    +    CoordinatesIterator &operator=(const Coordinates &container) { c= &container; i= c->constBegin(); return *this; }
    +                        ~CoordinatesIterator() {}
    +
    +    bool                findNext(const coordT &t) { while (i != c->constEnd()) if(*i++ == t){ return true;} return false; }
    +    bool                findPrevious(const coordT &t) { while (i != c->constBegin())if (*(--i) == t){ return true;} return false;  }
    +    bool                hasNext() const { return i != c->constEnd(); }
    +    bool                hasPrevious() const { return i != c->constBegin(); }
    +    const coordT &      next() { return *i++; }
    +    const coordT &      previous() { return *--i; }
    +    const coordT &      peekNext() const { return *i; }
    +    const coordT &      peekPrevious() const { const_iterator p= i; return *--p; }
    +    void                toFront() { i= c->constBegin(); }
    +    void                toBack() { i= c->constEnd(); }
    +};//CoordinatesIterator
    +
    +//class MutableCoordinatesIterator
    +//QHULL_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR(Coordinates, coordT)
    +class MutableCoordinatesIterator
    +{
    +    typedef Coordinates::iterator iterator;
    +    typedef Coordinates::const_iterator const_iterator;
    +
    +private:
    +    Coordinates *       c;
    +    iterator            i;
    +    iterator            n;
    +    bool                item_exists() const { return const_iterator(n) != c->constEnd(); }
    +
    +public:
    +                        MutableCoordinatesIterator(Coordinates &container) : c(&container) { i= c->begin(); n= c->end(); }
    +    MutableCoordinatesIterator &operator=(Coordinates &container) { c= &container; i= c->begin(); n= c->end(); return *this; }
    +                        ~MutableCoordinatesIterator() {}
    +
    +    bool                findNext(const coordT &t) { while(c->constEnd()!=const_iterator(n= i)){ if(*i++==t){ return true;}} return false; }
    +    bool                findPrevious(const coordT &t) { while(c->constBegin()!=const_iterator(i)){ if(*(n= --i)== t){ return true;}} n= c->end(); return false;  }
    +    bool                hasNext() const { return (c->constEnd()!=const_iterator(i)); }
    +    bool                hasPrevious() const { return (c->constBegin()!=const_iterator(i)); }
    +    void                insert(const coordT &t) { n= i= c->insert(i, t); ++i; }
    +    coordT &            next() { n= i++; return *n; }
    +    coordT &            peekNext() const { return *i; }
    +    coordT &            peekPrevious() const { iterator p= i; return *--p; }
    +    coordT &            previous() { n= --i; return *n; }
    +    void                remove() { if(c->constEnd()!=const_iterator(n)){ i= c->erase(n); n= c->end();} }
    +    void                setValue(const coordT &t) const { if(c->constEnd()!=const_iterator(n)){ *n= t;} }
    +    void                toFront() { i= c->begin(); n= c->end(); }
    +    void                toBack() { i= c->end(); n= i; }
    +    coordT &            value() { QHULL_ASSERT(item_exists()); return *n; }
    +    const coordT &      value() const { QHULL_ASSERT(item_exists()); return *n; }
    +};//MutableCoordinatesIterator
    +
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::Coordinates &c);
    +
    +#endif // QHCOORDINATES_H
    diff --git a/xs/src/qhull/src/libqhullcpp/PointCoordinates.cpp b/xs/src/qhull/src/libqhullcpp/PointCoordinates.cpp
    new file mode 100644
    index 0000000000..a5b71e901d
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/PointCoordinates.cpp
    @@ -0,0 +1,348 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/PointCoordinates.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/PointCoordinates.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullPoint.h"
    +
    +#include 
    +#include 
    +
    +using std::istream;
    +using std::string;
    +using std::ws;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//! PointCoordinates -- vector of PointCoordinates
    +
    +#//!\name Constructors
    +
    +PointCoordinates::
    +PointCoordinates()
    +: QhullPoints()
    +, point_coordinates()
    +, describe_points()
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const std::string &aComment)
    +: QhullPoints()
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(int pointDimension, const std::string &aComment)
    +: QhullPoints()
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +}
    +
    +//! Qhull and QhullQh constructors are the same
    +PointCoordinates::
    +PointCoordinates(const Qhull &q)
    +: QhullPoints(q)
    +, point_coordinates()
    +, describe_points()
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const Qhull &q, const std::string &aComment)
    +: QhullPoints(q)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const Qhull &q, int pointDimension, const std::string &aComment)
    +: QhullPoints(q)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const Qhull &q, int pointDimension, const std::string &aComment, countT coordinatesCount, const coordT *c)
    +: QhullPoints(q)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +    append(coordinatesCount, c);
    +}
    +
    +PointCoordinates::
    +PointCoordinates(QhullQh *qqh)
    +: QhullPoints(qqh)
    +, point_coordinates()
    +, describe_points()
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(QhullQh *qqh, const std::string &aComment)
    +: QhullPoints(qqh)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(QhullQh *qqh, int pointDimension, const std::string &aComment)
    +: QhullPoints(qqh)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +}
    +
    +PointCoordinates::
    +PointCoordinates(QhullQh *qqh, int pointDimension, const std::string &aComment, countT coordinatesCount, const coordT *c)
    +: QhullPoints(qqh)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +    append(coordinatesCount, c);
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const PointCoordinates &other)
    +: QhullPoints(other)
    +, point_coordinates(other.point_coordinates)
    +, describe_points(other.describe_points)
    +{
    +    makeValid();  // Update point_first and point_end
    +}
    +
    +PointCoordinates & PointCoordinates::
    +operator=(const PointCoordinates &other)
    +{
    +    QhullPoints::operator=(other);
    +    point_coordinates= other.point_coordinates;
    +    describe_points= other.describe_points;
    +    makeValid(); // Update point_first and point_end
    +    return *this;
    +}//operator=
    +
    +PointCoordinates::
    +~PointCoordinates()
    +{ }
    +
    +#//!\name GetSet
    +
    +void PointCoordinates::
    +checkValid() const
    +{
    +    if(getCoordinates().data()!=data()
    +    || getCoordinates().count()!=coordinateCount()){
    +        throw QhullError(10060, "Qhull error: first point (%x) is not PointCoordinates.data() or count (%d) is not PointCoordinates.count (%d)", coordinateCount(), getCoordinates().count(), 0.0, data());
    +    }
    +}//checkValid
    +
    +void PointCoordinates::
    +setDimension(int i)
    +{
    +    if(i<0){
    +        throw QhullError(10062, "Qhull error: can not set PointCoordinates dimension to %d", i);
    +    }
    +    int currentDimension=QhullPoints::dimension();
    +    if(currentDimension!=0 && i!=currentDimension){
    +        throw QhullError(10063, "Qhull error: can not change PointCoordinates dimension (from %d to %d)", currentDimension, i);
    +    }
    +    QhullPoints::setDimension(i);
    +}//setDimension
    +
    +#//!\name Foreach
    +
    +Coordinates::ConstIterator PointCoordinates::
    +beginCoordinates(countT pointIndex) const
    +{
    +    return point_coordinates.begin()+indexOffset(pointIndex);
    +}
    +
    +Coordinates::Iterator PointCoordinates::
    +beginCoordinates(countT pointIndex)
    +{
    +    return point_coordinates.begin()+indexOffset(pointIndex);
    +}
    +
    +#//!\name Methods
    +
    +void PointCoordinates::
    +append(countT coordinatesCount, const coordT *c)
    +{
    +    if(coordinatesCount<=0){
    +        return;
    +    }
    +    if(includesCoordinates(c)){
    +        throw QhullError(10065, "Qhull error: can not append a subset of PointCoordinates to itself.  The coordinates for point %d may move.", indexOf(c, QhullError::NOthrow));
    +    }
    +    reserveCoordinates(coordinatesCount);
    +    std::copy(c, c+coordinatesCount, std::back_inserter(point_coordinates));
    +    makeValid();
    +}//append coordT
    +
    +void PointCoordinates::
    +append(const PointCoordinates &other)
    +{
    +    setDimension(other.dimension());
    +    append(other.coordinateCount(), other.data());
    +}//append PointCoordinates
    +
    +void PointCoordinates::
    +append(const QhullPoint &p)
    +{
    +    setDimension(p.dimension());
    +    append(p.dimension(), p.coordinates());
    +}//append QhullPoint
    +
    +void PointCoordinates::
    +appendComment(const std::string &s){
    +    if(char c= s[0] && describe_points.empty()){
    +        if(c=='-' || isdigit(c)){
    +            throw QhullError(10028, "Qhull argument error: comments can not start with a number or minus, %s", 0, 0, 0.0, s.c_str());
    +        }
    +    }
    +    describe_points += s;
    +}//appendComment
    +
    +//! Read PointCoordinates from istream.  First two numbers are dimension and count.  A non-digit starts a rboxCommand.
    +//! Overwrites describe_points.  See qh_readpoints [io.c]
    +void PointCoordinates::
    +appendPoints(istream &in)
    +{
    +    int inDimension;
    +    countT inCount;
    +    in >> ws >> inDimension >> ws;
    +    if(!in.good()){
    +        in.clear();
    +        string remainder;
    +        getline(in, remainder);
    +        throw QhullError(10005, "Qhull error: input did not start with dimension or count -- %s", 0, 0, 0, remainder.c_str());
    +    }
    +    char c= (char)in.peek();
    +    if(c!='-' && !isdigit(c)){         // Comments start with a non-digit
    +        getline(in, describe_points);
    +        in >> ws;
    +    }
    +    in >> inCount >> ws;
    +    if(!in.good()){
    +        in.clear();
    +        string remainder;
    +        getline(in, remainder);
    +        throw QhullError(10009, "Qhull error: input did not start with dimension and count -- %d %s", inDimension, 0, 0, remainder.c_str());
    +    }
    +    c= (char)in.peek();
    +    if(c!='-' && !isdigit(c)){         // Comments start with a non-digit
    +        getline(in, describe_points);
    +        in >> ws;
    +    }
    +    if(inCount> p >> ws;
    +        if(in.fail()){
    +            in.clear();
    +            string remainder;
    +            getline(in, remainder);
    +            throw QhullError(10008, "Qhull error: failed to read coordinate %d  of point %d\n   %s", coordinatesCount % inDimension, coordinatesCount/inDimension, 0, remainder.c_str());
    +        }else{
    +            point_coordinates.push_back(p);
    +            coordinatesCount++;
    +        }
    +    }
    +    if(coordinatesCount != inCount*inDimension){
    +        if(coordinatesCount%inDimension==0){
    +            throw QhullError(10006, "Qhull error: expected %d %d-d PointCoordinates but read %i PointCoordinates", int(inCount), inDimension, 0.0, int(coordinatesCount/inDimension));
    +        }else{
    +            throw QhullError(10012, "Qhull error: expected %d %d-d PointCoordinates but read %i PointCoordinates plus %f extra coordinates", inCount, inDimension, float(coordinatesCount%inDimension), coordinatesCount/inDimension);
    +        }
    +    }
    +    makeValid();
    +}//appendPoints istream
    +
    +PointCoordinates PointCoordinates::
    +operator+(const PointCoordinates &other) const
    +{
    +    PointCoordinates pc= *this;
    +    pc << other;
    +    return pc;
    +}//operator+
    +
    +void PointCoordinates::
    +reserveCoordinates(countT newCoordinates)
    +{
    +    // vector::reserve is not const
    +    point_coordinates.reserve((countT)point_coordinates.size()+newCoordinates); // WARN64
    +    makeValid();
    +}//reserveCoordinates
    +
    +#//!\name Helpers
    +
    +countT PointCoordinates::
    +indexOffset(countT i) const {
    +    countT n= i*dimension();
    +    countT coordinatesCount= point_coordinates.count();
    +    if(i<0 || n>coordinatesCount){
    +        throw QhullError(10061, "Qhull error: point_coordinates is too short (%d) for point %d", coordinatesCount, i);
    +    }
    +    return n;
    +}
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +
    +using orgQhull::Coordinates;
    +using orgQhull::PointCoordinates;
    +
    +ostream&
    +operator<<(ostream &os, const PointCoordinates &p)
    +{
    +    p.checkValid();
    +    countT count= p.count();
    +    int dimension= p.dimension();
    +    string comment= p.comment();
    +    if(comment.empty()){
    +        os << dimension << endl;
    +    }else{
    +        os << dimension << " " << comment << endl;
    +    }
    +    os << count << endl;
    +    Coordinates::ConstIterator c= p.beginCoordinates();
    +    for(countT i=0; i
    +#include 
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! QhullPoints with Coordinates and description
    +    //! Inherited by RboxPoints
    +    class PointCoordinates;
    +
    +class PointCoordinates : public QhullPoints {
    +
    +private:
    +#//!\name Fields
    +    Coordinates         point_coordinates;      //! std::vector of point coordinates
    +                                                //! may have extraCoordinates()
    +    std::string         describe_points;          //! Comment describing PointCoordinates
    +
    +public:
    +#//!\name Construct
    +    //! QhullPoint, PointCoordinates, and QhullPoints have similar constructors
    +    //! If Qhull/QhullQh is not initialized, then dimension()==0                        PointCoordinates();
    +                        PointCoordinates();
    +    explicit            PointCoordinates(const std::string &aComment);
    +                        PointCoordinates(int pointDimension, const std::string &aComment);
    +                        //! Qhull/QhullQh used for dimension() and QhullPoint equality
    +    explicit            PointCoordinates(const Qhull &q);
    +                        PointCoordinates(const Qhull &q, const std::string &aComment);
    +                        PointCoordinates(const Qhull &q, int pointDimension, const std::string &aComment);
    +                        PointCoordinates(const Qhull &q, int pointDimension, const std::string &aComment, countT coordinatesCount, const coordT *c); // may be invalid
    +                        //! Use append() and appendPoints() for Coordinates and vector
    +    explicit            PointCoordinates(QhullQh *qqh);
    +                        PointCoordinates(QhullQh *qqh, const std::string &aComment);
    +                        PointCoordinates(QhullQh *qqh, int pointDimension, const std::string &aComment);
    +                        PointCoordinates(QhullQh *qqh, int pointDimension, const std::string &aComment, countT coordinatesCount, const coordT *c); // may be invalid
    +                        //! Use append() and appendPoints() for Coordinates and vector
    +                        PointCoordinates(const PointCoordinates &other);
    +    PointCoordinates &  operator=(const PointCoordinates &other);
    +                        ~PointCoordinates();
    +
    +#//!\name Convert
    +    //! QhullPoints coordinates, constData, data, count, size
    +#ifndef QHULL_NO_STL
    +    void                append(const std::vector &otherCoordinates) { if(!otherCoordinates.empty()){ append((int)otherCoordinates.size(), &otherCoordinates[0]); } }
    +    std::vector toStdVector() const { return point_coordinates.toStdVector(); }
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    void                append(const QList &pointCoordinates) { if(!pointCoordinates.isEmpty()){ append(pointCoordinates.count(), &pointCoordinates[0]); } }
    +    QList       toQList() const { return point_coordinates.toQList(); }
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +    //! See QhullPoints for coordinates, coordinateCount, dimension, empty, isEmpty, ==, !=
    +    void                checkValid() const;
    +    std::string         comment() const { return describe_points; }
    +    void                makeValid() { defineAs(point_coordinates.count(), point_coordinates.data()); }
    +    const Coordinates & getCoordinates() const { return point_coordinates; }
    +    void                setComment(const std::string &s) { describe_points= s; }
    +    void                setDimension(int i);
    +
    +private:
    +    //! disable QhullPoints.defineAs()
    +    void                defineAs(countT coordinatesCount, coordT *c) { QhullPoints::defineAs(coordinatesCount, c); }
    +public:
    +
    +#//!\name ElementAccess
    +    //! See QhullPoints for at, back, first, front, last, mid, [], value
    +
    +#//!\name Foreach
    +    //! See QhullPoints for begin, constBegin, end
    +    Coordinates::ConstIterator  beginCoordinates() const { return point_coordinates.begin(); }
    +    Coordinates::Iterator       beginCoordinates() { return point_coordinates.begin(); }
    +    Coordinates::ConstIterator  beginCoordinates(countT pointIndex) const;
    +    Coordinates::Iterator       beginCoordinates(countT pointIndex);
    +    Coordinates::ConstIterator  endCoordinates() const { return point_coordinates.end(); }
    +    Coordinates::Iterator       endCoordinates() { return point_coordinates.end(); }
    +
    +#//!\name Search
    +    //! See QhullPoints for contains, count, indexOf, lastIndexOf
    +
    +#//!\name GetSet
    +    PointCoordinates    operator+(const PointCoordinates &other) const;
    +
    +#//!\name Modify
    +    //FIXUP QH11001: Add clear() and other modify operators from Coordinates.h.  Include QhullPoint::operator=()
    +    void                append(countT coordinatesCount, const coordT *c);  //! Dimension previously defined
    +    void                append(const coordT &c) { append(1, &c); } //! Dimension previously defined
    +    void                append(const QhullPoint &p);
    +    //! See convert for std::vector and QList
    +    void                append(const Coordinates &c) { append(c.count(), c.data()); }
    +    void                append(const PointCoordinates &other);
    +    void                appendComment(const std::string &s);
    +    void                appendPoints(std::istream &in);
    +    PointCoordinates &  operator+=(const PointCoordinates &other) { append(other); return *this; }
    +    PointCoordinates &  operator+=(const coordT &c) { append(c); return *this; }
    +    PointCoordinates &  operator+=(const QhullPoint &p) { append(p); return *this; }
    +    PointCoordinates &  operator<<(const PointCoordinates &other) { return *this += other; }
    +    PointCoordinates &  operator<<(const coordT &c) { return *this += c; }
    +    PointCoordinates &  operator<<(const QhullPoint &p) { return *this += p; }
    +    // reserve() is non-const
    +    void                reserveCoordinates(countT newCoordinates);
    +
    +#//!\name Helpers
    +private:
    +    int                 indexOffset(int i) const;
    +
    +};//PointCoordinates
    +
    +// No references to QhullPoint.  Prevents use of QHULL_DECLARE_SEQUENTIAL_ITERATOR(PointCoordinates, QhullPoint)
    +class PointCoordinatesIterator
    +{
    +    typedef PointCoordinates::const_iterator const_iterator;
    +
    +private:
    +    const PointCoordinates *c;
    +    const_iterator      i;
    +
    +public:
    +                        PointCoordinatesIterator(const PointCoordinates &container) : c(&container), i(c->constBegin()) {}
    +                        PointCoordinatesIterator &operator=(const PointCoordinates &container) { c = &container; i = c->constBegin(); return *this; }
    +
    +    void                toFront() { i = c->constBegin(); }
    +    void                toBack() { i = c->constEnd(); }
    +    bool                hasNext() const { return i != c->constEnd(); }
    +    const QhullPoint    next() { return *i++; }
    +    const QhullPoint    peekNext() const { return *i; }
    +    bool                hasPrevious() const { return i != c->constBegin(); }
    +    const QhullPoint    previous() { return *--i; }
    +    const QhullPoint    peekPrevious() const { const_iterator p = i; return *--p; }
    +    bool                findNext(const QhullPoint &t) { while(i != c->constEnd()){ if (*i++ == t) return true;} return false; }
    +    bool                findPrevious(const QhullPoint &t) { while(i != c->constBegin()){ if (*(--i) == t) return true;} return false;  }
    +};//CoordinatesIterator
    +
    +// FIXUP QH11002:  Add MutablePointCoordinatesIterator after adding modify operators
    +\
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &          operator<<(std::ostream &os, const orgQhull::PointCoordinates &p);
    +
    +#endif // QHPOINTCOORDINATES_H
    diff --git a/xs/src/qhull/src/libqhullcpp/Qhull.cpp b/xs/src/qhull/src/libqhullcpp/Qhull.cpp
    new file mode 100644
    index 0000000000..7124a15cdc
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/Qhull.cpp
    @@ -0,0 +1,352 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/Qhull.cpp#4 $$Change: 2078 $
    +** $DateTime: 2016/02/07 16:53:56 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! Qhull -- invoke qhull from C++
    +#//! Compile libqhull_r and Qhull together due to use of setjmp/longjmp()
    +
    +#include "libqhullcpp/Qhull.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullQh.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +
    +#include 
    +
    +using std::cerr;
    +using std::string;
    +using std::vector;
    +using std::ostream;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Global variables
    +
    +const char s_unsupported_options[]=" Fd TI ";
    +const char s_not_output_options[]= " Fd TI A C d E H P Qb QbB Qbb Qc Qf Qg Qi Qm QJ Qr QR Qs Qt Qv Qx Qz Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 Q10 Q11 R Tc TC TM TP TR Tv TV TW U v V W ";
    +
    +#//!\name Constructor, destructor, etc.
    +Qhull::
    +Qhull()
    +: qh_qh(0)
    +, origin_point()
    +, run_called(false)
    +, feasible_point()
    +{
    +    allocateQhullQh();
    +}//Qhull
    +
    +//! Invokes Qhull on rboxPoints
    +//! Same as runQhull()
    +//! For rbox commands, see http://www.qhull.org/html/rbox.htm or html/rbox.htm
    +//! For qhull commands, see http://www.qhull.org/html/qhull.htm or html/qhull.htm
    +Qhull::
    +Qhull(const RboxPoints &rboxPoints, const char *qhullCommand2)
    +: qh_qh(0)
    +, origin_point()
    +, run_called(false)
    +, feasible_point()
    +{
    +    allocateQhullQh();
    +    runQhull(rboxPoints, qhullCommand2);
    +}//Qhull rbox
    +
    +//! Invokes Qhull on a set of input points
    +//! Same as runQhull()
    +//! For qhull commands, see http://www.qhull.org/html/qhull.htm or html/qhull.htm
    +Qhull::
    +Qhull(const char *inputComment2, int pointDimension, int pointCount, const realT *pointCoordinates, const char *qhullCommand2)
    +: qh_qh(0)
    +, origin_point()
    +, run_called(false)
    +, feasible_point()
    +{
    +    allocateQhullQh();
    +    runQhull(inputComment2, pointDimension, pointCount, pointCoordinates, qhullCommand2);
    +}//Qhull points
    +
    +void Qhull::
    +allocateQhullQh()
    +{
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +    qh_qh= new QhullQh;
    +    void *p= qh_qh;
    +    void *p2= static_cast(qh_qh);
    +    char *s= static_cast(p);
    +    char *s2= static_cast(p2);
    +    if(s!=s2){
    +        throw QhullError(10074, "Qhull error: QhullQh at a different address than base type QhT (%d bytes).  Please report compiler to qhull.org", int(s2-s));
    +    }
    +}//allocateQhullQh
    +
    +Qhull::
    +~Qhull() throw()
    +{
    +    // Except for cerr, does not throw errors
    +    if(qh_qh->hasQhullMessage()){
    +        cerr<< "\nQhull output at end\n"; //FIXUP QH11005: where should error and log messages go on ~Qhull?
    +        cerr<< qh_qh->qhullMessage();
    +        qh_qh->clearQhullMessage();
    +    }
    +    delete qh_qh;
    +    qh_qh= 0;
    +}//~Qhull
    +
    +#//!\name GetSet
    +
    +void Qhull::
    +checkIfQhullInitialized()
    +{
    +    if(!initialized()){ // qh_initqhull_buffers() not called
    +        throw QhullError(10023, "Qhull error: checkIfQhullInitialized failed.  Call runQhull() first.");
    +    }
    +}//checkIfQhullInitialized
    +
    +//! Return feasiblePoint for halfspace intersection
    +//! If called before runQhull(), then it returns the value from setFeasiblePoint.  qh.feasible_string overrides this value if it is defined.
    +Coordinates Qhull::
    +feasiblePoint() const
    +{
    +    Coordinates result;
    +    if(qh_qh->feasible_point){
    +        result.append(qh_qh->hull_dim, qh_qh->feasible_point);
    +    }else{
    +        result= feasible_point;
    +    }
    +    return result;
    +}//feasiblePoint
    +
    +//! Return origin point for qh.input_dim
    +QhullPoint Qhull::
    +inputOrigin()
    +{
    +    QhullPoint result= origin();
    +    result.setDimension(qh_qh->input_dim);
    +    return result;
    +}//inputOrigin
    +
    +#//!\name GetValue
    +
    +double Qhull::
    +area(){
    +    checkIfQhullInitialized();
    +    if(!qh_qh->hasAreaVolume){
    +        QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +            qh_getarea(qh_qh, qh_qh->facet_list);
    +        }
    +        qh_qh->NOerrexit= true;
    +        qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    }
    +    return qh_qh->totarea;
    +}//area
    +
    +double Qhull::
    +volume(){
    +    checkIfQhullInitialized();
    +    if(!qh_qh->hasAreaVolume){
    +        QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +            qh_getarea(qh_qh, qh_qh->facet_list);
    +        }
    +        qh_qh->NOerrexit= true;
    +        qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    }
    +    return qh_qh->totvol;
    +}//volume
    +
    +#//!\name Foreach
    +
    +//! Define QhullVertex::neighborFacets().
    +//! Automatically called if merging facets or computing the Voronoi diagram.
    +//! Noop if called multiple times.
    +void Qhull::
    +defineVertexNeighborFacets(){
    +    checkIfQhullInitialized();
    +    if(!qh_qh->hasAreaVolume){
    +        QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +            qh_vertexneighbors(qh_qh);
    +        }
    +        qh_qh->NOerrexit= true;
    +        qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    }
    +}//defineVertexNeighborFacets
    +
    +QhullFacetList Qhull::
    +facetList() const{
    +    return QhullFacetList(beginFacet(), endFacet());
    +}//facetList
    +
    +QhullPoints Qhull::
    +points() const
    +{
    +    return QhullPoints(qh_qh, qh_qh->hull_dim, qh_qh->num_points*qh_qh->hull_dim, qh_qh->first_point);
    +}//points
    +
    +QhullPointSet Qhull::
    +otherPoints() const
    +{
    +    return QhullPointSet(qh_qh, qh_qh->other_points);
    +}//otherPoints
    +
    +//! Return vertices of the convex hull.
    +QhullVertexList Qhull::
    +vertexList() const{
    +    return QhullVertexList(beginVertex(), endVertex());
    +}//vertexList
    +
    +#//!\name Methods
    +
    +void Qhull::
    +outputQhull()
    +{
    +    checkIfQhullInitialized();
    +    QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +        qh_produce_output2(qh_qh);
    +    }
    +    qh_qh->NOerrexit= true;
    +    qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +}//outputQhull
    +
    +void Qhull::
    +outputQhull(const char *outputflags)
    +{
    +    checkIfQhullInitialized();
    +    string cmd(" "); // qh_checkflags skips first word
    +    cmd += outputflags;
    +    char *command= const_cast(cmd.c_str());
    +    QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +        qh_clear_outputflags(qh_qh);
    +        char *s = qh_qh->qhull_command + strlen(qh_qh->qhull_command) + 1; //space
    +        strncat(qh_qh->qhull_command, command, sizeof(qh_qh->qhull_command)-strlen(qh_qh->qhull_command)-1);
    +        qh_checkflags(qh_qh, command, const_cast(s_not_output_options));
    +        qh_initflags(qh_qh, s);
    +        qh_initqhull_outputflags(qh_qh);
    +        if(qh_qh->KEEPminArea < REALmax/2
    +           || (0 != qh_qh->KEEParea + qh_qh->KEEPmerge + qh_qh->GOODvertex
    +                    + qh_qh->GOODthreshold + qh_qh->GOODpoint + qh_qh->SPLITthresholds)){
    +            facetT *facet;
    +            qh_qh->ONLYgood= False;
    +            FORALLfacet_(qh_qh->facet_list) {
    +                facet->good= True;
    +            }
    +            qh_prepare_output(qh_qh);
    +        }
    +        qh_produce_output2(qh_qh);
    +        if(qh_qh->VERIFYoutput && !qh_qh->STOPpoint && !qh_qh->STOPcone){
    +            qh_check_points(qh_qh);
    +        }
    +    }
    +    qh_qh->NOerrexit= true;
    +    qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +}//outputQhull
    +
    +//! For qhull commands, see http://www.qhull.org/html/qhull.htm or html/qhull.htm
    +void Qhull::
    +runQhull(const RboxPoints &rboxPoints, const char *qhullCommand2)
    +{
    +    runQhull(rboxPoints.comment().c_str(), rboxPoints.dimension(), rboxPoints.count(), &*rboxPoints.coordinates(), qhullCommand2);
    +}//runQhull, RboxPoints
    +
    +//! pointCoordinates is a array of points, input sites ('d' or 'v'), or halfspaces with offset last ('H')
    +//! Derived from qh_new_qhull [user.c]
    +//! For rbox commands, see http://www.qhull.org/html/rbox.htm or html/rbox.htm
    +//! For qhull commands, see http://www.qhull.org/html/qhull.htm or html/qhull.htm
    +void Qhull::
    +runQhull(const char *inputComment, int pointDimension, int pointCount, const realT *pointCoordinates, const char *qhullCommand)
    +{
    +  /* gcc may issue a "might be clobbered" warning for pointDimension and pointCoordinates [-Wclobbered].
    +     These parameters are not referenced after a longjmp() and hence not clobbered.
    +     See http://stackoverflow.com/questions/7721854/what-sense-do-these-clobbered-variable-warnings-make */
    +    if(run_called){
    +        throw QhullError(10027, "Qhull error: runQhull called twice.  Only one call allowed.");
    +    }
    +    run_called= true;
    +    string s("qhull ");
    +    s += qhullCommand;
    +    char *command= const_cast(s.c_str());
    +    /************* Expansion of QH_TRY_ for debugging
    +    int QH_TRY_status;
    +    if(qh_qh->NOerrexit){
    +        qh_qh->NOerrexit= False;
    +        QH_TRY_status= setjmp(qh_qh->errexit);
    +    }else{
    +        QH_TRY_status= QH_TRY_ERROR;
    +    }
    +    if(!QH_TRY_status){
    +    *************/
    +    QH_TRY_(qh_qh){ // no object creation -- destructors are skipped on longjmp()
    +        qh_checkflags(qh_qh, command, const_cast(s_unsupported_options));
    +        qh_initflags(qh_qh, command);
    +        *qh_qh->rbox_command= '\0';
    +        strncat( qh_qh->rbox_command, inputComment, sizeof(qh_qh->rbox_command)-1);
    +        if(qh_qh->DELAUNAY){
    +            qh_qh->PROJECTdelaunay= True;   // qh_init_B() calls qh_projectinput()
    +        }
    +        pointT *newPoints= const_cast(pointCoordinates);
    +        int newDimension= pointDimension;
    +        int newIsMalloc= False;
    +        if(qh_qh->HALFspace){
    +            --newDimension;
    +            initializeFeasiblePoint(newDimension);
    +            newPoints= qh_sethalfspace_all(qh_qh, pointDimension, pointCount, newPoints, qh_qh->feasible_point);
    +            newIsMalloc= True;
    +        }
    +        qh_init_B(qh_qh, newPoints, pointCount, newDimension, newIsMalloc);
    +        qh_qhull(qh_qh);
    +        qh_check_output(qh_qh);
    +        qh_prepare_output(qh_qh);
    +        if(qh_qh->VERIFYoutput && !qh_qh->STOPpoint && !qh_qh->STOPcone){
    +            qh_check_points(qh_qh);
    +        }
    +    }
    +    qh_qh->NOerrexit= true;
    +    for(int k= qh_qh->hull_dim; k--; ){  // Do not move into QH_TRY block.  It may throw an error
    +        origin_point << 0.0;
    +    }
    +    qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +}//runQhull
    +
    +#//!\name Helpers -- be careful of allocating C++ objects due to setjmp/longjmp() error handling by qh_... routines
    +
    +//! initialize qh.feasible_point for half-space intersection
    +//! Sets from qh.feasible_string if available, otherwise from Qhull::feasible_point
    +//! called only once from runQhull(), otherwise it leaks memory (the same as qh_setFeasible)
    +void Qhull::
    +initializeFeasiblePoint(int hulldim)
    +{
    +    if(qh_qh->feasible_string){
    +        qh_setfeasible(qh_qh, hulldim);
    +    }else{
    +        if(feasible_point.isEmpty()){
    +            qh_fprintf(qh_qh, qh_qh->ferr, 6209, "qhull error: missing feasible point for halfspace intersection.  Use option 'Hn,n' or Qhull::setFeasiblePoint before runQhull()\n");
    +            qh_errexit(qh_qh, qh_ERRmem, NULL, NULL);
    +        }
    +        if(feasible_point.size()!=(size_t)hulldim){
    +            qh_fprintf(qh_qh, qh_qh->ferr, 6210, "qhull error: dimension of feasiblePoint should be %d.  It is %u", hulldim, feasible_point.size());
    +            qh_errexit(qh_qh, qh_ERRmem, NULL, NULL);
    +        }
    +        if (!(qh_qh->feasible_point= (coordT*)qh_malloc(hulldim * sizeof(coordT)))) {
    +            qh_fprintf(qh_qh, qh_qh->ferr, 6202, "qhull error: insufficient memory for feasible point\n");
    +            qh_errexit(qh_qh, qh_ERRmem, NULL, NULL);
    +        }
    +        coordT *t= qh_qh->feasible_point;
    +        // No qh_... routines after here -- longjmp() ignores destructor
    +        for(Coordinates::ConstIterator p=feasible_point.begin(); p.  It could be rewritten for another vector class such as QList
    +   #define QHULL_USES_QT
    +      Supply conversions to QT
    +      qhulltest requires QT.  It is defined in RoadTest.h
    +
    +  #define QHULL_ASSERT
    +      Defined by QhullError.h
    +      It invokes assert()
    +*/
    +
    +#//!\name Used here
    +    class QhullFacetList;
    +    class QhullPoints;
    +    class QhullQh;
    +    class RboxPoints;
    +
    +#//!\name Defined here
    +    class Qhull;
    +
    +//! Interface to Qhull from C++
    +class Qhull {
    +
    +private:
    +#//!\name Members and friends
    +    QhullQh *           qh_qh;          //! qhT for this instance
    +    Coordinates         origin_point;   //! origin for qh_qh->hull_dim.  Set by runQhull()
    +    bool                run_called;     //! True at start of runQhull.  Errors if call again.
    +    Coordinates         feasible_point;  //! feasible point for half-space intersection (alternative to qh.feasible_string for qh.feasible_point)
    +
    +public:
    +#//!\name Constructors
    +                        Qhull();      //!< call runQhull() next
    +                        Qhull(const RboxPoints &rboxPoints, const char *qhullCommand2);
    +                        Qhull(const char *inputComment2, int pointDimension, int pointCount, const realT *pointCoordinates, const char *qhullCommand2);
    +                        ~Qhull() throw();
    +private:                //! Disable copy constructor and assignment.  Qhull owns QhullQh.
    +                        Qhull(const Qhull &);
    +    Qhull &             operator=(const Qhull &);
    +
    +private:
    +    void                allocateQhullQh();
    +
    +public:
    +
    +#//!\name GetSet
    +    void                checkIfQhullInitialized();
    +    int                 dimension() const { return qh_qh->input_dim; } //!< Dimension of input and result
    +    void                disableOutputStream() { qh_qh->disableOutputStream(); }
    +    void                enableOutputStream() { qh_qh->enableOutputStream(); }
    +    countT              facetCount() const { return qh_qh->num_facets; }
    +    Coordinates         feasiblePoint() const; 
    +    int                 hullDimension() const { return qh_qh->hull_dim; } //!< Dimension of the computed hull
    +    bool                hasOutputStream() const { return qh_qh->hasOutputStream(); }
    +    bool                initialized() const { return (qh_qh->hull_dim>0); }
    +    const char *        inputComment() const { return qh_qh->rbox_command; }
    +    QhullPoint          inputOrigin();
    +                        //! non-const due to QhullPoint
    +    QhullPoint          origin() { QHULL_ASSERT(initialized()); return QhullPoint(qh_qh, origin_point.data()); }
    +    QhullQh *           qh() const { return qh_qh; };
    +    const char *        qhullCommand() const { return qh_qh->qhull_command; }
    +    const char *        rboxCommand() const { return qh_qh->rbox_command; }
    +    int                 rotateRandom() const { return qh_qh->ROTATErandom; } //!< Return QRn for repeating QR0 runs
    +    void                setFeasiblePoint(const Coordinates &c) { feasible_point= c; } //!< Sets qh.feasible_point via initializeFeasiblePoint
    +    countT              vertexCount() const { return qh_qh->num_vertices; }
    +
    +#//!\name Delegated to QhullQh
    +    double              angleEpsilon() const { return qh_qh->angleEpsilon(); } //!< Epsilon for hyperplane angle equality
    +    void                appendQhullMessage(const std::string &s) { qh_qh->appendQhullMessage(s); }
    +    void                clearQhullMessage() { qh_qh->clearQhullMessage(); }
    +    double              distanceEpsilon() const { return qh_qh->distanceEpsilon(); } //!< Epsilon for distance to hyperplane
    +    double              factorEpsilon() const { return qh_qh->factorEpsilon(); }  //!< Factor for angleEpsilon and distanceEpsilon
    +    std::string         qhullMessage() const { return qh_qh->qhullMessage(); }
    +    bool                hasQhullMessage() const { return qh_qh->hasQhullMessage(); }
    +    int                 qhullStatus() const { return qh_qh->qhullStatus(); }
    +    void                setErrorStream(std::ostream *os) { qh_qh->setErrorStream(os); }
    +    void                setFactorEpsilon(double a) { qh_qh->setFactorEpsilon(a); }
    +    void                setOutputStream(std::ostream *os) { qh_qh->setOutputStream(os); }
    +
    +#//!\name ForEach
    +    QhullFacet          beginFacet() const { return QhullFacet(qh_qh, qh_qh->facet_list); }
    +    QhullVertex         beginVertex() const { return QhullVertex(qh_qh, qh_qh->vertex_list); }
    +    void                defineVertexNeighborFacets(); //!< Automatically called if merging facets or Voronoi diagram
    +    QhullFacet          endFacet() const { return QhullFacet(qh_qh, qh_qh->facet_tail); }
    +    QhullVertex         endVertex() const { return QhullVertex(qh_qh, qh_qh->vertex_tail); }
    +    QhullFacetList      facetList() const;
    +    QhullFacet          firstFacet() const { return beginFacet(); }
    +    QhullVertex         firstVertex() const { return beginVertex(); }
    +    QhullPoints         points() const;
    +    QhullPointSet       otherPoints() const;
    +                        //! Same as points().coordinates()
    +    coordT *            pointCoordinateBegin() const { return qh_qh->first_point; }
    +    coordT *            pointCoordinateEnd() const { return qh_qh->first_point + qh_qh->num_points*qh_qh->hull_dim; }
    +    QhullVertexList     vertexList() const;
    +
    +#//!\name Methods
    +    double              area();
    +    void                outputQhull();
    +    void                outputQhull(const char * outputflags);
    +    void                runQhull(const RboxPoints &rboxPoints, const char *qhullCommand2);
    +    void                runQhull(const char *inputComment2, int pointDimension, int pointCount, const realT *pointCoordinates, const char *qhullCommand2);
    +    double              volume();
    +
    +#//!\name Helpers
    +private:
    +    void                initializeFeasiblePoint(int hulldim);
    +};//Qhull
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLCPP_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullError.h b/xs/src/qhull/src/libqhullcpp/QhullError.h
    new file mode 100644
    index 0000000000..08d50aa0ff
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullError.h
    @@ -0,0 +1,62 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullError.h#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLERROR_H
    +#define QHULLERROR_H
    +
    +#include "libqhullcpp/RoadError.h"
    +// No dependencies on libqhull
    +
    +#ifndef QHULL_ASSERT
    +#define QHULL_ASSERT assert
    +#include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! QhullError -- std::exception class for Qhull
    +    class QhullError;
    +
    +class QhullError : public RoadError {
    +
    +public:
    +#//!\name Constants
    +    enum {
    +        QHULLfirstError= 10000, //MSG_QHULL_ERROR in Qhull's user.h
    +        QHULLlastError= 10078,
    +        NOthrow= 1 //! For flag to indexOf()
    +    };
    +
    +#//!\name Constructors
    +    // default constructors
    +    QhullError() : RoadError() {};
    +    QhullError(const QhullError &other) : RoadError(other) {}
    +    QhullError(int code, const std::string &message) : RoadError(code, message) {};
    +    QhullError(int code, const char *fmt) : RoadError(code, fmt) {};
    +    QhullError(int code, const char *fmt, int d) : RoadError(code, fmt, d) {};
    +    QhullError(int code, const char *fmt, int d, int d2) : RoadError(code, fmt, d, d2) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f) : RoadError(code, fmt, d, d2, f) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, const char *s) : RoadError(code, fmt, d, d2, f, s) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, const void *x) : RoadError(code, fmt, d, d2, f, x) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, int i) : RoadError(code, fmt, d, d2, f, i) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, long long i) : RoadError(code, fmt, d, d2, f, i) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, double e) : RoadError(code, fmt, d, d2, f, e) {};
    +    QhullError &operator=(const QhullError &other) { this->RoadError::operator=(other); return *this; }
    +    ~QhullError() throw() {}
    +
    +};//class QhullError
    +
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +inline std::ostream &operator<<(std::ostream &os, const orgQhull::QhullError &e) { return os << e.what(); }
    +
    +#endif // QHULLERROR_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacet.cpp b/xs/src/qhull/src/libqhullcpp/QhullFacet.cpp
    new file mode 100644
    index 0000000000..40d3828a4c
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacet.cpp
    @@ -0,0 +1,519 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacet.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullFacet -- Qhull's facet structure, facetT, as a C++ class
    +
    +#include "libqhullcpp/QhullFacet.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullSet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullPointSet.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullVertex.h"
    +
    +#include 
    +
    +using std::endl;
    +using std::ostream;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class objects
    +facetT QhullFacet::
    +s_empty_facet= {0,0,0,0,{0},
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0};
    +
    +#//!\name Constructors
    +
    +QhullFacet::
    +QhullFacet(const Qhull &q) 
    +: qh_facet(&s_empty_facet)
    +, qh_qh(q.qh())
    +{
    +}
    +
    +QhullFacet::
    +QhullFacet(const Qhull &q, facetT *f) 
    +: qh_facet(f ? f : &s_empty_facet)
    +, qh_qh(q.qh())
    +{
    +}
    +
    +#//!\name GetSet
    +
    +//! Return voronoi center or facet centrum.  Derived from qh_printcenter [io_r.c]
    +//! if printFormat=qh_PRINTtriangles and qh.DELAUNAY, returns centrum of a Delaunay facet
    +//! Sets center if needed
    +//! Code duplicated for PrintCenter and getCenter
    +//! Returns QhullPoint() if none or qh_INFINITE
    +QhullPoint QhullFacet::
    +getCenter(qh_PRINT printFormat)
    +{
    +    if(!qh_qh){
    +        // returns QhullPoint()
    +    }else if(qh_qh->CENTERtype==qh_ASvoronoi){
    +        if(!qh_facet->normal || !qh_facet->upperdelaunay || !qh_qh->ATinfinity){
    +            if(!qh_facet->center){
    +                QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +                    qh_facet->center= qh_facetcenter(qh_qh, qh_facet->vertices);
    +                }
    +                qh_qh->NOerrexit= true;
    +                qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +            }
    +            return QhullPoint(qh_qh, qh_qh->hull_dim-1, qh_facet->center);
    +        }
    +    }else if(qh_qh->CENTERtype==qh_AScentrum){
    +        volatile int numCoords= qh_qh->hull_dim;
    +        if(printFormat==qh_PRINTtriangles && qh_qh->DELAUNAY){
    +            numCoords--;
    +        }
    +        if(!qh_facet->center){
    +            QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +                qh_facet->center= qh_getcentrum(qh_qh, getFacetT());
    +            }
    +            qh_qh->NOerrexit= true;
    +            qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +        }
    +        return QhullPoint(qh_qh, numCoords, qh_facet->center);
    +    }
    +    return QhullPoint();
    + }//getCenter
    +
    +//! Return innerplane clearly below the vertices
    +//! from io_r.c[qh_PRINTinner]
    +QhullHyperplane QhullFacet::
    +innerplane() const{
    +    QhullHyperplane h;
    +    if(qh_qh){
    +        realT inner;
    +        // Does not error, TRY_QHULL_ not needed
    +        qh_outerinner(qh_qh, const_cast(getFacetT()), NULL, &inner);
    +        h= hyperplane();
    +        h.setOffset(h.offset()-inner); //inner is negative
    +    }
    +    return h;
    +}//innerplane
    +
    +//! Return outerplane clearly above all points
    +//! from io_r.c[qh_PRINTouter]
    +QhullHyperplane QhullFacet::
    +outerplane() const{
    +    QhullHyperplane h;
    +    if(qh_qh){
    +        realT outer;
    +        // Does not error, TRY_QHULL_ not needed
    +        qh_outerinner(qh_qh, const_cast(getFacetT()), &outer, NULL);
    +        h= hyperplane();
    +        h.setOffset(h.offset()-outer); //outer is positive
    +    }
    +    return h;
    +}//outerplane
    +
    +//! Set by qh_triangulate for option 'Qt'.
    +//! Errors if tricoplanar and facetArea() or qh_getarea() called first.
    +QhullFacet QhullFacet::
    +tricoplanarOwner() const
    +{
    +    if(qh_facet->tricoplanar){
    +        if(qh_facet->isarea){
    +            throw QhullError(10018, "Qhull error: facetArea() or qh_getarea() previously called.  triCoplanarOwner() is not available.");
    +        }
    +        return QhullFacet(qh_qh, qh_facet->f.triowner);
    +    }
    +    return QhullFacet(qh_qh); 
    +}//tricoplanarOwner
    +
    +QhullPoint QhullFacet::
    +voronoiVertex()
    +{
    +    if(qh_qh && qh_qh->CENTERtype!=qh_ASvoronoi){
    +          throw QhullError(10052, "Error: QhullFacet.voronoiVertex() requires option 'v' (qh_ASvoronoi)");
    +    }
    +    return getCenter();
    +}//voronoiVertex
    +
    +#//!\name Value
    +
    +//! Disables tricoplanarOwner()
    +double QhullFacet::
    +facetArea()
    +{
    +    if(qh_qh && !qh_facet->isarea){
    +        QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +            qh_facet->f.area= qh_facetarea(qh_qh, qh_facet);
    +            qh_facet->isarea= True;
    +        }
    +        qh_qh->NOerrexit= true;
    +        qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    }
    +    return qh_facet->f.area;
    +}//facetArea
    +
    +#//!\name Foreach
    +
    +QhullPointSet QhullFacet::
    +coplanarPoints() const
    +{
    +    return QhullPointSet(qh_qh, qh_facet->coplanarset);
    +}//coplanarPoints
    +
    +QhullFacetSet QhullFacet::
    +neighborFacets() const
    +{
    +    return QhullFacetSet(qh_qh, qh_facet->neighbors);
    +}//neighborFacets
    +
    +QhullPointSet QhullFacet::
    +outsidePoints() const
    +{
    +    return QhullPointSet(qh_qh, qh_facet->outsideset);
    +}//outsidePoints
    +
    +QhullRidgeSet QhullFacet::
    +ridges() const
    +{
    +    return QhullRidgeSet(qh_qh, qh_facet->ridges);
    +}//ridges
    +
    +QhullVertexSet QhullFacet::
    +vertices() const
    +{
    +    return QhullVertexSet(qh_qh, qh_facet->vertices);
    +}//vertices
    +
    +}//namespace orgQhull
    +
    +#//!\name operator<<
    +
    +using std::ostream;
    +
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetSet;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullPointSet;
    +using orgQhull::QhullRidge;
    +using orgQhull::QhullRidgeSet;
    +using orgQhull::QhullSetBase;
    +using orgQhull::QhullVertexSet;
    +
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintFacet &pr)
    +{
    +    os << pr.message;
    +    QhullFacet f= *pr.facet;
    +    if(f.getFacetT()==0){ // Special values from set iterator
    +        os << " NULLfacet" << endl;
    +        return os;
    +    }
    +    if(f.getFacetT()==qh_MERGEridge){
    +        os << " MERGEridge" << endl;
    +        return os;
    +    }
    +    if(f.getFacetT()==qh_DUPLICATEridge){
    +        os << " DUPLICATEridge" << endl;
    +        return os;
    +    }
    +    os << f.printHeader();
    +    if(!f.ridges().isEmpty()){
    +        os << f.printRidges();
    +    }
    +    return os;
    +}//operator<< PrintFacet
    +
    +//! Print Voronoi center or facet centrum to stream.  Same as qh_printcenter [_r.]
    +//! Code duplicated for PrintCenter and getCenter
    +//! Sets center if needed
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintCenter &pr)
    +{
    +    facetT *f= pr.facet->getFacetT();
    +    if(pr.facet->qh()->CENTERtype!=qh_ASvoronoi && pr.facet->qh()->CENTERtype!=qh_AScentrum){
    +        return os;
    +    }
    +    if (pr.message){
    +        os << pr.message;
    +    }
    +    int numCoords;
    +    if(pr.facet->qh()->CENTERtype==qh_ASvoronoi){
    +        numCoords= pr.facet->qh()->hull_dim-1;
    +        if(!f->normal || !f->upperdelaunay || !pr.facet->qh()->ATinfinity){
    +            if(!f->center){
    +                f->center= qh_facetcenter(pr.facet->qh(), f->vertices);
    +            }
    +            for(int k=0; kcenter[k] << " "; // FIXUP QH11010 qh_REAL_1
    +            }
    +        }else{
    +            for(int k=0; kqh()->hull_dim;
    +        if(pr.print_format==qh_PRINTtriangles && pr.facet->qh()->DELAUNAY){
    +            numCoords--;
    +        }
    +        if(!f->center){
    +            f->center= qh_getcentrum(pr.facet->qh(), f);
    +        }
    +        for(int k=0; kcenter[k] << " "; // FIXUP QH11010 qh_REAL_1
    +        }
    +    }
    +    if(pr.print_format==qh_PRINTgeom && numCoords==2){
    +        os << " 0";
    +    }
    +    os << endl;
    +    return os;
    +}//operator<< PrintCenter
    +
    +//! Print flags for facet to stream.  Space prefix.  From qh_printfacetheader [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintFlags &p)
    +{
    +    const facetT *f= p.facet->getFacetT();
    +    if(p.message){
    +        os << p.message;
    +    }
    +
    +    os << (p.facet->isTopOrient() ? " top" : " bottom");
    +    if(p.facet->isSimplicial()){
    +        os << " simplicial";
    +    }
    +    if(p.facet->isTriCoplanar()){
    +        os << " tricoplanar";
    +    }
    +    if(p.facet->isUpperDelaunay()){
    +        os << " upperDelaunay";
    +    }
    +    if(f->visible){
    +        os << " visible";
    +    }
    +    if(f->newfacet){
    +        os << " new";
    +    }
    +    if(f->tested){
    +        os << " tested";
    +    }
    +    if(!f->good){
    +        os << " notG";
    +    }
    +    if(f->seen){
    +        os << " seen";
    +    }
    +    if(f->coplanar){
    +        os << " coplanar";
    +    }
    +    if(f->mergehorizon){
    +        os << " mergehorizon";
    +    }
    +    if(f->keepcentrum){
    +        os << " keepcentrum";
    +    }
    +    if(f->dupridge){
    +        os << " dupridge";
    +    }
    +    if(f->mergeridge && !f->mergeridge2){
    +        os << " mergeridge1";
    +    }
    +    if(f->mergeridge2){
    +        os << " mergeridge2";
    +    }
    +    if(f->newmerge){
    +        os << " newmerge";
    +    }
    +    if(f->flipped){
    +        os << " flipped";
    +    }
    +    if(f->notfurthest){
    +        os << " notfurthest";
    +    }
    +    if(f->degenerate){
    +        os << " degenerate";
    +    }
    +    if(f->redundant){
    +        os << " redundant";
    +    }
    +    os << endl;
    +    return os;
    +}//operator<< PrintFlags
    +
    +//! Print header for facet to stream. Space prefix.  From qh_printfacetheader [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintHeader &pr)
    +{
    +    QhullFacet facet= *pr.facet;
    +    facetT *f= facet.getFacetT();
    +    os << "- f" << facet.id() << endl;
    +    os << facet.printFlags("    - flags:");
    +    if(f->isarea){
    +        os << "    - area: " << f->f.area << endl; //FIXUP QH11010 2.2g
    +    }else if(pr.facet->qh()->NEWfacets && f->visible && f->f.replace){
    +        os << "    - replacement: f" << f->f.replace->id << endl;
    +    }else if(f->newfacet){
    +        if(f->f.samecycle && f->f.samecycle != f){
    +            os << "    - shares same visible/horizon as f" << f->f.samecycle->id << endl;
    +        }
    +    }else if(f->tricoplanar /* !isarea */){
    +        if(f->f.triowner){
    +            os << "    - owner of normal & centrum is facet f" << f->f.triowner->id << endl;
    +        }
    +    }else if(f->f.newcycle){
    +        os << "    - was horizon to f" << f->f.newcycle->id << endl;
    +    }
    +    if(f->nummerge){
    +        os << "    - merges: " << f->nummerge << endl;
    +    }
    +    os << facet.hyperplane().print("    - normal: ", "\n    - offset: "); // FIXUP QH11010 %10.7g
    +    if(pr.facet->qh()->CENTERtype==qh_ASvoronoi || f->center){
    +        os << facet.printCenter(qh_PRINTfacets, "    - center: ");
    +    }
    +#if qh_MAXoutside
    +    if(f->maxoutside > pr.facet->qh()->DISTround){
    +        os << "    - maxoutside: " << f->maxoutside << endl; //FIXUP QH11010 %10.7g
    +    }
    +#endif
    +    QhullPointSet ps= facet.outsidePoints();
    +    if(!ps.isEmpty()){
    +        QhullPoint furthest= ps.last();
    +        if (ps.size() < 6) {
    +            os << "    - outside set(furthest p" << furthest.id() << "):" << endl;
    +            for(QhullPointSet::iterator i=ps.begin(); i!=ps.end(); ++i){
    +                QhullPoint p= *i;
    +                os << p.print("     ");
    +            }
    +        }else if(ps.size()<21){
    +            os << ps.print("    - outside set:");
    +        }else{
    +            os << "    - outside set:  " << ps.size() << " points.";
    +            os << furthest.print("  Furthest");
    +        }
    +#if !qh_COMPUTEfurthest
    +        os << "    - furthest distance= " << f->furthestdist << endl; //FIXUP QH11010 %2.2g
    +#endif
    +    }
    +    QhullPointSet cs= facet.coplanarPoints();
    +    if(!cs.isEmpty()){
    +        QhullPoint furthest= cs.last();
    +        if (cs.size() < 6) {
    +            os << "    - coplanar set(furthest p" << furthest.id() << "):" << endl;
    +            for(QhullPointSet::iterator i=cs.begin(); i!=cs.end(); ++i){
    +                QhullPoint p= *i;
    +                os << p.print("     ");
    +            }
    +        }else if(cs.size()<21){
    +            os << cs.print("    - coplanar set:");
    +        }else{
    +            os << "    - coplanar set:  " << cs.size() << " points.";
    +            os << furthest.print("  Furthest");
    +        }
    +        // FIXUP QH11027 Can/should zinc_(Zdistio) be called from C++ interface
    +        double d= facet.distance(furthest);
    +        os << "      furthest distance= " << d << endl; //FIXUP QH11010 %2.2g
    +    }
    +    QhullVertexSet vs= facet.vertices();
    +    if(!vs.isEmpty()){
    +        os << vs.print("    - vertices:");
    +    }
    +    QhullFacetSet fs= facet.neighborFacets();
    +    fs.selectAll();
    +    if(!fs.isEmpty()){
    +        os << fs.printIdentifiers("    - neighboring facets:");
    +    }
    +    return os;
    +}//operator<< PrintHeader
    +
    +
    +//! Print ridges of facet to stream.  Same as qh_printfacetridges [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintRidges &pr)
    +{
    +    const QhullFacet facet= *pr.facet;
    +    facetT *f= facet.getFacetT();
    +    QhullRidgeSet rs= facet.ridges();
    +    if(!rs.isEmpty()){
    +        if(f->visible && pr.facet->qh()->NEWfacets){
    +            os << "    - ridges(ids may be garbage):";
    +            for(QhullRidgeSet::iterator i=rs.begin(); i!=rs.end(); ++i){
    +                QhullRidge r= *i;
    +                os << " r" << r.id();
    +            }
    +            os << endl;
    +        }else{
    +            os << "    - ridges:" << endl;
    +        }
    +
    +        // Keep track of printed ridges
    +        for(QhullRidgeSet::iterator i=rs.begin(); i!=rs.end(); ++i){
    +            QhullRidge r= *i;
    +            r.getRidgeT()->seen= false;
    +        }
    +        int ridgeCount= 0;
    +        if(facet.dimension()==3){
    +            for(QhullRidge r= rs.first(); !r.getRidgeT()->seen; r= r.nextRidge3d(facet)){
    +                r.getRidgeT()->seen= true;
    +                os << r.print("");
    +                ++ridgeCount;
    +                if(!r.hasNextRidge3d(facet)){
    +                    break;
    +                }
    +            }
    +        }else {
    +            QhullFacetSet ns(facet.neighborFacets());
    +            for(QhullFacetSet::iterator i=ns.begin(); i!=ns.end(); ++i){
    +                QhullFacet neighbor= *i;
    +                QhullRidgeSet nrs(neighbor.ridges());
    +                for(QhullRidgeSet::iterator j=nrs.begin(); j!=nrs.end(); ++j){
    +                    QhullRidge r= *j;
    +                    if(r.otherFacet(neighbor)==facet){
    +                        r.getRidgeT()->seen= true;
    +                        os << r.print("");
    +                        ridgeCount++;
    +                    }
    +                }
    +            }
    +        }
    +        if(ridgeCount!=rs.count()){
    +            os << "     - all ridges:";
    +            for(QhullRidgeSet::iterator i=rs.begin(); i!=rs.end(); ++i){
    +                QhullRidge r= *i;
    +                os << " r" << r.id();
    +            }
    +            os << endl;
    +        }
    +        for(QhullRidgeSet::iterator i=rs.begin(); i!=rs.end(); ++i){
    +            QhullRidge r= *i;
    +            if(!r.getRidgeT()->seen){
    +                os << r.print("");
    +            }
    +        }
    +    }
    +    return os;
    +}//operator<< PrintRidges
    +
    +// "No conversion" error if defined inline
    +ostream &
    +operator<<(ostream &os, QhullFacet &f)
    +{
    +    os << f.print("");
    +    return os;
    +}//<< QhullFacet
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacet.h b/xs/src/qhull/src/libqhullcpp/QhullFacet.h
    new file mode 100644
    index 0000000000..ae4f008fd2
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacet.h
    @@ -0,0 +1,151 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacet.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLFACET_H
    +#define QHULLFACET_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullHyperplane.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullSet.h"
    +#include "libqhullcpp/QhullPointSet.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Coordinates;
    +    class Qhull;
    +    class QhullFacetSet;
    +    class QhullRidge;
    +    class QhullVertex;
    +    class QhullVertexSet;
    +
    +#//!\name Defined here
    +    class QhullFacet;
    +    typedef QhullSet  QhullRidgeSet;
    +
    +//! A QhullFacet is the C++ equivalent to Qhull's facetT*
    +class QhullFacet {
    +
    +#//!\name Defined here
    +public:
    +    typedef facetT *   base_type;  // for QhullVertexSet
    +
    +private:
    +#//!\name Fields -- no additions (QhullFacetSet of facetT*)
    +    facetT *            qh_facet;  //!< Corresponding facetT, may be 0 for corner cases (e.g., *facetSet.end()==0) and tricoplanarOwner()
    +    QhullQh *           qh_qh;     //!< QhullQh/qhT for facetT, may be 0
    +
    +#//!\name Class objects
    +    static facetT       s_empty_facet; // needed for shallow copy
    +
    +public:
    +#//!\name Constructors
    +                        QhullFacet() : qh_facet(&s_empty_facet), qh_qh(0) {}
    +    explicit            QhullFacet(const Qhull &q);
    +                        QhullFacet(const Qhull &q, facetT *f);
    +    explicit            QhullFacet(QhullQh *qqh) : qh_facet(&s_empty_facet), qh_qh(qqh) {}
    +                        QhullFacet(QhullQh *qqh, facetT *f) : qh_facet(f ? f : &s_empty_facet), qh_qh(qqh) {}
    +                        // Creates an alias.  Does not copy QhullFacet.  Needed for return by value and parameter passing
    +                        QhullFacet(const QhullFacet &other) : qh_facet(other.qh_facet ? other.qh_facet : &s_empty_facet), qh_qh(other.qh_qh) {}
    +                        // Creates an alias.  Does not copy QhullFacet.  Needed for vector
    +    QhullFacet &        operator=(const QhullFacet &other) { qh_facet= other.qh_facet ? other.qh_facet : &s_empty_facet; qh_qh= other.qh_qh; return *this; }
    +                        ~QhullFacet() {}
    +
    +
    +#//!\name GetSet
    +    int                 dimension() const { return (qh_qh ? qh_qh->hull_dim : 0); }
    +    QhullPoint          getCenter() { return getCenter(qh_PRINTpoints); }
    +    QhullPoint          getCenter(qh_PRINT printFormat);
    +    facetT *            getBaseT() const { return getFacetT(); } //!< For QhullSet
    +                        // Do not define facetT().  It conflicts with return type facetT*
    +    facetT *            getFacetT() const { return qh_facet; }
    +    QhullHyperplane     hyperplane() const { return QhullHyperplane(qh_qh, dimension(), qh_facet->normal, qh_facet->offset); }
    +    countT              id() const { return (qh_facet ? qh_facet->id : (int)qh_IDunknown); }
    +    QhullHyperplane     innerplane() const;
    +    bool                isValid() const { return qh_qh && qh_facet && qh_facet != &s_empty_facet; }
    +    bool                isGood() const { return qh_facet && qh_facet->good; }
    +    bool                isSimplicial() const { return qh_facet && qh_facet->simplicial; }
    +    bool                isTopOrient() const { return qh_facet && qh_facet->toporient; }
    +    bool                isTriCoplanar() const { return qh_facet && qh_facet->tricoplanar; }
    +    bool                isUpperDelaunay() const { return qh_facet && qh_facet->upperdelaunay; }
    +    QhullFacet          next() const { return QhullFacet(qh_qh, qh_facet->next); }
    +    bool                operator==(const QhullFacet &other) const { return qh_facet==other.qh_facet; }
    +    bool                operator!=(const QhullFacet &other) const { return !operator==(other); }
    +    QhullHyperplane     outerplane() const;
    +    QhullFacet          previous() const { return QhullFacet(qh_qh, qh_facet->previous); }
    +    QhullQh *           qh() const { return qh_qh; }
    +    QhullFacet          tricoplanarOwner() const;
    +    QhullPoint          voronoiVertex();
    +
    +#//!\name value
    +    //! Undefined if c.size() != dimension()
    +    double              distance(const Coordinates &c) const { return distance(c.data()); }
    +    double              distance(const pointT *p) const { return distance(QhullPoint(qh_qh, const_cast(p))); }
    +    double              distance(const QhullPoint &p) const { return hyperplane().distance(p); }
    +    double              facetArea();
    +
    +#//!\name foreach
    +    // Can not inline.  Otherwise circular reference
    +    QhullPointSet       coplanarPoints() const;
    +    QhullFacetSet       neighborFacets() const;
    +    QhullPointSet       outsidePoints() const;
    +    QhullRidgeSet       ridges() const;
    +    QhullVertexSet      vertices() const;
    +
    +#//!\name IO
    +    struct PrintCenter{
    +        QhullFacet *    facet;  // non-const due to facet.center()
    +        const char *    message;
    +        qh_PRINT        print_format;
    +                        PrintCenter(QhullFacet &f, qh_PRINT printFormat, const char * s) : facet(&f), message(s), print_format(printFormat){}
    +    };//PrintCenter
    +    PrintCenter         printCenter(qh_PRINT printFormat, const char *message) { return PrintCenter(*this, printFormat, message); }
    +
    +    struct PrintFacet{
    +        QhullFacet *    facet;  // non-const due to f->center()
    +        const char *    message;
    +        explicit        PrintFacet(QhullFacet &f, const char * s) : facet(&f), message(s) {}
    +    };//PrintFacet
    +    PrintFacet          print(const char *message) { return PrintFacet(*this, message); }
    +
    +    struct PrintFlags{
    +        const QhullFacet *facet;
    +        const char *    message;
    +                        PrintFlags(const QhullFacet &f, const char *s) : facet(&f), message(s) {}
    +    };//PrintFlags
    +    PrintFlags          printFlags(const char *message) const { return PrintFlags(*this, message); }
    +
    +    struct PrintHeader{
    +        QhullFacet *    facet;  // non-const due to f->center()
    +                        PrintHeader(QhullFacet &f) : facet(&f) {}
    +    };//PrintHeader
    +    PrintHeader         printHeader() { return PrintHeader(*this); }
    +
    +    struct PrintRidges{
    +        const QhullFacet *facet;
    +                        PrintRidges(QhullFacet &f) : facet(&f) {}
    +    };//PrintRidges
    +    PrintRidges         printRidges() { return PrintRidges(*this); }
    +
    +};//class QhullFacet
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintFacet &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintCenter &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintFlags &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintHeader &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintRidges &pr);
    +std::ostream &operator<<(std::ostream &os, orgQhull::QhullFacet &f); // non-const due to qh_getcenter()
    +
    +#endif // QHULLFACET_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacetList.cpp b/xs/src/qhull/src/libqhullcpp/QhullFacetList.cpp
    new file mode 100644
    index 0000000000..9e6ddfe9ec
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacetList.cpp
    @@ -0,0 +1,174 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacetList.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullFacetList -- Qhull's linked facets, as a C++ class
    +
    +#include "libqhullcpp/QhullFacetList.h"
    +
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullVertex.h"
    +
    +using std::string;
    +using std::vector;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructors
    +
    +QhullFacetList::
    +QhullFacetList(const Qhull &q, facetT *b, facetT *e ) 
    +: QhullLinkedList(QhullFacet(q, b), QhullFacet(q, e))
    +, select_all(false)
    +{
    +}
    +
    +#//!\name Conversions
    +
    +// See qt_qhull.cpp for QList conversions
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullFacetList::
    +toStdVector() const
    +{
    +    QhullLinkedListIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        QhullFacet f= i.next();
    +        if(isSelectAll() || f.isGood()){
    +            vs.push_back(f);
    +        }
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#ifndef QHULL_NO_STL
    +//! Same as PrintVertices
    +std::vector QhullFacetList::
    +vertices_toStdVector() const
    +{
    +    std::vector vs;
    +    QhullVertexSet qvs(qh(), first().getFacetT(), 0, isSelectAll());
    +
    +    for(QhullVertexSet::iterator i=qvs.begin(); i!=qvs.end(); ++i){
    +        vs.push_back(*i);
    +    }
    +    return vs;
    +}//vertices_toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +bool QhullFacetList::
    +contains(const QhullFacet &facet) const
    +{
    +    if(isSelectAll()){
    +        return QhullLinkedList::contains(facet);
    +    }
    +    for(QhullFacetList::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f==facet && f.isGood()){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//contains
    +
    +int QhullFacetList::
    +count() const
    +{
    +    if(isSelectAll()){
    +        return QhullLinkedList::count();
    +    }
    +    int counter= 0;
    +    for(QhullFacetList::const_iterator i=begin(); i != end(); ++i){
    +        if((*i).isGood()){
    +            counter++;
    +        }
    +    }
    +    return counter;
    +}//count
    +
    +int QhullFacetList::
    +count(const QhullFacet &facet) const
    +{
    +    if(isSelectAll()){
    +        return QhullLinkedList::count(facet);
    +    }
    +    int counter= 0;
    +    for(QhullFacetList::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f==facet && f.isGood()){
    +            counter++;
    +        }
    +    }
    +    return counter;
    +}//count
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetList;
    +using orgQhull::QhullVertex;
    +using orgQhull::QhullVertexSet;
    +
    +ostream &
    +operator<<(ostream &os, const QhullFacetList::PrintFacetList &pr)
    +{
    +    os << pr.print_message;
    +    QhullFacetList fs= *pr.facet_list;
    +    os << "Vertices for " << fs.count() << " facets" << endl;
    +    os << fs.printVertices();
    +    os << fs.printFacets();
    +    return os;
    +}//operator<<
    +
    +//! Print facet list to stream.  From qh_printafacet [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullFacetList::PrintFacets &pr)
    +{
    +    for(QhullFacetList::const_iterator i= pr.facet_list->begin(); i != pr.facet_list->end(); ++i){
    +        QhullFacet f= *i;
    +        if(pr.facet_list->isSelectAll() || f.isGood()){
    +            os << f.print("");
    +        }
    +    }
    +    return os;
    +}//printFacets
    +
    +//! Print vertices of good faces in facet list to stream.  From qh_printvertexlist [io_r.c]
    +//! Same as vertices_toStdVector
    +ostream &
    +operator<<(ostream &os, const QhullFacetList::PrintVertices &pr)
    +{
    +    QhullVertexSet vs(pr.facet_list->qh(), pr.facet_list->first().getFacetT(), NULL, pr.facet_list->isSelectAll());
    +    for(QhullVertexSet::iterator i=vs.begin(); i!=vs.end(); ++i){
    +        QhullVertex v= *i;
    +        os << v.print("");
    +    }
    +    return os;
    +}//printVertices
    +
    +std::ostream &
    +operator<<(ostream &os, const QhullFacetList &fs)
    +{
    +    os << fs.printFacets();
    +    return os;
    +}//QhullFacetList
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacetList.h b/xs/src/qhull/src/libqhullcpp/QhullFacetList.h
    new file mode 100644
    index 0000000000..e61e568403
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacetList.h
    @@ -0,0 +1,106 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacetList.h#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLFACETLIST_H
    +#define QHULLFACETLIST_H
    +
    +#include "libqhullcpp/QhullLinkedList.h"
    +#include "libqhullcpp/QhullFacet.h"
    +
    +#include 
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +    class QhullFacet;
    +    class QhullQh;
    +
    +#//!\name Defined here
    +    //! QhullFacetList -- List of QhullFacet/facetT, as a C++ class.  
    +    //!\see QhullFacetSet.h
    +    class QhullFacetList;
    +    //! QhullFacetListIterator -- if(f.isGood()){ ... }
    +    typedef QhullLinkedListIterator QhullFacetListIterator;
    +
    +class QhullFacetList : public QhullLinkedList {
    +
    +#//!\name  Fields
    +private:
    +    bool                select_all;   //! True if include bad facets.  Default is false.
    +
    +#//!\name Constructors
    +public:
    +                        QhullFacetList(const Qhull &q, facetT *b, facetT *e);
    +                        QhullFacetList(QhullQh *qqh, facetT *b, facetT *e);
    +                        QhullFacetList(QhullFacet b, QhullFacet e) : QhullLinkedList(b, e), select_all(false) {}
    +                        //Copy constructor copies pointer but not contents.  Needed for return by value and parameter passing.
    +                        QhullFacetList(const QhullFacetList &other) : QhullLinkedList(*other.begin(), *other.end()), select_all(other.select_all) {}
    +    QhullFacetList &    operator=(const QhullFacetList &other) { QhullLinkedList::operator =(other); select_all= other.select_all; return *this; }
    +                        ~QhullFacetList() {}
    +
    +private:                //!Disable default constructor.  See QhullLinkedList
    +                    QhullFacetList();
    +public:
    +
    +#//!\name Conversion
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +    std::vector vertices_toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList   toQList() const;
    +    QList  vertices_toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +                        //! Filtered by facet.isGood().  May be 0 when !isEmpty().
    +    countT              count() const;
    +    bool                contains(const QhullFacet &f) const;
    +    countT              count(const QhullFacet &f) const;
    +    bool                isSelectAll() const { return select_all; }
    +    QhullQh *           qh() const { return first().qh(); }
    +    void                selectAll() { select_all= true; }
    +    void                selectGood() { select_all= false; }
    +                        //!< operator==() does not depend on isGood()
    +
    +#//!\name IO
    +    struct PrintFacetList{
    +        const QhullFacetList *facet_list;
    +        const char *    print_message;   //!< non-null message
    +                        PrintFacetList(const QhullFacetList &fl, const char *message) : facet_list(&fl), print_message(message) {}
    +    };//PrintFacetList
    +    PrintFacetList      print(const char *message) const  { return PrintFacetList(*this, message); }
    +
    +    struct PrintFacets{
    +        const QhullFacetList *facet_list;
    +                        PrintFacets(const QhullFacetList &fl) : facet_list(&fl) {}
    +    };//PrintFacets
    +    PrintFacets         printFacets() const { return PrintFacets(*this); }
    +
    +    struct PrintVertices{
    +        const QhullFacetList *facet_list;
    +                        PrintVertices(const QhullFacetList &fl) : facet_list(&fl) {}
    +    };//PrintVertices
    +    PrintVertices       printVertices() const { return PrintVertices(*this); }
    +};//class QhullFacetList
    +
    +}//namespace orgQhull
    +
    +#//!\name == Global namespace =========================================
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetList::PrintFacetList &p);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetList::PrintFacets &p);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetList::PrintVertices &p);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetList &fs);
    +
    +#endif // QHULLFACETLIST_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacetSet.cpp b/xs/src/qhull/src/libqhullcpp/QhullFacetSet.cpp
    new file mode 100644
    index 0000000000..d30c21e26a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacetSet.cpp
    @@ -0,0 +1,147 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacetSet.cpp#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullFacetSet -- Qhull's linked facets, as a C++ class
    +
    +#include "libqhullcpp/QhullFacetSet.h"
    +
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullVertex.h"
    +
    +#ifndef QHULL_NO_STL
    +using std::vector;
    +#endif
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Conversions
    +
    +// See qt-qhull.cpp for QList conversions
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullFacetSet::
    +toStdVector() const
    +{
    +    QhullSetIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        QhullFacet f= i.next();
    +        if(isSelectAll() || f.isGood()){
    +            vs.push_back(f);
    +        }
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +bool QhullFacetSet::
    +contains(const QhullFacet &facet) const
    +{
    +    if(isSelectAll()){
    +        return QhullSet::contains(facet);
    +    }
    +    for(QhullFacetSet::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f==facet && f.isGood()){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//contains
    +
    +int QhullFacetSet::
    +count() const
    +{
    +    if(isSelectAll()){
    +        return QhullSet::count();
    +    }
    +    int counter= 0;
    +    for(QhullFacetSet::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f.isGood()){
    +            counter++;
    +        }
    +    }
    +    return counter;
    +}//count
    +
    +int QhullFacetSet::
    +count(const QhullFacet &facet) const
    +{
    +    if(isSelectAll()){
    +        return QhullSet::count(facet);
    +    }
    +    int counter= 0;
    +    for(QhullFacetSet::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f==facet && f.isGood()){
    +            counter++;
    +        }
    +    }
    +    return counter;
    +}//count
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetSet;
    +
    +ostream &
    +operator<<(ostream &os, const QhullFacetSet &fs)
    +{
    +    os << fs.print("");
    +    return os;
    +}//<begin(); i!=p.facet_set->end(); ++i){
    +        const QhullFacet f= *i;
    +        if(f.getFacetT()==qh_MERGEridge){
    +            os << " MERGE";
    +        }else if(f.getFacetT()==qh_DUPLICATEridge){
    +            os << " DUP";
    +        }else if(p.facet_set->isSelectAll() || f.isGood()){
    +            os << " f" << f.id();
    +        }
    +    }
    +    os << endl;
    +    return os;
    +}//<
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +
    +#//!\name Defined here
    +    //! QhullFacetSet -- a set of Qhull facets, as a C++ class.  See QhullFacetList.h
    +    class QhullFacetSet;
    +    typedef QhullSetIterator QhullFacetSetIterator;
    +
    +class QhullFacetSet : public QhullSet {
    +
    +#//!\name Defined here
    +public:
    +    typedef facetT *   base_type;  // for QhullVertexSet
    +
    +private:
    +#//!\name Fields
    +    bool                select_all;   //! True if include bad facets.  Default is false.
    +
    +public:
    +#//!\name Constructor
    +                        //Conversion from setT* is not type-safe.  Implicit conversion for void* to T
    +                        QhullFacetSet(const Qhull &q, setT *s) : QhullSet(q, s), select_all(false) {}
    +                        QhullFacetSet(QhullQh *qqh, setT *s) : QhullSet(qqh, s), select_all(false) {}
    +                        //!Copy constructor copies pointers but not contents.  Needed for return by value and parameter passing.
    +                        QhullFacetSet(const QhullFacetSet &other) : QhullSet(other), select_all(other.select_all) {}
    +                        //!Assignment copies pointers but not contents.
    +    QhullFacetSet &     operator=(const QhullFacetSet &other) { QhullSet::operator=(other); select_all= other.select_all; return *this; }
    +
    +private:
    +                        //!Disable default constructor.  See QhullSetBase
    +                        QhullFacetSet();
    +public:
    +
    +#//!\name Conversion
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList   toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +                        //! Filtered by facet.isGood().  May be 0 when !isEmpty().
    +    countT              count() const;
    +    bool                contains(const QhullFacet &f) const;
    +    countT              count(const QhullFacet &f) const;
    +    bool                isSelectAll() const { return select_all; }
    +                        //! operator==() does not depend on isGood()
    +    void                selectAll() { select_all= true; }
    +    void                selectGood() { select_all= false; }
    +
    +#//!\name IO
    +    // Not same as QhullFacetList#IO.  A QhullFacetSet is a component of a QhullFacetList.
    +
    +    struct PrintFacetSet{
    +        const QhullFacetSet *facet_set;
    +        const char *    print_message;  //!< non-null message
    +                        PrintFacetSet(const char *message, const QhullFacetSet *s) : facet_set(s), print_message(message) {}
    +    };//PrintFacetSet
    +    const PrintFacetSet print(const char *message) const { return PrintFacetSet(message, this); }
    +
    +    struct PrintIdentifiers{
    +        const QhullFacetSet *facet_set;
    +        const char *    print_message;  //!< non-null message
    +                        PrintIdentifiers(const char *message, const QhullFacetSet *s) : facet_set(s), print_message(message) {}
    +    };//PrintIdentifiers
    +    PrintIdentifiers    printIdentifiers(const char *message) const { return PrintIdentifiers(message, this); }
    +
    +};//class QhullFacetSet
    +
    +}//namespace orgQhull
    +
    +#//!\name == Global namespace =========================================
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetSet &fs);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetSet::PrintFacetSet &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetSet::PrintIdentifiers &p);
    +
    +#endif // QHULLFACETSET_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullHyperplane.cpp b/xs/src/qhull/src/libqhullcpp/QhullHyperplane.cpp
    new file mode 100644
    index 0000000000..ed5cc4bae1
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullHyperplane.cpp
    @@ -0,0 +1,187 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullHyperplane.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/QhullHyperplane.h"
    +
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullPoint.h"
    +
    +#include 
    +
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructors
    +
    +QhullHyperplane::
    +QhullHyperplane(const Qhull &q) 
    +: hyperplane_coordinates(0)
    +, qh_qh(q.qh())
    +, hyperplane_offset(0.0)
    +, hyperplane_dimension(0)
    +{
    +}
    +
    +QhullHyperplane::
    +QhullHyperplane(const Qhull &q, int hyperplaneDimension, coordT *c, coordT hyperplaneOffset) 
    +: hyperplane_coordinates(c)
    +, qh_qh(q.qh())
    +, hyperplane_offset(hyperplaneOffset)
    +, hyperplane_dimension(hyperplaneDimension)
    +{
    +}
    +
    +#//!\name Conversions
    +
    +// See qt-qhull.cpp for QList conversions
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullHyperplane::
    +toStdVector() const
    +{
    +    QhullHyperplaneIterator i(*this);
    +    std::vector fs;
    +    while(i.hasNext()){
    +        fs.push_back(i.next());
    +    }
    +    fs.push_back(hyperplane_offset);
    +    return fs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +//! Return true if equal
    +//! If qh_qh defined, tests qh.distanceEpsilon and qh.angleEpsilon
    +//! otherwise, tests equal coordinates and offset
    +bool QhullHyperplane::
    +operator==(const QhullHyperplane &other) const
    +{
    +    if(hyperplane_dimension!=other.hyperplane_dimension || !hyperplane_coordinates || !other.hyperplane_coordinates){
    +        return false;
    +    }
    +    double d= fabs(hyperplane_offset-other.hyperplane_offset);
    +    if(d > (qh_qh ? qh_qh->distanceEpsilon() : 0.0)){
    +        return false;
    +    }
    +    double angle= hyperplaneAngle(other);
    +
    +    double a= fabs(angle-1.0);
    +    if(a > (qh_qh ? qh_qh->angleEpsilon() : 0.0)){
    +        return false;
    +    }
    +    return true;
    +}//operator==
    +
    +#//!\name Methods
    +
    +//! Return distance from point to hyperplane.
    +//!   If greater than zero, the point is above the facet (i.e., outside).
    +// qh_distplane [geom_r.c], QhullFacet::distance, and QhullHyperplane::distance are copies
    +//    Does not support RANDOMdist or logging
    +double QhullHyperplane::
    +distance(const QhullPoint &p) const
    +{
    +    const coordT *point= p.coordinates();
    +    int dim= p.dimension();
    +    QHULL_ASSERT(dim==dimension());
    +    const coordT *normal= coordinates();
    +    double dist;
    +
    +    switch (dim){
    +  case 2:
    +      dist= offset() + point[0] * normal[0] + point[1] * normal[1];
    +      break;
    +  case 3:
    +      dist= offset() + point[0] * normal[0] + point[1] * normal[1] + point[2] * normal[2];
    +      break;
    +  case 4:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3];
    +      break;
    +  case 5:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4];
    +      break;
    +  case 6:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5];
    +      break;
    +  case 7:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6];
    +      break;
    +  case 8:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6]+point[7]*normal[7];
    +      break;
    +  default:
    +      dist= offset();
    +      for (int k=dim; k--; )
    +          dist += *point++ * *normal++;
    +      break;
    +    }
    +    return dist;
    +}//distance
    +
    +double QhullHyperplane::
    +hyperplaneAngle(const QhullHyperplane &other) const
    +{
    +    volatile realT result= 0.0;
    +    QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +        result= qh_getangle(qh_qh, hyperplane_coordinates, other.hyperplane_coordinates);
    +    }
    +    qh_qh->NOerrexit= true;
    +    qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    return result;
    +}//hyperplaneAngle
    +
    +double QhullHyperplane::
    +norm() const {
    +    double d= 0.0;
    +    const coordT *c= coordinates();
    +    for (int k=dimension(); k--; ){
    +        d += *c * *c;
    +        ++c;
    +    }
    +    return sqrt(d);
    +}//norm
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::ostream;
    +using orgQhull::QhullHyperplane;
    +
    +#//!\name GetSet<<
    +
    +ostream &
    +operator<<(ostream &os, const QhullHyperplane &p)
    +{
    +    os << p.print("");
    +    return os;
    +}
    +
    +ostream &
    +operator<<(ostream &os, const QhullHyperplane::PrintHyperplane &pr)
    +{
    +    os << pr.print_message;
    +    QhullHyperplane p= *pr.hyperplane;
    +    const realT *c= p.coordinates();
    +    for(int k=p.dimension(); k--; ){
    +        realT r= *c++;
    +        if(pr.print_message){
    +            os << " " << r; // FIXUP QH11010 %8.4g
    +        }else{
    +            os << " " << r; // FIXUP QH11010 qh_REAL_1
    +        }
    +    }
    +    os << pr.hyperplane_offset_message << " " << p.offset();
    +    os << std::endl;
    +    return os;
    +}//PrintHyperplane
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullHyperplane.h b/xs/src/qhull/src/libqhullcpp/QhullHyperplane.h
    new file mode 100644
    index 0000000000..2868ce5c99
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullHyperplane.h
    @@ -0,0 +1,123 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullHyperplane.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHHYPERPLANE_H
    +#define QHHYPERPLANE_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullIterator.h"
    +#include "libqhullcpp/QhullQh.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +    class QhullPoint;
    +
    +#//!\name Defined here
    +    //! QhullHyperplane as an offset, dimension, and pointer to coordinates
    +    class QhullHyperplane;
    +    //! Java-style iterator for QhullHyperplane coordinates
    +    class QhullHyperplaneIterator;
    +
    +class QhullHyperplane { // Similar to QhullPoint
    +public:
    +#//!\name Subtypes
    +    typedef const coordT *                  iterator;
    +    typedef const coordT *                  const_iterator;
    +    typedef QhullHyperplane::iterator       Iterator;
    +    typedef QhullHyperplane::const_iterator ConstIterator;
    +
    +private:
    +#//!\name Fields
    +    coordT *            hyperplane_coordinates;  //!< Normal to hyperplane.   facetT.normal is normalized to 1.0
    +    QhullQh *           qh_qh;                  //!< qhT for distanceEpsilon() in operator==
    +    coordT              hyperplane_offset;      //!< Distance from hyperplane to origin
    +    int                 hyperplane_dimension;   //!< Dimension of hyperplane
    +
    +#//!\name Construct
    +public:
    +                        QhullHyperplane() : hyperplane_coordinates(0), qh_qh(0), hyperplane_offset(0.0), hyperplane_dimension(0) {}
    +    explicit            QhullHyperplane(const Qhull &q);
    +                        QhullHyperplane(const Qhull &q, int hyperplaneDimension, coordT *c, coordT hyperplaneOffset);
    +    explicit            QhullHyperplane(QhullQh *qqh) : hyperplane_coordinates(0), qh_qh(qqh), hyperplane_offset(0.0), hyperplane_dimension(0) {}
    +                        QhullHyperplane(QhullQh *qqh, int hyperplaneDimension, coordT *c, coordT hyperplaneOffset) : hyperplane_coordinates(c), qh_qh(qqh), hyperplane_offset(hyperplaneOffset), hyperplane_dimension(hyperplaneDimension) {}
    +                        // Creates an alias.  Does not copy the hyperplane's coordinates.  Needed for return by value and parameter passing.
    +                        QhullHyperplane(const QhullHyperplane &other)  : hyperplane_coordinates(other.hyperplane_coordinates), qh_qh(other.qh_qh), hyperplane_offset(other.hyperplane_offset), hyperplane_dimension(other.hyperplane_dimension) {}
    +                        // Creates an alias.  Does not copy the hyperplane's coordinates.  Needed for vector
    +    QhullHyperplane &   operator=(const QhullHyperplane &other) { hyperplane_coordinates= other.hyperplane_coordinates; qh_qh= other.qh_qh; hyperplane_offset= other.hyperplane_offset; hyperplane_dimension= other.hyperplane_dimension; return *this; }
    +                        ~QhullHyperplane() {}
    +
    +#//!\name Conversions --
    +//! Includes offset at end
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList       toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +public:
    +    const coordT *      coordinates() const { return hyperplane_coordinates; }
    +    coordT *            coordinates() { return hyperplane_coordinates; }
    +    void                defineAs(int hyperplaneDimension, coordT *c, coordT hyperplaneOffset) { QHULL_ASSERT(hyperplaneDimension>=0); hyperplane_coordinates= c; hyperplane_dimension= hyperplaneDimension; hyperplane_offset= hyperplaneOffset; }
    +    //! Creates an alias to other using the same qh_qh
    +    void                defineAs(QhullHyperplane &other) { hyperplane_coordinates= other.coordinates(); hyperplane_dimension= other.dimension();  hyperplane_offset= other.offset(); }
    +    int                 dimension() const { return hyperplane_dimension; }
    +    bool                isValid() const { return hyperplane_coordinates!=0 && hyperplane_dimension>0; }
    +    coordT              offset() const { return hyperplane_offset; }
    +    bool                operator==(const QhullHyperplane &other) const;
    +    bool                operator!=(const QhullHyperplane &other) const { return !operator==(other); }
    +    const coordT &      operator[](int idx) const { QHULL_ASSERT(idx>=0 && idx=0 && idx
    +#include 
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! Only QHULL_DECLARE_SEQUENTIAL_ITERATOR is used in libqhullcpp.  The others need further development
    +    //! QHULL_DECLARE_SEQUENTIAL_ITERATOR(C) -- Declare a Java-style iterator
    +    //! QHULL_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR(C) -- Declare a mutable Java-style iterator
    +    //! QHULL_DECLARE_SET_ITERATOR(C) -- Declare a set iterator
    +    //! QHULL_DECLARE_MUTABLE_SET_ITERATOR(C) -- Declare a mutable set iterator
    +    //! Derived from Qt/core/tools/qiterator.h and qset_r.h/FOREACHsetelement_()
    +
    +// Stores C* as done in Mutable...  Assumes the container is not deleted.
    +// C::const_iterator is an STL-style iterator that returns T&
    +#define QHULL_DECLARE_SEQUENTIAL_ITERATOR(C, T) \
    +    \
    +    class C##Iterator \
    +    { \
    +        typedef C::const_iterator const_iterator; \
    +        const C *c; \
    +        const_iterator i; \
    +        public: \
    +        inline C##Iterator(const C &container) \
    +        : c(&container), i(c->constBegin()) {} \
    +        inline C##Iterator &operator=(const C &container) \
    +        { c = &container; i = c->constBegin(); return *this; } \
    +        inline void toFront() { i = c->constBegin(); } \
    +        inline void toBack() { i = c->constEnd(); } \
    +        inline bool hasNext() const { return i != c->constEnd(); } \
    +        inline const T &next() { return *i++; } \
    +        inline const T &peekNext() const { return *i; } \
    +        inline bool hasPrevious() const { return i != c->constBegin(); } \
    +        inline const T &previous() { return *--i; } \
    +        inline const T &peekPrevious() const { const_iterator p = i; return *--p; } \
    +        inline bool findNext(const T &t) \
    +        { while (i != c->constEnd()) if (*i++ == t) return true; return false; } \
    +        inline bool findPrevious(const T &t) \
    +        { while (i != c->constBegin()) if (*(--i) == t) return true; \
    +        return false;  } \
    +    };//C##Iterator
    +
    +// Remove setShareable() from Q_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR
    +// Uses QHULL_ASSERT (assert.h)
    +// Duplicated in MutablePointIterator without insert or remove
    +// Not used in libqhullcpp.  See Coordinates.h
    +#define QHULL_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR(C, T) \
    +    class Mutable##C##Iterator \
    +    { \
    +        typedef C::iterator iterator; \
    +        typedef C::const_iterator const_iterator; \
    +        C *c; \
    +        iterator i, n; \
    +        inline bool item_exists() const { return const_iterator(n) != c->constEnd(); } \
    +        public: \
    +        inline Mutable##C##Iterator(C &container) \
    +        : c(&container) \
    +        { i = c->begin(); n = c->end(); } \
    +        inline ~Mutable##C##Iterator() \
    +        {} \
    +        inline Mutable##C##Iterator &operator=(C &container) \
    +        { c = &container; \
    +        i = c->begin(); n = c->end(); return *this; } \
    +        inline void toFront() { i = c->begin(); n = c->end(); } \
    +        inline void toBack() { i = c->end(); n = i; } \
    +        inline bool hasNext() const { return c->constEnd() != const_iterator(i); } \
    +        inline T &next() { n = i++; return *n; } \
    +        inline T &peekNext() const { return *i; } \
    +        inline bool hasPrevious() const { return c->constBegin() != const_iterator(i); } \
    +        inline T &previous() { n = --i; return *n; } \
    +        inline T &peekPrevious() const { iterator p = i; return *--p; } \
    +        inline void remove() \
    +        { if (c->constEnd() != const_iterator(n)) { i = c->erase(n); n = c->end(); } } \
    +        inline void setValue(const T &t) const { if (c->constEnd() != const_iterator(n)) *n = t; } \
    +        inline T &value() { QHULL_ASSERT(item_exists()); return *n; } \
    +        inline const T &value() const { QHULL_ASSERT(item_exists()); return *n; } \
    +        inline void insert(const T &t) { n = i = c->insert(i, t); ++i; } \
    +        inline bool findNext(const T &t) \
    +        { while (c->constEnd() != const_iterator(n = i)) if (*i++ == t) return true; return false; } \
    +        inline bool findPrevious(const T &t) \
    +        { while (c->constBegin() != const_iterator(i)) if (*(n = --i) == t) return true; \
    +        n = c->end(); return false;  } \
    +    };//Mutable##C##Iterator
    +
    +// Not used in libqhullcpp.
    +#define QHULL_DECLARE_SET_ITERATOR(C) \
    +\
    +    template  \
    +    class Qhull##C##Iterator \
    +    { \
    +        typedef typename Qhull##C::const_iterator const_iterator; \
    +        Qhull##C c; \
    +        const_iterator i; \
    +    public: \
    +        inline Qhull##C##Iterator(const Qhull##C &container) \
    +        : c(container), i(c.constBegin()) {} \
    +        inline Qhull##C##Iterator &operator=(const Qhull##C &container) \
    +        { c = container; i = c.constBegin(); return *this; } \
    +        inline void toFront() { i = c.constBegin(); } \
    +        inline void toBack() { i = c.constEnd(); } \
    +        inline bool hasNext() const { return i != c.constEnd(); } \
    +        inline const T &next() { return *i++; } \
    +        inline const T &peekNext() const { return *i; } \
    +        inline bool hasPrevious() const { return i != c.constBegin(); } \
    +        inline const T &previous() { return *--i; } \
    +        inline const T &peekPrevious() const { const_iterator p = i; return *--p; } \
    +        inline bool findNext(const T &t) \
    +        { while (i != c.constEnd()) if (*i++ == t) return true; return false; } \
    +        inline bool findPrevious(const T &t) \
    +        { while (i != c.constBegin()) if (*(--i) == t) return true; \
    +        return false;  } \
    +    };//Qhull##C##Iterator
    +
    +// Not used in libqhullcpp.
    +#define QHULL_DECLARE_MUTABLE_SET_ITERATOR(C) \
    +\
    +template  \
    +class QhullMutable##C##Iterator \
    +{ \
    +    typedef typename Qhull##C::iterator iterator; \
    +    typedef typename Qhull##C::const_iterator const_iterator; \
    +    Qhull##C *c; \
    +    iterator i, n; \
    +    inline bool item_exists() const { return const_iterator(n) != c->constEnd(); } \
    +public: \
    +    inline Mutable##C##Iterator(Qhull##C &container) \
    +        : c(&container) \
    +    { c->setSharable(false); i = c->begin(); n = c->end(); } \
    +    inline ~Mutable##C##Iterator() \
    +    { c->setSharable(true); } \
    +    inline Mutable##C##Iterator &operator=(Qhull##C &container) \
    +    { c->setSharable(true); c = &container; c->setSharable(false); \
    +      i = c->begin(); n = c->end(); return *this; } \
    +    inline void toFront() { i = c->begin(); n = c->end(); } \
    +    inline void toBack() { i = c->end(); n = i; } \
    +    inline bool hasNext() const { return c->constEnd() != const_iterator(i); } \
    +    inline T &next() { n = i++; return *n; } \
    +    inline T &peekNext() const { return *i; } \
    +    inline bool hasPrevious() const { return c->constBegin() != const_iterator(i); } \
    +    inline T &previous() { n = --i; return *n; } \
    +    inline T &peekPrevious() const { iterator p = i; return *--p; } \
    +    inline void remove() \
    +    { if (c->constEnd() != const_iterator(n)) { i = c->erase(n); n = c->end(); } } \
    +    inline void setValue(const T &t) const { if (c->constEnd() != const_iterator(n)) *n = t; } \
    +    inline T &value() { Q_ASSERT(item_exists()); return *n; } \
    +    inline const T &value() const { Q_ASSERT(item_exists()); return *n; } \
    +    inline void insert(const T &t) { n = i = c->insert(i, t); ++i; } \
    +    inline bool findNext(const T &t) \
    +    { while (c->constEnd() != const_iterator(n = i)) if (*i++ == t) return true; return false; } \
    +    inline bool findPrevious(const T &t) \
    +    { while (c->constBegin() != const_iterator(i)) if (*(n = --i) == t) return true; \
    +      n = c->end(); return false;  } \
    +};//QhullMutable##C##Iterator
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLITERATOR_H
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullLinkedList.h b/xs/src/qhull/src/libqhullcpp/QhullLinkedList.h
    new file mode 100644
    index 0000000000..d4caf52c18
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullLinkedList.h
    @@ -0,0 +1,388 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullLinkedList.h#7 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLLINKEDLIST_H
    +#define QHULLLINKEDLIST_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +
    +#include   // ptrdiff_t, size_t
    +
    +#ifdef QHULL_USES_QT
    +#include 
    +#endif
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#include 
    +#include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! QhullLinkedList -- A linked list modeled on QLinkedList.
    +    //!   T is an opaque type with T(B *b), b=t.getBaseT(), t=t.next(), and t=t.prev().  The end node is a sentinel.
    +    //!   QhullQh/qhT owns the contents.
    +    //!   QhullLinkedList does not define erase(), clear(), removeFirst(), removeLast(), pop_back(), pop_front(), fromStdList()
    +    //!   Derived from Qt/core/tools/qlinkedlist.h and libqhull_r.h/FORALLfacets_()
    +    //! QhullLinkedList::const_iterator -- STL-style iterator
    +    //! QhullLinkedList::iterator -- STL-style iterator
    +    //! QhullLinkedListIterator -- Java-style iterator
    +    //!   Derived from Qt/core/tools/qiterator.h
    +    //!   Works with Qt's foreach keyword [Qt/src/corelib/global/qglobal.h]
    +
    +template 
    +class QhullLinkedList
    +{
    +#//!\name Defined here
    +public:
    +    class const_iterator;
    +    class iterator;
    +    typedef const_iterator  ConstIterator;
    +    typedef iterator    Iterator;
    +    typedef ptrdiff_t   difference_type;
    +    typedef countT      size_type;
    +    typedef T           value_type;
    +    typedef const value_type *const_pointer;
    +    typedef const value_type &const_reference;
    +    typedef value_type *pointer;
    +    typedef value_type &reference;
    +
    +#//!\name Fields
    +private:
    +    T                   begin_node;
    +    T                   end_node;     //! Sentinel node at end of list
    +
    +#//!\name Constructors
    +public:
    +                        QhullLinkedList(T b, T e) : begin_node(b), end_node(e) {}
    +                        //! Copy constructor copies begin_node and end_node, but not the list elements.  Needed for return by value and parameter passing.
    +                        QhullLinkedList(const QhullLinkedList &other) : begin_node(other.begin_node), end_node(other.end_node) {}
    +                        //! Copy assignment copies begin_node and end_node, but not the list elements.
    +                        QhullLinkedList & operator=(const QhullLinkedList &other) { begin_node= other.begin_node; end_node= other.end_node; return *this; }
    +                        ~QhullLinkedList() {}
    +
    +private:
    +                        //!disabled since a sentinel must be allocated as the private type
    +                        QhullLinkedList() {}
    +
    +public:
    +
    +#//!\name Conversions
    +#ifndef QHULL_NO_STL
    +    std::vector      toStdVector() const;
    +#endif
    +#ifdef QHULL_USES_QT
    +    QList            toQList() const;
    +#endif
    +
    +#//!\name GetSet
    +    countT              count() const;
    +                        //count(t) under #//!\name Search
    +    bool                isEmpty() const { return (begin_node==end_node); }
    +    bool                operator==(const QhullLinkedList &o) const;
    +    bool                operator!=(const QhullLinkedList &o) const { return !operator==(o); }
    +    size_t              size() const { return count(); }
    +
    +#//!\name Element access
    +    //! For back() and last(), return T instead of T& (T is computed)
    +    const T             back() const { return last(); }
    +    T                   back() { return last(); }
    +    const T &           first() const { QHULL_ASSERT(!isEmpty()); return begin_node; }
    +    T &                 first() { QHULL_ASSERT(!isEmpty()); return begin_node; }
    +    const T &           front() const { return first(); }
    +    T &                 front() { return first(); }
    +    const T             last() const { QHULL_ASSERT(!isEmpty()); return *--end(); }
    +    T                   last() { QHULL_ASSERT(!isEmpty()); return *--end(); }
    +
    +#//!\name Modify -- Allocation of opaque types not implemented.
    +
    +#//!\name Search
    +    bool                contains(const T &t) const;
    +    countT              count(const T &t) const;
    +
    +#//!\name Iterator
    +    iterator            begin() { return begin_node; }
    +    const_iterator      begin() const { return begin_node; }
    +    const_iterator      constBegin() const { return begin_node; }
    +    const_iterator      constEnd() const { return end_node; }
    +    iterator            end() { return end_node; }
    +    const_iterator      end() const { return end_node; }
    +
    +    class iterator {
    +
    +    private:
    +        T               i;
    +        friend class const_iterator;
    +
    +    public:
    +        typedef std::bidirectional_iterator_tag  iterator_category;
    +        typedef T           value_type;
    +        typedef value_type *pointer;
    +        typedef value_type &reference;
    +        typedef ptrdiff_t   difference_type;
    +
    +                        iterator() : i() {}
    +                        iterator(const T &t) : i(t) {}  //!< Automatic conversion to iterator
    +                        iterator(const iterator &o) : i(o.i) {}
    +        iterator &      operator=(const iterator &o) { i= o.i; return *this; }
    +
    +        const T &       operator*() const { return i; }
    +        T &             operator*() { return i; }
    +        // Do not define operator[]
    +        const T *       operator->() const { return &i; }
    +        T *             operator->() { return &i; }
    +        bool            operator==(const iterator &o) const { return i == o.i; }
    +        bool            operator!=(const iterator &o) const { return !operator==(o); }
    +        bool            operator==(const const_iterator &o) const { return i==reinterpret_cast(o).i; }
    +        bool            operator!=(const const_iterator &o) const { return !operator==(o); }
    +        iterator &      operator++() { i= i.next(); return *this; }
    +        iterator        operator++(int) { iterator o= i; i= i.next(); return o; }
    +        iterator &      operator--() { i= i.previous(); return *this; }
    +        iterator        operator--(int) { iterator o= i; i= i.previous(); return o; }
    +        iterator        operator+(int j) const;
    +        iterator        operator-(int j) const { return operator+(-j); }
    +        iterator &      operator+=(int j) { return (*this= *this + j); }
    +        iterator &      operator-=(int j) { return (*this= *this - j); }
    +    };//QhullLinkedList::iterator
    +
    +    class const_iterator {
    +
    +    private:
    +        T               i;
    +
    +    public:
    +        typedef std::bidirectional_iterator_tag  iterator_category;
    +        typedef T                 value_type;
    +        typedef const value_type *pointer;
    +        typedef const value_type &reference;
    +        typedef ptrdiff_t         difference_type;
    +
    +                        const_iterator() : i() {}
    +                        const_iterator(const T &t) : i(t) {}  //!< Automatic conversion to const_iterator
    +                        const_iterator(const iterator &o) : i(o.i) {}
    +                        const_iterator(const const_iterator &o) : i(o.i) {}
    +        const_iterator &operator=(const const_iterator &o) { i= o.i; return *this; }
    +
    +        const T &       operator*() const { return i; }
    +        const T *       operator->() const { return i; }
    +        bool            operator==(const const_iterator &o) const { return i == o.i; }
    +        bool            operator!=(const const_iterator &o) const { return !operator==(o); }
    +                        // No comparisons or iterator diff
    +        const_iterator &operator++() { i= i.next(); return *this; }
    +        const_iterator  operator++(int) { const_iterator o= i; i= i.next(); return o; }
    +        const_iterator &operator--() { i= i.previous(); return *this; }
    +        const_iterator  operator--(int) { const_iterator o= i; i= i.previous(); return o; }
    +        const_iterator  operator+(int j) const;
    +        const_iterator  operator-(int j) const { return operator+(-j); }
    +        const_iterator &operator+=(int j) { return (*this= *this + j); }
    +        const_iterator &operator-=(int j) { return (*this= *this - j); }
    +    };//QhullLinkedList::const_iterator
    +
    +};//QhullLinkedList
    +
    +template 
    +class QhullLinkedListIterator // FIXUP QH11016 define QhullMutableLinkedListIterator
    +{
    +    typedef typename QhullLinkedList::const_iterator const_iterator;
    +    const QhullLinkedList *c;
    +    const_iterator      i;
    +
    +public:
    +                        QhullLinkedListIterator(const QhullLinkedList &container) : c(&container), i(c->constBegin()) {}
    +    QhullLinkedListIterator & operator=(const QhullLinkedList &container) { c= &container; i= c->constBegin(); return *this; }
    +    bool                findNext(const T &t);
    +    bool                findPrevious(const T &t);
    +    bool                hasNext() const { return i != c->constEnd(); }
    +    bool                hasPrevious() const { return i != c->constBegin(); }
    +    T                   next() { return *i++; }
    +    T                   peekNext() const { return *i; }
    +    T                   peekPrevious() const { const_iterator p= i; return *--p; }
    +    T                   previous() { return *--i; }
    +    void                toFront() { i= c->constBegin(); }
    +    void                toBack() { i= c->constEnd(); }
    +};//QhullLinkedListIterator
    +
    +#//!\name == Definitions =========================================
    +
    +#//!\name Conversion
    +
    +#ifndef QHULL_NO_STL
    +template 
    +std::vector QhullLinkedList::
    +toStdVector() const
    +{
    +    std::vector tmp;
    +    std::copy(constBegin(), constEnd(), std::back_inserter(tmp));
    +    return tmp;
    +}//toStdVector
    +#endif
    +
    +#ifdef QHULL_USES_QT
    +template 
    +QList  QhullLinkedList::
    +toQList() const
    +{
    +    QhullLinkedListIterator i(*this);
    +    QList ls;
    +    while(i.hasNext()){
    +        ls.append(i.next());
    +    }
    +    return ls;
    +}//toQList
    +#endif
    +
    +#//!\name GetSet
    +
    +template 
    +countT QhullLinkedList::
    +count() const
    +{
    +    const_iterator i= begin_node;
    +    countT c= 0;
    +    while(i != end_node){
    +        c++;
    +        i++;
    +    }
    +    return c;
    +}//count
    +
    +#//!\name Search
    +
    +template 
    +bool QhullLinkedList::
    +contains(const T &t) const
    +{
    +    const_iterator i= begin_node;
    +    while(i != end_node){
    +        if(i==t){
    +            return true;
    +        }
    +        i++;
    +    }
    +    return false;
    +}//contains
    +
    +template 
    +countT QhullLinkedList::
    +count(const T &t) const
    +{
    +    const_iterator i= begin_node;
    +    countT c= 0;
    +    while(i != end_node){
    +        if(i==t){
    +            c++;
    +        }
    +        i++;
    +    }
    +    return c;
    +}//count
    +
    +template 
    +bool QhullLinkedList::
    +operator==(const QhullLinkedList &l) const
    +{
    +    if(begin_node==l.begin_node){
    +        return (end_node==l.end_node);
    +    }
    +    T i= begin_node;
    +    T il= l.begin_node;
    +    while(i != end_node){
    +        if(i != il){
    +            return false;
    +        }
    +        i= static_cast(i.next());
    +        il= static_cast(il.next());
    +    }
    +    if(il != l.end_node){
    +        return false;
    +    }
    +    return true;
    +}//operator==
    +
    +#//!\name Iterator
    +
    +template 
    +typename QhullLinkedList::iterator  QhullLinkedList::iterator::
    +operator+(int j) const
    +{
    +    T n= i;
    +    if(j>0){
    +        while(j--){
    +            n= n.next();
    +        }
    +    }else{
    +        while(j++){
    +            n= n.previous();
    +        }
    +    }
    +    return iterator(n);
    +}//operator+
    +
    +template 
    +typename QhullLinkedList::const_iterator  QhullLinkedList::const_iterator::
    +operator+(int j) const
    +{
    +    T n= i;
    +    if(j>0){
    +        while(j--){
    +            n= n.next();
    +        }
    +    }else{
    +        while(j++){
    +            n= n.previous();
    +        }
    +    }
    +    return const_iterator(n);
    +}//operator+
    +
    +#//!\name QhullLinkedListIterator
    +
    +template 
    +bool QhullLinkedListIterator::
    +findNext(const T &t)
    +{
    +    while(i != c->constEnd()){
    +        if (*i++ == t){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findNext
    +
    +template 
    +bool QhullLinkedListIterator::
    +findPrevious(const T &t)
    +{
    +    while(i!=c->constBegin()){
    +        if(*(--i)==t){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findNext
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +template 
    +std::ostream &
    +operator<<(std::ostream &os, const orgQhull::QhullLinkedList &qs)
    +{
    +    typename orgQhull::QhullLinkedList::const_iterator i;
    +    for(i= qs.begin(); i != qs.end(); ++i){
    +        os << *i;
    +    }
    +    return os;
    +}//operator<<
    +
    +#endif // QHULLLINKEDLIST_H
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullPoint.cpp b/xs/src/qhull/src/libqhullcpp/QhullPoint.cpp
    new file mode 100644
    index 0000000000..f5e9124609
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullPoint.cpp
    @@ -0,0 +1,203 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullPoint.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/QhullPoint.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructors
    +
    +
    +QhullPoint::
    +QhullPoint(const Qhull &q) 
    +: point_coordinates(0)
    +, qh_qh(q.qh())
    +, point_dimension(q.hullDimension())
    +{
    +}//QhullPoint
    +
    +QhullPoint::
    +QhullPoint(const Qhull &q, coordT *c) 
    +: point_coordinates(c)
    +, qh_qh(q.qh())
    +, point_dimension(q.hullDimension())
    +{
    +    QHULL_ASSERT(q.hullDimension()>0);
    +}//QhullPoint dim, coordT
    +
    +QhullPoint::
    +QhullPoint(const Qhull &q, int pointDimension, coordT *c) 
    +: point_coordinates(c)
    +, qh_qh(q.qh())
    +, point_dimension(pointDimension)
    +{
    +}//QhullPoint dim, coordT
    +
    +//! QhullPoint of Coordinates with point_dimension==c.count()
    +QhullPoint::
    +QhullPoint(const Qhull &q, Coordinates &c) 
    +: point_coordinates(c.data())
    +, qh_qh(q.qh())
    +, point_dimension(c.count())
    +{
    +}//QhullPoint Coordinates
    +
    +#//!\name Conversions
    +
    +// See qt-qhull.cpp for QList conversion
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullPoint::
    +toStdVector() const
    +{
    +    QhullPointIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        vs.push_back(i.next());
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +//! QhullPoint is equal if it has the same address and dimension
    +//! If !qh_qh, returns true if dimension and coordinates are equal
    +//! If qh_qh, returns true if the distance between points is less than qh_qh->distanceEpsilon()
    +//!\todo Compares distance with distance-to-hyperplane (distanceEpsilon).   Is that correct?
    +bool QhullPoint::
    +operator==(const QhullPoint &other) const
    +{
    +    if(point_dimension!=other.point_dimension){
    +        return false;
    +    }
    +    const coordT *c= point_coordinates;
    +    const coordT *c2= other.point_coordinates;
    +    if(c==c2){
    +        return true;
    +    }
    +    if(!c || !c2){
    +        return false;
    +    }
    +    if(!qh_qh || qh_qh->hull_dim==0){
    +        for(int k= point_dimension; k--; ){
    +            if(*c++ != *c2++){
    +                return false;
    +            }
    +        }
    +        return true;
    +    }
    +    double dist2= 0.0;
    +    for(int k= point_dimension; k--; ){
    +        double diff= *c++ - *c2++;
    +        dist2 += diff*diff;
    +    }
    +    dist2= sqrt(dist2);
    +    return (dist2 < qh_qh->distanceEpsilon());
    +}//operator==
    +
    +#//!\name Methods
    +
    +//! Return distance between two points.
    +double QhullPoint::
    +distance(const QhullPoint &p) const
    +{
    +    const coordT *c= point_coordinates;
    +    const coordT *c2= p.point_coordinates;
    +    int dim= point_dimension;
    +    if(dim!=p.point_dimension){
    +        throw QhullError(10075, "QhullPoint error: Expecting dimension %d for distance().  Got %d", dim, p.point_dimension);
    +    }
    +    if(!c || !c2){
    +        throw QhullError(10076, "QhullPoint error: Cannot compute distance() for undefined point");
    +    }
    +    double dist;
    +
    +    switch(dim){
    +  case 2:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]);
    +      break;
    +  case 3:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]);
    +      break;
    +  case 4:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]);
    +      break;
    +  case 5:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]) + (c[4]-c2[4])*(c[4]-c2[4]);
    +      break;
    +  case 6:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]) + (c[4]-c2[4])*(c[4]-c2[4]) + (c[5]-c2[5])*(c[5]-c2[5]);
    +      break;
    +  case 7:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]) + (c[4]-c2[4])*(c[4]-c2[4]) + (c[5]-c2[5])*(c[5]-c2[5]) + (c[6]-c2[6])*(c[6]-c2[6]);
    +      break;
    +  case 8:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]) + (c[4]-c2[4])*(c[4]-c2[4]) + (c[5]-c2[5])*(c[5]-c2[5]) + (c[6]-c2[6])*(c[6]-c2[6]) + (c[7]-c2[7])*(c[7]-c2[7]);
    +      break;
    +  default:
    +      dist= 0.0;
    +      for(int k=dim; k--; ){
    +          dist += (*c - *c2) * (*c - *c2);
    +          ++c;
    +          ++c2;
    +      }
    +      break;
    +    }
    +    return sqrt(dist);
    +}//distance
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::ostream;
    +using orgQhull::QhullPoint;
    +
    +//! Same as qh_printpointid [io.c]
    +ostream &
    +operator<<(ostream &os, const QhullPoint::PrintPoint &pr)
    +{
    +    QhullPoint p= *pr.point; 
    +    countT i= p.id();
    +    if(pr.point_message){
    +        if(*pr.point_message){
    +            os << pr.point_message << " ";
    +        }
    +        if(pr.with_identifier && (i!=qh_IDunknown) && (i!=qh_IDnone)){
    +            os << "p" << i << ": ";
    +        }
    +    }
    +    const realT *c= p.coordinates();
    +    for(int k=p.dimension(); k--; ){
    +        realT r= *c++;
    +        if(pr.point_message){
    +            os << " " << r; // FIXUP QH11010 %8.4g
    +        }else{
    +            os << " " << r; // FIXUP QH11010 qh_REAL_1
    +        }
    +    }
    +    os << std::endl;
    +    return os;
    +}//printPoint
    +
    +ostream & 
    +operator<<(ostream &os, const QhullPoint &p)
    +{
    +    os << p.print(""); 
    +    return os;
    +}//operator<<
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullPoint.h b/xs/src/qhull/src/libqhullcpp/QhullPoint.h
    new file mode 100644
    index 0000000000..17f94ab364
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullPoint.h
    @@ -0,0 +1,136 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullPoint.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHPOINT_H
    +#define QHPOINT_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullIterator.h"
    +#include "libqhullcpp/QhullQh.h"
    +#include "libqhullcpp/Coordinates.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    class QhullPoint;  //!<  QhullPoint as a pointer and dimension to shared memory
    +    class QhullPointIterator; //!< Java-style iterator for QhullPoint coordinates
    +
    +#//!\name Used here
    +    class Qhull;
    +
    +//! A QhullPoint is a dimension and an array of coordinates.
    +//! With Qhull/QhullQh, a QhullPoint has an identifier.  Point equality is relative to qh.distanceEpsilon
    +class QhullPoint {
    +
    +#//!\name Iterators
    +public:
    +    typedef coordT *                    base_type;  // for QhullPointSet
    +    typedef const coordT *              iterator;
    +    typedef const coordT *              const_iterator;
    +    typedef QhullPoint::iterator        Iterator;
    +    typedef QhullPoint::const_iterator  ConstIterator;
    +
    +#//!\name Fields
    +protected: // For QhullPoints::iterator, QhullPoints::const_iterator
    +    coordT *            point_coordinates;  //!< Pointer to first coordinate,   0 if undefined
    +    QhullQh *           qh_qh;              //!< qhT for this instance of Qhull.  0 if undefined.
    +                                            //!< operator==() returns true if points within sqrt(qh_qh->distanceEpsilon())
    +                                            //!< If !qh_qh, id() is -3, and operator==() requires equal coordinates
    +    int                 point_dimension;    //!< Default dimension is qh_qh->hull_dim
    +public:
    +
    +#//!\name Constructors
    +    //! QhullPoint, PointCoordinates, and QhullPoints have similar constructors
    +    //! If Qhull/QhullQh is not initialized, then QhullPoint.dimension() is zero unless explicitly set
    +    //! Cannot define QhullPoints(int pointDimension) since it is ambiguous with QhullPoints(QhullQh *qqh)
    +                        QhullPoint() : point_coordinates(0), qh_qh(0), point_dimension(0) {}
    +                        QhullPoint(int pointDimension, coordT *c) : point_coordinates(c), qh_qh(0), point_dimension(pointDimension) { QHULL_ASSERT(pointDimension>0); }
    +    explicit            QhullPoint(const Qhull &q);
    +                        QhullPoint(const Qhull &q, coordT *c);
    +                        QhullPoint(const Qhull &q, Coordinates &c);
    +                        QhullPoint(const Qhull &q, int pointDimension, coordT *c);
    +    explicit            QhullPoint(QhullQh *qqh) : point_coordinates(0), qh_qh(qqh), point_dimension(qqh->hull_dim) {}
    +                        QhullPoint(QhullQh *qqh, coordT *c) : point_coordinates(c), qh_qh(qqh), point_dimension(qqh->hull_dim) { QHULL_ASSERT(qqh->hull_dim>0); }
    +                        QhullPoint(QhullQh *qqh, Coordinates &c) : point_coordinates(c.data()), qh_qh(qqh), point_dimension(c.count()) {}
    +                        QhullPoint(QhullQh *qqh, int pointDimension, coordT *c) : point_coordinates(c), qh_qh(qqh), point_dimension(pointDimension) {}
    +                        //! Creates an alias.  Does not make a deep copy of the point.  Needed for return by value and parameter passing.
    +                        QhullPoint(const QhullPoint &other) : point_coordinates(other.point_coordinates), qh_qh(other.qh_qh), point_dimension(other.point_dimension) {}
    +                        //! Creates an alias.  Does not make a deep copy of the point.  Needed for vector
    +    QhullPoint &        operator=(const QhullPoint &other) { point_coordinates= other.point_coordinates; qh_qh= other.qh_qh; point_dimension= other.point_dimension; return *this; }
    +                        ~QhullPoint() {}
    +
    +
    +#//!\name Conversions
    +
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList       toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +public:
    +    const coordT *      coordinates() const { return point_coordinates; }  //!< 0 if undefined
    +    coordT *            coordinates() { return point_coordinates; }        //!< 0 if undefined
    +    void                defineAs(coordT *c) { QHULL_ASSERT(point_dimension>0); point_coordinates= c; }
    +    void                defineAs(int pointDimension, coordT *c) { QHULL_ASSERT(pointDimension>=0); point_coordinates= c; point_dimension= pointDimension; }
    +    void                defineAs(QhullPoint &other) { point_coordinates= other.point_coordinates; qh_qh= other.qh_qh; point_dimension= other.point_dimension; }
    +    int                 dimension() const { return point_dimension; }
    +    coordT *            getBaseT() const { return point_coordinates; } // for QhullPointSet
    +    countT              id() const { return qh_pointid(qh_qh, point_coordinates); } // NOerrors
    +    bool                isValid() const { return (point_coordinates!=0 && point_dimension>0); };
    +    bool                operator==(const QhullPoint &other) const;
    +    bool                operator!=(const QhullPoint &other) const { return ! operator==(other); }
    +    const coordT &      operator[](int idx) const { QHULL_ASSERT(point_coordinates!=0 && idx>=0 && idx=0 && idx
    +#include 
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +// Implemented via QhullSet.h
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullPointSet;
    +using orgQhull::QhullPointSetIterator;
    +
    +ostream &
    +operator<<(ostream &os, const QhullPointSet::PrintIdentifiers &pr)
    +{
    +    os << pr.print_message;
    +    const QhullPointSet s= *pr.point_set;
    +    QhullPointSetIterator i(s);
    +    while(i.hasNext()){
    +        if(i.hasPrevious()){
    +            os << " ";
    +        }
    +        const QhullPoint point= i.next();
    +        countT id= point.id();
    +        os << "p" << id;
    +
    +    }
    +    os << endl;
    +    return os;
    +}//PrintIdentifiers
    +
    +ostream &
    +operator<<(ostream &os, const QhullPointSet::PrintPointSet &pr)
    +{
    +    os << pr.print_message;
    +    const QhullPointSet s= *pr.point_set;
    +    for(QhullPointSet::const_iterator i=s.begin(); i != s.end(); ++i){
    +        const QhullPoint point= *i;
    +        os << point;
    +    }
    +    return os;
    +}//printPointSet
    +
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullPointSet.h b/xs/src/qhull/src/libqhullcpp/QhullPointSet.h
    new file mode 100644
    index 0000000000..8562e170ea
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullPointSet.h
    @@ -0,0 +1,77 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullPointSet.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLPOINTSET_H
    +#define QHULLPOINTSET_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullSet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +    class QhullPoint;
    +
    +#//!\name Defined here
    +    //! QhullPointSet -- a set of coordinate pointers with input dimension
    +    // with const_iterator and iterator
    +    class QhullPointSet;
    +
    +class QhullPointSet : public QhullSet {
    +
    +private:
    +#//!\name Fields
    +    // no fields
    +public:
    +
    +#//!\name Construct
    +                        QhullPointSet(const Qhull &q, setT *s) : QhullSet(q, s) {}
    +                        //Conversion from setT* is not type-safe.  Implicit conversion for void* to T
    +                        QhullPointSet(QhullQh *qqh, setT *s) : QhullSet(qqh, s) {}
    +                        //Copy constructor copies pointer but not contents.  Needed for return by value and parameter passing.
    +                        QhullPointSet(const QhullPointSet &other) : QhullSet(other) {}
    +                        //!Assignment copies pointers but not contents.
    +    QhullPointSet &     operator=(const QhullPointSet &other) { QhullSet::operator=(other); return *this; }
    +                        ~QhullPointSet() {}
    +
    +                        //!Default constructor disabled.
    +private:
    +                        QhullPointSet();
    +public:
    +
    +#//!\name IO
    +    struct PrintIdentifiers{
    +        const QhullPointSet *point_set;
    +        const char *    print_message; //!< non-null message
    +        PrintIdentifiers(const char *message, const QhullPointSet *s) : point_set(s), print_message(message) {}
    +    };//PrintIdentifiers
    +    PrintIdentifiers printIdentifiers(const char *message) const { return PrintIdentifiers(message, this); }
    +
    +    struct PrintPointSet{
    +        const QhullPointSet *point_set;
    +        const char *    print_message;  //!< non-null message
    +        PrintPointSet(const char *message, const QhullPointSet &s) : point_set(&s), print_message(message) {}
    +    };//PrintPointSet
    +    PrintPointSet       print(const char *message) const { return PrintPointSet(message, *this); }
    +
    +};//QhullPointSet
    +
    +typedef QhullSetIterator  QhullPointSetIterator;
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullPointSet::PrintIdentifiers &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullPointSet::PrintPointSet &pr);
    +
    +#endif // QHULLPOINTSET_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullPoints.cpp b/xs/src/qhull/src/libqhullcpp/QhullPoints.cpp
    new file mode 100644
    index 0000000000..2320b5007a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullPoints.cpp
    @@ -0,0 +1,320 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullPoints.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/QhullPoints.h"
    +
    +#include "libqhullcpp/Qhull.h"
    +
    +#include 
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#endif
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructors
    +
    +QhullPoints::
    +QhullPoints(const Qhull &q)
    +: point_first(0)
    +, point_end(0)
    +, qh_qh(q.qh())
    +, point_dimension(q.hullDimension())
    +{
    +}//QhullPoints Qhull
    +
    +QhullPoints::
    +QhullPoints(const Qhull &q, countT coordinateCount2, coordT *c)
    +: point_first(c)
    +, point_end(c+coordinateCount2)
    +, qh_qh(q.qh())
    +, point_dimension(q.hullDimension())
    +{
    +    QHULL_ASSERT(q.hullDimension());
    +    QHULL_ASSERT(coordinateCount2>=0);
    +}//QhullPoints Qhull dim
    +
    +QhullPoints::
    +QhullPoints(const Qhull &q, int pointDimension, countT coordinateCount2, coordT *c)
    +: point_first(c)
    +, point_end(c+coordinateCount2)
    +, qh_qh(q.qh())
    +, point_dimension(pointDimension)
    +{
    +    QHULL_ASSERT(pointDimension>=0);
    +    QHULL_ASSERT(coordinateCount2>=0);
    +}//QhullPoints Qhull dim coordT
    +
    +QhullPoints::
    +QhullPoints(QhullQh *qqh, int pointDimension, countT coordinateCount2, coordT *c)
    +: point_first(c)
    +, point_end(c+coordinateCount2)
    +, qh_qh(qqh)
    +, point_dimension(pointDimension)
    +{
    +    QHULL_ASSERT(pointDimension>=0);
    +    QHULL_ASSERT(coordinateCount2>=0);
    +}//QhullPoints QhullQh dim coordT
    +
    +#//!\name Conversions
    +// See qt-qhull.cpp for QList conversion
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullPoints::
    +toStdVector() const
    +{
    +    QhullPointsIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        vs.push_back(i.next());
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +countT QhullPoints::
    +extraCoordinatesCount() const
    +{
    +    if(point_dimension>0){
    +        return (countT)((point_end-point_first)%(size_t)point_dimension);
    +    }
    +    return 0;
    +}//extraCoordinatesCount
    +
    +//! QhullPoints is equal if the same address, or if the coordinates are identical
    +//! Use QhullPoint.operator==() for DISTround equality
    +bool QhullPoints::
    +operator==(const QhullPoints &other) const
    +{
    +    if((point_end-point_first) != (other.point_end-other.point_first)){
    +        return false;
    +    }
    +    if(point_dimension!=other.point_dimension){
    +        return false;
    +    }
    +    if(point_first==other.point_first){
    +        return true;
    +    }
    +    if(!qh_qh || qh_qh->hull_dim==0){
    +        const coordT *c= point_first;
    +        const coordT *c2= other.point_first;
    +        while(chull_dim : 0);
    +    point_first= 0;
    +    point_end= 0;
    +}//resetQhullQh
    +
    +QhullPoint QhullPoints::
    +value(countT idx) const
    +{
    +    QhullPoint p(qh_qh);
    +    if(idx>=0 && idx=0 && idx=n){
    +        n= 0;
    +    }else if(length<0 || idx+length>=n){
    +        n -= idx;
    +    }else{
    +        n -= idx+length;
    +    }
    +    return QhullPoints(qh_qh, point_dimension, n*point_dimension, point_first+idx*point_dimension);
    +}//mid
    +
    +#//!\name QhullPointsIterator
    +
    +bool QhullPointsIterator::
    +findNext(const QhullPoint &p)
    +{
    +    while(i!=ps->constEnd()){
    +        if(*i++ == p){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findNext
    +
    +bool QhullPointsIterator::
    +findPrevious(const QhullPoint &p)
    +{
    +    while(i!=ps->constBegin()){
    +        if(*--i == p){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findPrevious
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::ostream;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullPoints;
    +using orgQhull::QhullPointsIterator;
    +
    +ostream &
    +operator<<(ostream &os, const QhullPoints &p)
    +{
    +    QhullPointsIterator i(p);
    +    while(i.hasNext()){
    +        os << i.next();
    +    }
    +    return os;
    +}//operator<  // ptrdiff_t, size_t
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    class QhullPoints;          //!< One or more points Coordinate pointers with dimension and iterators
    +    class QhullPointsIterator;  //!< Java-style iterator
    +
    +//! QhullPoints are an array of QhullPoint as pointers into an array of coordinates.
    +//! For Qhull/QhullQh, QhullPoints must use hull_dim.  Can change QhullPoint to input_dim if needed for Delaunay input site
    +class QhullPoints {
    +
    +private:
    +#//!\name Fields
    +    coordT *            point_first; //!< First coordinate of an array of points of point_dimension
    +    coordT *            point_end;   //!< End of point coordinates (end>=first).  Trailing coordinates ignored
    +    QhullQh *           qh_qh;       //!< Maybe initialized NULL to allow ownership by RboxPoints
    +                                     //!< qh_qh used for QhullPoint() and qh_qh->hull_dim in constructor
    +    int                 point_dimension;  //!< Dimension, >=0
    +
    +public:
    +#//!\name Subtypes
    +    class const_iterator;
    +    class iterator;
    +    typedef QhullPoints::const_iterator ConstIterator;
    +    typedef QhullPoints::iterator       Iterator;
    +
    +#//!\name Construct
    +    //! QhullPoint, PointCoordinates, and QhullPoints have similar constructors
    +    //! If Qhull/QhullQh is not initialized, then QhullPoints.dimension() is zero unless explicitly set
    +    //! Cannot define QhullPoints(int pointDimension) since it is ambiguous with QhullPoints(QhullQh *qqh)
    +                        QhullPoints() : point_first(0), point_end(0), qh_qh(0), point_dimension(0) { }
    +                        QhullPoints(int pointDimension, countT coordinateCount2, coordT *c) : point_first(c), point_end(c+coordinateCount2), qh_qh(0), point_dimension(pointDimension) { QHULL_ASSERT(pointDimension>=0); }
    +    explicit            QhullPoints(const Qhull &q);
    +                        QhullPoints(const Qhull &q, countT coordinateCount2, coordT *c);
    +                        QhullPoints(const Qhull &q, int pointDimension, countT coordinateCount2, coordT *c);
    +    explicit            QhullPoints(QhullQh *qqh) : point_first(0), point_end(0), qh_qh(qqh), point_dimension(qqh ? qqh->hull_dim : 0) { }
    +                        QhullPoints(QhullQh *qqh, countT coordinateCount2, coordT *c) : point_first(c), point_end(c+coordinateCount2), qh_qh(qqh), point_dimension(qqh ? qqh->hull_dim : 0) { QHULL_ASSERT(qqh && qqh->hull_dim>0); }
    +                        QhullPoints(QhullQh *qqh, int pointDimension, countT coordinateCount2, coordT *c);
    +                        //! Copy constructor copies pointers but not contents.  Needed for return by value and parameter passing.
    +                        QhullPoints(const QhullPoints &other)  : point_first(other.point_first), point_end(other.point_end), qh_qh(other.qh_qh), point_dimension(other.point_dimension) {}
    +    QhullPoints &       operator=(const QhullPoints &other) { point_first= other.point_first; point_end= other.point_end; qh_qh= other.qh_qh; point_dimension= other.point_dimension; return *this; }
    +                        ~QhullPoints() {}
    +
    +public:
    +
    +#//!\name Conversion
    +
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList   toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +    // Constructs QhullPoint.  Cannot return reference.
    +    const QhullPoint    at(countT idx) const { /* point_first==0 caught by point_end assert */ coordT *p= point_first+idx*point_dimension; QHULL_ASSERT(p=0 && coordinatesCount>=0 && c!=0); point_first= c; point_end= c+coordinatesCount; point_dimension= pointDimension; }
    +    void                defineAs(countT coordinatesCount, coordT *c) { QHULL_ASSERT((point_dimension>0 && coordinatesCount>=0 && c!=0) || (c==0 && coordinatesCount==0)); point_first= c; point_end= c+coordinatesCount; }
    +    void                defineAs(const QhullPoints &other) { point_first= other.point_first; point_end= other.point_end; qh_qh= other.qh_qh; point_dimension= other.point_dimension; }
    +    int                 dimension() const { return point_dimension; }
    +    ConstIterator       end() const { return ConstIterator(qh_qh, point_dimension, point_end); }
    +    Iterator            end() { return Iterator(qh_qh, point_dimension, point_end); }
    +    coordT *            extraCoordinates() const { return extraCoordinatesCount() ? (point_end-extraCoordinatesCount()) : 0; }
    +    countT              extraCoordinatesCount() const;  // WARN64
    +    // Constructs QhullPoint.  Cannot return reference.
    +    const QhullPoint    first() const { return QhullPoint(qh_qh, point_dimension, point_first); }
    +    QhullPoint          first() { return QhullPoint(qh_qh, point_dimension, point_first); }
    +    // Constructs QhullPoint.  Cannot return reference.
    +    const QhullPoint    front() const { return first(); }
    +    QhullPoint          front() { return first(); }
    +    bool                includesCoordinates(const coordT *c) const { return c>=point_first && c(other)); return *this; }
    +
    +        // Need 'const QhullPoint' to maintain const
    +        const QhullPoint & operator*() const { return *this; }
    +        QhullPoint &    operator*() { return *this; }
    +        const QhullPoint * operator->() const { return this; }
    +        QhullPoint *    operator->() { return this; }
    +        // value instead of reference since advancePoint() modifies self
    +        QhullPoint      operator[](countT idx) const { QhullPoint result= *this; result.advancePoint(idx); return result; }
    +        bool            operator==(const iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return (point_coordinates==o.point_coordinates && point_dimension==o.point_dimension); }
    +        bool            operator!=(const iterator &o) const { return !operator==(o); }
    +        bool            operator<(const iterator &o) const  { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates < o.point_coordinates; }
    +        bool            operator<=(const iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates <= o.point_coordinates; }
    +        bool            operator>(const iterator &o) const  { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates > o.point_coordinates; }
    +        bool            operator>=(const iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates >= o.point_coordinates; }
    +        // reinterpret_cast to break circular dependency
    +        bool            operator==(const QhullPoints::const_iterator &o) const { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return (point_coordinates==reinterpret_cast(o).point_coordinates && point_dimension==reinterpret_cast(o).point_dimension); }
    +        bool            operator!=(const QhullPoints::const_iterator &o) const { return !operator==(reinterpret_cast(o)); }
    +        bool            operator<(const QhullPoints::const_iterator &o) const  { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return point_coordinates < reinterpret_cast(o).point_coordinates; }
    +        bool            operator<=(const QhullPoints::const_iterator &o) const { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return point_coordinates <= reinterpret_cast(o).point_coordinates; }
    +        bool            operator>(const QhullPoints::const_iterator &o) const  { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return point_coordinates > reinterpret_cast(o).point_coordinates; }
    +        bool            operator>=(const QhullPoints::const_iterator &o) const { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return point_coordinates >= reinterpret_cast(o).point_coordinates; }
    +        iterator &      operator++() { advancePoint(1); return *this; }
    +        iterator        operator++(int) { iterator n= *this; operator++(); return iterator(n); }
    +        iterator &      operator--() { advancePoint(-1); return *this; }
    +        iterator        operator--(int) { iterator n= *this; operator--(); return iterator(n); }
    +        iterator &      operator+=(countT idx) { advancePoint(idx); return *this; }
    +        iterator &      operator-=(countT idx) { advancePoint(-idx); return *this; }
    +        iterator        operator+(countT idx) const { iterator n= *this; n.advancePoint(idx); return n; }
    +        iterator        operator-(countT idx) const { iterator n= *this; n.advancePoint(-idx); return n; }
    +        difference_type operator-(iterator o) const { QHULL_ASSERT(qh_qh==o.qh_qh && point_dimension==o.point_dimension); return (point_dimension ? (point_coordinates-o.point_coordinates)/point_dimension : 0); }
    +    };//QhullPoints::iterator
    +
    +#//!\name QhullPoints::const_iterator
    +    //!\todo FIXUP QH11018 const_iterator same as iterator.  SHould have a common definition
    +    class const_iterator : public QhullPoint {
    +
    +    public:
    +        typedef std::random_access_iterator_tag  iterator_category;
    +        typedef QhullPoint          value_type;
    +        typedef const value_type *  pointer;
    +        typedef const value_type &  reference;
    +        typedef ptrdiff_t           difference_type;
    +
    +                        const_iterator(const QhullPoints::iterator &o) : QhullPoint(*o) {}
    +        explicit        const_iterator(const QhullPoints &ps) : QhullPoint(ps.qh(), ps.dimension(), ps.coordinates()) {}
    +                        const_iterator(const int pointDimension, coordT *c): QhullPoint(pointDimension, c) {}
    +                        const_iterator(const Qhull &q, coordT *c): QhullPoint(q, c) {}
    +                        const_iterator(const Qhull &q, int pointDimension, coordT *c): QhullPoint(q, pointDimension, c) {}
    +                        const_iterator(QhullQh *qqh, coordT *c): QhullPoint(qqh, c) {}
    +                        const_iterator(QhullQh *qqh, int pointDimension, coordT *c): QhullPoint(qqh, pointDimension, c) {}
    +                        const_iterator(const const_iterator &o) : QhullPoint(*o) {}
    +        const_iterator &operator=(const const_iterator &o) { defineAs(const_cast(o)); return *this; }
    +
    +        // value/non-const since advancePoint(1), etc. modifies self
    +        const QhullPoint & operator*() const { return *this; }
    +        const QhullPoint * operator->() const { return this; }
    +        // value instead of reference since advancePoint() modifies self
    +        const QhullPoint operator[](countT idx) const { QhullPoint n= *this; n.advancePoint(idx); return n; }
    +        bool            operator==(const const_iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return (point_coordinates==o.point_coordinates && point_dimension==o.point_dimension); }
    +        bool            operator!=(const const_iterator &o) const { return ! operator==(o); }
    +        bool            operator<(const const_iterator &o) const  { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates < o.point_coordinates; }
    +        bool            operator<=(const const_iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates <= o.point_coordinates; }
    +        bool            operator>(const const_iterator &o) const  { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates > o.point_coordinates; }
    +        bool            operator>=(const const_iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates >= o.point_coordinates; }
    +        const_iterator &operator++() { advancePoint(1); return *this; }
    +        const_iterator  operator++(int) { const_iterator n= *this; operator++(); return const_iterator(n); }
    +        const_iterator &operator--() { advancePoint(-1); return *this; }
    +        const_iterator  operator--(int) { const_iterator n= *this; operator--(); return const_iterator(n); }
    +        const_iterator &operator+=(countT idx) { advancePoint(idx); return *this; }
    +        const_iterator &operator-=(countT idx) { advancePoint(-idx); return *this; }
    +        const_iterator  operator+(countT idx) const { const_iterator n= *this; n.advancePoint(idx); return n; }
    +        const_iterator  operator-(countT idx) const { const_iterator n= *this; n.advancePoint(-idx); return n; }
    +        difference_type operator-(const_iterator o) const { QHULL_ASSERT(qh_qh==o.qh_qh && point_dimension==o.point_dimension); return (point_dimension ? (point_coordinates-o.point_coordinates)/point_dimension : 0); }
    +    };//QhullPoints::const_iterator
    +
    +#//!\name IO
    +    struct PrintPoints{
    +        const QhullPoints  *points;
    +        const char *    point_message;
    +        bool            with_identifier;
    +        PrintPoints(const char *message, bool withIdentifier, const QhullPoints &ps) : points(&ps), point_message(message), with_identifier(withIdentifier) {}
    +    };//PrintPoints
    +    PrintPoints          print(const char *message) const { return PrintPoints(message, false, *this); }
    +    PrintPoints          printWithIdentifier(const char *message) const { return PrintPoints(message, true, *this); }
    +};//QhullPoints
    +
    +// Instead of QHULL_DECLARE_SEQUENTIAL_ITERATOR because next(),etc would return a reference to a temporary
    +class QhullPointsIterator
    +{
    +    typedef QhullPoints::const_iterator const_iterator;
    +
    +#//!\name Fields
    +private:
    +    const QhullPoints  *ps;
    +    const_iterator      i;
    +
    +public:
    +                        QhullPointsIterator(const QhullPoints &other) : ps(&other), i(ps->constBegin()) {}
    +    QhullPointsIterator &operator=(const QhullPoints &other) { ps = &other; i = ps->constBegin(); return *this; }
    +
    +    bool                findNext(const QhullPoint &t);
    +    bool                findPrevious(const QhullPoint &t);
    +    bool                hasNext() const { return i != ps->constEnd(); }
    +    bool                hasPrevious() const { return i != ps->constBegin(); }
    +    QhullPoint          next() { return *i++; }
    +    QhullPoint          peekNext() const { return *i; }
    +    QhullPoint          peekPrevious() const { const_iterator p = i; return *--p; }
    +    QhullPoint          previous() { return *--i; }
    +    void                toBack() { i = ps->constEnd(); }
    +    void                toFront() { i = ps->constBegin(); }
    +};//QhullPointsIterator
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &          operator<<(std::ostream &os, const orgQhull::QhullPoints &p);
    +std::ostream &          operator<<(std::ostream &os, const orgQhull::QhullPoints::PrintPoints &pr);
    +
    +#endif // QHULLPOINTS_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullQh.cpp b/xs/src/qhull/src/libqhullcpp/QhullQh.cpp
    new file mode 100644
    index 0000000000..3635337001
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullQh.cpp
    @@ -0,0 +1,237 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullQh.cpp#5 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullQh -- Qhull's global data structure, qhT, as a C++ class
    +
    +
    +#include "libqhullcpp/QhullQh.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullStat.h"
    +
    +#include 
    +#include 
    +
    +#include 
    +
    +using std::cerr;
    +using std::string;
    +using std::vector;
    +using std::ostream;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Global variables
    +const double QhullQh::
    +default_factor_epsilon= 1.0;
    +
    +#//!\name Constructor, destructor, etc.
    +
    +//! Derived from qh_new_qhull[user.c]
    +QhullQh::
    +QhullQh()
    +: qhull_status(qh_ERRnone)
    +, qhull_message()
    +, error_stream(0)
    +, output_stream(0)
    +, factor_epsilon(QhullQh::default_factor_epsilon)
    +, use_output_stream(false)
    +{
    +    // NOerrors: TRY_QHULL_ not needed since these routines do not call qh_errexit()
    +    qh_meminit(this, NULL);
    +    qh_initstatistics(this);
    +    qh_initqhull_start2(this, NULL, NULL, qh_FILEstderr);  // Initialize qhT
    +    this->ISqhullQh= True;
    +}//QhullQh
    +
    +QhullQh::
    +~QhullQh()
    +{
    +    checkAndFreeQhullMemory();
    +}//~QhullQh
    +
    +#//!\name Methods
    +
    +//! Check memory for internal consistency
    +//! Free global memory used by qh_initbuild and qh_buildhull
    +//! Zero the qhT data structure, except for memory (qhmemT) and statistics (qhstatT)
    +//! Check and free short memory (e.g., facetT)
    +//! Zero the qhmemT data structure
    +void QhullQh::
    +checkAndFreeQhullMemory()
    +{
    +#ifdef qh_NOmem
    +    qh_freeqhull(this, qh_ALL);
    +#else
    +    qh_memcheck(this);
    +    qh_freeqhull(this, !qh_ALL);
    +    countT curlong;
    +    countT totlong;
    +    qh_memfreeshort(this, &curlong, &totlong);
    +    if (curlong || totlong)
    +        throw QhullError(10026, "Qhull error: qhull did not free %d bytes of long memory (%d pieces).", totlong, curlong);
    +#endif
    +}//checkAndFreeQhullMemory
    +
    +#//!\name Messaging
    +
    +void QhullQh::
    +appendQhullMessage(const string &s)
    +{
    +    if(output_stream && use_output_stream && this->USEstdout){ 
    +        *output_stream << s;
    +    }else if(error_stream){
    +        *error_stream << s;
    +    }else{
    +        qhull_message += s;
    +    }
    +}//appendQhullMessage
    +
    +//! clearQhullMessage does not throw errors (~Qhull)
    +void QhullQh::
    +clearQhullMessage()
    +{
    +    qhull_status= qh_ERRnone;
    +    qhull_message.clear();
    +    RoadError::clearGlobalLog();
    +}//clearQhullMessage
    +
    +//! hasQhullMessage does not throw errors (~Qhull)
    +bool QhullQh::
    +hasQhullMessage() const
    +{
    +    return (!qhull_message.empty() || qhull_status!=qh_ERRnone);
    +    //FIXUP QH11006 -- inconsistent usage with Rbox.  hasRboxMessage just tests rbox_status.  No appendRboxMessage()
    +}
    +
    +void QhullQh::
    +maybeThrowQhullMessage(int exitCode)
    +{
    +    if(!NOerrexit){
    +        if(qhull_message.size()>0){
    +            qhull_message.append("\n");
    +        }
    +        if(exitCode || qhull_status==qh_ERRnone){
    +            qhull_status= 10073;
    +        }else{
    +            qhull_message.append("QH10073: ");
    +        }
    +        qhull_message.append("Cannot call maybeThrowQhullMessage() from QH_TRY_().  Or missing 'qh->NOerrexit=true;' after QH_TRY_(){...}.");
    +    }
    +    if(qhull_status==qh_ERRnone){
    +        qhull_status= exitCode;
    +    }
    +    if(qhull_status!=qh_ERRnone){
    +        QhullError e(qhull_status, qhull_message);
    +        clearQhullMessage();
    +        throw e; // FIXUP QH11007: copy constructor is expensive if logging
    +    }
    +}//maybeThrowQhullMessage
    +
    +void QhullQh::
    +maybeThrowQhullMessage(int exitCode, int noThrow)  throw()
    +{
    +    QHULL_UNUSED(noThrow);
    +
    +    if(qhull_status==qh_ERRnone){
    +        qhull_status= exitCode;
    +    }
    +    if(qhull_status!=qh_ERRnone){
    +        QhullError e(qhull_status, qhull_message);
    +        e.logErrorLastResort();
    +    }
    +}//maybeThrowQhullMessage
    +
    +//! qhullMessage does not throw errors (~Qhull)
    +std::string QhullQh::
    +qhullMessage() const
    +{
    +    if(qhull_message.empty() && qhull_status!=qh_ERRnone){
    +        return "qhull: no message for error.  Check cerr or error stream\n";
    +    }else{
    +        return qhull_message;
    +    }
    +}//qhullMessage
    +
    +int QhullQh::
    +qhullStatus() const
    +{
    +    return qhull_status;
    +}//qhullStatus
    +
    +void QhullQh::
    +setErrorStream(ostream *os)
    +{
    +    error_stream= os;
    +}//setErrorStream
    +
    +//! Updates use_output_stream
    +void QhullQh::
    +setOutputStream(ostream *os)
    +{
    +    output_stream= os;
    +    use_output_stream= (os!=0);
    +}//setOutputStream
    +
    +}//namespace orgQhull
    +
    +/*---------------------------------
    +
    +  qh_fprintf(qhT *qh, fp, msgcode, format, list of args )
    +    replaces qh_fprintf() in userprintf_r.c
    +
    +notes:
    +    only called from libqhull
    +    same as fprintf() and RboxPoints.qh_fprintf_rbox()
    +    fgets() is not trapped like fprintf()
    +    Do not throw errors from here.  Use qh_errexit;
    +*/
    +extern "C"
    +void qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    using namespace orgQhull;
    +
    +    if(!qh->ISqhullQh){
    +        qh_fprintf_stderr(10025, "Qhull error: qh_fprintf called from a Qhull instance without QhullQh defined\n");
    +        qh_exit(10025);
    +    }
    +    QhullQh *qhullQh= static_cast(qh);
    +    va_start(args, fmt);
    +    if(msgcode=MSG_ERROR && msgcodeqhull_statusqhull_status>=MSG_WARNING){
    +                qhullQh->qhull_status= msgcode;
    +            }
    +        }
    +        char newMessage[MSG_MAXLEN];
    +        // RoadError will add the message tag
    +        vsnprintf(newMessage, sizeof(newMessage), fmt, args);
    +        qhullQh->appendQhullMessage(newMessage);
    +        va_end(args);
    +        return;
    +    }
    +    if(qhullQh->output_stream && qhullQh->use_output_stream){
    +        char newMessage[MSG_MAXLEN];
    +        vsnprintf(newMessage, sizeof(newMessage), fmt, args);
    +        *qhullQh->output_stream << newMessage;
    +        va_end(args);
    +        return;
    +    }
    +    // FIXUP QH11008: how do users trap messages and handle input?  A callback?
    +    char newMessage[MSG_MAXLEN];
    +    vsnprintf(newMessage, sizeof(newMessage), fmt, args);
    +    qhullQh->appendQhullMessage(newMessage);
    +    va_end(args);
    +} /* qh_fprintf */
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullQh.h b/xs/src/qhull/src/libqhullcpp/QhullQh.h
    new file mode 100644
    index 0000000000..c3b277ff0f
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullQh.h
    @@ -0,0 +1,110 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullQh.h#2 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLQH_H
    +#define QHULLQH_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +
    +#include 
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  /* interaction between '_setjmp' and C++ object destruction is non-portable */
    +/* setjmp should not be implemented with 'catch' */
    +#endif
    +
    +//! Use QH_TRY_ or QH_TRY_NOTHROW_ to call a libqhull_r routine that may invoke qh_errexit()
    +//! QH_TRY_(qh){...} qh->NOerrexit=true;
    +//! No object creation -- longjmp() skips object destructors
    +//! To test for error when done -- qh->maybeThrowQhullMessage(QH_TRY_status);
    +//! Use the same compiler for QH_TRY_, libqhullcpp, and libqhull_r.  setjmp() is not portable between compilers.
    +
    +#define QH_TRY_ERROR 10071
    +
    +#define QH_TRY_(qh) \
    +    int QH_TRY_status; \
    +    if(qh->NOerrexit){ \
    +        qh->NOerrexit= False; \
    +        QH_TRY_status= setjmp(qh->errexit); \
    +    }else{ \
    +        throw QhullError(QH_TRY_ERROR, "Cannot invoke QH_TRY_() from inside a QH_TRY_.  Or missing 'qh->NOerrexit=true' after previously called QH_TRY_(qh){...}"); \
    +    } \
    +    if(!QH_TRY_status)
    +
    +#define QH_TRY_NO_THROW_(qh) \
    +    int QH_TRY_status; \
    +    if(qh->NOerrexit){ \
    +        qh->NOerrexit= False; \
    +        QH_TRY_status= setjmp(qh->errexit); \
    +    }else{ \
    +        QH_TRY_status= QH_TRY_ERROR; \
    +    } \
    +    if(!QH_TRY_status)
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! QhullQh -- Qhull's global data structure, qhT, as a C++ class
    +    class QhullQh;
    +
    +//! POD type equivalent to qhT.  No virtual members
    +class QhullQh : public qhT {
    +
    +#//!\name Constants
    +
    +#//!\name Fields
    +private:
    +    int                 qhull_status;   //!< qh_ERRnone if valid
    +    std::string         qhull_message;  //!< Returned messages from libqhull_r
    +    std::ostream *      error_stream;   //!< overrides errorMessage, use appendQhullMessage()
    +    std::ostream *      output_stream;  //!< send output to stream
    +    double              factor_epsilon; //!< Factor to increase ANGLEround and DISTround for hyperplane equality
    +    bool                use_output_stream; //!< True if using output_stream
    +
    +    friend void         ::qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +
    +    static const double default_factor_epsilon;  //!< Default factor_epsilon is 1.0, never updated
    +
    +#//!\name Constructors
    +public:
    +                        QhullQh();
    +                        ~QhullQh();
    +private:
    +                        //!disable copy constructor and assignment
    +                        QhullQh(const QhullQh &);
    +    QhullQh &           operator=(const QhullQh &);
    +public:
    +
    +#//!\name GetSet
    +    double              factorEpsilon() const { return factor_epsilon; }
    +    void                setFactorEpsilon(double a) { factor_epsilon= a; }
    +    void                disableOutputStream() { use_output_stream= false; }
    +    void                enableOutputStream() { use_output_stream= true; }
    +
    +#//!\name Messaging
    +    void                appendQhullMessage(const std::string &s);
    +    void                clearQhullMessage();
    +    std::string         qhullMessage() const;
    +    bool                hasOutputStream() const { return use_output_stream; }
    +    bool                hasQhullMessage() const;
    +    void                maybeThrowQhullMessage(int exitCode);
    +    void                maybeThrowQhullMessage(int exitCode, int noThrow) throw();
    +    int                 qhullStatus() const;
    +    void                setErrorStream(std::ostream *os);
    +    void                setOutputStream(std::ostream *os);
    +
    +#//!\name Methods
    +    double              angleEpsilon() const { return this->ANGLEround*factor_epsilon; } //!< Epsilon for hyperplane angle equality
    +    void                checkAndFreeQhullMemory();
    +    double              distanceEpsilon() const { return this->DISTround*factor_epsilon; } //!< Epsilon for distance to hyperplane
    +
    +};//class QhullQh
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLQH_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullRidge.cpp b/xs/src/qhull/src/libqhullcpp/QhullRidge.cpp
    new file mode 100644
    index 0000000000..7a01812805
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullRidge.cpp
    @@ -0,0 +1,124 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullRidge.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullRidge -- Qhull's ridge structure, ridgeT, as a C++ class
    +
    +#include "libqhullcpp/QhullRidge.h"
    +
    +#include "libqhullcpp/QhullSets.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class objects
    +ridgeT QhullRidge::
    +s_empty_ridge= {0,0,0,0,0,
    +                0,0};
    +
    +#//!\name Constructors
    +
    +QhullRidge::QhullRidge(const Qhull &q)
    +: qh_ridge(&s_empty_ridge)
    +, qh_qh(q.qh())
    +{
    +}//Default
    +
    +QhullRidge::QhullRidge(const Qhull &q, ridgeT *r)
    +: qh_ridge(r ? r : &s_empty_ridge)
    +, qh_qh(q.qh())
    +{
    +}//ridgeT
    +
    +#//!\name foreach
    +
    +//! Return True if nextRidge3d
    +//! Simplicial facets may have incomplete ridgeSets
    +//! Does not use qh_errexit()
    +bool QhullRidge::
    +hasNextRidge3d(const QhullFacet &f) const
    +{
    +    if(!qh_qh){
    +        return false;
    +    }
    +    vertexT *v= 0;
    +    // Does not call qh_errexit(), TRY_QHULL_ not needed
    +    ridgeT *ridge= qh_nextridge3d(getRidgeT(), f.getFacetT(), &v);
    +    return (ridge!=0);
    +}//hasNextRidge3d
    +
    +//! Return next ridge and optional vertex for a 3d facet and ridge
    +//! Does not use qh_errexit()
    +QhullRidge QhullRidge::
    +nextRidge3d(const QhullFacet &f, QhullVertex *nextVertex) const
    +{
    +    vertexT *v= 0;
    +    ridgeT *ridge= 0;
    +    if(qh_qh){
    +        // Does not call qh_errexit(), TRY_QHULL_ not needed
    +        ridge= qh_nextridge3d(getRidgeT(), f.getFacetT(), &v);
    +        if(!ridge){
    +            throw QhullError(10030, "Qhull error nextRidge3d:  missing next ridge for facet %d ridge %d.  Does facet contain ridge?", f.id(), id());
    +        }
    +    }
    +    if(nextVertex!=0){
    +        *nextVertex= QhullVertex(qh_qh, v);
    +    }
    +    return QhullRidge(qh_qh, ridge);
    +}//nextRidge3d
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullRidge;
    +using orgQhull::QhullVertex;
    +
    +ostream &
    +operator<<(ostream &os, const QhullRidge &r)
    +{
    +    os << r.print("");
    +    return os;
    +}//<< QhullRidge
    +
    +//! Duplicate of qh_printridge [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullRidge::PrintRidge &pr)
    +{
    +    if(*pr.print_message){
    +        os << pr.print_message << " ";
    +    }else{
    +        os << "     - ";
    +    }
    +    QhullRidge r= *pr.ridge;
    +    os << "r" << r.id();
    +    if(r.getRidgeT()->tested){
    +        os << " tested";
    +    }
    +    if(r.getRidgeT()->nonconvex){
    +        os << " nonconvex";
    +    }
    +    os << endl;
    +    os << r.vertices().print("           vertices:");
    +    if(r.getRidgeT()->top && r.getRidgeT()->bottom){
    +        os << "           between f" << r.topFacet().id() << " and f" << r.bottomFacet().id() << endl;
    +    }else if(r.getRidgeT()->top){
    +        os << "           top f" << r.topFacet().id() << endl;
    +    }else if(r.getRidgeT()->bottom){
    +        os << "           bottom f" << r.bottomFacet().id() << endl;
    +    }
    +
    +    return os;
    +}//<< PrintRidge
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullRidge.h b/xs/src/qhull/src/libqhullcpp/QhullRidge.h
    new file mode 100644
    index 0000000000..924340fb09
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullRidge.h
    @@ -0,0 +1,112 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullRidge.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLRIDGE_H
    +#define QHULLRIDGE_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullSet.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/QhullVertexSet.h"
    +#include "libqhullcpp/QhullFacet.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +    class QhullVertex;
    +    class QhullVertexSet;
    +    class QhullFacet;
    +
    +#//!\name Defined here
    +    //! QhullRidge -- Qhull's ridge structure, ridgeT [libqhull.h], as a C++ class
    +    class QhullRidge;
    +    typedef QhullSet  QhullRidgeSet;
    +    typedef QhullSetIterator  QhullRidgeSetIterator;
    +    // see QhullSets.h for QhullRidgeSet and QhullRidgeSetIterator -- avoids circular references
    +
    +/************************
    +a ridge is hull_dim-1 simplex between two neighboring facets.  If the
    +facets are non-simplicial, there may be more than one ridge between
    +two facets.  E.G. a 4-d hypercube has two triangles between each pair
    +of neighboring facets.
    +
    +topological information:
    +    vertices            a set of vertices
    +    top,bottom          neighboring facets with orientation
    +
    +geometric information:
    +    tested              True if ridge is clearly convex
    +    nonconvex           True if ridge is non-convex
    +*/
    +
    +class QhullRidge {
    +
    +#//!\name Defined here
    +public:
    +    typedef ridgeT *   base_type;  // for QhullRidgeSet
    +
    +#//!\name Fields
    +private:
    +    ridgeT *            qh_ridge;  //!< Corresponding ridgeT, never 0
    +    QhullQh *           qh_qh;     //!< QhullQh/qhT for ridgeT, may be 0
    +
    +#//!\name Class objects
    +    static ridgeT       s_empty_ridge;
    +
    +public:
    +#//!\name Constants
    +
    +#//!\name Constructors
    +                        QhullRidge() : qh_ridge(&s_empty_ridge), qh_qh(0) {}
    +    explicit            QhullRidge(const Qhull &q);
    +                        QhullRidge(const Qhull &q, ridgeT *r);
    +    explicit            QhullRidge(QhullQh *qqh) : qh_ridge(&s_empty_ridge), qh_qh(qqh) {}
    +                        QhullRidge(QhullQh *qqh, ridgeT *r) : qh_ridge(r ? r : &s_empty_ridge), qh_qh(qqh) {}
    +                        // Creates an alias.  Does not copy QhullRidge.  Needed for return by value and parameter passing
    +                        QhullRidge(const QhullRidge &other) : qh_ridge(other.qh_ridge), qh_qh(other.qh_qh) {}
    +                        // Creates an alias.  Does not copy QhullRidge.  Needed for vector
    +    QhullRidge &        operator=(const QhullRidge &other) { qh_ridge= other.qh_ridge; qh_qh= other.qh_qh; return *this; }
    +                        ~QhullRidge() {}
    +
    +#//!\name GetSet
    +    QhullFacet          bottomFacet() const { return QhullFacet(qh_qh, qh_ridge->bottom); }
    +    int                 dimension() const { return ((qh_qh && qh_qh->hull_dim) ? qh_qh->hull_dim-1 : 0); }
    +    ridgeT *            getBaseT() const { return getRidgeT(); } //!< For QhullSet
    +    ridgeT *            getRidgeT() const { return qh_ridge; }
    +    countT              id() const { return qh_ridge->id; }
    +    bool                isValid() const { return (qh_qh && qh_ridge != &s_empty_ridge); }
    +    bool                operator==(const QhullRidge &other) const { return qh_ridge==other.qh_ridge; }
    +    bool                operator!=(const QhullRidge &other) const { return !operator==(other); }
    +    QhullFacet          otherFacet(const QhullFacet &f) const { return QhullFacet(qh_qh, (qh_ridge->top==f.getFacetT() ? qh_ridge->bottom : qh_ridge->top)); }
    +    QhullFacet          topFacet() const { return QhullFacet(qh_qh, qh_ridge->top); }
    +
    +#//!\name foreach
    +    bool                hasNextRidge3d(const QhullFacet &f) const;
    +    QhullRidge          nextRidge3d(const QhullFacet &f) const { return nextRidge3d(f, 0); }
    +    QhullRidge          nextRidge3d(const QhullFacet &f, QhullVertex *nextVertex) const;
    +    QhullVertexSet      vertices() const { return QhullVertexSet(qh_qh, qh_ridge->vertices); }
    +
    +#//!\name IO
    +
    +    struct PrintRidge{
    +        const QhullRidge *ridge;
    +        const char *    print_message;    //!< non-null message
    +                        PrintRidge(const char *message, const QhullRidge &r) : ridge(&r), print_message(message) {}
    +    };//PrintRidge
    +    PrintRidge          print(const char* message) const { return PrintRidge(message, *this); }
    +};//class QhullRidge
    +
    +}//namespace orgQhull
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullRidge &r);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullRidge::PrintRidge &pr);
    +
    +#endif // QHULLRIDGE_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullSet.cpp b/xs/src/qhull/src/libqhullcpp/QhullSet.cpp
    new file mode 100644
    index 0000000000..dfdc3c51f3
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullSet.cpp
    @@ -0,0 +1,62 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullSet.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullSet -- Qhull's set structure, setT, as a C++ class
    +
    +#include "libqhullcpp/QhullSet.h"
    +
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullError.h"
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class objects
    +
    +setT QhullSetBase::
    +s_empty_set;
    +
    +#//!\name Constructors
    +
    +QhullSetBase::
    +QhullSetBase(const Qhull &q, setT *s) 
    +: qh_set(s ? s : &s_empty_set)
    +, qh_qh(q.qh())
    +{
    +}
    +
    +#//!\name Class methods
    +
    +// Same code for qh_setsize [qset_r.c] and QhullSetBase::count [static]
    +countT QhullSetBase::
    +count(const setT *set)
    +{
    +    countT size;
    +    const setelemT *sizep;
    +
    +    if (!set){
    +        return(0);
    +    }
    +    sizep= SETsizeaddr_(set);
    +    if ((size= sizep->i)) {
    +        size--;
    +        if (size > set->maxsize) {
    +            // FIXUP QH11022 How to add additional output to a error? -- qh_setprint(qhmem.ferr, "set: ", set);
    +            throw QhullError(10032, "QhullSet internal error: current set size %d is greater than maximum size %d\n",
    +                size, set->maxsize);
    +        }
    +    }else{
    +        size= set->maxsize;
    +    }
    +    return size;
    +}//count
    +
    +}//namespace orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullSet.h b/xs/src/qhull/src/libqhullcpp/QhullSet.h
    new file mode 100644
    index 0000000000..afb6b51d9f
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullSet.h
    @@ -0,0 +1,462 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullSet.h#6 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QhullSet_H
    +#define QhullSet_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullQh.h"
    +
    +#include   // ptrdiff_t, size_t
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#endif
    +
    +#ifdef QHULL_USES_QT
    + #include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +
    +#//!\name Defined here
    +    class QhullSetBase;  //! Base class for QhullSet
    +    //! QhullSet defined below
    +    //! QhullSetIterator defined below
    +    //! \see QhullPointSet, QhullLinkedList
    +
    +//! QhullSetBase is a wrapper for Qhull's setT of void* pointers
    +//! \see libqhull_r/qset.h
    +class QhullSetBase {
    +
    +private:
    +#//!\name Fields --
    +    setT *              qh_set;
    +    QhullQh *           qh_qh;             //! Provides access to setT memory allocator
    +
    +#//!\name Class objects
    +    static setT         s_empty_set;  //! Used if setT* is NULL
    +
    +public:
    +#//!\name Constructors
    +                        QhullSetBase(const Qhull &q, setT *s);
    +                        QhullSetBase(QhullQh *qqh, setT *s) : qh_set(s ? s : &s_empty_set), qh_qh(qqh) {}
    +                        //! Copy constructor copies the pointer but not the set.  Needed for return by value and parameter passing.
    +                        QhullSetBase(const QhullSetBase &other) : qh_set(other.qh_set), qh_qh(other.qh_qh) {}
    +    QhullSetBase &      operator=(const QhullSetBase &other) { qh_set= other.qh_set; qh_qh= other.qh_qh; return *this; }
    +                        ~QhullSetBase() {}
    +
    +private:
    +                        //!disabled since memory allocation for QhullSet not defined
    +                        QhullSetBase() {}
    +public:
    +
    +#//!\name GetSet
    +    countT              count() const { return QhullSetBase::count(qh_set); }
    +    void                defineAs(setT *s) { qh_set= s ? s : &s_empty_set; } //!< Not type-safe since setT may contain any type
    +    void                forceEmpty() { qh_set= &s_empty_set; }
    +    setT *              getSetT() const { return qh_set; }
    +    bool                isEmpty() const { return SETempty_(qh_set); }
    +    QhullQh *           qh() const { return qh_qh; }
    +    setT **             referenceSetT() { return &qh_set; }
    +    size_t              size() const { return QhullSetBase::count(qh_set); }
    +
    +#//!\name Element
    +protected:
    +    void **             beginPointer() const { return &qh_set->e[0].p; }
    +    void **             elementPointer(countT idx) const { QHULL_ASSERT(idx>=0 && idxmaxsize); return &SETelem_(qh_set, idx); }
    +                        //! Always points to 0
    +    void **             endPointer() const { return qh_setendpointer(qh_set); }
    +
    +#//!\name Class methods
    +public:
    +    static countT       count(const setT *set);
    +    //s may be null
    +    static bool         isEmpty(const setT *s) { return SETempty_(s); }
    +};//QhullSetBase
    +
    +
    +//! QhullSet -- A read-only wrapper to Qhull's collection class, setT.
    +//!  QhullSet is similar to STL's  and Qt's QVector
    +//!  QhullSet is unrelated to STL and Qt's set and map types (e.g., QSet and QMap)
    +//!  T is a Qhull type that defines 'base_type' and getBaseT() (e.g., QhullFacet with base_type 'facetT *'
    +//!  A QhullSet does not own its contents -- erase(), clear(), removeFirst(), removeLast(), pop_back(), pop_front(), fromStdList() not defined
    +//!  QhullSetIterator is faster than STL-style iterator/const_iterator
    +//!  Qhull's FOREACHelement_() [qset_r.h] maybe more efficient than QhullSet.  It uses a NULL terminator instead of an end pointer.  STL requires an end pointer.
    +//!  Derived from QhullLinkedList.h and Qt/core/tools/qlist.h w/o QT_STRICT_ITERATORS
    +template 
    +class QhullSet : public QhullSetBase {
    +
    +private:
    +#//!\name Fields -- see QhullSetBase
    +
    +#//!\name Class objects
    +    static setT         s_empty_set;  //! Workaround for no setT allocator.  Used if setT* is NULL
    +
    +public:
    +#//!\name Defined here
    +    class iterator;
    +    class const_iterator;
    +    typedef typename QhullSet::iterator Iterator;
    +    typedef typename QhullSet::const_iterator ConstIterator;
    +
    +#//!\name Constructors
    +                        QhullSet(const Qhull &q, setT *s) : QhullSetBase(q, s) { }
    +                        QhullSet(QhullQh *qqh, setT *s) : QhullSetBase(qqh, s) { }
    +                        //Conversion from setT* is not type-safe.  Implicit conversion for void* to T
    +                        //Copy constructor copies pointer but not contents.  Needed for return by value.
    +                        QhullSet(const QhullSet &other) : QhullSetBase(other) {}
    +    QhullSet &       operator=(const QhullSet &other) { QhullSetBase::operator=(other); return *this; }
    +                        ~QhullSet() {}
    +
    +private:
    +                        //!Disable default constructor.  See QhullSetBase
    +                        QhullSet();
    +public:
    +
    +#//!\name Conversion
    +
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif
    +#ifdef QHULL_USES_QT
    +    QList toQList() const;
    +#endif
    +
    +#//!\name GetSet -- see QhullSetBase for count(), empty(), isEmpty(), size()
    +    using QhullSetBase::count;
    +    using QhullSetBase::isEmpty;
    +    // operator== defined for QhullSets of the same type
    +    bool                operator==(const QhullSet &other) const { return qh_setequal(getSetT(), other.getSetT()); }
    +    bool                operator!=(const QhullSet &other) const { return !operator==(other); }
    +
    +#//!\name Element access
    +    // Constructs T.  Cannot return reference.
    +    const T             at(countT idx) const { return operator[](idx); }
    +    // Constructs T.  Cannot return reference.
    +    const T             back() const { return last(); }
    +    T                   back() { return last(); }
    +    //! end element is NULL
    +    const typename T::base_type * constData() const { return reinterpret_cast(beginPointer()); }
    +    typename T::base_type *     data() { return reinterpret_cast(beginPointer()); }
    +    const typename T::base_type *data() const { return reinterpret_cast(beginPointer()); }
    +    typename T::base_type *     endData() { return reinterpret_cast(endPointer()); }
    +    const typename T::base_type * endData() const { return reinterpret_cast(endPointer()); }
    +    // Constructs T.  Cannot return reference.
    +    const T             first() const { QHULL_ASSERT(!isEmpty()); return T(qh(), *data()); }
    +    T                   first() { QHULL_ASSERT(!isEmpty()); return T(qh(), *data()); }
    +    // Constructs T.  Cannot return reference.
    +    const T             front() const { return first(); }
    +    T                   front() { return first(); }
    +    // Constructs T.  Cannot return reference.
    +    const T             last() const { QHULL_ASSERT(!isEmpty()); return T(qh(), *(endData()-1)); }
    +    T                   last() { QHULL_ASSERT(!isEmpty()); return T(qh(), *(endData()-1)); }
    +    // mid() not available.  No setT constructor
    +    // Constructs T.  Cannot return reference.
    +    const T             operator[](countT idx) const { const typename T::base_type *p= reinterpret_cast(elementPointer(idx)); QHULL_ASSERT(idx>=0 && p < endData()); return T(qh(), *p); }
    +    T                   operator[](countT idx) { typename T::base_type *p= reinterpret_cast(elementPointer(idx)); QHULL_ASSERT(idx>=0 && p < endData()); return T(qh(), *p); }
    +    const T             second() const { return operator[](1); }
    +    T                   second() { return operator[](1); }
    +    T                   value(countT idx) const;
    +    T                   value(countT idx, const T &defaultValue) const;
    +
    +#//!\name Read-write -- Not available, no setT constructor
    +
    +#//!\name iterator
    +    iterator            begin() { return iterator(qh(), reinterpret_cast(beginPointer())); }
    +    const_iterator      begin() const { return const_iterator(qh(), data()); }
    +    const_iterator      constBegin() const { return const_iterator(qh(), data()); }
    +    const_iterator      constEnd() const { return const_iterator(qh(), endData()); }
    +    iterator            end() { return iterator(qh(), endData()); }
    +    const_iterator      end() const { return const_iterator(qh(), endData()); }
    +
    +#//!\name Search
    +    bool                contains(const T &t) const;
    +    countT              count(const T &t) const;
    +    countT              indexOf(const T &t) const { /* no qh_qh */ return qh_setindex(getSetT(), t.getBaseT()); }
    +    countT              lastIndexOf(const T &t) const;
    +
    +    // before const_iterator for conversion with comparison operators
    +    class iterator {
    +        friend class const_iterator;
    +    private:
    +        typename T::base_type *  i;  // e.g., facetT**, first for debugger
    +        QhullQh *       qh_qh;
    +
    +    public:
    +        typedef ptrdiff_t       difference_type;
    +        typedef std::bidirectional_iterator_tag  iterator_category;
    +        typedef T               value_type;
    +
    +                        iterator(QhullQh *qqh, typename T::base_type *p) : i(p), qh_qh(qqh) {}
    +                        iterator(const iterator &o) : i(o.i), qh_qh(o.qh_qh) {}
    +        iterator &      operator=(const iterator &o) { i= o.i; qh_qh= o.qh_qh; return *this; }
    +
    +        // Constructs T.  Cannot return reference.  
    +        T               operator*() const { return T(qh_qh, *i); }
    +        //operator->() n/a, value-type
    +        // Constructs T.  Cannot return reference.  
    +        T               operator[](countT idx) const { return T(qh_qh, *(i+idx)); } //!< No error checking
    +        bool            operator==(const iterator &o) const { return i == o.i; }
    +        bool            operator!=(const iterator &o) const { return !operator==(o); }
    +        bool            operator==(const const_iterator &o) const { return (i==reinterpret_cast(o).i); }
    +        bool            operator!=(const const_iterator &o) const { return !operator==(o); }
    +
    +        //! Assumes same point set
    +        countT          operator-(const iterator &o) const { return (countT)(i-o.i); } //WARN64
    +        bool            operator>(const iterator &o) const { return i>o.i; }
    +        bool            operator<=(const iterator &o) const { return !operator>(o); }
    +        bool            operator<(const iterator &o) const { return i=(const iterator &o) const { return !operator<(o); }
    +        bool            operator>(const const_iterator &o) const { return (i > reinterpret_cast(o).i); }
    +        bool            operator<=(const const_iterator &o) const { return !operator>(o); }
    +        bool            operator<(const const_iterator &o) const { return (i < reinterpret_cast(o).i); }
    +        bool            operator>=(const const_iterator &o) const { return !operator<(o); }
    +
    +        //! No error checking
    +        iterator &      operator++() { ++i; return *this; }
    +        iterator        operator++(int) { iterator o= *this; ++i; return o; }
    +        iterator &      operator--() { --i; return *this; }
    +        iterator        operator--(int) { iterator o= *this; --i; return o; }
    +        iterator        operator+(countT j) const { return iterator(qh_qh, i+j); }
    +        iterator        operator-(countT j) const { return operator+(-j); }
    +        iterator &      operator+=(countT j) { i += j; return *this; }
    +        iterator &      operator-=(countT j) { i -= j; return *this; }
    +    };//QhullPointSet::iterator
    +
    +    class const_iterator {
    +    private:
    +        const typename T::base_type *  i;  // e.g., const facetT**, first for debugger
    +        QhullQh *       qh_qh;
    +
    +    public:
    +        typedef ptrdiff_t       difference_type;
    +        typedef std::random_access_iterator_tag  iterator_category;
    +        typedef T               value_type;
    +
    +                        const_iterator(QhullQh *qqh, const typename T::base_type * p) : i(p), qh_qh(qqh) {}
    +                        const_iterator(const const_iterator &o) : i(o.i), qh_qh(o.qh_qh) {}
    +                        const_iterator(const iterator &o) : i(o.i), qh_qh(o.qh_qh) {}
    +        const_iterator &operator=(const const_iterator &o) { i= o.i; qh_qh= o.qh_qh; return *this; }
    +
    +        // Constructs T.  Cannot return reference.  Retaining 'const T' return type for consistency with QList/QVector
    +        const T         operator*() const { return T(qh_qh, *i); }
    +        const T         operator[](countT idx) const { return T(qh_qh, *(i+idx)); }  //!< No error checking
    +        //operator->() n/a, value-type
    +        bool            operator==(const const_iterator &o) const { return i == o.i; }
    +        bool            operator!=(const const_iterator &o) const { return !operator==(o); }
    +
    +        //! Assumes same point set
    +        countT          operator-(const const_iterator &o) { return (countT)(i-o.i); } //WARN64
    +        bool            operator>(const const_iterator &o) const { return i>o.i; }
    +        bool            operator<=(const const_iterator &o) const { return !operator>(o); }
    +        bool            operator<(const const_iterator &o) const { return i=(const const_iterator &o) const { return !operator<(o); }
    +
    +        //!< No error checking
    +        const_iterator &operator++() { ++i; return *this; }
    +        const_iterator  operator++(int) { const_iterator o= *this; ++i; return o; }
    +        const_iterator &operator--() { --i; return *this; }
    +        const_iterator  operator--(int) { const_iterator o= *this; --i; return o; }
    +        const_iterator  operator+(int j) const { return const_iterator(qh_qh, i+j); }
    +        const_iterator  operator-(int j) const { return operator+(-j); }
    +        const_iterator &operator+=(int j) { i += j; return *this; }
    +        const_iterator &operator-=(int j) { i -= j; return *this; }
    +    };//QhullPointSet::const_iterator
    +
    +};//class QhullSet
    +
    +
    +//! Faster then interator/const_iterator due to T::base_type
    +template 
    +class QhullSetIterator {
    +
    +#//!\name Subtypes
    +    typedef typename QhullSet::const_iterator const_iterator;
    +
    +private:
    +#//!\name Fields
    +    const typename T::base_type *  i;  // e.g., facetT**, first for debugger
    +    const typename T::base_type *  begin_i;  // must be initialized after i
    +    const typename T::base_type *  end_i;
    +    QhullQh *                qh_qh;
    +
    +public:
    +#//!\name Constructors
    +                        QhullSetIterator(const QhullSet &s) : i(s.data()), begin_i(i), end_i(s.endData()), qh_qh(s.qh()) {}
    +                        QhullSetIterator(const QhullSetIterator &o) : i(o.i), begin_i(o.begin_i), end_i(o.end_i), qh_qh(o.qh_qh) {}
    +    QhullSetIterator &operator=(const QhullSetIterator &o) { i= o.i; begin_i= o.begin_i; end_i= o.end_i; qh_qh= o.qh_qh; return *this; }
    +
    +#//!\name ReadOnly
    +    countT              countRemaining() { return (countT)(end_i-i); } // WARN64
    +
    +#//!\name Search
    +    bool                findNext(const T &t);
    +    bool                findPrevious(const T &t);
    +
    +#//!\name Foreach
    +    bool                hasNext() const { return i != end_i; }
    +    bool                hasPrevious() const { return i != begin_i; }
    +    T                   next() { return T(qh_qh, *i++); }
    +    T                   peekNext() const { return T(qh_qh, *i); }
    +    T                   peekPrevious() const { const typename T::base_type *p = i; return T(qh_qh, *--p); }
    +    T                   previous() { return T(qh_qh, *--i); }
    +    void                toBack() { i = end_i; }
    +    void                toFront() { i = begin_i; }
    +};//class QhullSetIterator
    +
    +#//!\name == Definitions =========================================
    +
    +#//!\name Conversions
    +
    +// See qt-qhull.cpp for QList conversion
    +
    +#ifndef QHULL_NO_STL
    +template 
    +std::vector QhullSet::
    +toStdVector() const
    +{
    +	typename QhullSet::const_iterator i = begin();
    +	typename QhullSet::const_iterator e = end();
    +    std::vector vs;
    +    while(i!=e){
    +        vs.push_back(*i++);
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#ifdef QHULL_USES_QT
    +template 
    +QList QhullSet::
    +toQList() const
    +{
    +    QhullSet::const_iterator i= begin();
    +    QhullSet::const_iterator e= end();
    +    QList vs;
    +    while(i!=e){
    +        vs.append(*i++);
    +    }
    +    return vs;
    +}//toQList
    +#endif
    +
    +#//!\name Element
    +
    +template 
    +T QhullSet::
    +value(countT idx) const
    +{
    +    // Avoid call to qh_setsize() and assert in elementPointer()
    +    const typename T::base_type *p= reinterpret_cast(&SETelem_(getSetT(), idx));
    +    return (idx>=0 && p
    +T QhullSet::
    +value(countT idx, const T &defaultValue) const
    +{
    +    // Avoid call to qh_setsize() and assert in elementPointer()
    +    const typename T::base_type *p= reinterpret_cast(&SETelem_(getSetT(), idx));
    +    return (idx>=0 && p
    +bool QhullSet::
    +contains(const T &t) const
    +{
    +    setT *s= getSetT();
    +    void *p= t.getBaseT();  // contains() is not inline for better error reporting
    +    int result= qh_setin(s, p);
    +    return result!=0;
    +}//contains
    +
    +template 
    +countT QhullSet::
    +count(const T &t) const
    +{
    +    countT n= 0;
    +    const typename T::base_type *i= data();
    +    const typename T::base_type *e= endData();
    +    typename T::base_type p= t.getBaseT();
    +    while(i
    +countT QhullSet::
    +lastIndexOf(const T &t) const
    +{
    +    const typename T::base_type *b= data();
    +    const typename T::base_type *i= endData();
    +    typename T::base_type p= t.getBaseT();
    +    while(--i>=b){
    +        if(*i==p){
    +            break;
    +        }
    +    }
    +    return (countT)(i-b); // WARN64
    +}//lastIndexOf
    +
    +#//!\name QhullSetIterator
    +
    +template 
    +bool QhullSetIterator::
    +findNext(const T &t)
    +{
    +    typename T::base_type p= t.getBaseT();
    +    while(i!=end_i){
    +        if(*(++i)==p){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findNext
    +
    +template 
    +bool QhullSetIterator::
    +findPrevious(const T &t)
    +{
    +    typename T::base_type p= t.getBaseT();
    +    while(i!=begin_i){
    +        if(*(--i)==p){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findPrevious
    +
    +}//namespace orgQhull
    +
    +
    +#//!\name == Global namespace =========================================
    +
    +template 
    +std::ostream &
    +operator<<(std::ostream &os, const orgQhull::QhullSet &qs)
    +{
    +    const typename T::base_type *i= qs.data();
    +    const typename T::base_type *e= qs.endData();
    +    while(i!=e){
    +        os << T(qs.qh(), *i++);
    +    }
    +    return os;
    +}//operator<<
    +
    +#endif // QhullSet_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullSets.h b/xs/src/qhull/src/libqhullcpp/QhullSets.h
    new file mode 100644
    index 0000000000..d0f200cbcf
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullSets.h
    @@ -0,0 +1,27 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullSets.h#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLSETS_H
    +#define QHULLSETS_H
    +
    +#include "libqhullcpp/QhullSet.h"
    +
    +namespace orgQhull {
    +
    +    //See: QhullFacetSet.h
    +    //See: QhullPointSet.h
    +    //See: QhullVertexSet.h
    +
    +    // Avoid circular references between QhullFacet, QhullRidge, and QhullVertex
    +    class QhullRidge;
    +    typedef QhullSet  QhullRidgeSet;
    +    typedef QhullSetIterator  QhullRidgeSetIterator;
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLSETS_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullStat.cpp b/xs/src/qhull/src/libqhullcpp/QhullStat.cpp
    new file mode 100644
    index 0000000000..c4fe6c4918
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullStat.cpp
    @@ -0,0 +1,42 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullStat.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullStat -- Qhull's global data structure, statT, as a C++ class
    +
    +#include "libqhullcpp/QhullStat.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +
    +#include 
    +#include 
    +
    +using std::cerr;
    +using std::string;
    +using std::vector;
    +using std::ostream;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructor, destructor, etc.
    +
    +//! If qh_QHpointer==0, invoke with placement new on qh_stat;
    +QhullStat::
    +QhullStat()
    +{
    +}//QhullStat
    +
    +QhullStat::
    +~QhullStat()
    +{
    +}//~QhullStat
    +
    +}//namespace orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullStat.h b/xs/src/qhull/src/libqhullcpp/QhullStat.h
    new file mode 100644
    index 0000000000..54bde8fc79
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullStat.h
    @@ -0,0 +1,49 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullStat.h#2 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLSTAT_H
    +#define QHULLSTAT_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name defined here
    +    //! QhullStat -- Qhull's statistics, qhstatT, as a C++ class
    +    //! Statistics defined with zzdef_() control Qhull's behavior, summarize its result, and report precision problems.
    +    class QhullStat;
    +
    +class QhullStat : public qhstatT {
    +
    +private:
    +#//!\name Fields (empty) -- POD type equivalent to qhstatT.  No data or virtual members
    +
    +public:
    +#//!\name Constants
    +
    +#//!\name class methods
    +
    +#//!\name constructor, assignment, destructor, invariant
    +                        QhullStat();
    +                        ~QhullStat();
    +
    +private:
    +    //!disable copy constructor and assignment
    +                        QhullStat(const QhullStat &);
    +    QhullStat &         operator=(const QhullStat &);
    +public:
    +
    +#//!\name Access
    +};//class QhullStat
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLSTAT_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullVertex.cpp b/xs/src/qhull/src/libqhullcpp/QhullVertex.cpp
    new file mode 100644
    index 0000000000..fd7aef0893
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullVertex.cpp
    @@ -0,0 +1,112 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullVertex.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullVertex -- Qhull's vertex structure, vertexT, as a C++ class
    +
    +#include "libqhullcpp/QhullVertex.h"
    +
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullFacet.h"
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class objects
    +vertexT QhullVertex::
    +s_empty_vertex= {0,0,0,0,0,
    +                 0,0,0,0,0,
    +                 0};
    +
    +#//!\name Constructors
    +
    +QhullVertex::QhullVertex(const Qhull &q)
    +: qh_vertex(&s_empty_vertex)
    +, qh_qh(q.qh())
    +{
    +}//Default
    +
    +QhullVertex::QhullVertex(const Qhull &q, vertexT *v)
    +: qh_vertex(v ? v : &s_empty_vertex)
    +, qh_qh(q.qh())
    +{
    +}//vertexT
    +
    +#//!\name foreach
    +
    +//! Return neighboring facets for a vertex
    +//! If neither merging nor Voronoi diagram, requires Qhull::defineVertexNeighborFacets() beforehand.
    +QhullFacetSet QhullVertex::
    +neighborFacets() const
    +{
    +    if(!neighborFacetsDefined()){
    +        throw QhullError(10034, "Qhull error: neighboring facets of vertex %d not defined.  Please call Qhull::defineVertexNeighborFacets() beforehand.", id());
    +    }
    +    return QhullFacetSet(qh_qh, qh_vertex->neighbors);
    +}//neighborFacets
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using std::string;
    +using std::vector;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetSet;
    +using orgQhull::QhullFacetSetIterator;
    +using orgQhull::QhullVertex;
    +
    +//! Duplicate of qh_printvertex [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullVertex::PrintVertex &pr)
    +{
    +    QhullVertex v= *pr.vertex;
    +    QhullPoint p= v.point();
    +    if(*pr.print_message){
    +        os << pr.print_message << " ";
    +    }else{
    +        os << "- ";
    +    }
    +    os << "p" << p.id() << " (v" << v.id() << "): ";
    +    const realT *c= p.coordinates();
    +    for(int k= p.dimension(); k--; ){
    +        os << " " << *c++; // FIXUP QH11010 %5.2g
    +    }
    +    if(v.getVertexT()->deleted){
    +        os << " deleted";
    +    }
    +    if(v.getVertexT()->delridge){
    +        os << " ridgedeleted";
    +    }
    +    os << endl;
    +    if(v.neighborFacetsDefined()){
    +        QhullFacetSetIterator i= v.neighborFacets();
    +        if(i.hasNext()){
    +            os << " neighborFacets:";
    +            countT count= 0;
    +            while(i.hasNext()){
    +                if(++count % 100 == 0){
    +                    os << endl << "     ";
    +                }
    +                QhullFacet f= i.next();
    +                os << " f" << f.id();
    +            }
    +            os << endl;
    +        }
    +    }
    +    return os;
    +}//<< PrintVertex
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullVertex.h b/xs/src/qhull/src/libqhullcpp/QhullVertex.h
    new file mode 100644
    index 0000000000..0137766db5
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullVertex.h
    @@ -0,0 +1,104 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullVertex.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLVERTEX_H
    +#define QHULLVERTEX_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullLinkedList.h"
    +#include "libqhullcpp/QhullSet.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class QhullFacetSet;
    +
    +#//!\name Defined here
    +    //! QhullVertex -- Qhull's vertex structure, vertexT [libqhull_r.h], as a C++ class
    +    class QhullVertex;
    +    typedef QhullLinkedList QhullVertexList;
    +    typedef QhullLinkedListIterator QhullVertexListIterator;
    +
    +
    +/*********************
    +  topological information:
    +    next,previous       doubly-linked list of all vertices
    +    neighborFacets           set of adjacent facets (only if qh.VERTEXneighbors)
    +
    +  geometric information:
    +    point               array of DIM coordinates
    +*/
    +
    +class QhullVertex {
    +
    +#//!\name Defined here
    +public:
    +    typedef vertexT *   base_type;  // for QhullVertexSet
    +
    +private:
    +#//!\name Fields
    +    vertexT *           qh_vertex;  //!< Corresponding vertexT, never 0
    +    QhullQh *           qh_qh;      //!< QhullQh/qhT for vertexT, may be 0
    +
    +#//!\name Class objects
    +    static vertexT      s_empty_vertex;  // needed for shallow copy
    +
    +public:
    +#//!\name Constants
    +
    +#//!\name Constructors
    +                        QhullVertex() : qh_vertex(&s_empty_vertex), qh_qh(0) {}
    +    explicit            QhullVertex(const Qhull &q);
    +                        QhullVertex(const Qhull &q, vertexT *v);
    +    explicit            QhullVertex(QhullQh *qqh) : qh_vertex(&s_empty_vertex), qh_qh(qqh) {}
    +                        QhullVertex(QhullQh *qqh, vertexT *v) : qh_vertex(v ? v : &s_empty_vertex), qh_qh(qqh) {}
    +                        // Creates an alias.  Does not copy QhullVertex.  Needed for return by value and parameter passing
    +                        QhullVertex(const QhullVertex &other) : qh_vertex(other.qh_vertex), qh_qh(other.qh_qh) {}
    +                        // Creates an alias.  Does not copy QhullVertex.  Needed for vector
    +    QhullVertex &       operator=(const QhullVertex &other) { qh_vertex= other.qh_vertex; qh_qh= other.qh_qh; return *this; }
    +                        ~QhullVertex() {}
    +
    +#//!\name GetSet
    +    int                 dimension() const { return (qh_qh ? qh_qh->hull_dim : 0); }
    +    vertexT *           getBaseT() const { return getVertexT(); } //!< For QhullSet
    +    vertexT *           getVertexT() const { return qh_vertex; }
    +    countT              id() const { return qh_vertex->id; }
    +    bool                isValid() const { return (qh_qh && qh_vertex != &s_empty_vertex); }
    +                        //! True if defineVertexNeighborFacets() already called.  Auotomatically set for facet merging, Voronoi diagrams
    +    bool                neighborFacetsDefined() const { return qh_vertex->neighbors != 0; }
    +    QhullVertex         next() const { return QhullVertex(qh_qh, qh_vertex->next); }
    +    bool                operator==(const QhullVertex &other) const { return qh_vertex==other.qh_vertex; }
    +    bool                operator!=(const QhullVertex &other) const { return !operator==(other); }
    +    QhullPoint          point() const { return QhullPoint(qh_qh, qh_vertex->point); }
    +    QhullVertex         previous() const { return QhullVertex(qh_qh, qh_vertex->previous); }
    +    QhullQh *           qh() const { return qh_qh; }
    +
    +#//!\name foreach
    +    //See also QhullVertexList
    +    QhullFacetSet       neighborFacets() const;
    +
    +#//!\name IO
    +    struct PrintVertex{
    +        const QhullVertex *vertex;
    +        const char *    print_message;    //!< non-null message
    +                        PrintVertex(const char *message, const QhullVertex &v) : vertex(&v), print_message(message) {}
    +    };//PrintVertex
    +    PrintVertex         print(const char *message) const { return PrintVertex(message, *this); }
    +};//class QhullVertex
    +
    +}//namespace orgQhull
    +
    +#//!\name GLobal
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertex::PrintVertex &pr);
    +inline std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertex &v) { os << v.print(""); return os; }
    +
    +#endif // QHULLVERTEX_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullVertexSet.cpp b/xs/src/qhull/src/libqhullcpp/QhullVertexSet.cpp
    new file mode 100644
    index 0000000000..00ba62d196
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullVertexSet.cpp
    @@ -0,0 +1,161 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullVertexSet.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullVertexSet -- Qhull's linked Vertexs, as a C++ class
    +
    +#include "libqhullcpp/QhullVertexSet.h"
    +
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::string;
    +using std::vector;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  /* interaction between '_setjmp' and C++ object destruction is non-portable */
    +                                    /* setjmp should not be implemented with 'catch' */
    +#endif
    +
    +namespace orgQhull {
    +
    +QhullVertexSet::
    +QhullVertexSet(const Qhull &q, facetT *facetlist, setT *facetset, bool allfacets)
    +: QhullSet(q.qh(), 0)
    +, qhsettemp_defined(false)
    +{
    +    QH_TRY_(q.qh()){ // no object creation -- destructors skipped on longjmp()
    +        setT *vertices= qh_facetvertices(q.qh(), facetlist, facetset, allfacets);
    +        defineAs(vertices);
    +        qhsettemp_defined= true;
    +    }
    +    q.qh()->NOerrexit= true;
    +    q.qh()->maybeThrowQhullMessage(QH_TRY_status);
    +}//QhullVertexSet facetlist facetset
    +
    +//! Return tempory QhullVertexSet of vertices for a list and/or a set of facets
    +//! Sets qhsettemp_defined (disallows copy constructor and assignment to prevent double-free)
    +QhullVertexSet::
    +QhullVertexSet(QhullQh *qqh, facetT *facetlist, setT *facetset, bool allfacets)
    +: QhullSet(qqh, 0)
    +, qhsettemp_defined(false)
    +{
    +    QH_TRY_(qh()){ // no object creation -- destructors skipped on longjmp()
    +        setT *vertices= qh_facetvertices(qh(), facetlist, facetset, allfacets);
    +        defineAs(vertices);
    +        qhsettemp_defined= true;
    +    }
    +    qh()->NOerrexit= true;
    +    qh()->maybeThrowQhullMessage(QH_TRY_status);
    +}//QhullVertexSet facetlist facetset
    +
    +//! Copy constructor for argument passing and returning a result
    +//! Only copies a pointer to the set.
    +//! Throws an error if qhsettemp_defined, otherwise have a double-free
    +//!\todo Convert QhullVertexSet to a shared pointer with reference counting
    +QhullVertexSet::
    +QhullVertexSet(const QhullVertexSet &other)
    +: QhullSet(other)
    +, qhsettemp_defined(false)
    +{
    +    if(other.qhsettemp_defined){
    +        throw QhullError(10077, "QhullVertexSet: Cannot use copy constructor since qhsettemp_defined (e.g., QhullVertexSet for a set and/or list of QhFacet).  Contains %d vertices", other.count());
    +    }
    +}//copy constructor
    +
    +//! Copy assignment only copies a pointer to the set.
    +//! Throws an error if qhsettemp_defined, otherwise have a double-free
    +QhullVertexSet & QhullVertexSet::
    +operator=(const QhullVertexSet &other)
    +{
    +    QhullSet::operator=(other);
    +    qhsettemp_defined= false;
    +    if(other.qhsettemp_defined){
    +        throw QhullError(10078, "QhullVertexSet: Cannot use copy constructor since qhsettemp_defined (e.g., QhullVertexSet for a set and/or list of QhFacet).  Contains %d vertices", other.count());
    +    }
    +    return *this;
    +}//assignment
    +
    +void QhullVertexSet::
    +freeQhSetTemp()
    +{
    +    if(qhsettemp_defined){
    +        qhsettemp_defined= false;
    +        QH_TRY_(qh()){ // no object creation -- destructors skipped on longjmp()
    +            qh_settempfree(qh(), referenceSetT()); // errors if not top of tempstack or if qhmem corrupted
    +        }
    +        qh()->NOerrexit= true;
    +        qh()->maybeThrowQhullMessage(QH_TRY_status, QhullError::NOthrow);
    +    }
    +}//freeQhSetTemp
    +
    +QhullVertexSet::
    +~QhullVertexSet()
    +{
    +    freeQhSetTemp();
    +}//~QhullVertexSet
    +
    +//FIXUP -- Move conditional, QhullVertexSet code to QhullVertexSet.cpp
    +#ifndef QHULL_NO_STL
    +std::vector QhullVertexSet::
    +toStdVector() const
    +{
    +    QhullSetIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        QhullVertex v= i.next();
    +        vs.push_back(v);
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullVertex;
    +using orgQhull::QhullVertexSet;
    +using orgQhull::QhullVertexSetIterator;
    +
    +//! Print Vertex identifiers to stream.  Space prefix.  From qh_printVertexheader [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullVertexSet::PrintIdentifiers &pr)
    +{
    +    os << pr.print_message;
    +    for(QhullVertexSet::const_iterator i= pr.vertex_set->begin(); i!=pr.vertex_set->end(); ++i){
    +        const QhullVertex v= *i;
    +        os << " v" << v.id();
    +    }
    +    os << endl;
    +    return os;
    +}//<
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +
    +#//!\name Defined here
    +    //! QhullVertexSet -- a set of Qhull Vertices, as a C++ class.
    +    //! See Qhull
    +    class QhullVertexSet;
    +    typedef QhullSetIterator QhullVertexSetIterator;
    +
    +class QhullVertexSet : public QhullSet {
    +
    +private:
    +#//!\name Fields
    +    bool                qhsettemp_defined;  //! Set was allocated with qh_settemp()
    +
    +public:
    +#//!\name Constructor
    +                        QhullVertexSet(const Qhull &q, setT *s) : QhullSet(q, s), qhsettemp_defined(false) {}
    +                        QhullVertexSet(const Qhull &q, facetT *facetlist, setT *facetset, bool allfacets);
    +                        //Conversion from setT* is not type-safe.  Implicit conversion for void* to T
    +                        QhullVertexSet(QhullQh *qqh, setT *s) : QhullSet(qqh, s), qhsettemp_defined(false) {}
    +                        QhullVertexSet(QhullQh *qqh, facetT *facetlist, setT *facetset, bool allfacets);
    +                        //Copy constructor and assignment copies pointer but not contents.  Throws error if qhsettemp_defined.  Needed for return by value.
    +                        QhullVertexSet(const QhullVertexSet &other);
    +    QhullVertexSet &    operator=(const QhullVertexSet &other);
    +                        ~QhullVertexSet();
    +
    +private:                //!Default constructor disabled.  Will implement allocation later
    +                        QhullVertexSet();
    +public:
    +
    +#//!\name Destructor
    +    void                freeQhSetTemp();
    +
    +#//!\name Conversion
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList   toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name IO
    +    struct PrintVertexSet{
    +        const QhullVertexSet *vertex_set;
    +        const char *    print_message;     //!< non-null message
    +                        
    +                        PrintVertexSet(const char *message, const QhullVertexSet *s) : vertex_set(s), print_message(message) {}
    +    };//PrintVertexSet
    +    const PrintVertexSet print(const char *message) const { return PrintVertexSet(message, this); }
    +
    +    struct PrintIdentifiers{
    +        const QhullVertexSet *vertex_set;
    +        const char *    print_message;    //!< non-null message
    +                        PrintIdentifiers(const char *message, const QhullVertexSet *s) : vertex_set(s), print_message(message) {}
    +    };//PrintIdentifiers
    +    PrintIdentifiers    printIdentifiers(const char *message) const { return PrintIdentifiers(message, this); }
    +
    +};//class QhullVertexSet
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertexSet::PrintVertexSet &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertexSet::PrintIdentifiers &p);
    +inline std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertexSet &vs) { os << vs.print(""); return os; }
    +
    +#endif // QHULLVERTEXSET_H
    diff --git a/xs/src/qhull/src/libqhullcpp/RboxPoints.cpp b/xs/src/qhull/src/libqhullcpp/RboxPoints.cpp
    new file mode 100644
    index 0000000000..d7acd9fce0
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RboxPoints.cpp
    @@ -0,0 +1,224 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RboxPoints.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/RboxPoints.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +
    +#include 
    +
    +using std::cerr;
    +using std::endl;
    +using std::istream;
    +using std::ostream;
    +using std::ostringstream;
    +using std::string;
    +using std::vector;
    +using std::ws;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//! RboxPoints -- generate random PointCoordinates for qhull (rbox)
    +
    +
    +#//!\name Constructors
    +RboxPoints::
    +RboxPoints()
    +: PointCoordinates("rbox")
    +, rbox_new_count(0)
    +, rbox_status(qh_ERRnone)
    +, rbox_message()
    +{
    +    allocateQhullQh();
    +}
    +
    +//! Allocate and generate points according to rboxCommand
    +//! For rbox commands, see http://www.qhull.org/html/rbox.htm or html/rbox.htm
    +//! Same as appendPoints()
    +RboxPoints::
    +RboxPoints(const char *rboxCommand)
    +: PointCoordinates("rbox")
    +, rbox_new_count(0)
    +, rbox_status(qh_ERRnone)
    +, rbox_message()
    +{
    +    allocateQhullQh();
    +    // rbox arguments added to comment() via qh_rboxpoints > qh_fprintf_rbox
    +    appendPoints(rboxCommand);
    +}
    +
    +RboxPoints::
    +~RboxPoints()
    +{
    +    delete qh();
    +    resetQhullQh(0);
    +}
    +
    +// RboxPoints and qh_rboxpoints has several fields in qhT (rbox_errexit..cpp_object)
    +// It shares last_random with qh_rand and qh_srand
    +// The other fields are unused
    +void RboxPoints::
    +allocateQhullQh()
    +{
    +    QHULL_LIB_CHECK /* Check for compatible library */
    +    resetQhullQh(new QhullQh);
    +}//allocateQhullQh
    +
    +#//!\name Messaging
    +
    +void RboxPoints::
    +clearRboxMessage()
    +{
    +    rbox_status= qh_ERRnone;
    +    rbox_message.clear();
    +}//clearRboxMessage
    +
    +std::string RboxPoints::
    +rboxMessage() const
    +{
    +    if(rbox_status!=qh_ERRnone){
    +        return rbox_message;
    +    }
    +    if(isEmpty()){
    +        return "rbox warning: no points generated\n";
    +    }
    +    return "rbox: OK\n";
    +}//rboxMessage
    +
    +int RboxPoints::
    +rboxStatus() const
    +{
    +    return rbox_status;
    +}
    +
    +bool RboxPoints::
    +hasRboxMessage() const
    +{
    +    return (rbox_status!=qh_ERRnone);
    +}
    +
    +#//!\name Methods
    +
    +//! Appends points as defined by rboxCommand
    +//! Appends rboxCommand to comment
    +//! For rbox commands, see http://www.qhull.org/html/rbox.htm or html/rbox.htm
    +void RboxPoints::
    +appendPoints(const char *rboxCommand)
    +{
    +    string s("rbox ");
    +    s += rboxCommand;
    +    char *command= const_cast(s.c_str());
    +    if(qh()->cpp_object){
    +        throw QhullError(10001, "Qhull error: conflicting user of cpp_object for RboxPoints::appendPoints() or corrupted qh_qh");
    +    }
    +    if(extraCoordinatesCount()!=0){
    +        throw QhullError(10067, "Qhull error: Extra coordinates (%d) prior to calling RboxPoints::appendPoints.  Was %s", extraCoordinatesCount(), 0, 0.0, comment().c_str());
    +    }
    +    countT previousCount= count();
    +    qh()->cpp_object= this;           // for qh_fprintf_rbox()
    +    int status= qh_rboxpoints(qh(), command);
    +    qh()->cpp_object= 0;
    +    if(rbox_status==qh_ERRnone){
    +        rbox_status= status;
    +    }
    +    if(rbox_status!=qh_ERRnone){
    +        throw QhullError(rbox_status, rbox_message);
    +    }
    +    if(extraCoordinatesCount()!=0){
    +        throw QhullError(10002, "Qhull error: extra coordinates (%d) for PointCoordinates (%x)", extraCoordinatesCount(), 0, 0.0, coordinates());
    +    }
    +    if(previousCount+newCount()!=count()){
    +        throw QhullError(10068, "Qhull error: rbox specified %d points but got %d points for command '%s'", newCount(), count()-previousCount, 0.0, comment().c_str());
    +    }
    +}//appendPoints
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +/*---------------------------------
    +
    +  qh_fprintf_rbox(qh, fp, msgcode, format, list of args )
    +    fp is ignored (replaces qh_fprintf_rbox() in userprintf_rbox.c)
    +    cpp_object == RboxPoints
    +
    +notes:
    +    only called from qh_rboxpoints()
    +    same as fprintf() and Qhull.qh_fprintf()
    +    fgets() is not trapped like fprintf()
    +    Do not throw errors from here.  Use qh_errexit_rbox;
    +    A similar technique can be used for qh_fprintf to capture all of its output
    +*/
    +extern "C"
    +void qh_fprintf_rbox(qhT *qh, FILE*, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    using namespace orgQhull;
    +
    +    if(!qh->cpp_object){
    +        qh_errexit_rbox(qh, 10072);
    +    }
    +    RboxPoints *out= reinterpret_cast(qh->cpp_object);
    +    va_start(args, fmt);
    +    if(msgcoderbox_message += newMessage;
    +        if(out->rbox_statusrbox_status>=MSG_STDERR){
    +            out->rbox_status= msgcode;
    +        }
    +        va_end(args);
    +        return;
    +    }
    +    switch(msgcode){
    +    case 9391:
    +    case 9392:
    +        out->rbox_message += "RboxPoints error: options 'h', 'n' not supported.\n";
    +        qh_errexit_rbox(qh, 10010);
    +        /* never returns */
    +    case 9393:  // FIXUP QH11026 countT vs. int
    +        {
    +            int dimension= va_arg(args, int);
    +            string command(va_arg(args, char*));
    +            countT count= va_arg(args, countT);
    +            out->setDimension(dimension);
    +            out->appendComment(" \"");
    +            out->appendComment(command.substr(command.find(' ')+1));
    +            out->appendComment("\"");
    +            out->setNewCount(count);
    +            out->reservePoints();
    +        }
    +        break;
    +    case 9407:
    +        *out << va_arg(args, int);
    +        // fall through
    +    case 9405:
    +        *out << va_arg(args, int);
    +        // fall through
    +    case 9403:
    +        *out << va_arg(args, int);
    +        break;
    +    case 9408:
    +        *out << va_arg(args, double);
    +        // fall through
    +    case 9406:
    +        *out << va_arg(args, double);
    +        // fall through
    +    case 9404:
    +        *out << va_arg(args, double);
    +        break;
    +    }
    +    va_end(args);
    +} /* qh_fprintf_rbox */
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/RboxPoints.h b/xs/src/qhull/src/libqhullcpp/RboxPoints.h
    new file mode 100644
    index 0000000000..e8ec72b14a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RboxPoints.h
    @@ -0,0 +1,69 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RboxPoints.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef RBOXPOINTS_H
    +#define RBOXPOINTS_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/PointCoordinates.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! RboxPoints -- generate random PointCoordinates for Qhull
    +    class RboxPoints;
    +
    +class RboxPoints : public PointCoordinates {
    +
    +private:
    +#//!\name Fields and friends
    +                        //! PointCoordinates.qh() is owned by RboxPoints
    +    countT              rbox_new_count;     //! Number of points for PointCoordinates
    +    int                 rbox_status;    //! error status from rboxpoints.  qh_ERRnone if none.
    +    std::string         rbox_message;   //! stderr from rboxpoints
    +
    +    // '::' is required for friend references
    +    friend void ::qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +
    +public:
    +#//!\name Construct
    +                        RboxPoints();
    +    explicit            RboxPoints(const char *rboxCommand);
    +                        ~RboxPoints();
    +private:                // Disable copy constructor and assignment.  RboxPoints owns QhullQh.
    +                        RboxPoints(const RboxPoints &);
    +                        RboxPoints &operator=(const RboxPoints &);
    +private:
    +    void                allocateQhullQh();
    +
    +public:
    +#//!\name GetSet
    +    void                clearRboxMessage();
    +    countT              newCount() const { return rbox_new_count; }
    +    std::string         rboxMessage() const;
    +    int                 rboxStatus() const;
    +    bool                hasRboxMessage() const;
    +    void                setNewCount(countT pointCount) { QHULL_ASSERT(pointCount>=0); rbox_new_count= pointCount; }
    +
    +#//!\name Modify
    +    void                appendPoints(const char* rboxCommand);
    +    using               PointCoordinates::appendPoints;
    +    void                reservePoints() { reserveCoordinates((count()+newCount())*dimension()); }
    +};//class RboxPoints
    +
    +}//namespace orgQhull
    +
    +#endif // RBOXPOINTS_H
    diff --git a/xs/src/qhull/src/libqhullcpp/RoadError.cpp b/xs/src/qhull/src/libqhullcpp/RoadError.cpp
    new file mode 100644
    index 0000000000..1d41ec1bc1
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RoadError.cpp
    @@ -0,0 +1,158 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RoadError.cpp#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! RoadError -- All exceptions thrown by Qhull are RoadErrors
    +#//! Do not throw RoadError's from destructors.  Use e.logError() instead.
    +
    +#include "libqhullcpp/RoadError.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +using std::cerr;
    +using std::cout;
    +using std::string;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class fields
    +
    +//! Identifies error messages from Qhull and Road for web searches.
    +//! See QhullError.h#QHULLlastError and user.h#MSG_ERROR
    +const char * RoadError::
    +ROADtag= "QH";
    +
    +std::ostringstream RoadError::
    +global_log;
    +
    +#//!\name Constructor
    +
    +RoadError::
    +RoadError()
    +: error_code(0)
    +, log_event()
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(const RoadError &other)
    +: error_code(other.error_code)
    +, log_event(other.log_event)
    +, error_message(other.error_message)
    +{
    +}//copy construct
    +
    +RoadError::
    +RoadError(int code, const std::string &message)
    +: error_code(code)
    +, log_event(message.c_str())
    +, error_message(log_event.toString(ROADtag, error_code))
    +{
    +    log_event.cstr_1= error_message.c_str(); // overwrites initial value
    +}
    +
    +RoadError::
    +RoadError(int code, const char *fmt)
    +: error_code(code)
    +, log_event(fmt)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d)
    +: error_code(code)
    +, log_event(fmt, d)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2)
    +: error_code(code)
    +, log_event(fmt, d, d2)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f)
    +: error_code(code)
    +, log_event(fmt, d, d2, f)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, const char *s)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, s)
    +, error_message(log_event.toString(ROADtag, code)) // char * may go out of scope
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, const void *x)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, x)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, int i)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, i)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, long long i)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, i)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, double e)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, e)
    +, error_message()
    +{ }
    +
    +RoadError & RoadError::
    +operator=(const RoadError &other)
    +{
    +    error_code= other.error_code;
    +    error_message= other.error_message;
    +    log_event= other.log_event;
    +    return *this;
    +}//operator=
    +
    +#//!\name Virtual
    +const char * RoadError::
    +what() const throw()
    +{
    +    if(error_message.empty()){
    +        error_message= log_event.toString(ROADtag, error_code);
    +    }
    +    return error_message.c_str();
    +}//what
    +
    +#//!\name Updates
    +
    +//! Log error instead of throwing it.
    +//! Not reentrant, so avoid using it if possible
    +//!\todo Redesign with a thread-local stream or a reentrant ostringstream
    +void RoadError::
    +logErrorLastResort() const
    +{
    +    global_log << what() << endl;
    +}//logError
    +
    +
    +}//namespace orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/RoadError.h b/xs/src/qhull/src/libqhullcpp/RoadError.h
    new file mode 100644
    index 0000000000..1c9f6cdd5a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RoadError.h
    @@ -0,0 +1,88 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RoadError.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef ROADERROR_H
    +#define ROADERROR_H
    +
    +#include "libqhull_r/user_r.h"  /* for QHULL_CRTDBG */
    +#include "libqhullcpp/RoadLogEvent.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +using std::endl;
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! RoadError -- Report and log errors
    +    //!  See discussion in Saylan, G., "Practical C++ error handling in hybrid environments," Dr. Dobb's Journal, p. 50-55, March 2007.
    +    //!   He uses an auto_ptr to track a stringstream.  It constructs a string on the fly.  RoadError uses the copy constructor to transform RoadLogEvent into a string
    +    class RoadError;
    +
    +class RoadError : public std::exception {
    +
    +private:
    +#//!\name Fields
    +    int                 error_code;  //! Non-zero code (not logged), maybe returned as program status
    +    RoadLogEvent        log_event;   //! Format string w/ arguments
    +    mutable std::string error_message;  //! Formated error message.  Must be after log_event.
    +
    +#//!\name Class fields
    +    static const char  *  ROADtag;
    +    static std::ostringstream  global_log; //!< May be replaced with any ostream object
    +                                    //!< Not reentrant -- only used by RoadError::logErrorLastResort()
    +
    +public:
    +#//!\name Constants
    +
    +#//!\name Constructors
    +    RoadError();
    +    RoadError(const RoadError &other);  //! Called on throw, generates error_message
    +    RoadError(int code, const std::string &message);
    +    RoadError(int code, const char *fmt);
    +    RoadError(int code, const char *fmt, int d);
    +    RoadError(int code, const char *fmt, int d, int d2);
    +    RoadError(int code, const char *fmt, int d, int d2, float f);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, const char *s);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, const void *x);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, int i);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, long long i);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, double e);
    +
    +    RoadError &         operator=(const RoadError &other);
    +                        ~RoadError() throw() {};
    +
    +#//!\name Class methods
    +
    +    static void         clearGlobalLog() { global_log.seekp(0); }
    +    static bool         emptyGlobalLog() { return global_log.tellp()<=0; }
    +    static const char  *stringGlobalLog() { return global_log.str().c_str(); }
    +
    +#//!\name Virtual
    +    virtual const char *what() const throw();
    +
    +#//!\name GetSet
    +    bool                isValid() const { return log_event.isValid(); }
    +    int                 errorCode() const { return error_code; };
    +   // FIXUP QH11021 should RoadError provide errorMessage().  Currently what()
    +    RoadLogEvent        roadLogEvent() const { return log_event; };
    +
    +#//!\name Update
    +    void                logErrorLastResort() const;
    +};//class RoadError
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +inline std::ostream &   operator<<(std::ostream &os, const orgQhull::RoadError &e) { return os << e.what(); }
    +
    +#endif // ROADERROR_H
    diff --git a/xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp b/xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp
    new file mode 100644
    index 0000000000..9a9cf5960a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp
    @@ -0,0 +1,122 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RoadLogEvent.cpp#3 $$Change: 2066 $
    +** $Date: 2016/01/18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! RoadLogEvent -- All exceptions thrown by Qhull are RoadErrors
    +
    +#include "libqhullcpp/RoadLogEvent.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::string;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Conversion
    +string RoadLogEvent::
    +toString(const char *tag, int code) const
    +{
    +    ostringstream os;
    +    if(tag && code){
    +        os << tag << code;
    +        if(format_string){
    +            os << " ";
    +        }
    +    }
    +    if(!format_string){
    +        return os.str();
    +    }
    +    const char *s= format_string;
    +    int dCount= 0;  // Count of %d
    +    int fCount= 0;  // Count of %f
    +    char extraCode= '\0';
    +    while(*s){
    +        if(*s!='%'){
    +            os << *s++;
    +        }else{
    +            char c= *++s;
    +            s++;
    +            switch(c){
    +            case 'd':
    +                if(++dCount>2){
    +                    os << " ERROR_three_%d_in_format ";
    +                }else if(dCount==2){
    +                    os << int_2;
    +                }else{
    +                    os << int_1;
    +                }
    +                break;
    +            case 'e':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << double_1;
    +                }
    +                break;
    +            case 'f':
    +                if(++fCount>1){
    +                    os << " ERROR_two_%f_in_format ";
    +                }else{
    +                    os << float_1;
    +                }
    +                break;
    +            case 'i':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << int64_1;
    +                }
    +                break;
    +            case 's':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << cstr_1;
    +                }
    +                break;
    +            case 'u':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << "0x" << std::hex << int64_1 << std::dec;
    +                }
    +                break;
    +            case 'x':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << void_1;
    +                }
    +                break;
    +            case '%':
    +                os << c;
    +                break;
    +            default:
    +                os << " ERROR_%" << c << "_not_defined_in_format";
    +                break;
    +            }
    +        }
    +    }
    +    if(s[-1]!='\n'){
    +        os << endl;
    +    }
    +    return os.str();
    +}//toString
    +
    +#//!\name Class helpers (static)
    +
    +//! True if this char is the first extra code
    +bool RoadLogEvent::
    +firstExtraCode(std::ostream &os, char c, char *extraCode){
    +    if(*extraCode){
    +        os << " ERROR_%" << *extraCode << "_and_%" << c << "_in_format ";
    +        return false;
    +    }
    +    *extraCode= c;
    +    return true;
    +}//firstExtraCode
    +
    +}//namespace orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/RoadLogEvent.h b/xs/src/qhull/src/libqhullcpp/RoadLogEvent.h
    new file mode 100644
    index 0000000000..7c4cfba0de
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RoadLogEvent.h
    @@ -0,0 +1,77 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RoadLogEvent.h#1 $$Change: 1981 $
    +** $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef ROADLOGEVENT_H
    +#define ROADLOGEVENT_H
    +
    +#include 
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! RoadLogEvent -- Record an event for the RoadLog
    +    struct RoadLogEvent;
    +
    +struct RoadLogEvent {
    +
    +public:
    +#//!\name Fields
    +    const char *    format_string; //! Format string (a literal with format codes, for logging)
    +    int             int_1;       //! Integer argument (%d, for logging)
    +    int             int_2;       //! Integer argument (%d, for logging)
    +    float           float_1;     //! Float argument (%f, for logging)
    +    union {                      //! One additional argument (for logging)
    +        const char *cstr_1;      //!   Cstr argument (%s) -- type checked at construct-time
    +        const void *void_1;      //!   Void* argument (%x) -- Use upper-case codes for object types
    +        long long   int64_1;     //!   signed int64 (%i).  Ambiguous if unsigned is also defined.
    +        double      double_1;    //!   Double argument (%e)
    +    };
    +
    +#//!\name Constants
    +
    +#//!\name Constructors
    +    RoadLogEvent() : format_string(0), int_1(0), int_2(0), float_1(0), int64_1(0) {};
    +    explicit RoadLogEvent(const char *fmt) : format_string(fmt), int_1(0), int_2(0), float_1(0), int64_1(0) {};
    +    RoadLogEvent(const char *fmt, int d) : format_string(fmt), int_1(d), int_2(0), float_1(0), int64_1(0) {};
    +    RoadLogEvent(const char *fmt, int d, int d2) : format_string(fmt), int_1(d), int_2(d2), float_1(0), int64_1(0) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f) : format_string(fmt), int_1(d), int_2(d2), float_1(f), int64_1(0) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, const char *s) : format_string(fmt), int_1(d), int_2(d2), float_1(f), cstr_1(s) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, const void *x) : format_string(fmt), int_1(d), int_2(d2), float_1(f), void_1(x) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, int i) : format_string(fmt), int_1(d), int_2(d2), float_1(f), int64_1(i) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, long long i) : format_string(fmt), int_1(d), int_2(d2), float_1(f), int64_1(i) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, double g) : format_string(fmt), int_1(d), int_2(d2), float_1(f), double_1(g) {};
    +    ~RoadLogEvent() {};
    +    //! Default copy constructor and assignment
    +
    +#//!\name GetSet
    +    bool                isValid() const { return format_string!=0; }
    +    int                 int1() const { return int_1; };
    +    int                 int2() const { return int_2; };
    +    float               float1() const { return float_1; };
    +    const char *        format() const { return format_string; };
    +    const char *        cstr1() const { return cstr_1; };
    +    const void *        void1() const { return void_1; };
    +    long long           int64() const { return int64_1; };
    +    double              double1() const { return double_1; };
    +
    +#//!\name Conversion
    +
    +    std::string        toString(const char* tag, int code) const;
    +
    +private:
    +#//!\name Class helpers
    +    static bool         firstExtraCode(std::ostream &os, char c, char *extraCode);
    +
    +
    +};//class RoadLogEvent
    +
    +}//namespace orgQhull
    +
    +#endif // ROADLOGEVENT_H
    diff --git a/xs/src/qhull/src/libqhullcpp/functionObjects.h b/xs/src/qhull/src/libqhullcpp/functionObjects.h
    new file mode 100644
    index 0000000000..3645c0713a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/functionObjects.h
    @@ -0,0 +1,67 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/functionObjects.h#1 $$Change: 1981 $
    +** $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHFUNCTIONOBJECTS_H
    +#define QHFUNCTIONOBJECTS_H
    +
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +
    +    //! Sum of absolute values of the elements in a container
    +    class AbsoluteSumOf;
    +    //! Sum of the elements in a container
    +    class SumOf;
    +    //! Sum of squares of the elements in a container
    +    class SumSquaresOf;
    +
    +#//!\name Class
    +
    +//! Absolute sum of the elements in a container
    +class AbsoluteSumOf
    +{
    +private:
    +    double sum;
    +public:
    +    inline AbsoluteSumOf() : sum(0.0) {}
    +    inline void operator()(double v) { sum += fabs(v); }
    +    inline operator double() { return sum; }
    +};//AbsoluteSumOf
    +
    +//! Sum of the elements in a container
    +class SumOf
    +{
    +private:
    +    double sum;
    +public:
    +    inline SumOf() : sum(0.0) {}
    +    inline void operator()(double v) { sum += v; }
    +    inline operator double() { return sum; }
    +};//SumOf
    +
    +
    +//! Sum of squares of the elements in a container
    +class SumSquaresOf
    +{
    +private:
    +    double sum;
    +public:
    +    inline SumSquaresOf() : sum(0.0) {}
    +    inline void operator()(double v) { sum += v*v; }
    +    inline operator double() { return sum; }
    +};//SumSquaresOf
    +
    +
    +}//orgQhull
    +
    +
    +#endif //QHFUNCTIONOBJECTS_H
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/libqhullcpp.pro b/xs/src/qhull/src/libqhullcpp/libqhullcpp.pro
    new file mode 100644
    index 0000000000..89b967bef2
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/libqhullcpp.pro
    @@ -0,0 +1,71 @@
    +# -------------------------------------------------
    +# libqhullcpp.pro -- Qt project for Qhull cpp shared library
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +DESTDIR = ../../lib
    +TEMPLATE = lib
    +# Do not create libqhullcpp as a shared library.  Qhull C++ classes may change layout and size. 
    +CONFIG += staticlib warn_on
    +CONFIG -= qt rtti
    +build_pass:CONFIG(debug, debug|release):{
    +   TARGET = qhullcpp_d
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +   TARGET = qhullcpp
    +   OBJECTS_DIR = Release
    +}
    +MOC_DIR = moc
    +
    +INCLUDEPATH += ../../src
    +INCLUDEPATH += $$PWD # for MOC_DIR
    +
    +CONFIG += qhull_warn_shadow qhull_warn_conversion
    +
    +SOURCES += ../libqhullcpp/Coordinates.cpp
    +SOURCES += ../libqhullcpp/PointCoordinates.cpp
    +SOURCES += ../libqhullcpp/Qhull.cpp
    +SOURCES += ../libqhullcpp/QhullFacet.cpp
    +SOURCES += ../libqhullcpp/QhullFacetList.cpp
    +SOURCES += ../libqhullcpp/QhullFacetSet.cpp
    +SOURCES += ../libqhullcpp/QhullHyperplane.cpp
    +SOURCES += ../libqhullcpp/QhullPoint.cpp
    +SOURCES += ../libqhullcpp/QhullPoints.cpp
    +SOURCES += ../libqhullcpp/QhullPointSet.cpp
    +SOURCES += ../libqhullcpp/QhullQh.cpp
    +SOURCES += ../libqhullcpp/QhullRidge.cpp
    +SOURCES += ../libqhullcpp/QhullSet.cpp
    +SOURCES += ../libqhullcpp/QhullStat.cpp
    +SOURCES += ../libqhullcpp/QhullVertex.cpp
    +SOURCES += ../libqhullcpp/QhullVertexSet.cpp
    +SOURCES += ../libqhullcpp/RboxPoints.cpp
    +SOURCES += ../libqhullcpp/RoadError.cpp
    +SOURCES += ../libqhullcpp/RoadLogEvent.cpp
    +
    +HEADERS += ../libqhullcpp/Coordinates.h
    +HEADERS += ../libqhullcpp/functionObjects.h
    +HEADERS += ../libqhullcpp/PointCoordinates.h
    +HEADERS += ../libqhullcpp/Qhull.h
    +HEADERS += ../libqhullcpp/QhullError.h
    +HEADERS += ../libqhullcpp/QhullFacet.h
    +HEADERS += ../libqhullcpp/QhullFacetList.h
    +HEADERS += ../libqhullcpp/QhullFacetSet.h
    +HEADERS += ../libqhullcpp/QhullHyperplane.h
    +HEADERS += ../libqhullcpp/QhullIterator.h
    +HEADERS += ../libqhullcpp/QhullLinkedList.h
    +HEADERS += ../libqhullcpp/QhullPoint.h
    +HEADERS += ../libqhullcpp/QhullPoints.h
    +HEADERS += ../libqhullcpp/QhullPointSet.h
    +HEADERS += ../libqhullcpp/QhullQh.h
    +HEADERS += ../libqhullcpp/QhullRidge.h
    +HEADERS += ../libqhullcpp/QhullSet.h
    +HEADERS += ../libqhullcpp/QhullSets.h
    +HEADERS += ../libqhullcpp/QhullStat.h
    +HEADERS += ../libqhullcpp/QhullVertex.h
    +HEADERS += ../libqhullcpp/QhullVertexSet.h
    +HEADERS += ../libqhullcpp/RboxPoints.h
    +HEADERS += ../libqhullcpp/RoadError.h
    +HEADERS += ../libqhullcpp/RoadLogEvent.h
    diff --git a/xs/src/qhull/src/libqhullcpp/qt-qhull.cpp b/xs/src/qhull/src/libqhullcpp/qt-qhull.cpp
    new file mode 100644
    index 0000000000..895f591a85
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/qt-qhull.cpp
    @@ -0,0 +1,130 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/qt-qhull.cpp#1 $$Change: 1981 $
    +** $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include 
    +#include "qhulltest/RoadTest.h"
    +
    +#ifndef QHULL_USES_QT
    +#define QHULL_USES_QT 1
    +#endif
    +
    +#include "Coordinates.h"
    +#include "QhullFacetList.h"
    +#include "QhullFacetSet.h"
    +#include "QhullHyperplane.h"
    +#include "QhullPoint.h"
    +#include "QhullPoints.h"
    +#include "QhullPointSet.h"
    +#include "QhullVertex.h"
    +#include "QhullVertexSet.h"
    +
    +namespace orgQhull {
    +
    +#//!\name Conversions
    +
    +QList Coordinates::
    +toQList() const
    +{
    +    CoordinatesIterator i(*this);
    +    QList cs;
    +    while(i.hasNext()){
    +        cs.append(i.next());
    +    }
    +    return cs;
    +}//toQList
    +
    +QList QhullFacetList::
    +toQList() const
    +{
    +    QhullLinkedListIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        QhullFacet f= i.next();
    +        if(isSelectAll() || f.isGood()){
    +            vs.append(f);
    +        }
    +    }
    +    return vs;
    +}//toQList
    +
    +//! Same as PrintVertices
    +QList QhullFacetList::
    +vertices_toQList() const
    +{
    +    QList vs;
    +    QhullVertexSet qvs(qh(), first().getFacetT(), NULL, isSelectAll());
    +    for(QhullVertexSet::iterator i=qvs.begin(); i!=qvs.end(); ++i){
    +        vs.push_back(*i);
    +    }
    +    return vs;
    +}//vertices_toQList
    +
    +QList QhullFacetSet::
    +toQList() const
    +{
    +    QhullSetIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        QhullFacet f= i.next();
    +        if(isSelectAll() || f.isGood()){
    +            vs.append(f);
    +        }
    +    }
    +    return vs;
    +}//toQList
    +
    +#ifdef QHULL_USES_QT
    +QList QhullHyperplane::
    +toQList() const
    +{
    +    QhullHyperplaneIterator i(*this);
    +    QList fs;
    +    while(i.hasNext()){
    +        fs.append(i.next());
    +    }
    +    fs.append(hyperplane_offset);
    +    return fs;
    +}//toQList
    +#endif //QHULL_USES_QT
    +
    +QList QhullPoint::
    +toQList() const
    +{
    +    QhullPointIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        vs.append(i.next());
    +    }
    +    return vs;
    +}//toQList
    +
    +QList QhullPoints::
    +toQList() const
    +{
    +    QhullPointsIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        vs.append(i.next());
    +    }
    +    return vs;
    +}//toQList
    +
    +/******
    +QList QhullPointSet::
    +toQList() const
    +{
    +    QhullPointSetIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        vs.append(i.next());
    +    }
    +    return vs;
    +}//toQList
    +*/
    +}//orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/usermem_r-cpp.cpp b/xs/src/qhull/src/libqhullcpp/usermem_r-cpp.cpp
    new file mode 100644
    index 0000000000..bb9534d098
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/usermem_r-cpp.cpp
    @@ -0,0 +1,93 @@
    +/*
      ---------------------------------
    +
    +   usermem_r-cpp.cpp
    +
    +   Redefine qh_exit() as 'throw std::runtime_error("QH10003 ...")'
    +
    +   This file is not included in the Qhull builds.
    +
    +   qhull_r calls qh_exit() when qh_errexit() is not available.  For example,
    +   it calls qh_exit() if you linked the wrong qhull library.
    +
    +   The C++ interface avoids most of the calls to qh_exit().
    +
    +   If needed, include usermem_r-cpp.o before libqhullstatic_r.a.  You may need to
    +   override duplicate symbol errors (e.g. /FORCE:MULTIPLE for DevStudio).  It
    +   may produce a warning about throwing an error from C code.
    +*/
    +
    +extern "C" {
    +    void    qh_exit(int exitcode);
    +}
    +
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +  qh_exit( exitcode )
    +    exit program
    +
    +  notes:
    +    same as exit()
    +*/
    +void qh_exit(int exitcode) {
    +    exitcode= exitcode;
    +    throw std::runtime_error("QH10003 Qhull error.  See stderr or errfile.");
    +} /* exit */
    +
    +/*---------------------------------
    +
    +  qh_fprintf_stderr( msgcode, format, list of args )
    +    fprintf to stderr with msgcode (non-zero)
    +
    +  notes:
    +    qh_fprintf_stderr() is called when qh->ferr is not defined, usually due to an initialization error
    +
    +    It is typically followed by qh_errexit().
    +
    +    Redefine this function to avoid using stderr
    +
    +    Use qh_fprintf [userprintf_r.c] for normal printing
    +*/
    +void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    va_start(args, fmt);
    +    if(msgcode)
    +      fprintf(stderr, "QH%.4d ", msgcode);
    +    vfprintf(stderr, fmt, args);
    +    va_end(args);
    +} /* fprintf_stderr */
    +
    +/*---------------------------------
    +
    +  qh_free(qhT *qh, mem )
    +    free memory
    +
    +  notes:
    +    same as free()
    +    No calls to qh_errexit()
    +*/
    +void qh_free(void *mem) {
    +    free(mem);
    +} /* free */
    +
    +/*---------------------------------
    +
    +    qh_malloc( mem )
    +      allocate memory
    +
    +    notes:
    +      same as malloc()
    +*/
    +void *qh_malloc(size_t size) {
    +    return malloc(size);
    +} /* malloc */
    +
    +
    diff --git a/xs/src/qhull/src/libqhullstatic/libqhullstatic.pro b/xs/src/qhull/src/libqhullstatic/libqhullstatic.pro
    new file mode 100644
    index 0000000000..1a516db73c
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullstatic/libqhullstatic.pro
    @@ -0,0 +1,19 @@
    +# -------------------------------------------------
    +# libqhullstatic.pro -- Qt project for Qhull static library
    +#   Built with qh_QHpointer=0.  See libqhullp.pro
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +include(../qhull-libqhull-src.pri)
    +
    +DESTDIR = ../../lib
    +TEMPLATE = lib
    +CONFIG += staticlib warn_on
    +CONFIG -= qt
    +build_pass:CONFIG(debug, debug|release):{
    +    TARGET = qhullstatic_d
    +    OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +    TARGET = qhullstatic
    +    OBJECTS_DIR = Release
    +}
    diff --git a/xs/src/qhull/src/libqhullstatic_r/libqhullstatic_r.pro b/xs/src/qhull/src/libqhullstatic_r/libqhullstatic_r.pro
    new file mode 100644
    index 0000000000..2f5bf4d076
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullstatic_r/libqhullstatic_r.pro
    @@ -0,0 +1,21 @@
    +# -------------------------------------------------
    +# libqhullstatic_r.pro -- Qt project for Qhull static library
    +#
    +# It uses reeentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +DESTDIR = ../../lib
    +TEMPLATE = lib
    +CONFIG += staticlib warn_on
    +CONFIG -= qt
    +build_pass:CONFIG(debug, debug|release):{
    +    TARGET = qhullstatic_rd
    +    OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +    TARGET = qhullstatic_r
    +    OBJECTS_DIR = Release
    +}
    +
    +include(../qhull-libqhull-src_r.pri)
    diff --git a/xs/src/qhull/src/qconvex/qconvex.c b/xs/src/qhull/src/qconvex/qconvex.c
    new file mode 100644
    index 0000000000..41bd666da1
    --- /dev/null
    +++ b/xs/src/qhull/src/qconvex/qconvex.c
    @@ -0,0 +1,326 @@
    +/*
      ---------------------------------
    +
    +   qconvex.c
    +      compute convex hulls using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull/libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qconvex
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qconvex.htm */
    +char hidden_options[]=" d v H Qbb Qf Qg Qm Qr Qu Qv Qx Qz TR E V Fp Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qconvex- compute the convex hull\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar points with nearest facet\n\
    +    Qi   - keep interior points with nearest facet\n\
    +\n\
    +Qhull control options:\n\
    +    Qbk:n   - scale coord k so that low bound is n\n\
    +      QBk:n - scale coord k so that upper bound is n (QBk is %2.2g)\n\
    +    QbB  - scale input to unit cube centered at the origin\n\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    QGn  - good facet if visible from point n, -n for not visible\n\
    +    QVn  - good facet if it includes point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Un   - max distance below plane for a new, coplanar point\n\
    +    Wn   - min facet width for outside point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each facet\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    n    - normals with offsets\n\
    +    o    - OFF file format (dim, points and facets; Voronoi regions)\n\
    +    p    - point coordinates \n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each facet\n\
    +    FA   - compute total area and volume for option 's'\n\
    +    Fc   - count plus coplanar points for each facet\n\
    +           use 'Qc' (default) for coplanar and 'Qi' for interior\n\
    +    FC   - centrum for each facet\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - inner plane for each facet\n\
    +    FI   - ID for each facet\n\
    +    Fm   - merge count for each facet (511 max)\n\
    +    Fn   - count plus neighboring facets for each facet\n\
    +    FN   - count plus neighboring facets for each point\n\
    +    Fo   - outer plane (or max_outside) for each facet\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest vertex for each coplanar point\n\
    +    FQ   - command used for qconvex\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                      for output: #vertices, #facets,\n\
    +                                  #coplanar points, #non-simplicial facets\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0) \n\
    +                    #real (2) tot area, tot volume\n\
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)\n\
    +    Fv   - count plus vertices for each facet\n\
    +    FV   - average of vertices (a feasible point for 'H')\n\
    +    Fx   - extreme points (in order for 2-d)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview output (2-d, 3-d, and 4-d)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qconvex- compute the convex hull.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qconvex.htm):\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each facet\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates (includes coplanar points if 'Qc')\n\
    +    Fx   - extreme points (convex hull vertices)\n\
    +    FA   - report total area and volume\n\
    +    FS   - compute total area and volume\n\
    +    o    - OFF format (dim, n, points, facets)\n\
    +    G    - Geomview output (2-d, 3-d, and 4-d)\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    QVn  - print facets that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox c D2 | qconvex s n                    rbox c D2 | qconvex i\n\
    +    rbox c D2 | qconvex o                      rbox 1000 s | qconvex s Tv FA\n\
    +    rbox c d D2 | qconvex s Qc Fx              rbox y 1000 W0 | qconvex s n\n\
    +    rbox y 1000 W0 | qconvex s QJ              rbox d G1 D12 | qconvex QR0 FA Pp\n\
    +    rbox c D7 | qconvex FA TF1000\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + incidences     mathematica    normals        OFF_format     points\n\
    + summary        facet_dump\n\
    +\n\
    + Farea          FArea_total    Fcoplanars     FCentrums      Fd_cdd_in\n\
    + FD_cdd_out     FFacet_xridge  Finner         FIDs           Fmerges\n\
    + Fneighbors     FNeigh_vertex  Fouter         FOptions       FPoint_near\n\
    + FQhull         Fsummary       FSize          Fvertices      FVertex_ave\n\
    + Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   PFacet_area_keep Pgood        PGood_neighbors\n\
    + PMerge_keep    Poutput_forced Pprecision_not\n\
    +\n\
    + QbBound 0:0.5  QbB_scale_box  Qcoplanar      QGood_point    Qinterior\n\
    + QJoggle        Qrandom        QRotate        Qsearch_1st    Qtriangulate\n\
    + QVertex_good\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version, qh_DEFAULTbox,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh NOerrexit = False;
    +    qh_checkflags(qh qhull_command, hidden_options);
    +    qh_initflags(qh qhull_command);
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option("Qxact_merge", NULL, NULL);
    +      qh MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh_ALL);
    +#else
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qconvex/qconvex.pro b/xs/src/qhull/src/qconvex/qconvex.pro
    new file mode 100644
    index 0000000000..1bf631bff6
    --- /dev/null
    +++ b/xs/src/qhull/src/qconvex/qconvex.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qconvex.pro -- Qt project file for qconvex.exe
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = qconvex
    +
    +SOURCES += qconvex.c
    diff --git a/xs/src/qhull/src/qconvex/qconvex_r.c b/xs/src/qhull/src/qconvex/qconvex_r.c
    new file mode 100644
    index 0000000000..abf68ce37e
    --- /dev/null
    +++ b/xs/src/qhull/src/qconvex/qconvex_r.c
    @@ -0,0 +1,328 @@
    +/*
      ---------------------------------
    +
    +   qconvex.c
    +      compute convex hulls using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qconvex
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qconvex.htm */
    +char hidden_options[]=" d v H Qbb Qf Qg Qm Qr Qu Qv Qx Qz TR E V Fp Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qconvex- compute the convex hull\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar points with nearest facet\n\
    +    Qi   - keep interior points with nearest facet\n\
    +\n\
    +Qhull control options:\n\
    +    Qbk:n   - scale coord k so that low bound is n\n\
    +      QBk:n - scale coord k so that upper bound is n (QBk is %2.2g)\n\
    +    QbB  - scale input to unit cube centered at the origin\n\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    QGn  - good facet if visible from point n, -n for not visible\n\
    +    QVn  - good facet if it includes point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Un   - max distance below plane for a new, coplanar point\n\
    +    Wn   - min facet width for outside point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each facet\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    n    - normals with offsets\n\
    +    o    - OFF file format (dim, points and facets; Voronoi regions)\n\
    +    p    - point coordinates \n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each facet\n\
    +    FA   - compute total area and volume for option 's'\n\
    +    Fc   - count plus coplanar points for each facet\n\
    +           use 'Qc' (default) for coplanar and 'Qi' for interior\n\
    +    FC   - centrum for each facet\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - inner plane for each facet\n\
    +    FI   - ID for each facet\n\
    +    Fm   - merge count for each facet (511 max)\n\
    +    Fn   - count plus neighboring facets for each facet\n\
    +    FN   - count plus neighboring facets for each point\n\
    +    Fo   - outer plane (or max_outside) for each facet\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest vertex for each coplanar point\n\
    +    FQ   - command used for qconvex\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                      for output: #vertices, #facets,\n\
    +                                  #coplanar points, #non-simplicial facets\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0) \n\
    +                    #real (2) tot area, tot volume\n\
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)\n\
    +    Fv   - count plus vertices for each facet\n\
    +    FV   - average of vertices (a feasible point for 'H')\n\
    +    Fx   - extreme points (in order for 2-d)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview output (2-d, 3-d, and 4-d)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qconvex- compute the convex hull.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qconvex.htm):\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each facet\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates (includes coplanar points if 'Qc')\n\
    +    Fx   - extreme points (convex hull vertices)\n\
    +    FA   - report total area and volume\n\
    +    FS   - compute total area and volume\n\
    +    o    - OFF format (dim, n, points, facets)\n\
    +    G    - Geomview output (2-d, 3-d, and 4-d)\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    QVn  - print facets that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox c D2 | qconvex s n                    rbox c D2 | qconvex i\n\
    +    rbox c D2 | qconvex o                      rbox 1000 s | qconvex s Tv FA\n\
    +    rbox c d D2 | qconvex s Qc Fx              rbox y 1000 W0 | qconvex s n\n\
    +    rbox y 1000 W0 | qconvex s QJ              rbox d G1 D12 | qconvex QR0 FA Pp\n\
    +    rbox c D7 | qconvex FA TF1000\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + incidences     mathematica    normals        OFF_format     points\n\
    + summary        facet_dump\n\
    +\n\
    + Farea          FArea_total    Fcoplanars     FCentrums      Fd_cdd_in\n\
    + FD_cdd_out     FFacet_xridge  Finner         FIDs           Fmerges\n\
    + Fneighbors     FNeigh_vertex  Fouter         FOptions       FPoint_near\n\
    + FQhull         Fsummary       FSize          Fvertices      FVertex_ave\n\
    + Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   PFacet_area_keep Pgood        PGood_neighbors\n\
    + PMerge_keep    Poutput_forced Pprecision_not\n\
    +\n\
    + QbBound 0:0.5  QbB_scale_box  Qcoplanar      QGood_point    Qinterior\n\
    + QJoggle        Qrandom        QRotate        Qsearch_1st    Qtriangulate\n\
    + QVertex_good\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version, qh_DEFAULTbox,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_checkflags(qh, qh->qhull_command, hidden_options);
    +    qh_initflags(qh, qh->qhull_command);
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option(qh, "Qxact_merge", NULL, NULL);
    +      qh->MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qdelaunay/qdelaun.c b/xs/src/qhull/src/qdelaunay/qdelaun.c
    new file mode 100644
    index 0000000000..9011d9fcc0
    --- /dev/null
    +++ b/xs/src/qhull/src/qdelaunay/qdelaun.c
    @@ -0,0 +1,315 @@
    +/*
      ---------------------------------
    +
    +   qdelaun.c
    +     compute Delaunay triangulations and furthest-point Delaunay
    +     triangulations using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull/libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qdelau_f.htm and qdelaun.htm */
    +char hidden_options[]=" d n v H U Qb QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V FC Fi Fo Ft Fp FV Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qdelaunay- compute the Delaunay triangulation\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qu   - compute furthest-site Delaunay triangulation\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +\n\
    +Qhull control options:\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    Qz   - add point-at-infinity to Delaunay triangulation\n\
    +    QGn  - print Delaunay region if visible from point n, -n if not\n\
    +    QVn  - print Delaunay regions that include point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Wn   - min facet width for outside point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each Delaunay region\n\
    +    m    - Mathematica output (2-d only, lifted to a paraboloid)\n\
    +    o    - OFF format (dim, points, and facets as a paraboloid)\n\
    +    p    - point coordinates (lifted to a paraboloid)\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each Delaunay region\n\
    +    FA   - compute total area for option 's'\n\
    +    Fc   - count plus coincident points for each Delaunay region\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    FI   - ID of each Delaunay region\n\
    +    Fm   - merge count for each Delaunay region (511 max)\n\
    +    FM   - Maple output (2-d only, lifted to a paraboloid)\n\
    +    Fn   - count plus neighboring region for each Delaunay region\n\
    +    FN   - count plus neighboring region for each point\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest point and distance for each coincident point\n\
    +    FQ   - command used for qdelaunay\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                    for output: #vertices, #Delaunay regions,\n\
    +                                #coincident points, #non-simplicial regions\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0)\n\
    +                    #real (2), tot area, 0\n\
    +    Fv   - count plus vertices for each Delaunay region\n\
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d and 3-d)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc     - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +    Gt   - transparent outer ridges to view 3-d Delaunay\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest Delaunay regions by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good Delaunay regions (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep Delaunay regions whose area is at least n\n\
    +    PG   - print neighbors of good regions (needs 'QGn' or 'QVn')\n\
    +    PMn  - keep n Delaunay regions with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qdelaunay- compute the Delaunay triangulation.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qdelaun.htm):\n\
    +    Qu   - furthest-site Delaunay triangulation\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each Delaunay region\n\
    +    Fx   - extreme points (vertices of the convex hull)\n\
    +    o    - OFF format (shows the points lifted to a paraboloid)\n\
    +    G    - Geomview output (2-d and 3-d points lifted to a paraboloid)\n\
    +    m    - Mathematica output (2-d inputs lifted to a paraboloid)\n\
    +    QVn  - print Delaunay regions that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox c P0 D2 | qdelaunay s o          rbox c P0 D2 | qdelaunay i\n\
    +    rbox c P0 D2 | qdelaunay Fv           rbox c P0 D2 | qdelaunay s Qu Fv\n\
    +    rbox c G1 d D2 | qdelaunay s i        rbox c G1 d D2 | qdelaunay Qt\n\
    +    rbox M3,4 z 100 D2 | qdelaunay s      rbox M3,4 z 100 D2 | qdelaunay s Qt\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + incidences     mathematica    OFF_format     points_lifted  summary\n\
    + facet_dump\n\
    +\n\
    + Farea          FArea_total    Fcoincident    Fd_cdd_in      FD_cdd_out\n\
    + FF_dump_xridge FIDs           Fmerges        Fneighbors     FNeigh_vertex\n\
    + FOptions       FPoint_near    FQdelaun       Fsummary       FSize\n\
    + Fvertices      Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    + Gtransparent\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QGood_point    QJoggle        Qsearch_1st    Qtriangulate   QupperDelaunay\n\
    + QVertex_good   Qzinfinite\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh NOerrexit = False;
    +    qh_option("delaunay  Qbbound-last", NULL, NULL);
    +    qh DELAUNAY= True;     /* 'd'   */
    +    qh SCALElast= True;    /* 'Qbb' */
    +    qh KEEPcoplanar= True; /* 'Qc', to keep coplanars in 'p' */
    +    qh_checkflags(qh qhull_command, hidden_options);
    +    qh_initflags(qh qhull_command);
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option("Qxact_merge", NULL, NULL);
    +      qh MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh_ALL);
    +#else
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qdelaunay/qdelaun_r.c b/xs/src/qhull/src/qdelaunay/qdelaun_r.c
    new file mode 100644
    index 0000000000..0854b8bb97
    --- /dev/null
    +++ b/xs/src/qhull/src/qdelaunay/qdelaun_r.c
    @@ -0,0 +1,317 @@
    +/*
      ---------------------------------
    +
    +   qdelaun.c
    +     compute Delaunay triangulations and furthest-point Delaunay
    +     triangulations using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qdelau_f.htm and qdelaun.htm */
    +char hidden_options[]=" d n v H U Qb QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V FC Fi Fo Ft Fp FV Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qdelaunay- compute the Delaunay triangulation\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qu   - compute furthest-site Delaunay triangulation\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +\n\
    +Qhull control options:\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    Qz   - add point-at-infinity to Delaunay triangulation\n\
    +    QGn  - print Delaunay region if visible from point n, -n if not\n\
    +    QVn  - print Delaunay regions that include point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Wn   - min facet width for outside point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each Delaunay region\n\
    +    m    - Mathematica output (2-d only, lifted to a paraboloid)\n\
    +    o    - OFF format (dim, points, and facets as a paraboloid)\n\
    +    p    - point coordinates (lifted to a paraboloid)\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each Delaunay region\n\
    +    FA   - compute total area for option 's'\n\
    +    Fc   - count plus coincident points for each Delaunay region\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    FI   - ID of each Delaunay region\n\
    +    Fm   - merge count for each Delaunay region (511 max)\n\
    +    FM   - Maple output (2-d only, lifted to a paraboloid)\n\
    +    Fn   - count plus neighboring region for each Delaunay region\n\
    +    FN   - count plus neighboring region for each point\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest point and distance for each coincident point\n\
    +    FQ   - command used for qdelaunay\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                    for output: #vertices, #Delaunay regions,\n\
    +                                #coincident points, #non-simplicial regions\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0)\n\
    +                    #real (2), tot area, 0\n\
    +    Fv   - count plus vertices for each Delaunay region\n\
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d and 3-d)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc     - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +    Gt   - transparent outer ridges to view 3-d Delaunay\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest Delaunay regions by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good Delaunay regions (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep Delaunay regions whose area is at least n\n\
    +    PG   - print neighbors of good regions (needs 'QGn' or 'QVn')\n\
    +    PMn  - keep n Delaunay regions with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qdelaunay- compute the Delaunay triangulation.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qdelaun.htm):\n\
    +    Qu   - furthest-site Delaunay triangulation\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each Delaunay region\n\
    +    Fx   - extreme points (vertices of the convex hull)\n\
    +    o    - OFF format (shows the points lifted to a paraboloid)\n\
    +    G    - Geomview output (2-d and 3-d points lifted to a paraboloid)\n\
    +    m    - Mathematica output (2-d inputs lifted to a paraboloid)\n\
    +    QVn  - print Delaunay regions that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox c P0 D2 | qdelaunay s o          rbox c P0 D2 | qdelaunay i\n\
    +    rbox c P0 D2 | qdelaunay Fv           rbox c P0 D2 | qdelaunay s Qu Fv\n\
    +    rbox c G1 d D2 | qdelaunay s i        rbox c G1 d D2 | qdelaunay Qt\n\
    +    rbox M3,4 z 100 D2 | qdelaunay s      rbox M3,4 z 100 D2 | qdelaunay s Qt\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + incidences     mathematica    OFF_format     points_lifted  summary\n\
    + facet_dump\n\
    +\n\
    + Farea          FArea_total    Fcoincident    Fd_cdd_in      FD_cdd_out\n\
    + FF_dump_xridge FIDs           Fmerges        Fneighbors     FNeigh_vertex\n\
    + FOptions       FPoint_near    FQdelaun       Fsummary       FSize\n\
    + Fvertices      Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    + Gtransparent\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QGood_point    QJoggle        Qsearch_1st    Qtriangulate   QupperDelaunay\n\
    + QVertex_good   Qzinfinite\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_option(qh, "delaunay  Qbbound-last", NULL, NULL);
    +    qh->DELAUNAY= True;     /* 'd'   */
    +    qh->SCALElast= True;    /* 'Qbb' */
    +    qh->KEEPcoplanar= True; /* 'Qc', to keep coplanars in 'p' */
    +    qh_checkflags(qh, qh->qhull_command, hidden_options);
    +    qh_initflags(qh, qh->qhull_command);
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option(qh, "Qxact_merge", NULL, NULL);
    +      qh->MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qdelaunay/qdelaunay.pro b/xs/src/qhull/src/qdelaunay/qdelaunay.pro
    new file mode 100644
    index 0000000000..138ac0589d
    --- /dev/null
    +++ b/xs/src/qhull/src/qdelaunay/qdelaunay.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qdelaunay.pro -- Qt project file for qvoronoi.exe
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = qdelaunay
    +
    +SOURCES += qdelaun.c
    diff --git a/xs/src/qhull/src/qhalf/qhalf.c b/xs/src/qhull/src/qhalf/qhalf.c
    new file mode 100644
    index 0000000000..4a5889ed78
    --- /dev/null
    +++ b/xs/src/qhull/src/qhalf/qhalf.c
    @@ -0,0 +1,316 @@
    +/*
      ---------------------------------
    +
    +   qhalf.c
    +     compute the intersection of halfspaces about a point
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull/libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qhalf.htm */
    +char hidden_options[]=" d n v Qbb QbB Qf Qg Qm Qr QR Qv Qx Qz TR E V Fa FA FC FD FS Ft FV Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qhalf- compute the intersection of halfspaces about a point\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    optional interior point: dimension, 1, coordinates\n\
    +    first lines: dimension+1 and number of halfspaces\n\
    +    other lines: halfspace coefficients followed by offset\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Hn,n - specify coordinates of interior point\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar halfspaces\n\
    +    Qi   - keep other redundant halfspaces\n\
    +\n\
    +Qhull control options:\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    Qs   - search all halfspaces for the initial simplex\n\
    +    QGn  - print intersection if visible to halfspace n, -n for not\n\
    +    QVn  - print intersections for halfspace n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and redundancy\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when halfspace n added to intersection\n\
    +    TMn  - turn on tracing at merge n\n\
    +    TWn  - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding halfspace n, -n for before (see TCn)\n\
    +    TCn  - stop qhull after building cone for halfspace n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Un   - max distance below plane for a new, coplanar halfspace\n\
    +    Wn   - min facet width for outside halfspace (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (dual convex hull)\n\
    +    i    - non-redundant halfspaces incident to each intersection\n\
    +    m    - Mathematica output (dual convex hull)\n\
    +    o    - OFF format (dual convex hull: dimension, points, and facets)\n\
    +    p    - vertex coordinates of dual convex hull (coplanars if 'Qc' or 'Qi')\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fc   - count plus redundant halfspaces for each intersection\n\
    +         -   Qc (default) for coplanar and Qi for other redundant\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    FI   - ID of each intersection\n\
    +    Fm   - merge count for each intersection (511 max)\n\
    +    FM   - Maple output (dual convex hull)\n\
    +    Fn   - count plus neighboring intersections for each intersection\n\
    +    FN   - count plus intersections for each non-redundant halfspace\n\
    +    FO   - options and precision constants\n\
    +    Fp   - dim, count, and intersection coordinates\n\
    +    FP   - nearest halfspace and distance for each redundant halfspace\n\
    +    FQ   - command used for qhalf\n\
    +    Fs   - summary: #int (8), dim, #halfspaces, #non-redundant, #intersections\n\
    +                      for output: #non-redundant, #intersections, #coplanar\n\
    +                                  halfspaces, #non-simplicial intersections\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    Fv   - count plus non-redundant halfspaces for each intersection\n\
    +    Fx   - non-redundant halfspaces\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview output (2-d, 3-d and 4-d; dual convex hull)\n\
    +    Ga   - all points (i.e., transformed halfspaces) as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices (i.e., non-redundant halfspaces) as spheres\n\
    +    Gi   - inner planes (i.e., halfspace intersections) only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets (i.e., intersections) by area\n\
    +    Pdk:n- drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n- drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qhalf- halfspace intersection about a point.  Qhull %s\n\
    +    input (stdin): [dim, 1, interior point], dim+1, n, coefficients+offset\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qhalf.htm):\n\
    +    Hn,n - specify coordinates of interior point\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and redundancy\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    Fp   - intersection coordinates\n\
    +    Fv   - non-redundant halfspaces incident to each intersection\n\
    +    Fx   - non-redundant halfspaces\n\
    +    o    - OFF file format (dual convex hull)\n\
    +    G    - Geomview output (dual convex hull)\n\
    +    m    - Mathematica output (dual convex hull)\n\
    +    QVn  - print intersections for halfspace n, -n if not\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox d | qconvex FQ n | qhalf s H0,0,0 Fp\n\
    +    rbox c | qconvex FQ FV n | qhalf s i\n\
    +    rbox c | qconvex FQ FV n | qhalf s o\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper_case options take an argument.\n\
    +\n\
    + incidences     Geomview       mathematica    OFF_format     point_dual\n\
    + summary        facet_dump\n\
    +\n\
    + Fc_redundant   Fd_cdd_in      FF_dump_xridge FIDs           Fmerges\n\
    + Fneighbors     FN_intersect   FOptions       Fp_coordinates FP_nearest\n\
    + FQhalf         Fsummary       Fv_halfspace   FMaple         Fx_non_redundant\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + Qbk:0Bk:0_drop Qcoplanar      QG_half_good   Qi_redundant   QJoggle\n\
    + Qsearch_1st    Qtriangulate   QVertex_good\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +        qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh NOerrexit = False;
    +    qh_option("Halfspace", NULL, NULL);
    +    qh HALFspace= True;    /* 'H'   */
    +    qh_checkflags(qh qhull_command, hidden_options);
    +    qh_initflags(qh qhull_command);
    +    if (qh SCALEinput) {
    +      fprintf(qh ferr, "\
    +qhull error: options 'Qbk:n' and 'QBk:n' are not used with qhalf.\n\
    +             Use 'Qbk:0Bk:0 to drop dimension k.\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option("Qxact_merge", NULL, NULL);
    +      qh MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh_ALL);
    +#else
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qhalf/qhalf.pro b/xs/src/qhull/src/qhalf/qhalf.pro
    new file mode 100644
    index 0000000000..ebad387893
    --- /dev/null
    +++ b/xs/src/qhull/src/qhalf/qhalf.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qhalf.pro -- Qt project file for qconvex.exe
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = qhalf
    +
    +SOURCES += qhalf.c
    diff --git a/xs/src/qhull/src/qhalf/qhalf_r.c b/xs/src/qhull/src/qhalf/qhalf_r.c
    new file mode 100644
    index 0000000000..c49d777f95
    --- /dev/null
    +++ b/xs/src/qhull/src/qhalf/qhalf_r.c
    @@ -0,0 +1,318 @@
    +/*
      ---------------------------------
    +
    +   qhalf.c
    +     compute the intersection of halfspaces about a point
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qhalf.htm */
    +char hidden_options[]=" d n v Qbb QbB Qf Qg Qm Qr QR Qv Qx Qz TR E V Fa FA FC FD FS Ft FV Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qhalf- compute the intersection of halfspaces about a point\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    optional interior point: dimension, 1, coordinates\n\
    +    first lines: dimension+1 and number of halfspaces\n\
    +    other lines: halfspace coefficients followed by offset\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Hn,n - specify coordinates of interior point\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar halfspaces\n\
    +    Qi   - keep other redundant halfspaces\n\
    +\n\
    +Qhull control options:\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    Qs   - search all halfspaces for the initial simplex\n\
    +    QGn  - print intersection if visible to halfspace n, -n for not\n\
    +    QVn  - print intersections for halfspace n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and redundancy\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when halfspace n added to intersection\n\
    +    TMn  - turn on tracing at merge n\n\
    +    TWn  - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding halfspace n, -n for before (see TCn)\n\
    +    TCn  - stop qhull after building cone for halfspace n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Un   - max distance below plane for a new, coplanar halfspace\n\
    +    Wn   - min facet width for outside halfspace (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (dual convex hull)\n\
    +    i    - non-redundant halfspaces incident to each intersection\n\
    +    m    - Mathematica output (dual convex hull)\n\
    +    o    - OFF format (dual convex hull: dimension, points, and facets)\n\
    +    p    - vertex coordinates of dual convex hull (coplanars if 'Qc' or 'Qi')\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fc   - count plus redundant halfspaces for each intersection\n\
    +         -   Qc (default) for coplanar and Qi for other redundant\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    FI   - ID of each intersection\n\
    +    Fm   - merge count for each intersection (511 max)\n\
    +    FM   - Maple output (dual convex hull)\n\
    +    Fn   - count plus neighboring intersections for each intersection\n\
    +    FN   - count plus intersections for each non-redundant halfspace\n\
    +    FO   - options and precision constants\n\
    +    Fp   - dim, count, and intersection coordinates\n\
    +    FP   - nearest halfspace and distance for each redundant halfspace\n\
    +    FQ   - command used for qhalf\n\
    +    Fs   - summary: #int (8), dim, #halfspaces, #non-redundant, #intersections\n\
    +                      for output: #non-redundant, #intersections, #coplanar\n\
    +                                  halfspaces, #non-simplicial intersections\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    Fv   - count plus non-redundant halfspaces for each intersection\n\
    +    Fx   - non-redundant halfspaces\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview output (2-d, 3-d and 4-d; dual convex hull)\n\
    +    Ga   - all points (i.e., transformed halfspaces) as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices (i.e., non-redundant halfspaces) as spheres\n\
    +    Gi   - inner planes (i.e., halfspace intersections) only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets (i.e., intersections) by area\n\
    +    Pdk:n- drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n- drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qhalf- halfspace intersection about a point.  Qhull %s\n\
    +    input (stdin): [dim, 1, interior point], dim+1, n, coefficients+offset\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qhalf.htm):\n\
    +    Hn,n - specify coordinates of interior point\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and redundancy\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    Fp   - intersection coordinates\n\
    +    Fv   - non-redundant halfspaces incident to each intersection\n\
    +    Fx   - non-redundant halfspaces\n\
    +    o    - OFF file format (dual convex hull)\n\
    +    G    - Geomview output (dual convex hull)\n\
    +    m    - Mathematica output (dual convex hull)\n\
    +    QVn  - print intersections for halfspace n, -n if not\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox d | qconvex FQ n | qhalf s H0,0,0 Fp\n\
    +    rbox c | qconvex FQ FV n | qhalf s i\n\
    +    rbox c | qconvex FQ FV n | qhalf s o\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper_case options take an argument.\n\
    +\n\
    + incidences     Geomview       mathematica    OFF_format     point_dual\n\
    + summary        facet_dump\n\
    +\n\
    + Fc_redundant   Fd_cdd_in      FF_dump_xridge FIDs           Fmerges\n\
    + Fneighbors     FN_intersect   FOptions       Fp_coordinates FP_nearest\n\
    + FQhalf         Fsummary       Fv_halfspace   FMaple         Fx_non_redundant\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + Qbk:0Bk:0_drop Qcoplanar      QG_half_good   Qi_redundant   QJoggle\n\
    + Qsearch_1st    Qtriangulate   QVertex_good\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +        qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_option(qh, "Halfspace", NULL, NULL);
    +    qh->HALFspace= True;    /* 'H'   */
    +    qh_checkflags(qh, qh->qhull_command, hidden_options);
    +    qh_initflags(qh, qh->qhull_command);
    +    if (qh->SCALEinput) {
    +      fprintf(qh->ferr, "\
    +qhull error: options 'Qbk:n' and 'QBk:n' are not used with qhalf.\n\
    +             Use 'Qbk:0Bk:0 to drop dimension k.\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option(qh, "Qxact_merge", NULL, NULL);
    +      qh->MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qhull-all.pro b/xs/src/qhull/src/qhull-all.pro
    new file mode 100644
    index 0000000000..1d3a0ac6f3
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-all.pro
    @@ -0,0 +1,94 @@
    +# -------------------------------------------------
    +# qhull-all.pro -- Qt project to build executables and static libraries
    +#
    +# To build with Qt on mingw
    +#   Download Qt SDK, install Perl
    +#   /c/qt/2010.05/qt> ./configure -static -platform win32-g++ -fast -no-qt3support
    +#
    +# To build DevStudio sln and proj files (Qhull ships with cmake derived files)
    +# qmake is in Qt's bin directory
    +# mkdir -p build && cd build && qmake -tp vc -r ../src/qhull-all.pro
    +# Additional Library Directories -- C:\qt\Qt5.2.0\5.2.0\msvc2012_64\lib
    +# libqhullcpp and libqhullstatic refered to $(QTDIR) but apparently didn't retrieve (should be %QTDIR%?)
    +# libqhull_r also needs ..\..\lib
    +# Need to change build/x64/Debug/*.lib to lib/ (or copy libs by hand, each time)
    +# Additional Build Dependencies
    +# See README.txt -- Need to add Build Dependencies, disable rtti, rename targets to qhull.dll, qhull6_p.dll and qhull6_pd.dll
    +# -------------------------------------------------
    +
    +TEMPLATE = subdirs
    +CONFIG += ordered
    +
    +SUBDIRS += libqhull_r      #shared library with reentrant code
    +SUBDIRS += libqhullstatic  #static library
    +SUBDIRS += libqhullstatic_r #static library with reentrant code
    +SUBDIRS += libqhullcpp     #static library for C++ interface with libqhullstatic_r
    +
    +SUBDIRS += qhull           #qhull program linked to libqhullstatic_r
    +SUBDIRS += rbox         
    +SUBDIRS += qconvex         #qhull programs linked to libqhullstatic
    +SUBDIRS += qdelaunay
    +SUBDIRS += qhalf
    +SUBDIRS += qvoronoi
    +
    +SUBDIRS += user_eg         #user programs linked to libqhull_r
    +SUBDIRS += user_eg2  
    +SUBDIRS += user_eg3        #user program with libqhullcpp and libqhullstatic_r
    +
    +SUBDIRS += qhulltest       #C++ test program with Qt, libqhullcpp, and libqhullstatic_r
    +SUBDIRS += testqset        #test program for qset.c with mem.c
    +SUBDIRS += testqset_r      #test program for qset_r.c with mem_r.c
    +                           #See eg/q_test for qhull tests
    +
    +OTHER_FILES += Changes.txt
    +OTHER_FILES += CMakeLists.txt
    +OTHER_FILES += Make-config.sh
    +OTHER_FILES += ../Announce.txt
    +OTHER_FILES += ../CMakeLists.txt
    +OTHER_FILES += ../COPYING.txt
    +OTHER_FILES += ../File_id.diz
    +OTHER_FILES += ../index.htm
    +OTHER_FILES += ../Makefile
    +OTHER_FILES += ../README.txt
    +OTHER_FILES += ../REGISTER.txt
    +OTHER_FILES += ../eg/q_eg
    +OTHER_FILES += ../eg/q_egtest
    +OTHER_FILES += ../eg/q_test
    +OTHER_FILES += ../html/index.htm
    +OTHER_FILES += ../html/qconvex.htm
    +OTHER_FILES += ../html/qdelau_f.htm
    +OTHER_FILES += ../html/qdelaun.htm
    +OTHER_FILES += ../html/qhalf.htm
    +OTHER_FILES += ../html/qh-code.htm
    +OTHER_FILES += ../html/qh-eg.htm
    +OTHER_FILES += ../html/qh-faq.htm
    +OTHER_FILES += ../html/qh-get.htm
    +OTHER_FILES += ../html/qh-impre.htm
    +OTHER_FILES += ../html/qh-optc.htm
    +OTHER_FILES += ../html/qh-optf.htm
    +OTHER_FILES += ../html/qh-optg.htm
    +OTHER_FILES += ../html/qh-opto.htm
    +OTHER_FILES += ../html/qh-optp.htm
    +OTHER_FILES += ../html/qh-optq.htm
    +OTHER_FILES += ../html/qh-optt.htm
    +OTHER_FILES += ../html/qh-quick.htm
    +OTHER_FILES += ../html/qhull.htm
    +OTHER_FILES += ../html/qhull.man
    +OTHER_FILES += ../html/qhull.txt
    +OTHER_FILES += ../html/qhull-cpp.xml
    +OTHER_FILES += ../html/qvoron_f.htm
    +OTHER_FILES += ../html/qvoronoi.htm
    +OTHER_FILES += ../html/rbox.htm
    +OTHER_FILES += ../html/rbox.man
    +OTHER_FILES += ../html/rbox.txt
    +OTHER_FILES += ../src/libqhull/Makefile
    +OTHER_FILES += ../src/libqhull_r/Makefile
    +OTHER_FILES += ../src/libqhull_r/qhull_r-exports.def
    +OTHER_FILES += ../src/qconvex/qconvex_r.c
    +OTHER_FILES += ../src/qdelaunay/qdelaun_r.c
    +OTHER_FILES += ../src/qhalf/qhalf_r.c
    +OTHER_FILES += ../src/qhull/rbox_r.c
    +OTHER_FILES += ../src/qvoronoi/qvoronoi_r.c
    +OTHER_FILES += ../src/qhull/unix.c
    +OTHER_FILES += ../src/user_eg/user_eg.c
    +OTHER_FILES += ../src/user_eg2/user_eg2.c
    diff --git a/xs/src/qhull/src/qhull-app-c.pri b/xs/src/qhull/src/qhull-app-c.pri
    new file mode 100644
    index 0000000000..05e5a00f28
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-c.pri
    @@ -0,0 +1,24 @@
    +# -------------------------------------------------
    +# qhull-app-c.pri -- Qt include project for C qhull applications linked to libqhull
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhullstatic_d
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhullstatic
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +INCLUDEPATH += ..
    +CONFIG += qhull_warn_conversion
    +
    diff --git a/xs/src/qhull/src/qhull-app-c_r.pri b/xs/src/qhull/src/qhull-app-c_r.pri
    new file mode 100644
    index 0000000000..9c2ef5600b
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-c_r.pri
    @@ -0,0 +1,26 @@
    +# -------------------------------------------------
    +# qhull-app-c_r.pri -- Qt include project for C qhull applications linked to qhullstatic_r
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhullstatic_rd
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhullstatic_r
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +INCLUDEPATH += ..
    +CONFIG += qhull_warn_conversion
    +
    diff --git a/xs/src/qhull/src/qhull-app-cpp.pri b/xs/src/qhull/src/qhull-app-cpp.pri
    new file mode 100644
    index 0000000000..a6f17d8ec4
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-cpp.pri
    @@ -0,0 +1,23 @@
    +# -------------------------------------------------
    +# qhull-app-cpp.pri -- Qt include project for qhull as C++ classes
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= rtti
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhullcpp_d
    +   LIBS += -lqhullstatic_rd  # Must be last, otherwise qh_fprintf,etc. are loaded from here instead of qhullcpp-d.lib
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhullcpp
    +   LIBS += -lqhullstatic_r  # Must be last, otherwise qh_fprintf,etc. are loaded from here instead of qhullcpp.lib
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +INCLUDEPATH += ../../src # "libqhull_r/qhull_a.h"
    diff --git a/xs/src/qhull/src/qhull-app-shared.pri b/xs/src/qhull/src/qhull-app-shared.pri
    new file mode 100644
    index 0000000000..1f4026a6aa
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-shared.pri
    @@ -0,0 +1,27 @@
    +# -------------------------------------------------
    +# qhull-app-shared.pri -- Deprecated Qt include project for C qhull applications linked with libqhull (shared library)
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhull_d
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhull
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +win32-msvc* : DEFINES += qh_dllimport # libqhull/user.h
    +
    +INCLUDEPATH += ../libqhull
    +CONFIG += qhull_warn_conversion
    +
    +
    diff --git a/xs/src/qhull/src/qhull-app-shared_r.pri b/xs/src/qhull/src/qhull-app-shared_r.pri
    new file mode 100644
    index 0000000000..e55c1a65f8
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-shared_r.pri
    @@ -0,0 +1,29 @@
    +# -------------------------------------------------
    +# qhull-app-shared_r.pri -- Qt include project for C qhull applications linked with libqhull_r (shared library)
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhull_rd
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhull_r
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +win32-msvc* : DEFINES += qh_dllimport # libqhull_r/user.h
    +
    +INCLUDEPATH += ..
    +CONFIG += qhull_warn_conversion
    +
    +
    diff --git a/xs/src/qhull/src/qhull-libqhull-src.pri b/xs/src/qhull/src/qhull-libqhull-src.pri
    new file mode 100644
    index 0000000000..e7aff3f781
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-libqhull-src.pri
    @@ -0,0 +1,39 @@
    +# -------------------------------------------------
    +# qhull-libqhull-src.pri -- Qt include project for libqhull sources and headers
    +#   libqhull.pro, libqhullp.pro, and libqhulldll.pro are the same for SOURCES and HEADERS
    +# -------------------------------------------------
    +
    +# Order object files by frequency of execution.  Small files at end.
    +# Current directory is caller
    +
    +# libqhull/libqhull.pro and ../qhull-libqhull-src.pri have the same SOURCES and HEADERS
    +SOURCES += ../libqhull/global.c
    +SOURCES += ../libqhull/stat.c
    +SOURCES += ../libqhull/geom2.c
    +SOURCES += ../libqhull/poly2.c
    +SOURCES += ../libqhull/merge.c
    +SOURCES += ../libqhull/libqhull.c
    +SOURCES += ../libqhull/geom.c
    +SOURCES += ../libqhull/poly.c
    +SOURCES += ../libqhull/qset.c
    +SOURCES += ../libqhull/mem.c
    +SOURCES += ../libqhull/random.c
    +SOURCES += ../libqhull/usermem.c
    +SOURCES += ../libqhull/userprintf.c
    +SOURCES += ../libqhull/io.c
    +SOURCES += ../libqhull/user.c
    +SOURCES += ../libqhull/rboxlib.c
    +SOURCES += ../libqhull/userprintf_rbox.c
    +
    +# [2014] qmake locates the headers in the shadow build directory not the src directory
    +HEADERS += ../libqhull/geom.h
    +HEADERS += ../libqhull/io.h
    +HEADERS += ../libqhull/libqhull.h
    +HEADERS += ../libqhull/mem.h
    +HEADERS += ../libqhull/merge.h
    +HEADERS += ../libqhull/poly.h
    +HEADERS += ../libqhull/random.h
    +HEADERS += ../libqhull/qhull_a.h
    +HEADERS += ../libqhull/qset.h
    +HEADERS += ../libqhull/stat.h
    +HEADERS += ../libqhull/user.h
    diff --git a/xs/src/qhull/src/qhull-libqhull-src_r.pri b/xs/src/qhull/src/qhull-libqhull-src_r.pri
    new file mode 100644
    index 0000000000..3b53291b1b
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-libqhull-src_r.pri
    @@ -0,0 +1,39 @@
    +# -------------------------------------------------
    +# qhull-libqhull-src_r.pri -- Qt include project for libqhull_r sources and headers
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +# Order object files by frequency of execution.  Small files at end.
    +# Current directory is caller
    +
    +# libqhull_r/libqhull_r.pro and ../qhull-libqhull-src_r.pri have the same SOURCES and HEADERS
    +SOURCES += ../libqhull_r/global_r.c
    +SOURCES += ../libqhull_r/stat_r.c
    +SOURCES += ../libqhull_r/geom2_r.c
    +SOURCES += ../libqhull_r/poly2_r.c
    +SOURCES += ../libqhull_r/merge_r.c
    +SOURCES += ../libqhull_r/libqhull_r.c
    +SOURCES += ../libqhull_r/geom_r.c
    +SOURCES += ../libqhull_r/poly_r.c
    +SOURCES += ../libqhull_r/qset_r.c
    +SOURCES += ../libqhull_r/mem_r.c
    +SOURCES += ../libqhull_r/random_r.c
    +SOURCES += ../libqhull_r/usermem_r.c
    +SOURCES += ../libqhull_r/userprintf_r.c
    +SOURCES += ../libqhull_r/io_r.c
    +SOURCES += ../libqhull_r/user_r.c
    +SOURCES += ../libqhull_r/rboxlib_r.c
    +SOURCES += ../libqhull_r/userprintf_rbox_r.c
    +
    +HEADERS += ../libqhull_r/geom_r.h
    +HEADERS += ../libqhull_r/io_r.h
    +HEADERS += ../libqhull_r/libqhull_r.h
    +HEADERS += ../libqhull_r/mem_r.h
    +HEADERS += ../libqhull_r/merge_r.h
    +HEADERS += ../libqhull_r/poly_r.h
    +HEADERS += ../libqhull_r/random_r.h
    +HEADERS += ../libqhull_r/qhull_ra.h
    +HEADERS += ../libqhull_r/qset_r.h
    +HEADERS += ../libqhull_r/stat_r.h
    +HEADERS += ../libqhull_r/user_r.h
    diff --git a/xs/src/qhull/src/qhull-warn.pri b/xs/src/qhull/src/qhull-warn.pri
    new file mode 100644
    index 0000000000..7d0e7fa2f4
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-warn.pri
    @@ -0,0 +1,57 @@
    +# -------------------------------------------------
    +# qhull-warn.pri -- Qt project warnings for warn_on
    +#   CONFIG += qhull_warn_all        # Qhull compiles with all warnings except for qhull_warn_shadow and qhull_warn_conversion
    +#   CONFIG += qhull_warn_conversion # Warn in Qt and Qhull about conversion errors
    +#   CONFIG += qhull_warn_error      # Turn warnings into errors
    +#   CONFIG += qhull_warn_shadow     # Warn in Qt about shadowing of functions and fields
    +# -------------------------------------------------
    +
    +# [apr'11] VERSION works erratically for msvc builds
    +# VERSION = 7.2.0
    +qhull_SOVERSION = 7
    +
    +# Uncomment to report warnings as errors
    +#CONFIG += qhull_warn_error
    +
    +*g++{
    +    qhull_warn_error{
    +        QMAKE_CFLAGS_WARN_ON += -Werror
    +        QMAKE_CXXFLAGS_WARN_ON += -Werror
    +    }
    +
    +    QMAKE_CFLAGS_WARN_ON += -Wcast-qual -Wextra -Wshadow -Wwrite-strings
    +
    +    QMAKE_CXXFLAGS_WARN_ON += -Wcast-qual -Wextra -Wwrite-strings
    +    QMAKE_CXXFLAGS_WARN_ON += -Wno-sign-conversion
    +
    +    qhull_warn_shadow{
    +        QMAKE_CXXFLAGS_WARN_ON += -Wshadow     # Shadowing occurs in Qt, e.g., nested foreach
    +    }
    +
    +    qhull_warn_conversion{
    +        QMAKE_CFLAGS_WARN_ON += -Wno-sign-conversion   # libqhullstatic has many size_t vs. int warnings
    +        QMAKE_CFLAGS_WARN_ON += -Wconversion           # libqhullstatic has no workaround for bit-field conversions
    +        QMAKE_CXXFLAGS_WARN_ON += -Wconversion         # Qt has conversion errors for qbitarray and qpalette
    +    }
    +
    +    qhull_warn_all{
    +        QMAKE_CFLAGS_WARN_ON += -Waddress -Warray-bounds -Wchar-subscripts -Wclobbered -Wcomment -Wempty-body
    +        QMAKE_CFLAGS_WARN_ON += -Wformat -Wignored-qualifiers -Wimplicit-function-declaration -Wimplicit-int
    +        QMAKE_CFLAGS_WARN_ON += -Wmain -Wmissing-braces -Wmissing-field-initializers -Wmissing-parameter-type
    +        QMAKE_CFLAGS_WARN_ON += -Wnonnull -Wold-style-declaration -Woverride-init -Wparentheses
    +        QMAKE_CFLAGS_WARN_ON += -Wpointer-sign -Wreturn-type -Wsequence-point -Wsign-compare
    +        QMAKE_CFLAGS_WARN_ON += -Wsign-compare -Wstrict-aliasing -Wstrict-overflow=1 -Wswitch
    +        QMAKE_CFLAGS_WARN_ON += -Wtrigraphs -Wtype-limits -Wuninitialized -Wuninitialized
    +        QMAKE_CFLAGS_WARN_ON += -Wunknown-pragmas -Wunused-function -Wunused-label -Wunused-parameter
    +        QMAKE_CFLAGS_WARN_ON += -Wunused-value -Wunused-variable -Wvolatile-register-var
    +
    +        QMAKE_CXXFLAGS_WARN_ON += -Waddress -Warray-bounds -Wc++0x-compat -Wchar-subscripts
    +        QMAKE_CXXFLAGS_WARN_ON += -Wclobbered -Wcomment -Wempty-body -Wenum-compare
    +        QMAKE_CXXFLAGS_WARN_ON += -Wformat -Wignored-qualifiers -Wmain -Wmissing-braces
    +        QMAKE_CXXFLAGS_WARN_ON += -Wmissing-field-initializers -Wparentheses -Wreorder -Wreturn-type
    +        QMAKE_CXXFLAGS_WARN_ON += -Wsequence-point -Wsign-compare -Wsign-compare -Wstrict-aliasing
    +        QMAKE_CXXFLAGS_WARN_ON += -Wstrict-overflow=1 -Wswitch -Wtrigraphs -Wtype-limits
    +        QMAKE_CXXFLAGS_WARN_ON += -Wuninitialized -Wunknown-pragmas -Wunused-function -Wunused-label
    +        QMAKE_CXXFLAGS_WARN_ON += -Wunused-parameter -Wunused-value -Wunused-variable -Wvolatile-register-var
    +    }
    +}
    diff --git a/xs/src/qhull/src/qhull/qhull.pro b/xs/src/qhull/src/qhull/qhull.pro
    new file mode 100644
    index 0000000000..8393728567
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull/qhull.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qhull.pro -- Qt project file for qhull.exe with libqhullstatic_r
    +# -------------------------------------------------
    +
    +include(../qhull-app-c_r.pri)
    +
    +TARGET = qhull
    +
    +SOURCES += unix_r.c
    diff --git a/xs/src/qhull/src/qhull/unix.c b/xs/src/qhull/src/qhull/unix.c
    new file mode 100644
    index 0000000000..892a819c31
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull/unix.c
    @@ -0,0 +1,372 @@
    +/*
      ---------------------------------
    +
    +   unix.c
    +     command line interface to qhull
    +         includes SIOUX interface for Macintoshes
    +
    +   see qh-qhull.htm
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/qhull/unix.c#4 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +*/
    +
    +#include "libqhull/libqhull.h"
    +#include "libqhull/qset.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  see:
    +    concise prompt below
    +*/
    +char qh_prompta[]= "\n\
    +qhull- compute convex hulls and related structures.\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +    halfspaces:  use dim plus one and put offset after coefficients.\n\
    +                 May be preceded by a single interior point ('H').\n\
    +\n\
    +options:\n\
    +    d    - Delaunay triangulation by lifting points to a paraboloid\n\
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)\n\
    +    v    - Voronoi diagram (dual of the Delaunay triangulation)\n\
    +    v Qu - furthest-site Voronoi diagram\n\
    +    Hn,n,... - halfspace intersection about point [n,n,0,...]\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar points with nearest facet\n\
    +    Qi   - keep interior points with nearest facet\n\
    +\n\
    +Qhull control options:\n\
    +    Qbk:n   - scale coord k so that low bound is n\n\
    +      QBk:n - scale coord k so that upper bound is n (QBk is %2.2g)\n\
    +    QbB  - scale input to unit cube centered at the origin\n\
    +    Qbb  - scale last coordinate to [0,m] for Delaunay triangulations\n\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qf   - partition point to furthest outside facet\n\
    +    Qg   - only build good facets (needs 'QGn', 'QVn', or 'PdD')\n\
    +    Qm   - only process points that would increase max_outside\n\
    +    Qr   - process random outside points instead of furthest ones\n\
    +    Qs   - search all points for the initial simplex\n\
    +    Qu   - for 'd' or 'v', compute upper hull without point at-infinity\n\
    +              returns furthest-site Delaunay triangulation\n\
    +    Qv   - test vertex neighbors for convexity\n\
    +    Qx   - exact pre-merges (skips coplanar and angle-coplanar facets)\n\
    +    Qz   - add point-at-infinity to Delaunay triangulation\n\
    +    QGn  - good facet if visible from point n, -n for not visible\n\
    +    QVn  - good facet if it includes point n, -n if not\n\
    +    Q0   - turn off default premerge with 'C-0'/'Qx'\n\
    +    Q1     - sort merges by type instead of angle\n\
    +    Q2   - merge all non-convex at once instead of independent sets\n\
    +    Q3   - do not merge redundant vertices\n\
    +    Q4   - avoid old->new merges\n\
    +    Q5   - do not correct outer planes at end of qhull\n\
    +    Q6   - do not pre-merge concave or coplanar facets\n\
    +    Q7   - depth-first processing instead of breadth-first\n\
    +    Q8   - do not process near-inside points\n\
    +    Q9   - process furthest of furthest points\n\
    +    Q10  - no special processing for narrow distributions\n\
    +    Q11  - copy normals and recompute centrums for tricoplanar facets\n\
    +    Q12  - no error on wide merge due to duplicate ridge\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Topts- Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Ta   - annotate output with message codes\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TRn  - rerun qhull n times.  Use with 'QJn'\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    En   - max roundoff error for distance computation\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Vn   - min distance above plane for a visible facet (default 3C-n or En)\n\
    +    Un   - max distance below plane for a new, coplanar point (default Vn)\n\
    +    Wn   - min facet width for outside point (before roundoff, default 2Vn)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each facet\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    o    - OFF format (dim, points and facets; Voronoi regions)\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates or Voronoi vertices (coplanar points if 'Qc')\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each facet\n\
    +    FA   - compute total area and volume for option 's'\n\
    +    Fc   - count plus coplanar points for each facet\n\
    +           use 'Qc' (default) for coplanar and 'Qi' for interior\n\
    +    FC   - centrum or Voronoi center for each facet\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - inner plane for each facet\n\
    +           for 'v', separating hyperplanes for bounded Voronoi regions\n\
    +    FI   - ID of each facet\n\
    +    Fm   - merge count for each facet (511 max)\n\
    +    FM   - Maple output (2-d and 3-d)\n\
    +    Fn   - count plus neighboring facets for each facet\n\
    +    FN   - count plus neighboring facets for each point\n\
    +    Fo   - outer plane (or max_outside) for each facet\n\
    +           for 'v', separating hyperplanes for unbounded Voronoi regions\n\
    +    FO   - options and precision constants\n\
    +    Fp   - dim, count, and intersection coordinates (halfspace only)\n\
    +    FP   - nearest vertex and distance for each coplanar point\n\
    +    FQ   - command used for qhull\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                      output: #vertices, #facets, #coplanars, #nonsimplicial\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0)\n\
    +                    #real (2) tot area, tot volume\n\
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)\n\
    +    Fv   - count plus vertices for each facet\n\
    +           for 'v', Voronoi diagram as Voronoi vertices for pairs of sites\n\
    +    FV   - average of vertices (a feasible point for 'H')\n\
    +    Fx   - extreme points (in order for 2-d)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d, 3-d, and 4-d; 2-d Voronoi)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +    Gt   - for 3-d 'd', transparent outer ridges\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qhull- compute convex hulls and related structures.  Qhull %s\n\
    +    input (stdin): dimension, n, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +    halfspace: use dim+1 and put offsets after coefficients\n\
    +\n\
    +options (qh-quick.htm):\n\
    +    d    - Delaunay triangulation by lifting points to a paraboloid\n\
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)\n\
    +    v    - Voronoi diagram as the dual of the Delaunay triangulation\n\
    +    v Qu - furthest-site Voronoi diagram\n\
    +    H1,1 - Halfspace intersection about [1,1,0,...] via polar duality\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of each option\n\
    +    -V   - version\n\
    +\n\
    +Output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each facet\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates (if 'Qc', includes coplanar points)\n\
    +           if 'v', Voronoi vertices\n\
    +    Fp   - halfspace intersections\n\
    +    Fx   - extreme points (convex hull vertices)\n\
    +    FA   - compute total area and volume\n\
    +    o    - OFF format (if 'v', outputs Voronoi regions)\n\
    +    G    - Geomview output (2-d, 3-d and 4-d)\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    QVn  - print facets that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox D4 | qhull Tv                        rbox 1000 s | qhull Tv s FA\n\
    +    rbox 10 D2 | qhull d QJ s i TO result     rbox 10 D2 | qhull v Qbb Qt p\n\
    +    rbox 10 D2 | qhull d Qu QJ m              rbox 10 D2 | qhull v Qu QJ o\n\
    +    rbox c d D2 | qhull Qc s f Fx | more      rbox c | qhull FV n | qhull H Fp\n\
    +    rbox d D12 | qhull QR0 FA                 rbox c D7 | qhull FA TF1000\n\
    +    rbox y 1000 W0 | qhull                    rbox c | qhull n\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + delaunay       voronoi        Geomview       Halfspace      facet_dump\n\
    + incidences     mathematica    normals        OFF_format     points\n\
    + summary\n\
    +\n\
    + Farea          FArea-total    Fcoplanars     FCentrums      Fd-cdd-in\n\
    + FD-cdd-out     FF-dump-xridge Finner         FIDs           Fmerges\n\
    + Fneighbors     FNeigh-vertex  Fouter         FOptions       Fpoint-intersect\n\
    + FPoint_near    FQhull         Fsummary       FSize          Ftriangles\n\
    + Fvertices      Fvoronoi       FVertex-ave    Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    + Gtransparent\n\
    +\n\
    + PArea-keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge-keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QbBound 0:0.5  Qbk:0Bk:0_drop QbB-scale-box  Qbb-scale-last Qcoplanar\n\
    + Qfurthest      Qgood_only     QGood_point    Qinterior      Qmax_out\n\
    + QJoggle        Qrandom        QRotate        Qsearch_1st    Qtriangulate\n\
    + QupperDelaunay QVertex_good   Qvneighbors    Qxact_merge    Qzinfinite\n\
    +\n\
    + Q0_no_premerge Q1_no_angle    Q2_no_independ Q3_no_redundant Q4_no_old\n\
    + Q5_no_check_out Q6_no_concave Q7_depth_first Q8_no_near_in  Q9_pick_furthest\n\
    + Q10_no_narrow  Q11_trinormals Q12_no_wide_dup\n\
    +\n\
    + T4_trace       Tannotate      Tcheck_often   Tstatistics    Tverify\n\
    + Tz_stdout      TFacet_log     TInput_file    TPoint_trace   TMerge_trace\n\
    + TOutput_file   TRerun         TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Error_round    Random_dist    Visible_min\n\
    + Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version, qh_DEFAULTbox,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh_initflags(qh qhull_command);
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull( qh_ALL);
    +#else
    +  qh_freeqhull( !qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qhull/unix_r.c b/xs/src/qhull/src/qhull/unix_r.c
    new file mode 100644
    index 0000000000..3f999f7fa9
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull/unix_r.c
    @@ -0,0 +1,374 @@
    +/*
      ---------------------------------
    +
    +   unix.c
    +     command line interface to qhull
    +         includes SIOUX interface for Macintoshes
    +
    +   see qh-qhull.htm
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/qhull/unix_r.c#6 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  see:
    +    concise prompt below
    +*/
    +char qh_prompta[]= "\n\
    +qhull- compute convex hulls and related structures.\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +    halfspaces:  use dim plus one and put offset after coefficients.\n\
    +                 May be preceded by a single interior point ('H').\n\
    +\n\
    +options:\n\
    +    d    - Delaunay triangulation by lifting points to a paraboloid\n\
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)\n\
    +    v    - Voronoi diagram (dual of the Delaunay triangulation)\n\
    +    v Qu - furthest-site Voronoi diagram\n\
    +    Hn,n,... - halfspace intersection about point [n,n,0,...]\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar points with nearest facet\n\
    +    Qi   - keep interior points with nearest facet\n\
    +\n\
    +Qhull control options:\n\
    +    Qbk:n   - scale coord k so that low bound is n\n\
    +      QBk:n - scale coord k so that upper bound is n (QBk is %2.2g)\n\
    +    QbB  - scale input to unit cube centered at the origin\n\
    +    Qbb  - scale last coordinate to [0,m] for Delaunay triangulations\n\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qf   - partition point to furthest outside facet\n\
    +    Qg   - only build good facets (needs 'QGn', 'QVn', or 'PdD')\n\
    +    Qm   - only process points that would increase max_outside\n\
    +    Qr   - process random outside points instead of furthest ones\n\
    +    Qs   - search all points for the initial simplex\n\
    +    Qu   - for 'd' or 'v', compute upper hull without point at-infinity\n\
    +              returns furthest-site Delaunay triangulation\n\
    +    Qv   - test vertex neighbors for convexity\n\
    +    Qx   - exact pre-merges (skips coplanar and angle-coplanar facets)\n\
    +    Qz   - add point-at-infinity to Delaunay triangulation\n\
    +    QGn  - good facet if visible from point n, -n for not visible\n\
    +    QVn  - good facet if it includes point n, -n if not\n\
    +    Q0   - turn off default premerge with 'C-0'/'Qx'\n\
    +    Q1     - sort merges by type instead of angle\n\
    +    Q2   - merge all non-convex at once instead of independent sets\n\
    +    Q3   - do not merge redundant vertices\n\
    +    Q4   - avoid old->new merges\n\
    +    Q5   - do not correct outer planes at end of qhull\n\
    +    Q6   - do not pre-merge concave or coplanar facets\n\
    +    Q7   - depth-first processing instead of breadth-first\n\
    +    Q8   - do not process near-inside points\n\
    +    Q9   - process furthest of furthest points\n\
    +    Q10  - no special processing for narrow distributions\n\
    +    Q11  - copy normals and recompute centrums for tricoplanar facets\n\
    +    Q12  - no error on wide merge due to duplicate ridge\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Topts- Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Ta   - annotate output with message codes\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TRn  - rerun qhull n times.  Use with 'QJn'\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    En   - max roundoff error for distance computation\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Vn   - min distance above plane for a visible facet (default 3C-n or En)\n\
    +    Un   - max distance below plane for a new, coplanar point (default Vn)\n\
    +    Wn   - min facet width for outside point (before roundoff, default 2Vn)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each facet\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    o    - OFF format (dim, points and facets; Voronoi regions)\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates or Voronoi vertices (coplanar points if 'Qc')\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each facet\n\
    +    FA   - compute total area and volume for option 's'\n\
    +    Fc   - count plus coplanar points for each facet\n\
    +           use 'Qc' (default) for coplanar and 'Qi' for interior\n\
    +    FC   - centrum or Voronoi center for each facet\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - inner plane for each facet\n\
    +           for 'v', separating hyperplanes for bounded Voronoi regions\n\
    +    FI   - ID of each facet\n\
    +    Fm   - merge count for each facet (511 max)\n\
    +    FM   - Maple output (2-d and 3-d)\n\
    +    Fn   - count plus neighboring facets for each facet\n\
    +    FN   - count plus neighboring facets for each point\n\
    +    Fo   - outer plane (or max_outside) for each facet\n\
    +           for 'v', separating hyperplanes for unbounded Voronoi regions\n\
    +    FO   - options and precision constants\n\
    +    Fp   - dim, count, and intersection coordinates (halfspace only)\n\
    +    FP   - nearest vertex and distance for each coplanar point\n\
    +    FQ   - command used for qhull\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                      output: #vertices, #facets, #coplanars, #nonsimplicial\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0)\n\
    +                    #real (2) tot area, tot volume\n\
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)\n\
    +    Fv   - count plus vertices for each facet\n\
    +           for 'v', Voronoi diagram as Voronoi vertices for pairs of sites\n\
    +    FV   - average of vertices (a feasible point for 'H')\n\
    +    Fx   - extreme points (in order for 2-d)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d, 3-d, and 4-d; 2-d Voronoi)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +    Gt   - for 3-d 'd', transparent outer ridges\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qhull- compute convex hulls and related structures.  Qhull %s\n\
    +    input (stdin): dimension, n, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +    halfspace: use dim+1 and put offsets after coefficients\n\
    +\n\
    +options (qh-quick.htm):\n\
    +    d    - Delaunay triangulation by lifting points to a paraboloid\n\
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)\n\
    +    v    - Voronoi diagram as the dual of the Delaunay triangulation\n\
    +    v Qu - furthest-site Voronoi diagram\n\
    +    H1,1 - Halfspace intersection about [1,1,0,...] via polar duality\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of each option\n\
    +    -V   - version\n\
    +\n\
    +Output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each facet\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates (if 'Qc', includes coplanar points)\n\
    +           if 'v', Voronoi vertices\n\
    +    Fp   - halfspace intersections\n\
    +    Fx   - extreme points (convex hull vertices)\n\
    +    FA   - compute total area and volume\n\
    +    o    - OFF format (if 'v', outputs Voronoi regions)\n\
    +    G    - Geomview output (2-d, 3-d and 4-d)\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    QVn  - print facets that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox D4 | qhull Tv                        rbox 1000 s | qhull Tv s FA\n\
    +    rbox 10 D2 | qhull d QJ s i TO result     rbox 10 D2 | qhull v Qbb Qt p\n\
    +    rbox 10 D2 | qhull d Qu QJ m              rbox 10 D2 | qhull v Qu QJ o\n\
    +    rbox c d D2 | qhull Qc s f Fx | more      rbox c | qhull FV n | qhull H Fp\n\
    +    rbox d D12 | qhull QR0 FA                 rbox c D7 | qhull FA TF1000\n\
    +    rbox y 1000 W0 | qhull                    rbox c | qhull n\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + delaunay       voronoi        Geomview       Halfspace      facet_dump\n\
    + incidences     mathematica    normals        OFF_format     points\n\
    + summary\n\
    +\n\
    + Farea          FArea-total    Fcoplanars     FCentrums      Fd-cdd-in\n\
    + FD-cdd-out     FF-dump-xridge Finner         FIDs           Fmerges\n\
    + Fneighbors     FNeigh-vertex  Fouter         FOptions       Fpoint-intersect\n\
    + FPoint_near    FQhull         Fsummary       FSize          Ftriangles\n\
    + Fvertices      Fvoronoi       FVertex-ave    Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    + Gtransparent\n\
    +\n\
    + PArea-keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge-keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QbBound 0:0.5  Qbk:0Bk:0_drop QbB-scale-box  Qbb-scale-last Qcoplanar\n\
    + Qfurthest      Qgood_only     QGood_point    Qinterior      Qmax_out\n\
    + QJoggle        Qrandom        QRotate        Qsearch_1st    Qtriangulate\n\
    + QupperDelaunay QVertex_good   Qvneighbors    Qxact_merge    Qzinfinite\n\
    +\n\
    + Q0_no_premerge Q1_no_angle    Q2_no_independ Q3_no_redundant Q4_no_old\n\
    + Q5_no_check_out Q6_no_concave Q7_depth_first Q8_no_near_in  Q9_pick_furthest\n\
    + Q10_no_narrow  Q11_trinormals Q12_no_wide_dup\n\
    +\n\
    + T4_trace       Tannotate      Tcheck_often   Tstatistics    Tverify\n\
    + Tz_stdout      TFacet_log     TInput_file    TPoint_trace   TMerge_trace\n\
    + TOutput_file   TRerun         TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Error_round    Random_dist    Visible_min\n\
    + Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version, qh_DEFAULTbox,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_initflags(qh, qh->qhull_command);
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qhulltest/Coordinates_test.cpp b/xs/src/qhull/src/qhulltest/Coordinates_test.cpp
    new file mode 100644
    index 0000000000..3e8658a5bd
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/Coordinates_test.cpp
    @@ -0,0 +1,539 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/Coordinates_test.cpp#2 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/Coordinates.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class Coordinates_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void t_construct();
    +    void t_convert();
    +    void t_element();
    +    void t_readonly();
    +    void t_operator();
    +    void t_const_iterator();
    +    void t_iterator();
    +    void t_coord_iterator();
    +    void t_mutable_coord_iterator();
    +    void t_readwrite();
    +    void t_search();
    +    void t_io();
    +};//Coordinates_test
    +
    +void
    +add_Coordinates_test()
    +{
    +    new Coordinates_test();  // RoadTest::s_testcases
    +}
    +
    +void Coordinates_test::
    +t_construct()
    +{
    +    Coordinates c;
    +    QCOMPARE(c.size(), 0U);
    +    QVERIFY(c.isEmpty());
    +    c << 1.0;
    +    QCOMPARE(c.count(), 1);
    +    Coordinates c2(c);
    +    c2 << 2.0;
    +    QCOMPARE(c2.count(), 2);
    +    Coordinates c3;
    +    c3 = c2;
    +    QCOMPARE(c3.count(), 2);
    +    QCOMPARE(c3[0]+c3[1], 3.0);
    +    QVERIFY(c2==c3);
    +    std::vector vc;
    +    vc.push_back(3.0);
    +    vc.push_back(4.0);
    +    Coordinates c4(vc);
    +    QCOMPARE(c4[0]+c4[1], 7.0);
    +    Coordinates c5(c3);
    +    QVERIFY(c5==c3);
    +    c5= vc;
    +    QVERIFY(c5!=c3);
    +    QVERIFY(c5==c4);
    +}//t_construct
    +
    +void Coordinates_test::
    +t_convert()
    +{
    +    Coordinates c;
    +    c << 1.0 << 3.0;
    +    QCOMPARE(c.data()[1], 3.0);
    +    coordT *c2= c.data();
    +    const coordT *c3= c.data();
    +    QCOMPARE(c2, c3);
    +    std::vector vc= c.toStdVector();
    +    QCOMPARE((size_t)vc.size(), c.size());
    +    for(int k= (int)vc.size(); k--; ){
    +        QCOMPARE(vc[k], c[k]);
    +    }
    +    QList qc= c.toQList();
    +    QCOMPARE(qc.count(), c.count());
    +    for(int k= qc.count(); k--; ){
    +        QCOMPARE(qc[k], c[k]);
    +    }
    +    Coordinates c4;
    +    c4= std::vector(2, 0.0);
    +    QCOMPARE(c4.back(), 0.0);
    +    Coordinates c5(std::vector(2, 0.0));
    +    QCOMPARE(c4.size(), c5.size());
    +    QVERIFY(c4==c5);
    +}//t_convert
    +
    +void Coordinates_test::
    +t_element()
    +{
    +    Coordinates c;
    +    c << 1.0 << -2.0;
    +    c.at(1)= -3;
    +    QCOMPARE(c.at(1), -3.0);
    +    QCOMPARE(c.back(), -3.0);
    +    QCOMPARE(c.front(), 1.0);
    +    c[1]= -2.0;
    +    QCOMPARE(c[1],-2.0);
    +    QCOMPARE(c.first(), 1.0);
    +    c.first()= 2.0;
    +    QCOMPARE(c.first(), 2.0);
    +    QCOMPARE(c.last(), -2.0);
    +    c.last()= 0.0;
    +    QCOMPARE(c.first()+c.last(), 2.0);
    +    coordT *c4= &c.first();
    +    const coordT *c5= &c.first();
    +    QCOMPARE(c4, c5);
    +    coordT *c6= &c.last();
    +    const coordT *c7= &c.last();
    +    QCOMPARE(c6, c7);
    +    Coordinates c2= c.mid(1);
    +    QCOMPARE(c2.count(), 1);
    +    c << 3.0;
    +    Coordinates c3= c.mid(1,1);
    +    QCOMPARE(c2, c3);
    +    QCOMPARE(c3.value(-1, -1.0), -1.0);
    +    QCOMPARE(c3.value(3, 4.0), 4.0);
    +    QCOMPARE(c.value(2, 4.0), 3.0);
    +}//t_element
    +
    +void Coordinates_test::
    +t_readonly()
    +{
    +    Coordinates c;
    +    QCOMPARE(c.size(), 0u);
    +    QCOMPARE(c.count(), 0);
    +    QVERIFY(c.isEmpty());
    +    c << 1.0 << -2.0;
    +    QCOMPARE(c.size(), 2u);
    +    QCOMPARE(c.count(), 2);
    +    QVERIFY(!c.isEmpty());
    +}//t_readonly
    +
    +void Coordinates_test::
    +t_operator()
    +{
    +    Coordinates c;
    +    Coordinates c2(c);
    +    QVERIFY(c==c2);
    +    QVERIFY(!(c!=c2));
    +    c << 1.0;
    +    QVERIFY(!(c==c2));
    +    QVERIFY(c!=c2);
    +    c2 << 1.0;
    +    QVERIFY(c==c2);
    +    QVERIFY(!(c!=c2));
    +    c[0]= 0.0;
    +    QVERIFY(c!=c2);
    +    Coordinates c3= c+c2;
    +    QCOMPARE(c3.count(), 2);
    +    QCOMPARE(c3[0], 0.0);
    +    QCOMPARE(c3[1], 1.0);
    +    c3 += c3;
    +    QCOMPARE(c3.count(), 4);
    +    QCOMPARE(c3[2], 0.0);
    +    QCOMPARE(c3[3], 1.0);
    +    c3 += c2;
    +    QCOMPARE(c3[4], 1.0);
    +    c3 += 5.0;
    +    QCOMPARE(c3.count(), 6);
    +    QCOMPARE(c3[5], 5.0);
    +    // << checked above
    +}//t_operator
    +
    +void Coordinates_test::
    +t_const_iterator()
    +{
    +    Coordinates c;
    +    QCOMPARE(c.begin(), c.end());
    +    // begin and end checked elsewhere
    +    c << 1.0 << 3.0;
    +    Coordinates::const_iterator i= c.begin();
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(i[1], 3.0);
    +    // i[1]= -3.0; // compiler error
    +    // operator-> is not applicable to double
    +    QCOMPARE(*i++, 1.0);
    +    QCOMPARE(*i, 3.0);
    +    QCOMPARE(*i--, 3.0);
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(*(i+1), 3.0);
    +    QCOMPARE(*++i, 3.0);
    +    QCOMPARE(*(i-1), 1.0);
    +    QCOMPARE(*--i, 1.0);
    +    QVERIFY(i==c.begin());
    +    QVERIFY(i==c.constBegin());
    +    QVERIFY(i!=c.end());
    +    QVERIFY(i!=c.constEnd());
    +    QVERIFY(i=c.begin());
    +    QVERIFY(i+1<=c.end());
    +    QVERIFY(i+1>c.begin());
    +    Coordinates::iterator i2= c.begin();
    +    Coordinates::const_iterator i3(i2);
    +    QCOMPARE(*i3, 1.0);
    +    QCOMPARE(i3[1], 3.0);
    +}//t_const_iterator
    +
    +void Coordinates_test::
    +t_iterator()
    +{
    +    Coordinates c;
    +    QCOMPARE(c.begin(), c.end());
    +    // begin and end checked elsewhere
    +    c << 1.0 << 3.0;
    +    Coordinates::iterator i= c.begin();
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(i[1], 3.0);
    +    *i= -1.0;
    +    QCOMPARE(*i, -1.0);
    +    i[1]= -3.0;
    +    QCOMPARE(i[1], -3.0);
    +    *i= 1.0;
    +    // operator-> is not applicable to double
    +    QCOMPARE(*i++, 1.0);
    +    QCOMPARE(*i, -3.0);
    +    *i= 3.0;
    +    QCOMPARE(*i--, 3.0);
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(*(i+1), 3.0);
    +    QCOMPARE(*++i, 3.0);
    +    QCOMPARE(*(i-1), 1.0);
    +    QCOMPARE(*--i, 1.0);
    +    QVERIFY(i==c.begin());
    +    QVERIFY(i==c.constBegin());
    +    QVERIFY(i!=c.end());
    +    QVERIFY(i!=c.constEnd());
    +    QVERIFY(i=c.begin());
    +    QVERIFY(i+1<=c.end());
    +    QVERIFY(i+1>c.begin());
    +}//t_iterator
    +
    +void Coordinates_test::
    +t_coord_iterator()
    +{
    +    Coordinates c;
    +    c << 1.0 << 3.0;
    +    CoordinatesIterator i(c);
    +    CoordinatesIterator i2= c;
    +    QVERIFY(i.findNext(1.0));
    +    QVERIFY(!i.findNext(2.0));
    +    QVERIFY(!i.findNext(3.0));
    +    QVERIFY(i.findPrevious(3.0));
    +    QVERIFY(!i.findPrevious(2.0));
    +    QVERIFY(!i.findPrevious(1.0));
    +    QVERIFY(i2.findNext(3.0));
    +    QVERIFY(i2.findPrevious(3.0));
    +    QVERIFY(i2.findNext(3.0));
    +    QVERIFY(i2.findPrevious(1.0));
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    i2.toFront();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(i.hasPrevious());
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    Coordinates c2;
    +    i2= c2;
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    i2.toBack();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekPrevious(), 3.0);
    +    QCOMPARE(i.previous(), 3.0);
    +    QCOMPARE(i.previous(), 1.0);
    +    QVERIFY(!i.hasPrevious());
    +    QCOMPARE(i.peekNext(), 1.0);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), 1.0);
    +    QCOMPARE(i.peekNext(), 3.0);
    +    QCOMPARE(i.next(), 3.0);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), 1.0);
    +}//t_coord_iterator
    +
    +void Coordinates_test::
    +t_mutable_coord_iterator()
    +{
    +    // Same tests as CoordinatesIterator
    +    Coordinates c;
    +    c << 1.0 << 3.0;
    +    MutableCoordinatesIterator i(c);
    +    MutableCoordinatesIterator i2= c;
    +    QVERIFY(i.findNext(1.0));
    +    QVERIFY(!i.findNext(2.0));
    +    QVERIFY(!i.findNext(3.0));
    +    QVERIFY(i.findPrevious(3.0));
    +    QVERIFY(!i.findPrevious(2.0));
    +    QVERIFY(!i.findPrevious(1.0));
    +    QVERIFY(i2.findNext(3.0));
    +    QVERIFY(i2.findPrevious(3.0));
    +    QVERIFY(i2.findNext(3.0));
    +    QVERIFY(i2.findPrevious(1.0));
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    i2.toFront();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(i.hasPrevious());
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    Coordinates c2;
    +    i2= c2;
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    i2.toBack();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekPrevious(), 3.0);
    +    QCOMPARE(i.peekPrevious(), 3.0);
    +    QCOMPARE(i.previous(), 3.0);
    +    QCOMPARE(i.previous(), 1.0);
    +    QVERIFY(!i.hasPrevious());
    +    QCOMPARE(i.peekNext(), 1.0);
    +    QCOMPARE(i.next(), 1.0);
    +    QCOMPARE(i.peekNext(), 3.0);
    +    QCOMPARE(i.next(), 3.0);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), 1.0);
    +
    +    // Mutable tests
    +    i.toFront();
    +    i.peekNext()= -1.0;
    +    QCOMPARE(i.peekNext(), -1.0);
    +    QCOMPARE((i.next()= 1.0), 1.0);
    +    QCOMPARE(i.peekPrevious(), 1.0);
    +    i.remove();
    +    QCOMPARE(c.count(), 1);
    +    i.remove();
    +    QCOMPARE(c.count(), 1);
    +    QCOMPARE(i.peekNext(), 3.0);
    +    i.insert(1.0);
    +    i.insert(2.0);
    +    QCOMPARE(c.count(), 3);
    +    QCOMPARE(i.peekNext(), 3.0);
    +    QCOMPARE(i.peekPrevious(), 2.0);
    +    i.peekPrevious()= -2.0;
    +    QCOMPARE(i.peekPrevious(), -2.0);
    +    QCOMPARE((i.previous()= 2.0), 2.0);
    +    QCOMPARE(i.peekNext(), 2.0);
    +    i.toBack();
    +    i.remove();
    +    QCOMPARE(c.count(), 3); // unchanged
    +    i.toFront();
    +    i.remove();
    +    QCOMPARE(c.count(), 3); // unchanged
    +    QCOMPARE(i.peekNext(), 1.0);
    +    i.remove();
    +    QCOMPARE(c.count(), 3); // unchanged
    +    i.insert(0.0);
    +    QCOMPARE(c.count(), 4);
    +    QCOMPARE(i.value(), 0.0);
    +    QCOMPARE(i.peekPrevious(), 0.0);
    +    i.setValue(-10.0);
    +    QCOMPARE(c.count(), 4); // unchanged
    +    QCOMPARE(i.peekNext(), 1.0);
    +    QCOMPARE(i.peekPrevious(), -10.0);
    +    i.findNext(1.0);
    +    i.setValue(-1.0);
    +    QCOMPARE(i.peekPrevious(), -1.0);
    +    i.setValue(1.0);
    +    QCOMPARE(i.peekPrevious(), 1.0);
    +    QCOMPARE(i.value(), 1.0);
    +    i.findPrevious(1.0);
    +    i.setValue(-1.0);
    +    QCOMPARE(i.peekNext(), -1.0);
    +    i.toBack();
    +    QCOMPARE(i.previous(), 3.0);
    +    i.setValue(-3.0);
    +    QCOMPARE(i.peekNext(), -3.0);
    +    double d= i.value();
    +    QCOMPARE(d, -3.0);
    +    QCOMPARE(i.previous(), 2.0);
    +}//t_mutable_coord_iterator
    +
    +void Coordinates_test::
    +t_readwrite()
    +{
    +    Coordinates c;
    +    c.clear();
    +    QCOMPARE(c.count(), 0);
    +    c << 1.0 << 3.0;
    +    c.clear();
    +    QCOMPARE(c.count(), 0);
    +    coordT c2[4]= { 0.0, 1.0, 2.0, 3.0};
    +    c.append(4, c2);
    +    QCOMPARE(c.count(), 4);
    +    QCOMPARE(c[0], 0.0);
    +    QCOMPARE(c[1], 1.0);
    +    QCOMPARE(c[3], 3.0);
    +    c.clear();
    +    c << 1.0 << 3.0;
    +    c.erase(c.begin(), c.end());
    +    QCOMPARE(c.count(), 0);
    +    c << 1.0 << 0.0;
    +    Coordinates::iterator i= c.erase(c.begin());
    +    QCOMPARE(*i, 0.0);
    +    i= c.insert(c.end(), 1.0);
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(c.count(), 2);
    +    c.pop_back();
    +    QCOMPARE(c.count(), 1);   // 0
    +    QCOMPARE(c[0], 0.0);
    +    c.push_back(2.0);
    +    QCOMPARE(c.count(), 2);
    +    c.append(3.0);
    +    QCOMPARE(c.count(), 3);   // 0, 2, 3
    +    QCOMPARE(c[2], 3.0);
    +    c.insert(0, 4.0);
    +    QCOMPARE(c[0], 4.0);
    +    QCOMPARE(c[3], 3.0);
    +    c.insert(c.count(), 5.0);
    +    QCOMPARE(c.count(), 5);   // 4, 0, 2, 3, 5
    +    QCOMPARE(c[4], 5.0);
    +    c.move(4, 0);
    +    QCOMPARE(c.count(), 5);   // 5, 4, 0, 2, 3
    +    QCOMPARE(c[0], 5.0);
    +    c.pop_front();
    +    QCOMPARE(c.count(), 4);
    +    QCOMPARE(c[0], 4.0);
    +    c.prepend(6.0);
    +    QCOMPARE(c.count(), 5);   // 6, 4, 0, 2, 3
    +    QCOMPARE(c[0], 6.0);
    +    c.push_front(7.0);
    +    QCOMPARE(c.count(), 6);
    +    QCOMPARE(c[0], 7.0);
    +    c.removeAt(1);
    +    QCOMPARE(c.count(), 5);   // 7, 4, 0, 2, 3
    +    QCOMPARE(c[1], 4.0);
    +    c.removeFirst();
    +    QCOMPARE(c.count(), 4);   // 4, 0, 2, 3
    +    QCOMPARE(c[0], 4.0);
    +    c.removeLast();
    +    QCOMPARE(c.count(), 3);
    +    QCOMPARE(c.last(), 2.0);
    +    c.replace(2, 8.0);
    +    QCOMPARE(c.count(), 3);   // 4, 0, 8
    +    QCOMPARE(c[2], 8.0);
    +    c.swap(0, 2);
    +    QCOMPARE(c[2], 4.0);
    +    double d= c.takeAt(2);
    +    QCOMPARE(c.count(), 2);   // 8, 0
    +    QCOMPARE(d, 4.0);
    +    double d2= c.takeFirst();
    +    QCOMPARE(c.count(), 1);   // 0
    +    QCOMPARE(d2, 8.0);
    +    double d3= c.takeLast();
    +    QVERIFY(c.isEmpty()); \
    +    QCOMPARE(d3, 0.0);
    +}//t_readwrite
    +
    +void Coordinates_test::
    +t_search()
    +{
    +    Coordinates c;
    +    c << 1.0 << 3.0 << 1.0;
    +    QVERIFY(c.contains(1.0));
    +    QVERIFY(c.contains(3.0));
    +    QVERIFY(!c.contains(0.0));
    +    QCOMPARE(c.count(1.0), 2);
    +    QCOMPARE(c.count(3.0), 1);
    +    QCOMPARE(c.count(0.0), 0);
    +    QCOMPARE(c.indexOf(1.0), 0);
    +    QCOMPARE(c.indexOf(3.0), 1);
    +    QCOMPARE(c.indexOf(1.0, -1), 2);
    +    QCOMPARE(c.indexOf(3.0, -1), -1);
    +    QCOMPARE(c.indexOf(3.0, -2), 1);
    +    QCOMPARE(c.indexOf(1.0, -3), 0);
    +    QCOMPARE(c.indexOf(1.0, -4), 0);
    +    QCOMPARE(c.indexOf(1.0, 1), 2);
    +    QCOMPARE(c.indexOf(3.0, 2), -1);
    +    QCOMPARE(c.indexOf(1.0, 2), 2);
    +    QCOMPARE(c.indexOf(1.0, 3), -1);
    +    QCOMPARE(c.indexOf(1.0, 4), -1);
    +    QCOMPARE(c.lastIndexOf(1.0), 2);
    +    QCOMPARE(c.lastIndexOf(3.0), 1);
    +    QCOMPARE(c.lastIndexOf(1.0, -1), 2);
    +    QCOMPARE(c.lastIndexOf(3.0, -1), 1);
    +    QCOMPARE(c.lastIndexOf(3.0, -2), 1);
    +    QCOMPARE(c.lastIndexOf(1.0, -3), 0);
    +    QCOMPARE(c.lastIndexOf(1.0, -4), -1);
    +    QCOMPARE(c.lastIndexOf(1.0, 1), 0);
    +    QCOMPARE(c.lastIndexOf(3.0, 2), 1);
    +    QCOMPARE(c.lastIndexOf(1.0, 2), 2);
    +    QCOMPARE(c.lastIndexOf(1.0, 3), 2);
    +    QCOMPARE(c.lastIndexOf(1.0, 4), 2);
    +    c.removeAll(3.0);
    +    QCOMPARE(c.count(), 2);
    +    c.removeAll(4.0);
    +    QCOMPARE(c.count(), 2);
    +    c.removeAll(1.0);
    +    QCOMPARE(c.count(), 0);
    +    c.removeAll(4.0);
    +    QCOMPARE(c.count(), 0);
    +}//t_search
    +
    +void Coordinates_test::
    +t_io()
    +{
    +    Coordinates c;
    +    c << 1.0 << 2.0 << 3.0;
    +    ostringstream os;
    +    os << "Coordinates 1-2-3\n" << c;
    +    cout << os.str();
    +    QString s= QString::fromStdString(os.str());
    +    QCOMPARE(s.count("2"), 2);
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/Coordinates_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/PointCoordinates_test.cpp b/xs/src/qhull/src/qhulltest/PointCoordinates_test.cpp
    new file mode 100644
    index 0000000000..09285954df
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/PointCoordinates_test.cpp
    @@ -0,0 +1,478 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/PointCoordinates_test.cpp#2 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/PointCoordinates.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +using std::stringstream;
    +
    +namespace orgQhull {
    +
    +class PointCoordinates_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void t_construct_q();
    +    void t_construct_qh();
    +    void t_convert();
    +    void t_getset();
    +    void t_element();
    +    void t_foreach();
    +    void t_search();
    +    void t_modify();
    +    void t_append_points();
    +    void t_coord_iterator();
    +    void t_io();
    +};//PointCoordinates_test
    +
    +void
    +add_PointCoordinates_test()
    +{
    +    new PointCoordinates_test();  // RoadTest::s_testcases
    +}
    +
    +void PointCoordinates_test::
    +t_construct_q()
    +{
    +    Qhull q;
    +    PointCoordinates pc(q);
    +    QCOMPARE(pc.size(), 0U);
    +    QCOMPARE(pc.coordinateCount(), 0);
    +    QCOMPARE(pc.dimension(), 0);
    +    QCOMPARE(pc.coordinates(), (coordT *)0);
    +    QVERIFY(pc.isEmpty());
    +    pc.checkValid();
    +    PointCoordinates pc7(q, 2, "test explicit dimension");
    +    QCOMPARE(pc7.dimension(), 2);
    +    QCOMPARE(pc7.count(), 0);
    +    QVERIFY(pc7.isEmpty());
    +    QCOMPARE(pc7.comment(), std::string("test explicit dimension"));
    +    pc7.checkValid();
    +    PointCoordinates pc2(q, "Test pc2");
    +    QCOMPARE(pc2.count(), 0);
    +    QVERIFY(pc2.isEmpty());
    +    QCOMPARE(pc2.comment(), std::string("Test pc2"));
    +    pc2.checkValid();
    +    PointCoordinates pc3(q, 3, "Test 3-d pc3");
    +    QCOMPARE(pc3.dimension(), 3);
    +    QVERIFY(pc3.isEmpty());
    +    pc3.checkValid();
    +    coordT c[]= { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 };
    +    PointCoordinates pc4(q, 2, "Test 2-d pc4", 6, c);
    +    QCOMPARE(pc4.dimension(), 2);
    +    QCOMPARE(pc4.count(), 3);
    +    QCOMPARE(pc4.size(), 3u);
    +    QVERIFY(!pc4.isEmpty());
    +    pc4.checkValid();
    +    QhullPoint p= pc4[2];
    +    QCOMPARE(p[1], 5.0);
    +    // QhullPoint refers to PointCoordinates
    +    p[1] += 1.0;
    +    QCOMPARE(pc4[2][1], 6.0);
    +    PointCoordinates pc5(q, 4, "Test 4-d pc5 with insufficient coordinates", 6, c);
    +    QCOMPARE(pc5.dimension(), 4);
    +    QCOMPARE(pc5.count(), 1);
    +    QCOMPARE(pc5.extraCoordinatesCount(), 2);
    +    QCOMPARE(pc5.extraCoordinates()[1], 5.0);
    +    QVERIFY(!pc5.isEmpty());;
    +    std::vector vc;
    +    vc.push_back(3.0);
    +    vc.push_back(4.0);
    +    vc.push_back(5.0);
    +    vc.push_back(6.0);
    +    vc.push_back(7.0);
    +    vc.push_back(9.0);
    +    pc5.append(2, &vc[3]); // Copy of vc[]
    +    pc5.checkValid();
    +    QhullPoint p5(q, 4, &vc[1]);
    +    QCOMPARE(pc5[1], p5);
    +    PointCoordinates pc6(pc5); // Makes copy of point_coordinates
    +    QCOMPARE(pc6[1], p5);
    +    QVERIFY(pc6==pc5);
    +    QhullPoint p6= pc5[1];  // Refers to pc5.coordinates
    +    pc5[1][0] += 1.0;
    +    QCOMPARE(pc5[1], p6);
    +    QVERIFY(pc5[1]!=p5);
    +    QVERIFY(pc6!=pc5);
    +    pc6= pc5;
    +    QVERIFY(pc6==pc5);
    +    PointCoordinates pc8(q);
    +    pc6= pc8;
    +    QVERIFY(pc6!=pc5);
    +    QVERIFY(pc6.isEmpty());
    +}//t_construct_q
    +
    +void PointCoordinates_test::
    +t_construct_qh()
    +{
    +    QhullQh qh;
    +    PointCoordinates pc(&qh);
    +    QCOMPARE(pc.size(), 0U);
    +    QCOMPARE(pc.coordinateCount(), 0);
    +    QCOMPARE(pc.dimension(), 0);
    +    QCOMPARE(pc.coordinates(), (coordT *)0);
    +    QVERIFY(pc.isEmpty());
    +    pc.checkValid();
    +    PointCoordinates pc7(&qh, 2, "test explicit dimension");
    +    QCOMPARE(pc7.dimension(), 2);
    +    QCOMPARE(pc7.count(), 0);
    +    QVERIFY(pc7.isEmpty());
    +    QCOMPARE(pc7.comment(), std::string("test explicit dimension"));
    +    pc7.checkValid();
    +    PointCoordinates pc2(&qh, "Test pc2");
    +    QCOMPARE(pc2.count(), 0);
    +    QVERIFY(pc2.isEmpty());
    +    QCOMPARE(pc2.comment(), std::string("Test pc2"));
    +    pc2.checkValid();
    +    PointCoordinates pc3(&qh, 3, "Test 3-d pc3");
    +    QCOMPARE(pc3.dimension(), 3);
    +    QVERIFY(pc3.isEmpty());
    +    pc3.checkValid();
    +    coordT c[]= { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 };
    +    PointCoordinates pc4(&qh, 2, "Test 2-d pc4", 6, c);
    +    QCOMPARE(pc4.dimension(), 2);
    +    QCOMPARE(pc4.count(), 3);
    +    QCOMPARE(pc4.size(), 3u);
    +    QVERIFY(!pc4.isEmpty());
    +    pc4.checkValid();
    +    QhullPoint p= pc4[2];
    +    QCOMPARE(p[1], 5.0);
    +    // QhullPoint refers to PointCoordinates
    +    p[1] += 1.0;
    +    QCOMPARE(pc4[2][1], 6.0);
    +    PointCoordinates pc5(&qh, 4, "Test 4-d pc5 with insufficient coordinates", 6, c);
    +    QCOMPARE(pc5.dimension(), 4);
    +    QCOMPARE(pc5.count(), 1);
    +    QCOMPARE(pc5.extraCoordinatesCount(), 2);
    +    QCOMPARE(pc5.extraCoordinates()[1], 5.0);
    +    QVERIFY(!pc5.isEmpty());;
    +    std::vector vc;
    +    vc.push_back(3.0);
    +    vc.push_back(4.0);
    +    vc.push_back(5.0);
    +    vc.push_back(6.0);
    +    vc.push_back(7.0);
    +    vc.push_back(9.0);
    +    pc5.append(2, &vc[3]); // Copy of vc[]
    +    pc5.checkValid();
    +    QhullPoint p5(&qh, 4, &vc[1]);
    +    QCOMPARE(pc5[1], p5);
    +    PointCoordinates pc6(pc5); // Makes copy of point_coordinates
    +    QCOMPARE(pc6[1], p5);
    +    QVERIFY(pc6==pc5);
    +    QhullPoint p6= pc5[1];  // Refers to pc5.coordinates
    +    pc5[1][0] += 1.0;
    +    QCOMPARE(pc5[1], p6);
    +    QVERIFY(pc5[1]!=p5);
    +    QVERIFY(pc6!=pc5);
    +    pc6= pc5;
    +    QVERIFY(pc6==pc5);
    +    PointCoordinates pc8(&qh);
    +    pc6= pc8;
    +    QVERIFY(pc6!=pc5);
    +    QVERIFY(pc6.isEmpty());
    +}//t_construct_qh
    +
    +void PointCoordinates_test::
    +t_convert()
    +{
    +    Qhull q;
    +    //defineAs tested above
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates ps(q, 3, "two 3-d points", 6, c);
    +    QCOMPARE(ps.dimension(), 3);
    +    QCOMPARE(ps.size(), 2u);
    +    const coordT *c2= ps.constData();
    +    QVERIFY(c!=c2);
    +    QCOMPARE(c[0], c2[0]);
    +    const coordT *c3= ps.data();
    +    QCOMPARE(c3, c2);
    +    coordT *c4= ps.data();
    +    QCOMPARE(c4, c2);
    +    std::vector vs= ps.toStdVector();
    +    QCOMPARE(vs.size(), 6u);
    +    QCOMPARE(vs[5], 5.0);
    +    QList qs= ps.toQList();
    +    QCOMPARE(qs.size(), 6);
    +    QCOMPARE(qs[5], 5.0);
    +}//t_convert
    +
    +void PointCoordinates_test::
    +t_getset()
    +{
    +    // See t_construct() for test of coordinates, coordinateCount, dimension, empty, isEmpty, ==, !=
    +    // See t_construct() for test of checkValid, comment, setDimension
    +    Qhull q;
    +    PointCoordinates pc(q, "Coordinates c");
    +    pc.setComment("New comment");
    +    QCOMPARE(pc.comment(), std::string("New comment"));
    +    pc.checkValid();
    +    pc.makeValid();  // A no-op
    +    pc.checkValid();
    +    Coordinates cs= pc.getCoordinates();
    +    QVERIFY(cs.isEmpty());
    +    PointCoordinates pc2(pc);
    +    pc.setDimension(3);
    +    QVERIFY(pc2!=pc);
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    pc.append(6, c);
    +    pc.checkValid();
    +    pc.makeValid();  // A no-op
    +    QhullPoint p= pc[0];
    +    QCOMPARE(p[2], 2.0);
    +    try{
    +        pc.setDimension(2);
    +        QFAIL("setDimension(2) did not fail for 3-d.");
    +    }catch (const std::exception &e) {
    +        const char *s= e.what();
    +        cout << "INFO   : Caught " << s;
    +    }
    +}//t_getset
    +
    +void PointCoordinates_test::
    +t_element()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc(q, 2, "2-d points", 6, c);
    +    QhullPoint p= pc.at(0);
    +    QCOMPARE(p, pc[0]);
    +    QCOMPARE(p, pc.first());
    +    QCOMPARE(p, pc.value(0));
    +    p= pc.back();
    +    QCOMPARE(p, pc[2]);
    +    QCOMPARE(p, pc.last());
    +    QCOMPARE(p, pc.value(2));
    +    QhullPoints ps= pc.mid(1, 2);
    +    QCOMPARE(ps[1], p);
    +}//t_element
    +
    +void PointCoordinates_test::
    +t_foreach()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc(q, 2, "2-d points", 6, c);
    +    QhullPoints::Iterator i= pc.begin();
    +    QhullPoint p= pc[0];
    +    QCOMPARE(*i, p);
    +    QCOMPARE((*i)[0], 0.0);
    +    QhullPoint p3= pc[2];
    +    i= pc.end();
    +    QCOMPARE(i[-1], p3);
    +    const PointCoordinates pc2(q, 2, "2-d points", 6, c);
    +    QhullPoints::ConstIterator i2= pc.begin();
    +    const QhullPoint p0= pc2[0];
    +    QCOMPARE(*i2, p0);
    +    QCOMPARE((*i2)[0], 0.0);
    +    QhullPoints::ConstIterator i3= i2;
    +    QCOMPARE(i3, i2);
    +    QCOMPARE((*i3)[0], 0.0);
    +    i3= pc.constEnd();
    +    --i3;
    +    QhullPoint p2= pc2[2];
    +    QCOMPARE(*i3, p2);
    +    i= pc.end();
    +    QVERIFY(i-1==i3);
    +    i2= pc2.end();
    +    QVERIFY(i2-1!=i3);
    +    QCOMPARE(*(i2-1), *i3);
    +    foreach(QhullPoint p3, pc){ //Qt only
    +        QVERIFY(p3[0]>=0.0);
    +        QVERIFY(p3[0]<=5.0);
    +    }
    +    Coordinates::ConstIterator i4= pc.beginCoordinates();
    +    QCOMPARE(*i4, 0.0);
    +    Coordinates::Iterator i5= pc.beginCoordinates();
    +    QCOMPARE(*i5, 0.0);
    +    i4= pc.beginCoordinates(1);
    +    QCOMPARE(*i4, 2.0);
    +    i5= pc.beginCoordinates(1);
    +    QCOMPARE(*i5, 2.0);
    +    i4= pc.endCoordinates();
    +    QCOMPARE(*--i4, 5.0);
    +    i5= pc.endCoordinates();
    +    QCOMPARE(*--i5, 5.0);
    +}//t_foreach
    +
    +void PointCoordinates_test::
    +t_search()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc(q, 2, "2-d points", 6, c);
    +    QhullPoint p0= pc[0];
    +    QhullPoint p2= pc[2];
    +    QVERIFY(pc.contains(p0));
    +    QVERIFY(pc.contains(p2));
    +    QCOMPARE(pc.count(p0), 1);
    +    QCOMPARE(pc.indexOf(p2), 2);
    +    QCOMPARE(pc.lastIndexOf(p0), 0);
    +}//t_search
    +
    +void PointCoordinates_test::
    +t_modify()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc(q, 2, "2-d points", 6, c);
    +    coordT c3[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc5(q, 2, "test explicit dimension");
    +    pc5.append(6, c3); // 0-5
    +    QVERIFY(pc5==pc);
    +    PointCoordinates pc2(q, 2, "2-d");
    +    coordT c2[]= {6.0, 7.0, 8.0, 9.0, 10.0, 11.0};
    +    pc2.append(6, c2);
    +    QCOMPARE(pc2.count(), 3);
    +    pc2.append(14.0);
    +    QCOMPARE(pc2.count(), 3);
    +    QCOMPARE(pc2.extraCoordinatesCount(), 1);
    +    pc2.append(15.0); // 6-11, 14,15
    +    QCOMPARE(pc2.count(), 4);
    +    QCOMPARE(pc2.extraCoordinatesCount(), 0);
    +    QhullPoint p(pc[0]);
    +    pc2.append(p); // 6-11, 14,15, 0,1
    +    QCOMPARE(pc2.count(), 5);
    +    QCOMPARE(pc2.extraCoordinatesCount(), 0);
    +    QCOMPARE(pc2.lastIndexOf(p), 4);
    +    pc.append(pc2); // Invalidates p
    +    QCOMPARE(pc.count(), 8); // 0-11, 14,15, 0,1
    +    QCOMPARE(pc.extraCoordinatesCount(), 0);
    +    QCOMPARE(pc.lastIndexOf(pc[0]), 7);
    +    pc.appendComment(" operators");
    +    QCOMPARE(pc.comment(), std::string("2-d points operators"));
    +    pc.checkValid();
    +    // see t_append_points for appendPoints
    +    PointCoordinates pc3= pc+pc2;
    +    pc3.checkValid();
    +    QCOMPARE(pc3.count(), 13);
    +    QCOMPARE(pc3[6][0], 14.0);
    +    QCOMPARE(pc3[8][0], 6.0);
    +    pc3 += pc;
    +    QCOMPARE(pc3.count(), 21);
    +    QCOMPARE(pc3[14][0], 2.0);
    +    pc3 += 12.0;
    +    pc3 += 14.0;
    +    QCOMPARE(pc3.count(), 22);
    +    QCOMPARE(pc3.last()[0], 12.0);
    +    // QhullPoint p3= pc3.first(); // += throws error because append may move the data
    +    QhullPoint p3= pc2.first();
    +    pc3 += p3;
    +    QCOMPARE(pc3.count(), 23);
    +    QCOMPARE(pc3.last()[0], 6.0);
    +    pc3 << pc;
    +    QCOMPARE(pc3.count(), 31);
    +    QCOMPARE(pc3.last()[0], 0.0);
    +    pc3 << 12.0 << 14.0;
    +    QCOMPARE(pc3.count(), 32);
    +    QCOMPARE(pc3.last()[0], 12.0);
    +    PointCoordinates pc4(pc3);
    +    pc4.reserveCoordinates(100);
    +    QVERIFY(pc3==pc4);
    +}//t_modify
    +
    +void PointCoordinates_test::
    +t_append_points()
    +{
    +    Qhull q;
    +    PointCoordinates pc(q, 2, "stringstream");
    +    stringstream s("2 3 1 2 3 4 5 6");
    +    pc.appendPoints(s);
    +    QCOMPARE(pc.count(), 3);
    +}//t_append_points
    +
    +void PointCoordinates_test::
    +t_coord_iterator()
    +{
    +    Qhull q;
    +    PointCoordinates c(q, 2, "2-d");
    +    c << 0.0 << 1.0 << 2.0 << 3.0 << 4.0 << 5.0;
    +    PointCoordinatesIterator i(c);
    +    QhullPoint p0(c[0]);
    +    QhullPoint p1(c[1]);
    +    QhullPoint p2(c[2]);
    +    coordT c2[] = {-1.0, -2.0};
    +    QhullPoint p3(q, 2, c2);
    +    PointCoordinatesIterator i2= c;
    +    QVERIFY(i.findNext(p1));
    +    QVERIFY(!i.findNext(p1));
    +    QVERIFY(!i.findNext(p2));
    +    QVERIFY(!i.findNext(p3));
    +    QVERIFY(i.findPrevious(p2));
    +    QVERIFY(!i.findPrevious(p2));
    +    QVERIFY(!i.findPrevious(p0));
    +    QVERIFY(!i.findPrevious(p3));
    +    QVERIFY(i2.findNext(p2));
    +    QVERIFY(i2.findPrevious(p0));
    +    QVERIFY(i2.findNext(p1));
    +    QVERIFY(i2.findPrevious(p0));
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    i2.toFront();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(i.hasPrevious());
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    PointCoordinates c3(q);
    +    PointCoordinatesIterator i3= c3;
    +    QVERIFY(!i3.hasNext());
    +    QVERIFY(!i3.hasPrevious());
    +    i3.toBack();
    +    QVERIFY(!i3.hasNext());
    +    QVERIFY(!i3.hasPrevious());
    +    QCOMPARE(i.peekPrevious(), p2);
    +    QCOMPARE(i.previous(), p2);
    +    QCOMPARE(i.previous(), p1);
    +    QCOMPARE(i.previous(), p0);
    +    QVERIFY(!i.hasPrevious());
    +    QCOMPARE(i.peekNext(), p0);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), p0);
    +    QCOMPARE(i.peekNext(), p1);
    +    QCOMPARE(i.next(), p1);
    +    QCOMPARE(i.next(), p2);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), p0);
    +}//t_coord_iterator
    +
    +void PointCoordinates_test::
    +t_io()
    +{
    +    Qhull q;
    +    PointCoordinates c(q);
    +    ostringstream os;
    +    os << "PointCoordinates 0-d\n" << c;
    +    c.setDimension(2);
    +    c << 1.0 << 2.0 << 3.0 << 1.0 << 2.0 << 3.0;
    +    os << "PointCoordinates 1,2 3,1 2,3\n" << c;
    +    cout << os.str();
    +    QString s= QString::fromStdString(os.str());
    +    QCOMPARE(s.count("0"), 3);
    +    QCOMPARE(s.count("2"), 5);
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/PointCoordinates_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullFacetList_test.cpp b/xs/src/qhull/src/qhulltest/QhullFacetList_test.cpp
    new file mode 100644
    index 0000000000..5a09d01da9
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullFacetList_test.cpp
    @@ -0,0 +1,196 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullFacetList_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/QhullVertexSet.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullFacetList_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct_qh();
    +    void t_construct_q();
    +    void t_convert();
    +    void t_readonly();
    +    void t_foreach();
    +    void t_io();
    +};//QhullFacetList_test
    +
    +void
    +add_QhullFacetList_test()
    +{
    +    new QhullFacetList_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullFacetList_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullFacetList_test::
    +t_construct_qh()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacetList fs2= q.facetList();
    +    QVERIFY(!fs2.isEmpty());
    +    QCOMPARE(fs2.count(),6);
    +    QhullFacetList fs3(q.endFacet(), q.endFacet());
    +    QVERIFY(fs3.isEmpty());
    +    QhullFacetList fs4(q.endFacet().previous(), q.endFacet());
    +    QCOMPARE(fs4.count(), 1);
    +    QhullFacetList fs5(q.beginFacet(), q.endFacet());
    +    QCOMPARE(fs2.count(), fs5.count());
    +    QVERIFY(fs2==fs5);
    +    QhullFacetList fs6= fs2; // copy constructor
    +    QVERIFY(fs6==fs2);
    +    std::vector fv= fs2.toStdVector();
    +    QCOMPARE(fv.size(), 6u);
    +}//t_construct_qh
    +
    +void QhullFacetList_test::
    +t_construct_q()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacetList fs2= q.facetList();
    +    QVERIFY(!fs2.isEmpty());
    +    QCOMPARE(fs2.count(),6);
    +    QhullFacetList fs3(q.endFacet(), q.endFacet());
    +    QVERIFY(fs3.isEmpty());
    +    QhullFacetList fs4(q.endFacet().previous(), q.endFacet());
    +    QCOMPARE(fs4.count(), 1);
    +    QhullFacetList fs5(q.beginFacet(), q.endFacet());
    +    QCOMPARE(fs2.count(), fs5.count());
    +    QVERIFY(fs2==fs5);
    +    QhullFacetList fs6= fs2; // copy constructor
    +    QVERIFY(fs6==fs2);
    +    std::vector fv= fs2.toStdVector();
    +    QCOMPARE(fv.size(), 6u);
    +}//t_construct_q
    +
    +void QhullFacetList_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0 QV2");  // rotated unit cube
    +    QhullFacetList fs2= q.facetList();
    +    QVERIFY(!fs2.isSelectAll());
    +    QVERIFY(!fs2.isEmpty());
    +    QCOMPARE(fs2.count(),3);
    +    std::vector fv= fs2.toStdVector();
    +    QCOMPARE(fv.size(), 3u);
    +    QList fv2= fs2.toQList();
    +    QCOMPARE(fv2.size(), 3);
    +    std::vector fv5= fs2.vertices_toStdVector();
    +    QCOMPARE(fv5.size(), 7u);
    +    QList fv6= fs2.vertices_toQList();
    +    QCOMPARE(fv6.size(), 7);
    +    fs2.selectAll();
    +    QVERIFY(fs2.isSelectAll());
    +    std::vector fv3= fs2.toStdVector();
    +    QCOMPARE(fv3.size(), 6u);
    +    QList fv4= fs2.toQList();
    +    QCOMPARE(fv4.size(), 6);
    +    std::vector fv7= fs2.vertices_toStdVector();
    +    QCOMPARE(fv7.size(), 8u);
    +    QList fv8= fs2.vertices_toQList();
    +    QCOMPARE(fv8.size(), 8);
    +}//t_convert
    +
    +//! Spot check properties and read-only.  See QhullLinkedList_test
    +void QhullFacetList_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QV0");  // good facets are adjacent to point 0
    +    QhullFacetList fs= q.facetList();
    +    QVERIFY(!fs.isSelectAll());
    +    QCOMPARE(fs.count(), 3);
    +    QCOMPARE(fs.first(), q.firstFacet());
    +    fs.selectAll();
    +    QVERIFY(fs.isSelectAll());
    +    QCOMPARE(fs.count(), 6);
    +    fs.selectGood();
    +    QVERIFY(!fs.isSelectAll());
    +    QCOMPARE(fs.count(), 3);
    +    fs.selectAll();
    +    QVERIFY(fs.isSelectAll());
    +    QCOMPARE(fs.count(), 6);
    +    QhullFacet f= fs.first();
    +    QhullFacet f2= fs.last();
    +    fs.selectAll();
    +    QVERIFY(fs.contains(f));
    +    QVERIFY(fs.contains(f2));
    +    QVERIFY(f.isGood());
    +    QVERIFY(!f2.isGood());
    +    fs.selectGood();
    +    QVERIFY(fs.contains(f));
    +    QVERIFY(!fs.contains(f2));
    +}//t_readonly
    +
    +void QhullFacetList_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");
    +    // Spot check predicates and accessors.  See QhullLinkedList_test
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullFacetList fs= q.facetList();
    +    QVERIFY(fs.contains(q.firstFacet()));
    +    QhullFacet f= q.firstFacet().next();
    +    QVERIFY(fs.contains(f));
    +    QCOMPARE(fs.first(), *fs.begin());
    +    QCOMPARE(*(fs.end()-1), fs.last());
    +    QCOMPARE(fs.first(), q.firstFacet());
    +    QCOMPARE(*fs.begin(), q.beginFacet());
    +    QCOMPARE(*fs.end(), q.endFacet());
    +}//t_foreach
    +
    +void QhullFacetList_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0 QV0");   // good facets are adjacent to point 0
    +        QhullFacetList fs= q.facetList();
    +        ostringstream os;
    +        os << fs.print("Show all of FacetList\n");
    +        os << "\nFacets only\n" << fs;
    +        os << "\nVertices only\n" << fs.printVertices();
    +        cout << os.str();
    +        QString facets= QString::fromStdString(os.str());
    +        QCOMPARE(facets.count("(v"), 2*7+12*3*2);
    +        QCOMPARE(facets.count(QRegExp("f\\d")), 2*3*7 + 13*3*2);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullFacetList_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullFacetSet_test.cpp b/xs/src/qhull/src/qhulltest/QhullFacetSet_test.cpp
    new file mode 100644
    index 0000000000..a7fe123a28
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullFacetSet_test.cpp
    @@ -0,0 +1,153 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullFacetSet_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullFacetSet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_readonly();
    +    void t_foreach();
    +    void t_io();
    +};//QhullFacetSet_test
    +
    +void
    +add_QhullFacetSet_test()
    +{
    +    new QhullFacetSet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullFacetSet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullFacetSet_test::
    +t_construct()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacet f= q.firstFacet();
    +    QhullFacetSet fs2= f.neighborFacets();
    +    QVERIFY(!fs2.isEmpty());
    +    QCOMPARE(fs2.count(),4);
    +    QhullFacetSet fs4= fs2; // copy constructor
    +    QVERIFY(fs4==fs2);
    +    QhullFacetSet fs3(q, q.qh()->facet_mergeset);
    +    QVERIFY(fs3.isEmpty());
    +}//t_construct
    +
    +void QhullFacetSet_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q2(rcube,"QR0 QV2");  // rotated unit cube
    +    QhullFacet f2= q2.firstFacet();
    +    QhullFacetSet fs2= f2.neighborFacets();
    +    QVERIFY(!fs2.isSelectAll());
    +    QCOMPARE(fs2.count(),2);
    +    std::vector fv= fs2.toStdVector();
    +    QCOMPARE(fv.size(), 2u);
    +    QList fv2= fs2.toQList();
    +    QCOMPARE(fv2.size(), 2);
    +    fs2.selectAll();
    +    QVERIFY(fs2.isSelectAll());
    +    std::vector fv3= fs2.toStdVector();
    +    QCOMPARE(fv3.size(), 4u);
    +    QList fv4= fs2.toQList();
    +    QCOMPARE(fv4.size(), 4);
    +}//t_convert
    +
    +//! Spot check properties and read-only.  See QhullSet_test
    +void QhullFacetSet_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QV0");  // good facets are adjacent to point 0
    +    QhullFacetSet fs= q.firstFacet().neighborFacets();
    +    QVERIFY(!fs.isSelectAll());
    +    QCOMPARE(fs.count(), 2);
    +    fs.selectAll();
    +    QVERIFY(fs.isSelectAll());
    +    QCOMPARE(fs.count(), 4);
    +    fs.selectGood();
    +    QVERIFY(!fs.isSelectAll());
    +    QCOMPARE(fs.count(), 2);
    +    QhullFacet f= fs.first();
    +    QhullFacet f2= fs.last();
    +    fs.selectAll();
    +    QVERIFY(fs.contains(f));
    +    QVERIFY(fs.contains(f2));
    +    QVERIFY(f.isGood());
    +    QVERIFY(!f2.isGood());
    +    fs.selectGood();
    +    QVERIFY(fs.contains(f));
    +    QVERIFY(!fs.contains(f2));
    +}//t_readonly
    +
    +void QhullFacetSet_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");
    +    // Spot check predicates and accessors.  See QhullLinkedList_test
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacetSet fs= q.firstFacet().neighborFacets();
    +    QVERIFY(!fs.contains(q.firstFacet()));
    +    QVERIFY(fs.contains(fs.first()));
    +    QhullFacet f= q.firstFacet().next();
    +    if(!fs.contains(f)){  // check if 'f' is the facet opposite firstFacet()
    +        f= f.next();
    +    }
    +    QVERIFY(fs.contains(f));
    +    QCOMPARE(fs.first(), *fs.begin());
    +    QCOMPARE(*(fs.end()-1), fs.last());
    +}//t_foreach
    +
    +void QhullFacetSet_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0 QV0");   // good facets are adjacent to point 0
    +        QhullFacetSet fs= q.firstFacet().neighborFacets();
    +        ostringstream os;
    +        os << fs.print("Neighbors of first facet with point 0");
    +        os << fs.printIdentifiers("\nFacet identifiers: ");
    +        cout << os.str();
    +        QString facets= QString::fromStdString(os.str());
    +        QCOMPARE(facets.count(QRegExp(" f[0-9]")), 2+13*2);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullFacetSet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullFacet_test.cpp b/xs/src/qhull/src/qhulltest/QhullFacet_test.cpp
    new file mode 100644
    index 0000000000..271f63753c
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullFacet_test.cpp
    @@ -0,0 +1,283 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullFacet_test.cpp#4 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/Coordinates.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullPointSet.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullFacet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct_qh();
    +    void t_constructConvert();
    +    void t_getSet();
    +    void t_value();
    +    void t_foreach();
    +    void t_io();
    +};//QhullFacet_test
    +
    +void
    +add_QhullFacet_test()
    +{
    +    new QhullFacet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullFacet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullFacet_test::
    +t_construct_qh()
    +{
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    QhullQh qh;
    +    QhullFacet f(&qh);
    +    QVERIFY(!f.isValid());
    +    QCOMPARE(f.dimension(),0);
    +}//t_construct_qh
    +
    +void QhullFacet_test::
    +t_constructConvert()
    +{
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    Qhull q2;
    +    QhullFacet f(q2);
    +    QVERIFY(!f.isValid());
    +    QCOMPARE(f.dimension(),0);
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullFacet f2(q.beginFacet());
    +    QCOMPARE(f2.dimension(),3);
    +    f= f2; // copy assignment
    +    QVERIFY(f.isValid());
    +    QCOMPARE(f.dimension(),3);
    +    QhullFacet f5= f2;
    +    QVERIFY(f5==f2);
    +    QVERIFY(f5==f);
    +    QhullFacet f3(q, f2.getFacetT());
    +    QCOMPARE(f,f3);
    +    QhullFacet f4(q, f2.getBaseT());
    +    QCOMPARE(f,f4);
    +}//t_constructConvert
    +
    +void QhullFacet_test::
    +t_getSet()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        cout << " rbox c | qhull Qt QR0 QR" << q.rotateRandom() << "   distanceEpsilon " << q.distanceEpsilon() << endl;
    +        QCOMPARE(q.facetCount(), 12);
    +        QCOMPARE(q.vertexCount(), 8);
    +        QhullFacetListIterator i(q.facetList());
    +        while(i.hasNext()){
    +            const QhullFacet f= i.next();
    +            cout << f.id() << endl;
    +            QCOMPARE(f.dimension(),3);
    +            QVERIFY(f.id()>0 && f.id()<=39);
    +            QVERIFY(f.isValid());
    +            if(i.hasNext()){
    +                QCOMPARE(f.next(), i.peekNext());
    +                QVERIFY(f.next()!=f);
    +            }
    +            QVERIFY(i.hasPrevious());
    +            QCOMPARE(f, i.peekPrevious());
    +        }
    +
    +        // test tricoplanarOwner
    +        QhullFacet facet = q.beginFacet();
    +        QhullFacet tricoplanarOwner = facet.tricoplanarOwner();
    +        int tricoplanarCount= 0;
    +        i.toFront();
    +        while(i.hasNext()){
    +            const QhullFacet f= i.next();
    +            if(f.tricoplanarOwner()==tricoplanarOwner){
    +                tricoplanarCount++;
    +            }
    +        }
    +        QCOMPARE(tricoplanarCount, 2);
    +        int tricoplanarCount2= 0;
    +        foreach (QhullFacet f, q.facetList()){  // Qt only
    +            QhullHyperplane h= f.hyperplane();
    +            cout << "Hyperplane: " << h;
    +            QCOMPARE(h.count(), 3);
    +            QCOMPARE(h.offset(), -0.5);
    +            double n= h.norm();
    +            QCOMPARE(n, 1.0);
    +            QhullHyperplane hi= f.innerplane();
    +            QCOMPARE(hi.count(), 3);
    +            double innerOffset= hi.offset()+0.5;
    +            cout << "InnerPlane: " << hi << "   innerOffset+0.5 " << innerOffset << endl;
    +            QVERIFY(innerOffset >= 0.0-(2*q.distanceEpsilon())); // A guessed epsilon.  It needs to account for roundoff due to rotation of the vertices
    +            QhullHyperplane ho= f.outerplane();
    +            QCOMPARE(ho.count(), 3);
    +            double outerOffset= ho.offset()+0.5;
    +            cout << "OuterPlane: " << ho << "   outerOffset+0.5 " << outerOffset << endl;
    +            QVERIFY(outerOffset <= 0.0+(2*q.distanceEpsilon())); // A guessed epsilon.  It needs to account for roundoff due to rotation of the vertices
    +            QVERIFY(outerOffset-innerOffset < 1e-7);
    +            for(int k= 0; k<3; k++){
    +                QVERIFY(ho[k]==hi[k]);
    +                QVERIFY(ho[k]==h[k]);
    +            }
    +            QhullPoint center= f.getCenter();
    +            cout << "Center: " << center;
    +            double d= f.distance(center);
    +            QVERIFY(d < innerOffset-outerOffset);
    +            QhullPoint center2= f.getCenter(qh_PRINTcentrums);
    +            QCOMPARE(center, center2);
    +            if(f.tricoplanarOwner()==tricoplanarOwner){
    +                tricoplanarCount2++;
    +            }
    +            cout << endl;
    +        }
    +        QCOMPARE(tricoplanarCount2, 2);
    +        Qhull q2(rcube,"d Qz Qt QR0");  // 3-d triangulation of Delaunay triangulation (the cube)
    +        cout << " rbox c | qhull d Qz Qt QR0 QR" << q2.rotateRandom() << "   distanceEpsilon " << q2.distanceEpsilon() << endl;
    +        QhullFacet f2= q2.firstFacet();
    +        QhullPoint center3= f2.getCenter(qh_PRINTtriangles);
    +        QCOMPARE(center3.dimension(), 3);
    +        QhullPoint center4= f2.getCenter();
    +        QCOMPARE(center4.dimension(), 4);
    +        for(int k= 0; k<3; k++){
    +            QVERIFY(center4[k]==center3[k]);
    +        }
    +        Qhull q3(rcube,"v Qz QR0");  // Voronoi diagram of a cube (one vertex)
    +        cout << " rbox c | qhull v Qz QR0 QR" << q3.rotateRandom() << "   distanceEpsilon " << q3.distanceEpsilon() << endl;
    +
    +        q3.setFactorEpsilon(400); // Voronoi vertices are not necessarily within distance episilon
    +        QhullPoint origin= q3.inputOrigin();
    +        int voronoiCount= 0;
    +        foreach(QhullFacet f, q3.facetList()){ //Qt only
    +            if(f.isGood()){
    +                ++voronoiCount;
    +                QhullPoint p= f.voronoiVertex();
    +                cout << p.print("Voronoi vertex: ")
    +                    << " Is it within " << q3.factorEpsilon() << " * distanceEpsilon (" << q3.distanceEpsilon() << ") of the origin?" << endl;
    +                QCOMPARE(p, origin);
    +            }
    +        }
    +        QCOMPARE(voronoiCount, 1);
    +    }
    +}//t_getSet
    +
    +void QhullFacet_test::
    +t_value()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        coordT c[]= {0.0, 0.0, 0.0};
    +        foreach (QhullFacet f, q.facetList()){  // Qt only
    +            double d= f.distance(q.origin());
    +            QCOMPARE(d, -0.5);
    +            double d0= f.distance(c);
    +            QCOMPARE(d0, -0.5);
    +            double facetArea= f.facetArea();
    +            QCOMPARE(facetArea, 1.0);
    +            #if qh_MAXoutside
    +                double maxoutside= f.getFacetT()->maxoutside;
    +                QVERIFY(maxoutside<1e-7);
    +            #endif
    +        }
    +    }
    +}//t_value
    +
    +void QhullFacet_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c W0 300");  // cube plus 300 points on its surface
    +    {
    +        Qhull q(rcube, "QR0 Qc"); // keep coplanars, thick facet, and rotate the cube
    +        int coplanarCount= 0;
    +        foreach(const QhullFacet f, q.facetList()){
    +            QhullPointSet coplanars= f.coplanarPoints();
    +            coplanarCount += coplanars.count();
    +            QhullFacetSet neighbors= f.neighborFacets();
    +            QCOMPARE(neighbors.count(), 4);
    +            QhullPointSet outsides= f.outsidePoints();
    +            QCOMPARE(outsides.count(), 0);
    +            QhullRidgeSet ridges= f.ridges();
    +            QCOMPARE(ridges.count(), 4);
    +            QhullVertexSet vertices= f.vertices();
    +            QCOMPARE(vertices.count(), 4);
    +            int ridgeCount= 0;
    +            QhullRidge r= ridges.first();
    +            for(int r0= r.id(); ridgeCount==0 || r.id()!=r0; r= r.nextRidge3d(f)){
    +                ++ridgeCount;
    +                if(!r.hasNextRidge3d(f)){
    +                    QFAIL("Unexpected simplicial facet.  They only have ridges to non-simplicial neighbors.");
    +                }
    +            }
    +            QCOMPARE(ridgeCount, 4);
    +        }
    +        QCOMPARE(coplanarCount, 300);
    +    }
    +}//t_foreach
    +
    +void QhullFacet_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullFacet f= q.beginFacet();
    +        cout << f;
    +        ostringstream os;
    +        os << f.print("\nWith a message\n");
    +        os << "\nPrint header for the same facet\n";
    +        os << f.printHeader();
    +        os << "\nPrint each component\n";
    +        os << f.printFlags("    - flags:");
    +        os << f.printCenter(qh_PRINTfacets, "    - center: ");
    +        os << f.printRidges();
    +        cout << os.str();
    +        ostringstream os2;
    +        os2 << f;
    +        QString facetString2= QString::fromStdString(os2.str());
    +        facetString2.replace(QRegExp("\\s\\s+"), " ");
    +        ostringstream os3;
    +        q.qh()->setOutputStream(&os3);
    +        q.outputQhull("f");
    +        QString facetsString= QString::fromStdString(os3.str());
    +        QString facetString3= facetsString.mid(facetsString.indexOf("- f1\n"));
    +        facetString3= facetString3.left(facetString3.indexOf("\n- f")+1);
    +        facetString3.replace(QRegExp("\\s\\s+"), " ");
    +        QCOMPARE(facetString2, facetString3);
    +    }
    +}//t_io
    +
    +// toQhullFacet is static_cast only
    +
    +}//orgQhull
    +
    +#include "moc/QhullFacet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullHyperplane_test.cpp b/xs/src/qhull/src/qhulltest/QhullHyperplane_test.cpp
    new file mode 100644
    index 0000000000..d016989a97
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullHyperplane_test.cpp
    @@ -0,0 +1,429 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullHyperplane_test.cpp#4 $$Change: 2064 $
    +** $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullHyperplane.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#include 
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullHyperplane_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_construct_qh();
    +    void t_convert();
    +    void t_readonly();
    +    void t_define();
    +    void t_value();
    +    void t_operator();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_qhullHyperplane_iterator();
    +    void t_io();
    +};//QhullHyperplane_test
    +
    +void
    +add_QhullHyperplane_test()
    +{
    +    new QhullHyperplane_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullHyperplane_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullHyperplane_test::
    +t_construct()
    +{
    +    QhullHyperplane h4;
    +    QVERIFY(!h4.isValid());
    +    QCOMPARE(h4.dimension(), 0);
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullHyperplane h(q);
    +    QVERIFY(!h.isValid());
    +    QCOMPARE(h.dimension(), 0);
    +    QCOMPARE(h.coordinates(),static_cast(0));
    +    QhullFacet f= q.firstFacet();
    +    QhullHyperplane h2(f.hyperplane());
    +    QVERIFY(h2.isValid());
    +    QCOMPARE(h2.dimension(), 3);
    +    h= h2;
    +    QCOMPARE(h, h2);
    +    QhullHyperplane h3(q, h2.dimension(), h2.coordinates(), h2.offset());
    +    QCOMPARE(h2, h3);
    +    QhullHyperplane h5= h2; // copy constructor
    +    QVERIFY(h5==h2);
    +}//t_construct
    +
    +void QhullHyperplane_test::
    +t_construct_qh()
    +{
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullFacet f= q.firstFacet();
    +    QhullHyperplane h2(f.hyperplane());
    +    QVERIFY(h2.isValid());
    +    QCOMPARE(h2.dimension(), 3);
    +    // h= h2;  // copy assignment disabled, ambiguous
    +    QhullHyperplane h3(q.qh(), h2.dimension(), h2.coordinates(), h2.offset());
    +    QCOMPARE(h2, h3);
    +    QhullHyperplane h5= h2; // copy constructor
    +    QVERIFY(h5==h2);
    +}//t_construct_qh
    +
    +void QhullHyperplane_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullHyperplane h= q.firstFacet().hyperplane();
    +    std::vector fs= h.toStdVector();
    +    QCOMPARE(fs.size(), 4u);
    +    double offset= fs.back();
    +    fs.pop_back();
    +    QCOMPARE(offset, -0.5);
    +
    +    double squareNorm= inner_product(fs.begin(), fs.end(), fs.begin(), 0.0);
    +    QCOMPARE(squareNorm, 1.0);
    +    QList qs= h.toQList();
    +    QCOMPARE(qs.size(), 4);
    +    double offset2= qs.takeLast();
    +    QCOMPARE(offset2, -0.5);
    +    double squareNorm2= std::inner_product(qs.begin(), qs.end(), qs.begin(), 0.0);
    +    QCOMPARE(squareNorm2, 1.0);
    +}//t_convert
    +
    +void QhullHyperplane_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QhullFacetList fs= q.facetList();
    +        QhullFacetListIterator i(fs);
    +        while(i.hasNext()){
    +            QhullFacet f= i.next();
    +            QhullHyperplane h= f.hyperplane();
    +            int id= f.id();
    +            cout << "h" << id << endl;
    +            QVERIFY(h.isValid());
    +            QCOMPARE(h.dimension(),3);
    +            const coordT *c= h.coordinates();
    +            coordT *c2= h.coordinates();
    +            QCOMPARE(c, c2);
    +            const coordT *c3= h.begin();
    +            QCOMPARE(c, c3);
    +            QCOMPARE(h.offset(), -0.5);
    +            int j= h.end()-h.begin();
    +            QCOMPARE(j, 3);
    +            double squareNorm= std::inner_product(h.begin(), h.end(), h.begin(), 0.0);
    +            QCOMPARE(squareNorm, 1.0);
    +        }
    +        QhullHyperplane h2= fs.first().hyperplane();
    +        QhullHyperplane h3= fs.last().hyperplane();
    +        QVERIFY(h2!=h3);
    +        QVERIFY(h3.coordinates()!=h2.coordinates());
    +    }
    +}//t_readonly
    +
    +void QhullHyperplane_test::
    +t_define()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QhullFacetList fs= q.facetList();
    +        QhullHyperplane h= fs.first().hyperplane();
    +        QhullHyperplane h2= h;
    +        QVERIFY(h==h2);
    +        QhullHyperplane h3= fs.last().hyperplane();
    +        QVERIFY(h2!=h3);
    +
    +        QhullHyperplane h4= h3;
    +        h4.defineAs(h2);
    +        QVERIFY(h2==h4);
    +        QhullHyperplane p5= h3;
    +        p5.defineAs(h2.dimension(), h2.coordinates(), h2.offset());
    +        QVERIFY(h2==p5);
    +        QhullHyperplane h6= h3;
    +        h6.setCoordinates(h2.coordinates());
    +        QCOMPARE(h2.coordinates(), h6.coordinates());
    +        h6.setOffset(h2.offset());
    +        QCOMPARE(h2.offset(), h6.offset());
    +        QVERIFY(h2==h6);
    +        h6.setDimension(2);
    +        QCOMPARE(h6.dimension(), 2);
    +        QVERIFY(h2!=h6);
    +    }
    +}//t_define
    +
    +void QhullHyperplane_test::
    +t_value()
    +{
    +    RboxPoints rcube("c G1");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullFacet f= q.firstFacet();
    +    QhullFacet f2= f.neighborFacets().at(0);
    +    const QhullHyperplane h= f.hyperplane();
    +    const QhullHyperplane h2= f2.hyperplane();   // At right angles
    +    double dist= h.distance(q.origin());
    +    QCOMPARE(dist, -1.0);
    +    double norm= h.norm();
    +    QCOMPARE(norm, 1.0);
    +    double angle= h.hyperplaneAngle(h2);
    +    cout << "angle " << angle << endl;
    +    QCOMPARE(angle+1.0, 1.0); // qFuzzyCompare does not work for 0.0
    +    QVERIFY(h==h);
    +    QVERIFY(h!=h2);
    +}//t_value
    +
    +void QhullHyperplane_test::
    +t_operator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    const QhullHyperplane h= q.firstFacet().hyperplane();
    +    //operator== and operator!= tested elsewhere
    +    const coordT *c= h.coordinates();
    +    for(int k=h.dimension(); k--; ){
    +        QCOMPARE(c[k], h[k]);
    +    }
    +    //h[0]= 10.0; // compiler error, const
    +    QhullHyperplane h2= q.firstFacet().hyperplane();
    +    h2[0]= 10.0;  // Overwrites Hyperplane coordinate!
    +    QCOMPARE(h2[0], 10.0);
    +}//t_operator
    +
    +void QhullHyperplane_test::
    +t_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullHyperplane h= q.firstFacet().hyperplane();
    +        QCOMPARE(h.count(), 3);
    +        QCOMPARE(h.size(), 3u);
    +        QhullHyperplane::Iterator i= h.begin();
    +        QhullHyperplane::iterator i2= h.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= h.begin();
    +        QVERIFY(i==i2);
    +        i2= h.end();
    +        QVERIFY(i!=i2);
    +        double d3= *i;
    +        i2--;
    +        double d2= *i2;
    +        QCOMPARE(d3, h[0]);
    +        QCOMPARE(d2, h[2]);
    +        QhullHyperplane::Iterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3), h[1]);
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        QhullHyperplane::ConstIterator i4= h.begin();
    +        QVERIFY(i==i4); // iterator COMP const_iterator
    +        QVERIFY(i<=i4);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4==i); // const_iterator COMP iterator
    +        QVERIFY(i4<=i);
    +        QVERIFY(i4>=i);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4<=i);
    +        QVERIFY(i2!=i4);
    +        QVERIFY(i2>i4);
    +        QVERIFY(i2>=i4);
    +        QVERIFY(i4!=i2);
    +        QVERIFY(i4i);
    +        QVERIFY(i4>=i);
    +
    +        i= h.begin();
    +        i2= h.begin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, h[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, h.begin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2 += 3, h.end());
    +        QCOMPARE(i2 -= 3, h.begin());
    +        QCOMPARE(i2+0, h.begin());
    +        QCOMPARE(i2+3, h.end());
    +        i2 += 3;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-3;
    +        QCOMPARE(i, h.begin());
    +        QCOMPARE(i2-i, 3);
    +
    +        //h.begin end tested above
    +
    +        // QhullHyperplane is const-only
    +    }
    +}//t_iterator
    +
    +void QhullHyperplane_test::
    +t_const_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullHyperplane h= q.firstFacet().hyperplane();
    +        QhullHyperplane::ConstIterator i= h.begin();
    +        QhullHyperplane::const_iterator i2= h.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= h.begin();
    +        QVERIFY(i==i2);
    +        i2= h.end();
    +        QVERIFY(i!=i2);
    +        double d3= *i;
    +        i2--;
    +        double d2= *i2;
    +        QCOMPARE(d3, h[0]);
    +        QCOMPARE(d2, h[2]);
    +        QhullHyperplane::ConstIterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3), h[1]);
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        // See t_iterator for const_iterator COMP iterator
    +
    +        i= h.begin();
    +        i2= h.constBegin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, h[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, h.constBegin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2+=3, h.constEnd());
    +        QCOMPARE(i2-=3, h.constBegin());
    +        QCOMPARE(i2+0, h.constBegin());
    +        QCOMPARE(i2+3, h.constEnd());
    +        i2 += 3;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-3;
    +        QCOMPARE(i, h.constBegin());
    +        QCOMPARE(i2-i, 3);
    +
    +        // QhullHyperplane is const-only
    +    }
    +}//t_const_iterator
    +
    +void QhullHyperplane_test::
    +t_qhullHyperplane_iterator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullHyperplane h = q.firstFacet().hyperplane();
    +    QhullHyperplaneIterator i2(h);
    +    QCOMPARE(h.dimension(), 3);
    +    QhullHyperplaneIterator i= h;
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    i.toFront();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    // i at front, i2 at end/back, 3 coordinates
    +    QCOMPARE(i.peekNext(), h[0]);
    +    QCOMPARE(i2.peekPrevious(), h[2]);
    +    QCOMPARE(i2.previous(), h[2]);
    +    QCOMPARE(i2.previous(), h[1]);
    +    QCOMPARE(i2.previous(), h[0]);
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), h[0]);
    +    // i.peekNext()= 1.0; // compiler error, i is const
    +    QCOMPARE(i.next(), h[0]);
    +    QCOMPARE(i.peekNext(), h[1]);
    +    QCOMPARE(i.next(), h[1]);
    +    QCOMPARE(i.next(), h[2]);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), h[0]);
    +}//t_qhullHyperplane_iterator
    +
    +void QhullHyperplane_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullHyperplane h= q.firstFacet().hyperplane();
    +        ostringstream os;
    +        os << "Hyperplane:\n";
    +        os << h;
    +        os << h.print("message");
    +        os << h.print(" and a message ", " offset ");
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count("1"), 3);
    +        // QCOMPARE(s.count(QRegExp("f\\d")), 3*7 + 13*3*2);
    +    }
    +}//t_io
    +
    +
    +}//orgQhull
    +
    +#include "moc/QhullHyperplane_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullLinkedList_test.cpp b/xs/src/qhull/src/qhulltest/QhullLinkedList_test.cpp
    new file mode 100644
    index 0000000000..e0cde4050f
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullLinkedList_test.cpp
    @@ -0,0 +1,330 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullLinkedList_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h"
    +
    +#include "libqhullcpp/QhullLinkedList.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +namespace orgQhull {
    +
    +class QhullLinkedList_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_element();
    +    void t_search();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_QhullLinkedList_iterator();
    +    void t_io();
    +};//QhullLinkedList_test
    +
    +void
    +add_QhullLinkedList_test()
    +{
    +    new QhullLinkedList_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullLinkedList_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullLinkedList_test::
    +t_construct()
    +{
    +    // QhullLinkedList vs; //private (compiler error).  No memory allocation
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q.facetCount(), 12);
    +        QhullVertexList vs = QhullVertexList(q.beginVertex(), q.endVertex());
    +        QCOMPARE(vs.count(), 8);
    +        QCOMPARE(vs.size(), 8u);
    +        QVERIFY(!vs.isEmpty());
    +        QhullVertexList vs2 = q.vertexList();
    +        QCOMPARE(vs2.count(), 8);
    +        QCOMPARE(vs2.size(),8u);
    +        QVERIFY(!vs2.isEmpty());
    +        QVERIFY(vs==vs2);
    +        // vs= vs2; // disabled.  Would not copy the vertices
    +        QhullVertexList vs3= vs2; // copy constructor
    +        QVERIFY(vs3==vs2);
    +    }
    +}//t_construct
    +
    +void QhullLinkedList_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q.facetCount(), 12);
    +        QhullVertexList vs = q.vertexList();
    +        QCOMPARE(vs.size(), 8u);
    +        QVERIFY(!vs.isEmpty());
    +        std::vector vs2= vs.toStdVector();
    +        QCOMPARE(vs2.size(), vs.size());
    +        QhullVertexList::Iterator i= vs.begin();
    +        for(int k= 0; k<(int)vs2.size(); k++){
    +            QCOMPARE(vs2[k], *i++);
    +        }
    +        QList vs3= vs.toQList();
    +        QCOMPARE(vs3.count(), vs.count());
    +        i= vs.begin();
    +        for(int k= 0; k
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullPointSet.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +
    +namespace orgQhull {
    +
    +class QhullPointSet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_element();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_search();
    +    void t_pointset_iterator();
    +    void t_io();
    +};//QhullPointSet_test
    +
    +void
    +add_QhullPointSet_test()
    +{
    +    new QhullPointSet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullPointSet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullPointSet_test::
    +t_construct()
    +{
    +    // Default constructor is disallowed (i.e., private)
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    int coplanarCount= 0;
    +    foreach(QhullFacet f, q.facetList()){
    +        QhullPointSet ps(q, f.getFacetT()->outsideset);
    +        QVERIFY(ps.isEmpty());
    +        QCOMPARE(ps.count(), 0);
    +        QCOMPARE(ps.size(), 0u);
    +        QhullPointSet ps2(q.qh(), f.getFacetT()->coplanarset);
    +        QVERIFY(!ps2.isEmpty());
    +        coplanarCount += ps2.count();
    +        QCOMPARE(ps2.count(), (int)ps2.size());
    +        QhullPointSet ps3(ps2);
    +        QVERIFY(!ps3.isEmpty());
    +        QCOMPARE(ps3.count(), ps2.count());
    +        QVERIFY(ps3==ps2);
    +        QVERIFY(ps3!=ps);
    +        QhullPointSet ps4= ps3;
    +        QVERIFY(ps4==ps2);
    +    }
    +    QCOMPARE(coplanarCount, 1000);
    +}//t_construct
    +
    +void QhullPointSet_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=1);   // Sometimes no coplanar points
    +    std::vector vs= ps.toStdVector();
    +    QCOMPARE(vs.size(), ps.size());
    +    QhullPoint p= ps[0];
    +    QhullPoint p2= vs[0];
    +    QCOMPARE(p, p2);
    +    QList qs= ps.toQList();
    +    QCOMPARE(qs.size(), static_cast(ps.size()));
    +    QhullPoint p3= qs[0];
    +    QCOMPARE(p3, p);
    +}//t_convert
    +
    +// readonly tested in t_construct
    +//   empty, isEmpty, ==, !=, size
    +
    +void QhullPointSet_test::
    +t_element()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPoint p= ps[0];
    +    QCOMPARE(p, ps[0]);
    +    QhullPoint p2= ps[ps.count()-1];
    +    QCOMPARE(ps.at(1), ps[1]);
    +    QCOMPARE(ps.second(), ps[1]);
    +    QCOMPARE(ps.first(), p);
    +    QCOMPARE(ps.front(), ps.first());
    +    QCOMPARE(ps.last(), p2);
    +    QCOMPARE(ps.back(), ps.last());
    +    QhullPoint p8(q);
    +    QCOMPARE(ps.value(2), ps[2]);
    +    QCOMPARE(ps.value(-1), p8);
    +    QCOMPARE(ps.value(ps.count()), p8);
    +    QCOMPARE(ps.value(ps.count(), p), p);
    +    QVERIFY(ps.value(1, p)!=p);
    +    QhullPointSet ps8= f.coplanarPoints();
    +    QhullPointSet::Iterator i= ps8.begin();
    +    foreach(QhullPoint p9, ps){  // Qt only
    +        QCOMPARE(p9.dimension(), 3);
    +        QCOMPARE(p9, *i++);
    +    }
    +}//t_element
    +
    +void QhullPointSet_test::
    +t_iterator()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPointSet::Iterator i= ps.begin();
    +    QhullPointSet::iterator i2= ps.begin();
    +    QVERIFY(i==i2);
    +    QVERIFY(i>=i2);
    +    QVERIFY(i<=i2);
    +    i= ps.begin();
    +    QVERIFY(i==i2);
    +    i2= ps.end();
    +    QVERIFY(i!=i2);
    +    QhullPoint p= *i;
    +    QCOMPARE(p.dimension(), q.dimension());
    +    QCOMPARE(p, ps[0]);
    +    i2--;
    +    QhullPoint p2= *i2;
    +    QCOMPARE(p2.dimension(), q.dimension());
    +    QCOMPARE(p2, ps.last());
    +    QhullPointSet::Iterator i5(i2);
    +    QCOMPARE(*i2, *i5);
    +    QhullPointSet::Iterator i3= i+1;
    +    QVERIFY(i!=i3);
    +    QCOMPARE(i[1], *i3);
    +    (i3= i)++;
    +    QCOMPARE((*i3)[0], ps[1][0]);
    +    QCOMPARE((*i3).dimension(), 3);
    +
    +    QVERIFY(i==i);
    +    QVERIFY(i!=i3);
    +    QVERIFY(ii);
    +    QVERIFY(i3>=i);
    +
    +    QhullPointSet::ConstIterator i4= ps.begin();
    +    QVERIFY(i==i4); // iterator COMP const_iterator
    +    QVERIFY(i<=i4);
    +    QVERIFY(i>=i4);
    +    QVERIFY(i4==i); // const_iterator COMP iterator
    +    QVERIFY(i4<=i);
    +    QVERIFY(i4>=i);
    +    QVERIFY(i>=i4);
    +    QVERIFY(i4<=i);
    +    QVERIFY(i2!=i4);
    +    QVERIFY(i2>i4);
    +    QVERIFY(i2>=i4);
    +    QVERIFY(i4!=i2);
    +    QVERIFY(i4i);
    +    QVERIFY(i4>=i);
    +    i4= ps.constBegin();
    +    QVERIFY(i==i4); // iterator COMP const_iterator
    +    QCOMPARE(i4+ps.count(), ps.constEnd());
    +
    +    i= ps.begin();
    +    i2= ps.begin();
    +    QCOMPARE(i, i2++);
    +    QCOMPARE(*i2, ps[1]);
    +    QCOMPARE(++i, i2);
    +    QCOMPARE(i, i2--);
    +    QCOMPARE(i2, ps.begin());
    +    QCOMPARE(--i, i2);
    +    QCOMPARE(i2+=ps.count(), ps.end());
    +    QCOMPARE(i2-=ps.count(), ps.begin());
    +    QCOMPARE(i2+0, ps.begin());
    +    QCOMPARE(i2+ps.count(), ps.end());
    +    i2 += ps.count();
    +    i= i2-0;
    +    QCOMPARE(i, i2);
    +    i= i2-ps.count();
    +    QCOMPARE(i, ps.begin());
    +    QCOMPARE(i2-i, ps.count());
    +
    +    //ps.begin end tested above
    +
    +    // QhullPointSet is const-only
    +}//t_iterator
    +
    +void QhullPointSet_test::
    +t_const_iterator()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPointSet::ConstIterator i= ps.begin();
    +    QhullPointSet::const_iterator i2= ps.begin();
    +    QVERIFY(i==i2);
    +    QVERIFY(i>=i2);
    +    QVERIFY(i<=i2);
    +
    +    // See t_iterator for const_iterator COMP iterator
    +
    +    i= ps.begin();
    +    QVERIFY(i==i2);
    +    i2= ps.end();
    +    QVERIFY(i!=i2);
    +    QhullPoint p= *i; // QhullPoint is the base class for QhullPointSet::iterator
    +    QCOMPARE(p.dimension(), q.dimension());
    +    QCOMPARE(p, ps[0]);
    +    i2--;
    +    QhullPoint p2= *i2;
    +    QCOMPARE(p2.dimension(), q.dimension());
    +    QCOMPARE(p2, ps.last());
    +    QhullPointSet::ConstIterator i5(i2);
    +    QCOMPARE(*i2, *i5);
    +
    +
    +    QhullPointSet::ConstIterator i3= i+1;
    +    QVERIFY(i!=i3);
    +    QCOMPARE(i[1], *i3);
    +
    +    QVERIFY(i==i);
    +    QVERIFY(i!=i3);
    +    QVERIFY(ii);
    +    QVERIFY(i3>=i);
    +
    +    // QhullPointSet is const-only
    +}//t_const_iterator
    +
    +
    +void QhullPointSet_test::
    +t_search()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPoint p= ps.first();
    +    QhullPoint p2= ps.last();
    +    QVERIFY(ps.contains(p));
    +    QVERIFY(ps.contains(p2));
    +    QVERIFY(p!=p2);
    +    QhullPoint p3= ps[2];
    +    QVERIFY(ps.contains(p3));
    +    QVERIFY(p!=p3);
    +    QCOMPARE(ps.indexOf(p), 0);
    +    QCOMPARE(ps.indexOf(p2), ps.count()-1);
    +    QCOMPARE(ps.indexOf(p3), 2);
    +    QhullPoint p4(q);
    +    QCOMPARE(ps.indexOf(p4), -1);
    +    QCOMPARE(ps.lastIndexOf(p), 0);
    +    QCOMPARE(ps.lastIndexOf(p2), ps.count()-1);
    +    QCOMPARE(ps.lastIndexOf(p3), 2);
    +    QCOMPARE(ps.lastIndexOf(p4), -1);
    +}//t_search
    +
    +void QhullPointSet_test::
    +t_pointset_iterator()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps2= f.outsidePoints();
    +    QVERIFY(ps2.count()==0); // No outside points after constructing the convex hull
    +    QhullPointSetIterator i2= ps2;
    +    QCOMPARE(i2.countRemaining(), 0);
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    i2.toBack();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPointSetIterator i(ps);
    +    i2= ps;
    +    QCOMPARE(i2.countRemaining(), ps.count());
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    QCOMPARE(i2.countRemaining(), 0);
    +    i.toFront();
    +    QCOMPARE(i.countRemaining(), ps.count());
    +    QCOMPARE(i2.countRemaining(), 0);
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    QhullPoint p= ps[0];
    +    QhullPoint p2(ps[0]);
    +    QCOMPARE(p, p2);
    +    QVERIFY(p==p2);
    +    QhullPoint p3(ps.last());
    + // p2[0]= 0.0;
    +    QVERIFY(p==p2);
    +    QCOMPARE(i2.peekPrevious(), p3);
    +    QCOMPARE(i2.previous(), p3);
    +    QCOMPARE(i2.previous(), ps[ps.count()-2]);
    +    QVERIFY(i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), p);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), p);
    +    QCOMPARE(i.countRemaining(), ps.count()-1);
    +    QhullPoint p4= i.peekNext();
    +    QVERIFY(p4!=p3);
    +    QCOMPARE(i.next(), p4);
    +    QVERIFY(i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), p);
    +}//t_pointset_iterator
    +
    +void QhullPointSet_test::
    +t_io()
    +{
    +    ostringstream os;
    +    RboxPoints rcube("c W0 120");
    +    Qhull q(rcube,"Qc");  // cube with 100 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    os << "QhullPointSet from coplanarPoints\n" << ps << endl;
    +    os << ps.print("\nWith message\n");
    +    os << ps.printIdentifiers("\nCoplanar points: ");
    +    os << "\nAs a point set:\n";
    +    os << ps;
    +    cout << os.str();
    +    QString s= QString::fromStdString(os.str());
    +    QCOMPARE(s.count(" 0.5\n"), 3*ps.count());
    +    QCOMPARE(s.count("p"), ps.count()+4);
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullPointSet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullPoint_test.cpp b/xs/src/qhull/src/qhulltest/QhullPoint_test.cpp
    new file mode 100644
    index 0000000000..1528086a15
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullPoint_test.cpp
    @@ -0,0 +1,437 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullPoint_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/Coordinates.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullPoint_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_readonly();
    +    void t_define();
    +    void t_operator();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_qhullpoint_iterator();
    +    void t_method();
    +    void t_io();
    +};//QhullPoint_test
    +
    +void
    +add_QhullPoint_test()
    +{
    +    new QhullPoint_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each test
    +void QhullPoint_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullPoint_test::
    +t_construct()
    +{
    +    QhullPoint p12;
    +    QVERIFY(!p12.isValid());
    +    QCOMPARE(p12.coordinates(), (coordT *)0);
    +    QCOMPARE(p12.dimension(), 0);
    +    QCOMPARE(p12.qh(), (QhullQh *)0);
    +    QCOMPARE(p12.id(), -3);
    +    QCOMPARE(p12.begin(), p12.end());
    +    QCOMPARE(p12.constBegin(), p12.constEnd());
    +
    +    RboxPoints rcube("c");
    +    Qhull q(rcube, "Qt QR0");  // triangulation of rotated unit cube
    +    QhullPoint p(q);
    +    QVERIFY(!p.isValid());
    +    QCOMPARE(p.dimension(),3);
    +    QCOMPARE(p.coordinates(),static_cast(0));
    +    QhullPoint p7(q.qh());
    +    QCOMPARE(p, p7);
    +
    +    // copy constructor and copy assignment
    +    QhullVertex v2(q.beginVertex());
    +    QhullPoint p2(v2.point());
    +    QVERIFY(p2.isValid());
    +    QCOMPARE(p2.dimension(),3);
    +    QVERIFY(p2!=p12);
    +    p= p2;
    +    QCOMPARE(p, p2);
    +
    +    QhullPoint p3(q, p2.dimension(), p2.coordinates());
    +    QCOMPARE(p3, p2);
    +    QhullPoint p8(q, p2.coordinates()); // Qhull defines dimension
    +    QCOMPARE(p8, p2);
    +    QhullPoint p9(q.qh(), p2.dimension(), p2.coordinates());
    +    QCOMPARE(p9, p2);
    +    QhullPoint p10(q.qh(), p2.coordinates()); // Qhull defines dimension
    +    QCOMPARE(p10, p2);
    +
    +    Coordinates c;
    +    c << 0.0 << 0.0 << 0.0;
    +    QhullPoint p6(q, c);
    +    QCOMPARE(p6, q.origin());
    +    QhullPoint p11(q.qh(), c);
    +    QCOMPARE(p11, q.origin());
    +
    +    QhullPoint p5= p2; // copy constructor
    +    QVERIFY(p5==p2);
    +}//t_construct
    +
    +void QhullPoint_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullVertex v= q.firstVertex();
    +    QhullPoint p= v.point();
    +    std::vector vs= p.toStdVector();
    +    QCOMPARE(vs.size(), 3u);
    +    for(int k=3; k--; ){
    +        QCOMPARE(vs[k], p[k]);
    +    }
    +    QList qs= p.toQList();
    +    QCOMPARE(qs.size(), 3);
    +    for(int k=3; k--; ){
    +        QCOMPARE(qs[k], p[k]);
    +    }
    +}//t_convert
    +
    +void QhullPoint_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QhullVertexList vs= q.vertexList();
    +        cout << "Point ids in 'rbox c'\n";
    +        QhullVertexListIterator i(vs);
    +        while(i.hasNext()){
    +            QhullPoint p= i.next().point();
    +            int id= p.id();
    +            cout << "p" << id << endl;
    +            QVERIFY(p.isValid());
    +            QCOMPARE(p.dimension(),3);
    +            QCOMPARE(id, p.id());
    +            QVERIFY(p.id()>=0 && p.id()<9);
    +            const coordT *c= p.coordinates();
    +            coordT *c2= p.coordinates();
    +            QCOMPARE(c, c2);
    +            QCOMPARE(p.dimension(), 3);
    +            QCOMPARE(q.qh(), p.qh());
    +        }
    +        QhullPoint p2= vs.first().point();
    +        QhullPoint p3= vs.last().point();
    +        QVERIFY(p2!=p3);
    +        QVERIFY(p3.coordinates()!=p2.coordinates());
    +    }
    +}//t_readonly
    +
    +void QhullPoint_test::
    +t_define()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QhullVertexList vs= q.vertexList();
    +        QhullPoint p= vs.first().point();
    +        QhullPoint p2= p;
    +        QVERIFY(p==p2);
    +        QhullPoint p3= vs.last().point();
    +        QVERIFY(p2!=p3);
    +        int idx= (p3.coordinates()-p2.coordinates())/p2.dimension();
    +        QVERIFY(idx>-8 && idx<8);
    +        p2.advancePoint(idx);
    +        QVERIFY(p2==p3);
    +        p2.advancePoint(-idx);
    +        QVERIFY(p2==p);
    +        p2.advancePoint(0);
    +        QVERIFY(p2==p);
    +
    +        QhullPoint p4= p3;
    +        QVERIFY(p4==p3);
    +        p4.defineAs(p2);
    +        QVERIFY(p2==p4);
    +        QhullPoint p5= p3;
    +        p5.defineAs(p2.dimension(), p2.coordinates());
    +        QVERIFY(p2==p5);
    +        QhullPoint p6= p3;
    +        p6.setCoordinates(p2.coordinates());
    +        QCOMPARE(p2.coordinates(), p6.coordinates());
    +        QVERIFY(p2==p6);
    +        p6.setDimension(2);
    +        QCOMPARE(p6.dimension(), 2);
    +        QVERIFY(p2!=p6);
    +    }
    +}//t_define
    +
    +void QhullPoint_test::
    +t_operator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    const QhullPoint p= q.firstVertex().point();
    +    //operator== and operator!= tested elsewhere
    +    const coordT *c= p.coordinates();
    +    for(int k=p.dimension(); k--; ){
    +        QCOMPARE(c[k], p[k]);
    +    }
    +    //p[0]= 10.0; // compiler error, const
    +    QhullPoint p2= q.firstVertex().point();
    +    p2[0]= 10.0;  // Overwrites point coordinate
    +    QCOMPARE(p2[0], 10.0);
    +}//t_operator
    +
    +void QhullPoint_test::
    +t_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullPoint p2(q);
    +        QCOMPARE(p2.begin(), p2.end());
    +
    +        QhullPoint p= q.firstVertex().point();
    +        QhullPoint::Iterator i= p.begin();
    +        QhullPoint::iterator i2= p.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= p.begin();
    +        QVERIFY(i==i2);
    +        i2= p.end();
    +        QVERIFY(i!=i2);
    +        double d3= *i;
    +        i2--;
    +        double d2= *i2;
    +        QCOMPARE(d3, p[0]);
    +        QCOMPARE(d2, p[2]);
    +        QhullPoint::Iterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3), p[1]);
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        QhullPoint::ConstIterator i4= p.begin();
    +        QVERIFY(i==i4); // iterator COMP const_iterator
    +        QVERIFY(i<=i4);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4==i); // const_iterator COMP iterator
    +        QVERIFY(i4<=i);
    +        QVERIFY(i4>=i);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4<=i);
    +        QVERIFY(i2!=i4);
    +        QVERIFY(i2>i4);
    +        QVERIFY(i2>=i4);
    +        QVERIFY(i4!=i2);
    +        QVERIFY(i4i);
    +        QVERIFY(i4>=i);
    +
    +        i= p.begin();
    +        i2= p.begin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, p[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, p.begin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2 += 3, p.end());
    +        QCOMPARE(i2 -= 3, p.begin());
    +        QCOMPARE(i2+0, p.begin());
    +        QCOMPARE(i2+3, p.end());
    +        i2 += 3;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-3;
    +        QCOMPARE(i, p.begin());
    +        QCOMPARE(i2-i, 3);
    +
    +        //p.begin end tested above
    +
    +        // QhullPoint is const-only
    +    }
    +}//t_iterator
    +
    +void QhullPoint_test::
    +t_const_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullPoint p= q.firstVertex().point();
    +        QhullPoint::ConstIterator i= p.begin();
    +        QhullPoint::const_iterator i2= p.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= p.begin();
    +        QVERIFY(i==i2);
    +        i2= p.end();
    +        QVERIFY(i!=i2);
    +        double d3= *i;
    +        i2--;
    +        double d2= *i2;
    +        QCOMPARE(d3, p[0]);
    +        QCOMPARE(d2, p[2]);
    +        QhullPoint::ConstIterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3), p[1]);
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        // See t_iterator for const_iterator COMP iterator
    +
    +        i= p.begin();
    +        i2= p.constBegin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, p[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, p.constBegin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2+=3, p.constEnd());
    +        QCOMPARE(i2-=3, p.constBegin());
    +        QCOMPARE(i2+0, p.constBegin());
    +        QCOMPARE(i2+3, p.constEnd());
    +        i2 += 3;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-3;
    +        QCOMPARE(i, p.constBegin());
    +        QCOMPARE(i2-i, 3);
    +
    +        // QhullPoint is const-only
    +    }
    +}//t_const_iterator
    +
    +void QhullPoint_test::
    +t_qhullpoint_iterator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +
    +    QhullPoint p2(q);
    +    QhullPointIterator i= p2;
    +    QCOMPARE(p2.dimension(), 3);
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    QhullPoint p = q.firstVertex().point();
    +    QhullPointIterator i2(p);
    +    QCOMPARE(p.dimension(), 3);
    +    i= p;
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    i.toFront();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    // i at front, i2 at end/back, 3 coordinates
    +    QCOMPARE(i.peekNext(), p[0]);
    +    QCOMPARE(i2.peekPrevious(), p[2]);
    +    QCOMPARE(i2.previous(), p[2]);
    +    QCOMPARE(i2.previous(), p[1]);
    +    QCOMPARE(i2.previous(), p[0]);
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), p[0]);
    +    // i.peekNext()= 1.0; // compiler error, i is const
    +    QCOMPARE(i.next(), p[0]);
    +    QCOMPARE(i.peekNext(), p[1]);
    +    QCOMPARE(i.next(), p[1]);
    +    QCOMPARE(i.next(), p[2]);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), p[0]);
    +}//t_qhullpoint_iterator
    +
    +void QhullPoint_test::
    +t_method()
    +{
    +    // advancePoint tested above
    +    RboxPoints rcube("c");
    +    Qhull q(rcube, "");
    +    QhullPoint p = q.firstVertex().point();
    +    double dist= p.distance(q.origin());
    +    QCOMPARE(dist, sqrt(double(2.0+1.0))/2); // half diagonal of unit cube
    +}//t_qhullpoint_iterator
    +
    +void QhullPoint_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullPoint p= q.beginVertex().point();
    +        ostringstream os;
    +        os << "Point:\n";
    +        os << p;
    +        os << "Point w/ print:\n";
    +        os << p.print(" message ");
    +        os << p.printWithIdentifier(" Point with id and a message ");
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count("p"), 2);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullPoint_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullPoints_test.cpp b/xs/src/qhull/src/qhulltest/QhullPoints_test.cpp
    new file mode 100644
    index 0000000000..c2d8347e28
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullPoints_test.cpp
    @@ -0,0 +1,561 @@
    +/****************************************************************************
    +**
    +** Copyright (p) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullPoints_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled header
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullPoints.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +
    +namespace orgQhull {
    +
    +class QhullPoints_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct_q();
    +    void t_construct_qh();
    +    void t_convert();
    +    void t_getset();
    +    void t_element();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_search();
    +    void t_points_iterator();
    +    void t_io();
    +};//QhullPoints_test
    +
    +void
    +add_QhullPoints_test()
    +{
    +    new QhullPoints_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullPoints_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullPoints_test::
    +t_construct_q()
    +{
    +    Qhull q;
    +    QhullPoints ps(q);
    +    QCOMPARE(ps.dimension(), 0);
    +    QVERIFY(ps.isEmpty());
    +    QCOMPARE(ps.count(), 0);
    +    QCOMPARE(ps.size(), 0u);
    +    QCOMPARE(ps.coordinateCount(), 0);
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps2(q);
    +    ps2.defineAs(2, 6, c);
    +    QCOMPARE(ps2.dimension(), 2);
    +    QVERIFY(!ps2.isEmpty());
    +    QCOMPARE(ps2.count(), 3);
    +    QCOMPARE(ps2.size(), 3u);
    +    QCOMPARE(ps2.coordinates(), c);
    +    QhullPoints ps3(q, 2, 6, c);
    +    QCOMPARE(ps3.dimension(), 2);
    +    QVERIFY(!ps3.isEmpty());
    +    QCOMPARE(ps3.coordinates(), ps2.coordinates());
    +    QVERIFY(ps3==ps2);
    +    QVERIFY(ps3!=ps);
    +    QhullPoints ps4= ps3;
    +    QVERIFY(ps4==ps3);
    +    // ps4= ps3; //compiler error
    +    QhullPoints ps5(ps4);
    +    QVERIFY(ps5==ps4);
    +    QVERIFY(!(ps5!=ps4));
    +    coordT c2[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps6(q, 2, 6, c2);
    +    QVERIFY(ps6==ps2);
    +
    +    RboxPoints rbox("c D2");
    +    Qhull q2(rbox, "");
    +    QhullPoints ps8(q2);
    +    QCOMPARE(ps8.dimension(), 2);
    +    QCOMPARE(ps8.count(), 0);
    +    QCOMPARE(ps8.size(), 0u);
    +    QCOMPARE(ps8.coordinateCount(), 0);
    +    coordT c3[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps9(q2, 6, c3);
    +    QCOMPARE(ps9.dimension(), 2);
    +    QCOMPARE(ps9.coordinateCount(), 6);
    +    QCOMPARE(ps9.count(), 3);
    +    QCOMPARE(ps9.coordinates(), c3);
    +    QCOMPARE(ps9, ps2);  // DISTround
    +    c3[1]= 1.0+1e-17;
    +    QCOMPARE(ps9, ps2);  // DISTround
    +    c3[1]= 1.0+1e-15;
    +    QVERIFY(ps9!=ps2);  // DISTround
    +
    +    ps9.defineAs(6, c2);
    +    QCOMPARE(ps9.dimension(), 2);
    +    QCOMPARE(ps9.coordinateCount(), 6);
    +    QCOMPARE(ps9.count(), 3);
    +    QCOMPARE(ps9.coordinates(), c2);
    +}//t_construct_q
    +
    +void QhullPoints_test::
    +t_construct_qh()
    +{
    +    Qhull q;
    +    QhullQh *qh= q.qh();
    +    QhullPoints ps(qh);
    +    QCOMPARE(ps.dimension(), 0);
    +    QVERIFY(ps.isEmpty());
    +    QCOMPARE(ps.count(), 0);
    +    QCOMPARE(ps.size(), 0u);
    +    QCOMPARE(ps.coordinateCount(), 0);
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps2(qh);
    +    ps2.defineAs(2, 6, c);
    +    QCOMPARE(ps2.dimension(), 2);
    +    QVERIFY(!ps2.isEmpty());
    +    QCOMPARE(ps2.count(), 3);
    +    QCOMPARE(ps2.size(), 3u);
    +    QCOMPARE(ps2.coordinates(), c);
    +    QhullPoints ps3(qh, 2, 6, c);
    +    QCOMPARE(ps3.dimension(), 2);
    +    QVERIFY(!ps3.isEmpty());
    +    QCOMPARE(ps3.coordinates(), ps2.coordinates());
    +    QVERIFY(ps3==ps2);
    +    QVERIFY(ps3!=ps);
    +    QhullPoints ps4= ps3;
    +    QVERIFY(ps4==ps3);
    +    // ps4= ps3; //compiler error
    +    QhullPoints ps5(ps4);
    +    QVERIFY(ps5==ps4);
    +    QVERIFY(!(ps5!=ps4));
    +    coordT c2[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps6(qh, 2, 6, c2);
    +    QVERIFY(ps6==ps2);
    +
    +    RboxPoints rbox("c D2");
    +    Qhull q2(rbox, "");
    +    QhullPoints ps8(q2.qh());
    +    QCOMPARE(ps8.dimension(), 2);
    +    QCOMPARE(ps8.count(), 0);
    +    QCOMPARE(ps8.size(), 0u);
    +    QCOMPARE(ps8.coordinateCount(), 0);
    +    coordT c3[]= {10.0, 11.0, 12.0, 13.0, 14.0, 15.0};
    +    QhullPoints ps9(q2.qh(), 6, c3);
    +    QCOMPARE(ps9.dimension(), 2);
    +    QCOMPARE(ps9.coordinateCount(), 6);
    +    QCOMPARE(ps9.count(), 3);
    +    QCOMPARE(ps9.coordinates(), c3);
    +    ps9.defineAs(6, c2);
    +    QCOMPARE(ps9.dimension(), 2);
    +    QCOMPARE(ps9.coordinateCount(), 6);
    +    QCOMPARE(ps9.count(), 3);
    +    QCOMPARE(ps9.coordinates(), c2);
    +}//t_construct_qh
    +
    +void QhullPoints_test::
    +t_convert()
    +{
    +    Qhull q;
    +    //defineAs tested above
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps(q, 3, 6, c);
    +    QCOMPARE(ps.dimension(), 3);
    +    QCOMPARE(ps.size(), 2u);
    +    const coordT *c2= ps.constData();
    +    QCOMPARE(c, c2);
    +    const coordT *c3= ps.data();
    +    QCOMPARE(c, c3);
    +    coordT *c4= ps.data();
    +    QCOMPARE(c, c4);
    +    std::vector vs= ps.toStdVector();
    +    QCOMPARE(vs.size(), 2u);
    +    QhullPoint p= vs[1];
    +    QCOMPARE(p[2], 5.0);
    +    QList qs= ps.toQList();
    +    QCOMPARE(qs.size(), 2);
    +    QhullPoint p2= qs[1];
    +    QCOMPARE(p2[2], 5.0);
    +}//t_convert
    +
    +void QhullPoints_test::
    +t_getset()
    +{
    +    Qhull q;
    +    //See t_construct for coordinates, count, defineAs, dimension, isempty, ==, !=, size
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps(q, 3, 6, c);
    +    QhullPoints ps2(q, 3, 6, c);
    +    QCOMPARE(ps2.dimension(), 3);
    +    QCOMPARE(ps2.coordinates(), c);
    +    QCOMPARE(ps2.count(), 2);
    +    QCOMPARE(ps2.coordinateCount(), 6);
    +    coordT c2[]= {-1.0, -2.0, -3.0, -4.0, -5.0, -6.0};
    +    ps2.defineAs(6, c2);
    +    QCOMPARE(ps2.coordinates(), c2);
    +    QCOMPARE(ps2.count(), 2);
    +    QCOMPARE(ps2.size(), 2u);
    +    QCOMPARE(ps2.dimension(), 3);
    +    QVERIFY(!ps2.isEmpty());
    +    QVERIFY(ps!=ps2);
    +    // ps2= ps; // assignment not available, compiler error
    +    ps2.defineAs(ps);
    +    QVERIFY(ps==ps2);
    +    ps2.setDimension(2);
    +    QCOMPARE(ps2.dimension(), 2);
    +    QCOMPARE(ps2.coordinates(), c);
    +    QVERIFY(!ps2.isEmpty());
    +    QCOMPARE(ps2.count(), 3);
    +    QCOMPARE(ps2.size(), 3u);
    +    QVERIFY(ps!=ps2);
    +    QhullPoints ps3(ps2);
    +    ps3.setDimension(3);
    +    ps3.defineAs(5, c2);
    +    QCOMPARE(ps3.count(), 1);
    +    QCOMPARE(ps3.extraCoordinatesCount(), 2);
    +    QCOMPARE(ps3.extraCoordinates()[0], -4.0);
    +    QVERIFY(ps3.includesCoordinates(ps3.data()));
    +    QVERIFY(ps3.includesCoordinates(ps3.data()+ps3.count()-1));
    +    QVERIFY(!ps3.includesCoordinates(ps3.data()-1));
    +    QVERIFY(!ps3.includesCoordinates(ps3.data()+ps3.coordinateCount()));
    +}//t_getset
    +
    +
    +void QhullPoints_test::
    +t_element()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps(q, 2, 6, c);
    +    QCOMPARE(ps.count(), 3);
    +    QhullPoint p(q, 2, c);
    +    QCOMPARE(ps[0], p);
    +    QCOMPARE(ps.at(1), ps[1]);
    +    QCOMPARE(ps.first(), p);
    +    QCOMPARE(ps.front(), ps.first());
    +    QCOMPARE(ps.last(), ps.at(2));
    +    QCOMPARE(ps.back(), ps.last());
    +    QhullPoints ps2= ps.mid(2);
    +    QCOMPARE(ps2.count(), 1);
    +    QhullPoints ps3= ps.mid(3);
    +    QVERIFY(ps3.isEmpty());
    +    QhullPoints ps4= ps.mid(10);
    +    QVERIFY(ps4.isEmpty());
    +    QhullPoints ps5= ps.mid(-1);
    +    QVERIFY(ps5.isEmpty());
    +    QhullPoints ps6= ps.mid(1, 1);
    +    QCOMPARE(ps6.count(), 1);
    +    QCOMPARE(ps6[0], ps[1]);
    +    QhullPoints ps7= ps.mid(1, 10);
    +    QCOMPARE(ps7.count(), 2);
    +    QCOMPARE(ps7[1], ps[2]);
    +    QhullPoint p8(q);
    +    QCOMPARE(ps.value(2), ps[2]);
    +    QCOMPARE(ps.value(-1), p8);
    +    QCOMPARE(ps.value(3), p8);
    +    QCOMPARE(ps.value(3, p), p);
    +    QVERIFY(ps.value(1, p)!=p);
    +    foreach(QhullPoint p9, ps){  // Qt only
    +        QCOMPARE(p9.dimension(), 2);
    +        QVERIFY(p9[0]==0.0 || p9[0]==2.0 || p9[0]==4.0);
    +    }
    +}//t_element
    +
    +void QhullPoints_test::
    +t_iterator()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0};
    +    QhullPoints ps(q, 1, 3, c);
    +    QCOMPARE(ps.dimension(), 1);
    +    QhullPoints::Iterator i(ps);
    +    QhullPoints::iterator i2= ps.begin();
    +    QVERIFY(i==i2);
    +    QVERIFY(i>=i2);
    +    QVERIFY(i<=i2);
    +    i= ps.begin();
    +    QVERIFY(i==i2);
    +    i2= ps.end();
    +    QVERIFY(i!=i2);
    +    QhullPoint p(i); // QhullPoint is the base class for QhullPoints::iterator
    +    QCOMPARE(p.dimension(), ps.dimension());
    +    QCOMPARE(p.coordinates(), ps.coordinates());
    +    i2--;
    +    QhullPoint p2= *i2;
    +    QCOMPARE(p[0], 0.0);
    +    QCOMPARE(p2[0], 2.0);
    +    QhullPoints::Iterator i5(i2);
    +    QCOMPARE(*i2, *i5);
    +    coordT c3[]= {0.0, -1.0, -2.0};
    +    QhullPoints::Iterator i3(q, 1, c3);
    +    QVERIFY(i!=i3);
    +    QCOMPARE(*i, *i3);
    +
    +    (i3= i)++;
    +    QCOMPARE((*i3)[0], 1.0);
    +    QCOMPARE(i3->dimension(), 1);
    +    QCOMPARE(i3[0][0], 1.0);
    +    QCOMPARE(i3[0], ps[1]);
    +
    +    QVERIFY(i==i);
    +    QVERIFY(i!=i2);
    +    QVERIFY(ii);
    +    QVERIFY(i2>=i);
    +
    +    QhullPoints::ConstIterator i4(q, 1, c);
    +    QVERIFY(i==i4); // iterator COMP const_iterator
    +    QVERIFY(i<=i4);
    +    QVERIFY(i>=i4);
    +    QVERIFY(i4==i); // const_iterator COMP iterator
    +    QVERIFY(i4<=i);
    +    QVERIFY(i4>=i);
    +    QVERIFY(i>=i4);
    +    QVERIFY(i4<=i);
    +    QVERIFY(i2!=i4);
    +    QVERIFY(i2>i4);
    +    QVERIFY(i2>=i4);
    +    QVERIFY(i4!=i2);
    +    QVERIFY(i4i);
    +    QVERIFY(i4>=i);
    +
    +    i= ps.begin();
    +    i2= ps.begin();
    +    QCOMPARE(i, i2++);
    +    QCOMPARE(*i2, ps[1]);
    +    QCOMPARE(++i, i2);
    +    QCOMPARE(i, i2--);
    +    QCOMPARE(i2, ps.begin());
    +    QCOMPARE(--i, i2);
    +    QCOMPARE(i2+=3, ps.end());
    +    QCOMPARE(i2-=3, ps.begin());
    +    QCOMPARE(i2+0, ps.begin());
    +    QCOMPARE(i2+3, ps.end());
    +    i2 += 3;
    +    i= i2-0;
    +    QCOMPARE(i, i2);
    +    i= i2-3;
    +    QCOMPARE(i, ps.begin());
    +    QCOMPARE(i2-i, 3);
    +
    +    //ps.begin end tested above
    +
    +    // QhullPoints is const-only
    +}//t_iterator
    +
    +void QhullPoints_test::
    +t_const_iterator()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0};
    +    const QhullPoints ps(q, 1, 3, c);
    +    QhullPoints::ConstIterator i(ps);
    +    QhullPoints::const_iterator i2= ps.begin();
    +    QVERIFY(i==i2);
    +    QVERIFY(i>=i2);
    +    QVERIFY(i<=i2);
    +    i= ps.begin();
    +    QVERIFY(i==i2);
    +    i2= ps.end();
    +    QVERIFY(i!=i2);
    +    QhullPoint p(i);
    +    QCOMPARE(p.dimension(), ps.dimension());
    +    QCOMPARE(p.coordinates(), ps.coordinates());
    +    i2--;
    +    QhullPoint p2= *i2;
    +    QCOMPARE(p[0], 0.0);
    +    QCOMPARE(p2[0], 2.0);
    +    QhullPoints::ConstIterator i5(i2);
    +    QCOMPARE(*i2, *i5);
    +    coordT c3[]= {0.0, -1.0, -2.0};
    +    QhullPoints::ConstIterator i3(q, 1, c3);
    +    QVERIFY(i!=i3);
    +    QCOMPARE(*i, *i3);
    +
    +    (i3= i)++;
    +    QCOMPARE((*i3)[0], 1.0);
    +    QCOMPARE(i3->dimension(), 1);
    +    QCOMPARE(i3[0][0], 1.0);
    +    QCOMPARE(i3[0][0], 1.0);
    +    QCOMPARE(i3[0], ps[1]);
    +
    +    QVERIFY(i==i);
    +    QVERIFY(i!=i2);
    +    QVERIFY(ii);
    +    QVERIFY(i2>=i);
    +
    +    // See t_iterator for const_iterator COMP iterator
    +
    +    i= ps.begin();
    +    i2= ps.constBegin();
    +    QCOMPARE(i, i2++);
    +    QCOMPARE(*i2, ps[1]);
    +    QCOMPARE(++i, i2);
    +    QCOMPARE(i, i2--);
    +    QCOMPARE(i2, ps.constBegin());
    +    QCOMPARE(--i, i2);
    +    QCOMPARE(i2+=3, ps.constEnd());
    +    QCOMPARE(i2-=3, ps.constBegin());
    +    QCOMPARE(i2+0, ps.constBegin());
    +    QCOMPARE(i2+3, ps.constEnd());
    +    i2 += 3;
    +    i= i2-0;
    +    QCOMPARE(i, i2);
    +    i= i2-3;
    +    QCOMPARE(i, ps.constBegin());
    +    QCOMPARE(i2-i, 3);
    +
    +    // QhullPoints is const-only
    +}//t_const_iterator
    +
    +
    +void QhullPoints_test::
    +t_search()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 0, 1};
    +    QhullPoints ps(q, 2, 8, c); //2-d array of 4 points
    +    QhullPoint p= ps.first();
    +    QhullPoint p2= ps.last();
    +    QVERIFY(ps.contains(p));
    +    QVERIFY(ps.contains(p2));
    +    QVERIFY(p==p2);
    +    QhullPoint p5= ps[2];
    +    QVERIFY(p!=p5);
    +    QVERIFY(ps.contains(p5));
    +    coordT c2[]= {0.0, 1.0, 2.0, 3.0};
    +    QhullPoint p3(q, 2, c2); //2-d point
    +    QVERIFY(ps.contains(p3));
    +    QhullPoint p4(q, 3, c2); //3-d point
    +    QVERIFY(!ps.contains(p4));
    +    p4.defineAs(2, c); //2-d point
    +    QVERIFY(ps.contains(p4));
    +    p4.defineAs(2, c+1); //2-d point
    +    QVERIFY(!ps.contains(p4));
    +    QhullPoint p6(q, 2, c2+2); //2-d point
    +    QCOMPARE(ps.count(p), 2);
    +    QCOMPARE(ps.count(p2), 2);
    +    QCOMPARE(ps.count(p3), 2);
    +    QCOMPARE(ps.count(p4), 0);
    +    QCOMPARE(ps.count(p6), 1);
    +    QCOMPARE(ps.indexOf(&ps[0][0]), 0);
    +    //QCOMPARE(ps.indexOf(ps.end()), -1); //ps.end() is a QhullPoint which may match
    +    QCOMPARE(ps.indexOf(0), -1);
    +    QCOMPARE(ps.indexOf(&ps[3][0]), 3);
    +    QCOMPARE(ps.indexOf(&ps[3][1], QhullError::NOthrow), 3);
    +    QCOMPARE(ps.indexOf(ps.data()+ps.coordinateCount(), QhullError::NOthrow), -1);
    +    QCOMPARE(ps.indexOf(p), 0);
    +    QCOMPARE(ps.indexOf(p2), 0);
    +    QCOMPARE(ps.indexOf(p3), 0);
    +    QCOMPARE(ps.indexOf(p4), -1);
    +    QCOMPARE(ps.indexOf(p5), 2);
    +    QCOMPARE(ps.indexOf(p6), 1);
    +    QCOMPARE(ps.lastIndexOf(p), 3);
    +    QCOMPARE(ps.lastIndexOf(p4), -1);
    +    QCOMPARE(ps.lastIndexOf(p6), 1);
    +    QhullPoints ps3(q);
    +    QCOMPARE(ps3.indexOf(ps3.data()), -1);
    +    QCOMPARE(ps3.indexOf(ps3.data()+1, QhullError::NOthrow), -1);
    +    QCOMPARE(ps3.indexOf(p), -1);
    +    QCOMPARE(ps3.lastIndexOf(p), -1);
    +    QhullPoints ps4(q, 2, 0, c);
    +    QCOMPARE(ps4.indexOf(p), -1);
    +    QCOMPARE(ps4.lastIndexOf(p), -1);
    +}//t_search
    +
    +void QhullPoints_test::
    +t_points_iterator()
    +{
    +    Qhull q;
    +    coordT c2[]= {0.0};
    +    QhullPoints ps2(q, 0, 0, c2); // 0-dimensional
    +    QhullPointsIterator i2= ps2;
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    i2.toBack();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps(q, 3, 6, c); // 3-dimensional
    +    QhullPointsIterator i(ps);
    +    i2= ps;
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    i.toFront();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    QhullPoint p= ps[0];
    +    QhullPoint p2(ps[0]);
    +    QCOMPARE(p, p2);
    +    QVERIFY(p==p2);
    +    QhullPoint p3(ps[1]);
    + // p2[0]= 0.0;
    +    QVERIFY(p==p2);
    +    QCOMPARE(i2.peekPrevious(), p3);
    +    QCOMPARE(i2.previous(), p3);
    +    QCOMPARE(i2.previous(), p);
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), p);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), p);
    +    QCOMPARE(i.peekNext(), p3);
    +    QCOMPARE(i.next(), p3);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), p);
    +}//t_points_iterator
    +
    +void QhullPoints_test::
    +t_io()
    +{
    +    Qhull q;
    +    QhullPoints ps(q);
    +    ostringstream os;
    +    os << "Empty QhullPoints\n" << ps << endl;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps2(q, 3, 6, c); // 3-dimensional explicit
    +    os << "QhullPoints from c[]\n" << ps2 << endl;
    +
    +    RboxPoints rcube("c");
    +    Qhull q2(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullPoints ps3= q2.points();
    +    os << "QhullPoints\n" << ps3;
    +    os << ps3.print("message\n");
    +    os << ps3.printWithIdentifier("w/ identifiers\n");
    +    cout << os.str();
    +    QString s= QString::fromStdString(os.str());
    +    QCOMPARE(s.count("p"), 8+1);
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullPoints_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullRidge_test.cpp b/xs/src/qhull/src/qhulltest/QhullRidge_test.cpp
    new file mode 100644
    index 0000000000..420a7f06d3
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullRidge_test.cpp
    @@ -0,0 +1,159 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullRidge_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullRidge_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_getSet();
    +    void t_foreach();
    +    void t_io();
    +};//QhullRidge_test
    +
    +void
    +add_QhullRidge_test()
    +{
    +    new QhullRidge_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullRidge_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullRidge_test::
    +t_construct()
    +{
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // triangulation of rotated unit cube
    +    QhullRidge r(q);
    +    QVERIFY(!r.isValid());
    +    QCOMPARE(r.dimension(),2);
    +    QhullFacet f(q.firstFacet());
    +    QhullRidgeSet rs(f.ridges());
    +    QVERIFY(!rs.isEmpty()); // Simplicial facets do not have ridges()
    +    QhullRidge r2(rs.first());
    +    QCOMPARE(r2.dimension(), 2); // One dimension lower than the facet
    +    r= r2;
    +    QVERIFY(r.isValid());
    +    QCOMPARE(r.dimension(), 2);
    +    QhullRidge r3(q, r2.getRidgeT());
    +    QCOMPARE(r,r3);
    +    QhullRidge r4(q, r2.getBaseT());
    +    QCOMPARE(r,r4);
    +    QhullRidge r5= r2; // copy constructor
    +    QVERIFY(r5==r2);
    +    QVERIFY(r5==r);
    +}//t_construct
    +
    +void QhullRidge_test::
    +t_getSet()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q.facetCount(), 6);
    +        QCOMPARE(q.vertexCount(), 8);
    +        QhullFacet f(q.firstFacet());
    +        QhullRidgeSet rs= f.ridges();
    +        QhullRidgeSetIterator i(rs);
    +        while(i.hasNext()){
    +            const QhullRidge r= i.next();
    +            cout << r.id() << endl;
    +            QVERIFY(r.bottomFacet()!=r.topFacet());
    +            QCOMPARE(r.dimension(), 2); // Ridge one-dimension less than facet
    +            QVERIFY(r.id()>=0 && r.id()<9*27);
    +            QVERIFY(r.isValid());
    +            QVERIFY(r==r);
    +            QVERIFY(r==i.peekPrevious());
    +            QCOMPARE(r.otherFacet(r.bottomFacet()),r.topFacet());
    +            QCOMPARE(r.otherFacet(r.topFacet()),r.bottomFacet());
    +        }
    +    }
    +}//t_getSet
    +
    +void QhullRidge_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");  // cube
    +    {
    +        Qhull q(rcube, "QR0"); // rotated cube
    +        QhullFacet f(q.firstFacet());
    +        foreach (const QhullRidge &r, f.ridges()){  // Qt only
    +            QhullVertexSet vs= r.vertices();
    +            QCOMPARE(vs.count(), 2);
    +            foreach (const QhullVertex &v, vs){  // Qt only
    +                QVERIFY(f.vertices().contains(v));
    +            }
    +        }
    +        QhullRidgeSet rs= f.ridges();
    +        QhullRidge r= rs.first();
    +        QhullRidge r2= r;
    +        QList vs;
    +        int count= 0;
    +        while(!count || r2!=r){
    +            ++count;
    +            QhullVertex v(q);
    +            QVERIFY2(r2.hasNextRidge3d(f),"A cube should only have non-simplicial facets.");
    +            QhullRidge r3= r2.nextRidge3d(f, &v);
    +            QVERIFY(!vs.contains(v));
    +            vs << v;
    +            r2= r2.nextRidge3d(f);
    +            QCOMPARE(r3, r2);
    +        }
    +        QCOMPARE(vs.count(), rs.count());
    +        QCOMPARE(count, rs.count());
    +    }
    +}//t_foreach
    +
    +void QhullRidge_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullFacet f(q.firstFacet());
    +        QhullRidgeSet rs= f.ridges();
    +        QhullRidge r= rs.first();
    +        ostringstream os;
    +        os << "Ridges\n" << rs << "Ridge\n" << r;
    +        os << r.print("\nRidge with message");
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count(" r"), 6);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullRidge_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullSet_test.cpp b/xs/src/qhull/src/qhulltest/QhullSet_test.cpp
    new file mode 100644
    index 0000000000..87fcf4acf2
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullSet_test.cpp
    @@ -0,0 +1,434 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullSet_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +
    +namespace orgQhull {
    +
    +class QhullSet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_qhullsetbase();
    +    void t_convert();
    +    void t_element();
    +    void t_search();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_qhullset_iterator();
    +    void t_io();
    +};//QhullSet_test
    +
    +void
    +add_QhullSet_test()
    +{
    +    new QhullSet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullSet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +// Test QhullFacetSet and QhullSet.
    +// Use QhullRidgeSet to test methods overloaded by QhullFacetSet
    +
    +void QhullSet_test::
    +t_qhullsetbase()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // triangulation of rotated unit cube
    +        // Fake an empty set.  Default constructor not defined.  No memory allocation.
    +        QhullFacet f4 = q.beginFacet();
    +        QhullFacetSet fs = f4.neighborFacets();
    +        fs.defineAs(q.qh()->other_points); // Force an empty set
    +        QVERIFY(fs.isEmpty());
    +        QCOMPARE(fs.count(), 0);
    +        QCOMPARE(fs.size(), 0u);
    +        QCOMPARE(fs.begin(), fs.end()); // beginPointer(), endPointer()
    +        QVERIFY(QhullSetBase::isEmpty(fs.getSetT()));
    +
    +        QhullRidgeSet rs = f4.ridges();
    +        QVERIFY(!rs.isEmpty());
    +        QCOMPARE(rs.count(), 4);
    +        QCOMPARE(rs.size(), 4u);
    +        QVERIFY(rs.begin()!=rs.end());
    +        QVERIFY(!QhullSetBase::isEmpty(rs.getSetT()));
    +        QhullRidgeSet rs2= rs; // copy constructor
    +        // rs= rs2; // disabled.  Would not copy ridges
    +        QCOMPARE(rs2, rs);
    +
    +        QCOMPARE(q.facetCount(), 6);
    +        QhullFacet f = q.beginFacet();
    +        QhullFacetSet fs2 = f.neighborFacets();
    +        QCOMPARE(fs2.count(), 4);
    +        QCOMPARE(fs2.size(), 4u);
    +        QVERIFY(!fs2.isEmpty());
    +        QVERIFY(!QhullSetBase::isEmpty(fs2.getSetT()));
    +        QVERIFY(fs!=fs2);
    +        setT *s= fs2.getSetT();
    +        fs.defineAs(s);
    +        QVERIFY(fs==fs2);
    +        QCOMPARE(fs[1], fs2[1]); // elementPointer
    +        QhullFacetSet fs3(fs2);
    +        QVERIFY(fs3==fs);
    +        // fs= fs2; // disabled.  Would not copy facets
    +        QhullFacetSet fs4= fs2; // copy constructor
    +        QVERIFY(fs4==fs2);
    +    }
    +}//t_qhullsetbase
    +
    +// constructors tested by t_qhullsetbase
    +
    +void QhullSet_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullFacet f= q.firstFacet();
    +        f= f.next();
    +        QhullRidgeSet rs= f.ridges();
    +        QCOMPARE(rs.count(),4);
    +        std::vector rv= rs.toStdVector();
    +        QCOMPARE(rv.size(), 4u);
    +        QList rv2= rs.toQList();
    +        QCOMPARE(rv2.size(), 4);
    +        std::vector::iterator i= rv.begin();
    +        foreach(QhullRidge r, rv2){  // Qt only
    +            QhullRidge r2= *i++;
    +            QCOMPARE(r, r2);
    +        }
    +
    +        Qhull q2(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q2.facetCount(), 12);
    +        QhullFacet f2 = q2.beginFacet();
    +        QhullFacetSet fs = f2.neighborFacets();
    +        QCOMPARE(fs.size(), 3U);
    +        std::vector vs= fs.toStdVector();
    +        QCOMPARE(vs.size(), fs.size());
    +        for(int k= fs.count(); k--; ){
    +            QCOMPARE(vs[k], fs[k]);
    +        }
    +        QList qv= fs.toQList();
    +        QCOMPARE(qv.count(), fs.count());
    +        for(int k= fs.count(); k--; ){
    +            QCOMPARE(qv[k], fs[k]);
    +        }
    +    }
    +}//t_convert
    +
    +//ReadOnly (count, isEmpty) tested by t_convert
    +//  operator== tested by t_search
    +
    +void QhullSet_test::
    +t_element()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacet f = q.beginFacet();
    +    QhullFacetSet fs = f.neighborFacets();
    +
    +    QCOMPARE(fs.at(1), fs[1]);
    +    QCOMPARE(fs.first(), fs[0]);
    +    QCOMPARE(fs.front(), fs.first());
    +    QCOMPARE(fs.last(), fs.at(3));
    +    QCOMPARE(fs.back(), fs.last());
    +    facetT **d = fs.data();
    +    facetT * const *d2= fs.data();
    +    facetT * const *d3= fs.constData();
    +    QVERIFY(d==d2);
    +    QVERIFY(d2==d3);
    +    QCOMPARE(QhullFacet(q, *d), fs.first());
    +    QhullFacetSet::iterator i(q.qh(), d+4);
    +    QCOMPARE(i, fs.end());
    +    QCOMPARE(d[4], static_cast(0));
    +    QhullFacet f4(q, d[4]);
    +    QVERIFY(!f4.isValid());
    +    QCOMPARE(fs.second(), fs[1]);
    +    const QhullFacet f2= fs.second();
    +    QVERIFY(f2==fs[1]);
    +    const QhullFacet f3= fs[1];
    +    QCOMPARE(f2, f3);
    +
    +    QCOMPARE(fs.value(2), fs[2]);
    +    QCOMPARE(fs.value(-1), QhullFacet());
    +    QCOMPARE(fs.value(10), QhullFacet());
    +    QCOMPARE(fs.value(2, f), fs[2]);
    +    QCOMPARE(fs.value(4, f), f);
    +    // mid() not available (read-only)
    +}//t_element
    +
    +void QhullSet_test::
    +t_search()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacet f = q.beginFacet();
    +    QhullFacetSet fs = f.neighborFacets();
    +    QhullFacet f2= *fs.begin();
    +    QhullFacet f3= fs.last();
    +    QVERIFY(fs.contains(f2));
    +    QVERIFY(fs.contains(f3));
    +    QVERIFY(!fs.contains(f));
    +
    +    QhullFacetSet fs2= f2.neighborFacets();
    +    QVERIFY(fs==fs);
    +    QVERIFY(fs!=fs2);
    +    QCOMPARE(fs.count(f2), 1);
    +    QCOMPARE(fs.count(f3), 1);
    +    QCOMPARE(fs.count(f), 0);
    +    QCOMPARE(fs.indexOf(f2), 0);
    +    QCOMPARE(fs.indexOf(f3), 3);
    +    QCOMPARE(fs.indexOf(f), -1);
    +    QCOMPARE(fs.lastIndexOf(f2), 0);
    +    QCOMPARE(fs.lastIndexOf(f3), 3);
    +    QCOMPARE(fs.lastIndexOf(f), -1);
    +}//t_search
    +
    +void QhullSet_test::
    +t_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullFacet f = q.beginFacet();
    +        QhullFacetSet fs = f.neighborFacets();
    +        QhullFacetSet::Iterator i= fs.begin();
    +        QhullFacetSet::iterator i2= fs.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= fs.begin();
    +        QVERIFY(i==i2);
    +        i2= fs.end();
    +        QVERIFY(i!=i2);
    +        QhullFacet f3(*i);
    +        i2--;
    +        QhullFacet f2= *i2;
    +        QCOMPARE(f3.id(), fs[0].id());
    +        QCOMPARE(f2.id(), fs[3].id());
    +        QhullFacetSet::Iterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3).id(), fs[1].id());
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        QhullFacetSet::ConstIterator i4= fs.begin();
    +        QVERIFY(i==i4); // iterator COMP const_iterator
    +        QVERIFY(i<=i4);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4==i); // const_iterator COMP iterator
    +        QVERIFY(i4<=i);
    +        QVERIFY(i4>=i);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4<=i);
    +        QVERIFY(i2!=i4);
    +        QVERIFY(i2>i4);
    +        QVERIFY(i2>=i4);
    +        QVERIFY(i4!=i2);
    +        QVERIFY(i4i);
    +        QVERIFY(i4>=i);
    +
    +        i= fs.begin();
    +        i2= fs.begin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, fs[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, fs.begin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2 += 4, fs.end());
    +        QCOMPARE(i2 -= 4, fs.begin());
    +        QCOMPARE(i2+0, fs.begin());
    +        QCOMPARE(i2+4, fs.end());
    +        i2 += 4;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-4;
    +        QCOMPARE(i, fs.begin());
    +        QCOMPARE(i2-i, 4);
    +
    +        //fs.begin end tested above
    +
    +        // QhullFacetSet is const-only
    +    }
    +}//t_iterator
    +
    +void QhullSet_test::
    +t_const_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullFacet f = q.beginFacet();
    +        QhullFacetSet fs = f.neighborFacets();
    +        QhullFacetSet::ConstIterator i= fs.begin();
    +        QhullFacetSet::const_iterator i2= fs.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= fs.begin();
    +        QVERIFY(i==i2);
    +        i2= fs.end();
    +        QVERIFY(i!=i2);
    +        QhullFacet f3(*i);
    +        i2--;
    +        QhullFacet f2= *i2;
    +        QCOMPARE(f3.id(), fs[0].id());
    +        QCOMPARE(f2.id(), fs[3].id());
    +        QhullFacetSet::ConstIterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3).id(), fs[1].id());
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        // See t_iterator for const_iterator COMP iterator
    +
    +        i= fs.begin();
    +        i2= fs.constBegin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, fs[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, fs.constBegin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2+=4, fs.constEnd());
    +        QCOMPARE(i2-=4, fs.constBegin());
    +        QCOMPARE(i2+0, fs.constBegin());
    +        QCOMPARE(i2+4, fs.constEnd());
    +        i2 += 4;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-4;
    +        QCOMPARE(i, fs.constBegin());
    +        QCOMPARE(i2-i, 4);
    +
    +        // QhullFacetSet is const-only
    +    }
    +}//t_const_iterator
    +
    +void QhullSet_test::
    +t_qhullset_iterator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    // Fake an empty set.  Default constructor not defined.  No memory allocation.
    +    QhullFacet f = q.beginFacet();
    +    QhullFacetSet fs = f.neighborFacets();
    +    fs.defineAs(q.qh()->other_points);
    +    QhullFacetSetIterator i(fs);
    +    QCOMPARE(fs.count(), 0);
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    QhullFacet f2 = q.beginFacet();
    +    QhullFacetSet fs2 = f2.neighborFacets();
    +    QhullFacetSetIterator i2(fs2);
    +    QCOMPARE(fs2.count(), 4);
    +    i= fs2;
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    i.toFront();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    // i at front, i2 at end/back, 4 neighbors
    +    QhullFacetSet fs3 = f2.neighborFacets(); // same as fs2
    +    QhullFacet f3(fs2[0]);
    +    QhullFacet f4= fs3[0];
    +    QCOMPARE(f3, f4);
    +    QVERIFY(f3==f4);
    +    QhullFacet f5(fs3[1]);
    +    QVERIFY(f4!=f5);
    +    QhullFacet f6(fs3[2]);
    +    QhullFacet f7(fs3[3]);
    +    QCOMPARE(i2.peekPrevious(), f7);
    +    QCOMPARE(i2.previous(), f7);
    +    QCOMPARE(i2.previous(), f6);
    +    QCOMPARE(i2.previous(), f5);
    +    QCOMPARE(i2.previous(), f4);
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), f4);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), f4);
    +    QCOMPARE(i.peekNext(), f5);
    +    QCOMPARE(i.next(), f5);
    +    QCOMPARE(i.next(), f6);
    +    QCOMPARE(i.next(), f7);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), f4);
    +}//t_qhullset_iterator
    +
    +void QhullSet_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    // Fake an empty set.  Default constructor not defined.  No memory allocation.
    +    QhullFacet f= q.beginFacet();
    +    QhullFacetSet fs= f.neighborFacets();
    +    fs.defineAs(q.qh()->other_points);
    +    cout << "INFO:     empty set" << fs << std::endl;
    +    QhullFacet f2= q.beginFacet();
    +    QhullFacetSet fs2= f2.neighborFacets();
    +    cout << "INFO:   Neighboring facets\n";
    +    cout << fs2 << std::endl;
    +
    +    QhullRidgeSet rs= f.ridges();
    +    cout << "INFO:   Ridges for a facet\n";
    +    cout << rs << std::endl;
    +}//t_io
    +
    +}//namespace orgQhull
    +
    +#include "moc/QhullSet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullVertexSet_test.cpp b/xs/src/qhull/src/qhulltest/QhullVertexSet_test.cpp
    new file mode 100644
    index 0000000000..41caacd290
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullVertexSet_test.cpp
    @@ -0,0 +1,152 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullVertexSet_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullVertexSet.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullVertexSet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_readonly();
    +    void t_foreach();
    +    void t_io();
    +};//QhullVertexSet_test
    +
    +void
    +add_QhullVertexSet_test()
    +{
    +    new QhullVertexSet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullVertexSet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullVertexSet_test::
    +t_construct()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    cout << "INFO   : Cube rotated by QR" << q.rotateRandom() << std::endl;
    +    QhullFacet f= q.firstFacet();
    +    QhullVertexSet vs= f.vertices();
    +    QVERIFY(!vs.isEmpty());
    +    QCOMPARE(vs.count(),4);
    +    QhullVertexSet vs4= vs; // copy constructor
    +    QVERIFY(vs4==vs);
    +    QhullVertexSet vs3(q, q.qh()->del_vertices);
    +    QVERIFY(vs3.isEmpty());
    +}//t_construct
    +
    +void QhullVertexSet_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0 QV2");  // rotated unit cube with "good" facets adjacent to point 0
    +    cout << "INFO   : Cube rotated by QR" << q.rotateRandom() << std::endl;
    +    QhullFacet f= q.firstFacet();
    +    QhullVertexSet vs2= f.vertices();
    +    QCOMPARE(vs2.count(),4);
    +    std::vector fv= vs2.toStdVector();
    +    QCOMPARE(fv.size(), 4u);
    +    QList fv2= vs2.toQList();
    +    QCOMPARE(fv2.size(), 4);
    +    std::vector fv3= vs2.toStdVector();
    +    QCOMPARE(fv3.size(), 4u);
    +    QList fv4= vs2.toQList();
    +    QCOMPARE(fv4.size(), 4);
    +}//t_convert
    +
    +//! Spot check properties and read-only.  See QhullSet_test
    +void QhullVertexSet_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QV0");  // good facets are adjacent to point 0
    +    QhullVertexSet vs= q.firstFacet().vertices();
    +    QCOMPARE(vs.count(), 4);
    +    QCOMPARE(vs.count(), 4);
    +    QhullVertex v= vs.first();
    +    QhullVertex v2= vs.last();
    +    QVERIFY(vs.contains(v));
    +    QVERIFY(vs.contains(v2));
    +}//t_readonly
    +
    +void QhullVertexSet_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");
    +    // Spot check predicates and accessors.  See QhullLinkedList_test
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    cout << "INFO   : Cube rotated by QR" << q.rotateRandom() << std::endl;
    +    QhullVertexSet vs= q.firstFacet().vertices();
    +    QVERIFY(vs.contains(vs.first()));
    +    QVERIFY(vs.contains(vs.last()));
    +    QCOMPARE(vs.first(), *vs.begin());
    +    QCOMPARE(*(vs.end()-1), vs.last());
    +}//t_foreach
    +
    +void QhullVertexSet_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0 QV0");   // good facets are adjacent to point 0
    +        cout << "INFO   : Cube rotated by QR" << q.rotateRandom() << std::endl;
    +        QhullVertexSet vs= q.firstFacet().vertices();
    +        ostringstream os;
    +        os << vs.print("Vertices of first facet with point 0");
    +        os << vs.printIdentifiers("\nVertex identifiers: ");
    +        cout<< os.str();
    +        QString vertices= QString::fromStdString(os.str());
    +        QCOMPARE(vertices.count(QRegExp(" v[0-9]")), 4);
    +    }
    +}//t_io
    +
    +#ifdef QHULL_USES_QT
    +QList QhullVertexSet::
    +toQList() const
    +{
    +    QhullSetIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        QhullVertex v= i.next();
    +        vs.append(v);
    +    }
    +    return vs;
    +}//toQList
    +#endif //QHULL_USES_QT
    +
    +}//orgQhull
    +
    +#include "moc/QhullVertexSet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullVertex_test.cpp b/xs/src/qhull/src/qhulltest/QhullVertex_test.cpp
    new file mode 100644
    index 0000000000..fb6ec9640a
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullVertex_test.cpp
    @@ -0,0 +1,184 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullVertex_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Coordinates.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullVertexSet.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullVertex_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_constructConvert();
    +    void t_getSet();
    +    void t_foreach();
    +    void t_io();
    +};//QhullVertex_test
    +
    +void
    +add_QhullVertex_test()
    +{
    +    new QhullVertex_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullVertex_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullVertex_test::
    +t_constructConvert()
    +{
    +    QhullVertex v6;
    +    QVERIFY(!v6.isValid());
    +    QCOMPARE(v6.dimension(),0);
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullVertex v(q);
    +    QVERIFY(!v.isValid());
    +    QCOMPARE(v.dimension(),3);
    +    QhullVertex v2(q.beginVertex());
    +    QCOMPARE(v2.dimension(),3);
    +    v= v2;  // copy assignment
    +    QVERIFY(v.isValid());
    +    QCOMPARE(v.dimension(),3);
    +    QhullVertex v5= v2; // copy constructor
    +    QVERIFY(v5==v2);
    +    QVERIFY(v5==v);
    +    QhullVertex v3(q, v2.getVertexT());
    +    QCOMPARE(v,v3);
    +    QhullVertex v4(q, v2.getBaseT());
    +    QCOMPARE(v,v4);
    +}//t_constructConvert
    +
    +void QhullVertex_test::
    +t_getSet()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q.facetCount(), 12);
    +        QCOMPARE(q.vertexCount(), 8);
    +
    +        // Also spot-test QhullVertexList.  See QhullLinkedList_test.cpp
    +        QhullVertexList vs= q.vertexList();
    +        QhullVertexListIterator i(vs);
    +        while(i.hasNext()){
    +            const QhullVertex v= i.next();
    +            cout << v.id() << endl;
    +            QCOMPARE(v.dimension(),3);
    +            QVERIFY(v.id()>=0 && v.id()<9);
    +            QVERIFY(v.isValid());
    +            if(i.hasNext()){
    +                QCOMPARE(v.next(), i.peekNext());
    +                QVERIFY(v.next()!=v);
    +                QVERIFY(v.next().previous()==v);
    +            }
    +            QVERIFY(i.hasPrevious());
    +            QCOMPARE(v, i.peekPrevious());
    +        }
    +
    +        // test point()
    +        foreach (QhullVertex v, q.vertexList()){  // Qt only
    +            QhullPoint p= v.point();
    +            int j= p.id();
    +            cout << "Point " << j << ":\n" << p << endl;
    +            QVERIFY(j>=0 && j<8);
    +        }
    +    }
    +}//t_getSet
    +
    +void QhullVertex_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c W0 300");  // 300 points on surface of cube
    +    {
    +        Qhull q(rcube, "QR0 Qc"); // keep coplanars, thick facet, and rotate the cube
    +        foreach (QhullVertex v, q.vertexList()){  // Qt only
    +            QhullFacetSet fs= v.neighborFacets();
    +            QCOMPARE(fs.count(), 3);
    +            foreach (QhullFacet f, fs){  // Qt only
    +                QVERIFY(f.vertices().contains(v));
    +            }
    +        }
    +    }
    +}//t_foreach
    +
    +void QhullVertex_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullVertex v= q.beginVertex();
    +        ostringstream os;
    +        os << "Vertex and vertices:\n";
    +        os << v;
    +        QhullVertexSet vs= q.firstFacet().vertices();
    +        os << vs;
    +        os << "\nVertex and vertices with message:\n";
    +        os << v.print("Vertex");
    +        os << vs.print("\nVertices:");
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count("(v"), 10);
    +        QCOMPARE(s.count(": f"), 2);
    +    }
    +    RboxPoints r10("10 D3");  // Without QhullVertex::facetNeighbors
    +    {
    +        Qhull q(r10, "");
    +        QhullVertex v= q.beginVertex();
    +        ostringstream os;
    +        os << "\nTry again with simplicial facets.  No neighboring facets listed for vertices.\n";
    +        os << "Vertex and vertices:\n";
    +        os << v;
    +        q.defineVertexNeighborFacets();
    +        os << "This time with neighborFacets() defined for all vertices:\n";
    +        os << v;
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count(": f"), 1);
    +
    +        Qhull q2(r10, "v"); // Voronoi diagram
    +        QhullVertex v2= q2.beginVertex();
    +        ostringstream os2;
    +        os2 << "\nTry again with Voronoi diagram of simplicial facets.  Neighboring facets automatically defined for vertices.\n";
    +        os2 << "Vertex and vertices:\n";
    +        os2 << v2;
    +        cout << os2.str();
    +        QString s2= QString::fromStdString(os2.str());
    +        QCOMPARE(s2.count(": f"), 1);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullVertex_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/Qhull_test.cpp b/xs/src/qhull/src/qhulltest/Qhull_test.cpp
    new file mode 100644
    index 0000000000..cc3918a050
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/Qhull_test.cpp
    @@ -0,0 +1,360 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/Qhull_test.cpp#4 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +//! Test C++ interface to Qhull
    +//! See eg/q_test for tests of Qhull commands
    +class Qhull_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_attribute();
    +    void t_message();
    +    void t_getSet();
    +    void t_getQh();
    +    void t_getValue();
    +    void t_foreach();
    +    void t_modify();
    +};//Qhull_test
    +
    +void
    +add_Qhull_test()
    +{
    +    new Qhull_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void Qhull_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void Qhull_test::
    +t_construct()
    +{
    +    {
    +        Qhull q;
    +        QCOMPARE(q.dimension(),0);
    +        QVERIFY(q.qh()!=0);
    +        QCOMPARE(QString(q.qhullCommand()),QString(""));
    +        QCOMPARE(QString(q.rboxCommand()),QString(""));
    +        try{
    +            QCOMPARE(q.area(),0.0);
    +            QFAIL("area() did not fail.");
    +        }catch (const std::exception &e) {
    +            cout << "INFO   : Caught " << e.what();
    +        }
    +    }
    +    {
    +        RboxPoints rbox("10000");
    +        Qhull q(rbox, "QR0"); // Random points in a randomly rotated cube.
    +        QCOMPARE(q.dimension(),3);
    +        QVERIFY(q.volume() < 1.0);
    +        QVERIFY(q.volume() > 0.99);
    +    }
    +    {
    +        double points[] = {
    +            0, 0,
    +            1, 0,
    +            1, 1
    +        };
    +        Qhull q("triangle", 2, 3, points, "");
    +        QCOMPARE(q.dimension(),2);
    +        QCOMPARE(q.facetCount(),3);
    +        QCOMPARE(q.vertexCount(),3);
    +        QCOMPARE(q.dimension(),2);
    +        QCOMPARE(q.area(), 2.0+sqrt(2.0)); // length of boundary
    +        QCOMPARE(q.volume(), 0.5);        // the 2-d area
    +    }
    +}//t_construct
    +
    +void Qhull_test::
    +t_attribute()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        double normals[] = {
    +            0,  -1, -0.5,
    +           -1,   0, -0.5,
    +            1,   0, -0.5,
    +            0,   1, -0.5
    +        };
    +        Qhull q;
    +        Coordinates feasible;
    +        feasible << 0.0 << 0.0;
    +        q.setFeasiblePoint(feasible);
    +        Coordinates c(std::vector(2, 0.0));
    +        QVERIFY(q.feasiblePoint()==c);
    +        q.setOutputStream(&cout);
    +        q.runQhull("normals of square", 3, 4, normals, "H"); // halfspace intersect
    +        QVERIFY(q.feasiblePoint()==c); // from qh.feasible_point after runQhull()
    +        QCOMPARE(q.facetList().count(), 4); // Vertices of square
    +        cout << "Expecting summary of halfspace intersection\n";
    +        q.outputQhull();
    +        q.qh()->disableOutputStream();  // Same as q.disableOutputStream()
    +        cout << "Expecting no output from qh_fprintf() in Qhull.cpp\n";
    +        q.outputQhull();
    +    }
    +}//t_attribute
    +
    +//! No QhullMessage for errors outside of qhull
    +void Qhull_test::
    +t_message()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        QCOMPARE(q.qhullMessage(), string(""));
    +        QCOMPARE(q.qhullStatus(), qh_ERRnone);
    +        QVERIFY(!q.hasQhullMessage());
    +        try{
    +            q.runQhull(rcube, "Fd");
    +            QFAIL("runQhull Fd did not fail.");
    +        }catch (const std::exception &e) {
    +            const char *s= e.what();
    +            cout << "INFO   : Caught " << s;
    +            QCOMPARE(QString::fromStdString(s).left(6), QString("QH6029"));
    +            // FIXUP QH11025 -- review decision to clearQhullMessage at QhullError()            // Cleared when copied to QhullError
    +            QVERIFY(!q.hasQhullMessage());
    +            // QCOMPARE(q.qhullMessage(), QString::fromStdString(s).remove(0, 7));
    +            // QCOMPARE(q.qhullStatus(), 6029);
    +            q.clearQhullMessage();
    +            QVERIFY(!q.hasQhullMessage());
    +        }
    +        q.appendQhullMessage("Append 1");
    +        QVERIFY(q.hasQhullMessage());
    +        QCOMPARE(QString::fromStdString(q.qhullMessage()), QString("Append 1"));
    +        q.appendQhullMessage("\nAppend 2\n");
    +        QCOMPARE(QString::fromStdString(q.qhullMessage()), QString("Append 1\nAppend 2\n"));
    +        q.clearQhullMessage();
    +        QVERIFY(!q.hasQhullMessage());
    +        QCOMPARE(QString::fromStdString(q.qhullMessage()), QString(""));
    +    }
    +    {
    +        cout << "INFO   : Error stream without output stream\n";
    +        Qhull q;
    +        q.setErrorStream(&cout);
    +        q.setOutputStream(0);
    +        try{
    +            q.runQhull(rcube, "Fd");
    +            QFAIL("runQhull Fd did not fail.");
    +        }catch (const QhullError &e) {
    +            cout << "INFO   : Caught " << e;
    +            QCOMPARE(e.errorCode(), 6029);
    +        }
    +        //FIXUP QH11025 Qhullmessage cleared when QhullError thrown.  Switched to e
    +        //QVERIFY(q.hasQhullMessage());
    +        //QCOMPARE(QString::fromStdString(q.qhullMessage()).left(6), QString("QH6029"));
    +        q.clearQhullMessage();
    +        QVERIFY(!q.hasQhullMessage());
    +    }
    +    {
    +        cout << "INFO   : Error output sent to output stream without error stream\n";
    +        Qhull q;
    +        q.setErrorStream(0);
    +        q.setOutputStream(&cout);
    +        try{
    +            q.runQhull(rcube, "Tz H0");
    +            QFAIL("runQhull TZ did not fail.");
    +        }catch (const std::exception &e) {
    +            const char *s= e.what();
    +            cout << "INFO   : Caught " << s;
    +            QCOMPARE(QString::fromLatin1(s).left(6), QString("QH6023"));
    +        }
    +        //FIXUP QH11025 Qhullmessage cleared when QhullError thrown.  Switched to e
    +        //QVERIFY(q.hasQhullMessage());
    +        //QCOMPARE(QString::fromStdString(q.qhullMessage()).left(17), QString("qhull: no message"));
    +        //QCOMPARE(q.qhullStatus(), 6023);
    +        q.clearQhullMessage();
    +        QVERIFY(!q.hasQhullMessage());
    +    }
    +    {
    +        cout << "INFO   : No error stream or output stream\n";
    +        Qhull q;
    +        q.setErrorStream(0);
    +        q.setOutputStream(0);
    +        try{
    +            q.runQhull(rcube, "Fd");
    +            QFAIL("outputQhull did not fail.");
    +        }catch (const std::exception &e) {
    +            const char *s= e.what();
    +            cout << "INFO   : Caught " << s;
    +            QCOMPARE(QString::fromLatin1(s).left(6), QString("QH6029"));
    +        }
    +        //FIXUP QH11025 Qhullmessage cleared when QhullError thrown.  Switched to e
    +        //QVERIFY(q.hasQhullMessage());
    +        //QCOMPARE(QString::fromStdString(q.qhullMessage()).left(9), QString("qhull err"));
    +        //QCOMPARE(q.qhullStatus(), 6029);
    +        q.clearQhullMessage();
    +        QVERIFY(!q.hasQhullMessage());
    +    }
    +}//t_message
    +
    +void Qhull_test::
    +t_getSet()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        QVERIFY(!q.initialized());
    +        q.runQhull(rcube, "s");
    +        QVERIFY(q.initialized());
    +        QCOMPARE(q.dimension(), 3);
    +        QhullPoint p= q.origin();
    +        QCOMPARE(p.dimension(), 3);
    +        QCOMPARE(p[0]+p[1]+p[2], 0.0);
    +        q.setErrorStream(&cout);
    +        q.outputQhull();
    +    }
    +    {
    +        Qhull q;
    +        q.runQhull(rcube, "");
    +        q.setOutputStream(&cout);
    +        q.outputQhull();
    +    }
    +}//t_getSet
    +
    +void Qhull_test::
    +t_getQh()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        q.runQhull(rcube, "s");
    +        QCOMPARE(QString(q.qhullCommand()), QString("qhull s"));
    +        QCOMPARE(QString(q.rboxCommand()), QString("rbox \"c\""));
    +        QCOMPARE(q.facetCount(), 6);
    +        QCOMPARE(q.vertexCount(), 8);
    +        // Sample fields from Qhull's qhT [libqhull.h]
    +        QCOMPARE(q.qh()->ALLpoints, 0u);
    +        QCOMPARE(q.qh()->GOODpoint, 0);
    +        QCOMPARE(q.qh()->IStracing, 0);
    +        QCOMPARE(q.qh()->MAXcoplanar+1.0, 1.0); // fuzzy compare
    +        QCOMPARE(q.qh()->MERGING, 1u);
    +        QCOMPARE(q.qh()->input_dim, 3);
    +        QCOMPARE(QString(q.qh()->qhull_options).left(8), QString("  run-id"));
    +        QCOMPARE(q.qh()->num_facets, 6);
    +        QCOMPARE(q.qh()->hasTriangulation, 0u);
    +        QCOMPARE(q.qh()->max_outside - q.qh()->min_vertex + 1.0, 1.0); // fuzzy compare
    +        QCOMPARE(*q.qh()->gm_matrix+1.0, 1.0); // fuzzy compare
    +    }
    +}//t_getQh
    +
    +void Qhull_test::
    +t_getValue()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        q.runQhull(rcube, "");
    +        QCOMPARE(q.area(), 6.0);
    +        QCOMPARE(q.volume(), 1.0);
    +    }
    +}//t_getValue
    +
    +void Qhull_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        QCOMPARE(q.beginFacet(),q.endFacet());
    +        QCOMPARE(q.beginVertex(),q.endVertex());
    +        q.runQhull(rcube, "");
    +        QCOMPARE(q.facetList().count(), 6);
    +
    +        // defineVertexNeighborFacets() tested in QhullVertex_test::t_io()
    +
    +        QhullFacetList facets(q.beginFacet(), q.endFacet());
    +        QCOMPARE(facets.count(), 6);
    +        QCOMPARE(q.firstFacet(), q.beginFacet());
    +        QhullVertexList vertices(q.beginVertex(), q.endVertex());
    +        QCOMPARE(vertices.count(), 8);
    +        QCOMPARE(q.firstVertex(), q.beginVertex());
    +        QhullPoints ps= q.points();
    +        QCOMPARE(ps.count(), 8);
    +        QhullPointSet ps2= q.otherPoints();
    +        QCOMPARE(ps2.count(), 0);
    +        // ps2= q.otherPoints(); //disabled, would not copy the points
    +        QCOMPARE(q.facetCount(), 6);
    +        QCOMPARE(q.vertexCount(), 8);
    +        coordT *c= q.pointCoordinateBegin(); // of q.points()
    +        QVERIFY(*c==0.5 || *c==-0.5);
    +        coordT *c3= q.pointCoordinateEnd();
    +        QVERIFY(c3[-1]==0.5 || c3[-1]==-0.5);
    +        QCOMPARE(c3-c, 8*3);
    +        QCOMPARE(q.vertexList().count(), 8);
    +    }
    +}//t_foreach
    +
    +void Qhull_test::
    +t_modify()
    +{
    +    //addPoint() tested in t_foreach
    +    RboxPoints diamond("d");
    +    Qhull q(diamond, "o");
    +    q.setOutputStream(&cout);
    +    cout << "Expecting vertexList and facetList of a 3-d diamond.\n";
    +    q.outputQhull();
    +    cout << "Expecting normals of a 3-d diamond.\n";
    +    q.outputQhull("n");
    +    // runQhull tested in t_attribute(), t_message(), etc.
    +}//t_modify
    +
    +}//orgQhull
    +
    +// Redefine Qhull's usermem_r.c in order to report erroneous calls to qh_exit
    +void qh_exit(int exitcode) {
    +    cout << "FAIL!  : Qhull called qh_exit().  Qhull's error handling not available.\n.. See the corresponding Qhull:qhull_message or setErrorStream().\n";
    +    exit(exitcode);
    +}
    +void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    va_start(args, fmt);
    +    if(msgcode)
    +        fprintf(stderr, "QH%.4d ", msgcode);
    +    vfprintf(stderr, fmt, args);
    +    va_end(args);
    +} /* fprintf_stderr */
    +void qh_free(void *mem) {
    +    free(mem);
    +}
    +void *qh_malloc(size_t size) {
    +    return malloc(size);
    +}
    +
    +#if 0
    +template<> char * QTest::
    +toString(const std::string &s)
    +{
    +    QByteArray ba = s.c_str();
    +    return qstrdup(ba.data());
    +}
    +#endif
    +
    +#include "moc/Qhull_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/RboxPoints_test.cpp b/xs/src/qhull/src/qhulltest/RboxPoints_test.cpp
    new file mode 100644
    index 0000000000..4f4ea984f8
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/RboxPoints_test.cpp
    @@ -0,0 +1,215 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2006-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/RboxPoints_test.cpp#2 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullError.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::string;
    +using std::stringstream;
    +
    +namespace orgQhull {
    +
    +//! Test C++ interface to Rbox
    +//! See eg/q_test for tests of rbox commands
    +class RboxPoints_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void t_construct();
    +    void t_error();
    +    void t_test();
    +    void t_getSet();
    +    void t_foreach();
    +    void t_change();
    +    void t_ostream();
    +};
    +
    +void
    +add_RboxPoints_test()
    +{
    +    new RboxPoints_test();  // RoadTest::s_testcases
    +}
    +
    +void RboxPoints_test::
    +t_construct()
    +{
    +    RboxPoints rp;
    +    QCOMPARE(rp.dimension(), 0);
    +    QCOMPARE(rp.count(), 0);
    +    QVERIFY(QString::fromStdString(rp.comment()) != QString(""));
    +    QVERIFY(rp.isEmpty());
    +    QVERIFY(!rp.hasRboxMessage());
    +    QCOMPARE(rp.rboxStatus(), qh_ERRnone);
    +    QCOMPARE(QString::fromStdString(rp.rboxMessage()), QString("rbox warning: no points generated\n"));
    +
    +    RboxPoints rp2("c"); // 3-d cube
    +    QCOMPARE(rp2.dimension(), 3);
    +    QCOMPARE(rp2.count(), 8);
    +    QCOMPARE(QString::fromStdString(rp2.comment()), QString("rbox \"c\""));
    +    QVERIFY(!rp2.isEmpty());
    +    QVERIFY(!rp2.hasRboxMessage());
    +    QCOMPARE(rp2.rboxStatus(), qh_ERRnone);
    +    QCOMPARE(QString::fromStdString(rp2.rboxMessage()), QString("rbox: OK\n"));
    +}//t_construct
    +
    +void RboxPoints_test::
    +t_error()
    +{
    +    RboxPoints rp;
    +    try{
    +        rp.appendPoints("D0 c");
    +        QFAIL("'D0 c' did not fail.");
    +    }catch (const std::exception &e) {
    +        const char *s= e.what();
    +        cout << "INFO   : Caught " << s;
    +        QCOMPARE(QString(s).left(6), QString("QH6189"));
    +        QVERIFY(rp.hasRboxMessage());
    +        QCOMPARE(QString::fromStdString(rp.rboxMessage()).left(8), QString("rbox err"));
    +        QCOMPARE(rp.rboxStatus(), 6189);
    +        rp.clearRboxMessage();
    +        QVERIFY(!rp.hasRboxMessage());
    +    }
    +    try{
    +        RboxPoints rp2;
    +        rp2.setDimension(-1);
    +        QFAIL("setDimension(-1) did not fail.");
    +    }catch (const RoadError &e) {
    +        const char *s= e.what();
    +        cout << "INFO   : Caught " << s;
    +        QCOMPARE(QString(s).left(7), QString("QH10062"));
    +        QCOMPARE(e.errorCode(), 10062);
    +        QCOMPARE(QString::fromStdString(e.what()), QString(s));
    +        RoadLogEvent logEvent= e.roadLogEvent();
    +        QCOMPARE(logEvent.int1(), -1);
    +    }
    +}//t_error
    +
    +void RboxPoints_test::
    +t_test()
    +{
    +    // isEmpty -- t_construct
    +}//t_test
    +
    +void RboxPoints_test::
    +t_getSet()
    +{
    +    // comment -- t_construct
    +    // count -- t_construct
    +    // dimension -- t_construct
    +
    +    RboxPoints rp;
    +    QCOMPARE(rp.dimension(), 0);
    +    rp.setDimension(2);
    +    QCOMPARE(rp.dimension(), 2);
    +    try{
    +        rp.setDimension(102);
    +        QFAIL("setDimension(102) did not fail.");
    +    }catch (const std::exception &e) {
    +        cout << "INFO   : Caught " << e.what();
    +    }
    +    QCOMPARE(rp.newCount(), 0);
    +    rp.appendPoints("D2 P1 P2");
    +    QCOMPARE(rp.count(), 2);
    +    QCOMPARE(rp.newCount(), 2); // From previous appendPoints();
    +    PointCoordinates pc(rp.qh(), 2, "Test qh() and <<");
    +    pc << 1.0 << 0.0 << 2.0 << 0.0;
    +    QCOMPARE(pc.dimension(), 2);
    +    QCOMPARE(pc.count(), 2);
    +    QVERIFY(rp==pc);
    +    rp.setNewCount(10);  // Normally only used by appendPoints for rbox processing
    +    QCOMPARE(rp.newCount(), 10);
    +    rp.reservePoints();
    +    QVERIFY(rp==pc);
    +}//t_getSet
    +
    +void RboxPoints_test::
    +t_foreach()
    +{
    +    RboxPoints rp("c");
    +    Coordinates::ConstIterator cci= rp.beginCoordinates();
    +    orgQhull::Coordinates::Iterator ci= rp.beginCoordinates();
    +    QCOMPARE(*cci, -0.5);
    +    QCOMPARE(*ci, *cci);
    +    int i=1;
    +    while(++cci
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +
    +namespace orgQhull {
    +
    +#//!\name class variable
    +
    +QList RoadTest::
    +s_testcases;
    +
    +int RoadTest::
    +s_test_count= 0;
    +
    +int RoadTest::
    +s_test_fail= 0;
    +
    +QStringList RoadTest::
    +s_failed_tests;
    +
    +#//!\name Slot
    +
    +//! Executed after each test
    +void RoadTest::
    +cleanup()
    +{
    +    s_test_count++;
    +    if(QTest::currentTestFailed()){
    +        recordFailedTest();
    +    }
    +}//cleanup
    +
    +#//!\name Helper
    +
    +void RoadTest::
    +recordFailedTest()
    +{
    +    s_test_fail++;
    +    QString className= metaObject()->className();
    +    s_failed_tests << className + "::" + QTest::currentTestFunction();
    +}
    +
    +#//!\name class function
    +
    +void RoadTest::
    +deleteTests()
    +{
    +    foreach(RoadTest *testcase, s_testcases){
    +        delete testcase;
    +    }
    +    s_failed_tests.clear();
    +}
    +
    +int RoadTest::
    +runTests(QStringList arguments)
    +{
    +    int result= 0; // assume success
    +
    +    foreach(RoadTest *testcase, s_testcases){
    +        try{
    +            result += QTest::qExec(testcase, arguments);
    +        }catch(const std::exception &e){
    +            cout << "FAIL!  : Threw error ";
    +            cout << e.what() << endl;
    +    s_test_count++;
    +            testcase->recordFailedTest();
    +            // Qt 4.5.2 OK.  In Qt 4.3.3, qtestcase did not clear currentTestObject
    +        }
    +    }
    +    if(s_test_fail){
    +        cout << "Failed " << s_test_fail << " of " << s_test_count << " tests.\n";
    +        cout << s_failed_tests.join("\n").toLocal8Bit().constData() << std::endl;
    +    }else{
    +        cout << "Passed " << s_test_count << " tests.\n";
    +    }
    +    return result;
    +}//runTests
    +
    +}//orgQhull
    +
    +#include "moc/moc_RoadTest.cpp"
    diff --git a/xs/src/qhull/src/qhulltest/RoadTest.h b/xs/src/qhull/src/qhulltest/RoadTest.h
    new file mode 100644
    index 0000000000..adfe0bf8c1
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/RoadTest.h
    @@ -0,0 +1,102 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/RoadTest.h#2 $$Change: 2062 $
    +** $Date: 2016/01/17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef ROADTEST_H
    +#define ROADTEST_H
    +
    +//pre-compiled with RoadTest.h
    +#include     // Qt C++ Framework
    +#include 
    +
    +#define QHULL_USES_QT 1
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +
    +    //! RoadTest -- Generic test for Qt's QTest
    +    class RoadTest;
    +    //! TESTadd_(t) -- Add a RoadTest
    +
    +/** Test Name objects using Qt's QTestLib
    +
    +Template:
    +
    +class Name_test : public RoadTest
    +{
    +    Q_OBJECT
    +#//!\name Test slot
    +private slots:
    +    void t_name();
    +    //Executed before any test
    +    void initTestCase();
    +    void init();          // Each test
    +    //Executed after each test
    +    void cleanup(); //RoadTest::cleanup();
    +    // Executed after last test
    +    void cleanupTestCase();
    +};
    +
    +void
    +add_Name_test()
    +{
    +    new Name_test();  // RoadTest::s_testcases
    +}
    +
    +Send additional output to cout
    +*/
    +
    +class RoadTest : public QObject
    +{
    +    Q_OBJECT
    +
    +#//!\name Class globals
    +protected:
    +    static QList
    +                        s_testcases; ///! List of testcases to execute.  Initialized via add_...()
    +    static int          s_test_count; ///! Total number of tests executed
    +    static int          s_test_fail; ///! Number of failed tests
    +    static QStringList  s_failed_tests; ///! List of failed tests
    +
    +#//!\name Test slots
    +public slots:
    +    void cleanup();
    +
    +public:
    +#//!\name Constructors, etc.
    +                        RoadTest()  { s_testcases.append(this); }
    +    virtual             ~RoadTest() {} // Derived from QObject
    +
    +#//!\name Helper
    +    void                recordFailedTest();
    +
    +
    +#//!\name Class functions
    +    static void         deleteTests();
    +    static int          runTests(QStringList arguments);
    +
    +};//RoadTest
    +
    +#define TESTadd_(t) extern void t(); t();
    +
    +
    +}//orgQhull
    +
    +namespace QTest{
    +
    +template<>
    +inline char *
    +toString(const std::string &s)
    +{
    +    return qstrdup(s.c_str());
    +}
    +
    +}//namespace QTest
    +
    +#endif //ROADTEST_H
    +
    diff --git a/xs/src/qhull/src/qhulltest/qhulltest.cpp b/xs/src/qhull/src/qhulltest/qhulltest.cpp
    new file mode 100644
    index 0000000000..5bfe16e9cf
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/qhulltest.cpp
    @@ -0,0 +1,94 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/qhulltest.cpp#5 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include "libqhull_r/user_r.h"
    +
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/RoadError.h"
    +#include "libqhull_r/qhull_ra.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +
    +namespace orgQhull {
    +
    +void addQhullTests(QStringList &args)
    +{
    +    TESTadd_(add_Qhull_test);
    +
    +    if(args.contains("--all")){
    +        args.removeAll("--all");
    +        // up-to-date
    +        TESTadd_(add_Coordinates_test);
    +        TESTadd_(add_PointCoordinates_test);
    +        TESTadd_(add_QhullFacet_test);
    +        TESTadd_(add_QhullFacetList_test);
    +        TESTadd_(add_QhullFacetSet_test);
    +        TESTadd_(add_QhullHyperplane_test);
    +        TESTadd_(add_QhullLinkedList_test);
    +        TESTadd_(add_QhullPoint_test);
    +        TESTadd_(add_QhullPoints_test);
    +        TESTadd_(add_QhullPointSet_test);
    +        TESTadd_(add_QhullRidge_test);
    +        TESTadd_(add_QhullSet_test);
    +        TESTadd_(add_QhullVertex_test);
    +        TESTadd_(add_QhullVertexSet_test);
    +        TESTadd_(add_RboxPoints_test);
    +        // qhullStat
    +        TESTadd_(add_Qhull_test);
    +    }//--all
    +}//addQhullTests
    +
    +int main(int argc, char *argv[])
    +{
    +
    +    QCoreApplication app(argc, argv);
    +    QStringList args= app.arguments();
    +    bool isAll= args.contains("--all");
    +
    +    QHULL_LIB_CHECK /* Check for compatible library */
    +
    +    addQhullTests(args);
    +    int status=1010;
    +    try{
    +        status= RoadTest::runTests(args);
    +    }catch(const std::exception &e){
    +        cout << "FAIL!  : runTests() did not catch error\n";
    +        cout << e.what() << endl;
    +        if(!RoadError::emptyGlobalLog()){
    +            cout << RoadError::stringGlobalLog() << endl;
    +            RoadError::clearGlobalLog();
    +        }
    +    }
    +    if(!RoadError::emptyGlobalLog()){
    +        cout << RoadError::stringGlobalLog() << endl;
    +        RoadError::clearGlobalLog();
    +    }
    +    if(isAll){
    +        cout << "Finished test of libqhullcpp.  Test libqhull_r with eg/q_test after building libqhull_r/Makefile" << endl;
    +    }else{
    +        cout << "Finished test of one class.  Test all classes with 'qhulltest --all'" << endl;
    +    }
    +    RoadTest::deleteTests();
    +    return status;
    +}
    +
    +}//orgQhull
    +
    +int main(int argc, char *argv[])
    +{
    +    return orgQhull::main(argc, argv); // Needs RoadTest:: for TESTadd_() linkage
    +}
    +
    diff --git a/xs/src/qhull/src/qhulltest/qhulltest.pro b/xs/src/qhull/src/qhulltest/qhulltest.pro
    new file mode 100644
    index 0000000000..0da34d3755
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/qhulltest.pro
    @@ -0,0 +1,36 @@
    +# -------------------------------------------------
    +# qhulltest.pro -- Qt project for qhulltest.exe (QTestLib)
    +# cd $qh/build/qhulltest && qmake -tp vc -r ../../src/qhulltest/qhulltest.pro
    +# -------------------------------------------------
    +
    +include(../qhull-app-cpp.pri)
    +
    +TARGET = qhulltest
    +QT += testlib
    +MOC_DIR = moc
    +INCLUDEPATH += ..  # for MOC_DIR
    +
    +PRECOMPILED_HEADER = RoadTest.h
    +
    +HEADERS += RoadTest.h
    +
    +SOURCES += ../libqhullcpp/qt-qhull.cpp
    +SOURCES += Coordinates_test.cpp
    +SOURCES += PointCoordinates_test.cpp
    +SOURCES += Qhull_test.cpp
    +SOURCES += QhullFacet_test.cpp
    +SOURCES += QhullFacetList_test.cpp
    +SOURCES += QhullFacetSet_test.cpp
    +SOURCES += QhullHyperplane_test.cpp
    +SOURCES += QhullLinkedList_test.cpp
    +SOURCES += QhullPoint_test.cpp
    +SOURCES += QhullPoints_test.cpp
    +SOURCES += QhullPointSet_test.cpp
    +SOURCES += QhullRidge_test.cpp
    +SOURCES += QhullSet_test.cpp
    +SOURCES += qhulltest.cpp
    +SOURCES += QhullVertex_test.cpp
    +SOURCES += QhullVertexSet_test.cpp
    +SOURCES += RboxPoints_test.cpp
    +SOURCES += RoadTest.cpp
    +
    diff --git a/xs/src/qhull/src/qvoronoi/qvoronoi.c b/xs/src/qhull/src/qvoronoi/qvoronoi.c
    new file mode 100644
    index 0000000000..b93d237114
    --- /dev/null
    +++ b/xs/src/qhull/src/qvoronoi/qvoronoi.c
    @@ -0,0 +1,303 @@
    +/*
      ---------------------------------
    +
    +   qvoronoi.c
    +     compute Voronoi diagrams and furthest-point Voronoi
    +     diagrams using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull/libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qvoron_f.htm and qvoronoi.htm
    +   QJ and Qt are deprecated, but allowed for backwards compatibility
    +*/
    +char hidden_options[]=" d n m v H U Qb QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V Fa FA FC Fp FS Ft FV Pv Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qvoronoi- compute the Voronoi diagram\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qu   - compute furthest-site Voronoi diagram\n\
    +\n\
    +Qhull control options:\n\
    +    Qz   - add point-at-infinity to Voronoi diagram\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    QGn  - Voronoi vertices if visible from point n, -n if not\n\
    +    QVn  - Voronoi vertices for input point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - statistics\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Wn   - min facet width for non-coincident point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    s    - summary to stderr\n\
    +    p    - Voronoi vertices\n\
    +    o    - OFF format (dim, Voronoi vertices, and Voronoi regions)\n\
    +    i    - Delaunay regions (use 'Pp' to avoid warning)\n\
    +    f    - facet dump\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fc   - count plus coincident points (by Voronoi vertex)\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - separating hyperplanes for bounded Voronoi regions\n\
    +    FI   - ID for each Voronoi vertex\n\
    +    Fm   - merge count for each Voronoi vertex (511 max)\n\
    +    Fn   - count plus neighboring Voronoi vertices for each Voronoi vertex\n\
    +    FN   - count and Voronoi vertices for each Voronoi region\n\
    +    Fo   - separating hyperplanes for unbounded Voronoi regions\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest point and distance for each coincident point\n\
    +    FQ   - command used for qvoronoi\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                    for output: #Voronoi regions, #Voronoi vertices,\n\
    +                                #coincident points, #non-simplicial regions\n\
    +                    #real (2), max outer plane and min vertex\n\
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites\n\
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d only)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest Voronoi vertices by 'area'\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good Voronoi vertices (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep Voronoi vertices whose 'area' is at least n\n\
    +    PG   - print neighbors of good Voronoi vertices\n\
    +    PMn  - keep n Voronoi vertices with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qvoronoi- compute the Voronoi diagram.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qvoronoi.htm):\n\
    +    Qu   - compute furthest-site Voronoi diagram\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    p    - Voronoi vertices\n\
    +    o    - OFF file format (dim, Voronoi vertices, and Voronoi regions)\n\
    +    FN   - count and Voronoi vertices for each Voronoi region\n\
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites\n\
    +    Fi   - separating hyperplanes for bounded regions, 'Fo' for unbounded\n\
    +    G    - Geomview output (2-d only)\n\
    +    QVn  - Voronoi vertices for input point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +rbox c P0 D2 | qvoronoi s o         rbox c P0 D2 | qvoronoi Fi\n\
    +rbox c P0 D2 | qvoronoi Fo          rbox c P0 D2 | qvoronoi Fv\n\
    +rbox c P0 D2 | qvoronoi s Qu Fv     rbox c P0 D2 | qvoronoi Qu Fo\n\
    +rbox c G1 d D2 | qvoronoi s p       rbox c P0 D2 | qvoronoi s Fv QV0\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + OFF_format     p_vertices     i_delaunay     summary        facet_dump\n\
    +\n\
    + Fcoincident    Fd_cdd_in      FD_cdd_out     FF-dump-xridge Fi_bounded\n\
    + Fxtremes       Fmerges        Fneighbors     FNeigh_region  FOptions\n\
    + Fo_unbounded   FPoint_near    FQvoronoi      Fsummary       Fvoronoi\n\
    + FIDs\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QG_vertex_good Qsearch_1st    Qupper_voronoi QV_point_good  Qzinfinite\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh NOerrexit= False;
    +    qh_option("voronoi  _bbound-last  _coplanar-keep", NULL, NULL);
    +    qh DELAUNAY= True;     /* 'v'   */
    +    qh VORONOI= True;
    +    qh SCALElast= True;    /* 'Qbb' */
    +    qh_checkflags(qh qhull_command, hidden_options);
    +    qh_initflags(qh qhull_command);
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option("_merge-exact", NULL, NULL);
    +      qh MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh_ALL);
    +#else
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qvoronoi/qvoronoi.pro b/xs/src/qhull/src/qvoronoi/qvoronoi.pro
    new file mode 100644
    index 0000000000..4646c84472
    --- /dev/null
    +++ b/xs/src/qhull/src/qvoronoi/qvoronoi.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qvoronoi.pro -- Qt project file for qvoronoi.exe
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = qvoronoi
    +
    +SOURCES += qvoronoi.c
    diff --git a/xs/src/qhull/src/qvoronoi/qvoronoi_r.c b/xs/src/qhull/src/qvoronoi/qvoronoi_r.c
    new file mode 100644
    index 0000000000..6323c8b496
    --- /dev/null
    +++ b/xs/src/qhull/src/qvoronoi/qvoronoi_r.c
    @@ -0,0 +1,305 @@
    +/*
      ---------------------------------
    +
    +   qvoronoi.c
    +     compute Voronoi diagrams and furthest-point Voronoi
    +     diagrams using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qvoron_f.htm and qvoronoi.htm
    +   QJ and Qt are deprecated, but allowed for backwards compatibility
    +*/
    +char hidden_options[]=" d n m v H U Qb QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V Fa FA FC Fp FS Ft FV Pv Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qvoronoi- compute the Voronoi diagram\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qu   - compute furthest-site Voronoi diagram\n\
    +\n\
    +Qhull control options:\n\
    +    Qz   - add point-at-infinity to Voronoi diagram\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    QGn  - Voronoi vertices if visible from point n, -n if not\n\
    +    QVn  - Voronoi vertices for input point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - statistics\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Wn   - min facet width for non-coincident point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    s    - summary to stderr\n\
    +    p    - Voronoi vertices\n\
    +    o    - OFF format (dim, Voronoi vertices, and Voronoi regions)\n\
    +    i    - Delaunay regions (use 'Pp' to avoid warning)\n\
    +    f    - facet dump\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fc   - count plus coincident points (by Voronoi vertex)\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - separating hyperplanes for bounded Voronoi regions\n\
    +    FI   - ID for each Voronoi vertex\n\
    +    Fm   - merge count for each Voronoi vertex (511 max)\n\
    +    Fn   - count plus neighboring Voronoi vertices for each Voronoi vertex\n\
    +    FN   - count and Voronoi vertices for each Voronoi region\n\
    +    Fo   - separating hyperplanes for unbounded Voronoi regions\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest point and distance for each coincident point\n\
    +    FQ   - command used for qvoronoi\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                    for output: #Voronoi regions, #Voronoi vertices,\n\
    +                                #coincident points, #non-simplicial regions\n\
    +                    #real (2), max outer plane and min vertex\n\
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites\n\
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d only)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest Voronoi vertices by 'area'\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good Voronoi vertices (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep Voronoi vertices whose 'area' is at least n\n\
    +    PG   - print neighbors of good Voronoi vertices\n\
    +    PMn  - keep n Voronoi vertices with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qvoronoi- compute the Voronoi diagram.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qvoronoi.htm):\n\
    +    Qu   - compute furthest-site Voronoi diagram\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    p    - Voronoi vertices\n\
    +    o    - OFF file format (dim, Voronoi vertices, and Voronoi regions)\n\
    +    FN   - count and Voronoi vertices for each Voronoi region\n\
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites\n\
    +    Fi   - separating hyperplanes for bounded regions, 'Fo' for unbounded\n\
    +    G    - Geomview output (2-d only)\n\
    +    QVn  - Voronoi vertices for input point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +rbox c P0 D2 | qvoronoi s o         rbox c P0 D2 | qvoronoi Fi\n\
    +rbox c P0 D2 | qvoronoi Fo          rbox c P0 D2 | qvoronoi Fv\n\
    +rbox c P0 D2 | qvoronoi s Qu Fv     rbox c P0 D2 | qvoronoi Qu Fo\n\
    +rbox c G1 d D2 | qvoronoi s p       rbox c P0 D2 | qvoronoi s Fv QV0\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + OFF_format     p_vertices     i_delaunay     summary        facet_dump\n\
    +\n\
    + Fcoincident    Fd_cdd_in      FD_cdd_out     FF-dump-xridge Fi_bounded\n\
    + Fxtremes       Fmerges        Fneighbors     FNeigh_region  FOptions\n\
    + Fo_unbounded   FPoint_near    FQvoronoi      Fsummary       Fvoronoi\n\
    + FIDs\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QG_vertex_good Qsearch_1st    Qupper_voronoi QV_point_good  Qzinfinite\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_option(qh, "voronoi  _bbound-last  _coplanar-keep", NULL, NULL);
    +    qh->DELAUNAY= True;     /* 'v'   */
    +    qh->VORONOI= True;
    +    qh->SCALElast= True;    /* 'Qbb' */
    +    qh_checkflags(qh, qh->qhull_command, hidden_options);
    +    qh_initflags(qh, qh->qhull_command);
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option(qh, "_merge-exact", NULL, NULL);
    +      qh->MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/rbox/rbox.c b/xs/src/qhull/src/rbox/rbox.c
    new file mode 100644
    index 0000000000..d7c51b1aaf
    --- /dev/null
    +++ b/xs/src/qhull/src/rbox/rbox.c
    @@ -0,0 +1,88 @@
    +/*
      ---------------------------------
    +
    +   rbox.c
    +     rbox program for generating input points for qhull.
    +
    +   notes:
    +     50 points generated for 'rbox D4'
    +
    +*/
    +
    +#include "libqhull/libqhull.h"
    +#include "libqhull/random.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#endif
    +
    +char prompt[]= "\n\
    +-rbox- generate various point distributions.  Default is random in cube.\n\
    +\n\
    +args (any order, space separated):                    Version: 2016/01/18\n\
    +  3000    number of random points in cube, lens, spiral, sphere or grid\n\
    +  D3      dimension 3-d\n\
    +  c       add a unit cube to the output ('c G2.0' sets size)\n\
    +  d       add a unit diamond to the output ('d G2.0' sets size)\n\
    +  l       generate a regular 3-d spiral\n\
    +  r       generate a regular polygon, ('r s Z1 G0.1' makes a cone)\n\
    +  s       generate cospherical points\n\
    +  x       generate random points in simplex, may use 'r' or 'Wn'\n\
    +  y       same as 'x', plus simplex\n\
    +  Cn,r,m  add n nearly coincident points within radius r of m points\n\
    +  Pn,m,r  add point [n,m,r] first, pads with 0, maybe repeated\n\
    +\n\
    +  Ln      lens distribution of radius n.  Also 's', 'r', 'G', 'W'.\n\
    +  Mn,m,r  lattice(Mesh) rotated by [n,-m,0], [m,n,0], [0,0,r], ...\n\
    +          '27 M1,0,1' is {0,1,2} x {0,1,2} x {0,1,2}.  Try 'M3,4 z'.\n\
    +  W0.1    random distribution within 0.1 of the cube's or sphere's surface\n\
    +  Z0.5 s  random points in a 0.5 disk projected to a sphere\n\
    +  Z0.5 s G0.6 same as Z0.5 within a 0.6 gap\n\
    +\n\
    +  Bn      bounding box coordinates, default %2.2g\n\
    +  h       output as homogeneous coordinates for cdd\n\
    +  n       remove command line from the first line of output\n\
    +  On      offset coordinates by n\n\
    +  t       use time as the random number seed(default is command line)\n\
    +  tn      use n as the random number seed\n\
    +  z       print integer coordinates, default 'Bn' is %2.2g\n\
    +";
    +
    +/*--------------------------------------------
    +-rbox-  main procedure of rbox application
    +*/
    +int main(int argc, char **argv) {
    +  char *command;
    +  int command_size;
    +  int return_status;
    +
    +  QHULL_LIB_CHECK_RBOX
    +
    +  if (argc == 1) {
    +    printf(prompt, qh_DEFAULTbox, qh_DEFAULTzbox);
    +    return 1;
    +  }
    +  if (argc == 2 && strcmp(argv[1], "D4")==0)
    +    qh_fprintf_stderr(0, "\nStarting the rbox smoketest for qhull.  An immediate failure indicates\nthat non-reentrant rbox was linked to reentrant routines.  An immediate\nfailure of qhull may indicate that qhull was linked to the wrong\nqhull library.  Also try 'rbox D4 | qhull T1'\n");
    +
    +  command_size= qh_argv_to_command_size(argc, argv);
    +  if ((command= (char *)qh_malloc((size_t)command_size))) {
    +    if (!qh_argv_to_command(argc, argv, command, command_size)) {
    +      qh_fprintf_stderr(6264, "rbox internal error: allocated insufficient memory (%d) for arguments\n", command_size);
    +      return_status= qh_ERRinput;
    +    }else{
    +      return_status= qh_rboxpoints(stdout, stderr, command);
    +    }
    +    qh_free(command);
    +  }else {
    +    qh_fprintf_stderr(6265, "rbox error: insufficient memory for %d bytes\n", command_size);
    +    return_status= qh_ERRmem;
    +  }
    +  return return_status;
    +}/*main*/
    +
    diff --git a/xs/src/qhull/src/rbox/rbox.pro b/xs/src/qhull/src/rbox/rbox.pro
    new file mode 100644
    index 0000000000..6c21bdb6df
    --- /dev/null
    +++ b/xs/src/qhull/src/rbox/rbox.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# rbox.pro -- Qt project for rbox.exe with libqhullstatic
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = rbox
    +
    +SOURCES += rbox.c
    diff --git a/xs/src/qhull/src/rbox/rbox_r.c b/xs/src/qhull/src/rbox/rbox_r.c
    new file mode 100644
    index 0000000000..6ec74d914b
    --- /dev/null
    +++ b/xs/src/qhull/src/rbox/rbox_r.c
    @@ -0,0 +1,78 @@
    +
    +/*
      ---------------------------------
    +
    +   rbox.c
    +     rbox program for generating input points for qhull.
    +
    +   notes:
    +     50 points generated for 'rbox D4'
    +
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +#include "libqhull/random_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#endif
    +
    +char prompt[]= "\n\
    +-rbox- generate various point distributions.  Default is random in cube.\n\
    +\n\
    +args (any order, space separated):                    Version: 2016/01/18 r\n\
    +  3000    number of random points in cube, lens, spiral, sphere or grid\n\
    +  D3      dimension 3-d\n\
    +  c       add a unit cube to the output ('c G2.0' sets size)\n\
    +  d       add a unit diamond to the output ('d G2.0' sets size)\n\
    +  l       generate a regular 3-d spiral\n\
    +  r       generate a regular polygon, ('r s Z1 G0.1' makes a cone)\n\
    +  s       generate cospherical points\n\
    +  x       generate random points in simplex, may use 'r' or 'Wn'\n\
    +  y       same as 'x', plus simplex\n\
    +  Cn,r,m  add n nearly coincident points within radius r of m points\n\
    +  Pn,m,r  add point [n,m,r] first, pads with 0, maybe repeated\n\
    +\n\
    +  Ln      lens distribution of radius n.  Also 's', 'r', 'G', 'W'.\n\
    +  Mn,m,r  lattice(Mesh) rotated by [n,-m,0], [m,n,0], [0,0,r], ...\n\
    +          '27 M1,0,1' is {0,1,2} x {0,1,2} x {0,1,2}.  Try 'M3,4 z'.\n\
    +  W0.1    random distribution within 0.1 of the cube's or sphere's surface\n\
    +  Z0.5 s  random points in a 0.5 disk projected to a sphere\n\
    +  Z0.5 s G0.6 same as Z0.5 within a 0.6 gap\n\
    +\n\
    +  Bn      bounding box coordinates, default %2.2g\n\
    +  h       output as homogeneous coordinates for cdd\n\
    +  n       remove command line from the first line of output\n\
    +  On      offset coordinates by n\n\
    +  t       use time as the random number seed(default is command line)\n\
    +  tn      use n as the random number seed\n\
    +  z       print integer coordinates, default 'Bn' is %2.2g\n\
    +";
    +
    +/*--------------------------------------------
    +-rbox-  main procedure of rbox application
    +*/
    +int main(int argc, char **argv) {
    +  int return_status;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK_RBOX
    +
    +  if (argc == 1) {
    +    printf(prompt, qh_DEFAULTbox, qh_DEFAULTzbox);
    +    return 1;
    +  }
    +  if (argc == 2 && strcmp(argv[1], "D4")==0)
    +    qh_fprintf_stderr(0, "\nStarting the rbox smoketest for qhull.  An immediate failure indicates\nthat reentrant rbox was linked to non-reentrant routines.  An immediate\nfailure of qhull may indicate that qhull was linked to the wrong\nqhull library.  Also try 'rbox D4 | qhull T1'\n");
    +
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /*no qh_errexit, sets qh->qhull_command */
    +  return_status= qh_rboxpoints(qh, qh->qhull_command); /* Traps its own errors, qh_errexit_rbox() */
    +  return return_status;
    +}/*main*/
    +
    diff --git a/xs/src/qhull/src/testqset/testqset.c b/xs/src/qhull/src/testqset/testqset.c
    new file mode 100644
    index 0000000000..61057eef9c
    --- /dev/null
    +++ b/xs/src/qhull/src/testqset/testqset.c
    @@ -0,0 +1,891 @@
    +/*
      ---------------------------------
    +
    +   testset.c -- test qset.c and its use of mem.c
    +
    +   The test sets are pointers to int.  Normally a set is a pointer to a type (e.g., facetT, ridgeT, etc.).
    +   For consistency in notation, an "int" is typedef'd to i2T
    +
    +Functions and macros from qset.h.  Counts occurrences in this test.  Does not correspond to thoroughness.
    +    qh_setaddsorted -- 4 tests
    +    qh_setaddnth -- 1 test
    +    qh_setappend -- 7 tests
    +    qh_setappend_set -- 1 test
    +    qh_setappend2ndlast -- 1 test
    +    qh_setcheck -- lots of tests
    +    qh_setcompact -- 7 tests
    +    qh_setcopy -- 3 tests
    +    qh_setdel -- 1 tests
    +    qh_setdellast -- 1 tests
    +    qh_setdelnth -- 2 tests
    +    qh_setdelnthsorted -- 2 tests
    +    qh_setdelsorted -- 1 test
    +    qh_setduplicate -- not testable here
    +    qh_setequal -- 4 tests
    +    qh_setequal_except -- 2 tests
    +    qh_setequal_skip -- 2 tests
    +    qh_setfree -- 11+ tests
    +    qh_setfree2 -- not testable here
    +    qh_setfreelong -- 2 tests
    +    qh_setin -- 3 tests
    +    qh_setindex -- 4 tests
    +    qh_setlarger -- 1 test
    +    qh_setlast -- 2 tests
    +    qh_setnew -- 6 tests
    +    qh_setnew_delnthsorted
    +    qh_setprint -- tested elsewhere
    +    qh_setreplace -- 1 test
    +    qh_setsize -- 9+ tests
    +    qh_settemp -- 2 tests
    +    qh_settempfree -- 1 test
    +    qh_settempfree_all -- 1 test
    +    qh_settemppop -- 1 test
    +    qh_settemppush -- 1 test
    +    qh_settruncate -- 3 tests
    +    qh_setunique -- 3 tests
    +    qh_setzero -- 1 test
    +    FOREACHint_ -- 2 test
    +    FOREACHint4_
    +    FOREACHint_i_ -- 1 test
    +    FOREACHintreverse_
    +    FOREACHintreverse12_
    +    FOREACHsetelement_ -- 1 test
    +    FOREACHsetelement_i_ -- 1 test
    +    FOREACHsetelementreverse_ -- 1 test
    +    FOREACHsetelementreverse12_ -- 1 test
    +    SETelem_ -- 3 tests
    +    SETelemaddr_ -- 2 tests
    +    SETelemt_ -- not tested (generic)
    +    SETempty_ -- 1 test
    +    SETfirst_ -- 4 tests
    +    SETfirstt_ -- 2 tests
    +    SETindex_ -- 2 tests
    +    SETref_ -- 2 tests
    +    SETreturnsize_ -- 2 tests
    +    SETsecond_ -- 1 test
    +    SETsecondt_ -- 2 tests
    +    SETtruncate_ -- 2 tests
    +
    +    Copyright (c) 2012-2015 C.B. Barber. All rights reserved.
    +    $Id: //main/2015/qhull/src/testqset/testqset.c#4 $$Change: 2062 $
    +    $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "libqhull/user.h"  /* QHULL_CRTDBG */
    +#include "libqhull/qset.h"
    +#include "libqhull/mem.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +typedef int i2T;
    +#define MAXerrorCount 100 /* quit after n errors */
    +
    +#define FOREACHint_( ints ) FOREACHsetelement_( i2T, ints, i2)
    +#define FOREACHint4_( ints ) FOREACHsetelement_( i2T, ints, i4)
    +#define FOREACHint_i_( ints ) FOREACHsetelement_i_( i2T, ints, i2)
    +#define FOREACHintreverse_( ints ) FOREACHsetelementreverse_( i2T, ints, i2)
    +#define FOREACHintreverse12_( ints ) FOREACHsetelementreverse12_( i2T, ints, i2)
    +
    +enum {
    +    MAXint= 0x7fffffff,
    +};
    +
    +char prompt[]= "testqset N [M] [T5] -- Test qset.c and mem.c\n\
    +  \n\
    +  If this test fails then qhull will not work.\n\
    +  \n\
    +  Test qsets of 0..N integers with a check every M iterations (default ~log10)\n\
    +  Additional checking and logging if M is 1\n\
    +  \n\
    +  T5 turns on memory logging (qset does not log)\n\
    +  \n\
    +  For example:\n\
    +    testqset 10000\n\
    +";
    +
    +int error_count= 0;  /* Global error_count.  checkSetContents() keeps its own error count.  It exits on too many errors */
    +
    +/* Macros normally defined in geom.h */
    +#define fmax_( a,b )  ( ( a ) < ( b ) ? ( b ) : ( a ) )
    +
    +/* Macros normally defined in user.h */
    +
    +#define realT double
    +#define qh_MEMalign ((int)(fmax_(sizeof(realT), sizeof(void *))))
    +#define qh_MEMbufsize 0x10000       /* allocate 64K memory buffers */
    +#define qh_MEMinitbuf 0x20000      /* initially allocate 128K buffer */
    +
    +/* Macros normally defined in QhullSet.h */
    +
    +
    +/* Functions normally defined in user.h for usermem.c */
    +
    +void    qh_exit(int exitcode);
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +
    +/* Normally defined in user.c */
    +
    +void    qh_errexit(int exitcode, void *f, void *r)
    +{
    +    (void)f; /* unused */
    +    (void)r; /* unused */
    +    qh_exit(exitcode);
    +}
    +
    +/* Normally defined in userprintf.c */
    +
    +void    qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... )
    +{
    +    static int needs_cr= 0;  /* True if qh_fprintf needs a CR */
    +
    +    size_t fmtlen= strlen(fmt);
    +    va_list args;
    +
    +    if (!fp) {
    +        /* Do not use qh_fprintf_stderr.  This is a standalone program */
    +        fprintf(stderr, "QH6232 qh_fprintf: fp not defined for '%s'", fmt);
    +        qh_errexit(6232, NULL, NULL);
    +    }
    +    if(fmtlen>0){
    +        if(fmt[fmtlen-1]=='\n'){
    +            if(needs_cr && fmtlen>1){
    +                fprintf(fp, "\n");
    +            }
    +            needs_cr= 0;
    +        }else{
    +            needs_cr= 1;
    +        }
    +    }
    +    if(msgcode>=6000 && msgcode<7000){
    +        fprintf(fp, "Error TQ%d ", msgcode);
    +    }
    +    va_start(args, fmt);
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +}
    +
    +/* Defined below in order of use */
    +int main(int argc, char **argv);
    +void readOptions(int argc, char **argv, const char *promptstr, int *numInts, int *checkEvery, int *traceLevel);
    +void setupMemory(int tracelevel, int numInts, int **intarray);
    +
    +void testSetappendSettruncate(int numInts, int *intarray, int checkEvery);
    +void testSetdelSetadd(int numInts, int *intarray, int checkEvery);
    +void testSetappendSet(int numInts, int *intarray, int checkEvery);
    +void testSetcompactCopy(int numInts, int *intarray, int checkEvery);
    +void testSetequalInEtc(int numInts, int *intarray, int checkEvery);
    +void testSettemp(int numInts, int *intarray, int checkEvery);
    +void testSetlastEtc(int numInts, int *intarray, int checkEvery);
    +void testSetdelsortedEtc(int numInts, int *intarray, int checkEvery);
    +
    +int log_i(setT *set, const char *s, int i, int numInts, int checkEvery);
    +void checkSetContents(const char *name, setT *set, int count, int rangeA, int rangeB, int rangeC);
    +
    +int main(int argc, char **argv) {
    +    int *intarray= NULL;
    +    int numInts;
    +    int checkEvery= MAXint;
    +    int curlong, totlong;
    +    int traceLevel= 4; /* 4 normally, no tracing since qset does not log.  5 for memory tracing */
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)  /* user.h */
    +    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) );
    +    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
    +#endif
    +
    +    readOptions(argc, argv, prompt, &numInts, &checkEvery, &traceLevel);
    +    setupMemory(traceLevel, numInts, &intarray);
    +
    +    testSetappendSettruncate(numInts, intarray, checkEvery);
    +    testSetdelSetadd(numInts, intarray, checkEvery);
    +    testSetappendSet(numInts, intarray, checkEvery);
    +    testSetcompactCopy(numInts, intarray, checkEvery);
    +    testSetequalInEtc(numInts, intarray, checkEvery);
    +    testSettemp(numInts, intarray, checkEvery);
    +    testSetlastEtc(numInts, intarray, checkEvery);
    +    testSetdelsortedEtc(numInts, intarray, checkEvery);
    +    printf("\n\nNot testing qh_setduplicate and qh_setfree2.\n  These routines use heap-allocated set contents.  See qhull tests.\n");
    +
    +    qh_memstatistics(stdout);
    +    qh_memfreeshort(&curlong, &totlong);
    +    if (curlong || totlong){
    +        qh_fprintf(stderr, 8043, "qh_memfreeshort: did not free %d bytes of long memory(%d pieces)\n", totlong, curlong);
    +        error_count++;
    +    }
    +    if(error_count){
    +        qh_fprintf(stderr, 8012, "testqset: %d errors\n\n", error_count);
    +        exit(1);
    +    }else{
    +        printf("testqset: OK\n\n");
    +    }
    +    return 0;
    +}/*main*/
    +
    +void readOptions(int argc, char **argv, const char *promptstr, int *numInts, int *checkEvery, int *traceLevel)
    +{
    +    long numIntsArg;
    +    long checkEveryArg;
    +    char *endp;
    +    int isTracing= 0;
    +
    +    if (argc < 2 || argc > 4) {
    +        printf("%s", promptstr);
    +        exit(0);
    +    }
    +    numIntsArg= strtol(argv[1], &endp, 10);
    +    if(numIntsArg<1){
    +        qh_fprintf(stderr, 6301, "First argument should be 1 or greater.  Got '%s'\n", argv[1]);
    +        exit(1);
    +    }
    +    if(numIntsArg>MAXint){
    +        qh_fprintf(stderr, 6302, "qset does not currently support 64-bit ints.  Maximum count is %d\n", MAXint);
    +        exit(1);
    +    }
    +    *numInts= (int)numIntsArg;
    +
    +    if(argc==3 && argv[2][0]=='T' && argv[2][1]=='5' ){
    +        isTracing= 1;
    +        *traceLevel= 5;
    +    }
    +    if(argc==4 || (argc==3 && !isTracing)){
    +        checkEveryArg= strtol(argv[2], &endp, 10);
    +        if(checkEveryArg<1){
    +            qh_fprintf(stderr, 6321, "checkEvery argument should be 1 or greater.  Got '%s'\n", argv[2]);
    +            exit(1);
    +        }
    +        if(checkEveryArg>MAXint){
    +            qh_fprintf(stderr, 6322, "qset does not currently support 64-bit ints.  Maximum checkEvery is %d\n", MAXint);
    +            exit(1);
    +        }
    +        if(argc==4){
    +            if(argv[3][0]=='T' && argv[3][1]=='5' ){
    +                isTracing= 1;
    +                *traceLevel= 5;
    +            }else{
    +                qh_fprintf(stderr, 6242, "Optional third argument must be 'T5'.  Got '%s'\n", argv[3]);
    +                exit(1);
    +            }
    +        }
    +        *checkEvery= (int)checkEveryArg;
    +    }
    +}/*readOptions*/
    +
    +void setupMemory(int tracelevel, int numInts, int **intarray)
    +{
    +    int i;
    +    if(numInts<0 || numInts*(int)sizeof(int)<0){
    +        qh_fprintf(stderr, 6303, "qset does not currently support 64-bit ints.  Integer overflow\n");
    +        exit(1);
    +    }
    +    *intarray= qh_malloc(numInts * sizeof(int));
    +    if(!*intarray){
    +        qh_fprintf(stderr, 6304, "Failed to allocate %d bytes of memory\n", numInts * sizeof(int));
    +        exit(1);
    +    }
    +    for(i= 0; i=2){
    +        isCheck= log_i(ints, "n", numInts/2, numInts, checkEvery);
    +        qh_settruncate(ints, numInts/2);
    +        checkSetContents("qh_settruncate by half", ints, numInts/2, 0, -1, -1);
    +    }
    +    isCheck= log_i(ints, "n", 0, numInts, checkEvery);
    +    qh_settruncate(ints, 0);
    +    checkSetContents("qh_settruncate", ints, 0, -1, -1, -1);
    +
    +    qh_fprintf(stderr, 8003, "\n\nTesting qh_setappend2ndlast 0,0..%d.  Test 0", numInts-1);
    +    qh_setfree(&ints);
    +    ints= qh_setnew(4);
    +    qh_setappend(&ints, intarray+0);
    +    for(i= 0; i=2){
    +        isCheck= log_i(ints, "n", numInts/2, numInts, checkEvery);
    +        SETtruncate_(ints, numInts/2);
    +        checkSetContents("SETtruncate_ by half", ints, numInts/2, 0, -1, -1);
    +    }
    +    isCheck= log_i(ints, "n", 0, numInts, checkEvery);
    +    SETtruncate_(ints, 0);
    +    checkSetContents("SETtruncate_", ints, 0, -1, -1, -1);
    +
    +    qh_setfree(&ints);
    +}/*testSetappendSettruncate*/
    +
    +void testSetdelSetadd(int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints=qh_setnew(1);
    +    int i,j,isCheck;
    +
    +    qh_fprintf(stderr, 8003, "\n\nTesting qh_setdelnthsorted and qh_setaddnth 1..%d. Test", numInts-1);
    +    for(j=1; j3){
    +                qh_setdelsorted(ints, intarray+i/2);
    +                checkSetContents("qh_setdelsorted", ints, j-1, 0, i/2+1, -1);
    +                qh_setaddsorted(&ints, intarray+i/2);
    +                checkSetContents("qh_setaddsorted i/2", ints, j, 0, 0, -1);
    +            }
    +            qh_setdellast(ints);
    +            checkSetContents("qh_setdellast", ints, (j ? j-1 : 0), 0, -1, -1);
    +            if(j>0){
    +                qh_setaddsorted(&ints, intarray+j-1);
    +                checkSetContents("qh_setaddsorted j-1", ints, j, 0, -1, -1);
    +            }
    +            if(j>4){
    +                qh_setdelnthsorted(ints, i/2);
    +                if (checkEvery==1)
    +                  checkSetContents("qh_setdelnthsorted", ints, j-1, 0, i/2+1, -1);
    +                /* test qh_setdelnth and move-to-front */
    +                qh_setdelsorted(ints, intarray+i/2+1);
    +                checkSetContents("qh_setdelsorted 2", ints, j-2, 0, i/2+2, -1);
    +                qh_setaddsorted(&ints, intarray+i/2+1);
    +                if (checkEvery==1)
    +                  checkSetContents("qh_setaddsorted i/2+1", ints, j-1, 0, i/2+1, -1);
    +                qh_setaddsorted(&ints, intarray+i/2);
    +                checkSetContents("qh_setaddsorted i/2 again", ints, j, 0, -1, -1);
    +            }
    +            qh_setfree(&ints2);
    +            ints2= qh_setcopy(ints, 0);
    +            qh_setcompact(ints);
    +            qh_setcompact(ints2);
    +            checkSetContents("qh_setcompact", ints, j, 0, 0, -1);
    +            checkSetContents("qh_setcompact 2", ints2, j, 0, 0, -1);
    +            qh_setcompact(ints);
    +            checkSetContents("qh_setcompact 3", ints, j, 0, 0, -1);
    +            qh_setfree(&ints2);
    +        }
    +    }
    +    qh_setfreelong(&ints);
    +    if(ints){
    +        qh_setfree(&ints); /* Was quick memory */
    +    }
    +}/*testSetdelsortedEtc*/
    +
    +void testSetequalInEtc(int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    setT *ints3= NULL;
    +    int i,j,n;
    +
    +    qh_fprintf(stderr, 8019, "\n\nTesting qh_setequal*, qh_setin*, qh_setdel, qh_setdelnth, and qh_setlarger 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                if(qh_setequal(ints, ints2)){
    +                    qh_fprintf(stderr, 6324, "testSetequalInEtc: non-empty set equal to empty set\n", j);
    +                    error_count++;
    +                }
    +                qh_setfree(&ints3);
    +                ints3= qh_setcopy(ints, 0);
    +                checkSetContents("qh_setreplace", ints3, j, 0, -1, -1);
    +                qh_setreplace(ints3, intarray+j/2, intarray+j/2+1);
    +                if(j==1){
    +                    checkSetContents("qh_setreplace 2", ints3, j, j/2+1, -1, -1);
    +                }else if(j==2){
    +                    checkSetContents("qh_setreplace 3", ints3, j, 0, j/2+1, -1);
    +                }else{
    +                    checkSetContents("qh_setreplace 3", ints3, j, 0, j/2+1, j/2+1);
    +                }
    +                if(qh_setequal(ints, ints3)){
    +                    qh_fprintf(stderr, 6325, "testSetequalInEtc: modified set equal to original set at %d/2\n", j);
    +                    error_count++;
    +                }
    +                if(!qh_setequal_except(ints, intarray+j/2, ints3, intarray+j/2+1)){
    +                    qh_fprintf(stderr, 6326, "qh_setequal_except: modified set not equal to original set except modified\n", j);
    +                    error_count++;
    +                }
    +                if(qh_setequal_except(ints, intarray+j/2, ints3, intarray)){
    +                    qh_fprintf(stderr, 6327, "qh_setequal_except: modified set equal to original set with wrong excepts\n", j);
    +                    error_count++;
    +                }
    +                if(!qh_setequal_skip(ints, j/2, ints3, j/2)){
    +                    qh_fprintf(stderr, 6328, "qh_setequal_skip: modified set not equal to original set except modified\n", j);
    +                    error_count++;
    +                }
    +                if(j>2 && qh_setequal_skip(ints, j/2, ints3, 0)){
    +                    qh_fprintf(stderr, 6329, "qh_setequal_skip: modified set equal to original set with wrong excepts\n", j);
    +                    error_count++;
    +                }
    +                if(intarray+j/2+1!=qh_setdel(ints3, intarray+j/2+1)){
    +                    qh_fprintf(stderr, 6330, "qh_setdel: failed to find added element\n", j);
    +                    error_count++;
    +                }
    +                checkSetContents("qh_setdel", ints3, j-1, 0, j-1, (j==1 ? -1 : j/2+1));  /* swaps last element with deleted element */
    +                if(j>3){
    +                    qh_setdelnth(ints3, j/2); /* Delete at the same location as the original replace, for only one out-of-order element */
    +                    checkSetContents("qh_setdelnth", ints3, j-2, 0, j-2, (j==2 ? -1 : j/2+1));
    +                }
    +                if(qh_setin(ints3, intarray+j/2)){
    +                    qh_fprintf(stderr, 6331, "qh_setin: found deleted element\n");
    +                    error_count++;
    +                }
    +                if(j>4 && !qh_setin(ints3, intarray+1)){
    +                    qh_fprintf(stderr, 6332, "qh_setin: did not find second element\n");
    +                    error_count++;
    +                }
    +                if(j>4 && !qh_setin(ints3, intarray+j-2)){
    +                    qh_fprintf(stderr, 6333, "qh_setin: did not find last element\n");
    +                    error_count++;
    +                }
    +                if(-1!=qh_setindex(ints2, intarray)){
    +                    qh_fprintf(stderr, 6334, "qh_setindex: found element in empty set\n");
    +                    error_count++;
    +                }
    +                if(-1!=qh_setindex(ints3, intarray+j/2)){
    +                    qh_fprintf(stderr, 6335, "qh_setindex: found deleted element in set\n");
    +                    error_count++;
    +                }
    +                if(0!=qh_setindex(ints, intarray)){
    +                    qh_fprintf(stderr, 6336, "qh_setindex: did not find first in set\n");
    +                    error_count++;
    +                }
    +                if(j-1!=qh_setindex(ints, intarray+j-1)){
    +                    qh_fprintf(stderr, 6337, "qh_setindex: did not find last in set\n");
    +                    error_count++;
    +                }
    +            }
    +            qh_setfree(&ints2);
    +        }
    +    }
    +    qh_setfree(&ints3);
    +    qh_setfreelong(&ints);
    +    if(ints){
    +        qh_setfree(&ints); /* Was quick memory */
    +    }
    +}/*testSetequalInEtc*/
    +
    +
    +void testSetlastEtc(int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    int i,j,prepend;
    +
    +    qh_fprintf(stderr, 8020, "\n\nTesting qh_setlast, qh_setnew_delnthsorted, qh_setunique, and qh_setzero 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                if(intarray+j-1!=qh_setlast(ints)){
    +                    qh_fprintf(stderr, 6338, "qh_setlast: wrong last element\n");
    +                    error_count++;
    +                }
    +                prepend= (j<100 ? j/4 : 0);
    +                ints2= qh_setnew_delnthsorted(ints, qh_setsize(ints), j/2, prepend);
    +                if(qh_setsize(ints2)!=j+prepend-1){
    +                    qh_fprintf(stderr, 6345, "qh_setnew_delnthsorted: Expecting %d elements, got %d\n", j+prepend-1, qh_setsize(ints2));
    +                    error_count++;
    +                }
    +                /* Define prepended elements.  Otherwise qh_setdelnthsorted may fail */
    +                for(i= 0; i2){
    +                    qh_setzero(ints2, j/2, j-1);  /* max size may be j-1 */
    +                    if(qh_setsize(ints2)!=j-1){
    +                        qh_fprintf(stderr, 6342, "qh_setzero: Expecting %d elements, got %d\n", j, qh_setsize(ints2));
    +                        error_count++;
    +                    }
    +                    qh_setcompact(ints2);
    +                    checkSetContents("qh_setzero", ints2, j/2, 0, -1, -1);
    +                }
    +            }
    +            qh_setfree(&ints2);
    +        }
    +    }
    +    qh_setfreelong(&ints);
    +    if(ints){
    +        qh_setfree(&ints); /* Was quick memory */
    +    }
    +}/*testSetlastEtc*/
    +
    +void testSettemp(int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    setT *ints3= NULL;
    +    int i,j;
    +
    +    qh_fprintf(stderr, 8021, "\n\nTesting qh_settemp* 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                qh_settemppush(ints);
    +                ints3= qh_settemppop();
    +                if(ints!=ints3){
    +                    qh_fprintf(stderr, 6343, "qh_settemppop: didn't pop the push\n");
    +                    error_count++;
    +                }
    +            }
    +            qh_settempfree(&ints2);
    +        }
    +    }
    +    qh_setfreelong(&ints);
    +    if(ints){
    +        qh_setfree(&ints); /* Was quick memory */
    +    }
    +}/*testSettemp*/
    +
    +/* Check that a set contains count elements
    +   Ranges are consecutive (e.g., 1,2,3,...) starting with first, mid, and last
    +   Use -1 for missing ranges
    +   Returns -1 if should check results
    +*/
    +int log_i(setT *set, const char *s, int i, int numInts, int checkEvery)
    +{
    +    int j= i;
    +    int scale= 1;
    +    int e= 0;
    +    int *i2, **i2p;
    +
    +    if(*s || checkEvery==1){
    +        if(i<10){
    +            qh_fprintf(stderr, 8004, " %s%d", s, i);
    +        }else{
    +            if(i==11 && checkEvery==1){
    +                qh_fprintf(stderr, 8005, "\nResults after 10: ");
    +                FOREACHint_(set){
    +                    qh_fprintf(stderr, 8006, " %d", *i2);
    +                }
    +                qh_fprintf(stderr, 8007, " Continue");
    +            }
    +            while((j= j/10)>=1){
    +                scale *= 10;
    +                e++;
    +            }
    +            if(i==numInts-1){
    +                qh_fprintf(stderr, 8008, " %s%d", s, i);
    +            }else if(i==scale){
    +                if(i<=1000){
    +                    qh_fprintf(stderr, 8010, " %s%d", s, i);
    +                }else{
    +                    qh_fprintf(stderr, 8009, " %s1e%d", s, e);
    +                }
    +            }
    +        }
    +    }
    +    if(i<1000 || i%checkEvery==0 || i== scale || i==numInts-1){
    +        return 1;
    +    }
    +    return 0;
    +}/*log_i*/
    +
    +/* Check that a set contains count elements
    +   Ranges are consecutive (e.g., 1,2,3,...) starting with first, mid, and last
    +   Use -1 for missing ranges
    +*/
    +void checkSetContents(const char *name, setT *set, int count, int rangeA, int rangeB, int rangeC)
    +{
    +
    +    i2T *i2, **i2p;
    +    int i2_i, i2_n;
    +    int prev= -1; /* avoid warning */
    +    int i;
    +    int first= -3;
    +    int second= -3;
    +    int rangeCount=1;
    +    int actualSize= 0;
    +
    +    qh_setcheck(set, name, 0);
    +    if(set){
    +        SETreturnsize_(set, actualSize);  /* normally used only when speed is critical */
    +        if(*qh_setendpointer(set)!=NULL){
    +            qh_fprintf(stderr, 6344, "%s: qh_setendpointer(), 0x%x, is not NULL terminator of set 0x%x", name, qh_setendpointer(set), set);
    +            error_count++;
    +        }
    +    }
    +    if(actualSize!=qh_setsize(set)){
    +        qh_fprintf(stderr, 6305, "%s: SETreturnsize_() returned %d while qh_setsize() returns %d\n", name, actualSize, qh_setsize(set));
    +        error_count++;
    +    }else if(actualSize!=count){
    +        qh_fprintf(stderr, 6306, "%s: Expecting %d elements for set.  Got %d elements\n", name, count, actualSize);
    +        error_count++;
    +    }
    +    if(SETempty_(set)){
    +        if(count!=0){
    +            qh_fprintf(stderr, 6307, "%s: Got empty set instead of count %d, rangeA %d, rangeB %d, rangeC %d\n", name, count, rangeA, rangeB, rangeC);
    +            error_count++;
    +        }
    +    }else{
    +        /* Must be first, otherwise trips msvc 8 */
    +        i2T **p= SETaddr_(set, i2T);
    +        if(*p!=SETfirstt_(set, i2T)){
    +            qh_fprintf(stderr, 6309, "%s: SETaddr_(set, i2t) [%p] is not the same as SETfirst_(set) [%p]\n", name, SETaddr_(set, i2T), SETfirst_(set));
    +            error_count++;
    +        }
    +        first= *(int *)SETfirst_(set);
    +        if(SETfirst_(set)!=SETfirstt_(set, i2T)){
    +            qh_fprintf(stderr, 6308, "%s: SETfirst_(set) [%p] is not the same as SETfirstt_(set, i2T [%p]\n", name, SETfirst_(set), SETfirstt_(set, i2T));
    +            error_count++;
    +        }
    +        if(qh_setsize(set)>1){
    +            second= *(int *)SETsecond_(set);
    +            if(SETsecond_(set)!=SETsecondt_(set, i2T)){
    +                qh_fprintf(stderr, 6310, "%s: SETsecond_(set) [%p] is not the same as SETsecondt_(set, i2T) [%p]\n", name, SETsecond_(set), SETsecondt_(set, i2T));
    +                error_count++;
    +            }
    +        }
    +    }
    +    /* Test first run of ints in set*/
    +    i= 0;
    +    FOREACHint_(set){
    +        if(i2!=SETfirst_(set) && *i2!=prev+1){
    +            break;
    +        }
    +        prev= *i2;
    +        if(SETindex_(set, i2)!=i){
    +            qh_fprintf(stderr, 6311, "%s: Expecting SETIndex_(set, pointer-to-%d) to be %d.  Got %d\n", name, *i2, i, SETindex_(set, i2));
    +            error_count++;;
    +        }
    +        if(i2!=SETref_(i2)){
    +            qh_fprintf(stderr, 6312, "%s: SETref_(i2) [%p] does not point to i2 (the %d'th element)\n", name, SETref_(i2), i);
    +            error_count++;;
    +        }
    +        i++;
    +    }
    +    FOREACHint_i_(set){
    +        /* Must be first conditional, otherwise it trips up msvc 8 */
    +        i2T **p= SETelemaddr_(set, i2_i, i2T);
    +        if(i2!=*p){
    +            qh_fprintf(stderr, 6320, "%s: SETelemaddr_(set, %d, i2T) [%p] does not point to i2\n", name, i2_i, SETelemaddr_(set, i2_i, int));
    +            error_count++;;
    +        }
    +        if(i2_i==0){
    +            if(first!=*i2){
    +                qh_fprintf(stderr, 6314, "%s: First element is %d instead of SETfirst %d\n", name, *i2, first);
    +                error_count++;;
    +            }
    +            if(rangeA!=*i2){
    +                qh_fprintf(stderr, 6315, "%s: starts with %d instead of rangeA %d\n", name, *i2, rangeA);
    +                error_count++;;
    +            }
    +            prev= rangeA;
    +        }else{
    +            if(i2_i==1 && second!=*i2){
    +                qh_fprintf(stderr, 6316, "%s: Second element is %d instead of SETsecond %d\n", name, *i2, second);
    +                error_count++;;
    +            }
    +            if(prev+1==*i2){
    +                prev++;
    +            }else{
    +                if(*i2==rangeB){
    +                    prev= rangeB;
    +                    rangeB= -1;
    +                    rangeCount++;
    +                }else if(rangeB==-1 && *i2==rangeC){
    +                    prev= rangeC;
    +                    rangeC= -1;
    +                    rangeCount++;
    +                }else{
    +                    prev++;
    +                    qh_fprintf(stderr, 6317, "%s: Expecting %d'th element to be %d.  Got %d\n", name, i2_i, prev, *i2);
    +                    error_count++;
    +                }
    +            }
    +        }
    +        if(i2!=SETelem_(set, i2_i)){
    +            qh_fprintf(stderr, 6318, "%s: SETelem_(set, %d) [%p] is not i2 [%p] (the %d'th element)\n", name, i2_i, SETelem_(set, i2_i), i2, i2_i);
    +            error_count++;;
    +        }
    +        if(SETelemt_(set, i2_i, i2T)!=SETelem_(set, i2_i)){   /* Normally SETelemt_ is used for generic sets */
    +            qh_fprintf(stderr, 6319, "%s: SETelemt_(set, %d, i2T) [%p] is not SETelem_(set, %d) [%p] (the %d'th element)\n", name, i2_i, SETelemt_(set, i2_i, int), i2_i, SETelem_(set, i2_i), i2_i);
    +            error_count++;;
    +        }
    +    }
    +    if(error_count>=MAXerrorCount){
    +        qh_fprintf(stderr, 8011, "testqset: Stop testing after %d errors\n", error_count);
    +        exit(1);
    +    }
    +}/*checkSetContents*/
    +
    diff --git a/xs/src/qhull/src/testqset/testqset.pro b/xs/src/qhull/src/testqset/testqset.pro
    new file mode 100644
    index 0000000000..3f69048aac
    --- /dev/null
    +++ b/xs/src/qhull/src/testqset/testqset.pro
    @@ -0,0 +1,30 @@
    +# -------------------------------------------------
    +# testqset.pro -- Qt project file for testqset.exe
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +TARGET = testqset
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +CONFIG += qhull_warn_conversion
    +
    +build_pass:CONFIG(debug, debug|release){
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   OBJECTS_DIR = Release
    +}
    +
    +INCLUDEPATH += ..
    +
    +SOURCES += testqset.c
    +SOURCES += ../libqhull/qset.c
    +SOURCES += ../libqhull/mem.c
    +SOURCES += ../libqhull/usermem.c
    +
    +HEADERS += ../libqhull/mem.h
    +HEADERS += ../libqhull/qset.h
    +
    diff --git a/xs/src/qhull/src/testqset_r/testqset_r.c b/xs/src/qhull/src/testqset_r/testqset_r.c
    new file mode 100644
    index 0000000000..9a6d496e40
    --- /dev/null
    +++ b/xs/src/qhull/src/testqset_r/testqset_r.c
    @@ -0,0 +1,890 @@
    +/*
      ---------------------------------
    +
    +   testset.c -- test qset.c and its use of mem.c
    +
    +   The test sets are pointers to int.  Normally a set is a pointer to a type (e.g., facetT, ridgeT, etc.).
    +   For consistency in notation, an "int" is typedef'd to i2T
    +
    +Functions and macros from qset.h.  Counts occurrences in this test.  Does not correspond to thoroughness.
    +    qh_setaddsorted -- 4 tests
    +    qh_setaddnth -- 1 test
    +    qh_setappend -- 7 tests
    +    qh_setappend_set -- 1 test
    +    qh_setappend2ndlast -- 1 test
    +    qh_setcheck -- lots of tests
    +    qh_setcompact -- 7 tests
    +    qh_setcopy -- 3 tests
    +    qh_setdel -- 1 tests
    +    qh_setdellast -- 1 tests
    +    qh_setdelnth -- 2 tests
    +    qh_setdelnthsorted -- 2 tests
    +    qh_setdelsorted -- 1 test
    +    qh_setduplicate -- not testable here
    +    qh_setequal -- 4 tests
    +    qh_setequal_except -- 2 tests
    +    qh_setequal_skip -- 2 tests
    +    qh_setfree -- 11+ tests
    +    qh_setfree2 -- not testable here
    +    qh_setfreelong -- 2 tests
    +    qh_setin -- 3 tests
    +    qh_setindex -- 4 tests
    +    qh_setlarger -- 1 test
    +    qh_setlast -- 2 tests
    +    qh_setnew -- 6 tests
    +    qh_setnew_delnthsorted
    +    qh_setprint -- tested elsewhere
    +    qh_setreplace -- 1 test
    +    qh_setsize -- 9+ tests
    +    qh_settemp -- 2 tests
    +    qh_settempfree -- 1 test
    +    qh_settempfree_all -- 1 test
    +    qh_settemppop -- 1 test
    +    qh_settemppush -- 1 test
    +    qh_settruncate -- 3 tests
    +    qh_setunique -- 3 tests
    +    qh_setzero -- 1 test
    +    FOREACHint_ -- 2 test
    +    FOREACHint4_
    +    FOREACHint_i_ -- 1 test
    +    FOREACHintreverse_
    +    FOREACHintreverse12_
    +    FOREACHsetelement_ -- 1 test
    +    FOREACHsetelement_i_ -- 1 test
    +    FOREACHsetelementreverse_ -- 1 test
    +    FOREACHsetelementreverse12_ -- 1 test
    +    SETelem_ -- 3 tests
    +    SETelemaddr_ -- 2 tests
    +    SETelemt_ -- not tested (generic)
    +    SETempty_ -- 1 test
    +    SETfirst_ -- 4 tests
    +    SETfirstt_ -- 2 tests
    +    SETindex_ -- 2 tests
    +    SETref_ -- 2 tests
    +    SETreturnsize_ -- 2 tests
    +    SETsecond_ -- 1 test
    +    SETsecondt_ -- 2 tests
    +    SETtruncate_ -- 2 tests
    +
    +    Copyright (c) 2012-2015 C.B. Barber. All rights reserved.
    +    $Id: //main/2015/qhull/src/testqset_r/testqset_r.c#5 $$Change: 2064 $
    +    $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "libqhull_r/user_r.h"  /* QHULL_CRTDBG */
    +#include "libqhull_r/qset_r.h"
    +#include "libqhull_r/mem_r.h"
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +typedef int i2T;
    +#define MAXerrorCount 100 /* quit after n errors */
    +
    +#define FOREACHint_( ints ) FOREACHsetelement_( i2T, ints, i2)
    +#define FOREACHint4_( ints ) FOREACHsetelement_( i2T, ints, i4)
    +#define FOREACHint_i_( qh, ints ) FOREACHsetelement_i_( qh, i2T, ints, i2)
    +#define FOREACHintreverse_( qh, ints ) FOREACHsetelementreverse_( qh, i2T, ints, i2)
    +#define FOREACHintreverse12_( ints ) FOREACHsetelementreverse12_( i2T, ints, i2)
    +
    +enum {
    +    MAXint= 0x7fffffff,
    +};
    +
    +char prompt[]= "testqset_r N [M] [T5] -- Test reentrant qset_r.c and mem_r.c\n\
    +  \n\
    +  If this test fails then reentrant Qhull will not work.\n\
    +  \n\
    +  Test qsets of 0..N integers with a check every M iterations (default ~log10)\n\
    +  Additional checking and logging if M is 1\n\
    +  \n\
    +  T5 turns on memory logging (qset does not log)\n\
    +  \n\
    +  For example:\n\
    +    testqset_r 10000\n\
    +";
    +
    +int error_count= 0;  /* Global error_count.  checkSetContents(qh) keeps its own error count.  It exits on too many errors */
    +
    +/* Macros normally defined in geom.h */
    +#define fmax_( a,b )  ( ( a ) < ( b ) ? ( b ) : ( a ) )
    +
    +/* Macros normally defined in QhullSet.h */
    +
    +/* Functions normally defined in user_r.h for usermem_r.c */
    +
    +void    qh_exit(int exitcode);
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +
    +/* Normally defined in user_r.c */
    +
    +void    qh_errexit(qhT *qh, int exitcode, facetT *f, ridgeT *r)
    +{
    +    (void)f; /* unused */
    +    (void)r; /* unused */
    +    (void)qh; /* unused */
    +    qh_exit(exitcode);
    +}
    +
    +/* Normally defined in userprintf.c */
    +
    +void    qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... )
    +{
    +    static int needs_cr= 0;  /* True if qh_fprintf needs a CR. testqset_r is not itself reentrant */
    +
    +    size_t fmtlen= strlen(fmt);
    +    va_list args;
    +
    +    if (!fp) {
    +        /* Do not use qh_fprintf_stderr.  This is a standalone program */
    +        if(!qh)
    +            fprintf(stderr, "QH6241 qh_fprintf: fp and qh not defined for '%s'", fmt);
    +        else
    +            fprintf(stderr, "QH6232 qh_fprintf: fp is 0.  Was wrong qh_fprintf called for '%s'", fmt);
    +        qh_errexit(qh, 6232, NULL, NULL);
    +    }
    +    if(fmtlen>0){
    +        if(fmt[fmtlen-1]=='\n'){
    +            if(needs_cr && fmtlen>1){
    +                fprintf(fp, "\n");
    +            }
    +            needs_cr= 0;
    +        }else{
    +            needs_cr= 1;
    +        }
    +    }
    +    if(msgcode>=6000 && msgcode<7000){
    +        fprintf(fp, "Error TQ%d ", msgcode);
    +    }
    +    va_start(args, fmt);
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +}
    +
    +/* Defined below in order of use */
    +int main(int argc, char **argv);
    +void readOptions(qhT *qh, int argc, char **argv, const char *promptstr, int *numInts, int *checkEvery, int *traceLevel);
    +void setupMemory(qhT *qh, int tracelevel, int numInts, int **intarray);
    +
    +void testSetappendSettruncate(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetdelSetadd(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetappendSet(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetcompactCopy(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetequalInEtc(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSettemp(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetlastEtc(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetdelsortedEtc(qhT *qh, int numInts, int *intarray, int checkEvery);
    +
    +int log_i(qhT *qh, setT *set, const char *s, int i, int numInts, int checkEvery);
    +void checkSetContents(qhT *qh, const char *name, setT *set, int count, int rangeA, int rangeB, int rangeC);
    +
    +int main(int argc, char **argv) {
    +    int *intarray= NULL;
    +    int numInts;
    +    int checkEvery= MAXint;
    +    int curlong, totlong;
    +    int traceLevel= 4; /* 4 normally, no tracing since qset does not log.  Option 'T5' for memory tracing */
    +    qhT qh_qh;
    +    qhT *qh= &qh_qh;
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)  /* user_r.h */
    +    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) );
    +    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
    +#endif
    +
    +    readOptions(qh, argc, argv, prompt, &numInts, &checkEvery, &traceLevel);
    +    setupMemory(qh, traceLevel, numInts, &intarray);
    +
    +    testSetappendSettruncate(qh, numInts, intarray, checkEvery);
    +    testSetdelSetadd(qh, numInts, intarray, checkEvery);
    +    testSetappendSet(qh, numInts, intarray, checkEvery);
    +    testSetcompactCopy(qh, numInts, intarray, checkEvery);
    +    testSetequalInEtc(qh, numInts, intarray, checkEvery);
    +    testSettemp(qh, numInts, intarray, checkEvery);
    +    testSetlastEtc(qh, numInts, intarray, checkEvery);
    +    testSetdelsortedEtc(qh, numInts, intarray, checkEvery);
    +    printf("\n\nNot testing qh_setduplicate and qh_setfree2.\n  These routines use heap-allocated set contents.  See qhull tests.\n");
    +
    +    qh_memstatistics(qh, stdout);
    +    qh_memfreeshort(qh, &curlong, &totlong);
    +    if (curlong || totlong){
    +        qh_fprintf(qh, stderr, 8043, "qh_memfreeshort: did not free %d bytes of long memory(%d pieces)\n", totlong, curlong);
    +        error_count++;
    +    }
    +    if(error_count){
    +        qh_fprintf(qh, stderr, 8012, "testqset: %d errors\n\n", error_count);
    +        exit(1);
    +    }else{
    +        printf("testqset_r: OK\n\n");
    +    }
    +    return 0;
    +}/*main*/
    +
    +void readOptions(qhT *qh, int argc, char **argv, const char *promptstr, int *numInts, int *checkEvery, int *traceLevel)
    +{
    +    long numIntsArg;
    +    long checkEveryArg;
    +    char *endp;
    +    int isTracing= 0;
    +
    +    if (argc < 2 || argc > 4) {
    +        printf("%s", promptstr);
    +        exit(0);
    +    }
    +    numIntsArg= strtol(argv[1], &endp, 10);
    +    if(numIntsArg<1){
    +        qh_fprintf(qh, stderr, 6301, "First argument should be 1 or greater.  Got '%s'\n", argv[1]);
    +        exit(1);
    +    }
    +    if(numIntsArg>MAXint){
    +        qh_fprintf(qh, stderr, 6302, "qset does not currently support 64-bit ints.  Maximum count is %d\n", MAXint);
    +        exit(1);
    +    }
    +    *numInts= (int)numIntsArg;
    +
    +    if(argc==3 && argv[2][0]=='T' && argv[2][1]=='5' ){
    +        isTracing= 1;
    +        *traceLevel= 5;
    +    }
    +    if(argc==4 || (argc==3 && !isTracing)){
    +        checkEveryArg= strtol(argv[2], &endp, 10);
    +        if(checkEveryArg<1){
    +            qh_fprintf(qh, stderr, 6321, "checkEvery argument should be 1 or greater.  Got '%s'\n", argv[2]);
    +            exit(1);
    +        }
    +        if(checkEveryArg>MAXint){
    +            qh_fprintf(qh, stderr, 6322, "qset does not currently support 64-bit ints.  Maximum checkEvery is %d\n", MAXint);
    +            exit(1);
    +        }
    +        if(argc==4){
    +            if(argv[3][0]=='T' && argv[3][1]=='5' ){
    +                isTracing= 1;
    +                *traceLevel= 5;
    +            }else{
    +                qh_fprintf(qh, stderr, 6242, "Optional third argument must be 'T5'.  Got '%s'\n", argv[3]);
    +                exit(1);
    +            }
    +        }
    +        *checkEvery= (int)checkEveryArg;
    +    }
    +}/*readOptions*/
    +
    +void setupMemory(qhT *qh, int tracelevel, int numInts, int **intarray)
    +{
    +    int i;
    +    if(numInts<0 || numInts*(int)sizeof(int)<0){
    +        qh_fprintf(qh, stderr, 6303, "qset does not currently support 64-bit ints.  Integer overflow\n");
    +        exit(1);
    +    }
    +    *intarray= qh_malloc(numInts * sizeof(int));
    +    if(!*intarray){
    +        qh_fprintf(qh, stderr, 6304, "Failed to allocate %d bytes of memory\n", numInts * sizeof(int));
    +        exit(1);
    +    }
    +    for(i= 0; i=2){
    +        isCheck= log_i(qh, ints, "n", numInts/2, numInts, checkEvery);
    +        qh_settruncate(qh, ints, numInts/2);
    +        checkSetContents(qh, "qh_settruncate by half", ints, numInts/2, 0, -1, -1);
    +    }
    +    isCheck= log_i(qh, ints, "n", 0, numInts, checkEvery);
    +    qh_settruncate(qh, ints, 0);
    +    checkSetContents(qh, "qh_settruncate", ints, 0, -1, -1, -1);
    +
    +    qh_fprintf(qh, stderr, 8003, "\n\nTesting qh_setappend2ndlast 0,0..%d.  Test 0", numInts-1);
    +    qh_setfree(qh, &ints);
    +    ints= qh_setnew(qh, 4);
    +    qh_setappend(qh, &ints, intarray+0);
    +    for(i= 0; i=2){
    +        isCheck= log_i(qh, ints, "n", numInts/2, numInts, checkEvery);
    +        SETtruncate_(ints, numInts/2);
    +        checkSetContents(qh, "SETtruncate_ by half", ints, numInts/2, 0, -1, -1);
    +    }
    +    isCheck= log_i(qh, ints, "n", 0, numInts, checkEvery);
    +    SETtruncate_(ints, 0);
    +    checkSetContents(qh, "SETtruncate_", ints, 0, -1, -1, -1);
    +
    +    qh_setfree(qh, &ints);
    +}/*testSetappendSettruncate*/
    +
    +void testSetdelSetadd(qhT *qh, int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints=qh_setnew(qh, 1);
    +    int i,j,isCheck;
    +
    +    qh_fprintf(qh, stderr, 8003, "\n\nTesting qh_setdelnthsorted and qh_setaddnth 1..%d. Test", numInts-1);
    +    for(j=1; j3){
    +                qh_setdelsorted(ints, intarray+i/2);
    +                checkSetContents(qh, "qh_setdelsorted", ints, j-1, 0, i/2+1, -1);
    +                qh_setaddsorted(qh, &ints, intarray+i/2);
    +                checkSetContents(qh, "qh_setaddsorted i/2", ints, j, 0, 0, -1);
    +            }
    +            qh_setdellast(ints);
    +            checkSetContents(qh, "qh_setdellast", ints, (j ? j-1 : 0), 0, -1, -1);
    +            if(j>0){
    +                qh_setaddsorted(qh, &ints, intarray+j-1);
    +                checkSetContents(qh, "qh_setaddsorted j-1", ints, j, 0, -1, -1);
    +            }
    +            if(j>4){
    +                qh_setdelnthsorted(qh, ints, i/2);
    +                if (checkEvery==1)
    +                  checkSetContents(qh, "qh_setdelnthsorted", ints, j-1, 0, i/2+1, -1);
    +                /* test qh_setdelnth and move-to-front */
    +                qh_setdelsorted(ints, intarray+i/2+1);
    +                checkSetContents(qh, "qh_setdelsorted 2", ints, j-2, 0, i/2+2, -1);
    +                qh_setaddsorted(qh, &ints, intarray+i/2+1);
    +                if (checkEvery==1)
    +                  checkSetContents(qh, "qh_setaddsorted i/2+1", ints, j-1, 0, i/2+1, -1);
    +                qh_setaddsorted(qh, &ints, intarray+i/2);
    +                checkSetContents(qh, "qh_setaddsorted i/2 again", ints, j, 0, -1, -1);
    +            }
    +            qh_setfree(qh, &ints2);
    +            ints2= qh_setcopy(qh, ints, 0);
    +            qh_setcompact(qh, ints);
    +            qh_setcompact(qh, ints2);
    +            checkSetContents(qh, "qh_setcompact", ints, j, 0, 0, -1);
    +            checkSetContents(qh, "qh_setcompact 2", ints2, j, 0, 0, -1);
    +            qh_setcompact(qh, ints);
    +            checkSetContents(qh, "qh_setcompact 3", ints, j, 0, 0, -1);
    +            qh_setfree(qh, &ints2);
    +        }
    +    }
    +    qh_setfreelong(qh, &ints);
    +    if(ints){
    +        qh_setfree(qh, &ints); /* Was quick memory */
    +    }
    +}/*testSetdelsortedEtc*/
    +
    +void testSetequalInEtc(qhT *qh, int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    setT *ints3= NULL;
    +    int i,j,n;
    +
    +    qh_fprintf(qh, stderr, 8019, "\n\nTesting qh_setequal*, qh_setin*, qh_setdel, qh_setdelnth, and qh_setlarger 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                if(qh_setequal(ints, ints2)){
    +                    qh_fprintf(qh, stderr, 6324, "testSetequalInEtc: non-empty set equal to empty set\n", j);
    +                    error_count++;
    +                }
    +                qh_setfree(qh, &ints3);
    +                ints3= qh_setcopy(qh, ints, 0);
    +                checkSetContents(qh, "qh_setreplace", ints3, j, 0, -1, -1);
    +                qh_setreplace(qh, ints3, intarray+j/2, intarray+j/2+1);
    +                if(j==1){
    +                    checkSetContents(qh, "qh_setreplace 2", ints3, j, j/2+1, -1, -1);
    +                }else if(j==2){
    +                    checkSetContents(qh, "qh_setreplace 3", ints3, j, 0, j/2+1, -1);
    +                }else{
    +                    checkSetContents(qh, "qh_setreplace 3", ints3, j, 0, j/2+1, j/2+1);
    +                }
    +                if(qh_setequal(ints, ints3)){
    +                    qh_fprintf(qh, stderr, 6325, "testSetequalInEtc: modified set equal to original set at %d/2\n", j);
    +                    error_count++;
    +                }
    +                if(!qh_setequal_except(ints, intarray+j/2, ints3, intarray+j/2+1)){
    +                    qh_fprintf(qh, stderr, 6326, "qh_setequal_except: modified set not equal to original set except modified\n", j);
    +                    error_count++;
    +                }
    +                if(qh_setequal_except(ints, intarray+j/2, ints3, intarray)){
    +                    qh_fprintf(qh, stderr, 6327, "qh_setequal_except: modified set equal to original set with wrong excepts\n", j);
    +                    error_count++;
    +                }
    +                if(!qh_setequal_skip(ints, j/2, ints3, j/2)){
    +                    qh_fprintf(qh, stderr, 6328, "qh_setequal_skip: modified set not equal to original set except modified\n", j);
    +                    error_count++;
    +                }
    +                if(j>2 && qh_setequal_skip(ints, j/2, ints3, 0)){
    +                    qh_fprintf(qh, stderr, 6329, "qh_setequal_skip: modified set equal to original set with wrong excepts\n", j);
    +                    error_count++;
    +                }
    +                if(intarray+j/2+1!=qh_setdel(ints3, intarray+j/2+1)){
    +                    qh_fprintf(qh, stderr, 6330, "qh_setdel: failed to find added element\n", j);
    +                    error_count++;
    +                }
    +                checkSetContents(qh, "qh_setdel", ints3, j-1, 0, j-1, (j==1 ? -1 : j/2+1));  /* swaps last element with deleted element */
    +                if(j>3){
    +                    qh_setdelnth(qh, ints3, j/2); /* Delete at the same location as the original replace, for only one out-of-order element */
    +                    checkSetContents(qh, "qh_setdelnth", ints3, j-2, 0, j-2, (j==2 ? -1 : j/2+1));
    +                }
    +                if(qh_setin(ints3, intarray+j/2)){
    +                    qh_fprintf(qh, stderr, 6331, "qh_setin: found deleted element\n");
    +                    error_count++;
    +                }
    +                if(j>4 && !qh_setin(ints3, intarray+1)){
    +                    qh_fprintf(qh, stderr, 6332, "qh_setin: did not find second element\n");
    +                    error_count++;
    +                }
    +                if(j>4 && !qh_setin(ints3, intarray+j-2)){
    +                    qh_fprintf(qh, stderr, 6333, "qh_setin: did not find last element\n");
    +                    error_count++;
    +                }
    +                if(-1!=qh_setindex(ints2, intarray)){
    +                    qh_fprintf(qh, stderr, 6334, "qh_setindex: found element in empty set\n");
    +                    error_count++;
    +                }
    +                if(-1!=qh_setindex(ints3, intarray+j/2)){
    +                    qh_fprintf(qh, stderr, 6335, "qh_setindex: found deleted element in set\n");
    +                    error_count++;
    +                }
    +                if(0!=qh_setindex(ints, intarray)){
    +                    qh_fprintf(qh, stderr, 6336, "qh_setindex: did not find first in set\n");
    +                    error_count++;
    +                }
    +                if(j-1!=qh_setindex(ints, intarray+j-1)){
    +                    qh_fprintf(qh, stderr, 6337, "qh_setindex: did not find last in set\n");
    +                    error_count++;
    +                }
    +            }
    +            qh_setfree(qh, &ints2);
    +        }
    +    }
    +    qh_setfree(qh, &ints3);
    +    qh_setfreelong(qh, &ints);
    +    if(ints){
    +        qh_setfree(qh, &ints); /* Was quick memory */
    +    }
    +}/*testSetequalInEtc*/
    +
    +
    +void testSetlastEtc(qhT *qh, int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    int i,j,prepend;
    +
    +    qh_fprintf(qh, stderr, 8020, "\n\nTesting qh_setlast, qh_setnew_delnthsorted, qh_setunique, and qh_setzero 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                if(intarray+j-1!=qh_setlast(ints)){
    +                    qh_fprintf(qh, stderr, 6338, "qh_setlast: wrong last element\n");
    +                    error_count++;
    +                }
    +                prepend= (j<100 ? j/4 : 0);
    +                ints2= qh_setnew_delnthsorted(qh, ints, qh_setsize(qh, ints), j/2, prepend);
    +                if(qh_setsize(qh, ints2)!=j+prepend-1){
    +                    qh_fprintf(qh, stderr, 6345, "qh_setnew_delnthsorted: Expecting %d elements, got %d\n", j+prepend-1, qh_setsize(qh, ints2));
    +                    error_count++;
    +                }
    +                /* Define prepended elements.  Otherwise qh_setdelnthsorted may fail */
    +                for(i= 0; i2){
    +                    qh_setzero(qh, ints2, j/2, j-1);  /* max size may be j-1 */
    +                    if(qh_setsize(qh, ints2)!=j-1){
    +                        qh_fprintf(qh, stderr, 6342, "qh_setzero: Expecting %d elements, got %d\n", j, qh_setsize(qh, ints2));
    +                        error_count++;
    +                    }
    +                    qh_setcompact(qh, ints2);
    +                    checkSetContents(qh, "qh_setzero", ints2, j/2, 0, -1, -1);
    +                }
    +            }
    +            qh_setfree(qh, &ints2);
    +        }
    +    }
    +    qh_setfreelong(qh, &ints);
    +    if(ints){
    +        qh_setfree(qh, &ints); /* Was quick memory */
    +    }
    +}/*testSetlastEtc*/
    +
    +void testSettemp(qhT *qh, int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    setT *ints3= NULL;
    +    int i,j;
    +
    +    qh_fprintf(qh, stderr, 8021, "\n\nTesting qh_settemp* 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                qh_settemppush(qh, ints);
    +                ints3= qh_settemppop(qh);
    +                if(ints!=ints3){
    +                    qh_fprintf(qh, stderr, 6343, "qh_settemppop: didn't pop the push\n");
    +                    error_count++;
    +                }
    +            }
    +            qh_settempfree(qh, &ints2);
    +        }
    +    }
    +    qh_setfreelong(qh, &ints);
    +    if(ints){
    +        qh_setfree(qh, &ints); /* Was quick memory */
    +    }
    +}/*testSettemp*/
    +
    +/* Check that a set contains count elements
    +   Ranges are consecutive (e.g., 1,2,3,...) starting with first, mid, and last
    +   Use -1 for missing ranges
    +   Returns -1 if should check results
    +*/
    +int log_i(qhT *qh, setT *set, const char *s, int i, int numInts, int checkEvery)
    +{
    +    int j= i;
    +    int scale= 1;
    +    int e= 0;
    +    int *i2, **i2p;
    +
    +    if(*s || checkEvery==1){
    +        if(i<10){
    +            qh_fprintf(qh, stderr, 8004, " %s%d", s, i);
    +        }else{
    +            if(i==11 && checkEvery==1){
    +                qh_fprintf(qh, stderr, 8005, "\nResults after 10: ");
    +                FOREACHint_(set){
    +                    qh_fprintf(qh, stderr, 8006, " %d", *i2);
    +                }
    +                qh_fprintf(qh, stderr, 8007, " Continue");
    +            }
    +            while((j= j/10)>=1){
    +                scale *= 10;
    +                e++;
    +            }
    +            if(i==numInts-1){
    +                qh_fprintf(qh, stderr, 8008, " %s%d", s, i);
    +            }else if(i==scale){
    +                if(i<=1000){
    +                    qh_fprintf(qh, stderr, 8010, " %s%d", s, i);
    +                }else{
    +                    qh_fprintf(qh, stderr, 8009, " %s1e%d", s, e);
    +                }
    +            }
    +        }
    +    }
    +    if(i<1000 || i%checkEvery==0 || i== scale || i==numInts-1){
    +        return 1;
    +    }
    +    return 0;
    +}/*log_i*/
    +
    +/* Check that a set contains count elements
    +   Ranges are consecutive (e.g., 1,2,3,...) starting with first, mid, and last
    +   Use -1 for missing ranges
    +*/
    +void checkSetContents(qhT *qh, const char *name, setT *set, int count, int rangeA, int rangeB, int rangeC)
    +{
    +
    +    i2T *i2, **i2p;
    +    int i2_i, i2_n;
    +    int prev= -1; /* avoid warning */
    +    int i;
    +    int first= -3;
    +    int second= -3;
    +    int rangeCount=1;
    +    int actualSize= 0;
    +
    +    qh_setcheck(qh, set, name, 0);
    +    if(set){
    +        SETreturnsize_(set, actualSize);  /* normally used only when speed is critical */
    +        if(*qh_setendpointer(set)!=NULL){
    +            qh_fprintf(qh, stderr, 6344, "%s: qh_setendpointer(set), 0x%x, is not NULL terminator of set 0x%x", name, qh_setendpointer(set), set);
    +            error_count++;
    +        }
    +    }
    +    if(actualSize!=qh_setsize(qh, set)){
    +        qh_fprintf(qh, stderr, 6305, "%s: SETreturnsize_(qh) returned %d while qh_setsize(qh) returns %d\n", name, actualSize, qh_setsize(qh, set));
    +        error_count++;
    +    }else if(actualSize!=count){
    +        qh_fprintf(qh, stderr, 6306, "%s: Expecting %d elements for set.  Got %d elements\n", name, count, actualSize);
    +        error_count++;
    +    }
    +    if(SETempty_(set)){
    +        if(count!=0){
    +            qh_fprintf(qh, stderr, 6307, "%s: Got empty set instead of count %d, rangeA %d, rangeB %d, rangeC %d\n", name, count, rangeA, rangeB, rangeC);
    +            error_count++;
    +        }
    +    }else{
    +        /* Must be first, otherwise trips msvc 8 */
    +        i2T **p= SETaddr_(set, i2T);
    +        if(*p!=SETfirstt_(set, i2T)){
    +            qh_fprintf(qh, stderr, 6309, "%s: SETaddr_(set, i2t) [%p] is not the same as SETfirst_(set) [%p]\n", name, SETaddr_(set, i2T), SETfirst_(set));
    +            error_count++;
    +        }
    +        first= *(int *)SETfirst_(set);
    +        if(SETfirst_(set)!=SETfirstt_(set, i2T)){
    +            qh_fprintf(qh, stderr, 6308, "%s: SETfirst_(set) [%p] is not the same as SETfirstt_(set, i2T [%p]\n", name, SETfirst_(set), SETfirstt_(set, i2T));
    +            error_count++;
    +        }
    +        if(qh_setsize(qh, set)>1){
    +            second= *(int *)SETsecond_(set);
    +            if(SETsecond_(set)!=SETsecondt_(set, i2T)){
    +                qh_fprintf(qh, stderr, 6310, "%s: SETsecond_(set) [%p] is not the same as SETsecondt_(set, i2T) [%p]\n", name, SETsecond_(set), SETsecondt_(set, i2T));
    +                error_count++;
    +            }
    +        }
    +    }
    +    /* Test first run of ints in set*/
    +    i= 0;
    +    FOREACHint_(set){
    +        if(i2!=SETfirst_(set) && *i2!=prev+1){
    +            break;
    +        }
    +        prev= *i2;
    +        if(SETindex_(set, i2)!=i){
    +            qh_fprintf(qh, stderr, 6311, "%s: Expecting SETindex_(set, pointer-to-%d) to be %d.  Got %d\n", name, *i2, i, SETindex_(set, i2));
    +            error_count++;;
    +        }
    +        if(i2!=SETref_(i2)){
    +            qh_fprintf(qh, stderr, 6312, "%s: SETref_(i2) [%p] does not point to i2 (the %d'th element)\n", name, SETref_(i2), i);
    +            error_count++;;
    +        }
    +        i++;
    +    }
    +    FOREACHint_i_(qh, set){
    +        /* Must be first conditional, otherwise it trips up msvc 8 */
    +        i2T **p= SETelemaddr_(set, i2_i, i2T);
    +        if(i2!=*p){
    +            qh_fprintf(qh, stderr, 6320, "%s: SETelemaddr_(set, %d, i2T) [%p] does not point to i2\n", name, i2_i, SETelemaddr_(set, i2_i, int));
    +            error_count++;;
    +        }
    +        if(i2_i==0){
    +            if(first!=*i2){
    +                qh_fprintf(qh, stderr, 6314, "%s: First element is %d instead of SETfirst %d\n", name, *i2, first);
    +                error_count++;;
    +            }
    +            if(rangeA!=*i2){
    +                qh_fprintf(qh, stderr, 6315, "%s: starts with %d instead of rangeA %d\n", name, *i2, rangeA);
    +                error_count++;;
    +            }
    +            prev= rangeA;
    +        }else{
    +            if(i2_i==1 && second!=*i2){
    +                qh_fprintf(qh, stderr, 6316, "%s: Second element is %d instead of SETsecond %d\n", name, *i2, second);
    +                error_count++;;
    +            }
    +            if(prev+1==*i2){
    +                prev++;
    +            }else{
    +                if(*i2==rangeB){
    +                    prev= rangeB;
    +                    rangeB= -1;
    +                    rangeCount++;
    +                }else if(rangeB==-1 && *i2==rangeC){
    +                    prev= rangeC;
    +                    rangeC= -1;
    +                    rangeCount++;
    +                }else{
    +                    prev++;
    +                    qh_fprintf(qh, stderr, 6317, "%s: Expecting %d'th element to be %d.  Got %d\n", name, i2_i, prev, *i2);
    +                    error_count++;
    +                }
    +            }
    +        }
    +        if(i2!=SETelem_(set, i2_i)){
    +            qh_fprintf(qh, stderr, 6318, "%s: SETelem_(set, %d) [%p] is not i2 [%p] (the %d'th element)\n", name, i2_i, SETelem_(set, i2_i), i2, i2_i);
    +            error_count++;;
    +        }
    +        if(SETelemt_(set, i2_i, i2T)!=SETelem_(set, i2_i)){   /* Normally SETelemt_ is used for generic sets */
    +            qh_fprintf(qh, stderr, 6319, "%s: SETelemt_(set, %d, i2T) [%p] is not SETelem_(set, %d) [%p] (the %d'th element)\n", name, i2_i, SETelemt_(set, i2_i, int), i2_i, SETelem_(set, i2_i), i2_i);
    +            error_count++;;
    +        }
    +    }
    +    if(error_count>=MAXerrorCount){
    +        qh_fprintf(qh, stderr, 8011, "testqset: Stop testing after %d errors\n", error_count);
    +        exit(1);
    +    }
    +}/*checkSetContents*/
    +
    diff --git a/xs/src/qhull/src/testqset_r/testqset_r.pro b/xs/src/qhull/src/testqset_r/testqset_r.pro
    new file mode 100644
    index 0000000000..951e0624e8
    --- /dev/null
    +++ b/xs/src/qhull/src/testqset_r/testqset_r.pro
    @@ -0,0 +1,30 @@
    +# -------------------------------------------------
    +# testqset_r.pro -- Qt project file for testqset_r.exe
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +TARGET = testqset_r
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +CONFIG += qhull_warn_conversion
    +
    +build_pass:CONFIG(debug, debug|release){
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   OBJECTS_DIR = Release
    +}
    +
    +INCLUDEPATH += ..
    +
    +SOURCES += testqset_r.c
    +SOURCES += ../libqhull_r/qset_r.c
    +SOURCES += ../libqhull_r/mem_r.c
    +SOURCES += ../libqhull_r/usermem_r.c
    +
    +HEADERS += ../libqhull_r/mem_r.h
    +HEADERS += ../libqhull_r/qset_r.h
    +
    diff --git a/xs/src/qhull/src/user_eg/user_eg.c b/xs/src/qhull/src/user_eg/user_eg.c
    new file mode 100644
    index 0000000000..9c5fee51b3
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg/user_eg.c
    @@ -0,0 +1,330 @@
    +/*
      ---------------------------------
    +
    +  user_eg.c
    +  sample code for calling qhull() from an application
    +
    +  call with:
    +
    +     user_eg "cube/diamond options" "delaunay options" "halfspace options"
    +
    +  for example:
    +
    +     user_eg                             # return summaries
    +
    +     user_eg "n" "o" "Fp"                # return normals, OFF, points
    +
    +     user_eg "n Qt" "o" "Fp"             # triangulated cube
    +
    +     user_eg "QR0 p" "QR0 v p" "QR0 Fp"  # rotate input and return points
    +                                         # 'v' returns Voronoi
    +                                         # transform is rotated for halfspaces
    +
    +   main() makes three runs of qhull.
    +
    +     1) compute the convex hull of a cube
    +
    +     2a) compute the Delaunay triangulation of random points
    +
    +     2b) find the Delaunay triangle closest to a point.
    +
    +     3) compute the halfspace intersection of a diamond
    +
    + notes:
    +
    +   For another example, see main() in unix.c and user_eg2.c.
    +   These examples, call qh_qhull() directly.  They allow
    +   tighter control on the code loaded with Qhull.
    +
    +   For a C++ example, see user_eg3/user_eg3_r.cpp
    +
    +   Summaries are sent to stderr if other output formats are used
    +
    +   compiled by 'make bin/user_eg'
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +*/
    +
    +#define qh_QHimport
    +#include "libqhull/qhull_a.h"
    +
    +/*-------------------------------------------------
    +-internal function prototypes
    +*/
    +void print_summary(void);
    +void makecube(coordT *points, int numpoints, int dim);
    +void makeDelaunay(coordT *points, int numpoints, int dim, int seed);
    +void findDelaunay(int dim);
    +void makehalf(coordT *points, int numpoints, int dim);
    +
    +/*-------------------------------------------------
    +-print_summary()
    +*/
    +void print_summary(void) {
    +  facetT *facet;
    +  int k;
    +
    +  printf("\n%d vertices and %d facets with normals:\n",
    +                 qh num_vertices, qh num_facets);
    +  FORALLfacets {
    +    for (k=0; k < qh hull_dim; k++)
    +      printf("%6.2g ", facet->normal[k]);
    +    printf("\n");
    +  }
    +}
    +
    +/*--------------------------------------------------
    +-makecube- set points to vertices of cube
    +  points is numpoints X dim
    +*/
    +void makecube(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jlocate a facet with qh_findbestfacet()
    +*/
    +void findDelaunay(int dim) {
    +  int k;
    +  coordT point[ 100];
    +  boolT isoutside;
    +  realT bestdist;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  for (k= 0; k < dim; k++)
    +    point[k]= 0.5;
    +  qh_setdelaunay(dim+1, 1, point);
    +  facet= qh_findbestfacet(point, qh_ALL, &bestdist, &isoutside);
    +  if (facet->tricoplanar) {
    +    fprintf(stderr, "findDelaunay: not implemented for triangulated, non-simplicial Delaunay regions (tricoplanar facet, f%d).\n",
    +       facet->id);
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    for (k=0; k < dim; k++)
    +      printf("%5.2f ", vertex->point[k]);
    +    printf("\n");
    +  }
    +} /*.findDelaunay.*/
    +
    +/*--------------------------------------------------
    +-makehalf- set points to halfspaces for a (dim)-dimensional diamond
    +  points is numpoints X dim+1
    +
    +  each halfspace consists of dim coefficients followed by an offset
    +*/
    +void makehalf(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; j&1'\n\n");
    +
    +#if qh_QHpointer  /* see user.h */
    +  if (qh_qh){
    +      printf("QH6233: Qhull link error.  The global variable qh_qh was not initialized\n\
    +to NULL by global.c.  Please compile user_eg.c with -Dqh_QHpointer_dllimport\n\
    +as well as -Dqh_QHpointer, or use libqhullstatic, or use a different tool chain.\n\n");
    +      return -1;
    +  }
    +#endif
    +
    +  /*
    +    Run 1: convex hull
    +  */
    +  printf( "\ncompute convex hull of cube after rotating input\n");
    +  sprintf(flags, "qhull s Tcv %s", argc >= 2 ? argv[1] : "");
    +  numpoints= SIZEcube;
    +  makecube(points, numpoints, DIM);
    +  for (i=numpoints; i--; )
    +    rows[i]= points+dim*i;
    +  qh_printmatrix(outfile, "input", rows, numpoints, dim);
    +  exitcode= qh_new_qhull(dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh facet_list' contains the convex hull */
    +    print_summary();
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +  }
    +  qh_freeqhull(!qh_ALL);                   /* free long memory  */
    +  qh_memfreeshort(&curlong, &totlong);    /* free short memory and memory allocator */
    +  if (curlong || totlong)
    +    fprintf(errfile, "qhull internal warning (user_eg, #1): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +  /*
    +    Run 2: Delaunay triangulation
    +  */
    +
    +  printf( "\ncompute %d-d Delaunay triangulation\n", dim);
    +  sprintf(flags, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +  numpoints= SIZEcube;
    +  makeDelaunay(points, numpoints, dim, (int)time(NULL));
    +  for (i=numpoints; i--; )
    +    rows[i]= points+dim*i;
    +  qh_printmatrix(outfile, "input", rows, numpoints, dim);
    +  exitcode= qh_new_qhull(dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh facet_list' contains the convex hull */
    +    /* If you want a Voronoi diagram ('v') and do not request output (i.e., outfile=NULL),
    +       call qh_setvoronoi_all() after qh_new_qhull(). */
    +    print_summary();
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +    printf( "\nfind %d-d Delaunay triangle closest to [0.5, 0.5, ...]\n", dim);
    +    exitcode= setjmp(qh errexit);
    +    if (!exitcode) {
    +      /* Trap Qhull errors in findDelaunay().  Without the setjmp(), Qhull
    +         will exit() after reporting an error */
    +      qh NOerrexit= False;
    +      findDelaunay(DIM);
    +    }
    +    qh NOerrexit= True;
    +  }
    +#if qh_QHpointer  /* see user.h */
    +  {
    +    qhT *oldqhA, *oldqhB;
    +    coordT pointsB[DIM*TOTpoints]; /* array of coordinates for each point */
    +
    +    printf( "\nsave first triangulation and compute a new triangulation\n");
    +    oldqhA= qh_save_qhull();
    +    sprintf(flags, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +    numpoints= SIZEcube;
    +    makeDelaunay(pointsB, numpoints, dim, (int)time(NULL)+1);
    +    for (i=numpoints; i--; )
    +      rows[i]= pointsB+dim*i;
    +    qh_printmatrix(outfile, "input", rows, numpoints, dim);
    +    exitcode= qh_new_qhull(dim, numpoints, pointsB, ismalloc,
    +                      flags, outfile, errfile);
    +    if (!exitcode)
    +      print_summary();
    +    printf( "\nsave second triangulation and restore first one\n");
    +    oldqhB= qh_save_qhull();
    +    qh_restore_qhull(&oldqhA);
    +    print_summary();
    +    printf( "\nfree first triangulation and restore second one.\n");
    +    qh_freeqhull(qh_ALL);               /* free short and long memory used by first call */
    +                                         /* do not use qh_memfreeshort */
    +    qh_restore_qhull(&oldqhB);
    +    print_summary();
    +  }
    +#endif
    +  qh_freeqhull(!qh_ALL);                 /* free long memory */
    +  qh_memfreeshort(&curlong, &totlong);  /* free short memory and memory allocator */
    +  if (curlong || totlong)
    +    fprintf(errfile, "qhull internal warning (user_eg, #2): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +  /*
    +    Run 3: halfspace intersection about the origin
    +  */
    +  printf( "\ncompute halfspace intersection about the origin for a diamond\n");
    +  sprintf(flags, "qhull H0 s Tcv %s", argc >= 4 ? argv[3] : "Fp");
    +  numpoints= SIZEcube;
    +  makehalf(points, numpoints, dim);
    +  for (i=numpoints; i--; )
    +    rows[i]= points+(dim+1)*i;
    +  qh_printmatrix(outfile, "input as halfspace coefficients + offsets", rows, numpoints, dim+1);
    +  /* use qh_sethalfspace_all to transform the halfspaces yourself.
    +     If so, set 'qh feasible_point and do not use option 'Hn,...' [it would retransform the halfspaces]
    +  */
    +  exitcode= qh_new_qhull(dim+1, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode)
    +    print_summary();
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)  /* could also check previous runs */
    +    fprintf(stderr, "qhull internal warning (user_eg, #3): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/user_eg/user_eg.pro b/xs/src/qhull/src/user_eg/user_eg.pro
    new file mode 100644
    index 0000000000..9dda010099
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg/user_eg.pro
    @@ -0,0 +1,11 @@
    +# -------------------------------------------------
    +# user_eg.pro -- Qt project for Qhull demonstration using shared Qhull library
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-app-shared_r.pri)
    +
    +TARGET = user_eg
    +
    +SOURCES += user_eg_r.c
    diff --git a/xs/src/qhull/src/user_eg/user_eg_r.c b/xs/src/qhull/src/user_eg/user_eg_r.c
    new file mode 100644
    index 0000000000..21b0ccf4e9
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg/user_eg_r.c
    @@ -0,0 +1,326 @@
    +/*
      ---------------------------------
    +
    +  user_eg_r.c
    +  sample code for calling qhull() from an application.  Uses reentrant libqhull_r
    +
    +  call with:
    +
    +     user_eg "cube/diamond options" "delaunay options" "halfspace options"
    +
    +  for example:
    +
    +     user_eg                             # return summaries
    +
    +     user_eg "n" "o" "Fp"                # return normals, OFF, points
    +
    +     user_eg "n Qt" "o" "Fp"             # triangulated cube
    +
    +     user_eg "QR0 p" "QR0 v p" "QR0 Fp"  # rotate input and return points
    +                                         # 'v' returns Voronoi
    +                                         # transform is rotated for halfspaces
    +
    +   main() makes three runs of qhull.
    +
    +     1) compute the convex hull of a cube
    +
    +     2a) compute the Delaunay triangulation of random points
    +
    +     2b) find the Delaunay triangle closest to a point.
    +
    +     3) compute the halfspace intersection of a diamond
    +
    + notes:
    +
    +   For another example, see main() in unix_r.c and user_eg2_r.c.
    +   These examples, call qh_qhull() directly.  They allow
    +   tighter control on the code loaded with Qhull.
    +
    +   For a C++ example, see user_eg3/user_eg3_r.cpp
    +
    +   Summaries are sent to stderr if other output formats are used
    +
    +   compiled by 'make bin/user_eg'
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +*/
    +
    +#define qh_QHimport
    +#include "libqhull_r/qhull_ra.h"
    +
    +/*-------------------------------------------------
    +-internal function prototypes
    +*/
    +void print_summary(qhT *qh);
    +void makecube(coordT *points, int numpoints, int dim);
    +void makeDelaunay(qhT *qh, coordT *points, int numpoints, int dim, int seed);
    +void findDelaunay(qhT *qh, int dim);
    +void makehalf(coordT *points, int numpoints, int dim);
    +
    +/*-------------------------------------------------
    +-print_summary(qh)
    +*/
    +void print_summary(qhT *qh) {
    +  facetT *facet;
    +  int k;
    +
    +  printf("\n%d vertices and %d facets with normals:\n",
    +                 qh->num_vertices, qh->num_facets);
    +  FORALLfacets {
    +    for (k=0; k < qh->hull_dim; k++)
    +      printf("%6.2g ", facet->normal[k]);
    +    printf("\n");
    +  }
    +}
    +
    +/*--------------------------------------------------
    +-makecube- set points to vertices of cube
    +  points is numpoints X dim
    +*/
    +void makecube(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jlocate a facet with qh_findbestfacet()
    +*/
    +void findDelaunay(qhT *qh, int dim) {
    +  int k;
    +  coordT point[ 100];
    +  boolT isoutside;
    +  realT bestdist;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  for (k= 0; k < dim; k++)
    +    point[k]= 0.5;
    +  qh_setdelaunay(qh, dim+1, 1, point);
    +  facet= qh_findbestfacet(qh, point, qh_ALL, &bestdist, &isoutside);
    +  if (facet->tricoplanar) {
    +    fprintf(stderr, "findDelaunay: not implemented for triangulated, non-simplicial Delaunay regions (tricoplanar facet, f%d).\n",
    +       facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    for (k=0; k < dim; k++)
    +      printf("%5.2f ", vertex->point[k]);
    +    printf("\n");
    +  }
    +} /*.findDelaunay.*/
    +
    +/*--------------------------------------------------
    +-makehalf- set points to halfspaces for a (dim)-dimensional diamond
    +  points is numpoints X dim+1
    +
    +  each halfspace consists of dim coefficients followed by an offset
    +*/
    +void makehalf(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; j&1'\n\n");
    +
    +  /*
    +    Run 1: convex hull
    +  */
    +  printf( "\ncompute convex hull of cube after rotating input\n");
    +  sprintf(flags, "qhull s Tcv %s", argc >= 2 ? argv[1] : "");
    +  numpoints= SIZEcube;
    +  makecube(points, numpoints, DIM);
    +  for (i=numpoints; i--; )
    +    rows[i]= points+dim*i;
    +  qh_printmatrix(qh, outfile, "input", rows, numpoints, dim);
    +  exitcode= qh_new_qhull(qh, dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh->facet_list' contains the convex hull */
    +    print_summary(qh);
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +  }
    +  qh_freeqhull(qh, !qh_ALL);                   /* free long memory  */
    +  qh_memfreeshort(qh, &curlong, &totlong);    /* free short memory and memory allocator */
    +  if (curlong || totlong)
    +    fprintf(errfile, "qhull internal warning (user_eg, #1): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +  /*
    +    Run 2: Delaunay triangulation, reusing the previous qh/qh_qh
    +  */
    +
    +  printf( "\ncompute %d-d Delaunay triangulation\n", dim);
    +  sprintf(flags, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +  numpoints= SIZEcube;
    +  makeDelaunay(qh, points, numpoints, dim, (int)time(NULL));
    +  for (i=numpoints; i--; )
    +    rows[i]= points+dim*i;
    +  qh_printmatrix(qh, outfile, "input", rows, numpoints, dim);
    +  exitcode= qh_new_qhull(qh, dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh->facet_list' contains the convex hull */
    +    /* If you want a Voronoi diagram ('v') and do not request output (i.e., outfile=NULL),
    +       call qh_setvoronoi_all() after qh_new_qhull(). */
    +    print_summary(qh);
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +    printf( "\nfind %d-d Delaunay triangle closest to [0.5, 0.5, ...]\n", dim);
    +    exitcode= setjmp(qh->errexit);
    +    if (!exitcode) {
    +      /* Trap Qhull errors in findDelaunay().  Without the setjmp(), Qhull
    +         will exit() after reporting an error */
    +      qh->NOerrexit= False;
    +      findDelaunay(qh, DIM);
    +    }
    +    qh->NOerrexit= True;
    +  }
    +  {
    +    coordT pointsB[DIM*TOTpoints]; /* array of coordinates for each point */
    +
    +    qhT qh_qhB;    /* Create a new instance of Qhull (qhB) */
    +    qhT *qhB= &qh_qhB;
    +    qh_zero(qhB, errfile);
    +
    +    printf( "\nCompute a new triangulation as a separate instance of Qhull\n");
    +    sprintf(flags, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +    numpoints= SIZEcube;
    +    makeDelaunay(qhB, pointsB, numpoints, dim, (int)time(NULL)+1);
    +    for (i=numpoints; i--; )
    +      rows[i]= pointsB+dim*i;
    +    qh_printmatrix(qhB, outfile, "input", rows, numpoints, dim);
    +    exitcode= qh_new_qhull(qhB, dim, numpoints, pointsB, ismalloc,
    +                      flags, outfile, errfile);
    +    if (!exitcode)
    +      print_summary(qhB);
    +    printf( "\nFree memory allocated by the new instance of Qhull, and redisplay the old results.\n");
    +    qh_freeqhull(qhB, !qh_ALL);                 /* free long memory */
    +    qh_memfreeshort(qhB, &curlong, &totlong);  /* free short memory and memory allocator */
    +    if (curlong || totlong)
    +        fprintf(errfile, "qhull internal warning (user_eg, #4): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +    printf( "\n\n");
    +    print_summary(qh);  /* The other instance is unchanged */
    +    /* Exiting the block frees qh_qhB */
    +  }
    +  qh_freeqhull(qh, !qh_ALL);                 /* free long memory */
    +  qh_memfreeshort(qh, &curlong, &totlong);  /* free short memory and memory allocator */
    +  if (curlong || totlong)
    +    fprintf(errfile, "qhull internal warning (user_eg, #2): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +  /*
    +    Run 3: halfspace intersection about the origin
    +  */
    +  printf( "\ncompute halfspace intersection about the origin for a diamond\n");
    +  sprintf(flags, "qhull H0 s Tcv %s", argc >= 4 ? argv[3] : "Fp");
    +  numpoints= SIZEcube;
    +  makehalf(points, numpoints, dim);
    +  for (i=numpoints; i--; )
    +    rows[i]= points+(dim+1)*i;
    +  qh_printmatrix(qh, outfile, "input as halfspace coefficients + offsets", rows, numpoints, dim+1);
    +  /* use qh_sethalfspace_all to transform the halfspaces yourself.
    +     If so, set 'qh->feasible_point and do not use option 'Hn,...' [it would retransform the halfspaces]
    +  */
    +  exitcode= qh_new_qhull(qh, dim+1, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode)
    +    print_summary(qh);
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)  /* could also check previous runs */
    +    fprintf(stderr, "qhull internal warning (user_eg, #3): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/user_eg2/user_eg2.c b/xs/src/qhull/src/user_eg2/user_eg2.c
    new file mode 100644
    index 0000000000..a455f025d1
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg2/user_eg2.c
    @@ -0,0 +1,746 @@
    +/*
      ---------------------------------
    +
    +  user_eg2.c
    +
    +  sample code for calling qhull() from an application.
    +
    +  See user_eg.c for a simpler method using qh_new_qhull().
    +  The method used here and in unix.c gives you additional
    +  control over Qhull.
    +
    +  See user_eg3/user_eg3_r.cpp for a C++ example
    +
    +  call with:
    +
    +     user_eg2 "triangulated cube/diamond options" "delaunay options" "halfspace options"
    +
    +  for example:
    +
    +     user_eg2                             # return summaries
    +
    +     user_eg2 "n" "o" "Fp"                # return normals, OFF, points
    +
    +     user_eg2 "QR0 p" "QR0 v p" "QR0 Fp"  # rotate input and return points
    +                                         # 'v' returns Voronoi
    +                                         # transform is rotated for halfspaces
    +
    +   main() makes three runs of qhull.
    +
    +     1) compute the convex hull of a cube, and incrementally add a diamond
    +
    +     2a) compute the Delaunay triangulation of random points, and add points.
    +
    +     2b) find the Delaunay triangle closest to a point.
    +
    +     3) compute the halfspace intersection of a diamond, and add a cube
    +
    + notes:
    +
    +   summaries are sent to stderr if other output formats are used
    +
    +   derived from unix.c and compiled by 'make bin/user_eg2'
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +
    +   If you want to control all output to stdio and input to stdin,
    +   set the #if below to "1" and delete all lines that contain "io.c".
    +   This prevents the loading of io.o.  Qhull will
    +   still write to 'qh ferr' (stderr) for error reporting and tracing.
    +
    +   Defining #if 1, also prevents user.o from being loaded.
    +*/
    +
    +#include "libqhull/qhull_a.h"
    +
    +/*-------------------------------------------------
    +-internal function prototypes
    +*/
    +void print_summary(void);
    +void makecube(coordT *points, int numpoints, int dim);
    +void adddiamond(coordT *points, int numpoints, int numnew, int dim);
    +void makeDelaunay(coordT *points, int numpoints, int dim);
    +void addDelaunay(coordT *points, int numpoints, int numnew, int dim);
    +void findDelaunay(int dim);
    +void makehalf(coordT *points, int numpoints, int dim);
    +void addhalf(coordT *points, int numpoints, int numnew, int dim, coordT *feasible);
    +
    +/*-------------------------------------------------
    +-print_summary()
    +*/
    +void print_summary(void) {
    +  facetT *facet;
    +  int k;
    +
    +  printf("\n%d vertices and %d facets with normals:\n",
    +                 qh num_vertices, qh num_facets);
    +  FORALLfacets {
    +    for (k=0; k < qh hull_dim; k++)
    +      printf("%6.2g ", facet->normal[k]);
    +    printf("\n");
    +  }
    +}
    +
    +/*--------------------------------------------------
    +-makecube- set points to vertices of cube
    +  points is numpoints X dim
    +*/
    +void makecube(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jlocate a facet with qh_findbestfacet()
    +*/
    +void findDelaunay(int dim) {
    +  int k;
    +  coordT point[ 100];
    +  boolT isoutside;
    +  realT bestdist;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  for (k= 0; k < dim-1; k++)
    +    point[k]= 0.5;
    +  qh_setdelaunay(dim, 1, point);
    +  facet= qh_findbestfacet(point, qh_ALL, &bestdist, &isoutside);
    +  if (facet->tricoplanar) {
    +    fprintf(stderr, "findDelaunay: not implemented for triangulated, non-simplicial Delaunay regions (tricoplanar facet, f%d).\n",
    +       facet->id);
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    for (k=0; k < dim-1; k++)
    +      printf("%5.2f ", vertex->point[k]);
    +    printf("\n");
    +  }
    +} /*.findDelaunay.*/
    +
    +/*--------------------------------------------------
    +-makehalf- set points to halfspaces for a (dim)-d diamond
    +  points is numpoints X dim+1
    +
    +  each halfspace consists of dim coefficients followed by an offset
    +*/
    +void makehalf(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; j= 2 ? argv[1] : "");
    +    qh_initflags(options);
    +    printf( "\ncompute triangulated convex hull of cube after rotating input\n");
    +    makecube(array[0], SIZEcube, DIM);
    +    qh_init_B(array[0], SIZEcube, DIM, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_triangulate();  /* requires option 'Q11' if want to add points */
    +    print_summary();
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    printf( "\nadd points in a diamond\n");
    +    adddiamond(array[0], SIZEcube, SIZEdiamond, DIM);
    +    qh_check_output();
    +    print_summary();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +  }
    +  qh NOerrexit= True;
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    fprintf(stderr, "qhull warning (user_eg, run 1): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +
    +  /*
    +    Run 2: Delaunay triangulation
    +  */
    +  qh_init_A(stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM];
    +
    +    strcat(qh rbox_command, "user_eg Delaunay");
    +    sprintf(options, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +    qh_initflags(options);
    +    printf( "\ncompute %d-d Delaunay triangulation\n", DIM-1);
    +    makeDelaunay(array[0], SIZEcube, DIM);
    +    /* Instead of makeDelaunay with qh_setdelaunay, you may
    +       produce a 2-d array of points, set DIM to 2, and set
    +       qh PROJECTdelaunay to True.  qh_init_B will call
    +       qh_projectinput to project the points to the paraboloid
    +       and add a point "at-infinity".
    +    */
    +    qh_init_B(array[0], SIZEcube, DIM, ismalloc);
    +    qh_qhull();
    +    /* If you want Voronoi ('v') without qh_produce_output(), call
    +       qh_setvoronoi_all() after qh_qhull() */
    +    qh_check_output();
    +    print_summary();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    printf( "\nadd points to triangulation\n");
    +    addDelaunay(array[0], SIZEcube, SIZEdiamond, DIM);
    +    qh_check_output();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    printf( "\nfind Delaunay triangle closest to [0.5, 0.5, ...]\n");
    +    findDelaunay(DIM);
    +  }
    +  qh NOerrexit= True;
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    fprintf(stderr, "qhull warning (user_eg, run 2): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +
    +  /*
    +    Run 3: halfspace intersection
    +  */
    +  qh_init_A(stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM+1];  /* +1 for halfspace offset */
    +    pointT *points;
    +
    +    strcat(qh rbox_command, "user_eg halfspaces");
    +    sprintf(options, "qhull H0 s Tcv %s", argc >= 4 ? argv[3] : "");
    +    qh_initflags(options);
    +    printf( "\ncompute halfspace intersection about the origin for a diamond\n");
    +    makehalf(array[0], SIZEcube, DIM);
    +    qh_setfeasible(DIM); /* from io.c, sets qh feasible_point from 'Hn,n' */
    +    /* you may malloc and set qh feasible_point directly.  It is only used for
    +       option 'Fp' */
    +    points= qh_sethalfspace_all( DIM+1, SIZEcube, array[0], qh feasible_point);
    +    qh_init_B(points, SIZEcube, DIM, True); /* qh_freeqhull frees points */
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    printf( "\nadd halfspaces for cube to intersection\n");
    +    addhalf(array[0], SIZEcube, SIZEdiamond, DIM, qh feasible_point);
    +    qh_check_output();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +  }
    +  qh NOerrexit= True;
    +  qh NOerrexit= True;
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    fprintf(stderr, "qhull warning (user_eg, run 3): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +  return exitcode;
    +} /* main */
    +
    +#if 1    /* use 1 to prevent loading of io.o and user.o */
    +/*-------------------------------------------
    +-errexit- return exitcode to system after an error
    +  assumes exitcode non-zero
    +  prints useful information
    +  see qh_errexit2() in libqhull.c for 2 facets
    +*/
    +void qh_errexit(int exitcode, facetT *facet, ridgeT *ridge) {
    +  QHULL_UNUSED(facet);
    +  QHULL_UNUSED(ridge);
    +
    +  if (qh ERREXITcalled) {
    +    fprintf(qh ferr, "qhull error while processing previous error.  Exit program\n");
    +    exit(1);
    +  }
    +  qh ERREXITcalled= True;
    +  if (!qh QHULLfinished)
    +    qh hulltime= (unsigned)clock() - qh hulltime;
    +  fprintf(qh ferr, "\nWhile executing: %s | %s\n", qh rbox_command, qh qhull_command);
    +  fprintf(qh ferr, "Options selected:\n%s\n", qh qhull_options);
    +  if (qh furthest_id >= 0) {
    +    fprintf(qh ferr, "\nLast point added to hull was p%d", qh furthest_id);
    +    if (zzval_(Ztotmerge))
    +      fprintf(qh ferr, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    if (qh QHULLfinished)
    +      fprintf(qh ferr, "\nQhull has finished constructing the hull.");
    +    else if (qh POSTmerging)
    +      fprintf(qh ferr, "\nQhull has started post-merging");
    +    fprintf(qh ferr, "\n\n");
    +  }
    +  if (qh NOerrexit) {
    +    fprintf(qh ferr, "qhull error while ending program.  Exit program\n");
    +    exit(1);
    +  }
    +  if (!exitcode)
    +    exitcode= qh_ERRqhull;
    +  qh NOerrexit= True;
    +  longjmp(qh errexit, exitcode);
    +} /* errexit */
    +
    +
    +/*-------------------------------------------
    +-errprint- prints out the information of the erroneous object
    +    any parameter may be NULL, also prints neighbors and geomview output
    +*/
    +void qh_errprint(const char *string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex) {
    +
    +  fprintf(qh ferr, "%s facets f%d f%d ridge r%d vertex v%d\n",
    +           string, getid_(atfacet), getid_(otherfacet), getid_(atridge),
    +           getid_(atvertex));
    +} /* errprint */
    +
    +
    +void qh_printfacetlist(facetT *facetlist, setT *facets, boolT printall) {
    +  facetT *facet, **facetp;
    +
    +  /* remove these calls to help avoid io.c */
    +  qh_printbegin(qh ferr, qh_PRINTfacets, facetlist, facets, printall);/*io.c*/
    +  FORALLfacet_(facetlist)                                              /*io.c*/
    +    qh_printafacet(qh ferr, qh_PRINTfacets, facet, printall);          /*io.c*/
    +  FOREACHfacet_(facets)                                                /*io.c*/
    +    qh_printafacet(qh ferr, qh_PRINTfacets, facet, printall);          /*io.c*/
    +  qh_printend(qh ferr, qh_PRINTfacets, facetlist, facets, printall);  /*io.c*/
    +
    +  FORALLfacet_(facetlist)
    +    fprintf( qh ferr, "facet f%d\n", facet->id);
    +} /* printfacetlist */
    +
    +/* qh_printhelp_degenerate( fp )
    +    prints descriptive message for precision error
    +
    +  notes:
    +    no message if qh_QUICKhelp
    +*/
    +void qh_printhelp_degenerate(FILE *fp) {
    +
    +  if (qh MERGEexact || qh PREmerge || qh JOGGLEmax < REALmax/2)
    +    qh_fprintf(fp, 9368, "\n\
    +A Qhull error has occurred.  Qhull should have corrected the above\n\
    +precision error.  Please send the input and all of the output to\n\
    +qhull_bug@qhull.org\n");
    +  else if (!qh_QUICKhelp) {
    +    qh_fprintf(fp, 9369, "\n\
    +Precision problems were detected during construction of the convex hull.\n\
    +This occurs because convex hull algorithms assume that calculations are\n\
    +exact, but floating-point arithmetic has roundoff errors.\n\
    +\n\
    +To correct for precision problems, do not use 'Q0'.  By default, Qhull\n\
    +selects 'C-0' or 'Qx' and merges non-convex facets.  With option 'QJ',\n\
    +Qhull joggles the input to prevent precision problems.  See \"Imprecision\n\
    +in Qhull\" (qh-impre.htm).\n\
    +\n\
    +If you use 'Q0', the output may include\n\
    +coplanar ridges, concave ridges, and flipped facets.  In 4-d and higher,\n\
    +Qhull may produce a ridge with four neighbors or two facets with the same \n\
    +vertices.  Qhull reports these events when they occur.  It stops when a\n\
    +concave ridge, flipped facet, or duplicate facet occurs.\n");
    +#if REALfloat
    +    qh_fprintf(fp, 9370, "\
    +\n\
    +Qhull is currently using single precision arithmetic.  The following\n\
    +will probably remove the precision problems:\n\
    +  - recompile qhull for realT precision(#define REALfloat 0 in user.h).\n");
    +#endif
    +    if (qh DELAUNAY && !qh SCALElast && qh MAXabs_coord > 1e4)
    +      qh_fprintf(fp, 9371, "\
    +\n\
    +When computing the Delaunay triangulation of coordinates > 1.0,\n\
    +  - use 'Qbb' to scale the last coordinate to [0,m] (max previous coordinate)\n");
    +    if (qh DELAUNAY && !qh ATinfinity)
    +      qh_fprintf(fp, 9372, "\
    +When computing the Delaunay triangulation:\n\
    +  - use 'Qz' to add a point at-infinity.  This reduces precision problems.\n");
    +
    +    qh_fprintf(fp, 9373, "\
    +\n\
    +If you need triangular output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft'.  It triangulates non-simplicial facets with added points.\n\
    +\n\
    +If you must use 'Q0',\n\
    +try one or more of the following options.  They can not guarantee an output.\n\
    +  - use 'QbB' to scale the input to a cube.\n\
    +  - use 'Po' to produce output and prevent partitioning for flipped facets\n\
    +  - use 'V0' to set min. distance to visible facet as 0 instead of roundoff\n\
    +  - use 'En' to specify a maximum roundoff error less than %2.2g.\n\
    +  - options 'Qf', 'Qbb', and 'QR0' may also help\n",
    +               qh DISTround);
    +    qh_fprintf(fp, 9374, "\
    +\n\
    +To guarantee simplicial output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft' to triangulate the output by adding points\n\
    +  - use exact arithmetic (see \"Imprecision in Qhull\", qh-impre.htm)\n\
    +");
    +  }
    +} /* printhelp_degenerate */
    +
    +
    +/* qh_printhelp_narrowhull( minangle )
    +     Warn about a narrow hull
    +
    +  notes:
    +    Alternatively, reduce qh_WARNnarrow in user.h
    +
    +*/
    +void qh_printhelp_narrowhull(FILE *fp, realT minangle) {
    +
    +    qh_fprintf(fp, 9375, "qhull precision warning: \n\
    +The initial hull is narrow (cosine of min. angle is %.16f).\n\
    +A coplanar point may lead to a wide facet.  Options 'QbB' (scale to unit box)\n\
    +or 'Qbb' (scale last coordinate) may remove this warning.  Use 'Pp' to skip\n\
    +this warning.  See 'Limitations' in qh-impre.htm.\n",
    +          -minangle);   /* convert from angle between normals to angle between facets */
    +} /* printhelp_narrowhull */
    +
    +/* qh_printhelp_singular
    +      prints descriptive message for singular input
    +*/
    +void qh_printhelp_singular(FILE *fp) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  realT min, max, *coord, dist;
    +  int i,k;
    +
    +  qh_fprintf(fp, 9376, "\n\
    +The input to qhull appears to be less than %d dimensional, or a\n\
    +computation has overflowed.\n\n\
    +Qhull could not construct a clearly convex simplex from points:\n",
    +           qh hull_dim);
    +  qh_printvertexlist(fp, "", qh facet_list, NULL, qh_ALL);
    +  if (!qh_QUICKhelp)
    +    qh_fprintf(fp, 9377, "\n\
    +The center point is coplanar with a facet, or a vertex is coplanar\n\
    +with a neighboring facet.  The maximum round off error for\n\
    +computing distances is %2.2g.  The center point, facets and distances\n\
    +to the center point are as follows:\n\n", qh DISTround);
    +  qh_printpointid(fp, "center point", qh hull_dim, qh interior_point, -1);
    +  qh_fprintf(fp, 9378, "\n");
    +  FORALLfacets {
    +    qh_fprintf(fp, 9379, "facet");
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(fp, 9380, " p%d", qh_pointid(vertex->point));
    +    zinc_(Zdistio);
    +    qh_distplane(qh interior_point, facet, &dist);
    +    qh_fprintf(fp, 9381, " distance= %4.2g\n", dist);
    +  }
    +  if (!qh_QUICKhelp) {
    +    if (qh HALFspace)
    +      qh_fprintf(fp, 9382, "\n\
    +These points are the dual of the given halfspaces.  They indicate that\n\
    +the intersection is degenerate.\n");
    +    qh_fprintf(fp, 9383,"\n\
    +These points either have a maximum or minimum x-coordinate, or\n\
    +they maximize the determinant for k coordinates.  Trial points\n\
    +are first selected from points that maximize a coordinate.\n");
    +    if (qh hull_dim >= qh_INITIALmax)
    +      qh_fprintf(fp, 9384, "\n\
    +Because of the high dimension, the min x-coordinate and max-coordinate\n\
    +points are used if the determinant is non-zero.  Option 'Qs' will\n\
    +do a better, though much slower, job.  Instead of 'Qs', you can change\n\
    +the points by randomly rotating the input with 'QR0'.\n");
    +  }
    +  qh_fprintf(fp, 9385, "\nThe min and max coordinates for each dimension are:\n");
    +  for (k=0; k < qh hull_dim; k++) {
    +    min= REALmax;
    +    max= -REALmin;
    +    for (i=qh num_points, coord= qh first_point+k; i--; coord += qh hull_dim) {
    +      maximize_(max, *coord);
    +      minimize_(min, *coord);
    +    }
    +    qh_fprintf(fp, 9386, "  %d:  %8.4g  %8.4g  difference= %4.4g\n", k, min, max, max-min);
    +  }
    +  if (!qh_QUICKhelp) {
    +    qh_fprintf(fp, 9387, "\n\
    +If the input should be full dimensional, you have several options that\n\
    +may determine an initial simplex:\n\
    +  - use 'QJ'  to joggle the input and make it full dimensional\n\
    +  - use 'QbB' to scale the points to the unit cube\n\
    +  - use 'QR0' to randomly rotate the input for different maximum points\n\
    +  - use 'Qs'  to search all points for the initial simplex\n\
    +  - use 'En'  to specify a maximum roundoff error less than %2.2g.\n\
    +  - trace execution with 'T3' to see the determinant for each point.\n",
    +                     qh DISTround);
    +#if REALfloat
    +    qh_fprintf(fp, 9388, "\
    +  - recompile qhull for realT precision(#define REALfloat 0 in libqhull.h).\n");
    +#endif
    +    qh_fprintf(fp, 9389, "\n\
    +If the input is lower dimensional:\n\
    +  - use 'QJ' to joggle the input and make it full dimensional\n\
    +  - use 'Qbk:0Bk:0' to delete coordinate k from the input.  You should\n\
    +    pick the coordinate with the least range.  The hull will have the\n\
    +    correct topology.\n\
    +  - determine the flat containing the points, rotate the points\n\
    +    into a coordinate plane, and delete the other coordinates.\n\
    +  - add one or more points to make the input full dimensional.\n\
    +");
    +    if (qh DELAUNAY && !qh ATinfinity)
    +      qh_fprintf(fp, 9390, "\n\n\
    +This is a Delaunay triangulation and the input is co-circular or co-spherical:\n\
    +  - use 'Qz' to add a point \"at infinity\" (i.e., above the paraboloid)\n\
    +  - or use 'QJ' to joggle the input and avoid co-circular data\n");
    +  }
    +} /* printhelp_singular */
    +
    +
    +/*-----------------------------------------
    +-user_memsizes- allocate up to 10 additional, quick allocation sizes
    +*/
    +void qh_user_memsizes(void) {
    +
    +  /* qh_memsize(size); */
    +} /* user_memsizes */
    +
    +#endif
    diff --git a/xs/src/qhull/src/user_eg2/user_eg2.pro b/xs/src/qhull/src/user_eg2/user_eg2.pro
    new file mode 100644
    index 0000000000..c841bfe134
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg2/user_eg2.pro
    @@ -0,0 +1,11 @@
    +# -------------------------------------------------
    +# user_eg2.pro -- Qt project for Qhull demonstration using the static Qhull library
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-app-c_r.pri)
    +
    +TARGET = user_eg2
    +
    +SOURCES += user_eg2_r.c
    diff --git a/xs/src/qhull/src/user_eg2/user_eg2_r.c b/xs/src/qhull/src/user_eg2/user_eg2_r.c
    new file mode 100644
    index 0000000000..2f8b4e6c76
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg2/user_eg2_r.c
    @@ -0,0 +1,742 @@
    +/*
      ---------------------------------
    +
    +  user_eg2_r.c
    +
    +  sample code for calling qhull() from an application.
    +
    +  See user_eg_r.c for a simpler method using qh_new_qhull().
    +  The method used here and in unix_r.c gives you additional
    +  control over Qhull.
    +
    +  See user_eg3/user_eg3_r.cpp for a C++ example
    +
    +  call with:
    +
    +     user_eg2 "triangulated cube/diamond options" "delaunay options" "halfspace options"
    +
    +  for example:
    +
    +     user_eg2                             # return summaries
    +
    +     user_eg2 "n" "o" "Fp"                # return normals, OFF, points
    +
    +     user_eg2 "QR0 p" "QR0 v p" "QR0 Fp"  # rotate input and return points
    +                                         # 'v' returns Voronoi
    +                                         # transform is rotated for halfspaces
    +
    +   main() makes three runs of qhull.
    +
    +     1) compute the convex hull of a cube, and incrementally add a diamond
    +
    +     2a) compute the Delaunay triangulation of random points, and add points.
    +
    +     2b) find the Delaunay triangle closest to a point.
    +
    +     3) compute the halfspace intersection of a diamond, and add a cube
    +
    + notes:
    +
    +   summaries are sent to stderr if other output formats are used
    +
    +   derived from unix.c and compiled by 'make bin/user_eg2'
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +
    +   If you want to control all output to stdio and input to stdin,
    +   set the #if below to "1" and delete all lines that contain "io.c".
    +   This prevents the loading of io.o.  Qhull will
    +   still write to 'qh->ferr' (stderr) for error reporting and tracing.
    +
    +   Defining #if 1, also prevents user.o from being loaded.
    +*/
    +
    +#include "libqhull_r/qhull_ra.h"
    +
    +/*-------------------------------------------------
    +-internal function prototypes
    +*/
    +void print_summary(qhT *qh);
    +void makecube(coordT *points, int numpoints, int dim);
    +void adddiamond(qhT *qh, coordT *points, int numpoints, int numnew, int dim);
    +void makeDelaunay(qhT *qh, coordT *points, int numpoints, int dim);
    +void addDelaunay(qhT *qh, coordT *points, int numpoints, int numnew, int dim);
    +void findDelaunay(qhT *qh, int dim);
    +void makehalf(coordT *points, int numpoints, int dim);
    +void addhalf(qhT *qh, coordT *points, int numpoints, int numnew, int dim, coordT *feasible);
    +
    +/*-------------------------------------------------
    +-print_summary(qh)
    +*/
    +void print_summary(qhT *qh) {
    +  facetT *facet;
    +  int k;
    +
    +  printf("\n%d vertices and %d facets with normals:\n",
    +                 qh->num_vertices, qh->num_facets);
    +  FORALLfacets {
    +    for (k=0; k < qh->hull_dim; k++)
    +      printf("%6.2g ", facet->normal[k]);
    +    printf("\n");
    +  }
    +}
    +
    +/*--------------------------------------------------
    +-makecube- set points to vertices of cube
    +  points is numpoints X dim
    +*/
    +void makecube(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jfirst_point)  /* in case of 'QRn' */
    +      qh->num_points= numpoints+j+1;
    +    /* qh.num_points sets the size of the points array.  You may
    +       allocate the points elsewhere.  If so, qh_addpoint records
    +       the point's address in qh->other_points
    +    */
    +    for (k=dim; k--; ) {
    +      if (j/2 == k)
    +        point[k]= (j & 1) ? 2.0 : -2.0;
    +      else
    +        point[k]= 0.0;
    +    }
    +    facet= qh_findbestfacet(qh, point, !qh_ALL, &bestdist, &isoutside);
    +    if (isoutside) {
    +      if (!qh_addpoint(qh, point, facet, False))
    +        break;  /* user requested an early exit with 'TVn' or 'TCn' */
    +    }
    +    printf("%d vertices and %d facets\n",
    +                 qh->num_vertices, qh->num_facets);
    +    /* qh_produce_output(); */
    +  }
    +  if (qh->DOcheckmax)
    +    qh_check_maxout(qh);
    +  else if (qh->KEEPnearinside)
    +    qh_nearcoplanar(qh);
    +} /*.adddiamond.*/
    +
    +/*--------------------------------------------------
    +-makeDelaunay- set points for dim-1 Delaunay triangulation of random points
    +  points is numpoints X dim.  Each point is projected to a paraboloid.
    +*/
    +void makeDelaunay(qhT *qh, coordT *points, int numpoints, int dim) {
    +  int j,k, seed;
    +  coordT *point, realr;
    +
    +  seed= (int)time(NULL); /* time_t to int */
    +  printf("seed: %d\n", seed);
    +  qh_RANDOMseed_(qh, seed);
    +  for (j=0; jfirst_point)  /* in case of 'QRn' */
    +      qh->num_points= numpoints+j+1;
    +    /* qh.num_points sets the size of the points array.  You may
    +       allocate the point elsewhere.  If so, qh_addpoint records
    +       the point's address in qh->other_points
    +    */
    +    for (k= 0; k < dim-1; k++) {
    +      realr= qh_RANDOMint;
    +      point[k]= 2.0 * realr/(qh_RANDOMmax+1) - 1.0;
    +    }
    +    qh_setdelaunay(qh, dim, 1, point);
    +    facet= qh_findbestfacet(qh, point, !qh_ALL, &bestdist, &isoutside);
    +    if (isoutside) {
    +      if (!qh_addpoint(qh, point, facet, False))
    +        break;  /* user requested an early exit with 'TVn' or 'TCn' */
    +    }
    +    qh_printpoint(qh, stdout, "added point", point);
    +    printf("%d points, %d extra points, %d vertices, and %d facets in total\n",
    +                  qh->num_points, qh_setsize(qh, qh->other_points),
    +                  qh->num_vertices, qh->num_facets);
    +
    +    /* qh_produce_output(qh); */
    +  }
    +  if (qh->DOcheckmax)
    +    qh_check_maxout(qh);
    +  else if (qh->KEEPnearinside)
    +    qh_nearcoplanar(qh);
    +} /*.addDelaunay.*/
    +
    +/*--------------------------------------------------
    +-findDelaunay- find Delaunay triangle for [0.5,0.5,...]
    +  assumes dim < 100
    +notes:
    +  calls qh_setdelaunay() to project the point to a parabaloid
    +warning:
    +  This is not implemented for tricoplanar facets ('Qt'),
    +  See locate a facet with qh_findbestfacet()
    +*/
    +void findDelaunay(qhT *qh, int dim) {
    +  int k;
    +  coordT point[ 100];
    +  boolT isoutside;
    +  realT bestdist;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  for (k= 0; k < dim-1; k++)
    +    point[k]= 0.5;
    +  qh_setdelaunay(qh, dim, 1, point);
    +  facet= qh_findbestfacet(qh, point, qh_ALL, &bestdist, &isoutside);
    +  if (facet->tricoplanar) {
    +    fprintf(stderr, "findDelaunay: not implemented for triangulated, non-simplicial Delaunay regions (tricoplanar facet, f%d).\n",
    +       facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    for (k=0; k < dim-1; k++)
    +      printf("%5.2f ", vertex->point[k]);
    +    printf("\n");
    +  }
    +} /*.findDelaunay.*/
    +
    +/*--------------------------------------------------
    +-makehalf- set points to halfspaces for a (dim)-d diamond
    +  points is numpoints X dim+1
    +
    +  each halfspace consists of dim coefficients followed by an offset
    +*/
    +void makehalf(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jnum_points, qh_setsize(qh, qh->other_points),
    +                  qh->num_vertices, qh->num_facets);
    +    /* qh_produce_output(qh); */
    +  }
    +  if (qh->DOcheckmax)
    +    qh_check_maxout(qh);
    +  else if (qh->KEEPnearinside)
    +    qh_nearcoplanar(qh);
    +} /*.addhalf.*/
    +
    +#define DIM 3     /* dimension of points, must be < 31 for SIZEcube */
    +#define SIZEcube (1<&1'\n\n");
    +
    +  ismalloc= False;      /* True if qh_freeqhull should 'free(array)' */
    +  /*
    +    Run 1: convex hull
    +  */
    +  qh_init_A(qh, stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh->errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM];
    +
    +    qh->NOerrexit= False;
    +    strcat(qh->rbox_command, "user_eg2 cube example");
    +    sprintf(options, "qhull s Tcv Q11 %s ", argc >= 2 ? argv[1] : "");
    +    qh_initflags(qh, options);
    +    printf( "\ncompute triangulated convex hull of cube after rotating input\n");
    +    makecube(array[0], SIZEcube, DIM);
    +    qh_init_B(qh, array[0], SIZEcube, DIM, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_triangulate(qh);  /* requires option 'Q11' if want to add points */
    +    print_summary(qh);
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    printf( "\nadd points in a diamond\n");
    +    adddiamond(qh, array[0], SIZEcube, SIZEdiamond, DIM);
    +    qh_check_output(qh);
    +    print_summary(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +  }
    +  qh->NOerrexit= True;
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +      fprintf(stderr, "qhull warning (user_eg2, run 1): did not free %d bytes of long memory (%d pieces)\n",
    +          totlong, curlong);
    +  /*
    +    Run 2: Delaunay triangulation
    +  */
    +  qh_init_A(qh, stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh->errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM];
    +
    +    qh->NOerrexit= False;
    +    strcat(qh->rbox_command, "user_eg2 Delaunay example");
    +    sprintf(options, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +    qh_initflags(qh, options);
    +    printf( "\ncompute %d-d Delaunay triangulation\n", DIM-1);
    +    makeDelaunay(qh, array[0], SIZEcube, DIM);
    +    /* Instead of makeDelaunay with qh_setdelaunay, you may
    +       produce a 2-d array of points, set DIM to 2, and set
    +       qh->PROJECTdelaunay to True.  qh_init_B will call
    +       qh_projectinput to project the points to the paraboloid
    +       and add a point "at-infinity".
    +    */
    +    qh_init_B(qh, array[0], SIZEcube, DIM, ismalloc);
    +    qh_qhull(qh);
    +    /* If you want Voronoi ('v') without qh_produce_output(), call
    +       qh_setvoronoi_all() after qh_qhull() */
    +    qh_check_output(qh);
    +    print_summary(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    printf( "\nadd points to triangulation\n");
    +    addDelaunay(qh, array[0], SIZEcube, SIZEdiamond, DIM);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    printf( "\nfind Delaunay triangle closest to [0.5, 0.5, ...]\n");
    +    findDelaunay(qh, DIM);
    +  }
    +  qh->NOerrexit= True;
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong) 
    +      fprintf(stderr, "qhull warning (user_eg2, run 2): did not free %d bytes of long memory (%d pieces)\n",
    +         totlong, curlong);
    +  /*
    +    Run 3: halfspace intersection
    +  */
    +  qh_init_A(qh, stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh->errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM+1];  /* +1 for halfspace offset */
    +    pointT *points;
    +
    +    qh->NOerrexit= False;
    +    strcat(qh->rbox_command, "user_eg2 halfspace example");
    +    sprintf(options, "qhull H0 s Tcv %s", argc >= 4 ? argv[3] : "");
    +    qh_initflags(qh, options);
    +    printf( "\ncompute halfspace intersection about the origin for a diamond\n");
    +    makehalf(array[0], SIZEcube, DIM);
    +    qh_setfeasible(qh, DIM); /* from io.c, sets qh->feasible_point from 'Hn,n' */
    +    /* you may malloc and set qh->feasible_point directly.  It is only used for
    +       option 'Fp' */
    +    points= qh_sethalfspace_all(qh, DIM+1, SIZEcube, array[0], qh->feasible_point);
    +    qh_init_B(qh, points, SIZEcube, DIM, True); /* qh_freeqhull frees points */
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    printf( "\nadd halfspaces for cube to intersection\n");
    +    addhalf(qh, array[0], SIZEcube, SIZEdiamond, DIM, qh->feasible_point);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +  }
    +  qh->NOerrexit= True;
    +  qh->NOerrexit= True;
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +      fprintf(stderr, "qhull warning (user_eg2, run 3): did not free %d bytes of long memory (%d pieces)\n",
    +          totlong, curlong);
    +  return exitcode;
    +} /* main */
    +
    +#if 1    /* use 1 to prevent loading of io.o and user.o */
    +/*-------------------------------------------
    +-errexit- return exitcode to system after an error
    +  assumes exitcode non-zero
    +  prints useful information
    +  see qh_errexit2() in libqhull.c for 2 facets
    +*/
    +void qh_errexit(qhT *qh, int exitcode, facetT *facet, ridgeT *ridge) {
    +  QHULL_UNUSED(facet);
    +  QHULL_UNUSED(ridge);
    +
    +  if (qh->ERREXITcalled) {
    +    fprintf(qh->ferr, "qhull error while processing previous error.  Exit program\n");
    +    exit(1);
    +  }
    +  qh->ERREXITcalled= True;
    +  if (!qh->QHULLfinished)
    +    qh->hulltime= (unsigned)clock() - qh->hulltime;
    +  fprintf(qh->ferr, "\nWhile executing: %s | %s\n", qh->rbox_command, qh->qhull_command);
    +  fprintf(qh->ferr, "Options selected:\n%s\n", qh->qhull_options);
    +  if (qh->furthest_id >= 0) {
    +    fprintf(qh->ferr, "\nLast point added to hull was p%d", qh->furthest_id);
    +    if (zzval_(Ztotmerge))
    +      fprintf(qh->ferr, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    if (qh->QHULLfinished)
    +      fprintf(qh->ferr, "\nQhull has finished constructing the hull.");
    +    else if (qh->POSTmerging)
    +      fprintf(qh->ferr, "\nQhull has started post-merging");
    +    fprintf(qh->ferr, "\n\n");
    +  }
    +  if (qh->NOerrexit) {
    +    fprintf(qh->ferr, "qhull error while ending program.  Exit program\n");
    +    exit(1);
    +  }
    +  if (!exitcode)
    +    exitcode= qh_ERRqhull;
    +  qh->NOerrexit= True;
    +  longjmp(qh->errexit, exitcode);
    +} /* errexit */
    +
    +
    +/*-------------------------------------------
    +-errprint- prints out the information of the erroneous object
    +    any parameter may be NULL, also prints neighbors and geomview output
    +*/
    +void qh_errprint(qhT *qh, const char *string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex) {
    +
    +  fprintf(qh->ferr, "%s facets f%d f%d ridge r%d vertex v%d\n",
    +           string, getid_(atfacet), getid_(otherfacet), getid_(atridge),
    +           getid_(atvertex));
    +} /* errprint */
    +
    +
    +void qh_printfacetlist(qhT *qh, facetT *facetlist, setT *facets, boolT printall) {
    +  facetT *facet, **facetp;
    +
    +  /* remove these calls to help avoid io.c */
    +  qh_printbegin(qh, qh->ferr, qh_PRINTfacets, facetlist, facets, printall);/*io.c*/
    +  FORALLfacet_(facetlist)                                              /*io.c*/
    +    qh_printafacet(qh, qh->ferr, qh_PRINTfacets, facet, printall);          /*io.c*/
    +  FOREACHfacet_(facets)                                                /*io.c*/
    +    qh_printafacet(qh, qh->ferr, qh_PRINTfacets, facet, printall);          /*io.c*/
    +  qh_printend(qh, qh->ferr, qh_PRINTfacets, facetlist, facets, printall);  /*io.c*/
    +
    +  FORALLfacet_(facetlist)
    +    fprintf( qh->ferr, "facet f%d\n", facet->id);
    +} /* printfacetlist */
    +
    +/* qh_printhelp_degenerate( fp )
    +    prints descriptive message for precision error
    +
    +  notes:
    +    no message if qh_QUICKhelp
    +*/
    +void qh_printhelp_degenerate(qhT *qh, FILE *fp) {
    +
    +  if (qh->MERGEexact || qh->PREmerge || qh->JOGGLEmax < REALmax/2)
    +    qh_fprintf(qh, fp, 9368, "\n\
    +A Qhull error has occurred.  Qhull should have corrected the above\n\
    +precision error.  Please send the input and all of the output to\n\
    +qhull_bug@qhull.org\n");
    +  else if (!qh_QUICKhelp) {
    +    qh_fprintf(qh, fp, 9369, "\n\
    +Precision problems were detected during construction of the convex hull.\n\
    +This occurs because convex hull algorithms assume that calculations are\n\
    +exact, but floating-point arithmetic has roundoff errors.\n\
    +\n\
    +To correct for precision problems, do not use 'Q0'.  By default, Qhull\n\
    +selects 'C-0' or 'Qx' and merges non-convex facets.  With option 'QJ',\n\
    +Qhull joggles the input to prevent precision problems.  See \"Imprecision\n\
    +in Qhull\" (qh-impre.htm).\n\
    +\n\
    +If you use 'Q0', the output may include\n\
    +coplanar ridges, concave ridges, and flipped facets.  In 4-d and higher,\n\
    +Qhull may produce a ridge with four neighbors or two facets with the same \n\
    +vertices.  Qhull reports these events when they occur.  It stops when a\n\
    +concave ridge, flipped facet, or duplicate facet occurs.\n");
    +#if REALfloat
    +    qh_fprintf(qh, fp, 9370, "\
    +\n\
    +Qhull is currently using single precision arithmetic.  The following\n\
    +will probably remove the precision problems:\n\
    +  - recompile qhull for realT precision(#define REALfloat 0 in user.h).\n");
    +#endif
    +    if (qh->DELAUNAY && !qh->SCALElast && qh->MAXabs_coord > 1e4)
    +      qh_fprintf(qh, fp, 9371, "\
    +\n\
    +When computing the Delaunay triangulation of coordinates > 1.0,\n\
    +  - use 'Qbb' to scale the last coordinate to [0,m] (max previous coordinate)\n");
    +    if (qh->DELAUNAY && !qh->ATinfinity)
    +      qh_fprintf(qh, fp, 9372, "\
    +When computing the Delaunay triangulation:\n\
    +  - use 'Qz' to add a point at-infinity.  This reduces precision problems.\n");
    +
    +    qh_fprintf(qh, fp, 9373, "\
    +\n\
    +If you need triangular output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft'.  It triangulates non-simplicial facets with added points.\n\
    +\n\
    +If you must use 'Q0',\n\
    +try one or more of the following options.  They can not guarantee an output.\n\
    +  - use 'QbB' to scale the input to a cube.\n\
    +  - use 'Po' to produce output and prevent partitioning for flipped facets\n\
    +  - use 'V0' to set min. distance to visible facet as 0 instead of roundoff\n\
    +  - use 'En' to specify a maximum roundoff error less than %2.2g.\n\
    +  - options 'Qf', 'Qbb', and 'QR0' may also help\n",
    +               qh->DISTround);
    +    qh_fprintf(qh, fp, 9374, "\
    +\n\
    +To guarantee simplicial output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft' to triangulate the output by adding points\n\
    +  - use exact arithmetic (see \"Imprecision in Qhull\", qh-impre.htm)\n\
    +");
    +  }
    +} /* printhelp_degenerate */
    +
    +
    +/* qh_printhelp_narrowhull( minangle )
    +     Warn about a narrow hull
    +
    +  notes:
    +    Alternatively, reduce qh_WARNnarrow in user.h
    +
    +*/
    +void qh_printhelp_narrowhull(qhT *qh, FILE *fp, realT minangle) {
    +
    +    qh_fprintf(qh, fp, 9375, "qhull precision warning: \n\
    +The initial hull is narrow (cosine of min. angle is %.16f).\n\
    +A coplanar point may lead to a wide facet.  Options 'QbB' (scale to unit box)\n\
    +or 'Qbb' (scale last coordinate) may remove this warning.  Use 'Pp' to skip\n\
    +this warning.  See 'Limitations' in qh-impre.htm.\n",
    +          -minangle);   /* convert from angle between normals to angle between facets */
    +} /* printhelp_narrowhull */
    +
    +/* qh_printhelp_singular
    +      prints descriptive message for singular input
    +*/
    +void qh_printhelp_singular(qhT *qh, FILE *fp) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  realT min, max, *coord, dist;
    +  int i,k;
    +
    +  qh_fprintf(qh, fp, 9376, "\n\
    +The input to qhull appears to be less than %d dimensional, or a\n\
    +computation has overflowed.\n\n\
    +Qhull could not construct a clearly convex simplex from points:\n",
    +           qh->hull_dim);
    +  qh_printvertexlist(qh, fp, "", qh->facet_list, NULL, qh_ALL);
    +  if (!qh_QUICKhelp)
    +    qh_fprintf(qh, fp, 9377, "\n\
    +The center point is coplanar with a facet, or a vertex is coplanar\n\
    +with a neighboring facet.  The maximum round off error for\n\
    +computing distances is %2.2g.  The center point, facets and distances\n\
    +to the center point are as follows:\n\n", qh->DISTround);
    +  qh_printpointid(qh, fp, "center point", qh->hull_dim, qh->interior_point, -1);
    +  qh_fprintf(qh, fp, 9378, "\n");
    +  FORALLfacets {
    +    qh_fprintf(qh, fp, 9379, "facet");
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(qh, fp, 9380, " p%d", qh_pointid(qh, vertex->point));
    +    zinc_(Zdistio);
    +    qh_distplane(qh, qh->interior_point, facet, &dist);
    +    qh_fprintf(qh, fp, 9381, " distance= %4.2g\n", dist);
    +  }
    +  if (!qh_QUICKhelp) {
    +    if (qh->HALFspace)
    +      qh_fprintf(qh, fp, 9382, "\n\
    +These points are the dual of the given halfspaces.  They indicate that\n\
    +the intersection is degenerate.\n");
    +    qh_fprintf(qh, fp, 9383,"\n\
    +These points either have a maximum or minimum x-coordinate, or\n\
    +they maximize the determinant for k coordinates.  Trial points\n\
    +are first selected from points that maximize a coordinate.\n");
    +    if (qh->hull_dim >= qh_INITIALmax)
    +      qh_fprintf(qh, fp, 9384, "\n\
    +Because of the high dimension, the min x-coordinate and max-coordinate\n\
    +points are used if the determinant is non-zero.  Option 'Qs' will\n\
    +do a better, though much slower, job.  Instead of 'Qs', you can change\n\
    +the points by randomly rotating the input with 'QR0'.\n");
    +  }
    +  qh_fprintf(qh, fp, 9385, "\nThe min and max coordinates for each dimension are:\n");
    +  for (k=0; k < qh->hull_dim; k++) {
    +    min= REALmax;
    +    max= -REALmin;
    +    for (i=qh->num_points, coord= qh->first_point+k; i--; coord += qh->hull_dim) {
    +      maximize_(max, *coord);
    +      minimize_(min, *coord);
    +    }
    +    qh_fprintf(qh, fp, 9386, "  %d:  %8.4g  %8.4g  difference= %4.4g\n", k, min, max, max-min);
    +  }
    +  if (!qh_QUICKhelp) {
    +    qh_fprintf(qh, fp, 9387, "\n\
    +If the input should be full dimensional, you have several options that\n\
    +may determine an initial simplex:\n\
    +  - use 'QJ'  to joggle the input and make it full dimensional\n\
    +  - use 'QbB' to scale the points to the unit cube\n\
    +  - use 'QR0' to randomly rotate the input for different maximum points\n\
    +  - use 'Qs'  to search all points for the initial simplex\n\
    +  - use 'En'  to specify a maximum roundoff error less than %2.2g.\n\
    +  - trace execution with 'T3' to see the determinant for each point.\n",
    +                     qh->DISTround);
    +#if REALfloat
    +    qh_fprintf(qh, fp, 9388, "\
    +  - recompile qhull for realT precision(#define REALfloat 0 in libqhull.h).\n");
    +#endif
    +    qh_fprintf(qh, fp, 9389, "\n\
    +If the input is lower dimensional:\n\
    +  - use 'QJ' to joggle the input and make it full dimensional\n\
    +  - use 'Qbk:0Bk:0' to delete coordinate k from the input.  You should\n\
    +    pick the coordinate with the least range.  The hull will have the\n\
    +    correct topology.\n\
    +  - determine the flat containing the points, rotate the points\n\
    +    into a coordinate plane, and delete the other coordinates.\n\
    +  - add one or more points to make the input full dimensional.\n\
    +");
    +    if (qh->DELAUNAY && !qh->ATinfinity)
    +      qh_fprintf(qh, fp, 9390, "\n\n\
    +This is a Delaunay triangulation and the input is co-circular or co-spherical:\n\
    +  - use 'Qz' to add a point \"at infinity\" (i.e., above the paraboloid)\n\
    +  - or use 'QJ' to joggle the input and avoid co-circular data\n");
    +  }
    +} /* printhelp_singular */
    +
    +
    +/*-----------------------------------------
    +-user_memsizes- allocate up to 10 additional, quick allocation sizes
    +*/
    +void qh_user_memsizes(qhT *qh) {
    +
    +  QHULL_UNUSED(qh);
    +  /* qh_memsize(qh, size); */
    +} /* user_memsizes */
    +
    +#endif
    diff --git a/xs/src/qhull/src/user_eg3/user_eg3.pro b/xs/src/qhull/src/user_eg3/user_eg3.pro
    new file mode 100644
    index 0000000000..35372fbf92
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg3/user_eg3.pro
    @@ -0,0 +1,12 @@
    +# -------------------------------------------------
    +# user_eg3.pro -- Qt project for cpp demonstration user_eg3.exe
    +#
    +# The C++ interface requires reentrant Qhull.
    +# -------------------------------------------------
    +
    +include(../qhull-app-cpp.pri)
    +
    +TARGET = user_eg3
    +CONFIG -= qt
    +
    +SOURCES += user_eg3_r.cpp
    diff --git a/xs/src/qhull/src/user_eg3/user_eg3_r.cpp b/xs/src/qhull/src/user_eg3/user_eg3_r.cpp
    new file mode 100644
    index 0000000000..5257872ab8
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg3/user_eg3_r.cpp
    @@ -0,0 +1,162 @@
    +#//! user_eg3_r.cpp -- Invoke rbox and qhull from C++
    +
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullQh.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/QhullLinkedList.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#include    /* for printf() of help message */
    +#include 
    +#include 
    +
    +using std::cerr;
    +using std::cin;
    +using std::cout;
    +using std::endl;
    +
    +using orgQhull::Qhull;
    +using orgQhull::QhullError;
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetList;
    +using orgQhull::QhullQh;
    +using orgQhull::RboxPoints;
    +using orgQhull::QhullVertex;
    +using orgQhull::QhullVertexSet;
    +
    +int main(int argc, char **argv);
    +int user_eg3(int argc, char **argv);
    +
    +char prompt[]= "\n\
    +user_eg3 -- demonstrate calling rbox and qhull from C++.\n\
    +\n\
    +user_eg3 is statically linked to reentrant qhull.  If user_eg3\n\
    +fails immediately, it is probably linked to the non-reentrant qhull.\n\
    +Try 'user_eg3 rbox qhull \"T1\"'\n\
    +\n\
    +  eg-100                       Run the example in qh-code.htm\n\
    +  rbox \"200 D4\" ...            Generate points from rbox\n\
    +  qhull \"d p\" ...              Run qhull and produce output\n\
    +  qhull-cout \"o\" ...           Run qhull and produce output to cout\n\
    +  qhull \"T1\" ...               Run qhull with level-1 trace to cerr\n\
    +  facets                       Print facets when done\n\
    +\n\
    +For example\n\
    +  user_eg3 rbox qhull\n\
    +  user_eg3 rbox qhull d\n\
    +  user_eg3 rbox \"10 D2\"  \"2 D2\" qhull  \"s p\" facets\n\
    +\n\
    +";
    +
    +
    +/*--------------------------------------------
    +-user_eg3-  main procedure of user_eg3 application
    +*/
    +int main(int argc, char **argv) {
    +
    +    QHULL_LIB_CHECK
    +
    +    if(argc==1){
    +        cout << prompt;
    +        return 1;
    +    }
    +    try{
    +        return user_eg3(argc, argv);
    +    }catch(QhullError &e){
    +        cerr << e.what() << std::endl;
    +        return e.errorCode();
    +    }
    +}//main
    +
    +int user_eg3(int argc, char **argv)
    +{
    +    if(strcmp(argv[1], "eg-100")==0){
    +        RboxPoints rbox("100");
    +        Qhull q(rbox, "");
    +        QhullFacetList facets= q.facetList();
    +        cout << facets;
    +        return 0;
    +    }
    +    bool printFacets= false;
    +    RboxPoints rbox;
    +    Qhull qhull;
    +    int readingRbox= 0;
    +    int readingQhull= 0;
    +    for(int i=1; i
    Date: Wed, 15 Aug 2018 12:50:06 +0200
    Subject: [PATCH 089/119] Use of bounding box of rotated 3D convex hull for out
     of print volume detection
    
    ---
     xs/src/libslic3r/Format/3mf.cpp   |   1 +
     xs/src/libslic3r/Format/AMF.cpp   |   1 +
     xs/src/libslic3r/Model.cpp        | 129 +++++++++--------------------
     xs/src/libslic3r/Model.hpp        |  36 +++++---
     xs/src/libslic3r/TriangleMesh.cpp | 133 ++++++++++++++++++++++++++++++
     xs/src/libslic3r/TriangleMesh.hpp |   6 +-
     xs/src/slic3r/GUI/3DScene.cpp     |  76 ++++++++++++-----
     xs/src/slic3r/GUI/3DScene.hpp     |  16 ++--
     xs/src/slic3r/GUI/GLCanvas3D.cpp  |   2 +-
     xs/src/slic3r/GUI/GLGizmo.cpp     |  14 ++--
     xs/src/slic3r/GUI/GLGizmo.hpp     |   2 +-
     xs/xsp/GUI_3DScene.xsp            |   1 -
     12 files changed, 276 insertions(+), 141 deletions(-)
    
    diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp
    index dd3500eba0..5de1d26c5f 100644
    --- a/xs/src/libslic3r/Format/3mf.cpp
    +++ b/xs/src/libslic3r/Format/3mf.cpp
    @@ -1491,6 +1491,7 @@ namespace Slic3r {
     
                 stl_get_size(&stl);
                 volume->mesh.repair();
    +            volume->calculate_convex_hull();
     
                 // apply volume's name and config data
                 for (const Metadata& metadata : volume_data.metadata)
    diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp
    index 600aa6cd97..886bbae971 100644
    --- a/xs/src/libslic3r/Format/AMF.cpp
    +++ b/xs/src/libslic3r/Format/AMF.cpp
    @@ -406,6 +406,7 @@ void AMFParserContext::endElement(const char * /* name */)
             }
             stl_get_size(&stl);
             m_volume->mesh.repair();
    +        m_volume->calculate_convex_hull();
             m_volume_facets.clear();
             m_volume = nullptr;
             break;
    diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp
    index f9936537fa..23d4477487 100644
    --- a/xs/src/libslic3r/Model.cpp
    +++ b/xs/src/libslic3r/Model.cpp
    @@ -17,6 +17,11 @@
     #include "SVG.hpp"
     #include 
     
    +static const float UNIT_MATRIX[] = { 1.0f, 0.0f, 0.0f, 0.0f,
    +                                     0.0f, 1.0f, 0.0f, 0.0f,
    +                                     0.0f, 0.0f, 1.0f, 0.0f,
    +                                     0.0f, 0.0f, 0.0f, 1.0f };
    +
     namespace Slic3r {
     
         unsigned int Model::s_auto_extruder_id = 1;
    @@ -235,14 +240,6 @@ BoundingBoxf3 Model::bounding_box() const
         return bb;
     }
     
    -BoundingBoxf3 Model::transformed_bounding_box() const
    -{
    -    BoundingBoxf3 bb;
    -    for (const ModelObject* obj : this->objects)
    -        bb.merge(obj->tight_bounding_box(false));
    -    return bb;
    -}
    -
     void Model::center_instances_around_point(const Pointf &point)
     {
     //    BoundingBoxf3 bb = this->bounding_box();
    @@ -623,54 +620,6 @@ const BoundingBoxf3& ModelObject::bounding_box() const
         return m_bounding_box;
     }
     
    -BoundingBoxf3 ModelObject::tight_bounding_box(bool include_modifiers) const
    -{
    -    BoundingBoxf3 bb;
    -
    -    for (const ModelVolume* vol : this->volumes)
    -    {
    -        if (include_modifiers || !vol->modifier)
    -        {
    -            for (const ModelInstance* inst : this->instances)
    -            {
    -                double c = cos(inst->rotation);
    -                double s = sin(inst->rotation);
    -
    -                for (int f = 0; f < vol->mesh.stl.stats.number_of_facets; ++f)
    -                {
    -                    const stl_facet& facet = vol->mesh.stl.facet_start[f];
    -
    -                    for (int i = 0; i < 3; ++i)
    -                    {
    -                        // original point
    -                        const stl_vertex& v = facet.vertex[i];
    -                        Pointf3 p((double)v.x, (double)v.y, (double)v.z);
    -
    -                        // scale
    -                        p.x *= inst->scaling_factor;
    -                        p.y *= inst->scaling_factor;
    -                        p.z *= inst->scaling_factor;
    -
    -                        // rotate Z
    -                        double x = p.x;
    -                        double y = p.y;
    -                        p.x = c * x - s * y;
    -                        p.y = s * x + c * y;
    -
    -                        // translate
    -                        p.x += inst->offset.x;
    -                        p.y += inst->offset.y;
    -
    -                        bb.merge(p);
    -                    }
    -                }
    -            }
    -        }
    -    }
    -
    -    return bb;
    -}
    -
     // A mesh containing all transformed instances of this object.
     TriangleMesh ModelObject::mesh() const
     {
    @@ -755,15 +704,22 @@ void ModelObject::center_around_origin()
     void ModelObject::translate(coordf_t x, coordf_t y, coordf_t z)
     {
         for (ModelVolume *v : this->volumes)
    +    {
             v->mesh.translate(float(x), float(y), float(z));
    -    if (m_bounding_box_valid) 
    +        v->m_convex_hull.translate(float(x), float(y), float(z));
    +    }
    +
    +    if (m_bounding_box_valid)
             m_bounding_box.translate(x, y, z);
     }
     
     void ModelObject::scale(const Pointf3 &versor)
     {
         for (ModelVolume *v : this->volumes)
    +    {
             v->mesh.scale(versor);
    +        v->m_convex_hull.scale(versor);
    +    }
         // reset origin translation since it doesn't make sense anymore
         this->origin_translation = Pointf3(0,0,0);
         this->invalidate_bounding_box();
    @@ -774,6 +730,7 @@ void ModelObject::rotate(float angle, const Axis &axis)
         for (ModelVolume *v : this->volumes)
         {
             v->mesh.rotate(angle, axis);
    +        v->m_convex_hull.rotate(angle, axis);
         }
     
         center_around_origin();
    @@ -790,6 +747,7 @@ void ModelObject::transform(const float* matrix3x4)
         for (ModelVolume* v : volumes)
         {
             v->mesh.transform(matrix3x4);
    +        v->m_convex_hull.transform(matrix3x4);
         }
     
         origin_translation = Pointf3(0.0, 0.0, 0.0);
    @@ -799,8 +757,12 @@ void ModelObject::transform(const float* matrix3x4)
     void ModelObject::mirror(const Axis &axis)
     {
         for (ModelVolume *v : this->volumes)
    +    {
             v->mesh.mirror(axis);
    -    this->origin_translation = Pointf3(0,0,0);
    +        v->m_convex_hull.mirror(axis);
    +    }
    +
    +    this->origin_translation = Pointf3(0, 0, 0);
         this->invalidate_bounding_box();
     }
     
    @@ -904,45 +866,20 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
     
     void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume)
     {
    -    for (ModelVolume* vol : this->volumes)
    +    for (const ModelVolume* vol : this->volumes)
         {
             if (!vol->modifier)
             {
                 for (ModelInstance* inst : this->instances)
                 {
    -                BoundingBoxf3 bb;
    +                std::vector world_mat(UNIT_MATRIX, std::end(UNIT_MATRIX));
    +                Eigen::Transform m = Eigen::Transform::Identity();
    +                m.translate(Eigen::Vector3f((float)inst->offset.x, (float)inst->offset.y, 0.0f));
    +                m.rotate(Eigen::AngleAxisf(inst->rotation, Eigen::Vector3f::UnitZ()));
    +                m.scale(inst->scaling_factor);
    +                ::memcpy((void*)world_mat.data(), (const void*)m.data(), 16 * sizeof(float));
     
    -                double c = cos(inst->rotation);
    -                double s = sin(inst->rotation);
    -
    -                for (int f = 0; f < vol->mesh.stl.stats.number_of_facets; ++f)
    -                {
    -                    const stl_facet& facet = vol->mesh.stl.facet_start[f];
    -
    -                    for (int i = 0; i < 3; ++i)
    -                    {
    -                        // original point
    -                        const stl_vertex& v = facet.vertex[i];
    -                        Pointf3 p((double)v.x, (double)v.y, (double)v.z);
    -
    -                        // scale
    -                        p.x *= inst->scaling_factor;
    -                        p.y *= inst->scaling_factor;
    -                        p.z *= inst->scaling_factor;
    -
    -                        // rotate Z
    -                        double x = p.x;
    -                        double y = p.y;
    -                        p.x = c * x - s * y;
    -                        p.y = s * x + c * y;
    -
    -                        // translate
    -                        p.x += inst->offset.x;
    -                        p.y += inst->offset.y;
    -
    -                        bb.merge(p);
    -                    }
    -                }
    +                BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(world_mat);
     
                     if (print_volume.contains(bb))
                         inst->print_volume_state = ModelInstance::PVS_Inside;
    @@ -1025,6 +962,16 @@ ModelMaterial* ModelVolume::assign_unique_material()
         return model->add_material(this->_material_id);
     }
     
    +void ModelVolume::calculate_convex_hull()
    +{
    +    m_convex_hull = mesh.convex_hull_3d();
    +}
    +
    +const TriangleMesh& ModelVolume::get_convex_hull() const
    +{
    +    return m_convex_hull;
    +}
    +
     // Split this volume, append the result to the object owning this volume.
     // Return the number of volumes created from this one.
     // This is useful to assign different materials to different volumes of an object.
    diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp
    index 4c650f0de8..23af9fb1c4 100644
    --- a/xs/src/libslic3r/Model.hpp
    +++ b/xs/src/libslic3r/Model.hpp
    @@ -105,9 +105,6 @@ public:
         // This bounding box is being cached.
         const BoundingBoxf3& bounding_box() const;
         void invalidate_bounding_box() { m_bounding_box_valid = false; }
    -    // Returns a snug bounding box of the transformed instances.
    -    // This bounding box is not being cached.
    -    BoundingBoxf3 tight_bounding_box(bool include_modifiers) const;
     
         // A mesh containing all transformed instances of this object.
         TriangleMesh mesh() const;
    @@ -157,6 +154,10 @@ private:
     class ModelVolume
     {
         friend class ModelObject;
    +
    +    // The convex hull of this model's mesh.
    +    TriangleMesh m_convex_hull;
    +
     public:
         std::string name;
         // The triangular model.
    @@ -180,19 +181,32 @@ public:
     
         ModelMaterial* assign_unique_material();
         
    +    void calculate_convex_hull();
    +    const TriangleMesh& get_convex_hull() const;
    +
     private:
         // Parent object owning this ModelVolume.
         ModelObject* object;
         t_model_material_id _material_id;
         
    -    ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object) {}
    -    ModelVolume(ModelObject *object, TriangleMesh &&mesh) : mesh(std::move(mesh)), modifier(false), object(object) {}
    -    ModelVolume(ModelObject *object, const ModelVolume &other) : 
    -        name(other.name), mesh(other.mesh), config(other.config), modifier(other.modifier), object(object)
    -        { this->material_id(other.material_id()); }
    -    ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : 
    +    ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object)
    +    {
    +        if (mesh.stl.stats.number_of_facets > 1)
    +            calculate_convex_hull();
    +    }
    +    ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), modifier(false), object(object) {}
    +    ModelVolume(ModelObject *object, const ModelVolume &other) :
    +        name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), modifier(other.modifier), object(object)
    +    {
    +        this->material_id(other.material_id());
    +    }
    +    ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
             name(other.name), mesh(std::move(mesh)), config(other.config), modifier(other.modifier), object(object)
    -        { this->material_id(other.material_id()); }
    +    {
    +        this->material_id(other.material_id());
    +        if (mesh.stl.stats.number_of_facets > 1)
    +            calculate_convex_hull();
    +    }
     };
     
     // A single instance of a ModelObject.
    @@ -285,8 +299,6 @@ public:
         bool add_default_instances();
         // Returns approximate axis aligned bounding box of this model
         BoundingBoxf3 bounding_box() const;
    -    // Returns tight axis aligned bounding box of this model
    -    BoundingBoxf3 transformed_bounding_box() const;
         void center_instances_around_point(const Pointf &point);
         void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
         TriangleMesh mesh() const;
    diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp
    index 45e4b6f5dc..fc72a45aad 100644
    --- a/xs/src/libslic3r/TriangleMesh.cpp
    +++ b/xs/src/libslic3r/TriangleMesh.cpp
    @@ -1,6 +1,9 @@
     #include "TriangleMesh.hpp"
     #include "ClipperUtils.hpp"
     #include "Geometry.hpp"
    +#include "qhull/src/libqhullcpp/Qhull.h"
    +#include "qhull/src/libqhullcpp/QhullFacetList.h"
    +#include "qhull/src/libqhullcpp/QhullVertexSet.h"
     #include 
     #include 
     #include 
    @@ -15,6 +18,8 @@
     
     #include 
     
    +#include 
    +
     #if 0
         #define DEBUG
         #define _DEBUG
    @@ -597,6 +602,134 @@ TriangleMesh::bounding_box() const
         return bb;
     }
     
    +BoundingBoxf3 TriangleMesh::transformed_bounding_box(const std::vector& matrix) const
    +{
    +    bool has_shared = (stl.v_shared != nullptr);
    +    if (!has_shared)
    +        stl_generate_shared_vertices(&stl);
    +
    +    unsigned int vertices_count = (stl.stats.shared_vertices > 0) ? (unsigned int)stl.stats.shared_vertices : 3 * (unsigned int)stl.stats.number_of_facets;
    +
    +    if (vertices_count == 0)
    +        return BoundingBoxf3();
    +
    +    Eigen::MatrixXf src_vertices(3, vertices_count);
    +
    +    if (stl.stats.shared_vertices > 0)
    +    {
    +        stl_vertex* vertex_ptr = stl.v_shared;
    +        for (int i = 0; i < stl.stats.shared_vertices; ++i)
    +        {
    +            src_vertices(0, i) = vertex_ptr->x;
    +            src_vertices(1, i) = vertex_ptr->y;
    +            src_vertices(2, i) = vertex_ptr->z;
    +            vertex_ptr += 1;
    +        }
    +    }
    +    else
    +    {
    +        stl_facet* facet_ptr = stl.facet_start;
    +        unsigned int v_id = 0;
    +        while (facet_ptr < stl.facet_start + stl.stats.number_of_facets)
    +        {
    +            for (int i = 0; i < 3; ++i)
    +            {
    +                src_vertices(0, v_id) = facet_ptr->vertex[i].x;
    +                src_vertices(1, v_id) = facet_ptr->vertex[i].y;
    +                src_vertices(2, v_id) = facet_ptr->vertex[i].z;
    +            }
    +            facet_ptr += 1;
    +            ++v_id;
    +        }
    +    }
    +
    +    if (!has_shared && (stl.stats.shared_vertices > 0))
    +        stl_invalidate_shared_vertices(&stl);
    +
    +    Eigen::Transform m;
    +    ::memcpy((void*)m.data(), (const void*)matrix.data(), 16 * sizeof(float));
    +
    +    Eigen::MatrixXf dst_vertices(3, vertices_count);
    +    dst_vertices = m * src_vertices.colwise().homogeneous();
    +
    +    float min_x = dst_vertices(0, 0);
    +    float max_x = dst_vertices(0, 0);
    +    float min_y = dst_vertices(1, 0);
    +    float max_y = dst_vertices(1, 0);
    +    float min_z = dst_vertices(2, 0);
    +    float max_z = dst_vertices(2, 0);
    +
    +    for (int i = 1; i < vertices_count; ++i)
    +    {
    +        min_x = std::min(min_x, dst_vertices(0, i));
    +        max_x = std::max(max_x, dst_vertices(0, i));
    +        min_y = std::min(min_y, dst_vertices(1, i));
    +        max_y = std::max(max_y, dst_vertices(1, i));
    +        min_z = std::min(min_z, dst_vertices(2, i));
    +        max_z = std::max(max_z, dst_vertices(2, i));
    +    }
    +
    +    return BoundingBoxf3(Pointf3((coordf_t)min_x, (coordf_t)min_y, (coordf_t)min_z), Pointf3((coordf_t)max_x, (coordf_t)max_y, (coordf_t)max_z));
    +}
    +
    +TriangleMesh TriangleMesh::convex_hull_3d() const
    +{
    +    // Helper struct for qhull:
    +    struct PointForQHull{
    +        PointForQHull(float x_p, float y_p, float z_p) : x((realT)x_p), y((realT)y_p), z((realT)z_p) {}
    +        realT x, y, z;
    +    };
    +    std::vector src_vertices;
    +
    +    // We will now fill the vector with input points for computation:
    +    stl_facet* facet_ptr = stl.facet_start;
    +    while (facet_ptr < stl.facet_start + stl.stats.number_of_facets)
    +    {
    +        for (int i = 0; i < 3; ++i)
    +        {
    +            const stl_vertex& v = facet_ptr->vertex[i];
    +            src_vertices.emplace_back(v.x, v.y, v.z);
    +        }
    +
    +        facet_ptr += 1;
    +    }
    +
    +    // The qhull call:
    +    orgQhull::Qhull qhull;
    +    qhull.disableOutputStream(); // we want qhull to be quiet
    +    try
    +    {
    +        qhull.runQhull("", 3, (int)src_vertices.size(), (const realT*)(src_vertices.data()), "Qt");
    +    }
    +    catch (...)
    +    {
    +        std::cout << "Unable to create convex hull" << std::endl;
    +        return TriangleMesh();
    +    }
    +
    +    // Let's collect results:
    +    Pointf3s det_vertices;
    +    std::vector facets;
    +    auto facet_list = qhull.facetList().toStdVector();
    +    for (const orgQhull::QhullFacet& facet : facet_list)
    +    {   // iterate through facets
    +        orgQhull::QhullVertexSet vertices = facet.vertices();
    +        for (int i = 0; i < 3; ++i)
    +        {   // iterate through facet's vertices
    +
    +            orgQhull::QhullPoint p = vertices[i].point();
    +            const float* coords = p.coordinates();
    +            det_vertices.emplace_back(coords[0], coords[1], coords[2]);
    +        }
    +        unsigned int size = (unsigned int)det_vertices.size();
    +        facets.emplace_back(size - 3, size - 2, size - 1);
    +    }
    +
    +    TriangleMesh output_mesh(det_vertices, facets);
    +    output_mesh.repair();
    +    return output_mesh;
    +}
    +
     void
     TriangleMesh::require_shared_vertices()
     {
    diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp
    index c700784a51..6ab52efe25 100644
    --- a/xs/src/libslic3r/TriangleMesh.hpp
    +++ b/xs/src/libslic3r/TriangleMesh.hpp
    @@ -55,6 +55,10 @@ public:
         ExPolygons horizontal_projection() const;
         Polygon convex_hull();
         BoundingBoxf3 bounding_box() const;
    +    // Returns the bbox of this TriangleMesh transformed by the given matrix
    +    BoundingBoxf3 transformed_bounding_box(const std::vector& matrix) const;
    +    // Returns the convex hull of this TriangleMesh
    +    TriangleMesh convex_hull_3d() const;
         void reset_repair_stats();
         bool needed_repair() const;
         size_t facets_count() const;
    @@ -66,7 +70,7 @@ public:
         // Count disconnected triangle patches.
         size_t number_of_patches() const;
     
    -    stl_file stl;
    +    mutable stl_file stl;
         bool repaired;
         
     private:
    diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
    index 3f01eb20c1..0709271b8c 100644
    --- a/xs/src/slic3r/GUI/3DScene.cpp
    +++ b/xs/src/slic3r/GUI/3DScene.cpp
    @@ -202,7 +202,8 @@ const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 0.19f, 0.58f, 1.0f, 1.0f };
     GLVolume::GLVolume(float r, float g, float b, float a)
         : m_angle_z(0.0f)
         , m_scale_factor(1.0f)
    -    , m_dirty(true)
    +    , m_transformed_bounding_box_dirty(true)
    +    , m_transformed_convex_hull_bounding_box_dirty(true)
         , composite_id(-1)
         , select_group_id(-1)
         , drag_group_id(-1)
    @@ -219,8 +220,6 @@ GLVolume::GLVolume(float r, float g, float b, float a)
         , tverts_range(0, size_t(-1))
         , qverts_range(0, size_t(-1))
     {
    -    m_world_mat = std::vector(UNIT_MATRIX, std::end(UNIT_MATRIX));
    -
         color[0] = r;
         color[1] = g;
         color[2] = b;
    @@ -264,45 +263,76 @@ const Pointf3& GLVolume::get_origin() const
     
     void GLVolume::set_origin(const Pointf3& origin)
     {
    -    m_origin = origin;
    -    m_dirty = true;
    +    if (m_origin != origin)
    +    {
    +        m_origin = origin;
    +        m_transformed_bounding_box_dirty = true;
    +        m_transformed_convex_hull_bounding_box_dirty = true;
    +    }
     }
     
     void GLVolume::set_angle_z(float angle_z)
     {
    -    m_angle_z = angle_z;
    -    m_dirty = true;
    +    if (m_angle_z != angle_z)
    +    {
    +        m_angle_z = angle_z;
    +        m_transformed_bounding_box_dirty = true;
    +        m_transformed_convex_hull_bounding_box_dirty = true;
    +    }
     }
     
     void GLVolume::set_scale_factor(float scale_factor)
     {
    -    m_scale_factor = scale_factor;
    -    m_dirty = true;
    +    if (m_scale_factor != scale_factor)
    +    {
    +        m_scale_factor = scale_factor;
    +        m_transformed_bounding_box_dirty = true;
    +        m_transformed_convex_hull_bounding_box_dirty = true;
    +    }
     }
     
    -const std::vector& GLVolume::world_matrix() const
    +void GLVolume::set_convex_hull(const TriangleMesh& convex_hull)
     {
    -    if (m_dirty)
    -    {
    -        Eigen::Transform m = Eigen::Transform::Identity();
    -        m.translate(Eigen::Vector3f((float)m_origin.x, (float)m_origin.y, (float)m_origin.z));
    -        m.rotate(Eigen::AngleAxisf(m_angle_z, Eigen::Vector3f::UnitZ()));
    -        m.scale(m_scale_factor);
    -        ::memcpy((void*)m_world_mat.data(), (const void*)m.data(), 16 * sizeof(float));
    -        m_dirty = false;
    -    }
    +    m_convex_hull = convex_hull;
    +}
     
    -    return m_world_mat;
    +std::vector GLVolume::world_matrix() const
    +{
    +    std::vector world_mat(UNIT_MATRIX, std::end(UNIT_MATRIX));
    +    Eigen::Transform m = Eigen::Transform::Identity();
    +    m.translate(Eigen::Vector3f((float)m_origin.x, (float)m_origin.y, (float)m_origin.z));
    +    m.rotate(Eigen::AngleAxisf(m_angle_z, Eigen::Vector3f::UnitZ()));
    +    m.scale(m_scale_factor);
    +    ::memcpy((void*)world_mat.data(), (const void*)m.data(), 16 * sizeof(float));
    +    return world_mat;
     }
     
     BoundingBoxf3 GLVolume::transformed_bounding_box() const
     {
    -    if (m_dirty)
    +    if (m_transformed_bounding_box_dirty)
    +    {
             m_transformed_bounding_box = bounding_box.transformed(world_matrix());
    +        m_transformed_bounding_box_dirty = false;
    +    }
     
         return m_transformed_bounding_box;
     }
     
    +BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box() const
    +{
    +    if (m_transformed_convex_hull_bounding_box_dirty)
    +    {
    +        if (m_convex_hull.stl.stats.number_of_facets > 0)
    +            m_transformed_convex_hull_bounding_box = m_convex_hull.transformed_bounding_box(world_matrix());
    +        else
    +            m_transformed_convex_hull_bounding_box = bounding_box.transformed(world_matrix());
    +
    +        m_transformed_convex_hull_bounding_box_dirty = false;
    +    }
    +
    +    return m_transformed_convex_hull_bounding_box;
    +}
    +
     void GLVolume::set_range(double min_z, double max_z)
     {
         this->qverts_range.first  = 0;
    @@ -629,6 +659,7 @@ std::vector GLVolumeCollection::load_object(
     
                 if (!model_volume->modifier)
                 {
    +                v.set_convex_hull(model_volume->get_convex_hull());
                     v.layer_height_texture = layer_height_texture;
                     if (extruder_id != -1)
                         v.extruder_id = extruder_id;
    @@ -716,6 +747,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
         v.drag_group_id = obj_idx * 1000;
         v.is_wipe_tower = true;
         v.shader_outside_printer_detection_enabled = ! size_unknown;
    +    v.set_convex_hull(mesh.convex_hull_3d());
         return int(this->volumes.size() - 1);
     }
     
    @@ -803,7 +835,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
         {
             if ((volume != nullptr) && !volume->is_modifier)
             {
    -            const BoundingBoxf3& bb = volume->transformed_bounding_box();
    +            const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box();
                 bool contained = print_volume.contains(bb);
                 all_contained &= contained;
     
    diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp
    index a552b32a7c..1265bc20de 100644
    --- a/xs/src/slic3r/GUI/3DScene.hpp
    +++ b/xs/src/slic3r/GUI/3DScene.hpp
    @@ -260,12 +260,16 @@ private:
         float                 m_angle_z;
         // Scale factor of the volume to be rendered.
         float                 m_scale_factor;
    -    // World matrix of the volume to be rendered.
    -    std::vector    m_world_mat;
         // Bounding box of this volume, in unscaled coordinates.
         mutable BoundingBoxf3 m_transformed_bounding_box;
    -    // Whether or not is needed to recalculate the world matrix.
    -    mutable bool          m_dirty;
    +    // Whether or not is needed to recalculate the transformed bounding box.
    +    mutable bool          m_transformed_bounding_box_dirty;
    +    // Convex hull of the original mesh, if any.
    +    TriangleMesh          m_convex_hull;
    +    // Bounding box of this volume, in unscaled coordinates.
    +    mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box;
    +    // Whether or not is needed to recalculate the transformed convex hull bounding box.
    +    mutable bool          m_transformed_convex_hull_bounding_box_dirty;
     
     public:
     
    @@ -323,13 +327,15 @@ public:
         void set_origin(const Pointf3& origin);
         void set_angle_z(float angle_z);
         void set_scale_factor(float scale_factor);
    +    void set_convex_hull(const TriangleMesh& convex_hull);
     
         int                 object_idx() const { return this->composite_id / 1000000; }
         int                 volume_idx() const { return (this->composite_id / 1000) % 1000; }
         int                 instance_idx() const { return this->composite_id % 1000; }
     
    -    const std::vector& world_matrix() const;
    +    std::vector world_matrix() const;
         BoundingBoxf3       transformed_bounding_box() const;
    +    BoundingBoxf3       transformed_convex_hull_bounding_box() const;
     
         bool                empty() const { return this->indexed_vertex_array.empty(); }
         bool                indexed() const { return this->indexed_vertex_array.indexed(); }
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index ab4095e6fe..92bde34ce7 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -3243,7 +3243,7 @@ BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const
         {
             for (const GLVolume* volume : selected_volumes)
             {
    -            bb.merge(volume->transformed_bounding_box());
    +            bb.merge(volume->transformed_convex_hull_bounding_box());
             }
         }
     
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 47b01e8a28..7207022bd3 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -189,7 +189,7 @@ GLGizmoRotate::GLGizmoRotate()
         , m_angle_z(0.0f)
         , m_center(Pointf(0.0, 0.0))
         , m_radius(0.0f)
    -    , m_keep_radius(false)
    +    , m_keep_initial_values(false)
     {
     }
     
    @@ -229,7 +229,7 @@ bool GLGizmoRotate::on_init()
     
     void GLGizmoRotate::on_set_state()
     {
    -    m_keep_radius = (m_state == On) ? false : true;
    +    m_keep_initial_values = (m_state == On) ? false : true;
     }
     
     void GLGizmoRotate::on_update(const Pointf& mouse_pos)
    @@ -255,19 +255,19 @@ void GLGizmoRotate::on_update(const Pointf& mouse_pos)
     
     void GLGizmoRotate::on_refresh()
     {
    -    m_keep_radius = false;
    +    m_keep_initial_values = false;
     }
     
     void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
     {
         ::glDisable(GL_DEPTH_TEST);
     
    -    const Pointf3& size = box.size();
    -    m_center = box.center();
    -    if (!m_keep_radius)
    +    if (!m_keep_initial_values)
         {
    +        const Pointf3& size = box.size();
    +        m_center = box.center();
             m_radius = Offset + ::sqrt(sqr(0.5f * size.x) + sqr(0.5f * size.y));
    -        m_keep_radius = true;
    +        m_keep_initial_values = true;
         }
     
         ::glLineWidth(2.0f);
    diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
    index 506b3972e7..839969090d 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.hpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.hpp
    @@ -101,7 +101,7 @@ class GLGizmoRotate : public GLGizmoBase
     
         mutable Pointf m_center;
         mutable float m_radius;
    -    mutable bool m_keep_radius;
    +    mutable bool m_keep_initial_values;
     
     public:
         GLGizmoRotate();
    diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp
    index 0e0a03f5ed..1a0fc9b9f9 100644
    --- a/xs/xsp/GUI_3DScene.xsp
    +++ b/xs/xsp/GUI_3DScene.xsp
    @@ -65,7 +65,6 @@
                  %};
         Clone bounding_box() const
             %code%{ RETVAL = THIS->bounding_box; %};
    -    Clone transformed_bounding_box() const;
     
         bool                empty() const;
         bool                indexed() const;
    
    From 2c9b41623a3d003134501fa66bfe9c1947ca2db4 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Thu, 16 Aug 2018 13:22:02 +0200
    Subject: [PATCH 090/119] Fixed wipe tower loosing selection after displacement
    
    ---
     xs/src/slic3r/GUI/GLCanvas3D.cpp | 4 ++++
     1 file changed, 4 insertions(+)
    
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index 92bde34ce7..f5122539e3 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -3017,6 +3017,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 }
                 
                 _on_move(volume_idxs);
    +
    +            // force re-selection of the wipe tower, if needed
    +            if ((volume_idxs.size() == 1) && m_volumes.volumes[volume_idxs[0]]->is_wipe_tower)
    +                select_volume(volume_idxs[0]);
             }
             else if (!m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled())
             {
    
    From d38816bd9c32badaf2f2946f0bfe395c4632858c Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Thu, 16 Aug 2018 13:35:56 +0200
    Subject: [PATCH 091/119] GLVolume use a pointer to ModelVolume's convex hull
     instead of a copy of it
    
    ---
     xs/src/slic3r/GUI/3DScene.cpp | 8 ++++----
     xs/src/slic3r/GUI/3DScene.hpp | 4 ++--
     2 files changed, 6 insertions(+), 6 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
    index 0709271b8c..171f4dbe85 100644
    --- a/xs/src/slic3r/GUI/3DScene.cpp
    +++ b/xs/src/slic3r/GUI/3DScene.cpp
    @@ -204,6 +204,7 @@ GLVolume::GLVolume(float r, float g, float b, float a)
         , m_scale_factor(1.0f)
         , m_transformed_bounding_box_dirty(true)
         , m_transformed_convex_hull_bounding_box_dirty(true)
    +    , m_convex_hull(nullptr)
         , composite_id(-1)
         , select_group_id(-1)
         , drag_group_id(-1)
    @@ -293,7 +294,7 @@ void GLVolume::set_scale_factor(float scale_factor)
     
     void GLVolume::set_convex_hull(const TriangleMesh& convex_hull)
     {
    -    m_convex_hull = convex_hull;
    +    m_convex_hull = &convex_hull;
     }
     
     std::vector GLVolume::world_matrix() const
    @@ -322,8 +323,8 @@ BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box() const
     {
         if (m_transformed_convex_hull_bounding_box_dirty)
         {
    -        if (m_convex_hull.stl.stats.number_of_facets > 0)
    -            m_transformed_convex_hull_bounding_box = m_convex_hull.transformed_bounding_box(world_matrix());
    +        if ((m_convex_hull != nullptr) && (m_convex_hull->stl.stats.number_of_facets > 0))
    +            m_transformed_convex_hull_bounding_box = m_convex_hull->transformed_bounding_box(world_matrix());
             else
                 m_transformed_convex_hull_bounding_box = bounding_box.transformed(world_matrix());
     
    @@ -747,7 +748,6 @@ int GLVolumeCollection::load_wipe_tower_preview(
         v.drag_group_id = obj_idx * 1000;
         v.is_wipe_tower = true;
         v.shader_outside_printer_detection_enabled = ! size_unknown;
    -    v.set_convex_hull(mesh.convex_hull_3d());
         return int(this->volumes.size() - 1);
     }
     
    diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp
    index 1265bc20de..5cd144c680 100644
    --- a/xs/src/slic3r/GUI/3DScene.hpp
    +++ b/xs/src/slic3r/GUI/3DScene.hpp
    @@ -264,8 +264,8 @@ private:
         mutable BoundingBoxf3 m_transformed_bounding_box;
         // Whether or not is needed to recalculate the transformed bounding box.
         mutable bool          m_transformed_bounding_box_dirty;
    -    // Convex hull of the original mesh, if any.
    -    TriangleMesh          m_convex_hull;
    +    // Pointer to convex hull of the original mesh, if any.
    +    const TriangleMesh*   m_convex_hull;
         // Bounding box of this volume, in unscaled coordinates.
         mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box;
         // Whether or not is needed to recalculate the transformed convex hull bounding box.
    
    From 1fff2252bce88b4a7099f6347d7b264ecb793571 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Thu, 16 Aug 2018 13:42:35 +0200
    Subject: [PATCH 092/119] Detection of out of print volume disabled for wipe
     tower of unknown size
    
    ---
     xs/src/slic3r/GUI/3DScene.cpp | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
    index 171f4dbe85..7053470943 100644
    --- a/xs/src/slic3r/GUI/3DScene.cpp
    +++ b/xs/src/slic3r/GUI/3DScene.cpp
    @@ -833,7 +833,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
     
         for (GLVolume* volume : this->volumes)
         {
    -        if ((volume != nullptr) && !volume->is_modifier)
    +        if ((volume != nullptr) && !volume->is_modifier && (!volume->is_wipe_tower || (volume->is_wipe_tower && volume->shader_outside_printer_detection_enabled)))
             {
                 const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box();
                 bool contained = print_volume.contains(bb);
    
    From b6e0458201128b5db4db5faaeb41d307e395edd6 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Fri, 17 Aug 2018 09:16:34 +0200
    Subject: [PATCH 093/119] Fixed lost selection of imported objects
    
    ---
     lib/Slic3r/GUI/Plater.pm | 6 +++++-
     1 file changed, 5 insertions(+), 1 deletion(-)
    
    diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
    index a0eef72fea..4fd9a2692a 100644
    --- a/lib/Slic3r/GUI/Plater.pm
    +++ b/lib/Slic3r/GUI/Plater.pm
    @@ -1291,7 +1291,9 @@ sub async_apply_config {
     
             # We also need to reload 3D scene because of the wipe tower preview box
             if ($self->{config}->wipe_tower) {
    -	       Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1) if $self->{canvas3D}
    +            my $selections = $self->collect_selections;
    +            Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections);
    +	        Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1) if $self->{canvas3D}
             }
         }
     }
    @@ -1507,6 +1509,8 @@ sub on_process_completed {
         $self->{preview3D}->reload_print if $self->{preview3D};
     
         # in case this was MM print, wipe tower bounding box on 3D tab might need redrawing with exact depth:
    +    my $selections = $self->collect_selections;
    +    Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections);
         Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1);
         
         # if we have an export filename, start a new thread for exporting G-code
    
    From 048f3a03fe884566b2ec290164e46ca3fce39b28 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Fri, 17 Aug 2018 10:12:43 +0200
    Subject: [PATCH 094/119] Fixed scale to size of objects with multiple
     instances
    
    ---
     lib/Slic3r/GUI/Plater.pm | 5 ++---
     1 file changed, 2 insertions(+), 3 deletions(-)
    
    diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
    index 4fd9a2692a..b4677b1af6 100644
    --- a/lib/Slic3r/GUI/Plater.pm
    +++ b/lib/Slic3r/GUI/Plater.pm
    @@ -1126,8 +1126,7 @@ sub changescale {
         my $model_object = $self->{model}->objects->[$obj_idx];
         my $model_instance = $model_object->instances->[0];
         
    -    my $object_size = $model_object->bounding_box->size;
    -    my $bed_size = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape})->bounding_box->size;
    +    my $object_size = $model_object->instance_bounding_box(0)->size;
         
         if (defined $axis) {
             my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z';
    @@ -1135,7 +1134,7 @@ sub changescale {
             if ($tosize) {
                 my $cursize = $object_size->[$axis];
                 my $newsize = $self->_get_number_from_user(
    -                sprintf(L('Enter the new size for the selected object (print bed: %smm):'), unscale($bed_size->[$axis])), 
    +                L('Enter the new size for the selected object:'), 
                     L("Scale along ").$axis_name, L('Invalid scaling value entered'), $cursize, 1);
                 return if $newsize eq '';
                 $scale = $newsize / $cursize * 100;
    
    From 48b9793d3d2f5a9b4163f4927563ffd4266e4f12 Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Fri, 17 Aug 2018 15:20:35 +0200
    Subject: [PATCH 095/119] Templated convex_hull function in Geometry.cpp
    
    ---
     xs/src/libslic3r/Geometry.cpp | 53 ++++++++++++++++++++++-------------
     xs/src/libslic3r/Geometry.hpp |  2 ++
     xs/src/libslic3r/Point.cpp    |  6 ++++
     xs/src/libslic3r/Point.hpp    |  1 +
     4 files changed, 43 insertions(+), 19 deletions(-)
    
    diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp
    index 39b626ee31..aaf0352c99 100644
    --- a/xs/src/libslic3r/Geometry.cpp
    +++ b/xs/src/libslic3r/Geometry.cpp
    @@ -195,47 +195,62 @@ using namespace boost::polygon;  // provides also high() and low()
     
     namespace Slic3r { namespace Geometry {
     
    -static bool
    -sort_points (Point a, Point b)
    -{
    -    return (a.x < b.x) || (a.x == b.x && a.y < b.y);
    -}
    +struct SortPoints {
    +    template 
    +    bool operator()(const T& a, const T& b) const {
    +        return (b.x > a.x) || (a.x == b.x && b.y > a.y);
    +    }
    +};
     
    -/* This implementation is based on Andrew's monotone chain 2D convex hull algorithm */
    -Polygon
    -convex_hull(Points points)
    +// This implementation is based on Andrew's monotone chain 2D convex hull algorithm
    +template
    +static T raw_convex_hull(T& points)
     {
         assert(points.size() >= 3);
         // sort input points
    -    std::sort(points.begin(), points.end(), sort_points);
    +    std::sort(points.begin(), points.end(), SortPoints());
         
         int n = points.size(), k = 0;
    -    Polygon hull;
    +    T hull;
     
         if (n >= 3) {
    -        hull.points.resize(2*n);
    +        hull.resize(2*n);
     
             // Build lower hull
             for (int i = 0; i < n; i++) {
    -            while (k >= 2 && points[i].ccw(hull.points[k-2], hull.points[k-1]) <= 0) k--;
    -            hull.points[k++] = points[i];
    +            while (k >= 2 && points[i].ccw(hull[k-2], hull[k-1]) <= 0) k--;
    +            hull[k++] = points[i];
             }
     
             // Build upper hull
             for (int i = n-2, t = k+1; i >= 0; i--) {
    -            while (k >= t && points[i].ccw(hull.points[k-2], hull.points[k-1]) <= 0) k--;
    -            hull.points[k++] = points[i];
    +            while (k >= t && points[i].ccw(hull[k-2], hull[k-1]) <= 0) k--;
    +            hull[k++] = points[i];
             }
     
    -        hull.points.resize(k);
    +        hull.resize(k);
             
    -        assert( hull.points.front().coincides_with(hull.points.back()) );
    -        hull.points.pop_back();
    +        assert( hull.front().coincides_with(hull.back()) );
    +        hull.pop_back();
         }
         
         return hull;
     }
     
    +Pointf3s
    +convex_hull(Pointf3s points)
    +{
    +    return raw_convex_hull(points);
    +}
    +
    +Polygon
    +convex_hull(Points points)
    +{
    +    Polygon hull;
    +    hull.points = raw_convex_hull(points);
    +    return hull;
    +}
    +
     Polygon
     convex_hull(const Polygons &polygons)
     {
    @@ -243,7 +258,7 @@ convex_hull(const Polygons &polygons)
         for (Polygons::const_iterator p = polygons.begin(); p != polygons.end(); ++p) {
             pp.insert(pp.end(), p->points.begin(), p->points.end());
         }
    -    return convex_hull(pp);
    +    return convex_hull(std::move(pp));
     }
     
     /* accepts an arrayref of points and returns a list of indices
    diff --git a/xs/src/libslic3r/Geometry.hpp b/xs/src/libslic3r/Geometry.hpp
    index c2c3dc8b75..956ef82aab 100644
    --- a/xs/src/libslic3r/Geometry.hpp
    +++ b/xs/src/libslic3r/Geometry.hpp
    @@ -108,8 +108,10 @@ inline bool segment_segment_intersection(const Pointf &p1, const Vectorf &v1, co
         return true;
     }
     
    +Pointf3s convex_hull(Pointf3s points);
     Polygon convex_hull(Points points);
     Polygon convex_hull(const Polygons &polygons);
    +
     void chained_path(const Points &points, std::vector &retval, Point start_near);
     void chained_path(const Points &points, std::vector &retval);
     template void chained_path_items(Points &points, T &items, T &retval);
    diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp
    index 2abcd26af4..349b00bb67 100644
    --- a/xs/src/libslic3r/Point.cpp
    +++ b/xs/src/libslic3r/Point.cpp
    @@ -263,6 +263,12 @@ operator<<(std::ostream &stm, const Pointf &pointf)
         return stm << pointf.x << "," << pointf.y;
     }
     
    +double
    +Pointf::ccw(const Pointf &p1, const Pointf &p2) const
    +{
    +    return (double)(p2.x - p1.x)*(double)(this->y - p1.y) - (double)(p2.y - p1.y)*(double)(this->x - p1.x);
    +}
    +
     std::string
     Pointf::wkt() const
     {
    diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp
    index 87104674f4..347966c03a 100644
    --- a/xs/src/libslic3r/Point.hpp
    +++ b/xs/src/libslic3r/Point.hpp
    @@ -221,6 +221,7 @@ public:
         static Pointf new_unscale(const Point &p) {
             return Pointf(unscale(p.x), unscale(p.y));
         };
    +    double ccw(const Pointf &p1, const Pointf &p2) const;
         std::string wkt() const;
         std::string dump_perl() const;
         void scale(double factor);
    
    From 267712eb32e663873578b589d9383074a25feb3a Mon Sep 17 00:00:00 2001
    From: Vojtech Kral 
    Date: Thu, 16 Aug 2018 16:34:59 +0200
    Subject: [PATCH 096/119] Build: Plumb perl include path Thanks to @kortschak
     for contributing to this fix Co-authored-by: Dan Kortschak 
    
    ---
     Build.PL          |  1 -
     CMakeLists.txt    | 15 +++++++++++----
     xs/CMakeLists.txt |  5 +++--
     3 files changed, 14 insertions(+), 7 deletions(-)
    
    diff --git a/Build.PL b/Build.PL
    index 8f882fc4bb..16e37986a7 100644
    --- a/Build.PL
    +++ b/Build.PL
    @@ -38,7 +38,6 @@ if ($gui) {
         %prereqs = qw(
         Class::Accessor                 0
         Wx                              0.9918
    -    Socket                          2.016
         );
         %recommends = qw(
         Wx::GLCanvas                    0
    diff --git a/CMakeLists.txt b/CMakeLists.txt
    index 89e6369e01..5d9194d06f 100644
    --- a/CMakeLists.txt
    +++ b/CMakeLists.txt
    @@ -1,5 +1,4 @@
    -# Boost 1.63 requires CMake 3.7 or newer
    -cmake_minimum_required(VERSION 2.8)
    +cmake_minimum_required(VERSION 3.2)
     
     project(Slic3r)
     
    @@ -36,10 +35,18 @@ else()
         set(ENV_PATH_SEPARATOR ":")
     endif()
     set(ENV{PATH}     "${PROJECT_SOURCE_DIR}/local-lib/bin${ENV_PATH_SEPARATOR}$ENV{PATH}")
    -set(ENV{PERL5LIB} "${PROJECT_SOURCE_DIR}/local-lib/lib/perl${ENV_PATH_SEPARATOR}$ENV{PERL5LIB}")
    +set(PERL_INCLUDE  "${PROJECT_SOURCE_DIR}/local-lib/lib/perl5${ENV_PATH_SEPARATOR}$ENV{PERL5LIB}")
     message("PATH: $ENV{PATH}")
    -message("PERL5LIB: $ENV{PERL5LIB}")
    +message("PERL_INCLUDE: ${PERL_INCLUDE}")
     find_package(Perl REQUIRED)
    +if (WIN32)
    +    # On Windows passing the PERL5LIB variable causes various problems (such as with MAX_PATH and others),
    +    # basically I've found no good way to do it on Windows.
    +    set(PERL5LIB_ENV_CMD "")
    +else()
    +    set(PERL5LIB_ENV_CMD ${CMAKE_COMMAND} -E env PERL5LIB=${PERL_INCLUDE})
    +endif()
    +
     
     # CMAKE_PREFIX_PATH is used to point CMake to the remaining dependencies (Boost, TBB, ...)
     # We pick it from environment if it is not defined in another way
    diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
    index d41b4c13a9..6c2a61685c 100644
    --- a/xs/CMakeLists.txt
    +++ b/xs/CMakeLists.txt
    @@ -367,7 +367,7 @@ set(MyTypemap ${CMAKE_CURRENT_BINARY_DIR}/typemap)
     add_custom_command(
             OUTPUT ${MyTypemap}
             DEPENDS ${CMAKE_CURRENT_LIST_DIR}/xsp/my.map
    -        COMMAND ${PERL_EXECUTABLE} -MExtUtils::Typemaps -MExtUtils::Typemaps::Basic -e "$typemap = ExtUtils::Typemaps->new(file => \"${CMAKE_CURRENT_LIST_DIR}/xsp/my.map\"); $typemap->merge(typemap => ExtUtils::Typemaps::Basic->new); $typemap->write(file => \"${MyTypemap}\")"
    +        COMMAND ${PERL5LIB_ENV_CMD} ${PERL_EXECUTABLE} -MExtUtils::Typemaps -MExtUtils::Typemaps::Basic -e "$typemap = ExtUtils::Typemaps->new(file => \"${CMAKE_CURRENT_LIST_DIR}/xsp/my.map\"); $typemap->merge(typemap => ExtUtils::Typemaps::Basic->new); $typemap->write(file => \"${MyTypemap}\")"
             VERBATIM
     )
     
    @@ -432,7 +432,8 @@ set(XS_MAIN_CPP ${CMAKE_CURRENT_BINARY_DIR}/XS.cpp)
     add_custom_command(
             OUTPUT ${XS_MAIN_CPP}
             DEPENDS ${MyTypemap} ${XS_XSP_FILES} ${CMAKE_CURRENT_LIST_DIR}/xsp/typemap.xspt
    -        COMMAND COMMAND xsubpp -typemap typemap -output ${XS_MAIN_CPP} -hiertype ${XS_MAIN_XS}
    +        COMMAND ${PERL5LIB_ENV_CMD} xsubpp -typemap typemap -output ${XS_MAIN_CPP} -hiertype ${XS_MAIN_XS}
    +        VERBATIM
     )
     
     # Define the Perl XS shared library.
    
    From 7be24414f3ad83f7c45c40f244699fd41d487f72 Mon Sep 17 00:00:00 2001
    From: Vojtech Kral 
    Date: Fri, 17 Aug 2018 12:38:33 +0200
    Subject: [PATCH 097/119] Build: Option to force generation of PDB file on MSVC
     Release build
    
    ---
     CMakeLists.txt    | 1 +
     xs/CMakeLists.txt | 8 ++++++++
     2 files changed, 9 insertions(+)
    
    diff --git a/CMakeLists.txt b/CMakeLists.txt
    index 5d9194d06f..8e3d8ad4df 100644
    --- a/CMakeLists.txt
    +++ b/CMakeLists.txt
    @@ -22,6 +22,7 @@ option(SLIC3R_GUI    			"Compile Slic3r with GUI components (OpenGL, wxWidgets)"
     option(SLIC3R_PRUSACONTROL		"Compile Slic3r with the PrusaControl prject file format (requires wxWidgets base library)" 1)
     option(SLIC3R_PROFILE 			"Compile Slic3r with an invasive Shiny profiler" 0)
     option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1)
    +option(SLIC3R_MSVC_PDB          "Generate PDB files on MSVC in Release mode" 1)
     
     if (MSVC AND SLIC3R_MSVC_COMPILE_PARALLEL)
     	add_compile_options(/MP)
    diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
    index 6c2a61685c..d4306c525c 100644
    --- a/xs/CMakeLists.txt
    +++ b/xs/CMakeLists.txt
    @@ -497,6 +497,14 @@ if (WIN32)
         target_compile_definitions(XS PRIVATE -DNOGDI -DNOMINMAX -DHAS_BOOL)
     endif ()
     
    +# SLIC3R_MSVC_PDB
    +if (MSVC AND SLIC3R_MSVC_PDB AND ${CMAKE_BUILD_TYPE} STREQUAL "Release")
    +    set_target_properties(XS PROPERTIES
    +        COMPILE_FLAGS "/Zi"
    +        LINK_FLAGS "/DEBUG /OPT:REF /OPT:ICF"
    +    )
    +endif()
    +
     ## Configuration flags
     if (SLIC3R_GUI)
         message("Slic3r will be built with GUI support")
    
    From f9efcc36b64d631bfece0a207fbb74388c3bb514 Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Fri, 17 Aug 2018 15:40:47 +0200
    Subject: [PATCH 098/119] Lay flat gizmo improvements - merge adjacent faces,
     compute and cache convex hull for entire ModelObject, refresh when moved,
     etc.
    
    ---
     xs/src/libslic3r/TriangleMesh.cpp |   1 +
     xs/src/slic3r/GUI/GLCanvas3D.cpp  |  63 +-------
     xs/src/slic3r/GUI/GLCanvas3D.hpp  |   2 +-
     xs/src/slic3r/GUI/GLGizmo.cpp     | 257 ++++++++++++++++++++++++++----
     xs/src/slic3r/GUI/GLGizmo.hpp     |  31 +++-
     5 files changed, 256 insertions(+), 98 deletions(-)
    
    diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp
    index 957515db3f..19dcc6a070 100644
    --- a/xs/src/libslic3r/TriangleMesh.cpp
    +++ b/xs/src/libslic3r/TriangleMesh.cpp
    @@ -728,6 +728,7 @@ TriangleMesh TriangleMesh::convex_hull_3d() const
     
         TriangleMesh output_mesh(det_vertices, facets);
         output_mesh.repair();
    +    output_mesh.require_shared_vertices();
         return output_mesh;
     }
     
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index 2bf516e3f6..947af67543 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -1409,14 +1409,14 @@ Pointf3 GLCanvas3D::Gizmos::get_flattening_normal() const
         return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_flattening_normal() : Pointf3(0.f, 0.f, 0.f);
     }
     
    -void GLCanvas3D::Gizmos::set_flattening_data(std::vector vertices_list)
    +void GLCanvas3D::Gizmos::set_flattening_data(const ModelObject* model_object)
     {
         if (!m_enabled)
             return;
     
         GizmosMap::const_iterator it = m_gizmos.find(Flatten);
         if (it != m_gizmos.end())
    -        reinterpret_cast(it->second)->set_flattening_data(vertices_list);
    +        reinterpret_cast(it->second)->set_flattening_data(model_object);
     }
     
     void GLCanvas3D::Gizmos::render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const
    @@ -2202,62 +2202,7 @@ void GLCanvas3D::update_gizmos_data()
                 {
                     m_gizmos.set_scale(model_instance->scaling_factor);
                     m_gizmos.set_angle_z(model_instance->rotation);
    -
    -                /////////////////////////////////////////////////////////////////////////
    -                // Following block provides convex hull data to the Flatten gizmo
    -                // It is temporary, it should be optimized and moved elsewhere later
    -                TriangleMesh ch = model_object->mesh().convex_hull_3d();
    -                stl_facet* facet_ptr = ch.stl.facet_start;
    -                std::vector points;
    -                const unsigned int k = 20;
    -                const float ratio = 0.2f;
    -                const unsigned int N = 3; // 3 - triangle
    -
    -                while (facet_ptr < ch.stl.facet_start+ch.stl.stats.number_of_facets) {
    -                    Pointf3 a = Pointf3(facet_ptr->vertex[1].x - facet_ptr->vertex[0].x, facet_ptr->vertex[1].y - facet_ptr->vertex[0].y, facet_ptr->vertex[1].z - facet_ptr->vertex[0].z);
    -                    Pointf3 b = Pointf3(facet_ptr->vertex[2].x - facet_ptr->vertex[0].x, facet_ptr->vertex[2].y - facet_ptr->vertex[0].y, facet_ptr->vertex[2].z - facet_ptr->vertex[0].z);
    -
    -
    -                    if (0.5 * sqrt(dot(cross(a, b), cross(a,b))) > 50.f) {
    -                        points.emplace_back(Pointf3s(2*k*N));
    -
    -                        std::vector> neighbours;
    -                        if (k != 0) {
    -                            for (unsigned int j=0; jvertex[j].x, facet_ptr->vertex[j].y, facet_ptr->vertex[j].z);
    -                                neighbours.push_back(std::make_pair((int)(j*2*k-k) < 0 ? (N-1)*2*k+k : j*2*k-k, j*2*k+k));
    -                            }
    -
    -                            for (unsigned int i=0; ivertex[j].x, facet_ptr->vertex[j].y, facet_ptr->vertex[j].z));
    -
    -                        points.back().emplace_back(Pointf3(facet_ptr->normal.x, facet_ptr->normal.y, facet_ptr->normal.z));
    -                    }
    -                    facet_ptr+=1;
    -                }
    -                m_gizmos.set_flattening_data(points);
    -                ////////////////////////////////////////////////////////////////////////
    +                m_gizmos.set_flattening_data(model_object);
                 }
             }
         }
    @@ -2265,7 +2210,7 @@ void GLCanvas3D::update_gizmos_data()
         {
             m_gizmos.set_scale(1.0f);
             m_gizmos.set_angle_z(0.0f);
    -        m_gizmos.set_flattening_data(std::vector());
    +        m_gizmos.set_flattening_data(nullptr);
         }
     }
     
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    index 09a7de823b..5f955cce20 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    @@ -383,7 +383,7 @@ public:
             float get_angle_z() const;
             void set_angle_z(float angle_z);
     
    -        void set_flattening_data(std::vector vertices_list);
    +        void set_flattening_data(const ModelObject* model_object);
             Pointf3 get_flattening_normal() const;
     
             void render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const;
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 5a69bc9b75..f0a8f2e712 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -2,6 +2,8 @@
     
     #include "../../libslic3r/Utils.hpp"
     #include "../../libslic3r/BoundingBox.hpp"
    +#include "../../libslic3r/Model.hpp"
    +#include "../../libslic3r/Geometry.hpp"
     
     #include 
     
    @@ -534,22 +536,19 @@ void GLGizmoFlatten::on_start_dragging()
             m_normal = m_planes[m_hover_id].normal;
     }
     
    -void GLGizmoFlatten::on_update(const Pointf& mouse_pos)
    -{
    -    /*Pointf center(0.5 * (m_grabbers[1].center.x + m_grabbers[0].center.x), 0.5 * (m_grabbers[3].center.y + m_grabbers[0].center.y));
    -
    -    coordf_t orig_len = length(m_starting_drag_position - center);
    -    coordf_t new_len = length(mouse_pos - center);
    -    coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0;
    -
    -    m_scale = m_starting_scale * (float)ratio;*/
    -}
    -
     void GLGizmoFlatten::on_render(const BoundingBoxf3& box) const
     {
    +    // the dragged_offset is a vector measuring where was the object moved
    +    // with the gizmo being on. This is reset in set_flattening_data and
    +    // does not work correctly when there are multiple copies.
    +    if (!m_center) // this is the first bounding box that we see
    +        m_center.reset(new Pointf3(box.center().x, box.center().y));
    +    Pointf3 dragged_offset = box.center() - *m_center;
    +
         bool blending_was_enabled = ::glIsEnabled(GL_BLEND);
    +    bool depth_test_was_enabled = ::glIsEnabled(GL_DEPTH_TEST);
         ::glEnable(GL_BLEND);
    -    ::glDisable(GL_DEPTH_TEST);
    +    ::glEnable(GL_DEPTH_TEST);
     
         for (int i=0; i<(int)m_planes.size(); ++i) {
             if (i == m_hover_id)
    @@ -557,14 +556,19 @@ void GLGizmoFlatten::on_render(const BoundingBoxf3& box) const
                 else
                     ::glColor4f(0.9f, 0.9f, 0.9f, 0.5f);
     
    -        ::glBegin(GL_POLYGON);
    -        for (const auto& vertex : m_planes[i].vertices)
    -            ::glVertex3f((GLfloat)vertex.x, (GLfloat)vertex.y, (GLfloat)vertex.z);
    -        ::glEnd();
    +        for (Pointf offset : m_instances_positions) {
    +            offset += dragged_offset;
    +            ::glBegin(GL_POLYGON);
    +            for (const auto& vertex : m_planes[i].vertices)
    +                ::glVertex3f((GLfloat)vertex.x + offset.x, (GLfloat)vertex.y + offset.y, (GLfloat)vertex.z);
    +            ::glEnd();
    +        }
         }
     
         if (!blending_was_enabled)
             ::glDisable(GL_BLEND);
    +    if (!depth_test_was_enabled)
    +        ::glDisable(GL_DEPTH_TEST);
     }
     
     void GLGizmoFlatten::on_render_for_picking(const BoundingBoxf3& box) const
    @@ -576,27 +580,220 @@ void GLGizmoFlatten::on_render_for_picking(const BoundingBoxf3& box) const
         for (unsigned int i = 0; i < m_planes.size(); ++i)
         {
             ::glColor3f(1.f, 1.f, (254.0f - (float)i) * INV_255);
    -        ::glBegin(GL_POLYGON);
    -        for (const auto& vertex : m_planes[i].vertices)
    -            ::glVertex3f((GLfloat)vertex.x, (GLfloat)vertex.y, (GLfloat)vertex.z);
    -        ::glEnd();
    +        for (const Pointf& offset : m_instances_positions) {
    +            ::glBegin(GL_POLYGON);
    +            for (const auto& vertex : m_planes[i].vertices)
    +                ::glVertex3f((GLfloat)vertex.x + offset.x, (GLfloat)vertex.y + offset.y, (GLfloat)vertex.z);
    +            ::glEnd();
    +        }
         }
     }
     
    -void GLGizmoFlatten::set_flattening_data(std::vector vertices_list)
    +
    +// TODO - remove and use Eigen instead
    +static Pointf3 super_rotation(const Pointf3& axis, float angle, const Pointf3& point)
     {
    -    // Each entry in vertices_list describe one polygon that can be laid flat.
    -    // All points but the last one are vertices of the polygon, the last "point" is the outer normal vector.
    +    float axis_length = axis.distance_to(Pointf3(0.f, 0.f, 0.f));
    +    float x = axis.x / axis_length;
    +    float y = axis.y / axis_length;
    +    float z = axis.z / axis_length;
    +    float s = sin(angle);
    +    float c = cos(angle);
    +    float D = 1-c;
    +    float matrix[3][3] = { { c + x*x*D, x*y*D-z*s, x*z*D+y*s },
    +                           { y*x*D+z*s, c+y*y*D,   y*z*D-x*s },
    +                           { z*x*D-y*s, z*y*D+x*s, c+z*z*D   } };
    +    float in[3] = { (float)point.x, (float)point.y, (float)point.z };
    +    float out[3] = { 0, 0, 0 };
    +
    +    for (unsigned char i=0; i<3; ++i)
    +        for (unsigned char j=0; j<3; ++j)
    +            out[i] += matrix[i][j] * in[j];
    +
    +    return Pointf3(out[0], out[1], out[2]);
    +}
    +
    +
    +void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object)
    +{
    +    m_center.release(); // object is not being dragged (this would not be called otherwise) - we must forget about the bounding box position...
    +    m_model_object = model_object;
    +
    +    // ...and save the updated positions of the object instances:
    +    if (m_model_object && !m_model_object->instances.empty()) {
    +        m_instances_positions.clear();
    +        for (const auto* instance : m_model_object->instances)
    +            m_instances_positions.emplace_back(instance->offset);
    +    }
    +
    +    if (is_plane_update_necessary())
    +        update_planes();
    +}
    +
    +void GLGizmoFlatten::update_planes()
    +{
    +    TriangleMesh ch;
    +    for (const ModelVolume* vol : m_model_object->volumes)
    +        ch.merge(vol->get_convex_hull());
    +    ch = ch.convex_hull_3d();
    +    ch.scale(m_model_object->instances.front()->scaling_factor);
    +    ch.rotate_z(m_model_object->instances.front()->rotation);
     
         m_planes.clear();
    -    m_planes.reserve(vertices_list.size());
     
    -    for (const auto& plane_data : vertices_list) {
    -        m_planes.emplace_back(PlaneData());
    -        for (unsigned int i=0; i  facet_queue(num_of_facets, 0);
    +    std::vector facet_visited(num_of_facets, false);
    +    int               facet_queue_cnt = 0;
    +    const stl_normal* normal_ptr = nullptr;
    +    while (1) {
    +        // Find next unvisited triangle:
    +        int facet_idx = 0;
    +        for (; facet_idx < num_of_facets; ++ facet_idx)
    +            if (!facet_visited[facet_idx]) {
    +                facet_queue[facet_queue_cnt ++] = facet_idx;
    +                facet_visited[facet_idx] = true;
    +                normal_ptr = &ch.stl.facet_start[facet_idx].normal;
    +                m_planes.emplace_back();
    +                break;
    +            }
    +        if (facet_idx == num_of_facets)
    +            break; // Everything was visited already
    +
    +        while (facet_queue_cnt > 0) {
    +            int facet_idx = facet_queue[-- facet_queue_cnt];
    +            const stl_normal* this_normal_ptr = &ch.stl.facet_start[facet_idx].normal;
    +            //if (this_normal_ptr->x == normal_ptr->x && this_normal_ptr->y == normal_ptr->y && this_normal_ptr->z == normal_ptr->z) {
    +            if (std::abs(this_normal_ptr->x-normal_ptr->x) < 0.001 && std::abs(this_normal_ptr->y-normal_ptr->y) < 0.001 && std::abs(this_normal_ptr->z-normal_ptr->z) < 0.001) {
    +                stl_vertex* first_vertex = ch.stl.facet_start[facet_idx].vertex;
    +                for (int j=0; j<3; ++j)
    +                    m_planes.back().vertices.emplace_back(first_vertex[j].x, first_vertex[j].y, first_vertex[j].z);
    +
    +                facet_visited[facet_idx] = true;
    +                for (int j = 0; j < 3; ++ j) {
    +                    int neighbor_idx = ch.stl.neighbors_start[facet_idx].neighbor[j];
    +                    if (! facet_visited[neighbor_idx])
    +                        facet_queue[facet_queue_cnt ++] = neighbor_idx;
    +                }
    +            }
    +        }
    +        m_planes.back().normal = Pointf3(normal_ptr->x, normal_ptr->y, normal_ptr->z);
         }
    +
    +    // Now we'll go through all the polygons, transform the points into xy plane to process them:
    +    for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) {
    +        Pointf3s& polygon = m_planes[polygon_id].vertices;
    +        const Pointf3& normal = m_planes[polygon_id].normal;
    +
    +        // We are going to rotate about z and y to flatten the plane
    +        float angle_z = 0.f;
    +        float angle_y = 0.f;
    +        if (std::abs(normal.y) > 0.001)
    +            angle_z = -atan2(normal.y, normal.x); // angle to rotate so that normal ends up in xz-plane
    +        if (std::abs(normal.x*cos(angle_z)-normal.y*sin(angle_z)) > 0.001)
    +            angle_y = - atan2(normal.x*cos(angle_z)-normal.y*sin(angle_z), normal.z); // angle to rotate to make normal point upwards
    +        else {
    +            // In case it already was in z-direction, we must ensure it is not the wrong way:
    +            angle_y = normal.z > 0.f ? 0 : -M_PI;
    +        }
    +
    +        // Rotate all points to the xy plane:
    +        for (auto& vertex : polygon) {
    +            vertex = super_rotation(Pointf3(0,0,1), angle_z, vertex);
    +            vertex = super_rotation(Pointf3(0,1,0), angle_y, vertex);
    +        }
    +        polygon = Slic3r::Geometry::convex_hull(polygon); // To remove the inner points
    +
    +        // Calculate area of the polygon and discard ones that are too small
    +        float area = 0.f;
    +        for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula
    +            area += polygon[i].x*polygon[i+1 < polygon.size() ? i+1 : 0 ].y - polygon[i+1 < polygon.size() ? i+1 : 0].x*polygon[i].y;
    +        area = std::abs(area/2.f);
    +        if (area < 20.f) {
    +            m_planes.erase(m_planes.begin()+(polygon_id--));
    +            continue;
    +        }
    +
    +        // We will shrink the polygon a little bit so it does not touch the object edges:
    +        Pointf3 centroid = std::accumulate(polygon.begin(), polygon.end(), Pointf3(0.f, 0.f, 0.f));
    +        centroid.scale(1.f/polygon.size());
    +        for (auto& vertex : polygon)
    +            vertex = 0.9f*vertex + 0.1f*centroid;
    +
    +        // Polygon is now simple and convex, we'll round the corners to make them look nicer.
    +        // The algorithm takes a vertex, calculates middles of respective sides and moves the vertex
    +        // towards their average (controlled by 'aggressivity'). This is repeated k times.
    +        // In next iterations, the neighbours are not always taken at the middle (to increase the
    +        // rounding effect at the corners, where we need it most).
    +        const unsigned int k = 10; // number of iterations
    +        const float aggressivity = 0.2f;  // agressivity
    +        const unsigned int N = polygon.size();
    +        std::vector> neighbours;
    +        if (k != 0) {
    +            Pointf3s points_out(2*k*N); // vector long enough to store the future vertices
    +            for (unsigned int j=0; jvolumes)
    +        m_source_data.bounding_boxes.push_back(vol->get_convex_hull().bounding_box());
    +    m_source_data.scaling_factor = m_model_object->instances.front()->scaling_factor;
    +    m_source_data.rotation = m_model_object->instances.front()->rotation;
    +}
    +
    +// Check if the bounding boxes of each volume's convex hull is the same as before
    +// and that scaling and rotation has not changed. In that case we don't have to recalculate it.
    +bool GLGizmoFlatten::is_plane_update_necessary() const
    +{
    +    if (m_state != On || !m_model_object || m_model_object->instances.empty())
    +        return false;
    +
    +    if (m_model_object->volumes.size() != m_source_data.bounding_boxes.size()
    +     || m_model_object->instances.front()->scaling_factor != m_source_data.scaling_factor
    +     || m_model_object->instances.front()->rotation != m_source_data.rotation)
    +        return true;
    +
    +    // now compare the bounding boxes:
    +    for (unsigned int i=0; ivolumes.size(); ++i)
    +        if (m_model_object->volumes[i]->get_convex_hull().bounding_box() != m_source_data.bounding_boxes[i])
    +            return true;
    +
    +    return false;
     }
     
     Pointf3 GLGizmoFlatten::get_flattening_normal() const {
    @@ -607,7 +804,5 @@ Pointf3 GLGizmoFlatten::get_flattening_normal() const {
     
     
     
    -
    -
     } // namespace GUI
     } // namespace Slic3r
    diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
    index d1ef6452a1..0cab603eb2 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.hpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.hpp
    @@ -10,6 +10,7 @@ namespace Slic3r {
     
     class BoundingBoxf3;
     class Pointf3;
    +class ModelObject;
     
     namespace GUI {
     
    @@ -160,23 +161,39 @@ private:
         struct PlaneData {
             std::vector vertices;
             Pointf3 normal;
    -        float color[3];
    +    };
    +    struct SourceDataSummary {
    +        std::vector bounding_boxes; // bounding boxes of convex hulls of individual volumes
    +        float scaling_factor;
    +        float rotation;
         };
     
    +    // This holds information to decide whether recalculation is necessary:
    +    SourceDataSummary m_source_data;
    +
         std::vector m_planes;
    +    std::vector m_instances_positions;
    +    mutable std::unique_ptr m_center = nullptr;
    +    const ModelObject* m_model_object = nullptr;
    +    void update_planes();
    +    bool is_plane_update_necessary() const;
     
     public:
         GLGizmoFlatten();
     
    -    void set_flattening_data(std::vector vertices_list);
    +    void set_flattening_data(const ModelObject* model_object);
         Pointf3 get_flattening_normal() const;
     
     protected:
    -    virtual bool on_init();
    -    virtual void on_start_dragging();
    -    virtual void on_update(const Pointf& mouse_pos);
    -    virtual void on_render(const BoundingBoxf3& box) const;
    -    virtual void on_render_for_picking(const BoundingBoxf3& box) const;
    +    bool on_init() override;
    +    void on_start_dragging() override;
    +    void on_update(const Pointf& mouse_pos) override {};
    +    void on_render(const BoundingBoxf3& box) const override;
    +    void on_render_for_picking(const BoundingBoxf3& box) const override;
    +    void on_set_state() override {
    +        if (m_state == On && is_plane_update_necessary())
    +            update_planes();
    +    }
     };
     
     
    
    From 3433e8e3743f8aebea940ae92e3f26fec03c7a22 Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Fri, 17 Aug 2018 15:42:46 +0200
    Subject: [PATCH 099/119] Fixed a few tooltips, changed default value for
     minimal purge on wipe tower
    
    ---
     xs/src/libslic3r/PrintConfig.cpp | 9 +++------
     1 file changed, 3 insertions(+), 6 deletions(-)
    
    diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
    index d8f2d85a63..a78e73fb53 100644
    --- a/xs/src/libslic3r/PrintConfig.cpp
    +++ b/xs/src/libslic3r/PrintConfig.cpp
    @@ -518,7 +518,7 @@ PrintConfigDef::PrintConfigDef()
         def->cli = "filament-minimal-purge-on-wipe-tower=f@";
         def->sidetext = L("mm³");
         def->min = 0;
    -    def->default_value = new ConfigOptionFloats { 5.f };
    +    def->default_value = new ConfigOptionFloats { 15.f };
     
         def = this->add("filament_cooling_final_speed", coFloats);
         def->label = L("Speed of the last cooling move");
    @@ -572,10 +572,7 @@ PrintConfigDef::PrintConfigDef()
     
         def = this->add("filament_type", coStrings);
         def->label = L("Filament type");
    -    def->tooltip = L("If you want to process the output G-code through custom scripts, just list their "
    -                   "absolute paths here. Separate multiple scripts with a semicolon. Scripts will be passed "
    -                   "the absolute path to the G-code file as the first argument, and they can access "
    -                   "the Slic3r config settings by reading environment variables.");
    +    def->tooltip = L("The filament material type for use in custom G-codes.");
         def->cli = "filament_type=s@";
         def->gui_type = "f_enum_open";
         def->gui_flags = "show_value";
    @@ -921,7 +918,7 @@ PrintConfigDef::PrintConfigDef()
     
         def = this->add("remaining_times", coBool);
         def->label = L("Supports remaining times");
    -    def->tooltip = L("Emit M73 P[percent printed] R[remaining time in seconds] at 1 minute"
    +    def->tooltip = L("Emit M73 P[percent printed] R[remaining time in minutes] at 1 minute"
                          " intervals into the G-code to let the firmware show accurate remaining time."
                          " As of now only the Prusa i3 MK3 firmware recognizes M73."
                          " Also the i3 MK3 firmware supports M73 Qxx Sxx for the silent mode.");
    
    From b0dd328fdec094cd072ece2cbfff87dcf07d51c2 Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Mon, 20 Aug 2018 11:27:25 +0200
    Subject: [PATCH 100/119] Lay flat - icons and invalidation improvement
    
    ---
     lib/Slic3r/GUI/Plater.pm                  |   2 +-
     resources/icons/overlay/layflat_hover.png | Bin 0 -> 2410 bytes
     resources/icons/overlay/layflat_off.png   | Bin 0 -> 2864 bytes
     resources/icons/overlay/layflat_on.png    | Bin 0 -> 3816 bytes
     xs/src/libslic3r/TriangleMesh.cpp         |   5 +++++
     xs/src/libslic3r/TriangleMesh.hpp         |   1 +
     xs/src/slic3r/GUI/GLGizmo.cpp             |  23 ++++++++++++++--------
     xs/src/slic3r/GUI/GLGizmo.hpp             |   1 +
     8 files changed, 23 insertions(+), 9 deletions(-)
     create mode 100644 resources/icons/overlay/layflat_hover.png
     create mode 100644 resources/icons/overlay/layflat_off.png
     create mode 100644 resources/icons/overlay/layflat_on.png
    
    diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
    index 8ab749e241..98f31fb8b9 100644
    --- a/lib/Slic3r/GUI/Plater.pm
    +++ b/lib/Slic3r/GUI/Plater.pm
    @@ -143,7 +143,7 @@ sub new {
         my $on_gizmo_rotate = sub {
             my ($angle_z, $angle_y) = @_;
             $self->rotate(rad2deg($angle_z), Z, 'absolute');
    -        $self->rotate(rad2deg($angle_y), Y, 'absolute');
    +        $self->rotate(rad2deg($angle_y), Y, 'absolute') if $angle_y != 0;
         };
     
         # callback to update object's geometry info while using gizmos
    diff --git a/resources/icons/overlay/layflat_hover.png b/resources/icons/overlay/layflat_hover.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..afce81d19ee2a26916a1e7684f1bef13e42e486c
    GIT binary patch
    literal 2410
    zcmV-w36=JVP)004R=004l4008;_004mL004C`008P>0026e000+nl3&F}00002
    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
    
    literal 0
    HcmV?d00001
    
    diff --git a/resources/icons/overlay/layflat_off.png b/resources/icons/overlay/layflat_off.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..70d198112910a86d34a96bd7e536bfd79a75ae5c
    GIT binary patch
    literal 2864
    zcmV-03(xe4P)004R=004l4008;_004mL004C`008P>0026e000+nl3&F}00002
    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[0].x : nullptr;
    +}
    +
     void
     TriangleMesh::require_shared_vertices()
     {
    diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp
    index 6ab52efe25..be151f062b 100644
    --- a/xs/src/libslic3r/TriangleMesh.hpp
    +++ b/xs/src/libslic3r/TriangleMesh.hpp
    @@ -53,6 +53,7 @@ public:
         TriangleMeshPtrs split() const;
         void merge(const TriangleMesh &mesh);
         ExPolygons horizontal_projection() const;
    +    const float* first_vertex() const;
         Polygon convex_hull();
         BoundingBoxf3 bounding_box() const;
         // Returns the bbox of this TriangleMesh transformed by the given matrix
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index f0a8f2e712..2b91afe41d 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -515,15 +515,15 @@ bool GLGizmoFlatten::on_init()
     {
         std::string path = resources_dir() + "/icons/overlay/";
     
    -    std::string filename = path + "scale_off.png";
    +    std::string filename = path + "layflat_off.png";
         if (!m_textures[Off].load_from_file(filename, false))
             return false;
     
    -    filename = path + "scale_hover.png";
    +    filename = path + "layflat_hover.png";
         if (!m_textures[Hover].load_from_file(filename, false))
             return false;
     
    -    filename = path + "scale_on.png";
    +    filename = path + "layflat_on.png";
         if (!m_textures[On].load_from_file(filename, false))
             return false;
     
    @@ -591,12 +591,12 @@ void GLGizmoFlatten::on_render_for_picking(const BoundingBoxf3& box) const
     
     
     // TODO - remove and use Eigen instead
    -static Pointf3 super_rotation(const Pointf3& axis, float angle, const Pointf3& point)
    +static Pointf3 super_rotation(Pointf3 axis, float angle, const Pointf3& point)
     {
    -    float axis_length = axis.distance_to(Pointf3(0.f, 0.f, 0.f));
    -    float x = axis.x / axis_length;
    -    float y = axis.y / axis_length;
    -    float z = axis.z / axis_length;
    +    axis = normalize(axis);
    +    const float& x = axis.x;
    +    const float& y = axis.y;
    +    const float& z = axis.z;
         float s = sin(angle);
         float c = cos(angle);
         float D = 1-c;
    @@ -774,6 +774,8 @@ void GLGizmoFlatten::update_planes()
             m_source_data.bounding_boxes.push_back(vol->get_convex_hull().bounding_box());
         m_source_data.scaling_factor = m_model_object->instances.front()->scaling_factor;
         m_source_data.rotation = m_model_object->instances.front()->rotation;
    +    const float* first_vertex = m_model_object->volumes.front()->get_convex_hull().first_vertex();
    +    m_source_data.mesh_first_point = Pointf3(first_vertex[0], first_vertex[1], first_vertex[3]);
     }
     
     // Check if the bounding boxes of each volume's convex hull is the same as before
    @@ -793,6 +795,11 @@ bool GLGizmoFlatten::is_plane_update_necessary() const
             if (m_model_object->volumes[i]->get_convex_hull().bounding_box() != m_source_data.bounding_boxes[i])
                 return true;
     
    +    const float* first_vertex = m_model_object->volumes.front()->get_convex_hull().first_vertex();
    +    Pointf3 first_point(first_vertex[0], first_vertex[1], first_vertex[2]);
    +    if (first_point != m_source_data.mesh_first_point)
    +        return true;
    +
         return false;
     }
     
    diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
    index 0cab603eb2..2c82f73f37 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.hpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.hpp
    @@ -166,6 +166,7 @@ private:
             std::vector bounding_boxes; // bounding boxes of convex hulls of individual volumes
             float scaling_factor;
             float rotation;
    +        Pointf3 mesh_first_point;
         };
     
         // This holds information to decide whether recalculation is necessary:
    
    From 3b86c57c8f917c3d3ef0d2c65528b398808146eb Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Mon, 20 Aug 2018 12:56:01 +0200
    Subject: [PATCH 101/119] Lay flat gizmo is rendered before the bed, so the
     surfaces are visible from below, and a rotation-related bugfix
    
    ---
     xs/src/slic3r/GUI/GLCanvas3D.cpp | 26 ++++++++++++++++----------
     xs/src/slic3r/GUI/GLCanvas3D.hpp |  8 ++++++--
     xs/src/slic3r/GUI/GLGizmo.cpp    |  5 +++--
     3 files changed, 25 insertions(+), 14 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index 01a8b80f9f..88dd88ebbc 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -1419,22 +1419,27 @@ void GLCanvas3D::Gizmos::set_flattening_data(const ModelObject* model_object)
             reinterpret_cast(it->second)->set_flattening_data(model_object);
     }
     
    -void GLCanvas3D::Gizmos::render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const
    +void GLCanvas3D::Gizmos::render(const GLCanvas3D& canvas, const BoundingBoxf3& box, RenderOrder render_order) const
     {
         if (!m_enabled)
             return;
     
         ::glDisable(GL_DEPTH_TEST);
     
    -    if (box.radius() > 0.0)
    -        _render_current_gizmo(box);
    +    if ((render_order == BeforeBed && dynamic_cast(_get_current()))
    +     || (render_order == AfterBed && !dynamic_cast(_get_current()))) {
    +        if (box.radius() > 0.0)
    +            _render_current_gizmo(box);
    +     }
     
    -    ::glPushMatrix();
    -    ::glLoadIdentity();
    +    if  (render_order == AfterBed) {
    +        ::glPushMatrix();
    +        ::glLoadIdentity();
     
    -    _render_overlay(canvas);
    +        _render_overlay(canvas);
     
    -    ::glPopMatrix();
    +        ::glPopMatrix();
    +    }
     }
     
     void GLCanvas3D::Gizmos::render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const
    @@ -2249,6 +2254,7 @@ void GLCanvas3D::render()
             _render_axes(false);
         }
         _render_objects();
    +    _render_gizmo(Gizmos::RenderOrder::BeforeBed);
         // textured bed needs to be rendered after objects
         if (!is_custom_bed)
         {
    @@ -2258,7 +2264,7 @@ void GLCanvas3D::render()
         _render_cutting_plane();
         _render_warning_texture();
         _render_legend_texture();
    -    _render_gizmo();
    +    _render_gizmo(Gizmos::RenderOrder::AfterBed);
         _render_layer_editing_overlay();
     
         m_canvas->SwapBuffers();
    @@ -3756,9 +3762,9 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const
             ::glDisable(GL_LIGHTING);
     }
     
    -void GLCanvas3D::_render_gizmo() const
    +void GLCanvas3D::_render_gizmo(Gizmos::RenderOrder render_order) const
     {
    -    m_gizmos.render(*this, _selected_volumes_bounding_box());
    +    m_gizmos.render(*this, _selected_volumes_bounding_box(), render_order);
     }
     
     float GLCanvas3D::_get_layers_editing_cursor_z_relative() const
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    index 5f955cce20..f09bd4b209 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    @@ -341,6 +341,10 @@ public:
                 Flatten,
                 Num_Types
             };
    +        enum RenderOrder : unsigned char {
    +            BeforeBed,
    +            AfterBed
    +        };
     
         private:
             bool m_enabled;
    @@ -386,7 +390,7 @@ public:
             void set_flattening_data(const ModelObject* model_object);
             Pointf3 get_flattening_normal() const;
     
    -        void render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const;
    +        void render(const GLCanvas3D& canvas, const BoundingBoxf3& box, RenderOrder render_order) const;
             void render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const;
     
         private:
    @@ -633,7 +637,7 @@ private:
         void _render_legend_texture() const;
         void _render_layer_editing_overlay() const;
         void _render_volumes(bool fake_colors) const;
    -    void _render_gizmo() const;
    +    void _render_gizmo(Gizmos::RenderOrder render_order) const;
     
         float _get_layers_editing_cursor_z_relative() const;
         void _perform_layer_editing_action(wxMouseEvent* evt = nullptr);
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 2b91afe41d..c75c365b2b 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -775,7 +775,7 @@ void GLGizmoFlatten::update_planes()
         m_source_data.scaling_factor = m_model_object->instances.front()->scaling_factor;
         m_source_data.rotation = m_model_object->instances.front()->rotation;
         const float* first_vertex = m_model_object->volumes.front()->get_convex_hull().first_vertex();
    -    m_source_data.mesh_first_point = Pointf3(first_vertex[0], first_vertex[1], first_vertex[3]);
    +    m_source_data.mesh_first_point = Pointf3(first_vertex[0], first_vertex[1], first_vertex[2]);
     }
     
     // Check if the bounding boxes of each volume's convex hull is the same as before
    @@ -788,7 +788,7 @@ bool GLGizmoFlatten::is_plane_update_necessary() const
         if (m_model_object->volumes.size() != m_source_data.bounding_boxes.size()
          || m_model_object->instances.front()->scaling_factor != m_source_data.scaling_factor
          || m_model_object->instances.front()->rotation != m_source_data.rotation)
    -        return true;
    +         return true;
     
         // now compare the bounding boxes:
         for (unsigned int i=0; ivolumes.size(); ++i)
    @@ -805,6 +805,7 @@ bool GLGizmoFlatten::is_plane_update_necessary() const
     
     Pointf3 GLGizmoFlatten::get_flattening_normal() const {
         Pointf3 normal = m_normal;
    +    normal.rotate(-m_model_object->instances.front()->rotation);
         m_normal = Pointf3(0.f, 0.f, 0.f);
         return normal;
     }
    
    From d197a5149ae86800ca84fe62af3d2b42e90c0d92 Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Mon, 20 Aug 2018 13:02:54 +0200
    Subject: [PATCH 102/119] Added a missing header (numeric for std::accumulate)
    
    ---
     xs/src/slic3r/GUI/GLGizmo.cpp | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index c75c365b2b..72bb7ee7ca 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -8,6 +8,7 @@
     #include 
     
     #include 
    +#include 
     
     namespace Slic3r {
     namespace GUI {
    
    From dd1fd66a47f64ca8bfba736728787d2781a983e1 Mon Sep 17 00:00:00 2001
    From: Martin Loidl 
    Date: Sun, 8 Jul 2018 14:32:48 +0200
    Subject: [PATCH 103/119] Added possibility for upload to Duet
    
    Further changes:
    - Added new configuration option Host Type
    - Added abstract base class for future printer hosts
    - Moved location of upload dialog (also made it a little bit more configureable)
    - added possibility to send file via postfield instead a new frame
    ---
     lib/Slic3r.pm                               |   2 +
     lib/Slic3r/GUI/Plater.pm                    |  15 +-
     resources/profiles/PrusaResearch.ini        |   4 +-
     xs/CMakeLists.txt                           |   9 +-
     xs/src/libslic3r/PrintConfig.cpp            |  32 ++-
     xs/src/libslic3r/PrintConfig.hpp            |  27 +-
     xs/src/perlglue.cpp                         |   3 +-
     xs/src/slic3r/GUI/Field.cpp                 |   2 +
     xs/src/slic3r/GUI/GUI.cpp                   |   2 +
     xs/src/slic3r/GUI/OptionsGroup.cpp          |   6 +-
     xs/src/slic3r/GUI/Preset.cpp                |   4 +-
     xs/src/slic3r/GUI/Tab.cpp                   |  63 +++--
     xs/src/slic3r/GUI/Tab.hpp                   |   3 +-
     xs/src/slic3r/Utils/Duet.cpp                | 281 ++++++++++++++++++++
     xs/src/slic3r/Utils/Duet.hpp                |  46 ++++
     xs/src/slic3r/Utils/Http.cpp                |  24 ++
     xs/src/slic3r/Utils/Http.hpp                |   3 +
     xs/src/slic3r/Utils/OctoPrint.cpp           |  78 ++----
     xs/src/slic3r/Utils/OctoPrint.hpp           |   8 +-
     xs/src/slic3r/Utils/PrintHost.hpp           |  31 +++
     xs/src/slic3r/Utils/PrintHostFactory.cpp    |  21 ++
     xs/src/slic3r/Utils/PrintHostFactory.hpp    |  24 ++
     xs/src/slic3r/Utils/PrintHostSendDialog.cpp |  54 ++++
     xs/src/slic3r/Utils/PrintHostSendDialog.hpp |  40 +++
     xs/xsp/Utils_OctoPrint.xsp                  |  13 -
     xs/xsp/Utils_PrintHost.xsp                  |  10 +
     xs/xsp/Utils_PrintHostFactory.xsp           |  13 +
     xs/xsp/my.map                               |   8 +-
     xs/xsp/typemap.xspt                         |   1 +
     29 files changed, 699 insertions(+), 128 deletions(-)
     create mode 100644 xs/src/slic3r/Utils/Duet.cpp
     create mode 100644 xs/src/slic3r/Utils/Duet.hpp
     create mode 100644 xs/src/slic3r/Utils/PrintHost.hpp
     create mode 100644 xs/src/slic3r/Utils/PrintHostFactory.cpp
     create mode 100644 xs/src/slic3r/Utils/PrintHostFactory.hpp
     create mode 100644 xs/src/slic3r/Utils/PrintHostSendDialog.cpp
     create mode 100644 xs/src/slic3r/Utils/PrintHostSendDialog.hpp
     delete mode 100644 xs/xsp/Utils_OctoPrint.xsp
     create mode 100644 xs/xsp/Utils_PrintHost.xsp
     create mode 100644 xs/xsp/Utils_PrintHostFactory.xsp
    
    diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm
    index 0c6c81bb57..5eaa0e522a 100644
    --- a/lib/Slic3r.pm
    +++ b/lib/Slic3r.pm
    @@ -167,6 +167,8 @@ sub thread_cleanup {
         *Slic3r::GUI::PresetHints::DESTROY      = sub {};
         *Slic3r::GUI::TabIface::DESTROY         = sub {};
         *Slic3r::OctoPrint::DESTROY             = sub {};
    +    *Slic3r::Duet::DESTROY                  = sub {};
    +    *Slic3r::PrintHostFactory::DESTROY                  = sub {};
         *Slic3r::PresetUpdater::DESTROY         = sub {};
         return undef;  # this prevents a "Scalars leaked" warning
     }
    diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
    index a0eef72fea..89f803228e 100644
    --- a/lib/Slic3r/GUI/Plater.pm
    +++ b/lib/Slic3r/GUI/Plater.pm
    @@ -53,7 +53,7 @@ sub new {
         my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
         $self->{config} = Slic3r::Config::new_from_defaults_keys([qw(
             bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height
    -        serial_port serial_speed octoprint_host octoprint_apikey octoprint_cafile
    +        serial_port serial_speed host_type print_host printhost_apikey printhost_cafile
             nozzle_diameter single_extruder_multi_material wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width
     	wipe_tower_rotation_angle extruder_colour filament_colour max_print_height printer_model
         )]);
    @@ -1569,7 +1569,7 @@ sub on_export_completed {
                 $message = L("File added to print queue");
                 $do_print = 1;
             } elsif ($self->{send_gcode_file}) {
    -            $message = L("Sending G-code file to the OctoPrint server...");
    +            $message = L("Sending G-code file to the Printer Host ...");
                 $send_gcode = 1;
             } else {
                 $message = L("G-code file exported to ") . $self->{export_gcode_output_file};
    @@ -1585,9 +1585,10 @@ sub on_export_completed {
     
         # Send $self->{send_gcode_file} to OctoPrint.
         if ($send_gcode) {
    -        my $op = Slic3r::OctoPrint->new($self->{config});
    -        if ($op->send_gcode($self->{send_gcode_file})) {
    -            $self->statusbar->SetStatusText(L("OctoPrint upload finished."));
    +        my $host = Slic3r::PrintHostFactory::get_print_host($self->{config});
    +
    +        if ($host->send_gcode($self->{send_gcode_file})) {
    +            $self->statusbar->SetStatusText(L("Upload to host finished."));
             } else {
                 $self->statusbar->SetStatusText("");
             }
    @@ -1914,8 +1915,8 @@ sub on_config_change {
             } elsif ($opt_key eq 'serial_port') {
                 $self->{btn_print}->Show($config->get('serial_port'));
                 $self->Layout;
    -        } elsif ($opt_key eq 'octoprint_host') {
    -            $self->{btn_send_gcode}->Show($config->get('octoprint_host'));
    +        } elsif ($opt_key eq 'print_host') {
    +            $self->{btn_send_gcode}->Show($config->get('print_host'));
                 $self->Layout;
             } elsif ($opt_key eq 'variable_layer_height') {
                 if ($config->get('variable_layer_height') != 1) {
    diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini
    index 32ec800e7f..7e358c77e2 100644
    --- a/resources/profiles/PrusaResearch.ini
    +++ b/resources/profiles/PrusaResearch.ini
    @@ -1007,8 +1007,8 @@ max_layer_height = 0.25
     min_layer_height = 0.07
     max_print_height = 200
     nozzle_diameter = 0.4
    -octoprint_apikey = 
    -octoprint_host = 
    +printhost_apikey = 
    +print_host = 
     printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\n
     printer_settings_id = 
     retract_before_travel = 1
    diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
    index d4306c525c..998d44cb05 100644
    --- a/xs/CMakeLists.txt
    +++ b/xs/CMakeLists.txt
    @@ -251,8 +251,14 @@ add_library(libslic3r_gui STATIC
         ${LIBDIR}/slic3r/Utils/Http.hpp
         ${LIBDIR}/slic3r/Utils/FixModelByWin10.cpp
         ${LIBDIR}/slic3r/Utils/FixModelByWin10.hpp
    +    ${LIBDIR}/slic3r/Utils/PrintHostSendDialog.cpp
    +    ${LIBDIR}/slic3r/Utils/PrintHostSendDialog.hpp
         ${LIBDIR}/slic3r/Utils/OctoPrint.cpp
         ${LIBDIR}/slic3r/Utils/OctoPrint.hpp
    +	${LIBDIR}/slic3r/Utils/Duet.cpp
    +	${LIBDIR}/slic3r/Utils/Duet.hpp
    +	${LIBDIR}/slic3r/Utils/PrintHostFactory.cpp
    +	${LIBDIR}/slic3r/Utils/PrintHostFactory.hpp
         ${LIBDIR}/slic3r/Utils/Bonjour.cpp
         ${LIBDIR}/slic3r/Utils/Bonjour.hpp
         ${LIBDIR}/slic3r/Utils/PresetUpdater.cpp
    @@ -411,7 +417,8 @@ set(XS_XSP_FILES
         ${XSP_DIR}/Surface.xsp
         ${XSP_DIR}/SurfaceCollection.xsp
         ${XSP_DIR}/TriangleMesh.xsp
    -    ${XSP_DIR}/Utils_OctoPrint.xsp
    +	${XSP_DIR}/Utils_PrintHostFactory.xsp
    +	${XSP_DIR}/Utils_PrintHost.xsp
         ${XSP_DIR}/Utils_PresetUpdater.xsp
         ${XSP_DIR}/AppController.xsp
         ${XSP_DIR}/XS.xsp
    diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
    index a78e73fb53..794c276085 100644
    --- a/xs/src/libslic3r/PrintConfig.cpp
    +++ b/xs/src/libslic3r/PrintConfig.cpp
    @@ -1137,24 +1137,36 @@ PrintConfigDef::PrintConfigDef()
         def->cli = "nozzle-diameter=f@";
         def->default_value = new ConfigOptionFloats { 0.5 };
     
    -    def = this->add("octoprint_apikey", coString);
    -    def->label = L("API Key");
    -    def->tooltip = L("Slic3r can upload G-code files to OctoPrint. This field should contain "
    -                   "the API Key required for authentication.");
    +    def = this->add("host_type", coEnum);
    +    def->label = L("Host Type");
    +    def->tooltip = L("Slic3r can upload G-code files to a printer host. This field must contain "
    +                   "the kind of the host.");
    +    def->cli = "host-type=s";
    +    def->enum_keys_map = &ConfigOptionEnum::get_enum_values();
    +    def->enum_values.push_back("octoprint");
    +    def->enum_values.push_back("duet");
    +    def->enum_labels.push_back("OctoPrint");
    +    def->enum_labels.push_back("Duet");
    +    def->default_value = new ConfigOptionEnum(htOctoPrint);
    +
    +    def = this->add("printhost_apikey", coString);
    +    def->label = L("API Key / Password");
    +    def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
    +                   "the API Key or the password required for authentication.");
         def->cli = "octoprint-apikey=s";
         def->default_value = new ConfigOptionString("");
         
    -    def = this->add("octoprint_cafile", coString);
    +    def = this->add("printhost_cafile", coString);
         def->label = "HTTPS CA file";
         def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
                        "If left blank, the default OS CA certificate repository is used.";
         def->cli = "octoprint-cafile=s";
         def->default_value = new ConfigOptionString("");
     
    -    def = this->add("octoprint_host", coString);
    +    def = this->add("print_host", coString);
         def->label = L("Hostname, IP or URL");
    -    def->tooltip = L("Slic3r can upload G-code files to OctoPrint. This field should contain "
    -                   "the hostname, IP address or URL of the OctoPrint instance.");
    +    def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
    +                   "the hostname, IP address or URL of the printer host instance.");
         def->cli = "octoprint-host=s";
         def->default_value = new ConfigOptionString("");
     
    @@ -2107,10 +2119,6 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
             std::ostringstream oss;
             oss << "0x0," << p.value.x << "x0," << p.value.x << "x" << p.value.y << ",0x" << p.value.y;
             value = oss.str();
    -// Maybe one day we will rename octoprint_host to print_host as it has been done in the upstream Slic3r.
    -// Commenting this out fixes github issue #869 for now.
    -//    } else if (opt_key == "octoprint_host" && !value.empty()) {
    -//        opt_key = "print_host";
         } else if ((opt_key == "perimeter_acceleration" && value == "25")
             || (opt_key == "infill_acceleration" && value == "50")) {
             /*  For historical reasons, the world's full of configs having these very low values;
    diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp
    index b18603d877..438e90681f 100644
    --- a/xs/src/libslic3r/PrintConfig.hpp
    +++ b/xs/src/libslic3r/PrintConfig.hpp
    @@ -27,6 +27,10 @@ enum GCodeFlavor {
         gcfSmoothie, gcfNoExtrusion,
     };
     
    +enum PrintHostType {
    +    htOctoPrint, htDuet,
    +};
    +
     enum InfillPattern {
         ipRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
         ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral,
    @@ -61,6 +65,15 @@ template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_
         return keys_map;
     }
     
    +template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() {
    +    static t_config_enum_values keys_map;
    +    if (keys_map.empty()) {
    +        keys_map["octoprint"]       = htOctoPrint;
    +        keys_map["duet"]            = htDuet;
    +    }
    +    return keys_map;
    +}
    +
     template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() {
         static t_config_enum_values keys_map;
         if (keys_map.empty()) {
    @@ -789,18 +802,20 @@ class HostConfig : public StaticPrintConfig
     {
         STATIC_PRINT_CONFIG_CACHE(HostConfig)
     public:
    -    ConfigOptionString              octoprint_host;
    -    ConfigOptionString              octoprint_apikey;
    -    ConfigOptionString              octoprint_cafile;
    +    ConfigOptionEnum host_type;
    +    ConfigOptionString              print_host;
    +    ConfigOptionString              printhost_apikey;
    +    ConfigOptionString              printhost_cafile;
         ConfigOptionString              serial_port;
         ConfigOptionInt                 serial_speed;
         
     protected:
         void initialize(StaticCacheBase &cache, const char *base_ptr)
         {
    -        OPT_PTR(octoprint_host);
    -        OPT_PTR(octoprint_apikey);
    -        OPT_PTR(octoprint_cafile);
    +        OPT_PTR(host_type);
    +        OPT_PTR(print_host);
    +        OPT_PTR(printhost_apikey);
    +        OPT_PTR(printhost_cafile);
             OPT_PTR(serial_port);
             OPT_PTR(serial_speed);
         }
    diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp
    index c8aadc8c35..997938a910 100644
    --- a/xs/src/perlglue.cpp
    +++ b/xs/src/perlglue.cpp
    @@ -64,9 +64,10 @@ REGISTER_CLASS(PresetCollection, "GUI::PresetCollection");
     REGISTER_CLASS(PresetBundle, "GUI::PresetBundle");
     REGISTER_CLASS(TabIface, "GUI::Tab");
     REGISTER_CLASS(PresetUpdater, "PresetUpdater");
    -REGISTER_CLASS(OctoPrint, "OctoPrint");
     REGISTER_CLASS(AppController, "AppController");
     REGISTER_CLASS(PrintController, "PrintController");
    +REGISTER_CLASS(PrintHost, "PrintHost");
    +REGISTER_CLASS(PrintHostFactory, "PrintHostFactory");
     
     SV* ConfigBase__as_hash(ConfigBase* THIS)
     {
    diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp
    index 85fa790a5e..757a18f115 100644
    --- a/xs/src/slic3r/GUI/Field.cpp
    +++ b/xs/src/slic3r/GUI/Field.cpp
    @@ -586,6 +586,8 @@ boost::any& Choice::get_value()
     			m_value = static_cast(ret_enum);
     		else if (m_opt_id.compare("seam_position") == 0)
     			m_value = static_cast(ret_enum);
    +		else if (m_opt_id.compare("host_type") == 0)
    +			m_value = static_cast(ret_enum);
     	}	
     
     	return m_value;
    diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
    index 8cd7ed7768..8555f0b921 100644
    --- a/xs/src/slic3r/GUI/GUI.cpp
    +++ b/xs/src/slic3r/GUI/GUI.cpp
    @@ -604,6 +604,8 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
     				config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value)));
     			else if (opt_key.compare("seam_position") == 0)
     				config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value)));
    +			else if (opt_key.compare("host_type") == 0)
    +				config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value)));
     			}
     			break;
     		case coPoints:{
    diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp
    index d5cc29e19f..a2d6559a9a 100644
    --- a/xs/src/slic3r/GUI/OptionsGroup.cpp
    +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp
    @@ -459,8 +459,12 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
     		else if (opt_key.compare("support_material_pattern") == 0){
     			ret = static_cast(config.option>(opt_key)->value);
     		}
    -		else if (opt_key.compare("seam_position") == 0)
    +		else if (opt_key.compare("seam_position") == 0){
     			ret = static_cast(config.option>(opt_key)->value);
    +		}
    +		else if (opt_key.compare("host_type") == 0){
    +			ret = static_cast(config.option>(opt_key)->value);
    +		}
     	}
     		break;
     	case coPoints:
    diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp
    index 9f51f7b978..8335e48b58 100644
    --- a/xs/src/slic3r/GUI/Preset.cpp
    +++ b/xs/src/slic3r/GUI/Preset.cpp
    @@ -329,8 +329,8 @@ const std::vector& Preset::printer_options()
         static std::vector s_opts;
         if (s_opts.empty()) {
             s_opts = {
    -            "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", 
    -            "octoprint_host", "octoprint_apikey", "octoprint_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
    +            "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "host_type",
    +            "print_host", "printhost_apikey", "printhost_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
                 "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
                 "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction",
                 "cooling_tube_length", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits",
    diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp
    index 7c4322c5a0..13b0ece5f5 100644
    --- a/xs/src/slic3r/GUI/Tab.cpp
    +++ b/xs/src/slic3r/GUI/Tab.cpp
    @@ -5,7 +5,8 @@
     #include "../../libslic3r/Utils.hpp"
     
     #include "slic3r/Utils/Http.hpp"
    -#include "slic3r/Utils/OctoPrint.hpp"
    +#include "slic3r/Utils/PrintHostFactory.hpp"
    +#include "slic3r/Utils/PrintHost.hpp"
     #include "slic3r/Utils/Serial.hpp"
     #include "BonjourDialog.hpp"
     #include "WipeTowerDialog.hpp"
    @@ -1521,10 +1522,12 @@ void TabPrinter::build()
     			optgroup->append_line(line);
     		}
     
    -		optgroup = page->new_optgroup(_(L("OctoPrint upload")));
    +		optgroup = page->new_optgroup(_(L("Printer Host upload")));
     
    -		auto octoprint_host_browse = [this, optgroup] (wxWindow* parent) {
    -			auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
    +		optgroup->append_single_option_line("host_type");
    +
    +		auto printhost_browse = [this, optgroup] (wxWindow* parent) {
    +			auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
     			btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG));
     			auto sizer = new wxBoxSizer(wxHORIZONTAL);
     			sizer->Add(btn);
    @@ -1532,47 +1535,52 @@ void TabPrinter::build()
     			btn->Bind(wxEVT_BUTTON, [this, parent, optgroup](wxCommandEvent e) {
     				BonjourDialog dialog(parent);
     				if (dialog.show_and_lookup()) {
    -					optgroup->set_value("octoprint_host", std::move(dialog.get_selected()), true);
    +					optgroup->set_value("print_host", std::move(dialog.get_selected()), true);
     				}
     			});
     
     			return sizer;
     		};
     
    -		auto octoprint_host_test = [this](wxWindow* parent) {
    -			auto btn = m_octoprint_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), 
    +		auto print_host_test = [this](wxWindow* parent) {
    +			auto btn = m_print_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), 
     				wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
     			btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG));
     			auto sizer = new wxBoxSizer(wxHORIZONTAL);
     			sizer->Add(btn);
     
     			btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {
    -				OctoPrint octoprint(m_config);
    -				wxString msg;
    -				if (octoprint.test(msg)) {
    -					show_info(this, _(L("Connection to OctoPrint works correctly.")), _(L("Success!")));
    -				} else {
    -					const auto text = wxString::Format("%s: %s\n\n%s",
    -						_(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required."))
    -					);
    +				PrintHost *host = PrintHostFactory::get_print_host(m_config);
    +				if (host == NULL) {
    +					const auto text = wxString::Format("%s",
    +						_(L("Could not get a valid Printer Host reference")));
     					show_error(this, text);
    +					return;
     				}
    +				wxString msg;
    +				if (host->test(msg)) {
    +					show_info(this, host->get_test_ok_msg(), _(L("Success!")));
    +				} else {
    +					show_error(this, host->get_test_failed_msg(msg));
    +				}
    +
    +				delete (host);
     			});
     
     			return sizer;
     		};
     
    -		Line host_line = optgroup->create_single_option_line("octoprint_host");
    -		host_line.append_widget(octoprint_host_browse);
    -		host_line.append_widget(octoprint_host_test);
    +		Line host_line = optgroup->create_single_option_line("print_host");
    +		host_line.append_widget(printhost_browse);
    +		host_line.append_widget(print_host_test);
     		optgroup->append_line(host_line);
    -		optgroup->append_single_option_line("octoprint_apikey");
    +		optgroup->append_single_option_line("printhost_apikey");
     
     		if (Http::ca_file_supported()) {
     
    -			Line cafile_line = optgroup->create_single_option_line("octoprint_cafile");
    +			Line cafile_line = optgroup->create_single_option_line("printhost_cafile");
     
    -			auto octoprint_cafile_browse = [this, optgroup] (wxWindow* parent) {
    +			auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) {
     				auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
     				btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG));
     				auto sizer = new wxBoxSizer(wxHORIZONTAL);
    @@ -1582,17 +1590,17 @@ void TabPrinter::build()
     					static const auto filemasks = _(L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*"));
     					wxFileDialog openFileDialog(this, _(L("Open CA certificate file")), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
     					if (openFileDialog.ShowModal() != wxID_CANCEL) {
    -						optgroup->set_value("octoprint_cafile", std::move(openFileDialog.GetPath()), true);
    +						optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true);
     					}
     				});
     
     				return sizer;
     			};
     
    -			cafile_line.append_widget(octoprint_cafile_browse);
    +			cafile_line.append_widget(printhost_cafile_browse);
     			optgroup->append_line(cafile_line);
     
    -			auto octoprint_cafile_hint = [this, optgroup] (wxWindow* parent) {
    +			auto printhost_cafile_hint = [this, optgroup] (wxWindow* parent) {
     				auto txt = new wxStaticText(parent, wxID_ANY, 
     					_(L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate.")));
     				auto sizer = new wxBoxSizer(wxHORIZONTAL);
    @@ -1602,7 +1610,7 @@ void TabPrinter::build()
     
     			Line cafile_hint { "", "" };
     			cafile_hint.full_width = 1;
    -			cafile_hint.widget = std::move(octoprint_cafile_hint);
    +			cafile_hint.widget = std::move(printhost_cafile_hint);
     			optgroup->append_line(cafile_hint);
     
     		}
    @@ -1897,7 +1905,10 @@ void TabPrinter::update(){
     			m_serial_test_btn->Disable();
     	}
     
    -	m_octoprint_host_test_btn->Enable(!m_config->opt_string("octoprint_host").empty());
    +	PrintHost *host = PrintHostFactory::get_print_host(m_config);
    +	m_print_host_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test());
    +	m_printhost_browse_btn->Enable(host->have_auto_discovery());
    +	delete (host);
     	
     	bool have_multiple_extruders = m_extruders_count > 1;
     	get_field("toolchange_gcode")->toggle(have_multiple_extruders);
    diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp
    index 8b4eae7de5..230fe659e5 100644
    --- a/xs/src/slic3r/GUI/Tab.hpp
    +++ b/xs/src/slic3r/GUI/Tab.hpp
    @@ -321,7 +321,8 @@ class TabPrinter : public Tab
     	bool		m_rebuild_kinematics_page = false;
     public:
     	wxButton*	m_serial_test_btn;
    -	wxButton*	m_octoprint_host_test_btn;
    +	wxButton*	m_print_host_test_btn;
    +	wxButton*	m_printhost_browse_btn;
     
     	size_t		m_extruders_count;
     	size_t		m_extruders_count_old = 0;
    diff --git a/xs/src/slic3r/Utils/Duet.cpp b/xs/src/slic3r/Utils/Duet.cpp
    new file mode 100644
    index 0000000000..aabc8eb403
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/Duet.cpp
    @@ -0,0 +1,281 @@
    +#include "Duet.hpp"
    +#include "PrintHostSendDialog.hpp"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#include "libslic3r/PrintConfig.hpp"
    +#include "slic3r/GUI/GUI.hpp"
    +#include "slic3r/GUI/MsgDialog.hpp"
    +#include "Http.hpp"
    +
    +namespace fs = boost::filesystem;
    +namespace pt = boost::property_tree;
    +
    +namespace Slic3r {
    +
    +Duet::Duet(DynamicPrintConfig *config) :
    +	host(config->opt_string("print_host")),
    +	password(config->opt_string("printhost_apikey"))
    +{}
    +
    +bool Duet::test(wxString &msg) const
    +{
    +	bool connected = connect(msg);
    +	if (connected) {
    +		disconnect();
    +	}
    +
    +	return connected;
    +}
    +
    +wxString Duet::get_test_ok_msg () const
    +{
    +	return wxString::Format("%s", _(L("Connection to Duet works correctly.")));
    +}
    +
    +wxString Duet::get_test_failed_msg (wxString &msg) const
    +{
    +	return wxString::Format("%s: %s",
    +						_(L("Could not connect to Duet")), msg);
    +}
    +
    +bool Duet::send_gcode(const std::string &filename) const
    +{
    +	enum { PROGRESS_RANGE = 1000 };
    +
    +	const auto errortitle = _(L("Error while uploading to the Duet"));
    +	fs::path filepath(filename);
    +
    +	PrintHostSendDialog send_dialog(filepath.filename(), true);
    +	if (send_dialog.ShowModal() != wxID_OK) { return false; }
    +
    +	const bool print = send_dialog.print(); 
    +	const auto upload_filepath = send_dialog.filename();
    +	const auto upload_filename = upload_filepath.filename();
    +	const auto upload_parent_path = upload_filepath.parent_path();
    +
    +	wxProgressDialog progress_dialog(
    +	 	_(L("Duet upload")),
    +	 	_(L("Sending G-code file to Duet...")),
    +		PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
    +	progress_dialog.Pulse();
    +
    +	wxString connect_msg;
    +	if (!connect(connect_msg)) {
    +		auto errormsg = wxString::Format("%s: %s", errortitle, connect_msg);
    +		GUI::show_error(&progress_dialog, std::move(errormsg));
    +		return false;
    +	}
    +
    +	bool res = true;
    +
    +
    +	auto upload_cmd = get_upload_url(upload_filepath.string());
    +	BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filename: %2%, path: %3%, print: %4%, command: %5%")
    +		% filepath.string()
    +		% upload_filename.string()
    +		% upload_parent_path.string()
    +		% print
    +		% upload_cmd;
    +
    +	auto http = Http::post(std::move(upload_cmd));
    +	http.postfield_add_file(filename)
    +		.on_complete([&](std::string body, unsigned status) {
    +			BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
    +			progress_dialog.Update(PROGRESS_RANGE);
    +
    +			int err_code = get_err_code_from_body(body);
    +			switch (err_code) {
    +				case 0:
    +					break;
    +				default:
    +					auto msg = format_error(body, L("Unknown error occured"), 0);
    +					GUI::show_error(&progress_dialog, std::move(msg));
    +					res = false;
    +					break;
    +			}
    +
    +			if (err_code == 0 && print) {
    +				wxString errormsg;
    +				res = start_print(errormsg, upload_filepath.string());
    +				if (!res) {
    +					GUI::show_error(&progress_dialog, std::move(errormsg));
    +				}
    +			}
    +		})
    +		.on_error([&](std::string body, std::string error, unsigned status) {
    +			BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
    +			auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status));
    +			GUI::show_error(&progress_dialog, std::move(errormsg));
    +			res = false;
    +		})
    +		.on_progress([&](Http::Progress progress, bool &cancel) {
    +			if (cancel) {
    +				// Upload was canceled
    +				res = false;
    +			} else if (progress.ultotal > 0) {
    +				int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
    +				cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1));    // Cap the value to prevent premature dialog closing
    +			} else {
    +				cancel = !progress_dialog.Pulse();
    +			}
    +		})
    +		.perform_sync();
    +
    +	disconnect();
    +
    +	return res;
    +}
    +
    +bool Duet::have_auto_discovery() const
    +{
    +	return false;
    +}
    +
    +bool Duet::can_test() const
    +{
    +	return true;
    +}
    +
    +bool Duet::connect(wxString &msg) const
    +{
    +	bool res = false;
    +	auto url = get_connect_url();
    +
    +	auto http = Http::get(std::move(url));
    +	http.on_error([&](std::string body, std::string error, unsigned status) {
    +			BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error connecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
    +			msg = format_error(body, error, status);
    +		})
    +		.on_complete([&](std::string body, unsigned) {
    +			BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body;
    +
    +			int err_code = get_err_code_from_body(body);
    +			switch (err_code) {
    +				case 0:
    +					res = true;
    +					break;
    +				case 1:
    +					msg = format_error(body, L("Wrong password"), 0);
    +					break;
    +				case 2:
    +					msg = format_error(body, L("Could not get resources to create a new connection"), 0);
    +					break;
    +				default:
    +					msg = format_error(body, L("Unknown error occured"), 0);
    +					break;
    +			}
    +
    +		})
    +		.perform_sync();
    +
    +	return res;
    +}
    +
    +void Duet::disconnect() const
    +{
    +	auto url =  (boost::format("%1%rr_disconnect")
    +			% get_base_url()).str();
    +
    +	auto http = Http::get(std::move(url));
    +	http.on_error([&](std::string body, std::string error, unsigned status) {
    +		// we don't care about it, if disconnect is not working Duet will disconnect automatically after some time
    +		BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error disconnecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
    +	})
    +	.perform_sync();
    +
    +}
    +
    +std::string Duet::get_upload_url(const std::string &filename) const
    +{
    +	return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%")
    +			% get_base_url()
    +			% filename 
    +			% timestamp_str()).str();
    +}
    +
    +std::string Duet::get_connect_url() const
    +{
    +	return (boost::format("%1%rr_connect?password=%2%&%3%")
    +			% get_base_url()
    +			% (password.empty() ? "reprap" : password)
    +			% timestamp_str()).str();
    +}
    +
    +std::string Duet::get_base_url() const
    +{
    +	if (host.find("http://") == 0 || host.find("https://") == 0) {
    +		if (host.back() == '/') {
    +			return host;
    +		} else {
    +			return (boost::format("%1%/") % host).str();
    +		}
    +	} else {
    +		return (boost::format("http://%1%/") % host).str();
    +	}
    +}
    +
    +std::string Duet::timestamp_str() const
    +{
    +	auto t = std::time(nullptr);
    +    auto tm = *std::localtime(&t);
    +    std::stringstream ss;
    +	ss << "time=" << std::put_time(&tm, "%Y-%d-%mT%H:%M:%S");
    +
    +	return ss.str();
    +}
    +
    +wxString Duet::format_error(const std::string &body, const std::string &error, unsigned status)
    +{
    +	if (status != 0) {
    +		auto wxbody = wxString::FromUTF8(body.data());
    +		return wxString::Format("HTTP %u: %s", status, wxbody);
    +	} else {
    +		return wxString::FromUTF8(error.data());
    +	}
    +}
    +
    +bool Duet::start_print(wxString &msg, const std::string &filename) const {
    +	bool res = false;
    +	auto url = (boost::format("%1%rr_gcode?gcode=M32%%20\"%2%\"")
    +			% get_base_url()
    +			% filename).str();
    +
    +	auto http = Http::get(std::move(url));
    +	http.on_error([&](std::string body, std::string error, unsigned status) {
    +			BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error starting print: %1%, HTTP %2%, body: `%3%`") % error % status % body;
    +			msg = format_error(body, error, status);
    +		})
    +		.on_complete([&](std::string body, unsigned) {
    +			BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body;
    +			res = true;
    +		})
    +		.perform_sync();
    +
    +	return res;
    +}
    +
    +int Duet::get_err_code_from_body(const std::string &body) const
    +{
    +	pt::ptree root;
    +	std::istringstream iss (body); // wrap returned json to istringstream
    +	pt::read_json(iss, root);
    +
    +	return root.get("err", 0);
    +}
    +
    +
    +}
    diff --git a/xs/src/slic3r/Utils/Duet.hpp b/xs/src/slic3r/Utils/Duet.hpp
    new file mode 100644
    index 0000000000..83ba0cbbbc
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/Duet.hpp
    @@ -0,0 +1,46 @@
    +#ifndef slic3r_Duet_hpp_
    +#define slic3r_Duet_hpp_
    +
    +#include 
    +#include 
    +
    +#include "PrintHost.hpp"
    +
    +
    +namespace Slic3r {
    +
    +
    +class DynamicPrintConfig;
    +class Http;
    +
    +class Duet : public PrintHost
    +{
    +public:
    +	Duet(DynamicPrintConfig *config);
    +
    +	bool test(wxString &curl_msg) const;
    +	wxString get_test_ok_msg () const;
    +	wxString get_test_failed_msg (wxString &msg) const;
    +	// Send gcode file to duet, filename is expected to be in UTF-8
    +	bool send_gcode(const std::string &filename) const;
    +	bool have_auto_discovery() const;
    +	bool can_test() const;
    +private:
    +	std::string host;
    +	std::string password;
    +	
    +	std::string get_upload_url(const std::string &filename) const;
    +	std::string get_connect_url() const;
    +	std::string get_base_url() const;
    +	std::string timestamp_str() const;
    +	bool connect(wxString &msg) const;
    +	void disconnect() const;
    +	bool start_print(wxString &msg, const std::string &filename) const;
    +	int get_err_code_from_body(const std::string &body) const;
    +	static wxString format_error(const std::string &body, const std::string &error, unsigned status);
    +};
    +
    +
    +}
    +
    +#endif
    diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp
    index 37eb59a00f..f5407b9fb6 100644
    --- a/xs/src/slic3r/Utils/Http.cpp
    +++ b/xs/src/slic3r/Utils/Http.cpp
    @@ -4,6 +4,7 @@
     #include 
     #include 
     #include 
    +#include 
     #include 
     #include 
     
    @@ -42,6 +43,7 @@ struct Http::priv
     	// Used for storing file streams added as multipart form parts
     	// Using a deque here because unlike vector it doesn't ivalidate pointers on insertion
     	std::deque form_files;
    +	std::string postfields;
     	size_t limit;
     	bool cancel;
     
    @@ -60,6 +62,7 @@ struct Http::priv
     	static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp);
     
     	void form_add_file(const char *name, const fs::path &path, const char* filename);
    +	void postfield_add_file(const fs::path &path);
     
     	std::string curl_error(CURLcode curlcode);
     	std::string body_size_error();
    @@ -187,6 +190,16 @@ void Http::priv::form_add_file(const char *name, const fs::path &path, const cha
     	}
     }
     
    +void Http::priv::postfield_add_file(const fs::path &path)
    +{
    +	std::ifstream f (path.string());
    +	std::string file_content { std::istreambuf_iterator(f), std::istreambuf_iterator() };
    +	if (!postfields.empty()) {
    +		postfields += "&";
    +	}
    +	postfields += file_content;
    +}
    +
     std::string Http::priv::curl_error(CURLcode curlcode)
     {
     	return (boost::format("%1% (%2%)")
    @@ -229,6 +242,11 @@ void Http::priv::http_perform()
     		::curl_easy_setopt(curl, CURLOPT_HTTPPOST, form);
     	}
     
    +	if (!postfields.empty()) {
    +		::curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields.c_str());
    +		::curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, postfields.size());
    +	}
    +
     	CURLcode res = ::curl_easy_perform(curl);
     
     	if (res != CURLE_OK) {
    @@ -338,6 +356,12 @@ Http& Http::form_add_file(const std::string &name, const fs::path &path, const s
     	return *this;
     }
     
    +Http& Http::postfield_add_file(const fs::path &path)
    +{
    +	if (p) { p->postfield_add_file(path);}
    +	return *this;
    +}
    +
     Http& Http::on_complete(CompleteFn fn)
     {
     	if (p) { p->completefn = std::move(fn); }
    diff --git a/xs/src/slic3r/Utils/Http.hpp b/xs/src/slic3r/Utils/Http.hpp
    index ce4e438cad..cf5712d966 100644
    --- a/xs/src/slic3r/Utils/Http.hpp
    +++ b/xs/src/slic3r/Utils/Http.hpp
    @@ -73,6 +73,9 @@ public:
     	// Same as above except also override the file's filename with a custom one
     	Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename);
     
    +	// Add the file as POSTFIELD to the request, this can be used for hosts which do not support multipart requests
    +	Http& postfield_add_file(const boost::filesystem::path &path);
    +
     	// Callback called on HTTP request complete
     	Http& on_complete(CompleteFn fn);
     	// Callback called on an error occuring at any stage of the requests: Url parsing, DNS lookup,
    diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp
    index 97b4123d44..c62f9b55c7 100644
    --- a/xs/src/slic3r/Utils/OctoPrint.cpp
    +++ b/xs/src/slic3r/Utils/OctoPrint.cpp
    @@ -1,21 +1,11 @@
     #include "OctoPrint.hpp"
    +#include "PrintHostSendDialog.hpp"
     
     #include 
    -#include 
     #include 
     #include 
     
    -#include 
    -#include 
    -#include 
    -#include 
    -#include 
    -#include 
    -#include 
    -
     #include "libslic3r/PrintConfig.hpp"
    -#include "slic3r/GUI/GUI.hpp"
    -#include "slic3r/GUI/MsgDialog.hpp"
     #include "Http.hpp"
     
     namespace fs = boost::filesystem;
    @@ -23,47 +13,10 @@ namespace fs = boost::filesystem;
     
     namespace Slic3r {
     
    -
    -struct SendDialog : public GUI::MsgDialog
    -{
    -	wxTextCtrl *txt_filename;
    -	wxCheckBox *box_print;
    -
    -	SendDialog(const fs::path &path) :
    -		MsgDialog(nullptr, _(L("Send G-Code to printer")), _(L("Upload to OctoPrint with the following filename:")), wxID_NONE),
    -		txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())),
    -		box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload"))))
    -	{
    -		auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed.")));
    -		label_dir_hint->Wrap(CONTENT_WIDTH);
    -
    -		content_sizer->Add(txt_filename, 0, wxEXPAND);
    -		content_sizer->Add(label_dir_hint);
    -		content_sizer->AddSpacer(VERT_SPACING);
    -		content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
    -
    -		btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
    -
    -		txt_filename->SetFocus();
    -		wxString stem(path.stem().wstring());
    -		txt_filename->SetSelection(0, stem.Length());
    -
    -		Fit();
    -	}
    -
    -	fs::path filename() const {
    -		return fs::path(txt_filename->GetValue().wx_str());
    -	}
    -
    -	bool print() const { return box_print->GetValue(); }
    -};
    -
    -
    -
     OctoPrint::OctoPrint(DynamicPrintConfig *config) :
    -	host(config->opt_string("octoprint_host")),
    -	apikey(config->opt_string("octoprint_apikey")),
    -	cafile(config->opt_string("octoprint_cafile"))
    +	host(config->opt_string("print_host")),
    +	apikey(config->opt_string("printhost_apikey")),
    +	cafile(config->opt_string("printhost_cafile"))
     {}
     
     bool OctoPrint::test(wxString &msg) const
    @@ -91,6 +44,17 @@ bool OctoPrint::test(wxString &msg) const
     	return res;
     }
     
    +wxString OctoPrint::get_test_ok_msg () const
    +{
    +	return wxString::Format("%s", _(L("Connection to OctoPrint works correctly.")));
    +}
    +
    +wxString OctoPrint::get_test_failed_msg (wxString &msg) const
    +{
    +	return wxString::Format("%s: %s\n\n%s",
    +						_(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required.")));
    +}
    +
     bool OctoPrint::send_gcode(const std::string &filename) const
     {
     	enum { PROGRESS_RANGE = 1000 };
    @@ -98,7 +62,7 @@ bool OctoPrint::send_gcode(const std::string &filename) const
     	const auto errortitle = _(L("Error while uploading to the OctoPrint server"));
     	fs::path filepath(filename);
     
    -	SendDialog send_dialog(filepath.filename());
    +	PrintHostSendDialog send_dialog(filepath.filename(), true);
     	if (send_dialog.ShowModal() != wxID_OK) { return false; }
     
     	const bool print = send_dialog.print();
    @@ -161,6 +125,16 @@ bool OctoPrint::send_gcode(const std::string &filename) const
     	return res;
     }
     
    +bool OctoPrint::have_auto_discovery() const
    +{
    +	return true;
    +}
    +
    +bool OctoPrint::can_test() const
    +{
    +	return true;
    +}
    +
     void OctoPrint::set_auth(Http &http) const
     {
     	http.header("X-Api-Key", apikey);
    diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp
    index 1e2098ae3f..aea2ba58f9 100644
    --- a/xs/src/slic3r/Utils/OctoPrint.hpp
    +++ b/xs/src/slic3r/Utils/OctoPrint.hpp
    @@ -4,6 +4,8 @@
     #include 
     #include 
     
    +#include "PrintHost.hpp"
    +
     
     namespace Slic3r {
     
    @@ -11,14 +13,18 @@ namespace Slic3r {
     class DynamicPrintConfig;
     class Http;
     
    -class OctoPrint
    +class OctoPrint : public PrintHost
     {
     public:
     	OctoPrint(DynamicPrintConfig *config);
     
     	bool test(wxString &curl_msg) const;
    +	wxString get_test_ok_msg () const;
    +	wxString get_test_failed_msg (wxString &msg) const;
     	// Send gcode file to octoprint, filename is expected to be in UTF-8
     	bool send_gcode(const std::string &filename) const;
    +	bool have_auto_discovery() const;
    +	bool can_test() const;
     private:
     	std::string host;
     	std::string apikey;
    diff --git a/xs/src/slic3r/Utils/PrintHost.hpp b/xs/src/slic3r/Utils/PrintHost.hpp
    new file mode 100644
    index 0000000000..2047406356
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/PrintHost.hpp
    @@ -0,0 +1,31 @@
    +#ifndef slic3r_PrintHost_hpp_
    +#define slic3r_PrintHost_hpp_
    +
    +#include 
    +#include 
    +
    +
    +namespace Slic3r {
    +
    +
    +class DynamicPrintConfig;
    +
    +class PrintHost
    +{
    +public:
    +
    +	virtual bool test(wxString &curl_msg) const = 0;
    +	virtual wxString get_test_ok_msg () const = 0;
    +	virtual wxString get_test_failed_msg (wxString &msg) const = 0;
    +	// Send gcode file to print host, filename is expected to be in UTF-8
    +	virtual bool send_gcode(const std::string &filename) const = 0;
    +	virtual bool have_auto_discovery() const = 0;
    +	virtual bool can_test() const = 0;
    +};
    +
    +
    +
    +
    +}
    +
    +#endif
    diff --git a/xs/src/slic3r/Utils/PrintHostFactory.cpp b/xs/src/slic3r/Utils/PrintHostFactory.cpp
    new file mode 100644
    index 0000000000..173c5d743c
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/PrintHostFactory.cpp
    @@ -0,0 +1,21 @@
    +#include "PrintHostFactory.hpp"
    +#include "OctoPrint.hpp"
    +#include "Duet.hpp"
    +
    +#include "libslic3r/PrintConfig.hpp"
    +
    +namespace Slic3r {
    +
    +
    +PrintHost * PrintHostFactory::get_print_host(DynamicPrintConfig *config)
    +{
    +	PrintHostType kind = config->option>("host_type")->value;
    +	if (kind == htOctoPrint) {
    +		return new OctoPrint(config);
    +	} else if (kind == htDuet) {
    +		return new Duet(config);
    +	}
    +	return NULL;
    +}
    +
    +}
    diff --git a/xs/src/slic3r/Utils/PrintHostFactory.hpp b/xs/src/slic3r/Utils/PrintHostFactory.hpp
    new file mode 100644
    index 0000000000..4c9ff2bf24
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/PrintHostFactory.hpp
    @@ -0,0 +1,24 @@
    +#ifndef slic3r_PrintHostFactory_hpp_
    +#define slic3r_PrintHostFactory_hpp_
    +
    +#include 
    +#include 
    +
    +
    +namespace Slic3r {
    +
    +class DynamicPrintConfig;
    +class PrintHost;
    +
    +class PrintHostFactory
    +{
    +public:
    +	PrintHostFactory() {};
    +	~PrintHostFactory() {};
    +	static PrintHost * get_print_host(DynamicPrintConfig *config);
    +};
    +
    +
    +}
    +
    +#endif
    diff --git a/xs/src/slic3r/Utils/PrintHostSendDialog.cpp b/xs/src/slic3r/Utils/PrintHostSendDialog.cpp
    new file mode 100644
    index 0000000000..b1dd86961f
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/PrintHostSendDialog.cpp
    @@ -0,0 +1,54 @@
    +#include "PrintHostSendDialog.hpp"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#include "slic3r/GUI/GUI.hpp"
    +#include "slic3r/GUI/MsgDialog.hpp"
    +
    +
    +namespace fs = boost::filesystem;
    +
    +namespace Slic3r {
    +
    +PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print) :
    +	MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE),
    +	txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())),
    +	box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload")))),
    +	can_start_print(can_start_print)
    +{
    +	auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed.")));
    +	label_dir_hint->Wrap(CONTENT_WIDTH);
    +
    +	content_sizer->Add(txt_filename, 0, wxEXPAND);
    +	content_sizer->Add(label_dir_hint);
    +	content_sizer->AddSpacer(VERT_SPACING);
    +	content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
    +
    +	btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
    +
    +	txt_filename->SetFocus();
    +	wxString stem(path.stem().wstring());
    +	txt_filename->SetSelection(0, stem.Length());
    +
    +	if (!can_start_print) {
    +		box_print->Disable();
    +	}
    +
    +	Fit();
    +}
    +
    +fs::path PrintHostSendDialog::filename() const 
    +{
    +	return fs::path(txt_filename->GetValue().wx_str());
    +}
    +
    +bool PrintHostSendDialog::print() const 
    +{ 
    +	return box_print->GetValue(); }
    +}
    diff --git a/xs/src/slic3r/Utils/PrintHostSendDialog.hpp b/xs/src/slic3r/Utils/PrintHostSendDialog.hpp
    new file mode 100644
    index 0000000000..7d2040d976
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/PrintHostSendDialog.hpp
    @@ -0,0 +1,40 @@
    +#ifndef slic3r_PrintHostSendDialog_hpp_
    +#define slic3r_PrintHostSendDialog_hpp_
    +
    +#include 
    +
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#include "slic3r/GUI/GUI.hpp"
    +#include "slic3r/GUI/MsgDialog.hpp"
    +
    +
    +namespace Slic3r {
    +
    +class PrintHostSendDialog : public GUI::MsgDialog
    +{
    +
    +private:
    +	wxTextCtrl *txt_filename;
    +	wxCheckBox *box_print;
    +	bool can_start_print;
    +
    +public:
    +
    +	PrintHostSendDialog(const boost::filesystem::path &path, bool can_start_print);
    +	boost::filesystem::path filename() const;
    +	bool print() const;
    +};
    +
    +}
    +
    +#endif
    diff --git a/xs/xsp/Utils_OctoPrint.xsp b/xs/xsp/Utils_OctoPrint.xsp
    deleted file mode 100644
    index 28610cb01e..0000000000
    --- a/xs/xsp/Utils_OctoPrint.xsp
    +++ /dev/null
    @@ -1,13 +0,0 @@
    -%module{Slic3r::XS};
    -
    -%{
    -#include 
    -#include "slic3r/Utils/OctoPrint.hpp"
    -%}
    -
    -%name{Slic3r::OctoPrint} class OctoPrint {
    -    OctoPrint(DynamicPrintConfig *config);
    -    ~OctoPrint();
    -
    -    bool send_gcode(std::string filename) const;
    -};
    diff --git a/xs/xsp/Utils_PrintHost.xsp b/xs/xsp/Utils_PrintHost.xsp
    new file mode 100644
    index 0000000000..0c3fea137c
    --- /dev/null
    +++ b/xs/xsp/Utils_PrintHost.xsp
    @@ -0,0 +1,10 @@
    +%module{Slic3r::XS};
    +
    +%{
    +#include 
    +#include "slic3r/Utils/PrintHost.hpp"
    +%}
    +
    +%name{Slic3r::PrintHost} class PrintHost {
    +	bool send_gcode(std::string filename) const;
    +};
    diff --git a/xs/xsp/Utils_PrintHostFactory.xsp b/xs/xsp/Utils_PrintHostFactory.xsp
    new file mode 100644
    index 0000000000..2b083c957d
    --- /dev/null
    +++ b/xs/xsp/Utils_PrintHostFactory.xsp
    @@ -0,0 +1,13 @@
    +%module{Slic3r::XS};
    +
    +%{
    +#include 
    +#include "slic3r/Utils/PrintHostFactory.hpp"
    +%}
    +
    +%name{Slic3r::PrintHostFactory} class PrintHostFactory {
    +    PrintHostFactory();
    +    ~PrintHostFactory();
    +
    +   static PrintHost * get_print_host(DynamicPrintConfig *config);
    +};
    diff --git a/xs/xsp/my.map b/xs/xsp/my.map
    index 4a14f483fc..aefe7b3454 100644
    --- a/xs/xsp/my.map
    +++ b/xs/xsp/my.map
    @@ -239,9 +239,11 @@ Ref 				O_OBJECT_SLIC3R_T
     PresetUpdater*              O_OBJECT_SLIC3R
     Ref          O_OBJECT_SLIC3R_T
     
    -OctoPrint*                  O_OBJECT_SLIC3R
    -Ref              O_OBJECT_SLIC3R_T
    -Clone            O_OBJECT_SLIC3R_T
    +PrintHostFactory*            O_OBJECT_SLIC3R
    +Ref        O_OBJECT_SLIC3R_T
    +Clone      O_OBJECT_SLIC3R_T
    +
    +PrintHost*                   O_OBJECT_SLIC3R
     
     Axis                  T_UV
     ExtrusionLoopRole     T_UV
    diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt
    index b576b1373e..cee75fe261 100644
    --- a/xs/xsp/typemap.xspt
    +++ b/xs/xsp/typemap.xspt
    @@ -270,3 +270,4 @@
     };
     %typemap{AppController*};
     %typemap{PrintController*};
    +%typemap{PrintHost*};
    
    From d4b73701d939f6832d5b86818a318d409a25b508 Mon Sep 17 00:00:00 2001
    From: Martin Loidl 
    Date: Fri, 13 Jul 2018 16:10:55 +0200
    Subject: [PATCH 104/119] some code formatting
    
    ---
     lib/Slic3r.pm                |  2 +-
     xs/CMakeLists.txt            | 12 ++++++------
     xs/src/slic3r/Utils/Duet.cpp |  9 ++++-----
     3 files changed, 11 insertions(+), 12 deletions(-)
    
    diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm
    index 5eaa0e522a..7aacd1fd90 100644
    --- a/lib/Slic3r.pm
    +++ b/lib/Slic3r.pm
    @@ -168,7 +168,7 @@ sub thread_cleanup {
         *Slic3r::GUI::TabIface::DESTROY         = sub {};
         *Slic3r::OctoPrint::DESTROY             = sub {};
         *Slic3r::Duet::DESTROY                  = sub {};
    -    *Slic3r::PrintHostFactory::DESTROY                  = sub {};
    +    *Slic3r::PrintHostFactory::DESTROY      = sub {};
         *Slic3r::PresetUpdater::DESTROY         = sub {};
         return undef;  # this prevents a "Scalars leaked" warning
     }
    diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
    index 998d44cb05..3558b6d3c3 100644
    --- a/xs/CMakeLists.txt
    +++ b/xs/CMakeLists.txt
    @@ -255,10 +255,10 @@ add_library(libslic3r_gui STATIC
         ${LIBDIR}/slic3r/Utils/PrintHostSendDialog.hpp
         ${LIBDIR}/slic3r/Utils/OctoPrint.cpp
         ${LIBDIR}/slic3r/Utils/OctoPrint.hpp
    -	${LIBDIR}/slic3r/Utils/Duet.cpp
    -	${LIBDIR}/slic3r/Utils/Duet.hpp
    -	${LIBDIR}/slic3r/Utils/PrintHostFactory.cpp
    -	${LIBDIR}/slic3r/Utils/PrintHostFactory.hpp
    +    ${LIBDIR}/slic3r/Utils/Duet.cpp
    +    ${LIBDIR}/slic3r/Utils/Duet.hpp
    +    ${LIBDIR}/slic3r/Utils/PrintHostFactory.cpp
    +    ${LIBDIR}/slic3r/Utils/PrintHostFactory.hpp
         ${LIBDIR}/slic3r/Utils/Bonjour.cpp
         ${LIBDIR}/slic3r/Utils/Bonjour.hpp
         ${LIBDIR}/slic3r/Utils/PresetUpdater.cpp
    @@ -417,8 +417,8 @@ set(XS_XSP_FILES
         ${XSP_DIR}/Surface.xsp
         ${XSP_DIR}/SurfaceCollection.xsp
         ${XSP_DIR}/TriangleMesh.xsp
    -	${XSP_DIR}/Utils_PrintHostFactory.xsp
    -	${XSP_DIR}/Utils_PrintHost.xsp
    +    ${XSP_DIR}/Utils_PrintHostFactory.xsp
    +    ${XSP_DIR}/Utils_PrintHost.xsp
         ${XSP_DIR}/Utils_PresetUpdater.xsp
         ${XSP_DIR}/AppController.xsp
         ${XSP_DIR}/XS.xsp
    diff --git a/xs/src/slic3r/Utils/Duet.cpp b/xs/src/slic3r/Utils/Duet.cpp
    index aabc8eb403..86573ff300 100644
    --- a/xs/src/slic3r/Utils/Duet.cpp
    +++ b/xs/src/slic3r/Utils/Duet.cpp
    @@ -82,7 +82,6 @@ bool Duet::send_gcode(const std::string &filename) const
     
     	bool res = true;
     
    -
     	auto upload_cmd = get_upload_url(upload_filepath.string());
     	BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filename: %2%, path: %3%, print: %4%, command: %5%")
     		% filepath.string()
    @@ -196,7 +195,6 @@ void Duet::disconnect() const
     		BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error disconnecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
     	})
     	.perform_sync();
    -
     }
     
     std::string Duet::get_upload_url(const std::string &filename) const
    @@ -231,8 +229,8 @@ std::string Duet::get_base_url() const
     std::string Duet::timestamp_str() const
     {
     	auto t = std::time(nullptr);
    -    auto tm = *std::localtime(&t);
    -    std::stringstream ss;
    +	auto tm = *std::localtime(&t);
    +	std::stringstream ss;
     	ss << "time=" << std::put_time(&tm, "%Y-%d-%mT%H:%M:%S");
     
     	return ss.str();
    @@ -248,7 +246,8 @@ wxString Duet::format_error(const std::string &body, const std::string &error, u
     	}
     }
     
    -bool Duet::start_print(wxString &msg, const std::string &filename) const {
    +bool Duet::start_print(wxString &msg, const std::string &filename) const 
    +{
     	bool res = false;
     	auto url = (boost::format("%1%rr_gcode?gcode=M32%%20\"%2%\"")
     			% get_base_url()
    
    From ee9f7eaef69cbb84e3f5b54e6b624e317d16f7a3 Mon Sep 17 00:00:00 2001
    From: Martin Loidl 
    Date: Mon, 20 Aug 2018 20:19:22 +0200
    Subject: [PATCH 105/119] Host upload backwards compatibility
    
    * Added legacy code to preserve backwards compatibility
    * renamed some cli option names  to better match option names
    ---
     resources/profiles/PrusaResearch.ini |  4 ++--
     xs/src/libslic3r/PrintConfig.cpp     | 12 +++++++++---
     2 files changed, 11 insertions(+), 5 deletions(-)
    
    diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini
    index 7e358c77e2..32ec800e7f 100644
    --- a/resources/profiles/PrusaResearch.ini
    +++ b/resources/profiles/PrusaResearch.ini
    @@ -1007,8 +1007,8 @@ max_layer_height = 0.25
     min_layer_height = 0.07
     max_print_height = 200
     nozzle_diameter = 0.4
    -printhost_apikey = 
    -print_host = 
    +octoprint_apikey = 
    +octoprint_host = 
     printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\n
     printer_settings_id = 
     retract_before_travel = 1
    diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
    index 794c276085..943db2a302 100644
    --- a/xs/src/libslic3r/PrintConfig.cpp
    +++ b/xs/src/libslic3r/PrintConfig.cpp
    @@ -1153,21 +1153,21 @@ PrintConfigDef::PrintConfigDef()
         def->label = L("API Key / Password");
         def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
                        "the API Key or the password required for authentication.");
    -    def->cli = "octoprint-apikey=s";
    +    def->cli = "printhost-apikey=s";
         def->default_value = new ConfigOptionString("");
         
         def = this->add("printhost_cafile", coString);
         def->label = "HTTPS CA file";
         def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
                        "If left blank, the default OS CA certificate repository is used.";
    -    def->cli = "octoprint-cafile=s";
    +    def->cli = "printhost-cafile=s";
         def->default_value = new ConfigOptionString("");
     
         def = this->add("print_host", coString);
         def->label = L("Hostname, IP or URL");
         def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
                        "the hostname, IP address or URL of the printer host instance.");
    -    def->cli = "octoprint-host=s";
    +    def->cli = "print-host=s";
         def->default_value = new ConfigOptionString("");
     
         def = this->add("only_retract_when_crossing_perimeters", coBool);
    @@ -2129,6 +2129,12 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
         } else if (opt_key == "support_material_pattern" && value == "pillars") {
             // Slic3r PE does not support the pillars. They never worked well.
             value = "rectilinear";
    +    } else if (opt_key == "octoprint_host") {
    +        opt_key = "print_host";
    +    } else if (opt_key == "octoprint_cafile") {
    +        opt_key = "printhost_cafile";
    +    } else if (opt_key == "octoprint_apikey") {
    +        opt_key = "printhost_apikey";
         }
         
         // Ignore the following obsolete configuration keys:
    
    From 28c8e176b56e3fbff11a1f7b7f07bc87bcc252b4 Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Tue, 21 Aug 2018 14:36:24 +0200
    Subject: [PATCH 106/119] Yet another experimental parameter to adjust the
     initial loading speed of a newly loaded filament
    
    ---
     xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp |  9 +++++++--
     xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp |  4 +++-
     xs/src/libslic3r/Print.cpp                  |  2 ++
     xs/src/libslic3r/PrintConfig.cpp            |  8 ++++++++
     xs/src/libslic3r/PrintConfig.hpp            |  2 ++
     xs/src/slic3r/GUI/Preset.cpp                | 13 ++++++-------
     xs/src/slic3r/GUI/Tab.cpp                   |  5 +++--
     7 files changed, 31 insertions(+), 12 deletions(-)
    
    diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
    index de1f9a59b6..42c06252b9 100644
    --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
    +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
    @@ -879,10 +879,15 @@ void WipeTowerPrusaMM::toolchange_Load(
     
         writer.append("; CP TOOLCHANGE LOAD\n")
     		  .suppress_preview()
    -		  .load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed)  // Acceleration
    +		  /*.load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed)  // Acceleration
     		  .load_move_x_advanced(oldx,          0.5f * edist,        m_filpar[m_current_tool].loading_speed)  // Fast phase
     		  .load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed)  // Slowing down
    -		  .load_move_x_advanced(oldx,          0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed)  // Super slow
    +		  .load_move_x_advanced(oldx,          0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed)  // Super slow*/
    +
    +          .load(0.2f * edist, 60.f * m_filpar[m_current_tool].loading_speed_start)
    +          .load_move_x_advanced(turning_point, 0.7f * edist,        m_filpar[m_current_tool].loading_speed)  // Fast phase
    +		  .load_move_x_advanced(oldx,          0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed)  // Super slow*/
    +
               .travel(oldx, writer.y()) // in case last move was shortened to limit x feedrate
     		  .resume_preview();
     
    diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
    index 4b96ce17c0..305dbc40a8 100644
    --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
    +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
    @@ -65,7 +65,7 @@ public:
     
     
     	// Set the extruder properties.
    -	void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed,
    +	void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed, float loading_speed_start,
                           float unloading_speed, float unloading_speed_start, float delay, int cooling_moves,
                           float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float nozzle_diameter)
     	{
    @@ -76,6 +76,7 @@ public:
             m_filpar[idx].temperature = temp;
             m_filpar[idx].first_layer_temperature = first_layer_temp;
             m_filpar[idx].loading_speed = loading_speed;
    +        m_filpar[idx].loading_speed_start = loading_speed_start;
             m_filpar[idx].unloading_speed = unloading_speed;
             m_filpar[idx].unloading_speed_start = unloading_speed_start;
             m_filpar[idx].delay = delay;
    @@ -217,6 +218,7 @@ private:
             int  			    temperature = 0;
             int  			    first_layer_temperature = 0;
             float               loading_speed = 0.f;
    +        float               loading_speed_start = 0.f;
             float               unloading_speed = 0.f;
             float               unloading_speed_start = 0.f;
             float               delay = 0.f ;
    diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp
    index 4154378ec2..bd14837d92 100644
    --- a/xs/src/libslic3r/Print.cpp
    +++ b/xs/src/libslic3r/Print.cpp
    @@ -200,6 +200,7 @@ bool Print::invalidate_state_by_config_options(const std::vectorconfig.temperature.get_at(i),
                 this->config.first_layer_temperature.get_at(i),
                 this->config.filament_loading_speed.get_at(i),
    +            this->config.filament_loading_speed_start.get_at(i),
                 this->config.filament_unloading_speed.get_at(i),
                 this->config.filament_unloading_speed_start.get_at(i),
                 this->config.filament_toolchange_delay.get_at(i),
    diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
    index cd933deaf6..860283fbdf 100644
    --- a/xs/src/libslic3r/PrintConfig.cpp
    +++ b/xs/src/libslic3r/PrintConfig.cpp
    @@ -473,6 +473,14 @@ PrintConfigDef::PrintConfigDef()
         def->min = 0;
         def->default_value = new ConfigOptionFloats { 28. };
     
    +    def = this->add("filament_loading_speed_start", coFloats);
    +    def->label = L("EXPERIMENTAL: Loading speed at the start");
    +    def->tooltip = L("Speed used at the very beginning of loading phase. ");
    +    def->sidetext = L("mm/s");
    +    def->cli = "filament-loading-speed-start=f@";
    +    def->min = 0;
    +    def->default_value = new ConfigOptionFloats { 9. };
    +
         def = this->add("filament_unloading_speed", coFloats);
         def->label = L("Unloading speed");
         def->tooltip = L("Speed used for unloading the filament on the wipe tower (does not affect "
    diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp
    index edf7756e81..fd5392cab8 100644
    --- a/xs/src/libslic3r/PrintConfig.hpp
    +++ b/xs/src/libslic3r/PrintConfig.hpp
    @@ -528,6 +528,7 @@ public:
         ConfigOptionFloats              filament_cost;
         ConfigOptionFloats              filament_max_volumetric_speed;
         ConfigOptionFloats              filament_loading_speed;
    +    ConfigOptionFloats              filament_loading_speed_start;
         ConfigOptionFloats              filament_load_time;
         ConfigOptionFloats              filament_unloading_speed;
         ConfigOptionFloats              filament_unloading_speed_start;
    @@ -595,6 +596,7 @@ protected:
             OPT_PTR(filament_cost);
             OPT_PTR(filament_max_volumetric_speed);
             OPT_PTR(filament_loading_speed);
    +        OPT_PTR(filament_loading_speed_start);
             OPT_PTR(filament_load_time);
             OPT_PTR(filament_unloading_speed);
             OPT_PTR(filament_unloading_speed_start);
    diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp
    index ba27bea8a4..abf7e7d43c 100644
    --- a/xs/src/slic3r/GUI/Preset.cpp
    +++ b/xs/src/slic3r/GUI/Preset.cpp
    @@ -313,13 +313,12 @@ const std::vector& Preset::filament_options()
     {    
         static std::vector s_opts {
             "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
    -        "extrusion_multiplier", "filament_density", "filament_cost", 
    -        "filament_loading_speed", "filament_load_time", "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time",
    -        "filament_toolchange_delay", "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters",
    -        "filament_minimal_purge_on_wipe_tower", "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature",
    -        "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time",
    -        "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition",
    -        "inherits"
    +        "extrusion_multiplier", "filament_density", "filament_cost", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time",
    +        "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves",
    +        "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower",
    +        "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
    +        "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
    +        "start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition", "inherits"
         };
         return s_opts;
     }
    diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp
    index d3307f1f59..081c1d2494 100644
    --- a/xs/src/slic3r/GUI/Tab.cpp
    +++ b/xs/src/slic3r/GUI/Tab.cpp
    @@ -1290,9 +1290,10 @@ void TabFilament::build()
     		optgroup->append_line(line);
     
             optgroup = page->new_optgroup(_(L("Toolchange parameters with single extruder MM printers")));
    -		optgroup->append_single_option_line("filament_loading_speed");
    -        optgroup->append_single_option_line("filament_unloading_speed");
    +		optgroup->append_single_option_line("filament_loading_speed_start");
    +        optgroup->append_single_option_line("filament_loading_speed");
             optgroup->append_single_option_line("filament_unloading_speed_start");
    +        optgroup->append_single_option_line("filament_unloading_speed");
     		optgroup->append_single_option_line("filament_load_time");
     		optgroup->append_single_option_line("filament_unload_time");
             optgroup->append_single_option_line("filament_toolchange_delay");
    
    From 86b67bbd4282016fbbbbc94306e37eada582daf1 Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Tue, 21 Aug 2018 15:40:11 +0200
    Subject: [PATCH 107/119] Lay flat - rotation is now done in one go directly
     about the necessary axis
    
    ---
     lib/Slic3r/GUI/Plater.pm          | 56 +++++++++++++++++++++----------
     xs/src/libslic3r/Model.cpp        |  2 +-
     xs/src/libslic3r/Model.hpp        |  2 +-
     xs/src/libslic3r/TriangleMesh.cpp | 11 ++++++
     xs/src/libslic3r/TriangleMesh.hpp |  1 +
     xs/src/slic3r/GUI/GLCanvas3D.cpp  |  8 ++---
     xs/src/slic3r/GUI/GLGizmo.cpp     |  6 ++--
     xs/xsp/Model.xsp                  |  3 +-
     8 files changed, 62 insertions(+), 27 deletions(-)
    
    diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
    index 98f31fb8b9..7000d16d77 100644
    --- a/lib/Slic3r/GUI/Plater.pm
    +++ b/lib/Slic3r/GUI/Plater.pm
    @@ -140,10 +140,16 @@ sub new {
         };
         
         # callback to react to gizmo rotate
    +    # omitting last three parameters means rotation around Z
    +    # otherwise they are the components of the rotation axis vector
         my $on_gizmo_rotate = sub {
    -        my ($angle_z, $angle_y) = @_;
    -        $self->rotate(rad2deg($angle_z), Z, 'absolute');
    -        $self->rotate(rad2deg($angle_y), Y, 'absolute') if $angle_y != 0;
    +        my ($angle, $axis_x, $axis_y, $axis_z) = @_;
    +	if (!defined $axis_x) {
    +            $self->rotate(rad2deg($angle), Z, 'absolute');
    +        }
    +        else {
    +            $self->rotate(rad2deg($angle), undef, 'absolute', $axis_x, $axis_y, $axis_z) if $angle != 0;
    +        }
         };
     
         # callback to update object's geometry info while using gizmos
    @@ -1031,28 +1037,40 @@ sub _get_number_from_user {
     }
     
     sub rotate {
    -    my ($self, $angle, $axis, $relative_key) = @_;
    +    my ($self, $angle, $axis, $relative_key, $axis_x, $axis_y, $axis_z) = @_;
         $relative_key //= 'absolute'; # relative or absolute coordinates
    -    $axis //= Z; # angle is in degrees
    -
    +    $axis_x //= 0;
    +    $axis_y //= 0;
    +    $axis_z //= 0;
         my $relative = $relative_key eq 'relative';    
    -    
    +
         my ($obj_idx, $object) = $self->selected_object;
         return if !defined $obj_idx;
    -    
    +
         my $model_object = $self->{model}->objects->[$obj_idx];
         my $model_instance = $model_object->instances->[0];
    -        
    +
         if (!defined $angle) {
             my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z';
             my $default = $axis == Z ? rad2deg($model_instance->rotation) : 0;
             $angle = $self->_get_number_from_user(L("Enter the rotation angle:"), L("Rotate around ").$axis_name.(" axis"), L("Invalid rotation angle entered"), $default);
             return if $angle eq '';
         }
    +
    +    # Let's calculate vector of rotation axis (if we don't have it already)
    +    # The minus is there so that the direction is the same as was established
    +    if (defined $axis) {
    +        if ($axis == X) {
    +            $axis_x = -1;
    +        }
    +        if ($axis == Y) {
    +            $axis_y = -1;
    +        }
    +    }
         
         $self->stop_background_process;
         
    -    if ($axis == Z) {
    +    if (defined $axis && $axis == Z) {
             my $new_angle = deg2rad($angle);
             foreach my $inst (@{ $model_object->instances }) {
                 my $rotation = ($relative ? $inst->rotation : 0.) + $new_angle;
    @@ -1067,13 +1085,15 @@ sub rotate {
             }
     #        $object->transform_thumbnail($self->{model}, $obj_idx);
         } else {
    -        # rotation around X and Y needs to be performed on mesh
    -        # so we first apply any Z rotation
    -        if ($model_instance->rotation != 0) {
    -            $model_object->rotate($model_instance->rotation, Z);
    -            $_->set_rotation(0) for @{ $model_object->instances };
    +        if (defined $axis) {
    +            # rotation around X and Y needs to be performed on mesh
    +            # so we first apply any Z rotation
    +            if ($model_instance->rotation != 0) {
    +                $model_object->rotate($model_instance->rotation, Slic3r::Pointf3->new(0, 0, -1));
    +                $_->set_rotation(0) for @{ $model_object->instances };
    +            }
             }
    -        $model_object->rotate(deg2rad($angle), $axis);
    +        $model_object->rotate(deg2rad($angle), Slic3r::Pointf3->new($axis_x, $axis_y, $axis_z));
             
     #        # realign object to Z = 0
     #        $model_object->center_around_origin;
    @@ -1099,7 +1119,7 @@ sub mirror {
         
         # apply Z rotation before mirroring
         if ($model_instance->rotation != 0) {
    -        $model_object->rotate($model_instance->rotation, Z);
    +        $model_object->rotate($model_instance->rotation, Slic3r::Pointf3->new(0, 0, 1));
             $_->set_rotation(0) for @{ $model_object->instances };
         }
         
    @@ -1146,7 +1166,7 @@ sub changescale {
             
             # apply Z rotation before scaling
             if ($model_instance->rotation != 0) {
    -            $model_object->rotate($model_instance->rotation, Z);
    +            $model_object->rotate($model_instance->rotation, Slic3r::Pointf3->new(0, 0, 1));
                 $_->set_rotation(0) for @{ $model_object->instances };
             }
             
    diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp
    index 23d4477487..09b515c2fb 100644
    --- a/xs/src/libslic3r/Model.cpp
    +++ b/xs/src/libslic3r/Model.cpp
    @@ -725,7 +725,7 @@ void ModelObject::scale(const Pointf3 &versor)
         this->invalidate_bounding_box();
     }
     
    -void ModelObject::rotate(float angle, const Axis &axis)
    +void ModelObject::rotate(float angle, const Pointf3& axis)
     {
         for (ModelVolume *v : this->volumes)
         {
    diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp
    index 23af9fb1c4..dadd515dea 100644
    --- a/xs/src/libslic3r/Model.hpp
    +++ b/xs/src/libslic3r/Model.hpp
    @@ -120,7 +120,7 @@ public:
         void translate(const Vectorf3 &vector) { this->translate(vector.x, vector.y, vector.z); }
         void translate(coordf_t x, coordf_t y, coordf_t z);
         void scale(const Pointf3 &versor);
    -    void rotate(float angle, const Axis &axis);
    +    void rotate(float angle, const Pointf3& axis);
         void transform(const float* matrix3x4);
         void mirror(const Axis &axis);
         size_t materials_count() const;
    diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp
    index 008679e6c5..4c45680b67 100644
    --- a/xs/src/libslic3r/TriangleMesh.cpp
    +++ b/xs/src/libslic3r/TriangleMesh.cpp
    @@ -324,6 +324,17 @@ void TriangleMesh::translate(float x, float y, float z)
         stl_invalidate_shared_vertices(&this->stl);
     }
     
    +void TriangleMesh::rotate(float angle, Pointf3 axis)
    +{
    +    if (angle == 0.f)
    +        return;
    +
    +    axis = normalize(axis);
    +    Eigen::Transform m = Eigen::Transform::Identity();
    +    m.rotate(Eigen::AngleAxisf(angle, Eigen::Vector3f(axis.x, axis.y, axis.z)));
    +    stl_transform(&stl, (float*)m.data());
    +}
    +
     void TriangleMesh::rotate(float angle, const Axis &axis)
     {
         if (angle == 0.f)
    diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp
    index be151f062b..72e541afcf 100644
    --- a/xs/src/libslic3r/TriangleMesh.hpp
    +++ b/xs/src/libslic3r/TriangleMesh.hpp
    @@ -40,6 +40,7 @@ public:
         void scale(const Pointf3 &versor);
         void translate(float x, float y, float z);
         void rotate(float angle, const Axis &axis);
    +    void rotate(float angle, Pointf3 axis);
         void rotate_x(float angle);
         void rotate_y(float angle);
         void rotate_z(float angle);
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index 88dd88ebbc..ea9fd90864 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -2805,9 +2805,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                     // Rotate the object so the normal points downward:
                     Pointf3 normal = m_gizmos.get_flattening_normal();
                     if (normal.x != 0.f || normal.y != 0.f || normal.z != 0.f) {
    -                    float angle_z = -atan2(normal.y, normal.x);
    -                    float angle_y = M_PI - atan2(normal.x*cos(angle_z)-normal.y*sin(angle_z), normal.z);
    -                    m_on_gizmo_rotate_callback.call((double)angle_z, (double)angle_y);
    +                    Pointf3 axis = normal.z > 0.999f ? Pointf3(1, 0, 0) : cross(normal, Pointf3(0.f, 0.f, -1.f));
    +                    float angle = -acos(-normal.z);
    +                    m_on_gizmo_rotate_callback.call(angle, axis.x, axis.y, axis.z);
                     }
                 }
             }
    @@ -3093,7 +3093,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 }
                 case Gizmos::Rotate:
                 {
    -                m_on_gizmo_rotate_callback.call((double)m_gizmos.get_angle_z(), 0.);
    +                m_on_gizmo_rotate_callback.call((double)m_gizmos.get_angle_z());
                     break;
                 }
                 default:
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 72bb7ee7ca..fd4d205fb3 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -706,12 +706,14 @@ void GLGizmoFlatten::update_planes()
             }
             polygon = Slic3r::Geometry::convex_hull(polygon); // To remove the inner points
     
    -        // Calculate area of the polygon and discard ones that are too small
    +        // We will calculate area of the polygon and discard ones that are too small
    +        // The limit is more forgiving in case the normal is in the direction of the coordinate axes
    +        const float minimal_area = (std::abs(normal.x) > 0.999f || std::abs(normal.y) > 0.999f || std::abs(normal.z) > 0.999f) ? 1.f : 20.f;
             float area = 0.f;
             for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula
                 area += polygon[i].x*polygon[i+1 < polygon.size() ? i+1 : 0 ].y - polygon[i+1 < polygon.size() ? i+1 : 0].x*polygon[i].y;
             area = std::abs(area/2.f);
    -        if (area < 20.f) {
    +        if (area < minimal_area) {
                 m_planes.erase(m_planes.begin()+(polygon_id--));
                 continue;
             }
    diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp
    index 25c26c3804..1829632572 100644
    --- a/xs/xsp/Model.xsp
    +++ b/xs/xsp/Model.xsp
    @@ -301,7 +301,8 @@ ModelMaterial::attributes()
         void translate(double x, double y, double z);
         void scale_xyz(Pointf3* versor)
             %code{% THIS->scale(*versor); %};
    -    void rotate(float angle, Axis axis);
    +    void rotate(float angle, Pointf3* axis)
    +        %code{% THIS->rotate(angle, *axis); %};
         void mirror(Axis axis);
         
         Model* cut(double z)
    
    From 0c984c75841f2f691af5a73c070ba9d2378bb634 Mon Sep 17 00:00:00 2001
    From: Vojtech Kral 
    Date: Tue, 21 Aug 2018 11:10:32 +0200
    Subject: [PATCH 108/119] Print host bugfixes / refactoring
    
    ---
     lib/Slic3r.pm                                 |  1 -
     lib/Slic3r/GUI/Plater.pm                      |  2 +-
     xs/CMakeLists.txt                             |  5 ++-
     xs/src/libslic3r/PrintConfig.cpp              |  4 ---
     xs/src/perlglue.cpp                           |  1 -
     xs/src/slic3r/GUI/Tab.cpp                     | 18 +++++-----
     xs/src/slic3r/Utils/Duet.cpp                  | 35 +++++++++----------
     xs/src/slic3r/Utils/Duet.hpp                  |  5 +--
     xs/src/slic3r/Utils/Http.cpp                  | 17 ++++-----
     xs/src/slic3r/Utils/Http.hpp                  |  6 ++--
     xs/src/slic3r/Utils/OctoPrint.cpp             |  4 ++-
     xs/src/slic3r/Utils/OctoPrint.hpp             |  3 +-
     .../{PrintHostFactory.cpp => PrintHost.cpp}   |  8 +++--
     xs/src/slic3r/Utils/PrintHost.hpp             |  6 +++-
     xs/src/slic3r/Utils/PrintHostFactory.hpp      | 24 -------------
     xs/src/slic3r/Utils/PrintHostSendDialog.cpp   |  4 +--
     xs/src/slic3r/Utils/PrintHostSendDialog.hpp   |  2 --
     xs/xsp/Utils_PrintHost.xsp                    |  2 ++
     xs/xsp/Utils_PrintHostFactory.xsp             | 13 -------
     xs/xsp/my.map                                 |  6 +---
     20 files changed, 61 insertions(+), 105 deletions(-)
     rename xs/src/slic3r/Utils/{PrintHostFactory.cpp => PrintHost.cpp} (73%)
     delete mode 100644 xs/src/slic3r/Utils/PrintHostFactory.hpp
     delete mode 100644 xs/xsp/Utils_PrintHostFactory.xsp
    
    diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm
    index 7aacd1fd90..46627311ff 100644
    --- a/lib/Slic3r.pm
    +++ b/lib/Slic3r.pm
    @@ -168,7 +168,6 @@ sub thread_cleanup {
         *Slic3r::GUI::TabIface::DESTROY         = sub {};
         *Slic3r::OctoPrint::DESTROY             = sub {};
         *Slic3r::Duet::DESTROY                  = sub {};
    -    *Slic3r::PrintHostFactory::DESTROY      = sub {};
         *Slic3r::PresetUpdater::DESTROY         = sub {};
         return undef;  # this prevents a "Scalars leaked" warning
     }
    diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
    index 89f803228e..dbdf0be274 100644
    --- a/lib/Slic3r/GUI/Plater.pm
    +++ b/lib/Slic3r/GUI/Plater.pm
    @@ -1585,7 +1585,7 @@ sub on_export_completed {
     
         # Send $self->{send_gcode_file} to OctoPrint.
         if ($send_gcode) {
    -        my $host = Slic3r::PrintHostFactory::get_print_host($self->{config});
    +        my $host = Slic3r::PrintHost::get_print_host($self->{config});
     
             if ($host->send_gcode($self->{send_gcode_file})) {
                 $self->statusbar->SetStatusText(L("Upload to host finished."));
    diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
    index 3558b6d3c3..be7b57b72b 100644
    --- a/xs/CMakeLists.txt
    +++ b/xs/CMakeLists.txt
    @@ -257,8 +257,8 @@ add_library(libslic3r_gui STATIC
         ${LIBDIR}/slic3r/Utils/OctoPrint.hpp
         ${LIBDIR}/slic3r/Utils/Duet.cpp
         ${LIBDIR}/slic3r/Utils/Duet.hpp
    -    ${LIBDIR}/slic3r/Utils/PrintHostFactory.cpp
    -    ${LIBDIR}/slic3r/Utils/PrintHostFactory.hpp
    +    ${LIBDIR}/slic3r/Utils/PrintHost.cpp
    +    ${LIBDIR}/slic3r/Utils/PrintHost.hpp
         ${LIBDIR}/slic3r/Utils/Bonjour.cpp
         ${LIBDIR}/slic3r/Utils/Bonjour.hpp
         ${LIBDIR}/slic3r/Utils/PresetUpdater.cpp
    @@ -417,7 +417,6 @@ set(XS_XSP_FILES
         ${XSP_DIR}/Surface.xsp
         ${XSP_DIR}/SurfaceCollection.xsp
         ${XSP_DIR}/TriangleMesh.xsp
    -    ${XSP_DIR}/Utils_PrintHostFactory.xsp
         ${XSP_DIR}/Utils_PrintHost.xsp
         ${XSP_DIR}/Utils_PresetUpdater.xsp
         ${XSP_DIR}/AppController.xsp
    diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
    index 943db2a302..bf5f734ac4 100644
    --- a/xs/src/libslic3r/PrintConfig.cpp
    +++ b/xs/src/libslic3r/PrintConfig.cpp
    @@ -2144,9 +2144,6 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
             "standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid",
             "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start", 
             "seal_position", "vibration_limit", "bed_size", 
    -        // Maybe one day we will rename octoprint_host to print_host as it has been done in the upstream Slic3r.
    -        // Commenting this out fixes github issue #869 for now.
    -        // "octoprint_host",
             "print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe"
         };
     
    @@ -2156,7 +2153,6 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
         }
         
         if (! print_config_def.has(opt_key)) {
    -        //printf("Unknown option %s\n", opt_key.c_str());
             opt_key = "";
             return;
         }
    diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp
    index 997938a910..d6bd0e94c3 100644
    --- a/xs/src/perlglue.cpp
    +++ b/xs/src/perlglue.cpp
    @@ -67,7 +67,6 @@ REGISTER_CLASS(PresetUpdater, "PresetUpdater");
     REGISTER_CLASS(AppController, "AppController");
     REGISTER_CLASS(PrintController, "PrintController");
     REGISTER_CLASS(PrintHost, "PrintHost");
    -REGISTER_CLASS(PrintHostFactory, "PrintHostFactory");
     
     SV* ConfigBase__as_hash(ConfigBase* THIS)
     {
    diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp
    index 13b0ece5f5..bde4fdc348 100644
    --- a/xs/src/slic3r/GUI/Tab.cpp
    +++ b/xs/src/slic3r/GUI/Tab.cpp
    @@ -5,7 +5,6 @@
     #include "../../libslic3r/Utils.hpp"
     
     #include "slic3r/Utils/Http.hpp"
    -#include "slic3r/Utils/PrintHostFactory.hpp"
     #include "slic3r/Utils/PrintHost.hpp"
     #include "slic3r/Utils/Serial.hpp"
     #include "BonjourDialog.hpp"
    @@ -1550,8 +1549,8 @@ void TabPrinter::build()
     			sizer->Add(btn);
     
     			btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {
    -				PrintHost *host = PrintHostFactory::get_print_host(m_config);
    -				if (host == NULL) {
    +				std::unique_ptr host(PrintHost::get_print_host(m_config));
    +				if (! host) {
     					const auto text = wxString::Format("%s",
     						_(L("Could not get a valid Printer Host reference")));
     					show_error(this, text);
    @@ -1563,8 +1562,6 @@ void TabPrinter::build()
     				} else {
     					show_error(this, host->get_test_failed_msg(msg));
     				}
    -
    -				delete (host);
     			});
     
     			return sizer;
    @@ -1905,11 +1902,12 @@ void TabPrinter::update(){
     			m_serial_test_btn->Disable();
     	}
     
    -	PrintHost *host = PrintHostFactory::get_print_host(m_config);
    -	m_print_host_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test());
    -	m_printhost_browse_btn->Enable(host->have_auto_discovery());
    -	delete (host);
    -	
    +	{
    +		std::unique_ptr host(PrintHost::get_print_host(m_config));
    +		m_print_host_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test());
    +		m_printhost_browse_btn->Enable(host->has_auto_discovery());
    +	}
    +
     	bool have_multiple_extruders = m_extruders_count > 1;
     	get_field("toolchange_gcode")->toggle(have_multiple_extruders);
     	get_field("single_extruder_multi_material")->toggle(have_multiple_extruders);
    diff --git a/xs/src/slic3r/Utils/Duet.cpp b/xs/src/slic3r/Utils/Duet.cpp
    index 86573ff300..517f024864 100644
    --- a/xs/src/slic3r/Utils/Duet.cpp
    +++ b/xs/src/slic3r/Utils/Duet.cpp
    @@ -2,6 +2,7 @@
     #include "PrintHostSendDialog.hpp"
     
     #include 
    +#include 
     #include 
     #include 
     #include 
    @@ -31,6 +32,8 @@ Duet::Duet(DynamicPrintConfig *config) :
     	password(config->opt_string("printhost_apikey"))
     {}
     
    +Duet::~Duet() {}
    +
     bool Duet::test(wxString &msg) const
     {
     	bool connected = connect(msg);
    @@ -48,8 +51,7 @@ wxString Duet::get_test_ok_msg () const
     
     wxString Duet::get_test_failed_msg (wxString &msg) const
     {
    -	return wxString::Format("%s: %s",
    -						_(L("Could not connect to Duet")), msg);
    +	return wxString::Format("%s: %s", _(L("Could not connect to Duet")), msg);
     }
     
     bool Duet::send_gcode(const std::string &filename) const
    @@ -91,23 +93,17 @@ bool Duet::send_gcode(const std::string &filename) const
     		% upload_cmd;
     
     	auto http = Http::post(std::move(upload_cmd));
    -	http.postfield_add_file(filename)
    +	http.set_post_body(filename)
     		.on_complete([&](std::string body, unsigned status) {
     			BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
     			progress_dialog.Update(PROGRESS_RANGE);
     
     			int err_code = get_err_code_from_body(body);
    -			switch (err_code) {
    -				case 0:
    -					break;
    -				default:
    -					auto msg = format_error(body, L("Unknown error occured"), 0);
    -					GUI::show_error(&progress_dialog, std::move(msg));
    -					res = false;
    -					break;
    -			}
    -
    -			if (err_code == 0 && print) {
    +			if (err_code != 0) {
    +				auto msg = format_error(body, L("Unknown error occured"), 0);
    +				GUI::show_error(&progress_dialog, std::move(msg));
    +				res = false;
    +			} else if (print) {
     				wxString errormsg;
     				res = start_print(errormsg, upload_filepath.string());
     				if (!res) {
    @@ -139,7 +135,7 @@ bool Duet::send_gcode(const std::string &filename) const
     	return res;
     }
     
    -bool Duet::have_auto_discovery() const
    +bool Duet::has_auto_discovery() const
     {
     	return false;
     }
    @@ -228,12 +224,15 @@ std::string Duet::get_base_url() const
     
     std::string Duet::timestamp_str() const
     {
    +	enum { BUFFER_SIZE = 32 };
    +
     	auto t = std::time(nullptr);
     	auto tm = *std::localtime(&t);
    -	std::stringstream ss;
    -	ss << "time=" << std::put_time(&tm, "%Y-%d-%mT%H:%M:%S");
     
    -	return ss.str();
    +	char buffer[BUFFER_SIZE];
    +	std::strftime(buffer, BUFFER_SIZE, "%Y-%d-%mT%H:%M:%S", &tm);
    +
    +	return std::string(buffer);
     }
     
     wxString Duet::format_error(const std::string &body, const std::string &error, unsigned status)
    diff --git a/xs/src/slic3r/Utils/Duet.hpp b/xs/src/slic3r/Utils/Duet.hpp
    index 83ba0cbbbc..bc210d7a45 100644
    --- a/xs/src/slic3r/Utils/Duet.hpp
    +++ b/xs/src/slic3r/Utils/Duet.hpp
    @@ -17,18 +17,19 @@ class Duet : public PrintHost
     {
     public:
     	Duet(DynamicPrintConfig *config);
    +	virtual ~Duet();
     
     	bool test(wxString &curl_msg) const;
     	wxString get_test_ok_msg () const;
     	wxString get_test_failed_msg (wxString &msg) const;
     	// Send gcode file to duet, filename is expected to be in UTF-8
     	bool send_gcode(const std::string &filename) const;
    -	bool have_auto_discovery() const;
    +	bool has_auto_discovery() const;
     	bool can_test() const;
     private:
     	std::string host;
     	std::string password;
    -	
    +
     	std::string get_upload_url(const std::string &filename) const;
     	std::string get_connect_url() const;
     	std::string get_base_url() const;
    diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp
    index f5407b9fb6..a92e399a08 100644
    --- a/xs/src/slic3r/Utils/Http.cpp
    +++ b/xs/src/slic3r/Utils/Http.cpp
    @@ -62,7 +62,7 @@ struct Http::priv
     	static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp);
     
     	void form_add_file(const char *name, const fs::path &path, const char* filename);
    -	void postfield_add_file(const fs::path &path);
    +	void set_post_body(const fs::path &path);
     
     	std::string curl_error(CURLcode curlcode);
     	std::string body_size_error();
    @@ -190,14 +190,11 @@ void Http::priv::form_add_file(const char *name, const fs::path &path, const cha
     	}
     }
     
    -void Http::priv::postfield_add_file(const fs::path &path)
    +void Http::priv::set_post_body(const fs::path &path)
     {
    -	std::ifstream f (path.string());
    -	std::string file_content { std::istreambuf_iterator(f), std::istreambuf_iterator() };
    -	if (!postfields.empty()) {
    -		postfields += "&";
    -	}
    -	postfields += file_content;
    +	std::ifstream file(path.string());
    +	std::string file_content { std::istreambuf_iterator(file), std::istreambuf_iterator() };
    +	postfields = file_content;
     }
     
     std::string Http::priv::curl_error(CURLcode curlcode)
    @@ -356,9 +353,9 @@ Http& Http::form_add_file(const std::string &name, const fs::path &path, const s
     	return *this;
     }
     
    -Http& Http::postfield_add_file(const fs::path &path)
    +Http& Http::set_post_body(const fs::path &path)
     {
    -	if (p) { p->postfield_add_file(path);}
    +	if (p) { p->set_post_body(path);}
     	return *this;
     }
     
    diff --git a/xs/src/slic3r/Utils/Http.hpp b/xs/src/slic3r/Utils/Http.hpp
    index cf5712d966..f1302b0ed9 100644
    --- a/xs/src/slic3r/Utils/Http.hpp
    +++ b/xs/src/slic3r/Utils/Http.hpp
    @@ -73,8 +73,10 @@ public:
     	// Same as above except also override the file's filename with a custom one
     	Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename);
     
    -	// Add the file as POSTFIELD to the request, this can be used for hosts which do not support multipart requests
    -	Http& postfield_add_file(const boost::filesystem::path &path);
    +	// Set the file contents as a POST request body.
    +	// The data is used verbatim, it is not additionally encoded in any way.
    +	// This can be used for hosts which do not support multipart requests.
    +	Http& set_post_body(const boost::filesystem::path &path);
     
     	// Callback called on HTTP request complete
     	Http& on_complete(CompleteFn fn);
    diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp
    index c62f9b55c7..db86d76974 100644
    --- a/xs/src/slic3r/Utils/OctoPrint.cpp
    +++ b/xs/src/slic3r/Utils/OctoPrint.cpp
    @@ -19,6 +19,8 @@ OctoPrint::OctoPrint(DynamicPrintConfig *config) :
     	cafile(config->opt_string("printhost_cafile"))
     {}
     
    +OctoPrint::~OctoPrint() {}
    +
     bool OctoPrint::test(wxString &msg) const
     {
     	// Since the request is performed synchronously here,
    @@ -125,7 +127,7 @@ bool OctoPrint::send_gcode(const std::string &filename) const
     	return res;
     }
     
    -bool OctoPrint::have_auto_discovery() const
    +bool OctoPrint::has_auto_discovery() const
     {
     	return true;
     }
    diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp
    index aea2ba58f9..f6c4d58c87 100644
    --- a/xs/src/slic3r/Utils/OctoPrint.hpp
    +++ b/xs/src/slic3r/Utils/OctoPrint.hpp
    @@ -17,13 +17,14 @@ class OctoPrint : public PrintHost
     {
     public:
     	OctoPrint(DynamicPrintConfig *config);
    +	virtual ~OctoPrint();
     
     	bool test(wxString &curl_msg) const;
     	wxString get_test_ok_msg () const;
     	wxString get_test_failed_msg (wxString &msg) const;
     	// Send gcode file to octoprint, filename is expected to be in UTF-8
     	bool send_gcode(const std::string &filename) const;
    -	bool have_auto_discovery() const;
    +	bool has_auto_discovery() const;
     	bool can_test() const;
     private:
     	std::string host;
    diff --git a/xs/src/slic3r/Utils/PrintHostFactory.cpp b/xs/src/slic3r/Utils/PrintHost.cpp
    similarity index 73%
    rename from xs/src/slic3r/Utils/PrintHostFactory.cpp
    rename to xs/src/slic3r/Utils/PrintHost.cpp
    index 173c5d743c..dd72bae40f 100644
    --- a/xs/src/slic3r/Utils/PrintHostFactory.cpp
    +++ b/xs/src/slic3r/Utils/PrintHost.cpp
    @@ -1,4 +1,3 @@
    -#include "PrintHostFactory.hpp"
     #include "OctoPrint.hpp"
     #include "Duet.hpp"
     
    @@ -7,7 +6,9 @@
     namespace Slic3r {
     
     
    -PrintHost * PrintHostFactory::get_print_host(DynamicPrintConfig *config)
    +PrintHost::~PrintHost() {}
    +
    +PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
     {
     	PrintHostType kind = config->option>("host_type")->value;
     	if (kind == htOctoPrint) {
    @@ -15,7 +16,8 @@ PrintHost * PrintHostFactory::get_print_host(DynamicPrintConfig *config)
     	} else if (kind == htDuet) {
     		return new Duet(config);
     	}
    -	return NULL;
    +	return nullptr;
     }
     
    +
     }
    diff --git a/xs/src/slic3r/Utils/PrintHost.hpp b/xs/src/slic3r/Utils/PrintHost.hpp
    index 2047406356..bc828ea469 100644
    --- a/xs/src/slic3r/Utils/PrintHost.hpp
    +++ b/xs/src/slic3r/Utils/PrintHost.hpp
    @@ -1,6 +1,7 @@
     #ifndef slic3r_PrintHost_hpp_
     #define slic3r_PrintHost_hpp_
     
    +#include 
     #include 
     #include 
     
    @@ -13,14 +14,17 @@ class DynamicPrintConfig;
     class PrintHost
     {
     public:
    +	virtual ~PrintHost();
     
     	virtual bool test(wxString &curl_msg) const = 0;
     	virtual wxString get_test_ok_msg () const = 0;
     	virtual wxString get_test_failed_msg (wxString &msg) const = 0;
     	// Send gcode file to print host, filename is expected to be in UTF-8
     	virtual bool send_gcode(const std::string &filename) const = 0;
    -	virtual bool have_auto_discovery() const = 0;
    +	virtual bool has_auto_discovery() const = 0;
     	virtual bool can_test() const = 0;
    +
    +	static PrintHost* get_print_host(DynamicPrintConfig *config);
     };
     
     
    diff --git a/xs/src/slic3r/Utils/PrintHostFactory.hpp b/xs/src/slic3r/Utils/PrintHostFactory.hpp
    deleted file mode 100644
    index 4c9ff2bf24..0000000000
    --- a/xs/src/slic3r/Utils/PrintHostFactory.hpp
    +++ /dev/null
    @@ -1,24 +0,0 @@
    -#ifndef slic3r_PrintHostFactory_hpp_
    -#define slic3r_PrintHostFactory_hpp_
    -
    -#include 
    -#include 
    -
    -
    -namespace Slic3r {
    -
    -class DynamicPrintConfig;
    -class PrintHost;
    -
    -class PrintHostFactory
    -{
    -public:
    -	PrintHostFactory() {};
    -	~PrintHostFactory() {};
    -	static PrintHost * get_print_host(DynamicPrintConfig *config);
    -};
    -
    -
    -}
    -
    -#endif
    diff --git a/xs/src/slic3r/Utils/PrintHostSendDialog.cpp b/xs/src/slic3r/Utils/PrintHostSendDialog.cpp
    index b1dd86961f..c5d441f876 100644
    --- a/xs/src/slic3r/Utils/PrintHostSendDialog.cpp
    +++ b/xs/src/slic3r/Utils/PrintHostSendDialog.cpp
    @@ -36,9 +36,7 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr
     	wxString stem(path.stem().wstring());
     	txt_filename->SetSelection(0, stem.Length());
     
    -	if (!can_start_print) {
    -		box_print->Disable();
    -	}
    +	box_print->Enable(can_start_print);
     
     	Fit();
     }
    diff --git a/xs/src/slic3r/Utils/PrintHostSendDialog.hpp b/xs/src/slic3r/Utils/PrintHostSendDialog.hpp
    index 7d2040d976..dc4a8d6f7c 100644
    --- a/xs/src/slic3r/Utils/PrintHostSendDialog.hpp
    +++ b/xs/src/slic3r/Utils/PrintHostSendDialog.hpp
    @@ -22,14 +22,12 @@ namespace Slic3r {
     
     class PrintHostSendDialog : public GUI::MsgDialog
     {
    -
     private:
     	wxTextCtrl *txt_filename;
     	wxCheckBox *box_print;
     	bool can_start_print;
     
     public:
    -
     	PrintHostSendDialog(const boost::filesystem::path &path, bool can_start_print);
     	boost::filesystem::path filename() const;
     	bool print() const;
    diff --git a/xs/xsp/Utils_PrintHost.xsp b/xs/xsp/Utils_PrintHost.xsp
    index 0c3fea137c..59c09c4317 100644
    --- a/xs/xsp/Utils_PrintHost.xsp
    +++ b/xs/xsp/Utils_PrintHost.xsp
    @@ -7,4 +7,6 @@
     
     %name{Slic3r::PrintHost} class PrintHost {
     	bool send_gcode(std::string filename) const;
    +
    +	static PrintHost* get_print_host(DynamicPrintConfig *config);
     };
    diff --git a/xs/xsp/Utils_PrintHostFactory.xsp b/xs/xsp/Utils_PrintHostFactory.xsp
    deleted file mode 100644
    index 2b083c957d..0000000000
    --- a/xs/xsp/Utils_PrintHostFactory.xsp
    +++ /dev/null
    @@ -1,13 +0,0 @@
    -%module{Slic3r::XS};
    -
    -%{
    -#include 
    -#include "slic3r/Utils/PrintHostFactory.hpp"
    -%}
    -
    -%name{Slic3r::PrintHostFactory} class PrintHostFactory {
    -    PrintHostFactory();
    -    ~PrintHostFactory();
    -
    -   static PrintHost * get_print_host(DynamicPrintConfig *config);
    -};
    diff --git a/xs/xsp/my.map b/xs/xsp/my.map
    index aefe7b3454..ba20ee2362 100644
    --- a/xs/xsp/my.map
    +++ b/xs/xsp/my.map
    @@ -239,11 +239,7 @@ Ref 				O_OBJECT_SLIC3R_T
     PresetUpdater*              O_OBJECT_SLIC3R
     Ref          O_OBJECT_SLIC3R_T
     
    -PrintHostFactory*            O_OBJECT_SLIC3R
    -Ref        O_OBJECT_SLIC3R_T
    -Clone      O_OBJECT_SLIC3R_T
    -
    -PrintHost*                   O_OBJECT_SLIC3R
    +PrintHost*                  O_OBJECT_SLIC3R
     
     Axis                  T_UV
     ExtrusionLoopRole     T_UV
    
    From 9e6234fe3964696740caf3c9743076a5f43bf681 Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Tue, 21 Aug 2018 15:56:40 +0200
    Subject: [PATCH 109/119] Lay flat - limit number of active surfaces to 255 (to
     avoid problems with picking pass)
    
    ---
     xs/src/slic3r/GUI/GLGizmo.cpp | 7 ++++++-
     xs/src/slic3r/GUI/GLGizmo.hpp | 1 +
     2 files changed, 7 insertions(+), 1 deletion(-)
    
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index fd4d205fb3..9ef12c8139 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -709,7 +709,8 @@ void GLGizmoFlatten::update_planes()
             // We will calculate area of the polygon and discard ones that are too small
             // The limit is more forgiving in case the normal is in the direction of the coordinate axes
             const float minimal_area = (std::abs(normal.x) > 0.999f || std::abs(normal.y) > 0.999f || std::abs(normal.z) > 0.999f) ? 1.f : 20.f;
    -        float area = 0.f;
    +        float& area = m_planes[polygon_id].area;
    +        area = 0.f;
             for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula
                 area += polygon[i].x*polygon[i+1 < polygon.size() ? i+1 : 0 ].y - polygon[i+1 < polygon.size() ? i+1 : 0].x*polygon[i].y;
             area = std::abs(area/2.f);
    @@ -771,6 +772,10 @@ void GLGizmoFlatten::update_planes()
             }
         }
     
    +    // We'll sort the planes by area and only keep the 255 largest ones (because of the picking pass limitations):
    +    std::sort(m_planes.rbegin(), m_planes.rend(), [](const PlaneData& a, const PlaneData& b) { return a.area < b.area; });
    +    m_planes.resize(std::min((int)m_planes.size(), 255));
    +
         // Planes are finished - let's save what we calculated it from:
         m_source_data.bounding_boxes.clear();
         for (const auto& vol : m_model_object->volumes)
    diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
    index 2c82f73f37..aad31349c1 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.hpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.hpp
    @@ -161,6 +161,7 @@ private:
         struct PlaneData {
             std::vector vertices;
             Pointf3 normal;
    +        float area;
         };
         struct SourceDataSummary {
             std::vector bounding_boxes; // bounding boxes of convex hulls of individual volumes
    
    From 43f8f10445d46c582904eca8749116b115e0f39a Mon Sep 17 00:00:00 2001
    From: Martin Loidl 
    Date: Tue, 21 Aug 2018 22:56:29 +0200
    Subject: [PATCH 110/119] fixed timestamp for duet upload * Added missing time=
    
    ---
     xs/src/slic3r/Utils/Duet.cpp | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/xs/src/slic3r/Utils/Duet.cpp b/xs/src/slic3r/Utils/Duet.cpp
    index 517f024864..865d2b4187 100644
    --- a/xs/src/slic3r/Utils/Duet.cpp
    +++ b/xs/src/slic3r/Utils/Duet.cpp
    @@ -230,7 +230,7 @@ std::string Duet::timestamp_str() const
     	auto tm = *std::localtime(&t);
     
     	char buffer[BUFFER_SIZE];
    -	std::strftime(buffer, BUFFER_SIZE, "%Y-%d-%mT%H:%M:%S", &tm);
    +	std::strftime(buffer, BUFFER_SIZE, "time=%Y-%d-%mT%H:%M:%S", &tm);
     
     	return std::string(buffer);
     }
    
    From 0b210426065f02b46be2d0a2c62ee25435b272f3 Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Wed, 22 Aug 2018 14:02:32 +0200
    Subject: [PATCH 111/119] Lay flat minor bugfix (ObjectCutDialog called a
     changed function using the old signature)
    
    ---
     lib/Slic3r/GUI/Plater/ObjectCutDialog.pm | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm
    index 35aa28818b..26a6fdec32 100644
    --- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm
    +++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm
    @@ -137,7 +137,7 @@ sub new {
             # Adjust position / orientation of the split object halves.
             if ($self->{new_model_objects}{lower}) {
                 if ($self->{cut_options}{rotate_lower}) {
    -                $self->{new_model_objects}{lower}->rotate(PI, X);
    +                $self->{new_model_objects}{lower}->rotate(PI, Slic3r::Pointf3->new(1,0,0));
                     $self->{new_model_objects}{lower}->center_around_origin;  # align to Z = 0
                 }
             }
    
    From 60a0375ff99ae2d7608006ebda19d41bcf517b86 Mon Sep 17 00:00:00 2001
    From: Vojtech Kral 
    Date: Tue, 7 Aug 2018 11:28:39 +0200
    Subject: [PATCH 112/119] Firmware updater: Fix a race condition avrdude:
     Handle OOM with configurable handler
    
    ---
     xs/src/avrdude/avrdude-slic3r.cpp    | 95 ++++++++++++++++++++++------
     xs/src/avrdude/avrdude-slic3r.hpp    | 15 ++++-
     xs/src/avrdude/avrdude.h             |  6 ++
     xs/src/avrdude/avrpart.c             | 34 ++++++----
     xs/src/avrdude/buspirate.c           |  7 +-
     xs/src/avrdude/butterfly.c           |  7 +-
     xs/src/avrdude/lexer.c               |  8 ++-
     xs/src/avrdude/main.c                | 27 ++++++++
     xs/src/avrdude/pgm.c                 |  7 +-
     xs/src/avrdude/ser_win32.c           |  9 +--
     xs/src/avrdude/stk500v2.c            |  7 +-
     xs/src/avrdude/update.c              | 25 +++++---
     xs/src/avrdude/wiring.c              |  7 +-
     xs/src/slic3r/GUI/FirmwareDialog.cpp |  6 +-
     14 files changed, 193 insertions(+), 67 deletions(-)
    
    diff --git a/xs/src/avrdude/avrdude-slic3r.cpp b/xs/src/avrdude/avrdude-slic3r.cpp
    index 0577fe6d07..3037f52848 100644
    --- a/xs/src/avrdude/avrdude-slic3r.cpp
    +++ b/xs/src/avrdude/avrdude-slic3r.cpp
    @@ -2,6 +2,10 @@
     
     #include 
     #include 
    +#include 
    +#include 
    +#include 
    +#include 
     
     extern "C" {
     #include "ac_cfg.h"
    @@ -28,6 +32,11 @@ static void avrdude_progress_handler_closure(const char *task, unsigned progress
     	(*progress_fn)(task, progress);
     }
     
    +static void avrdude_oom_handler(const char *context, void *user_p)
    +{
    +	throw std::bad_alloc();
    +}
    +
     
     // Private
     
    @@ -47,16 +56,22 @@ struct AvrDude::priv
     
     	priv(std::string &&sys_config) : sys_config(sys_config) {}
     
    +	void set_handlers();
    +	void unset_handlers();
     	int run_one(const std::vector &args);
     	int run();
    +
    +	struct HandlerGuard
    +	{
    +		priv &p;
    +
    +		HandlerGuard(priv &p) : p(p) { p.set_handlers(); }
    +		~HandlerGuard() { p.unset_handlers(); }
    +	};
     };
     
    -int AvrDude::priv::run_one(const std::vector &args) {
    -	std::vector c_args {{ const_cast(PACKAGE_NAME) }};
    -	for (const auto &arg : args) {
    -		c_args.push_back(const_cast(arg.data()));
    -	}
    -
    +void AvrDude::priv::set_handlers()
    +{
     	if (message_fn) {
     		::avrdude_message_handler_set(avrdude_message_handler_closure, reinterpret_cast(&message_fn));
     	} else {
    @@ -69,10 +84,27 @@ int AvrDude::priv::run_one(const std::vector &args) {
     		::avrdude_progress_handler_set(nullptr, nullptr);
     	}
     
    -	const auto res = ::avrdude_main(static_cast(c_args.size()), c_args.data(), sys_config.c_str());
    +	::avrdude_oom_handler_set(avrdude_oom_handler, nullptr);
    +}
     
    +void AvrDude::priv::unset_handlers()
    +{
     	::avrdude_message_handler_set(nullptr, nullptr);
     	::avrdude_progress_handler_set(nullptr, nullptr);
    +	::avrdude_oom_handler_set(nullptr, nullptr);
    +}
    +
    +
    +int AvrDude::priv::run_one(const std::vector &args) {
    +	std::vector c_args {{ const_cast(PACKAGE_NAME) }};
    +	for (const auto &arg : args) {
    +		c_args.push_back(const_cast(arg.data()));
    +	}
    +
    +	HandlerGuard guard(*this);
    +
    +	const auto res = ::avrdude_main(static_cast(c_args.size()), c_args.data(), sys_config.c_str());
    +
     	return res;
     }
     
    @@ -134,7 +166,7 @@ AvrDude& AvrDude::on_complete(CompleteFn fn)
     
     int AvrDude::run_sync()
     {
    -	return p->run();
    +	return p ? p->run() : -1;
     }
     
     AvrDude::Ptr AvrDude::run()
    @@ -143,19 +175,46 @@ AvrDude::Ptr AvrDude::run()
     
     	if (self->p) {
     		auto avrdude_thread = std::thread([self]() {
    -			bool cancel = false;
    -			int res = -1;
    +			try {
    +				if (self->p->run_fn) {
    +					self->p->run_fn(self);
    +				}
     
    -			if (self->p->run_fn) {
    -				self->p->run_fn();
    -			}
    +				if (! self->p->cancelled) {
    +					self->p->exit_code = self->p->run();
    +				}
     
    -			if (! self->p->cancelled) {
    -				self->p->exit_code = self->p->run();
    -			}
    +				if (self->p->complete_fn) {
    +					self->p->complete_fn();
    +				}
    +			} catch (const std::exception &ex) {
    +				self->p->exit_code = EXIT_EXCEPTION;
     
    -			if (self->p->complete_fn) {
    -				self->p->complete_fn();
    +				static const char *msg = "An exception was thrown in the background thread:\n";
    +
    +				const char *what = ex.what();
    +				auto &message_fn = self->p->message_fn;
    +				if (message_fn) {
    +					message_fn(msg, sizeof(msg));
    +					message_fn(what, std::strlen(what));
    +					message_fn("\n", 1);
    +				}
    +
    +				if (self->p->complete_fn) {
    +					self->p->complete_fn();
    +				}
    +			} catch (...) {
    +				self->p->exit_code = EXIT_EXCEPTION;
    +
    +				static const char *msg = "An unkown exception was thrown in the background thread.\n";
    +
    +				if (self->p->message_fn) {
    +					self->p->message_fn(msg, sizeof(msg));
    +				}
    +
    +				if (self->p->complete_fn) {
    +					self->p->complete_fn();
    +				}
     			}
     		});
     
    diff --git a/xs/src/avrdude/avrdude-slic3r.hpp b/xs/src/avrdude/avrdude-slic3r.hpp
    index 86e097034c..754e1e3459 100644
    --- a/xs/src/avrdude/avrdude-slic3r.hpp
    +++ b/xs/src/avrdude/avrdude-slic3r.hpp
    @@ -11,8 +11,13 @@ namespace Slic3r {
     class AvrDude
     {
     public:
    +	enum {
    +		EXIT_SUCCEESS   = 0,
    +		EXIT_EXCEPTION  = -1000,
    +	};
    +
     	typedef std::shared_ptr Ptr;
    -	typedef std::function RunFn;
    +	typedef std::function RunFn;
     	typedef std::function MessageFn;
     	typedef std::function ProgressFn;
     	typedef std::function CompleteFn;
    @@ -49,10 +54,18 @@ public:
     	// This has no effect when using run_sync().
     	AvrDude& on_complete(CompleteFn fn);
     
    +	// Perform AvrDude invocation(s) synchronously on the current thread
     	int run_sync();
    +
    +	// Perform AvrDude invocation(s) on a background thread.
    +	// Current instance is moved into a shared_ptr which is returned (and also passed in on_run, if any).
     	Ptr run();
     
    +	// Cancel current operation
     	void cancel();
    +
    +	// If there is a background thread and it is joinable, join() it,
    +	// that is, wait for it to finish.
     	void join();
     
     	bool cancelled();          // Whether avrdude run was cancelled
    diff --git a/xs/src/avrdude/avrdude.h b/xs/src/avrdude/avrdude.h
    index 9f724433f2..f4c92a75d3 100644
    --- a/xs/src/avrdude/avrdude.h
    +++ b/xs/src/avrdude/avrdude.h
    @@ -39,6 +39,12 @@ typedef void (*avrdude_progress_handler_t)(const char *task, unsigned progress,
     void avrdude_progress_handler_set(avrdude_progress_handler_t newhandler, void *user_p);
     void avrdude_progress_external(const char *task, unsigned progress);
     
    +// OOM handler
    +typedef void (*avrdude_oom_handler_t)(const char *context, void *user_p);
    +void avrdude_oom_handler_set(avrdude_oom_handler_t newhandler, void *user_p);
    +void avrdude_oom(const char *context);
    +
    +
     // Cancellation
     void avrdude_cancel();
     
    diff --git a/xs/src/avrdude/avrpart.c b/xs/src/avrdude/avrpart.c
    index b04851ac14..d0bb951ee8 100644
    --- a/xs/src/avrdude/avrpart.c
    +++ b/xs/src/avrdude/avrpart.c
    @@ -36,8 +36,9 @@ OPCODE * avr_new_opcode(void)
     
       m = (OPCODE *)malloc(sizeof(*m));
       if (m == NULL) {
    -    avrdude_message(MSG_INFO, "avr_new_opcode(): out of memory\n");
    -    exit(1);
    +    // avrdude_message(MSG_INFO, "avr_new_opcode(): out of memory\n");
    +    // exit(1);
    +    avrdude_oom("avr_new_opcode(): out of memory\n");
       }
     
       memset(m, 0, sizeof(*m));
    @@ -56,8 +57,9 @@ static OPCODE * avr_dup_opcode(OPCODE * op)
     
       m = (OPCODE *)malloc(sizeof(*m));
       if (m == NULL) {
    -    avrdude_message(MSG_INFO, "avr_dup_opcode(): out of memory\n");
    -    exit(1);
    +    // avrdude_message(MSG_INFO, "avr_dup_opcode(): out of memory\n");
    +    // exit(1);
    +    avrdude_oom("avr_dup_opcode(): out of memory\n");
       }
     
       memcpy(m, op, sizeof(*m));
    @@ -249,8 +251,9 @@ AVRMEM * avr_new_memtype(void)
     
       m = (AVRMEM *)malloc(sizeof(*m));
       if (m == NULL) {
    -    avrdude_message(MSG_INFO, "avr_new_memtype(): out of memory\n");
    -    exit(1);
    +    // avrdude_message(MSG_INFO, "avr_new_memtype(): out of memory\n");
    +    // exit(1);
    +    avrdude_oom("avr_new_memtype(): out of memory\n");
       }
     
       memset(m, 0, sizeof(*m));
    @@ -300,9 +303,10 @@ AVRMEM * avr_dup_mem(AVRMEM * m)
       if (m->buf != NULL) {
         n->buf = (unsigned char *)malloc(n->size);
         if (n->buf == NULL) {
    -      avrdude_message(MSG_INFO, "avr_dup_mem(): out of memory (memsize=%d)\n",
    -                      n->size);
    -      exit(1);
    +      // avrdude_message(MSG_INFO, "avr_dup_mem(): out of memory (memsize=%d)\n",
    +      //                 n->size);
    +      // exit(1);
    +      avrdude_oom("avr_dup_mem(): out of memory");
         }
         memcpy(n->buf, m->buf, n->size);
       }
    @@ -310,9 +314,10 @@ AVRMEM * avr_dup_mem(AVRMEM * m)
       if (m->tags != NULL) {
         n->tags = (unsigned char *)malloc(n->size);
         if (n->tags == NULL) {
    -      avrdude_message(MSG_INFO, "avr_dup_mem(): out of memory (memsize=%d)\n",
    -                      n->size);
    -      exit(1);
    +      // avrdude_message(MSG_INFO, "avr_dup_mem(): out of memory (memsize=%d)\n",
    +      //                 n->size);
    +      // exit(1);
    +      avrdude_oom("avr_dup_mem(): out of memory");
         }
         memcpy(n->tags, m->tags, n->size);
       }
    @@ -441,8 +446,9 @@ AVRPART * avr_new_part(void)
     
       p = (AVRPART *)malloc(sizeof(AVRPART));
       if (p == NULL) {
    -    avrdude_message(MSG_INFO, "new_part(): out of memory\n");
    -    exit(1);
    +    // avrdude_message(MSG_INFO, "new_part(): out of memory\n");
    +    // exit(1);
    +    avrdude_oom("new_part(): out of memory\n");
       }
     
       memset(p, 0, sizeof(*p));
    diff --git a/xs/src/avrdude/buspirate.c b/xs/src/avrdude/buspirate.c
    index 435c4ce531..5875d42833 100644
    --- a/xs/src/avrdude/buspirate.c
    +++ b/xs/src/avrdude/buspirate.c
    @@ -1135,9 +1135,10 @@ static void buspirate_setup(struct programmer_t *pgm)
     {
     	/* Allocate private data */
     	if ((pgm->cookie = calloc(1, sizeof(struct pdata))) == 0) {
    -		avrdude_message(MSG_INFO, "%s: buspirate_initpgm(): Out of memory allocating private data\n",
    -		                progname);
    -		exit(1);
    +		// avrdude_message(MSG_INFO, "%s: buspirate_initpgm(): Out of memory allocating private data\n",
    +		//                 progname);
    +		// exit(1);
    +		avrdude_oom("buspirate_initpgm(): Out of memory allocating private data\n");
     	}
     	PDATA(pgm)->serial_recv_timeout = 100;
     }
    diff --git a/xs/src/avrdude/butterfly.c b/xs/src/avrdude/butterfly.c
    index de9a3175f5..beb5e04dea 100644
    --- a/xs/src/avrdude/butterfly.c
    +++ b/xs/src/avrdude/butterfly.c
    @@ -63,9 +63,10 @@ struct pdata
     static void butterfly_setup(PROGRAMMER * pgm)
     {
       if ((pgm->cookie = malloc(sizeof(struct pdata))) == 0) {
    -    avrdude_message(MSG_INFO, "%s: butterfly_setup(): Out of memory allocating private data\n",
    -                    progname);
    -    exit(1);
    +    // avrdude_message(MSG_INFO, "%s: butterfly_setup(): Out of memory allocating private data\n",
    +    //                 progname);
    +    // exit(1);
    +    avrdude_oom("butterfly_setup(): Out of memory allocating private data\n");
       }
       memset(pgm->cookie, 0, sizeof(struct pdata));
     }
    diff --git a/xs/src/avrdude/lexer.c b/xs/src/avrdude/lexer.c
    index 93249a9ab4..f2d8adb4bc 100644
    --- a/xs/src/avrdude/lexer.c
    +++ b/xs/src/avrdude/lexer.c
    @@ -2834,7 +2834,8 @@ YY_BUFFER_STATE yy_scan_bytes  (const char * yybytes, int  _yybytes_len )
     	n = (yy_size_t) (_yybytes_len + 2);
     	buf = (char *) yyalloc( n  );
     	if ( ! buf )
    -		YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" );
    +		// YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" );
    +		avrdude_oom("out of dynamic memory in yy_scan_bytes()");
     
     	for ( i = 0; i < _yybytes_len; ++i )
     		buf[i] = yybytes[i];
    @@ -2859,8 +2860,9 @@ YY_BUFFER_STATE yy_scan_bytes  (const char * yybytes, int  _yybytes_len )
     
     static void yynoreturn yy_fatal_error (const char* msg )
     {
    -			fprintf( stderr, "%s\n", msg );
    -	exit( YY_EXIT_FAILURE );
    +	fprintf( stderr, "%s\n", msg );
    +	// exit( YY_EXIT_FAILURE );
    +	abort();
     }
     
     /* Redefine yyless() so it works in section 3 code. */
    diff --git a/xs/src/avrdude/main.c b/xs/src/avrdude/main.c
    index 5d73403b09..ebda0ba19b 100644
    --- a/xs/src/avrdude/main.c
    +++ b/xs/src/avrdude/main.c
    @@ -144,6 +144,33 @@ void avrdude_progress_external(const char *task, unsigned progress)
         avrdude_progress_handler(task, progress, avrdude_progress_handler_user_p);
     }
     
    +static void avrdude_oom_handler_null(const char *context, void *user_p)
    +{
    +    // Output a message and just exit
    +    fputs("avrdude: Out of memory: ", stderr);
    +    fputs(context, stderr);
    +    exit(99);
    +}
    +
    +static void *avrdude_oom_handler_user_p = NULL;
    +static avrdude_oom_handler_t avrdude_oom_handler = avrdude_oom_handler_null;
    +
    +void avrdude_oom_handler_set(avrdude_oom_handler_t newhandler, void *user_p)
    +{
    +    if (newhandler != NULL) {
    +        avrdude_oom_handler = newhandler;
    +        avrdude_oom_handler_user_p = user_p;
    +    } else {
    +        avrdude_oom_handler = avrdude_oom_handler_null;
    +        avrdude_oom_handler_user_p = NULL;
    +    }
    +}
    +
    +void avrdude_oom(const char *context)
    +{
    +    avrdude_oom_handler(context, avrdude_oom_handler_user_p);
    +}
    +
     void avrdude_cancel()
     {
         cancel_flag = true;
    diff --git a/xs/src/avrdude/pgm.c b/xs/src/avrdude/pgm.c
    index 851ac5a87f..b8a93f1047 100644
    --- a/xs/src/avrdude/pgm.c
    +++ b/xs/src/avrdude/pgm.c
    @@ -172,9 +172,10 @@ PROGRAMMER * pgm_dup(const PROGRAMMER * const src)
       for (ln = lfirst(src->usbpid); ln; ln = lnext(ln)) {
         int *ip = malloc(sizeof(int));
         if (ip == NULL) {
    -      avrdude_message(MSG_INFO, "%s: out of memory allocating programmer structure\n",
    -              progname);
    -      exit(1);
    +      // avrdude_message(MSG_INFO, "%s: out of memory allocating programmer structure\n",
    +      //         progname);
    +      // exit(1);
    +      avrdude_oom("out of memory allocating programmer structure\n");
         }
         *ip = *(int *) ldata(ln);
         ladd(pgm->usbpid, ip);
    diff --git a/xs/src/avrdude/ser_win32.c b/xs/src/avrdude/ser_win32.c
    index 3a05cfa905..4e1713128c 100644
    --- a/xs/src/avrdude/ser_win32.c
    +++ b/xs/src/avrdude/ser_win32.c
    @@ -246,10 +246,11 @@ static int ser_open(char * port, union pinfo pinfo, union filedescriptor *fdp)
     	    newname = malloc(strlen("\\\\.\\") + strlen(port) + 1);
     
     	    if (newname == 0) {
    -		avrdude_message(MSG_INFO, "%s: ser_open(): out of memory\n",
    -                                progname);
    -		exit(1);
    -	    }
    +		// avrdude_message(MSG_INFO, "%s: ser_open(): out of memory\n",
    +		//                         progname);
    +		// exit(1);
    +			avrdude_oom("ser_open(): out of memory\n");
    +		}
     	    strcpy(newname, "\\\\.\\");
     	    strcat(newname, port);
     
    diff --git a/xs/src/avrdude/stk500v2.c b/xs/src/avrdude/stk500v2.c
    index 4d62640c0d..691152b460 100644
    --- a/xs/src/avrdude/stk500v2.c
    +++ b/xs/src/avrdude/stk500v2.c
    @@ -295,9 +295,10 @@ static int stk600_xprog_program_enable(PROGRAMMER * pgm, AVRPART * p);
     void stk500v2_setup(PROGRAMMER * pgm)
     {
       if ((pgm->cookie = malloc(sizeof(struct pdata))) == 0) {
    -    avrdude_message(MSG_INFO, "%s: stk500v2_setup(): Out of memory allocating private data\n",
    -                    progname);
    -    exit(1);
    +    // avrdude_message(MSG_INFO, "%s: stk500v2_setup(): Out of memory allocating private data\n",
    +    //                 progname);
    +    // exit(1);
    +    avrdude_oom("stk500v2_setup(): Out of memory allocating private data\n");
       }
       memset(pgm->cookie, 0, sizeof(struct pdata));
       PDATA(pgm)->command_sequence = 1;
    diff --git a/xs/src/avrdude/update.c b/xs/src/avrdude/update.c
    index 417cbf71d4..a255ab4f9c 100644
    --- a/xs/src/avrdude/update.c
    +++ b/xs/src/avrdude/update.c
    @@ -38,8 +38,9 @@ UPDATE * parse_op(char * s)
     
       upd = (UPDATE *)malloc(sizeof(UPDATE));
       if (upd == NULL) {
    -    avrdude_message(MSG_INFO, "%s: out of memory\n", progname);
    -    exit(1);
    +    // avrdude_message(MSG_INFO, "%s: out of memory\n", progname);
    +    // exit(1);
    +    avrdude_oom("parse_op: out of memory\n");
       }
     
       i = 0;
    @@ -53,8 +54,9 @@ UPDATE * parse_op(char * s)
         upd->op = DEVICE_WRITE;
         upd->filename = (char *)malloc(strlen(buf) + 1);
         if (upd->filename == NULL) {
    -        avrdude_message(MSG_INFO, "%s: out of memory\n", progname);
    -        exit(1);
    +        // avrdude_message(MSG_INFO, "%s: out of memory\n", progname);
    +        // exit(1);
    +        avrdude_oom("parse_op: out of memory\n");
         }
         strcpy(upd->filename, buf);
         upd->format = FMT_AUTO;
    @@ -63,8 +65,9 @@ UPDATE * parse_op(char * s)
     
       upd->memtype = (char *)malloc(strlen(buf)+1);
       if (upd->memtype == NULL) {
    -    avrdude_message(MSG_INFO, "%s: out of memory\n", progname);
    -    exit(1);
    +    // avrdude_message(MSG_INFO, "%s: out of memory\n", progname);
    +    // exit(1);
    +    avrdude_oom("parse_op: out of memory\n");
       }
       strcpy(upd->memtype, buf);
     
    @@ -179,8 +182,9 @@ UPDATE * dup_update(UPDATE * upd)
     
       u = (UPDATE *)malloc(sizeof(UPDATE));
       if (u == NULL) {
    -    avrdude_message(MSG_INFO, "%s: out of memory\n", progname);
    -    exit(1);
    +    // avrdude_message(MSG_INFO, "%s: out of memory\n", progname);
    +    // exit(1);
    +    avrdude_oom("dup_update: out of memory\n");
       }
     
       memcpy(u, upd, sizeof(UPDATE));
    @@ -200,8 +204,9 @@ UPDATE * new_update(int op, char * memtype, int filefmt, char * filename, unsign
     
       u = (UPDATE *)malloc(sizeof(UPDATE));
       if (u == NULL) {
    -    avrdude_message(MSG_INFO, "%s: out of memory\n", progname);
    -    exit(1);
    +    // avrdude_message(MSG_INFO, "%s: out of memory\n", progname);
    +    // exit(1);
    +    avrdude_oom("new_update: out of memory\n");
       }
     
       u->memtype = strdup(memtype);
    diff --git a/xs/src/avrdude/wiring.c b/xs/src/avrdude/wiring.c
    index 3954597622..562a3f17cb 100644
    --- a/xs/src/avrdude/wiring.c
    +++ b/xs/src/avrdude/wiring.c
    @@ -85,9 +85,10 @@ static void wiring_setup(PROGRAMMER * pgm)
        * Now prepare our data
        */
       if ((mycookie = malloc(sizeof(struct wiringpdata))) == 0) {
    -    avrdude_message(MSG_INFO, "%s: wiring_setup(): Out of memory allocating private data\n",
    -                    progname);
    -    exit(1);
    +    // avrdude_message(MSG_INFO, "%s: wiring_setup(): Out of memory allocating private data\n",
    +    //                 progname);
    +    // exit(1);
    +    avrdude_oom("wiring_setup(): Out of memory allocating private data\n");
       }
       memset(mycookie, 0, sizeof(struct wiringpdata));
       WIRINGPDATA(mycookie)->snoozetime = 0;
    diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp
    index 77e70c49b5..d0cd9f8cf6 100644
    --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp
    +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp
    @@ -551,8 +551,10 @@ void FirmwareDialog::priv::perform_upload()
     	// because the dialog ensures it doesn't exit before the background thread is done.
     	auto q = this->q;
     
    -	this->avrdude = avrdude
    -		.on_run([this]() {
    +	avrdude
    +		.on_run([this](AvrDude::Ptr avrdude) {
    +			this->avrdude = std::move(avrdude);
    +
     			try {
     				switch (this->hex_file.device) {
     				case HexFile::DEV_MK3:
    
    From e8aafd3c830cca2df628a2d330640f825e0067c1 Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Fri, 24 Aug 2018 11:46:54 +0200
    Subject: [PATCH 113/119] Lay flat - simple rejection of very small surfaces
    
    ---
     xs/src/slic3r/GUI/GLGizmo.cpp | 6 ++++++
     1 file changed, 6 insertions(+)
    
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 9ef12c8139..bbd8f44eb5 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -680,6 +680,12 @@ void GLGizmoFlatten::update_planes()
                 }
             }
             m_planes.back().normal = Pointf3(normal_ptr->x, normal_ptr->y, normal_ptr->z);
    +
    +        // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected anyway):
    +        if (m_planes.back().vertices.size() == 3 &&
    +             ( m_planes.back().vertices[0].distance_to(m_planes.back().vertices[1]) < 1.f
    +            || m_planes.back().vertices[0].distance_to(m_planes.back().vertices[2]) < 1.f))
    +            m_planes.pop_back();
         }
     
         // Now we'll go through all the polygons, transform the points into xy plane to process them:
    
    From be3b8e98daab21fda63bf126a1bcd6d80869706b Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Fri, 24 Aug 2018 12:46:32 +0200
    Subject: [PATCH 114/119] Edited captions and default values of the new
     parameters (initial loading and unloading speed)
    
    ---
     xs/src/libslic3r/PrintConfig.cpp | 8 ++++----
     1 file changed, 4 insertions(+), 4 deletions(-)
    
    diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
    index 860283fbdf..c2baba027e 100644
    --- a/xs/src/libslic3r/PrintConfig.cpp
    +++ b/xs/src/libslic3r/PrintConfig.cpp
    @@ -474,12 +474,12 @@ PrintConfigDef::PrintConfigDef()
         def->default_value = new ConfigOptionFloats { 28. };
     
         def = this->add("filament_loading_speed_start", coFloats);
    -    def->label = L("EXPERIMENTAL: Loading speed at the start");
    +    def->label = L("Loading speed at the start");
         def->tooltip = L("Speed used at the very beginning of loading phase. ");
         def->sidetext = L("mm/s");
         def->cli = "filament-loading-speed-start=f@";
         def->min = 0;
    -    def->default_value = new ConfigOptionFloats { 9. };
    +    def->default_value = new ConfigOptionFloats { 3. };
     
         def = this->add("filament_unloading_speed", coFloats);
         def->label = L("Unloading speed");
    @@ -491,12 +491,12 @@ PrintConfigDef::PrintConfigDef()
         def->default_value = new ConfigOptionFloats { 90. };
     
         def = this->add("filament_unloading_speed_start", coFloats);
    -    def->label = L("EXPERIMENTAL: Unloading speed at the start");
    +    def->label = L("Unloading speed at the start");
         def->tooltip = L("Speed used for unloading the tip of the filament immediately after ramming. ");
         def->sidetext = L("mm/s");
         def->cli = "filament-unloading-speed-start=f@";
         def->min = 0;
    -    def->default_value = new ConfigOptionFloats { 83. };
    +    def->default_value = new ConfigOptionFloats { 100. };
     
         def = this->add("filament_toolchange_delay", coFloats);
         def->label = L("Delay after unloading");
    
    From a4176ef9338e7a1984fd983661351275c7ff19f1 Mon Sep 17 00:00:00 2001
    From: Lukas Matena 
    Date: Fri, 24 Aug 2018 16:52:06 +0200
    Subject: [PATCH 115/119] Bugfix - dialog that changes number of copies deleted
     the object when cancelled
    
    ---
     lib/Slic3r/GUI/Plater.pm | 5 +++--
     1 file changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
    index dbdf0be274..cae167f286 100644
    --- a/lib/Slic3r/GUI/Plater.pm
    +++ b/lib/Slic3r/GUI/Plater.pm
    @@ -995,9 +995,10 @@ sub set_number_of_copies {
         my $model_object = $self->{model}->objects->[$obj_idx];
         
         # prompt user
    -    my $copies = Wx::GetNumberFromUser("", L("Enter the number of copies of the selected object:"), L("Copies"), $model_object->instances_count, 0, 1000, $self);
    +    my $copies = -1;
    +    $copies = Wx::GetNumberFromUser("", L("Enter the number of copies of the selected object:"), L("Copies"), $model_object->instances_count, 0, 1000, $self);
         my $diff = $copies - $model_object->instances_count;
    -    if ($diff == 0) {
    +    if ($diff == 0 || $copies == -1) {
             # no variation
             $self->resume_background_process;
         } elsif ($diff > 0) {
    
    From 78a7104994b671e622c6e2bb00718d3c0bd6fa60 Mon Sep 17 00:00:00 2001
    From: bubnikv 
    Date: Sat, 25 Aug 2018 22:09:55 +0200
    Subject: [PATCH 116/119] Changed the wording of "Purge into this object's ..."
     to "Wipe ..."
    
    ---
     xs/src/libslic3r/PrintConfig.cpp | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
    index b582b501bc..f9f0b2056a 100644
    --- a/xs/src/libslic3r/PrintConfig.cpp
    +++ b/xs/src/libslic3r/PrintConfig.cpp
    @@ -2059,7 +2059,7 @@ PrintConfigDef::PrintConfigDef()
     
         def = this->add("wipe_into_infill", coBool);
         def->category = L("Extruders");
    -    def->label = L("Purge into this object's infill");
    +    def->label = L("Wipe into this object's infill");
         def->tooltip = L("Purging after toolchange will done inside this object's infills. "
                          "This lowers the amount of waste but may result in longer print time "
                          " due to additional travel moves.");
    @@ -2068,7 +2068,7 @@ PrintConfigDef::PrintConfigDef()
     
         def = this->add("wipe_into_objects", coBool);
         def->category = L("Extruders");
    -    def->label = L("Purge into this object");
    +    def->label = L("Wipe into this object");
         def->tooltip = L("Object will be used to purge the nozzle after a toolchange to save material "
                          "that would otherwise end up in the wipe tower and decrease print time. "
                          "Colours of the objects will be mixed as a result.");
    
    From 4522811f5bae9ca3a66270ad7f51ac39e0cb6e47 Mon Sep 17 00:00:00 2001
    From: bubnikv 
    Date: Sat, 25 Aug 2018 22:11:04 +0200
    Subject: [PATCH 117/119] Bumped up the version number to 1.41.0-beta2
    
    ---
     xs/src/libslic3r/libslic3r.h | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h
    index 34f61cb127..6db60440b3 100644
    --- a/xs/src/libslic3r/libslic3r.h
    +++ b/xs/src/libslic3r/libslic3r.h
    @@ -14,7 +14,7 @@
     #include 
     
     #define SLIC3R_FORK_NAME "Slic3r Prusa Edition"
    -#define SLIC3R_VERSION "1.41.0-beta"
    +#define SLIC3R_VERSION "1.41.0-beta2"
     #define SLIC3R_BUILD "UNKNOWN"
     
     typedef int32_t coord_t;
    
    From eb9f4ee7777f96de59b132a50fce1fb0bd369eb3 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Mon, 27 Aug 2018 14:54:20 +0200
    Subject: [PATCH 118/119] Fixed moving center of rotate gizmo
    
    ---
     xs/src/slic3r/GUI/GLCanvas3D.cpp | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index ea9fd90864..062b57b494 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -3297,7 +3297,7 @@ BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const
         {
             for (const GLVolume* volume : selected_volumes)
             {
    -            bb.merge(volume->transformed_convex_hull_bounding_box());
    +            bb.merge(volume->transformed_bounding_box());
             }
         }
     
    
    From 5ee106fbf9baf114619f2622ef9e991c73a09667 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Wed, 29 Aug 2018 08:42:42 +0200
    Subject: [PATCH 119/119] Fixed transformation center for objects imported from
     3mf files
    
    ---
     xs/src/libslic3r/Format/3mf.cpp | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp
    index 5de1d26c5f..945bb1f868 100644
    --- a/xs/src/libslic3r/Format/3mf.cpp
    +++ b/xs/src/libslic3r/Format/3mf.cpp
    @@ -603,6 +603,8 @@ namespace Slic3r {
     
                 if (!_generate_volumes(*object.second, obj_geometry->second, *volumes_ptr))
                     return false;
    +
    +            object.second->center_around_origin();
             }
     
             // fixes the min z of the model if negative